
/* ===== tweaks-panel.jsx ===== */
// @ds-adherence-ignore -- omelette starter scaffold (raw elements/hex/px by design)

/* BEGIN USAGE */
// tweaks-panel.jsx
// Reusable Tweaks shell + form-control helpers.
// Exports (to window): useTweaks, TweaksPanel, TweakSection, TweakRow, TweakSlider,
//   TweakToggle, TweakRadio, TweakSelect, TweakText, TweakNumber, TweakColor, TweakButton.
//
// Owns the host protocol (listens for __activate_edit_mode / __deactivate_edit_mode,
// posts __edit_mode_available / __edit_mode_set_keys / __edit_mode_dismissed) so
// individual prototypes don't re-roll it. Ships a consistent set of controls so you
// don't hand-draw <input type="range">, segmented radios, steppers, etc.
//
// Usage (in an HTML file that loads React + Babel):
//
//   const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
//     "primaryColor": "#D97757",
//     "palette": ["#D97757", "#29261b", "#f6f4ef"],
//     "fontSize": 16,
//     "density": "regular",
//     "dark": false
//   }/*EDITMODE-END*/;
//
//   function App() {
//     const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
//     return (
//       <div style={{ fontSize: t.fontSize, color: t.primaryColor }}>
//         Hello
//         <TweaksPanel>
//           <TweakSection label="Typography" />
//           <TweakSlider label="Font size" value={t.fontSize} min={10} max={32} unit="px"
//                        onChange={(v) => setTweak('fontSize', v)} />
//           <TweakRadio  label="Density" value={t.density}
//                        options={['compact', 'regular', 'comfy']}
//                        onChange={(v) => setTweak('density', v)} />
//           <TweakSection label="Theme" />
//           <TweakColor  label="Primary" value={t.primaryColor}
//                        options={['#D97757', '#2A6FDB', '#1F8A5B', '#7A5AE0']}
//                        onChange={(v) => setTweak('primaryColor', v)} />
//           <TweakColor  label="Palette" value={t.palette}
//                        options={[['#D97757', '#29261b', '#f6f4ef'],
//                                  ['#475569', '#0f172a', '#f1f5f9']]}
//                        onChange={(v) => setTweak('palette', v)} />
//           <TweakToggle label="Dark mode" value={t.dark}
//                        onChange={(v) => setTweak('dark', v)} />
//         </TweaksPanel>
//       </div>
//     );
//   }
//
// TweakRadio is the segmented control for 2–3 short options (auto-falls-back to
// TweakSelect past ~16/~10 chars per label); reach for TweakSelect directly when
// options are many or long. For color tweaks always curate 3-4 options rather than
// a free picker; an option can also be a whole 2–5 color palette (the stored value
// is the array). The Tweak* controls are a floor, not a ceiling — build custom
// controls inside the panel if a tweak calls for UI they don't cover.
/* END USAGE */
// ─────────────────────────────────────────────────────────────────────────────

const __TWEAKS_STYLE = `
  .twk-panel{position:fixed;right:16px;bottom:16px;z-index:2147483646;width:280px;
    max-height:calc(100vh - 32px);display:flex;flex-direction:column;
    transform:scale(var(--dc-inv-zoom,1));transform-origin:bottom right;
    background:rgba(250,249,247,.78);color:#29261b;
    -webkit-backdrop-filter:blur(24px) saturate(160%);backdrop-filter:blur(24px) saturate(160%);
    border:.5px solid rgba(255,255,255,.6);border-radius:14px;
    box-shadow:0 1px 0 rgba(255,255,255,.5) inset,0 12px 40px rgba(0,0,0,.18);
    font:11.5px/1.4 ui-sans-serif,system-ui,-apple-system,sans-serif;overflow:hidden}
  .twk-hd{display:flex;align-items:center;justify-content:space-between;
    padding:10px 8px 10px 14px;cursor:move;user-select:none}
  .twk-hd b{font-size:12px;font-weight:600;letter-spacing:.01em}
  .twk-x{appearance:none;border:0;background:transparent;color:rgba(41,38,27,.55);
    width:22px;height:22px;border-radius:6px;cursor:default;font-size:13px;line-height:1}
  .twk-x:hover{background:rgba(0,0,0,.06);color:#29261b}
  .twk-body{padding:2px 14px 14px;display:flex;flex-direction:column;gap:10px;
    overflow-y:auto;overflow-x:hidden;min-height:0;
    scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.15) transparent}
  .twk-body::-webkit-scrollbar{width:8px}
  .twk-body::-webkit-scrollbar-track{background:transparent;margin:2px}
  .twk-body::-webkit-scrollbar-thumb{background:rgba(0,0,0,.15);border-radius:4px;
    border:2px solid transparent;background-clip:content-box}
  .twk-body::-webkit-scrollbar-thumb:hover{background:rgba(0,0,0,.25);
    border:2px solid transparent;background-clip:content-box}
  .twk-row{display:flex;flex-direction:column;gap:5px}
  .twk-row-h{flex-direction:row;align-items:center;justify-content:space-between;gap:10px}
  .twk-lbl{display:flex;justify-content:space-between;align-items:baseline;
    color:rgba(41,38,27,.72)}
  .twk-lbl>span:first-child{font-weight:500}
  .twk-val{color:rgba(41,38,27,.5);font-variant-numeric:tabular-nums}

  .twk-sect{font-size:10px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
    color:rgba(41,38,27,.45);padding:10px 0 0}
  .twk-sect:first-child{padding-top:0}

  .twk-field{appearance:none;box-sizing:border-box;width:100%;min-width:0;height:26px;padding:0 8px;
    border:.5px solid rgba(0,0,0,.1);border-radius:7px;
    background:rgba(255,255,255,.6);color:inherit;font:inherit;outline:none}
  .twk-field:focus{border-color:rgba(0,0,0,.25);background:rgba(255,255,255,.85)}
  select.twk-field{padding-right:22px;
    background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path fill='rgba(0,0,0,.5)' d='M0 0h10L5 6z'/></svg>");
    background-repeat:no-repeat;background-position:right 8px center}

  .twk-slider{appearance:none;-webkit-appearance:none;width:100%;height:4px;margin:6px 0;
    border-radius:999px;background:rgba(0,0,0,.12);outline:none}
  .twk-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
    width:14px;height:14px;border-radius:50%;background:#fff;
    border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default}
  .twk-slider::-moz-range-thumb{width:14px;height:14px;border-radius:50%;
    background:#fff;border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default}

  .twk-seg{position:relative;display:flex;padding:2px;border-radius:8px;
    background:rgba(0,0,0,.06);user-select:none}
  .twk-seg-thumb{position:absolute;top:2px;bottom:2px;border-radius:6px;
    background:rgba(255,255,255,.9);box-shadow:0 1px 2px rgba(0,0,0,.12);
    transition:left .15s cubic-bezier(.3,.7,.4,1),width .15s}
  .twk-seg.dragging .twk-seg-thumb{transition:none}
  .twk-seg button{appearance:none;position:relative;z-index:1;flex:1;border:0;
    background:transparent;color:inherit;font:inherit;font-weight:500;min-height:22px;
    border-radius:6px;cursor:default;padding:4px 6px;line-height:1.2;
    overflow-wrap:anywhere}

  .twk-toggle{position:relative;width:32px;height:18px;border:0;border-radius:999px;
    background:rgba(0,0,0,.15);transition:background .15s;cursor:default;padding:0}
  .twk-toggle[data-on="1"]{background:#34c759}
  .twk-toggle i{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;
    background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);transition:transform .15s}
  .twk-toggle[data-on="1"] i{transform:translateX(14px)}

  .twk-num{display:flex;align-items:center;box-sizing:border-box;min-width:0;height:26px;padding:0 0 0 8px;
    border:.5px solid rgba(0,0,0,.1);border-radius:7px;background:rgba(255,255,255,.6)}
  .twk-num-lbl{font-weight:500;color:rgba(41,38,27,.6);cursor:ew-resize;
    user-select:none;padding-right:8px}
  .twk-num input{flex:1;min-width:0;height:100%;border:0;background:transparent;
    font:inherit;font-variant-numeric:tabular-nums;text-align:right;padding:0 8px 0 0;
    outline:none;color:inherit;-moz-appearance:textfield}
  .twk-num input::-webkit-inner-spin-button,.twk-num input::-webkit-outer-spin-button{
    -webkit-appearance:none;margin:0}
  .twk-num-unit{padding-right:8px;color:rgba(41,38,27,.45)}

  .twk-btn{appearance:none;height:26px;padding:0 12px;border:0;border-radius:7px;
    background:rgba(0,0,0,.78);color:#fff;font:inherit;font-weight:500;cursor:default}
  .twk-btn:hover{background:rgba(0,0,0,.88)}
  .twk-btn.secondary{background:rgba(0,0,0,.06);color:inherit}
  .twk-btn.secondary:hover{background:rgba(0,0,0,.1)}

  .twk-swatch{appearance:none;-webkit-appearance:none;width:56px;height:22px;
    border:.5px solid rgba(0,0,0,.1);border-radius:6px;padding:0;cursor:default;
    background:transparent;flex-shrink:0}
  .twk-swatch::-webkit-color-swatch-wrapper{padding:0}
  .twk-swatch::-webkit-color-swatch{border:0;border-radius:5.5px}
  .twk-swatch::-moz-color-swatch{border:0;border-radius:5.5px}

  .twk-chips{display:flex;gap:6px}
  .twk-chip{position:relative;appearance:none;flex:1;min-width:0;height:46px;
    padding:0;border:0;border-radius:6px;overflow:hidden;cursor:default;
    box-shadow:0 0 0 .5px rgba(0,0,0,.12),0 1px 2px rgba(0,0,0,.06);
    transition:transform .12s cubic-bezier(.3,.7,.4,1),box-shadow .12s}
  .twk-chip:hover{transform:translateY(-1px);
    box-shadow:0 0 0 .5px rgba(0,0,0,.18),0 4px 10px rgba(0,0,0,.12)}
  .twk-chip[data-on="1"]{box-shadow:0 0 0 1.5px rgba(0,0,0,.85),
    0 2px 6px rgba(0,0,0,.15)}
  .twk-chip>span{position:absolute;top:0;bottom:0;right:0;width:34%;
    display:flex;flex-direction:column;box-shadow:-1px 0 0 rgba(0,0,0,.1)}
  .twk-chip>span>i{flex:1;box-shadow:0 -1px 0 rgba(0,0,0,.1)}
  .twk-chip>span>i:first-child{box-shadow:none}
  .twk-chip svg{position:absolute;top:6px;left:6px;width:13px;height:13px;
    filter:drop-shadow(0 1px 1px rgba(0,0,0,.3))}
`;

