// Scoped in an IIFE: this file shares global scope with the other page
// entries, and the router now loads all of them into one document. Names
// like App, Hero, Footer, Cover and C are declared by several of them, so
// leaking them at top level lets the last-loaded page silently clobber the
// others' components. Only window.Pages.<ns> is exported.
(function () {
const { useState, useEffect, useRef } = React;
const M = window.Marks;

// Palette indirection: every value resolves through theme.css, so these
// follow the active theme. React inline styles accept var() strings.
const C = {
  paper: 'var(--paper)', ink: 'var(--ink)',
  mustard: 'var(--mustard)', tomato: 'var(--tomato)', sage: 'var(--sage)',
  pink: 'var(--pink)', deep: 'var(--deep)', blue: 'var(--blue)', sand: 'var(--sand)'
};

const EMAIL = 'syedhamzatahir1001@gmail.com';
// Content blocks align to the drafting frame via the shared .frame-wrap class
// (see --wrap-x in theme.css) rather than a centred 92%/1180px column, so the
// nav brand, the hero and everything under it share one left and right edge.
// Every page in the site uses that same class; there is no per-page variant.

// ---------- bento palette ----------
// The accent family in theme.css is tuned for LARGE saturated panels. At the
// body-copy sizes these cards now carry, a fixed light foreground on a raw
// accent measured 3.2-3.8:1 in dark mode -- the dark-theme accents are
// *lighter* than their light-theme values, so no single foreground clears
// 4.5:1 against both. Mixing each accent toward a near-black of its own hue
// keeps the token's identity while pinning the panel dark in either theme,
// which lets one fixed light ink work everywhere.
//
// Every overview tile is now a FIXED dark panel, driven by the assets rather
// than taste: all five product logos are near-white artwork on transparent
// (measured 247-254 mean luminance), so a tile that went light in either theme
// would erase the logo sitting on it. solstice used to pair raw ink/paper and
// chaingpt raw mustard; both read light in one theme or in both, and both are
// pinned dark here instead. accent slots stay light on purpose -- they carry no
// logo, only text, so they give the focused spread some range.
//
// Fills are always applied as the background-color LONGHAND. A `background:`
// shorthand holding a var() is pending-substitution, so the CSSOM reports it as
// "" -- and initProjectDashboard builds each card face by reading
// slot.style.backgroundColor, which silently rendered every face transparent.
const PF_LIGHT_INK = '#F6F1E6';
const PF_DARK_INK = '#16150F';
function pfShade(token, black, pct) {
  return 'color-mix(in srgb, var(' + token + ') ' + pct + '%, ' + black + ')';
}
const PF_FILL = {
  ink: 'var(--ink)',
  inkSoft: 'var(--ink-soft)',
  paper: 'var(--paper)',
  mustard: 'var(--mustard)',
  sand: 'var(--sand)',
  // fixed, non-flipping darks for the two tiles whose own hue reads light
  slate: '#1B1A14',
  slateMid: '#2B2921',
  amber: pfShade('--mustard', '#140D02', 30),
  amberMid: pfShade('--mustard', '#140D02', 46),
  deep: pfShade('--deep', '#140D09', 58),
  tomato: pfShade('--tomato', '#1A0B08', 58),
  sage: pfShade('--sage', '#0B1207', 58),
  blue: pfShade('--blue', '#070D16', 58)
};

// Overview face of each tile: the domain worked in, the product logo, and the
// product name under it. Generated from one place because the same markup is
// needed twice -- inlined into PF_BENTO_HTML for first paint, and handed back by
// renderSlotOverview when a focused spread collapses. Writing it twice by hand
// is how the two silently drift apart.
// `ar` is each trimmed logo's width/height. It is needed because the marks are
// nowhere near a common shape -- they run from 0.62 (tall and narrow) to 1.29
// (wide) -- and sizing them all to one HEIGHT made the narrow one read as tiny:
// ResearchSoup covered 987px2 of ink against Femverse's 4049px2, four times as
// much. Normalising on AREA instead gives every mark the same optical weight
// whatever its shape.
const PF_OVERVIEW = {
  solstice:     { domain: 'Pharma',   name: 'Solstice Health', ar: 260 / 260, logo: 'images/products/trimmed/solistice.png' },
  chaingpt:     { domain: 'FinTech',  name: 'ChainGPT',        ar: 169 / 178, logo: 'images/products/trimmed/chainGPT.png' },
  femverse:     { domain: 'Health',   name: 'Femverse',        ar: 377 / 292, logo: 'images/products/trimmed/femverse.png' },
  researchsoup: { domain: 'Research', name: 'ResearchSoup',    ar: 195 / 316, logo: 'images/products/trimmed/reesarch-soup.png' },
  forwood:      { domain: 'Safety',   name: 'Forwood',         ar: 222 / 257, logo: 'images/products/trimmed/forwood.png' }
};

// Target ink areas, in px2. The small one is for slot D, the only tile short
// enough that a full-size mark would crowd the name out.
const PF_LOGO_AREA = 3364;
const PF_LOGO_AREA_SM = 2800;

// height for a given aspect at a target area: a = w*h and w = ar*h, so
// h = sqrt(a / ar). Emitted as px because CSS sqrt() is not usable yet.
function pfLogoHeight(ar, area) {
  return Math.round(Math.sqrt(area / ar));
}

// Logos point at images/products/trimmed/, not the originals. Each source PNG
// carries a lot of transparent padding and by wildly different amounts -- the ink
// filled 31% of the canvas on chainGPT and 73% on femverse, and none of them were
// centred in it. object-fit could only fit the CANVAS, so every mark rendered
// small, at a different size from its neighbours, and off-centre. The trimmed
// copies are cropped to their alpha bounds plus a few px, so sizing the box sizes
// the artwork. Originals are untouched in the parent folder.
//
// alt="" on purpose: the product name sits beside the logo as real text, so
// describing the mark again would only make a screen reader say it twice.
function pfOverviewHTML(key) {
  var o = PF_OVERVIEW[key];
  // both heights travel with the element so the CSS can pick one per slot
  var h = pfLogoHeight(o.ar, PF_LOGO_AREA);
  var hSm = pfLogoHeight(o.ar, PF_LOGO_AREA_SM);
  return '<div class="pf-domain">' + o.domain + '</div>'
    + '<div class="pf-mark">'
    + '<img class="pf-logo" src="' + o.logo + '" alt="" aria-hidden="true"'
    + ' style="--pf-logo-h:' + h + 'px;--pf-logo-h-sm:' + hSm + 'px">'
    + '<div class="pf-name">' + o.name + '</div>'
    + '</div>';
}

// ---------- project dashboard (bento case-study morph) ----------
const PF_BENTO_HTML = `
  <div class="pf-header">
    <div class="pf-title">2020&ndash;2025</div>
    <div class="pf-hint">Esc, or click any card &middot; &#8599; opens the write-up</div>
  </div>
  <div class="pf-canvas">
    <div class="pf-bento">

      <div class="pf-slot" data-slot="A" data-home="solstice" style="background-color:${PF_FILL.slate};color:${PF_LIGHT_INK}">
        <div class="pf-body">${pfOverviewHTML('solstice')}</div>
      </div>

      <div class="pf-slot" data-slot="B" data-home="chaingpt" style="background-color:${PF_FILL.amber};color:${PF_LIGHT_INK}">
        <div class="pf-body">${pfOverviewHTML('chaingpt')}</div>
      </div>

      <div class="pf-slot" data-slot="C" data-home="femverse" style="background-color:${PF_FILL.deep};color:${PF_LIGHT_INK}">
        <div class="pf-body">${pfOverviewHTML('femverse')}</div>
      </div>

      <div class="pf-slot" data-slot="D" data-home="researchsoup" style="background-color:${PF_FILL.sage};color:${PF_LIGHT_INK}">
        <div class="pf-body">${pfOverviewHTML('researchsoup')}</div>
      </div>

      <div class="pf-slot" data-slot="E" data-home="forwood" style="background-color:${PF_FILL.blue};color:${PF_LIGHT_INK}">
        <div class="pf-body">${pfOverviewHTML('forwood')}</div>
      </div>

    </div>
    <div class="pf-overlay"></div>
    <svg width="0" height="0" style="position:absolute;pointer-events:none" aria-hidden="true">
      <defs>
        <filter id="pf-diffuse" x="-40%" y="-40%" width="180%" height="180%" color-interpolation-filters="sRGB">
          <feTurbulence id="pf-turb" type="fractalNoise" baseFrequency="0.014" numOctaves="3" seed="1" result="noise"/>
          <feDisplacementMap in="SourceGraphic" in2="noise" scale="140"/>
          <feGaussianBlur stdDeviation="2"/>
        </filter>
        <mask id="pf-diffuse-mask" maskUnits="userSpaceOnUse" x="-400" y="-400" width="2400" height="2400">
          <circle id="pf-mask-circle" cx="0" cy="0" r="0" fill="#fff" filter="url(#pf-diffuse)"/>
        </mask>
      </defs>
    </svg>
  </div>
`;

const PF_DATA = {
  solstice: { name: 'Solstice', tag: 'FDA compliance', main: PF_FILL.slate, mid: PF_FILL.slateMid, accent: PF_FILL.sand, onMain: PF_LIGHT_INK, onAccent: PF_DARK_INK, caseUrl: 'https://example.com/case/solstice',
    intro: pfOverviewHTML('solstice'),
    A: '<div class="pf-lbl" style="opacity:0.55">Headline</div><div><div style="font-size:38px;font-weight:700;letter-spacing:-0.03em;line-height:0.95"><span style="text-decoration:line-through;text-decoration-thickness:3px;opacity:0.5">Manual review,</span><br>replaced.</div><div style="font-size:15px;opacity:0.7;margin-top:12px;max-width:340px">Automated contraindication detection, FDA-compliant, end-to-end.</div></div>',
    B: '<div class="pf-lbl">Review queue</div><div style="margin-top:8px"><div style="padding:5px 7px;background:color-mix(in srgb, currentColor 17%, transparent);border-radius:4px;margin-bottom:3px;display:flex;gap:6px;justify-content:space-between;align-items:center;font-size:11.5px"><span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0">Warfarin+Ibuprofen</span><span style="background:#A33B29;color:#F6F1E6;flex:0 0 auto;padding:1px 5px;border-radius:3px;font-size:10px;font-weight:600">FLAG</span></div><div style="padding:5px 7px;background:color-mix(in srgb, currentColor 11%, transparent);border-radius:4px;margin-bottom:3px;display:flex;gap:6px;justify-content:space-between;align-items:center;font-size:11.5px"><span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0">Metformin+Vit D</span><span style="background-color:#3F5C31;color:#F6F1E6;flex:0 0 auto;padding:1px 5px;border-radius:3px;font-size:10px;font-weight:600">CLEAR</span></div><div style="padding:5px 7px;background:color-mix(in srgb, currentColor 11%, transparent);border-radius:4px;display:flex;gap:6px;justify-content:space-between;align-items:center;font-size:11.5px"><span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0">Lisinopril+K+</span><span style="background-color:#3F5C31;color:#F6F1E6;flex:0 0 auto;padding:1px 5px;border-radius:3px;font-size:10px;font-weight:600">CLEAR</span></div></div>',
    C: '<div class="pf-lbl">What I did</div><div style="font-size:15px;line-height:1.5;margin-top:6px">Built the automated compliance layer end-to-end &mdash; model routing, rule engine, audit trail, human-in-loop escalation. Shipped in 6 weeks.</div><div style="display:flex;flex-wrap:wrap;gap:5px;margin-top:10px"><span style="background:color-mix(in srgb, currentColor 12%, transparent);padding:2px 7px;border-radius:20px;font-size:11.5px">Claude</span><span style="background:color-mix(in srgb, currentColor 12%, transparent);padding:2px 7px;border-radius:20px;font-size:11.5px">LangGraph</span><span style="background:color-mix(in srgb, currentColor 12%, transparent);padding:2px 7px;border-radius:20px;font-size:11.5px">Postgres</span></div>',
    D: '<div class="pf-lbl">Detection</div><div style="font-size:36px;font-weight:700;letter-spacing:-0.03em;line-height:1;margin-top:8px">12<span style="font-size:19px">ms</span></div><div style="font-size:12.5px;margin-top:2px">avg / pair</div>',
    E: '<div class="pf-lbl">Testimonial</div><div style="display:flex;gap:14px;align-items:flex-end;margin-top:6px"><div style="font-size:36px;line-height:0.7;opacity:0.4;font-family:Georgia,serif">&quot;</div><div style="flex:1"><div style="font-size:17px;line-height:1.4">Cut compliance review from three days to under a minute.</div><div style="font-size:12.5px;opacity:0.85;margin-top:4px">Dr. Sarah Kim &middot; VP Regulatory</div></div></div>'
  },
  femverse: { name: 'Femverse', tag: 'AI health', main: PF_FILL.deep, mid: PF_FILL.tomato, accent: PF_FILL.sand, onMain: PF_LIGHT_INK, onAccent: PF_DARK_INK, caseUrl: 'https://example.com/case/femverse',
    intro: pfOverviewHTML('femverse'),
    A: '<div class="pf-lbl" style="opacity:0.7">Headline</div><div><div style="font-size:56px;font-weight:700;letter-spacing:-0.03em;line-height:1">57K</div><div style="font-size:21px;margin-top:2px">women served, safely.</div><div style="font-size:15px;opacity:0.85;margin-top:10px;max-width:340px">MD-verified answers on pregnancy, cycles, medication.</div></div>',
    B: '<div class="pf-lbl">Chat &middot; sample</div><div style="display:flex;flex-direction:column;gap:4px;margin-top:8px"><div style="background:color-mix(in srgb, currentColor 20%, transparent);padding:5px 7px;border-radius:8px 8px 8px 2px;font-size:12px;align-self:flex-start;max-width:90%">Is Vitamin A safe during pregnancy?</div><div style="background:var(--paper);color:var(--ink);padding:5px 7px;border-radius:8px 8px 2px 8px;font-size:12px;align-self:flex-end;max-width:90%">Limit to 5,000 IU/day <span style="background-color:#3F5C31;color:#F6F1E6;padding:1px 4px;border-radius:3px;font-size:10px;font-weight:600">MD&#10003;</span></div></div>',
    C: '<div class="pf-lbl">What I did</div><div style="font-size:15px;line-height:1.5;margin-top:6px">Built the safety and validation layer &mdash; MD-verified response filtering, sensitive-topic detection, medication contraindication checks.</div><div style="display:flex;flex-wrap:wrap;gap:5px;margin-top:10px"><span style="background:color-mix(in srgb, currentColor 12%, transparent);padding:2px 7px;border-radius:20px;font-size:11.5px">Claude</span><span style="background:color-mix(in srgb, currentColor 12%, transparent);padding:2px 7px;border-radius:20px;font-size:11.5px">Medical KB</span><span style="background:color-mix(in srgb, currentColor 12%, transparent);padding:2px 7px;border-radius:20px;font-size:11.5px">Pinecone</span></div>',
    D: '<div class="pf-lbl">Session</div><div style="font-size:36px;font-weight:700;letter-spacing:-0.03em;line-height:1;margin-top:8px">8.3<span style="font-size:19px">m</span></div><div style="font-size:12.5px;margin-top:2px">avg / user</div>',
    E: '<div class="pf-lbl">Testimonial</div><div style="display:flex;gap:14px;align-items:flex-end;margin-top:6px"><div style="font-size:36px;line-height:0.7;opacity:0.4;font-family:Georgia,serif">&quot;</div><div style="flex:1"><div style="font-size:17px;line-height:1.4">Finally an AI I can actually trust for pregnancy questions.</div><div style="font-size:12.5px;opacity:0.85;margin-top:4px">Priya &middot; beta user</div></div></div>'
  },
  chaingpt: { name: 'ChainGPT', tag: 'AI trading', main: PF_FILL.amber, mid: PF_FILL.amberMid, accent: PF_FILL.sand, onMain: PF_LIGHT_INK, onAccent: PF_DARK_INK, caseUrl: 'https://example.com/case/chaingpt',
    intro: pfOverviewHTML('chaingpt'),
    A: '<div class="pf-lbl" style="opacity:0.6">Headline</div><div><div style="font-size:56px;font-weight:700;letter-spacing:-0.03em;line-height:1">+172<span style="font-size:36px">%</span></div><div style="font-size:21px;margin-top:2px">session time.</div><div style="font-size:15px;margin-top:10px;max-width:340px;opacity:0.7">Real-time market signals converted into agent tool calls.</div></div>',
    B: '<div class="pf-lbl">BTC / USD</div><div style="background:color-mix(in srgb, currentColor 16%, transparent);border-radius:8px;padding:8px;margin-top:8px"><svg viewBox="0 0 160 34" style="width:100%;height:26px" preserveAspectRatio="none"><polyline points="0,30 22,26 44,22 66,20 88,14 110,10 132,6 160,2" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round"/><circle cx="160" cy="2" r="2.5" fill="currentColor"/></svg><div style="display:inline-flex;align-items:center;gap:3px;background:var(--ink);color:var(--paper);padding:2px 5px;border-radius:3px;font-size:10px;font-weight:600;margin-top:4px">&#9679; BUY SIGNAL</div></div>',
    C: '<div class="pf-lbl">What I did</div><div style="font-size:15px;line-height:1.5;margin-top:6px">Turned market signals into real-time agent tool calls &mdash; indicator monitoring, signal generation, execution routing.</div><div style="display:flex;flex-wrap:wrap;gap:5px;margin-top:10px"><span style="background:color-mix(in srgb, currentColor 12%, transparent);padding:2px 7px;border-radius:20px;font-size:11.5px">Claude</span><span style="background:color-mix(in srgb, currentColor 12%, transparent);padding:2px 7px;border-radius:20px;font-size:11.5px">WebSockets</span><span style="background:color-mix(in srgb, currentColor 12%, transparent);padding:2px 7px;border-radius:20px;font-size:11.5px">Alpaca</span></div>',
    D: '<div class="pf-lbl">DAU</div><div style="font-size:36px;font-weight:700;letter-spacing:-0.03em;line-height:1;margin-top:8px">3.2&times;</div><div style="font-size:12.5px;margin-top:2px">to month 3</div>',
    E: '<div class="pf-lbl">Testimonial</div><div style="display:flex;gap:14px;align-items:flex-end;margin-top:6px"><div style="font-size:36px;line-height:0.7;opacity:0.4;font-family:Georgia,serif">&quot;</div><div style="flex:1"><div style="font-size:17px;line-height:1.4">Feels like having a quant on the team.</div><div style="font-size:12.5px;opacity:0.85;margin-top:4px">Jamie &middot; retail trader</div></div></div>'
  },
  forwood: { name: 'Forwood', tag: 'Enterprise RAG', main: PF_FILL.blue, mid: pfShade('--blue', '#070D16', 42), accent: PF_FILL.sand, onMain: PF_LIGHT_INK, onAccent: PF_DARK_INK, caseUrl: 'https://example.com/case/forwood',
    intro: pfOverviewHTML('forwood'),
    A: '<div class="pf-lbl" style="opacity:0.6">Headline</div><div><div style="font-size:56px;font-weight:700;letter-spacing:-0.03em;line-height:1">1,500<sup style="font-size:32px">+</sup></div><div style="font-size:21px;margin-top:2px">enterprise clients.</div><div style="font-size:15px;margin-top:10px;max-width:340px;opacity:0.7">RAG on five years of incident reports &mdash; searched, ranked, cited.</div></div>',
    B: '<div class="pf-lbl">Search &middot; sample</div><div style="background:var(--paper);border-radius:5px;padding:5px 7px;font-size:12.5px;color:var(--ink-soft);display:flex;align-items:center;gap:5px;margin-top:8px"><i class="ti ti-search" style="font-size:12.5px"></i><span>Q4 incidents at Site 12...</span></div><div style="margin-top:6px;padding:0 3px;font-size:12.5px"><div style="font-weight:600">&rarr; 3 near-miss reports</div><div style="opacity:0.7;font-size:11px;margin-top:2px">2 open &middot; 1 resolved &middot; cited</div></div>',
    C: '<div class="pf-lbl">What I did</div><div style="font-size:15px;line-height:1.5;margin-top:6px">Built the production RAG stack on five years of enterprise incident data &mdash; retrieval, cross-encoder ranking, source citation.</div><div style="display:flex;flex-wrap:wrap;gap:5px;margin-top:10px"><span style="background:color-mix(in srgb, currentColor 12%, transparent);padding:2px 7px;border-radius:20px;font-size:11.5px">Pinecone</span><span style="background:color-mix(in srgb, currentColor 12%, transparent);padding:2px 7px;border-radius:20px;font-size:11.5px">Claude</span><span style="background:color-mix(in srgb, currentColor 12%, transparent);padding:2px 7px;border-radius:20px;font-size:11.5px">Snowflake</span></div>',
    D: '<div class="pf-lbl">Retrieval</div><div style="font-size:33px;font-weight:700;letter-spacing:-0.03em;line-height:1;margin-top:8px">340<span style="font-size:19px">ms</span></div><div style="font-size:12.5px;margin-top:2px">on 2.4M docs</div>',
    E: '<div class="pf-lbl">Testimonial</div><div style="display:flex;gap:14px;align-items:flex-end;margin-top:6px"><div style="font-size:36px;line-height:0.7;opacity:0.4;font-family:Georgia,serif">&quot;</div><div style="flex:1"><div style="font-size:17px;line-height:1.4">Our safety team gets answers in seconds instead of hours.</div><div style="font-size:12.5px;opacity:0.85;margin-top:4px">Mike O. &middot; Head of HSE</div></div></div>'
  },
  researchsoup: { name: 'ResearchSoup', tag: 'MCP platform', main: PF_FILL.sage, mid: pfShade('--sage', '#0B1207', 42), accent: PF_FILL.sand, onMain: PF_LIGHT_INK, onAccent: PF_DARK_INK, caseUrl: 'https://example.com/case/researchsoup',
    intro: pfOverviewHTML('researchsoup'),
    A: '<div class="pf-lbl" style="opacity:0.6">Headline</div><div><div style="font-size:46px;font-weight:700;letter-spacing:-0.03em;line-height:1">3+ hrs / week</div><div style="font-size:19px;margin-top:2px">saved per researcher.</div><div style="font-size:15px;margin-top:10px;max-width:340px;opacity:0.7">Internal MCP framework &mdash; vector search, tool registry, agent context.</div></div>',
    B: '<div class="pf-lbl">CLI</div><div style="background:#12140E;color:#C9D4A0;border-radius:5px;padding:7px 9px;font-family:&#39;SF Mono&#39;,Menlo,Consolas,monospace;font-size:12px;line-height:1.7;margin-top:8px"><div><span style="color:#6B7659">$</span> mcp:search <span style="color:#E8D89A">&quot;safety&quot;</span></div><div style="color:#E8D89A">&rarr; 12 &middot; 340ms</div><div><span style="color:#6B7659">$</span> <span style="opacity:0.6">&#9608;</span></div></div>',
    C: '<div class="pf-lbl">What I did</div><div style="font-size:15px;line-height:1.5;margin-top:6px">Built the internal MCP framework &mdash; server hosts tools and vector search, client injects retrieved context into agent runs.</div><div style="display:flex;flex-wrap:wrap;gap:5px;margin-top:10px"><span style="background:color-mix(in srgb, currentColor 12%, transparent);padding:2px 7px;border-radius:20px;font-size:11.5px">MCP</span><span style="background:color-mix(in srgb, currentColor 12%, transparent);padding:2px 7px;border-radius:20px;font-size:11.5px">Qdrant</span><span style="background:color-mix(in srgb, currentColor 12%, transparent);padding:2px 7px;border-radius:20px;font-size:11.5px">Gemini</span></div>',
    D: '<div class="pf-lbl">Adoption</div><div style="font-size:36px;font-weight:700;letter-spacing:-0.03em;line-height:1;margin-top:8px">12/12</div><div style="font-size:12.5px;margin-top:2px">daily use</div>',
    E: '<div class="pf-lbl">Testimonial</div><div style="display:flex;gap:14px;align-items:flex-end;margin-top:6px"><div style="font-size:36px;line-height:0.7;opacity:0.4;font-family:Georgia,serif">&quot;</div><div style="flex:1"><div style="font-size:17px;line-height:1.4">Every researcher on the team uses it daily.</div><div style="font-size:12.5px;opacity:0.85;margin-top:4px">Team lead</div></div></div>'
  }
};

function initProjectDashboard(root) {
  var P = PF_DATA;
  var canvas = root.querySelector('.pf-canvas');
  var overlay = root.querySelector('.pf-overlay');
  var hint = root.querySelector('.pf-hint');
  var title = root.querySelector('.pf-title');
  var slots = root.querySelectorAll('.pf-slot');
  var mode = 'overview';
  var focused = null;
  var busy = false;
  var currentReveal = null;

  slots.forEach(function (slot) {
    var flipper = document.createElement('div');
    flipper.className = 'pf-flipper';
    var faceFront = document.createElement('div');
    faceFront.className = 'pf-face pf-face-front';
    faceFront.style.backgroundColor = slot.style.backgroundColor;
    faceFront.style.color = slot.style.color;
    slot.style.backgroundColor = '';
    slot.style.color = '';
    while (slot.firstChild) faceFront.appendChild(slot.firstChild);
    flipper.appendChild(faceFront);
    slot.appendChild(flipper);
  });

  function slotBg(key, slotId) {
    var p = P[key];
    return { A: p.main, B: p.mid, C: p.accent, D: p.mid, E: p.main }[slotId];
  }
  function slotFg(key, slotId) {
    var p = P[key];
    return { A: p.onMain, B: p.onMain, C: p.onAccent, D: p.onMain, E: p.onMain }[slotId];
  }

  function renderSlotOverview(s) {
    var face = s.querySelector('.pf-face-front');
    var home = s.dataset.home;
    var p = P[home];
    face.style.backgroundColor = p.main;
    face.style.color = p.onMain;
    face.querySelector('.pf-body').innerHTML = p.intro;
    var badge = face.querySelector('.pf-brand-badge');
    if (badge) badge.remove();
    var mark = face.querySelector('.pf-cta-mark');
    if (mark) mark.remove();
    s.classList.remove('pf-cta');
    s.removeAttribute('data-case-url');
  }

  function renderSlotFocused(s, key) {
    var face = s.querySelector('.pf-face-front');
    var slotId = s.dataset.slot;
    var home = s.dataset.home;
    face.style.backgroundColor = slotBg(key, slotId);
    face.style.color = slotFg(key, slotId);
    face.querySelector('.pf-body').innerHTML = P[key][slotId];
    var badge = face.querySelector('.pf-brand-badge');
    if (!badge) {
      badge = document.createElement('div');
      badge.className = 'pf-brand-badge';
      face.appendChild(badge);
    }
    badge.textContent = P[home].name;
    badge.style.color = slotFg(key, slotId);
    if (home === key) { badge.classList.add('pf-brand-active'); }
    else { badge.classList.remove('pf-brand-active'); }
    var oldMark = face.querySelector('.pf-cta-mark');
    if (oldMark) oldMark.remove();
    s.classList.remove('pf-cta');
    s.removeAttribute('data-case-url');
    if (slotId === 'A' && P[key].caseUrl) {
      s.classList.add('pf-cta');
      s.setAttribute('data-case-url', P[key].caseUrl);
      var mark = document.createElement('div');
      mark.className = 'pf-cta-mark';
      mark.textContent = '↗';
      mark.style.color = slotFg(key, slotId);
      face.appendChild(mark);
    }
  }

  function renderOverview() { slots.forEach(renderSlotOverview); }
  function renderFocused(key) { slots.forEach(function (s) { renderSlotFocused(s, key); }); }

  var WAVE = 1500;
  var maskCircle = root.querySelector('#pf-mask-circle');
  var turb = root.querySelector('#pf-turb');

  function radialReveal(cx, cy, updateSlot, onDone) {
    if (currentReveal) currentReveal.cancel();

    var rect = canvas.getBoundingClientRect();
    var w = rect.width, h = rect.height;
    var maxR = Math.max(1, Math.hypot(Math.max(cx, w - cx), Math.max(cy, h - cy)) + 140) || 1;

    turb.setAttribute('seed', Math.floor(Math.random() * 9999));
    maskCircle.setAttribute('cx', cx);
    maskCircle.setAttribute('cy', cy);
    maskCircle.setAttribute('r', 0);

    var original = canvas.querySelector('.pf-bento');
    var clone = original.cloneNode(true);
    clone.classList.add('pf-bento-clone');
    clone.style.transition = 'none';
    canvas.appendChild(clone);
    clone.querySelectorAll('.pf-slot').forEach(function (s) { updateSlot(s); });
    clone.offsetWidth;

    // Captured on the FIRST rAF tick, not here: a requestAnimationFrame
    // callback receives the timestamp of the frame it belongs to, which can
    // PREDATE a performance.now() sampled just before scheduling it. That made
    // t negative, so eased went negative, so <circle r> got a negative value,
    // which throws -- killing the rAF loop and leaving pf-busy set forever,
    // permanently freezing the board. router.js's animateCircle already does it
    // this way.
    var startTime = null;
    var cancelled = false;
    var rafId = null;

    var handle = {
      cancel: function () {
        if (cancelled) return;
        cancelled = true;
        if (rafId) cancelAnimationFrame(rafId);
        if (clone.parentNode) clone.remove();
        maskCircle.setAttribute('r', 0);
        if (currentReveal === handle) currentReveal = null;
      }
    };
    currentReveal = handle;

    function tick(now) {
      if (cancelled) return;
      if (startTime === null) startTime = now;
      var t = (now - startTime) / WAVE;
      if (t < 0) t = 0;
      if (t >= 1) {
        maskCircle.setAttribute('r', maxR);
        slots.forEach(function (s) {
          var f = s.querySelector('.pf-face-front');
          if (f) f.style.transition = 'none';
        });
        slots.forEach(updateSlot);
        canvas.offsetWidth;
        clone.remove();
        maskCircle.setAttribute('r', 0);
        slots.forEach(function (s) {
          var f = s.querySelector('.pf-face-front');
          if (f) f.style.transition = '';
        });
        if (currentReveal === handle) currentReveal = null;
        if (onDone) onDone();
        return;
      }
      var eased = 1 - Math.pow(1 - t, 3);
      maskCircle.setAttribute('r', Math.max(0, eased * maxR));
      rafId = requestAnimationFrame(tick);
    }
    rafId = requestAnimationFrame(tick);
  }

  function onSlotClick(e) {
    var slot = e.currentTarget;
    if (busy) return;
    if (mode === 'focused') {
      // The "<- Overview" button is gone, so the board itself is the exit:
      // clicking any card returns to the overview. Slot A is the exception --
      // it carries a visible arrow mark and opens the write-up instead.
      if (slot.classList.contains('pf-cta')) {
        var url = slot.getAttribute('data-case-url');
        if (url) window.open(url, '_blank', 'noopener');
        return;
      }
      returnToOverview();
      return;
    }
    busy = true;
    root.classList.add('pf-busy');
    var home = slot.dataset.home;
    var rect = canvas.getBoundingClientRect();
    var x = e.clientX - rect.left;
    var y = e.clientY - rect.top;
    mode = 'focused';
    focused = home;
    root.classList.add('pf-focused');
    hint.classList.add('pf-show');
    title.textContent = P[home].name + ' · ' + P[home].tag;
    title.style.textTransform = 'none';
    title.style.letterSpacing = '0.03em';
    title.style.fontSize = '13px';
    radialReveal(x, y, function (s) {
      renderSlotFocused(s, home);
    }, function () {
      busy = false;
      root.classList.remove('pf-busy');
    });
  }

  slots.forEach(function (slot) { slot.addEventListener('click', onSlotClick); });

  var FLIP_DUR = 720;
  var FLIP_STAGGER = 60;

  function flipCardToOverview(slot) {
    var flipper = slot.querySelector('.pf-flipper');
    var home = slot.dataset.home;
    var p = P[home];

    // The flip rebuilds the front face from scratch, so the focused-state CTA
    // affordance has to be cleared here too -- renderSlotOverview is not on
    // this path, and the exit now depends on which slots are CTAs.
    slot.classList.remove('pf-cta');
    slot.removeAttribute('data-case-url');

    var faceBack = document.createElement('div');
    faceBack.className = 'pf-face pf-face-back';
    faceBack.style.backgroundColor = p.main;
    faceBack.style.color = p.onMain;
    var body = document.createElement('div');
    body.className = 'pf-body';
    body.innerHTML = p.intro;
    faceBack.appendChild(body);
    flipper.appendChild(faceBack);
    flipper.offsetWidth;

    flipper.style.transition = 'transform ' + FLIP_DUR + 'ms cubic-bezier(0.55, 0, 0.35, 1)';
    flipper.style.transform = 'rotateY(180deg)';

    setTimeout(function () {
      var oldFront = flipper.querySelector('.pf-face-front');
      if (oldFront) oldFront.remove();
      faceBack.classList.remove('pf-face-back');
      faceBack.classList.add('pf-face-front');
      flipper.style.transition = 'none';
      flipper.style.transform = 'rotateY(0deg)';
      flipper.offsetWidth;
      flipper.style.transition = '';
    }, FLIP_DUR);
  }

  function returnToOverview() {
    if (busy || mode !== 'focused') return;
    busy = true;
    root.classList.add('pf-busy');
    mode = 'overview';
    focused = null;
    root.classList.remove('pf-focused');
    hint.classList.remove('pf-show');
    title.textContent = '2020–2025';
    title.style.textTransform = 'uppercase';
    title.style.letterSpacing = '0.25em';
    title.style.fontSize = '12px';

    slots.forEach(function (slot, idx) {
      setTimeout(function () { flipCardToOverview(slot); }, idx * FLIP_STAGGER);
    });

    var total = (slots.length - 1) * FLIP_STAGGER + FLIP_DUR + 40;
    setTimeout(function () {
      busy = false;
      root.classList.remove('pf-busy');
    }, total);
  }

  // Keyboard exit, so the board is not a trap for anyone not using a mouse.
  function onKeyDown(e) {
    if (e.key === 'Escape') returnToOverview();
  }
  document.addEventListener('keydown', onKeyDown);

  return function cleanup() {
    slots.forEach(function (slot) { slot.removeEventListener('click', onSlotClick); });
    document.removeEventListener('keydown', onKeyDown);
    if (currentReveal) currentReveal.cancel();
  };
}

function ProjectDashboard() {
  const rootRef = useRef(null);
  useEffect(() => {
    const cleanup = initProjectDashboard(rootRef.current);
    return cleanup;
  }, []);
  return (
    <section className="page-section frame-wrap" style={{ padding: '8px 0 78px' }}>
      <SectionLabel>Selected work</SectionLabel>
      <h2 style={{ margin: '20px 0 14px', fontWeight: 700, letterSpacing: '-0.03em', lineHeight: 0.98, fontSize: 'clamp(32px, 4.4vw, 54px)', textWrap: 'balance' }}>
        Proof, not just a pitch.
      </h2>
      <p style={{ margin: '0 0 34px', maxWidth: 640, fontSize: 'clamp(16px, 1.7vw, 19px)', lineHeight: 1.6, opacity: 0.86, textWrap: 'pretty' }}>
        Five engagements where the reliability work actually mattered. Click a card for what shipped,
        what it took, and what changed once real users showed up.
      </p>
      <div className="pf-root" ref={rootRef} dangerouslySetInnerHTML={{ __html: PF_BENTO_HTML }} />
    </section>);

}

// Styling lives in theme.css as .section-label so services.jsx can match it
// without duplicating the numbers.
function SectionLabel({ children }) {
  return (
    <div className="section-label">
      <span className="section-label-tick" />
      {children}
    </div>);

}

// ---------- hero highlight (hover → products shipped) ----------
function HeroHighlight() {
  const [open, setOpen] = useState(false);
  const products = [
    { name: 'Solstice Health', tag: 'FDA compliance', color: C.mustard },
    { name: 'Femverse', tag: 'AI health', color: C.deep },
    { name: 'ChainGPT', tag: 'AI trading', color: C.sand },
    { name: 'ResearchSoup', tag: 'MCP platform', color: C.sage },
    { name: 'Forwood', tag: 'Enterprise RAG', color: C.blue }
  ];
  return (
    <span className="hero-hl" tabIndex={0}
      onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}
      onFocus={() => setOpen(true)} onBlur={() => setOpen(false)}>
      apps people actually open
      <span className={`hero-hl-pop${open ? ' is-open' : ''}`} role="tooltip" aria-hidden={!open}>
        <span className="hero-hl-pop-label">Real ones I've shipped</span>
        {products.map((p) =>
          <span className="hero-hl-pop-item" key={p.name}>
            <span className="hero-hl-dot" style={{ background: p.color }} />
            <b>{p.name}</b><em>{p.tag}</em>
          </span>
        )}
      </span>
    </span>);

}

// ---------- hero ----------
function Hero() {
  return (
    <div className="hero-stage">
      <div className="hero-copy">
        <h1 className="hero-h1">I build AI<br /><span className="hero-accent">that ships.</span></h1>
        <p className="hero-lede">
          A demo just has to work once. The <HeroHighlight /> need it to work every time.
          That comes down to decisions nobody sees: what to build, what to reuse, what to reject.
        </p>
        <div className="hero-ctas">
          <a className="hero-btn hero-btn-solid" href="AI Portfolio.html">Selected work</a>
          <a className="hero-btn hero-btn-ghost" href="Writing.html">Blog</a>
        </div>
      </div>
    </div>);

}

// ---------- personality block ----------
function Personality() {
  return (
    <section className="frame-wrap" style={{ padding: '8px 0 78px' }}>
      <div className="home-personality-grid" style={{
        display: 'grid', gridTemplateColumns: 'minmax(0,420px) minmax(0,1fr)',
        gap: 56, alignItems: 'center'
      }}>
        {/* photo */}
        <div className="home-photo" style={{ position: 'relative' }}>
          <div style={{
            position: 'absolute', left: -16, top: -16, width: 70, height: 70,
            background: C.mustard, borderRadius: 22, zIndex: 0
          }} />
          <div style={{
            position: 'absolute', right: -14, bottom: -14, zIndex: 0, opacity: 0.9
          }}><M.DotGrid cols={5} rows={4} gap={11} /></div>
          <div style={{
            position: 'relative', zIndex: 1, borderRadius: 28, overflow: 'hidden',
            aspectRatio: '4 / 5', background: C.sand
          }}>
            <img src="headshot.png" alt="Syed Hamza Tahir"
              style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
          </div>
          <div style={{
            marginTop: 14, fontFamily: "'JetBrains Mono', monospace", fontSize: 10,
            letterSpacing: '0.12em', textTransform: 'uppercase', opacity: 0.6,
            display: 'flex', alignItems: 'center', gap: 8
          }}>
            <span style={{ display: 'inline-block', width: 6, height: 6, background: 'var(--ink)' }} />
            Islamabad, Pakistan
          </div>
        </div>

        {/* narrative */}
        <div>
          <SectionLabel>Who I am</SectionLabel>
          <h2 style={{ margin: '20px 0 22px', fontWeight: 700, letterSpacing: '-0.03em', lineHeight: 0.98, fontSize: 'clamp(32px, 4.4vw, 54px)', textWrap: 'balance' }}>
            It's never really about the model.
          </h2>
          <div style={{ maxWidth: 660, fontSize: 'clamp(16px, 1.7vw, 19px)', lineHeight: 1.6, opacity: 0.86, textWrap: 'pretty' }}>
            <p style={{ margin: '0 0 18px' }}>
              Three years building AI systems end-to-end. That means model selection, integration
              into the product, and the validation and monitoring layers that catch failures before
              users ever see them. The throughline across every engagement has been the same: AI
              that holds up under real use, not just AI that looks good in a demo.
            </p>
            <p style={{ margin: 0 }}>
              I care more about the boring parts than most people expect. Logging, edge case
              handling, the quiet checks that run in the background. That's usually where the
              actual risk lives, and it's the part of the job I've ended up specializing in without
              really planning to.
            </p>
          </div>
        </div>
      </div>
    </section>);

}

// ---------- FAQ ----------
function FAQ() {
  const items = [
  { q: 'What do you actually do all day?', a: 'I design and ship AI systems end to end — model selection, agent orchestration, retrieval, safety, and the production monitoring that keeps it honest once real users show up.' },
  { q: 'What kind of work are you best at?', a: 'Anything where a model has to be reliable, not just impressive. Agentic workflows, LLM product features, and RAG over messy, real-world data.' },
  { q: "What's in your toolkit?", a: 'Python and PyTorch; LangChain and LangGraph for orchestration; Qdrant, OpenSearch and pgvector for retrieval; vLLM and Triton for inference; AWS and GCP for everything else.' },
  { q: 'Do you write, too?', a: '80+ technical articles for AI-native companies like Qdrant, ZenML, Comet and TigerData — plus a few essays of my own over on the Writing page.' },
  { q: 'Can we work together?', a: "Probably. Tell me what you're building and where it hurts — the harder the reliability problem, the more interested I am." },
  { q: 'Where are you based?', a: 'Islamabad, Pakistan — working with teams worldwide.' }];

  const [open, setOpen] = useState(0);
  return (
    <section className="frame-wrap" style={{ paddingBottom: 80 }}>
      <SectionLabel>FAQ</SectionLabel>
      <div style={{ marginTop: 22, borderTop: '1px solid var(--ink)' }}>
        {items.map((it, i) => {
          const isOpen = open === i;
          return (
            <div key={i} style={{ borderBottom: '1px solid var(--ink)' }}>
              <button onClick={() => setOpen(isOpen ? -1 : i)} style={{
                width: '100%', background: 'transparent', border: 'none', cursor: 'pointer',
                display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 20,
                padding: '22px 2px', textAlign: 'left', color: 'var(--ink)', font: 'inherit'
              }}>
                <span style={{ fontSize: 'clamp(19px, 2.2vw, 24px)', fontWeight: 600, letterSpacing: '-0.01em' }}>{it.q}</span>
                <span style={{
                  flexShrink: 0, width: 30, height: 30, display: 'grid', placeItems: 'center',
                  transform: isOpen ? 'rotate(45deg)' : 'rotate(0deg)', transition: 'transform 260ms cubic-bezier(.2,.8,.2,1)'
                }}>
                  <M.Plus s={20} />
                </span>
              </button>
              <div style={{
                maxHeight: isOpen ? 240 : 0, overflow: 'hidden',
                transition: 'max-height 320ms cubic-bezier(.2,.8,.2,1), opacity 280ms, padding 320ms',
                opacity: isOpen ? 1 : 0, paddingBottom: isOpen ? 24 : 0
              }}>
                <p style={{ margin: 0, maxWidth: 720, fontSize: 16.5, lineHeight: 1.6, opacity: 0.85, textWrap: 'pretty' }}>{it.a}</p>
              </div>
            </div>);

        })}
      </div>
    </section>);

}

// ---------- CTA + footer ----------
function CTA() {
  return (
    <section style={{ background: 'var(--ink)', color: 'var(--paper)' }}>
      <div className="frame-wrap contact-grid" style={{ padding: '74px 0', display: 'grid', gridTemplateColumns: 'minmax(0,1fr) auto', gap: 30, alignItems: 'center' }}>
        <div>
          <div style={{ fontFamily: "'Cormorant Garamond', serif", fontStyle: 'italic', fontSize: 22, opacity: 0.8, marginBottom: 10 }}>Got a hard problem?</div>
          <h2 style={{ margin: 0, fontWeight: 700, letterSpacing: '-0.03em', lineHeight: 0.95, fontSize: 'clamp(38px, 6vw, 72px)' }}>Let's build it.</h2>
        </div>
        <a href={'mailto:' + EMAIL} style={{
          justifySelf: 'end', textDecoration: 'none', whiteSpace: 'nowrap',
          padding: '16px 30px', borderRadius: 999, border: '1.5px solid var(--paper)',
          color: 'var(--ink)', background: 'var(--paper)', fontFamily: "'JetBrains Mono', monospace", fontSize: 13
        }}>{EMAIL} →</a>
      </div>
      <div className="frame-wrap" style={{ borderTop: '1px solid rgba(237,230,220,0.25)' }}>
        <div style={{
          padding: '20px 0 40px', display: 'flex', justifyContent: 'space-between', flexWrap: 'wrap', gap: 16,
          fontFamily: "'JetBrains Mono', monospace", fontSize: 10, letterSpacing: '0.1em', textTransform: 'uppercase', opacity: 0.75
        }}>
          <span>Blobolio © 2026</span>
          <div style={{ display: 'flex', gap: 22 }}>
            <a href="https://www.linkedin.com" style={{ color: 'inherit', textDecoration: 'none' }}>LinkedIn</a>
            <a href="https://github.com" style={{ color: 'inherit', textDecoration: 'none' }}>GitHub</a>
            <a href="https://medium.com" style={{ color: 'inherit', textDecoration: 'none' }}>Medium</a>
          </div>
          <span>Islamabad, PK</span>
        </div>
      </div>
    </section>);

}

function App() {
  return (
    <div>
      <div className="chrome-shell">
        <window.Chrome.Frame />
        <window.Chrome.Nav active="Home" />
        <window.LatentZoom><Hero /></window.LatentZoom>
        {/* Inside .chrome-shell on purpose. The frame's two vertical rules are
            meant to run the full length of the pinned zoom AND this tray, with
            the bottom rule closing the frame once the tray ends. That makes the
            shell about 2.3 viewports tall by design -- it is not a first-screen
            device. */}
        <window.ClientTray />
      </div>
      <Personality />
      <ProjectDashboard />
      <window.Services />
      <FAQ />
      <CTA />
    </div>);

}

// Registered, not mounted: router.js owns mounting so Barba can swap
// containers and re-mount without a full page load.
window.Pages = window.Pages || {};
window.Pages.home = App;
})();