// ── useTweaks ───────────────────────────────────────────────────────────────
// Single source of truth for tweak values. setTweak persists via the host
// (__edit_mode_set_keys → host rewrites the EDITMODE block on disk).
function useTweaks(defaults) {
  const [values, setValues] = React.useState(defaults);
  // Accepts either setTweak('key', value) or setTweak({ key: value, ... }) so a
  // useState-style call doesn't write a "[object Object]" key into the persisted
  // JSON block.
  const setTweak = React.useCallback((keyOrEdits, val) => {
    const edits = typeof keyOrEdits === 'object' && keyOrEdits !== null
      ? keyOrEdits : { [keyOrEdits]: val };
    setValues((prev) => ({ ...prev, ...edits }));
    window.parent.postMessage({ type: '__edit_mode_set_keys', edits }, '*');
    // Same-window signal so in-page listeners (deck-stage rail thumbnails)
    // can react — the parent message only reaches the host, not peers.
    window.dispatchEvent(new CustomEvent('tweakchange', { detail: edits }));
  }, []);
  return [values, setTweak];
}

// ── TweaksPanel ─────────────────────────────────────────────────────────────
// Floating shell. Registers the protocol listener BEFORE announcing
// availability — if the announce ran first, the host's activate could land
// before our handler exists and the toolbar toggle would silently no-op.
// The close button posts __edit_mode_dismissed so the host's toolbar toggle
// flips off in lockstep; the host echoes __deactivate_edit_mode back which
// is what actually hides the panel.
function TweaksPanel({ title = 'Tweaks', children }) {
  const [open, setOpen] = React.useState(false);
  const dragRef = React.useRef(null);
  const offsetRef = React.useRef({ x: 16, y: 16 });
  const PAD = 16;

  const clampToViewport = React.useCallback(() => {
    const panel = dragRef.current;
    if (!panel) return;
    const w = panel.offsetWidth, h = panel.offsetHeight;
    const maxRight = Math.max(PAD, window.innerWidth - w - PAD);
    const maxBottom = Math.max(PAD, window.innerHeight - h - PAD);
    offsetRef.current = {
      x: Math.min(maxRight, Math.max(PAD, offsetRef.current.x)),
      y: Math.min(maxBottom, Math.max(PAD, offsetRef.current.y)),
    };
    panel.style.right = offsetRef.current.x + 'px';
    panel.style.bottom = offsetRef.current.y + 'px';
  }, []);

  React.useEffect(() => {
    if (!open) return;
    clampToViewport();
    if (typeof ResizeObserver === 'undefined') {
      window.addEventListener('resize', clampToViewport);
      return () => window.removeEventListener('resize', clampToViewport);
    }
    const ro = new ResizeObserver(clampToViewport);
    ro.observe(document.documentElement);
    return () => ro.disconnect();
  }, [open, clampToViewport]);

  React.useEffect(() => {
    const onMsg = (e) => {
      const t = e?.data?.type;
      if (t === '__activate_edit_mode') setOpen(true);
      else if (t === '__deactivate_edit_mode') setOpen(false);
    };
    window.addEventListener('message', onMsg);
    window.parent.postMessage({ type: '__edit_mode_available' }, '*');
    return () => window.removeEventListener('message', onMsg);
  }, []);

  const dismiss = () => {
    setOpen(false);
    window.parent.postMessage({ type: '__edit_mode_dismissed' }, '*');
  };

  const onDragStart = (e) => {
    const panel = dragRef.current;
    if (!panel) return;
    const r = panel.getBoundingClientRect();
    const sx = e.clientX, sy = e.clientY;
    const startRight = window.innerWidth - r.right;
    const startBottom = window.innerHeight - r.bottom;
    const move = (ev) => {
      offsetRef.current = {
        x: startRight - (ev.clientX - sx),
        y: startBottom - (ev.clientY - sy),
      };
      clampToViewport();
    };
    const up = () => {
      window.removeEventListener('mousemove', move);
      window.removeEventListener('mouseup', up);
    };
    window.addEventListener('mousemove', move);
    window.addEventListener('mouseup', up);
  };

  if (!open) return null;
  return (
    <>
      <style>{__TWEAKS_STYLE}</style>
      <div ref={dragRef} className="twk-panel" data-omelette-chrome=""
           style={{ right: offsetRef.current.x, bottom: offsetRef.current.y }}>
        <div className="twk-hd" onMouseDown={onDragStart}>
          <b>{title}</b>
          <button className="twk-x" aria-label="Close tweaks"
                  onMouseDown={(e) => e.stopPropagation()}
                  onClick={dismiss}>✕</button>
        </div>
        <div className="twk-body">
          {children}
        </div>
      </div>
    </>
  );
}

// ── Layout helpers ──────────────────────────────────────────────────────────

function TweakSection({ label, children }) {
  return (
    <>
      <div className="twk-sect">{label}</div>
      {children}
    </>
  );
}

function TweakRow({ label, value, children, inline = false }) {
  return (
    <div className={inline ? 'twk-row twk-row-h' : 'twk-row'}>
      <div className="twk-lbl">
        <span>{label}</span>
        {value != null && <span className="twk-val">{value}</span>}
      </div>
      {children}
    </div>
  );
}

// ── Controls ────────────────────────────────────────────────────────────────

function TweakSlider({ label, value, min = 0, max = 100, step = 1, unit = '', onChange }) {
  return (
    <TweakRow label={label} value={`${value}${unit}`}>
      <input type="range" className="twk-slider" min={min} max={max} step={step}
             value={value} onChange={(e) => onChange(Number(e.target.value))} />
    </TweakRow>
  );
}

function TweakToggle({ label, value, onChange }) {
  return (
    <div className="twk-row twk-row-h">
      <div className="twk-lbl"><span>{label}</span></div>
      <button type="button" className="twk-toggle" data-on={value ? '1' : '0'}
              role="switch" aria-checked={!!value}
              onClick={() => onChange(!value)}><i /></button>
    </div>
  );
}

function TweakRadio({ label, value, options, onChange }) {
  const trackRef = React.useRef(null);
  const [dragging, setDragging] = React.useState(false);
  // The active value is read by pointer-move handlers attached for the lifetime
  // of a drag — ref it so a stale closure doesn't fire onChange for every move.
  const valueRef = React.useRef(value);
  valueRef.current = value;

  // Segments wrap mid-word once per-segment width runs out. The track is
  // ~248px (280 panel − 28 body pad − 4 seg pad), each button loses 12px
  // to its own padding, and 11.5px system-ui averages ~6.3px/char — so 2
  // options fit ~16 chars each, 3 fit ~10. Past that (or >3 options), fall
  // back to a dropdown rather than wrap.
  const labelLen = (o) => String(typeof o === 'object' ? o.label : o).length;
  const maxLen = options.reduce((m, o) => Math.max(m, labelLen(o)), 0);
  const fitsAsSegments = maxLen <= ({ 2: 16, 3: 10 }[options.length] ?? 0);
  if (!fitsAsSegments) {
    // <select> emits strings — map back to the original option value so the
    // fallback stays type-preserving (numbers, booleans) like the segment path.
    const resolve = (s) => {
      const m = options.find((o) => String(typeof o === 'object' ? o.value : o) === s);
      return m === undefined ? s : typeof m === 'object' ? m.value : m;
    };
    return <TweakSelect label={label} value={value} options={options}
                        onChange={(s) => onChange(resolve(s))} />;
  }
  const opts = options.map((o) => (typeof o === 'object' ? o : { value: o, label: o }));
  const idx = Math.max(0, opts.findIndex((o) => o.value === value));
  const n = opts.length;

  const segAt = (clientX) => {
    const r = trackRef.current.getBoundingClientRect();
    const inner = r.width - 4;
    const i = Math.floor(((clientX - r.left - 2) / inner) * n);
    return opts[Math.max(0, Math.min(n - 1, i))].value;
  };

  const onPointerDown = (e) => {
    setDragging(true);
    const v0 = segAt(e.clientX);
    if (v0 !== valueRef.current) onChange(v0);
    const move = (ev) => {
      if (!trackRef.current) return;
      const v = segAt(ev.clientX);
      if (v !== valueRef.current) onChange(v);
    };
    const up = () => {
      setDragging(false);
      window.removeEventListener('pointermove', move);
      window.removeEventListener('pointerup', up);
    };
    window.addEventListener('pointermove', move);
    window.addEventListener('pointerup', up);
  };

  return (
    <TweakRow label={label}>
      <div ref={trackRef} role="radiogroup" onPointerDown={onPointerDown}
           className={dragging ? 'twk-seg dragging' : 'twk-seg'}>
        <div className="twk-seg-thumb"
             style={{ left: `calc(2px + ${idx} * (100% - 4px) / ${n})`,
                      width: `calc((100% - 4px) / ${n})` }} />
        {opts.map((o) => (
          <button key={o.value} type="button" role="radio" aria-checked={o.value === value}>
            {o.label}
          </button>
        ))}
      </div>
    </TweakRow>
  );
}

function TweakSelect({ label, value, options, onChange }) {
  return (
    <TweakRow label={label}>
      <select className="twk-field" value={value} onChange={(e) => onChange(e.target.value)}>
        {options.map((o) => {
          const v = typeof o === 'object' ? o.value : o;
          const l = typeof o === 'object' ? o.label : o;
          return <option key={v} value={v}>{l}</option>;
        })}
      </select>
    </TweakRow>
  );
}

function TweakText({ label, value, placeholder, onChange }) {
  return (
    <TweakRow label={label}>
      <input className="twk-field" type="text" value={value} placeholder={placeholder}
             onChange={(e) => onChange(e.target.value)} />
    </TweakRow>
  );
}

function TweakNumber({ label, value, min, max, step = 1, unit = '', onChange }) {
  const clamp = (n) => {
    if (min != null && n < min) return min;
    if (max != null && n > max) return max;
    return n;
  };
  const startRef = React.useRef({ x: 0, val: 0 });
  const onScrubStart = (e) => {
    e.preventDefault();
    startRef.current = { x: e.clientX, val: value };
    const decimals = (String(step).split('.')[1] || '').length;
    const move = (ev) => {
      const dx = ev.clientX - startRef.current.x;
      const raw = startRef.current.val + dx * step;
      const snapped = Math.round(raw / step) * step;
      onChange(clamp(Number(snapped.toFixed(decimals))));
    };
    const up = () => {
      window.removeEventListener('pointermove', move);
      window.removeEventListener('pointerup', up);
    };
    window.addEventListener('pointermove', move);
    window.addEventListener('pointerup', up);
  };
  return (
    <div className="twk-num">
      <span className="twk-num-lbl" onPointerDown={onScrubStart}>{label}</span>
      <input type="number" value={value} min={min} max={max} step={step}
             onChange={(e) => onChange(clamp(Number(e.target.value)))} />
      {unit && <span className="twk-num-unit">{unit}</span>}
    </div>
  );
}

// Relative-luminance contrast pick — checkmarks drawn over a swatch need to
// read on both #111 and #fafafa without per-option configuration. Hex input
// only (#rgb / #rrggbb); named or rgb()/hsl() colors fall through to "light".
function __twkIsLight(hex) {
  const h = String(hex).replace('#', '');
  const x = h.length === 3 ? h.replace(/./g, (c) => c + c) : h.padEnd(6, '0');
  const n = parseInt(x.slice(0, 6), 16);
  if (Number.isNaN(n)) return true;
  const r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255;
  return r * 299 + g * 587 + b * 114 > 148000;
}

const __TwkCheck = ({ light }) => (
  <svg viewBox="0 0 14 14" aria-hidden="true">
    <path d="M3 7.2 5.8 10 11 4.2" fill="none" strokeWidth="2.2"
          strokeLinecap="round" strokeLinejoin="round"
          stroke={light ? 'rgba(0,0,0,.78)' : '#fff'} />
  </svg>
);

// TweakColor — curated color/palette picker. Each option is either a single
// hex string or an array of 1-5 hex strings; the card adapts — a lone color
// renders solid, a palette renders colors[0] as the hero (left ~2/3) with the
// rest stacked in a sharp column on the right. onChange emits the
// option in the shape it was passed (string stays string, array stays array).
// Without options it falls back to the native color input for back-compat.
function TweakColor({ label, value, options, onChange }) {
  if (!options || !options.length) {
    return (
      <div className="twk-row twk-row-h">
        <div className="twk-lbl"><span>{label}</span></div>
        <input type="color" className="twk-swatch" value={value}
               onChange={(e) => onChange(e.target.value)} />
      </div>
    );
  }
  // Native <input type=color> emits lowercase hex per the HTML spec, so
  // compare case-insensitively. String() guards JSON.stringify(undefined),
  // which returns the primitive undefined (no .toLowerCase).
  const key = (o) => String(JSON.stringify(o)).toLowerCase();
  const cur = key(value);
  return (
    <TweakRow label={label}>
      <div className="twk-chips" role="radiogroup">
        {options.map((o, i) => {
          const colors = Array.isArray(o) ? o : [o];
          const [hero, ...rest] = colors;
          const sup = rest.slice(0, 4);
          const on = key(o) === cur;
          return (
            <button key={i} type="button" className="twk-chip" role="radio"
                    aria-checked={on} data-on={on ? '1' : '0'}
                    aria-label={colors.join(', ')} title={colors.join(' · ')}
                    style={{ background: hero }}
                    onClick={() => onChange(o)}>
              {sup.length > 0 && (
                <span>
                  {sup.map((c, j) => <i key={j} style={{ background: c }} />)}
                </span>
              )}
              {on && <__TwkCheck light={__twkIsLight(hero)} />}
            </button>
          );
        })}
      </div>
    </TweakRow>
  );
}

function TweakButton({ label, onClick, secondary = false }) {
  return (
    <button type="button" className={secondary ? 'twk-btn secondary' : 'twk-btn'}
            onClick={onClick}>{label}</button>
  );
}

Object.assign(window, {
  useTweaks, TweaksPanel, TweakSection, TweakRow,
  TweakSlider, TweakToggle, TweakRadio, TweakSelect,
  TweakText, TweakNumber, TweakColor, TweakButton,
});


/* ===== hooks.jsx ===== */
// Shared hooks + small primitives
const { useState, useEffect, useRef, useCallback } = React;

// Reveal-on-scroll: returns a ref to attach. Adds .in when visible.
function useReveal(opts = {}) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const targets = [];
    if (el.matches?.(".reveal")) targets.push(el);
    el.querySelectorAll?.(".reveal").forEach((n) => targets.push(n));

    const show = (t) => t.classList.add("in");
    // Immediately reveal anything already within (or near) the viewport — robust to
    // capture/offscreen contexts where IntersectionObserver may not fire.
    const revealInView = () => {
      const vh = window.innerHeight || 800;
      targets.forEach((t) => {
        const r = t.getBoundingClientRect();
        if (r.top < vh * 0.96 && r.bottom > 0) show(t);
      });
    };
    revealInView();

    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting) { show(e.target); io.unobserve(e.target); }
      });
    }, { threshold: opts.threshold ?? 0.12, rootMargin: opts.rootMargin ?? "0px 0px -6% 0px" });
    targets.forEach((t) => { if (!t.classList.contains("in")) io.observe(t); });

    // Safety net: if observers never fire (headless/offscreen), reveal everything.
    const fallback = setTimeout(() => targets.forEach(show), 1600);
    return () => { io.disconnect(); clearTimeout(fallback); };
  }, []);
  return ref;
}

// Count-up number when it scrolls into view
function CountUp({ value, decimals = 0, suffix = "", duration = 1500 }) {
  const ref = useRef(null);
  const [disp, setDisp] = useState(0);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    let raf, started = false;
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting && !started) {
          started = true;
          const t0 = performance.now();
          const tick = (now) => {
            const p = Math.min(1, (now - t0) / duration);
            const eased = 1 - Math.pow(1 - p, 3);
            setDisp(value * eased);
            if (p < 1) raf = requestAnimationFrame(tick);
            else setDisp(value);
          };
          raf = requestAnimationFrame(tick);
          io.unobserve(e.target);
        }
      });
    }, { threshold: 0.5 });
    io.observe(el);
    return () => { io.disconnect(); cancelAnimationFrame(raf); };
  }, [value]);
  return <span ref={ref}>{disp.toFixed(decimals)}{suffix}</span>;
}

// Magnetic wrapper — element drifts slightly toward cursor
function Magnetic({ strength = 0.32, children, className, style }) {
  const ref = useRef(null);
  const onMove = useCallback((e) => {
    const el = ref.current; if (!el) return;
    const r = el.getBoundingClientRect();
    const x = (e.clientX - (r.left + r.width / 2)) * strength;
    const y = (e.clientY - (r.top + r.height / 2)) * strength;
    el.style.transform = `translate(${x}px, ${y}px)`;
  }, [strength]);
  const reset = useCallback(() => {
    const el = ref.current; if (el) el.style.transform = "translate(0,0)";
  }, []);
  return (
    <span ref={ref} onMouseMove={onMove} onMouseLeave={reset}
      className={className}
      style={{ display: "inline-block", transition: "transform .45s cubic-bezier(.22,1,.36,1)", ...style }}>
      {children}
    </span>
  );
}

// Section scaffold with eyebrow/number + title
function SectionHead({ num, eyebrow, title }) {
  return (
    <div className="section-head reveal">
      <span className="section-num">{num}</span>
      <div>
        {eyebrow && <div className="eyebrow" style={{ marginBottom: 14 }}>{eyebrow}</div>}
        <h2 className="section-title" dangerouslySetInnerHTML={{ __html: title }} />
      </div>
    </div>
  );
}

Object.assign(window, { useReveal, CountUp, Magnetic, SectionHead });


/* ===== sections-top.jsx ===== */
// Nav + Hero + About
const { useState: useStateT, useEffect: useEffectT, useRef: useRefT } = React;
const P = window.PORTFOLIO;

const NAV_LINKS = [
  { id: "research", label: "Research" },
  { id: "publications", label: "Publications" },
  { id: "experience", label: "Experience" },
  { id: "achievements", label: "Recognition" },
  { id: "contact", label: "Contact" },
];

function Nav() {
  const [solid, setSolid] = useStateT(false);
  const [open, setOpen] = useStateT(false);
  useEffectT(() => {
    const onScroll = () => setSolid(window.scrollY > 80);
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  const go = (id) => {
    setOpen(false);
    document.getElementById(id)?.scrollIntoView({ behavior: "smooth", block: "start" });
  };
  return (
    <header style={{
      position: "fixed", top: 0, left: 0, right: 0, zIndex: 100,
      transition: "background .5s var(--ease), border-color .5s, backdrop-filter .5s",
      background: solid ? "rgba(11,11,12,0.72)" : "transparent",
      backdropFilter: solid ? "blur(14px) saturate(1.2)" : "none",
      borderBottom: `1px solid ${solid ? "var(--line)" : "transparent"}`,
    }}>
      <div className="wrap" style={{ height: 74, display: "flex", alignItems: "center", justifyContent: "space-between" }}>
        <span aria-hidden="true" />
        <nav className="nav-desktop" style={{ display: "flex", alignItems: "center", gap: 30 }}>
          {NAV_LINKS.map((l) => (
            <a key={l.id} href={"#" + l.id} onClick={(e) => { e.preventDefault(); go(l.id); }}
              className="navlink" style={{ fontFamily: "var(--mono)", fontSize: 12, letterSpacing: "0.12em", textTransform: "uppercase", color: "var(--muted)" }}>
              {l.label}
            </a>
          ))}
          <a href="assets/Dr-Muhammad-Umar-Mirza-CV.pdf" download="Dr-Muhammad-Umar-Mirza-CV.pdf" className="nav-cv">
            Download CV ↓
          </a>
        </nav>
        <button className="nav-burger" onClick={() => setOpen(!open)} aria-label="Menu" style={{
          display: "none", background: "none", border: "1px solid var(--line)", borderRadius: 999,
          color: "var(--ink)", padding: "8px 14px", fontFamily: "var(--mono)", fontSize: 12, cursor: "pointer",
        }}>{open ? "Close" : "Menu"}</button>
      </div>
      {open && (
        <div className="wrap" style={{ paddingBottom: 22, display: "flex", flexDirection: "column", gap: 14 }}>
          {NAV_LINKS.map((l) => (
            <a key={l.id} href={"#" + l.id} onClick={(e) => { e.preventDefault(); go(l.id); }}
              style={{ fontFamily: "var(--mono)", fontSize: 13, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--ink-soft)" }}>
              {l.label}
            </a>
          ))}
          <a href="assets/Dr-Muhammad-Umar-Mirza-CV.pdf" download="Dr-Muhammad-Umar-Mirza-CV.pdf" className="nav-cv" style={{ alignSelf: "flex-start", marginTop: 4 }}>
            Download CV ↓
          </a>
        </div>
      )}
    </header>
  );
}

function Hero() {
  const ref = useReveal();
  const imgWrap = useRefT(null);
  // parallax drift on the portrait
  useEffectT(() => {
    const onScroll = () => {
      const y = window.scrollY;
      if (imgWrap.current) imgWrap.current.style.transform = `translateY(${y * 0.06}px)`;
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  const keywords = ["Fuzzy MCDM", "Graph Theory", "Machine Learning", "Decision Science", "Topological Indices", "Computational Math"];

  return (
    <section id="top" ref={ref} style={{ minHeight: "100svh", display: "flex", alignItems: "center", padding: "90px 0 118px", position: "relative", overflow: "hidden" }}>
      {/* faint backdrop glyph */}
      <div aria-hidden="true" style={{
        position: "absolute", right: "-6%", top: "8%", fontFamily: "var(--serif)", fontStyle: "italic",
        fontSize: "min(62vw, 760px)", lineHeight: 1, color: "rgba(243,241,236,0.028)", pointerEvents: "none", userSelect: "none",
      }}>∑</div>
      <div aria-hidden="true" className="hero-grid-texture" style={{
        position: "absolute", inset: 0, pointerEvents: "none",
        backgroundImage: "radial-gradient(rgba(243,241,236,0.05) 1px, transparent 1px)",
        backgroundSize: "46px 46px", maskImage: "radial-gradient(80% 70% at 60% 40%, #000 30%, transparent 75%)",
        WebkitMaskImage: "radial-gradient(80% 70% at 60% 40%, #000 30%, transparent 75%)",
      }} />

      <div className="wrap hero-grid" style={{ width: "100%", display: "grid", gridTemplateColumns: "1.08fr 0.92fr", gap: 40, alignItems: "center", position: "relative", zIndex: 2 }}>
        <div>
          <div className="reveal eyebrow" style={{ display: "flex", alignItems: "center", gap: 12 }}>
            <span style={{ width: 28, height: 1, background: "var(--muted)" }} />
            PhD Mathematics · Researcher &amp; Educator
          </div>
          <h1 className="hero-name" style={{
            fontWeight: 400, lineHeight: 0.96, letterSpacing: "-0.022em",
            fontSize: "clamp(48px, 8.2vw, 116px)", margin: "26px 0 0",
          }}>
            <span className="reveal d1" style={{ display: "block", fontSize: "0.34em", fontStyle: "italic", letterSpacing: "0.02em", color: "var(--muted)", marginBottom: "0.12em" }}>Dr.</span>
            <span className="reveal d1" style={{ display: "block" }}>Muhammad</span>
            <span className="reveal d2" style={{ display: "block" }}>Umar <em style={{ fontStyle: "italic", color: "var(--ink-soft)" }}>Mirza</em></span>
          </h1>
          <p className="reveal d3" style={{ marginTop: 30, maxWidth: 540, fontSize: "clamp(17px,1.5vw,20px)", color: "var(--ink-soft)", lineHeight: 1.62 }}>
            {P.tagline}
          </p>
          <div className="reveal d4" style={{ marginTop: 38, display: "flex", flexWrap: "wrap", gap: 14, alignItems: "center" }}>
            <Magnetic strength={0.25}>
              <a href="#publications" onClick={(e)=>{e.preventDefault();document.getElementById("publications").scrollIntoView({behavior:"smooth"});}}
                className="btn-primary">View research →</a>
            </Magnetic>
            <Magnetic strength={0.25}>
              <a href="assets/Dr-Muhammad-Umar-Mirza-CV.pdf" download="Dr-Muhammad-Umar-Mirza-CV.pdf"
                className="btn-ghost">Download CV ↓</a>
            </Magnetic>
            <Magnetic strength={0.25}>
              <a href="#contact" onClick={(e)=>{e.preventDefault();document.getElementById("contact").scrollIntoView({behavior:"smooth"});}}
                className="btn-ghost">Get in touch</a>
            </Magnetic>
          </div>
          <div className="reveal d4" style={{ marginTop: 30, marginBottom: 8, display: "flex", gap: 12, flexWrap: "wrap" }}>
            {P.links.map((l) => (
              <a key={l.label} href={l.url} target="_blank" rel="noopener" className="pill-link">
                {l.short} <span style={{ opacity: 0.5, marginLeft: 2 }}>↗</span>
              </a>
            ))}
          </div>
        </div>

        {/* Portrait */}
        <div className="hero-portrait" style={{ position: "relative", justifySelf: "center", width: "100%", maxWidth: 400 }}>
          <div ref={imgWrap} style={{ position: "relative" }}>
            <div style={{
              position: "absolute", left: "50%", bottom: "2%", transform: "translateX(-50%)",
              width: "108%", height: "82%", borderRadius: "50%",
              background: "radial-gradient(closest-side, rgba(243,241,236,0.10), transparent 72%)",
              filter: "blur(8px)",
            }} />
            <div style={{
              position: "absolute", inset: "-6% -4% 0", border: "1px solid var(--line)", borderRadius: "260px 260px 22px 22px",
            }} />
            <img src="assets/umar-cutout.png" alt="Dr. Muhammad Umar Mirza" className="reveal d2"
              style={{ position: "relative", zIndex: 2, width: "100%", display: "block", filter: "contrast(1.03) brightness(1.02)", transition: "transform 1.1s var(--ease)" }} />
          </div>
          <div className="reveal d3 portrait-side-label" style={{
            position: "absolute", left: -14, top: "24%", transform: "rotate(-90deg)", transformOrigin: "left center",
            fontFamily: "var(--mono)", fontSize: 11, letterSpacing: "0.3em", color: "var(--faint)", textTransform: "uppercase", whiteSpace: "nowrap",
          }}>Bahawalnagar · Punjab</div>
          <div className="reveal d4 collab-badge" style={{ position: "absolute", right: -6, bottom: 18, display: "flex", alignItems: "center", gap: 8, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--muted)" }}>
            <span style={{ width: 8, height: 8, borderRadius: "50%", background: "var(--dot)", boxShadow: "0 0 10px var(--dot)" }} className="pulse" />
            Open to collaboration
          </div>
        </div>
      </div>

      {/* keyword marquee */}
      <div className="reveal hero-marquee" style={{ position: "absolute", bottom: 0, left: 0, right: 0, borderTop: "1px solid var(--line-soft)", padding: "16px 0", overflow: "hidden", background: "var(--bg)" }}>
        <div className="marquee" style={{ display: "flex", gap: 46, whiteSpace: "nowrap", width: "max-content" }}>
          {[...keywords, ...keywords, ...keywords].map((k, i) => (
            <span key={i} style={{ fontFamily: "var(--mono)", fontSize: 12, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--faint)", display: "inline-flex", alignItems: "center", gap: 46 }}>
              {k}<span style={{ color: "rgba(243,241,236,0.16)" }}>✦</span>
            </span>
          ))}
        </div>
      </div>
    </section>
  );
}

function About() {
  const ref = useReveal();
  return (
    <section id="about" ref={ref} className="section-pad" style={{ borderTop: "1px solid var(--line)" }}>
      <div className="wrap">
        <SectionHead num="01" eyebrow="About" title="Scholarship and <em>leadership</em>,<br/>held to one standard." />
        <div className="about-grid" style={{ display: "grid", gridTemplateColumns: "1.5fr 1fr", gap: 64, alignItems: "start" }}>
          <div>
            {P.bio.map((para, i) => (
              <p key={i} className={`reveal d${i + 1}`} style={{ fontSize: "clamp(19px,1.7vw,23px)", lineHeight: 1.6, color: i === 0 ? "var(--ink)" : "var(--ink-soft)", marginBottom: 26, textWrap: "pretty" }}>
                {para}
              </p>
            ))}
          </div>
          <div className="reveal d2 stat-card-wrap" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 1, background: "var(--line)", border: "1px solid var(--line)" }}>
            {P.stats.map((s, i) => (
              <div key={i} style={{ background: "var(--bg)", padding: "26px 22px" }}>
                <div style={{ fontFamily: "var(--serif)", fontSize: "clamp(34px,3.6vw,46px)", fontWeight: 400, letterSpacing: "-0.02em", lineHeight: 1 }}>
                  <CountUp value={s.value} decimals={s.decimals || 0} suffix={s.suffix || ""} />
                </div>
                <div style={{ marginTop: 12, fontFamily: "var(--mono)", fontSize: 11, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--muted)" }}>{s.label}</div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
}

Object.assign(window, { Nav, Hero, About });


/* ===== sections-mid.jsx ===== */
// Research expertise + Publications
const { useState: useStateM } = React;
const PM = window.PORTFOLIO;

function Research() {
  const ref = useReveal();
  return (
    <section id="research" ref={ref} className="section-pad" style={{ borderTop: "1px solid var(--line)", background: "var(--bg-2)" }}>
      <div className="wrap">
        <SectionHead num="02" eyebrow="Research Focus" title="Four <em>intersecting</em> lines of inquiry" />
        <div className="research-grid" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 1, background: "var(--line)", border: "1px solid var(--line)" }}>
          {PM.expertise.map((e, i) => (
            <article key={i} className={`reveal d${(i % 2) + 1} expertise-card`} style={{ background: "var(--bg-2)", padding: "42px 38px", position: "relative", overflow: "hidden" }}>
              <span style={{ fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)", letterSpacing: "0.1em" }}>0{i + 1}</span>
              <h3 style={{ marginTop: 18, fontSize: "clamp(22px,2.2vw,29px)", fontWeight: 400, lineHeight: 1.18, letterSpacing: "-0.01em" }}>{e.title}</h3>
              <p style={{ marginTop: 16, color: "var(--muted)", fontSize: 16.5, lineHeight: 1.6, textWrap: "pretty" }}>{e.desc}</p>
              <span className="expertise-rule" />
            </article>
          ))}
        </div>
      </div>
    </section>
  );
}

function Publications() {
  const ref = useReveal();
  const [filter, setFilter] = useStateM("All");
  const list = PM.publications.map((p, i) => ({ ...p, n: i + 1 }));
  const shown = filter === "All" ? list : list.filter((p) => p.tags.includes(filter));

  return (
    <section id="publications" ref={ref} className="section-pad" style={{ borderTop: "1px solid var(--line)" }}>
      <div className="wrap">
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", flexWrap: "wrap", gap: 24, marginBottom: 46 }}>
          <SectionHead num="03" eyebrow="Selected Work" title="Publications &amp; <em>manuscripts</em>" />
          <div className="reveal" style={{ fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--muted)", paddingBottom: 6 }}>
            <CountUp value={PM.publications.length} /> peer-reviewed publications
          </div>
        </div>

        {/* filters */}
        <div className="reveal" style={{ display: "flex", flexWrap: "wrap", gap: 10, marginBottom: 12 }}>
          {PM.pubFilters.map((f) => {
            const active = f === filter;
            const count = f === "All" ? list.length : list.filter((p) => p.tags.includes(f)).length;
            return (
              <button key={f} onClick={() => setFilter(f)} className="filter-chip" style={{
                fontFamily: "var(--mono)", fontSize: 12, letterSpacing: "0.04em", cursor: "pointer",
                padding: "8px 15px", borderRadius: 999, transition: "all .3s var(--ease)",
                border: `1px solid ${active ? "var(--ink)" : "var(--line)"}`,
                background: active ? "var(--ink)" : "transparent",
                color: active ? "var(--bg)" : "var(--muted)",
              }}>
                {f} <span style={{ opacity: 0.6 }}>{count}</span>
              </button>
            );
          })}
        </div>

        <div style={{ borderTop: "1px solid var(--line)" }}>
          {shown.map((p) => (
            <a key={p.n} href={PM.links[0].url} target="_blank" rel="noopener" className="pub-row" style={{
              display: "grid", gridTemplateColumns: "56px 1fr auto", gap: 26, alignItems: "start",
              padding: "28px 8px", borderBottom: "1px solid var(--line)", position: "relative",
            }}>
              <span className="pub-num" style={{ fontFamily: "var(--mono)", fontSize: 13, color: "var(--faint)", paddingTop: 6 }}>
                {String(p.n).padStart(2, "0")}
              </span>
              <div>
                <h3 className="pub-title" style={{ fontSize: "clamp(18px,1.7vw,22px)", fontWeight: 400, lineHeight: 1.32, letterSpacing: "-0.005em", textWrap: "pretty" }}>{p.title}</h3>
                <div style={{ marginTop: 12, display: "flex", flexWrap: "wrap", gap: 14, alignItems: "center" }}>
                  <span style={{ fontFamily: "var(--serif)", fontStyle: "italic", fontSize: 15.5, color: "var(--ink-soft)" }}>{p.journal}</span>
                  {p.tags.map((t) => (
                    <span key={t} style={{ fontFamily: "var(--mono)", fontSize: 10.5, letterSpacing: "0.08em", textTransform: "uppercase", color: "var(--faint)", border: "1px solid var(--line)", padding: "3px 8px", borderRadius: 4 }}>{t}</span>
                  ))}
                </div>
              </div>
              <span className="pub-status" style={{
                fontFamily: "var(--mono)", fontSize: 10.5, letterSpacing: "0.08em", textTransform: "uppercase", whiteSpace: "nowrap",
                padding: "5px 11px", borderRadius: 999, alignSelf: "start", marginTop: 4,
                color: "var(--ink-soft)", background: "transparent", border: "1px solid var(--line)",
                display: "inline-flex", alignItems: "center", gap: 7,
              }}>
                <span style={{ width: 6, height: 6, borderRadius: "50%", background: "var(--dot)", boxShadow: "0 0 8px var(--dot)" }} />
                {p.status}
              </span>
            </a>
          ))}
        </div>
      </div>
    </section>
  );
}

Object.assign(window, { Research, Publications });


/* ===== sections-bottom.jsx ===== */
// Experience, Education, Skills, Recognition, Speaking, Contact, Footer
const { useState: useStateB, useEffect: useEffectB, useRef: useRefB } = React;
const PB = window.PORTFOLIO;

function Experience() {
  const ref = useReveal();
  return (
    <section id="experience" ref={ref} className="section-pad" style={{ borderTop: "1px solid var(--line)", background: "var(--bg-2)" }}>
      <div className="wrap">
        <SectionHead num="04" eyebrow="Experience" title="From the classroom to the <em>head office</em>" />
        <div className="exp-grid" style={{ display: "grid", gridTemplateColumns: "0.8fr 1.2fr", gap: 60 }}>
          {/* timeline */}
          <div style={{ borderLeft: "1px solid var(--line)" }}>
            {PB.experience.map((x, i) => (
              <div key={i} className={`reveal d${(i % 3) + 1} exp-item`} style={{ position: "relative", padding: "0 0 44px 30px" }}>
                <span style={{ position: "absolute", left: -5, top: 7, width: 9, height: 9, borderRadius: "50%", background: "var(--bg)", border: "1px solid var(--ink-soft)" }} />
                <div style={{ fontFamily: "var(--mono)", fontSize: 11.5, letterSpacing: "0.08em", color: "var(--muted)" }}>{x.period}</div>
                <h3 style={{ marginTop: 10, fontSize: "clamp(20px,1.9vw,25px)", fontWeight: 400, lineHeight: 1.2 }}>{x.role}</h3>
                <div style={{ marginTop: 6, fontFamily: "var(--serif)", fontStyle: "italic", color: "var(--ink-soft)", fontSize: 16 }}>{x.org}</div>
                <ul style={{ marginTop: 14, listStyle: "none", display: "flex", flexDirection: "column", gap: 9 }}>
                  {x.points.map((pt, j) => (
                    <li key={j} style={{ display: "flex", gap: 12, color: "var(--muted)", fontSize: 16, lineHeight: 1.55 }}>
                      <span style={{ color: "var(--faint)", fontFamily: "var(--mono)" }}>—</span>
                      <span style={{ textWrap: "pretty" }}>{pt}</span>
                    </li>
                  ))}
                </ul>
              </div>
            ))}
          </div>
          {/* education + skills */}
          <div>
            <div className="reveal" style={{ fontFamily: "var(--mono)", fontSize: 11.5, letterSpacing: "0.2em", textTransform: "uppercase", color: "var(--muted)", marginBottom: 22 }}>Education</div>
            {PB.education.map((e, i) => (
              <div key={i} className={`reveal d${i + 1}`} style={{ display: "grid", gridTemplateColumns: "auto 1fr", gap: 24, padding: "22px 0", borderTop: "1px solid var(--line)" }}>
                <div style={{ fontFamily: "var(--mono)", fontSize: 12, color: "var(--muted)", whiteSpace: "nowrap", paddingTop: 4 }}>{e.period}</div>
                <div>
                  <h4 style={{ fontSize: "clamp(20px,1.8vw,24px)", fontWeight: 400 }}>{e.degree}</h4>
                  <div style={{ marginTop: 4, fontFamily: "var(--serif)", fontStyle: "italic", color: "var(--ink-soft)" }}>{e.school}</div>
                  <div style={{ marginTop: 8, color: "var(--muted)", fontSize: 15.5 }}>{e.detail}</div>
                </div>
              </div>
            ))}

            <div className="reveal" style={{ fontFamily: "var(--mono)", fontSize: 11.5, letterSpacing: "0.2em", textTransform: "uppercase", color: "var(--muted)", margin: "44px 0 22px" }}>Tools &amp; Methods</div>
            <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
              {PB.tools.map((t, i) => (
                <SkillBar key={t.name} name={t.name} level={t.level} delay={i * 90} />
              ))}
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

function SkillBar({ name, level, delay }) {
  const ref = useRefB(null);
  const [w, setW] = useStateB(0);
  useEffectB(() => {
    const el = ref.current; if (!el) return;
    const io = new IntersectionObserver((ents) => {
      ents.forEach((e) => { if (e.isIntersecting) { setTimeout(() => setW(level), delay); io.unobserve(e.target); } });
    }, { threshold: 0.6 });
    io.observe(el);
    return () => io.disconnect();
  }, [level, delay]);
  return (
    <div ref={ref} className="reveal" style={{ display: "grid", gridTemplateColumns: "150px 1fr 38px", gap: 16, alignItems: "center" }}>
      <span style={{ fontSize: 15.5, color: "var(--ink-soft)" }}>{name}</span>
      <span style={{ height: 2, background: "var(--line)", position: "relative", overflow: "hidden" }}>
        <span style={{ position: "absolute", inset: 0, width: w + "%", background: "var(--ink)", transition: "width 1.1s var(--ease)" }} />
      </span>
      <span style={{ fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--muted)", textAlign: "right" }}>{w}</span>
    </div>
  );
}

function Recognition() {
  const ref = useReveal();
  return (
    <section id="achievements" ref={ref} className="section-pad" style={{ borderTop: "1px solid var(--line)" }}>
      <div className="wrap">
        <SectionHead num="05" eyebrow="Recognition" title="Awards, honours &amp; <em>the podium</em>" />
        <div className="rec-grid" style={{ display: "grid", gridTemplateColumns: "1.25fr 0.75fr", gap: 60, alignItems: "start" }}>
          <div>
            {PB.achievements.map((a, i) => (
              <div key={i} className={`reveal d${(i % 3) + 1}`} style={{ display: "grid", gridTemplateColumns: "48px 1fr", gap: 18, padding: "22px 0", borderTop: i === 0 ? "1px solid var(--line)" : "1px solid var(--line-soft)" }}>
                <span style={{ fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)", paddingTop: 4 }}>{String(i + 1).padStart(2, "0")}</span>
                <p style={{ fontSize: "clamp(17px,1.5vw,20px)", lineHeight: 1.5, color: "var(--ink-soft)", textWrap: "pretty" }}>{a}</p>
              </div>
            ))}
          </div>
          <div className="reveal d2" style={{ position: "sticky", top: 100 }}>
            <div style={{ fontFamily: "var(--mono)", fontSize: 11.5, letterSpacing: "0.2em", textTransform: "uppercase", color: "var(--muted)", marginBottom: 22 }}>Speaking</div>
            <div style={{ display: "flex", flexDirection: "column", gap: 1, background: "var(--line)", border: "1px solid var(--line)" }}>
              {PB.speaking.map((s, i) => (
                <div key={i} style={{ background: "var(--bg)", padding: "24px 22px" }}>
                  <h4 style={{ fontSize: 20, fontWeight: 400 }}>{s.title}</h4>
                  <div style={{ marginTop: 6, fontFamily: "var(--serif)", fontStyle: "italic", fontSize: 15, color: "var(--ink-soft)" }}>{s.venue}</div>
                  <p style={{ marginTop: 10, fontSize: 14.5, color: "var(--muted)", lineHeight: 1.5 }}>{s.note}</p>
                </div>
              ))}
            </div>
            <div style={{ marginTop: 22, display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" }}>
              <span style={{ fontFamily: "var(--mono)", fontSize: 11, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--muted)" }}>Languages</span>
              {PB.languages.map((l) => (
                <span key={l} style={{ fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--ink-soft)", border: "1px solid var(--line)", borderRadius: 999, padding: "4px 12px" }}>{l}</span>
              ))}
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

function Contact() {
  const ref = useReveal();
  return (
    <section id="contact" ref={ref} className="section-pad" style={{ borderTop: "1px solid var(--line)", background: "var(--bg-2)", position: "relative", overflow: "hidden" }}>
      <div aria-hidden="true" style={{
        position: "absolute", left: "-4%", bottom: "-18%", fontFamily: "var(--serif)", fontStyle: "italic",
        fontSize: "min(46vw, 560px)", lineHeight: 1, color: "rgba(243,241,236,0.025)", pointerEvents: "none", userSelect: "none",
      }}>μ</div>
      <div className="wrap" style={{ position: "relative", zIndex: 2 }}>
        <div className="reveal eyebrow" style={{ marginBottom: 22 }}>Contact</div>
        <h2 className="reveal d1" style={{ fontSize: "clamp(36px,6vw,84px)", fontWeight: 400, lineHeight: 1.0, letterSpacing: "-0.02em", maxWidth: 900 }}>
          Let's build something <em style={{ fontStyle: "italic", color: "var(--ink-soft)" }}>rigorous</em> together.
        </h2>
        <p className="reveal d2" style={{ marginTop: 26, maxWidth: 560, fontSize: 19, color: "var(--ink-soft)", lineHeight: 1.6 }}>
          Open to research collaboration, supervision, speaking, and consulting on fuzzy decision-making, graph theory and applied machine learning.
        </p>

        <div className="reveal d2 contact-grid" style={{ marginTop: 50, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 1, background: "var(--line)", border: "1px solid var(--line)", maxWidth: 760 }}>
          <a href={`mailto:${PB.email}`} className="contact-cell" style={{ background: "var(--bg-2)", padding: "26px 26px", display: "block" }}>
            <div style={{ fontFamily: "var(--mono)", fontSize: 11, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--muted)" }}>Email</div>
            <div style={{ marginTop: 10, fontSize: 18, color: "var(--ink)" }}>{PB.email}</div>
          </a>
          <a href={`https://wa.me/${PB.whatsappRaw}`} target="_blank" rel="noopener" className="contact-cell" style={{ background: "var(--bg-2)", padding: "26px 26px", display: "block" }}>
            <div style={{ fontFamily: "var(--mono)", fontSize: 11, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--muted)" }}>WhatsApp</div>
            <div style={{ marginTop: 10, fontSize: 18, color: "var(--ink)" }}>{PB.whatsapp}</div>
          </a>
          <a href={PB.websiteUrl} target="_blank" rel="noopener" className="contact-cell" style={{ background: "var(--bg-2)", padding: "26px 26px", display: "block" }}>
            <div style={{ fontFamily: "var(--mono)", fontSize: 11, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--muted)" }}>Website</div>
            <div style={{ marginTop: 10, fontSize: 18, color: "var(--ink)" }}>{PB.website}</div>
          </a>
          <div className="contact-cell" style={{ background: "var(--bg-2)", padding: "26px 26px" }}>
            <div style={{ fontFamily: "var(--mono)", fontSize: 11, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--muted)" }}>Location</div>
            <div style={{ marginTop: 10, fontSize: 18, color: "var(--ink)" }}>{PB.location}</div>
          </div>
        </div>

        <div className="reveal d3" style={{ marginTop: 34, display: "flex", gap: 24, flexWrap: "wrap" }}>
          {PB.links.map((l) => (
            <a key={l.label} href={l.url} target="_blank" rel="noopener" className="sociallink" style={{ fontFamily: "var(--mono)", fontSize: 13, color: "var(--ink-soft)", borderBottom: "1px solid var(--line)", paddingBottom: 3 }}>
              {l.label} ↗
            </a>
          ))}
        </div>
      </div>
    </section>
  );
}

function Footer() {
  return (
    <footer style={{ borderTop: "1px solid var(--line)", padding: "30px 0" }}>
      <div className="wrap" style={{ display: "flex", justifyContent: "space-between", flexWrap: "wrap", gap: 14, alignItems: "center" }}>
        <span style={{ fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)", letterSpacing: "0.08em" }}>© {new Date().getFullYear()} DR. MUHAMMAD UMAR MIRZA</span>
        <span style={{ fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)", letterSpacing: "0.08em" }}>PhD Mathematics · Punjab, Pakistan</span>
      </div>
    </footer>
  );
}

Object.assign(window, { Experience, Recognition, Contact, Footer });


/* ===== app.jsx ===== */
// Mount
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "displayFont": "Newsreader",
  "accent": "#7fd18a",
  "showMarquee": true,
  "showGrid": true,
  "showCollab": true,
  "animations": true
}/*EDITMODE-END*/;

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);

  useEffect(() => {
    const root = document.documentElement;
    root.style.setProperty("--serif", `"${t.displayFont}", Georgia, "Times New Roman", serif`);
    root.style.setProperty("--dot", t.accent);
    root.classList.toggle("no-marquee", !t.showMarquee);
    root.classList.toggle("no-grid", !t.showGrid);
    root.classList.toggle("no-collab", !t.showCollab);
    root.classList.toggle("tweak-no-anim", !t.animations);
  }, [t]);

  return (
    <React.Fragment>
      <Nav />
      <main>
        <Hero />
        <About />
        <Research />
        <Publications />
        <Experience />
        <Recognition />
        <Contact />
      </main>
      <Footer />

      <TweaksPanel title="Tweaks">
        <TweakSection label="Typography" />
        <TweakSelect label="Display typeface" value={t.displayFont}
          options={["Newsreader", "Spectral", "Libre Caslon Text"]}
          onChange={(v) => setTweak("displayFont", v)} />

        <TweakSection label="Accent" />
        <TweakColor label="Accent dot" value={t.accent}
          options={["#7fd18a", "#d8b46a", "#6fa8dc", "#c98f8f", "#f3f1ec"]}
          onChange={(v) => setTweak("accent", v)} />

        <TweakSection label="Hero" />
        <TweakToggle label="Keyword marquee" value={t.showMarquee} onChange={(v) => setTweak("showMarquee", v)} />
        <TweakToggle label="Dot-grid texture" value={t.showGrid} onChange={(v) => setTweak("showGrid", v)} />
        <TweakToggle label="“Open to collaboration”" value={t.showCollab} onChange={(v) => setTweak("showCollab", v)} />

        <TweakSection label="Motion" />
        <TweakToggle label="Scroll animations" value={t.animations} onChange={(v) => setTweak("animations", v)} />
      </TweaksPanel>
    </React.Fragment>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);

