/* Shared portfolio components — Titlebar, Statusbar, Footer, ProjectThumb */

const { useState, useEffect, useRef } = React;

// Tweakable defaults
window.PORTFOLIO_TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "dark",
  "accent": "#ec5e94",
  "density": "comfortable"
}/*EDITMODE-END*/;

function applyTweaks(t) {
  document.documentElement.setAttribute('data-theme', t.theme);
  document.documentElement.setAttribute('data-density', t.density);
  document.documentElement.style.setProperty('--accent', t.accent);
}

// Apply on load — read persisted theme override if present
(function initTweaks(){
  const saved = (typeof localStorage !== 'undefined') ? localStorage.getItem('gg-theme') : null;
  const t = { ...window.PORTFOLIO_TWEAK_DEFAULTS };
  if (saved === 'light' || saved === 'dark' || saved === 'dim') t.theme = saved;
  applyTweaks(t);
  window.PORTFOLIO_TWEAK_DEFAULTS = t;
})();

function ThemeToggle() {
  const [theme, setTheme] = useState(() => document.documentElement.getAttribute('data-theme') || 'dark');
  function toggle() {
    const next = theme === 'dark' ? 'light' : 'dark';
    setTheme(next);
    document.documentElement.setAttribute('data-theme', next);
    try { localStorage.setItem('gg-theme', next); } catch (e) {}
    // notify any listening Tweaks panels
    try { window.parent?.postMessage({ type: '__edit_mode_set_keys', edits: { theme: next } }, '*'); } catch (e) {}
  }
  return (
    <button
      type="button"
      className={`theme-toggle theme-${theme}`}
      onClick={toggle}
      aria-label={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
      title={theme === 'dark' ? 'Switch to light' : 'Switch to dark'}
    >
      {theme === 'dark' ? (
        <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" aria-hidden="true">
          <circle cx="12" cy="12" r="4.2"/>
          <path d="M12 2.4v2.4M12 19.2v2.4M2.4 12h2.4M19.2 12h2.4M5.2 5.2l1.7 1.7M17.1 17.1l1.7 1.7M5.2 18.8l1.7-1.7M17.1 6.9l1.7-1.7"/>
        </svg>
      ) : (
        <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
          <path d="M20.5 14.6A8.4 8.4 0 0 1 9.4 3.5a8.4 8.4 0 1 0 11.1 11.1z"/>
        </svg>
      )}
    </button>
  );
}

function Titlebar({ active, path }) {
  const isInProjects = path?.startsWith('projects/');
  const indexHref = isInProjects ? '../index.html' : 'index.html';
  const aboutHref = isInProjects ? '../about.html' : 'about.html';
  const contactHref = isInProjects ? '../contact.html' : 'contact.html';
  const projectsHref = isInProjects ? '../projects.html' : 'projects.html';
  const projectsActive = active === 'projects' || active === 'work' || active === 'school';
  return (
    <header className="site-nav">
      <a href={indexHref} className="brand" aria-label="ggonzales.dev — Genevieve Gonzales">
        <pre className="brand-mark" aria-hidden="true">{` ██████╗  ██████╗
██╔════╝ ██╔════╝
██║  ███╗██║  ███╗
╚██████╔╝╚██████╔╝
 ╚═════╝  ╚═════╝`}</pre>
        <span className="brand-word">ggonzales.dev</span>
      </a>
      <nav className="links" aria-label="Primary">
        <a href={projectsHref} className={projectsActive ? 'on' : ''}>Projects</a>
        <a href={aboutHref} className={active==='about' ? 'on' : ''}>About</a>
      </nav>
      <div className="nav-end">
        <a href={contactHref} className={`nav-contact${active==='contact'?' on':''}`}>
          Contact
        </a>
        <ThemeToggle/>
      </div>
    </header>
  );
}

function Statusbar({ extra }) {
  const year = new Date().getFullYear();
  return (
    <footer className="site-footer">
      <div className="ft-inner">
        <div className="ft-col">
          <pre className="ft-brand-mark" aria-hidden="true">{` ██████╗  ██████╗
██╔════╝ ██╔════╝
██║  ███╗██║  ███╗
╚██████╔╝╚██████╔╝
 ╚═════╝  ╚═════╝`}</pre>
          <div className="ft-brand">ggonzales.dev <span className="ft-brand-sub">— Genevieve Gonzales</span></div>
          <div className="ft-meta">Product Solutions Analyst · En-Vision America · Palmetto, FL</div>
        </div>
        <div className="ft-col">
          <div className="ft-h">Site</div>
          <a href="index.html">Home</a>
          <a href="projects.html">Projects</a>
          <a href="about.html">About</a>
          <a href="contact.html">Contact</a>
        </div>
        <div className="ft-col ft-end">
          <div className="ft-h">Elsewhere</div>
          <a href="mailto:genbgon7@gmail.com">Email <span>↗</span></a>
          <a href="https://www.linkedin.com/in/genevieve-gonzales" target="_blank" rel="noopener">LinkedIn <span>↗</span></a>
        </div>
      </div>
      <div className="ft-baseline">© {year} · Genevieve Gonzales</div>
    </footer>
  );
}

// Realistic miniature UI mockups for each project — small product previews.
// Each one mimics the actual app's layout at thumbnail scale.
function ProjectThumb({ vibe, accent, accentSoft, title }) {
  const common = { width: '100%', height: '100%', display: 'block' };
  const BG = "#0d0e10";
  const PANEL = "#16181c";
  const PANEL_2 = "#1c1e23";
  const LINE = "#26282e";
  const TEXT_DIM = "#3a3d45";
  const TEXT_MUTE = "#5a5d66";

  // EVA — chat-style agent surface with sidebar of agents and a trace rail
  if (vibe === 'agents') {
    return (
      <svg viewBox="0 0 400 300" style={common} preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="300" fill={BG}/>
        {/* app chrome */}
        <rect x="0" y="0" width="400" height="20" fill={PANEL}/>
        <circle cx="12" cy="10" r="3" fill="#2a2a2a"/><circle cx="22" cy="10" r="3" fill="#2a2a2a"/><circle cx="32" cy="10" r="3" fill="#2a2a2a"/>
        <text x="200" y="13" textAnchor="middle" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="8">eva · session #482</text>
        {/* left rail — agents */}
        <rect x="0" y="20" width="78" height="280" fill={PANEL}/>
        <text x="10" y="36" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">AGENTS</text>
        {[0,1,2,3].map(i => (
          <g key={i}>
            <rect x="6" y={44 + i*30} width="66" height="24" rx="4" fill={i===1 ? accentSoft : 'transparent'} stroke={i===1 ? accent : 'transparent'}/>
            <circle cx="16" cy={56 + i*30} r="3" fill={i===1 ? accent : TEXT_DIM}/>
            <rect x="24" y={52 + i*30} width="36" height="3" rx="1" fill={i===1 ? accent : TEXT_DIM}/>
            <rect x="24" y={59 + i*30} width="24" height="2" rx="1" fill={TEXT_MUTE} opacity="0.6"/>
          </g>
        ))}
        {/* center — chat */}
        <rect x="78" y="20" width="232" height="280" fill={BG}/>
        {/* user message */}
        <rect x="180" y="36" width="120" height="22" rx="6" fill={PANEL_2}/>
        <rect x="190" y="44" width="100" height="2" rx="1" fill={TEXT_MUTE}/>
        <rect x="190" y="50" width="60" height="2" rx="1" fill={TEXT_MUTE}/>
        {/* agent message */}
        <rect x="88" y="68" width="180" height="48" rx="6" fill={PANEL}/>
        <circle cx="96" cy="76" r="3" fill={accent}/>
        <rect x="103" y="74" width="40" height="2.5" rx="1" fill={accent}/>
        <rect x="96" y="86" width="160" height="2" rx="1" fill={TEXT_DIM}/>
        <rect x="96" y="92" width="140" height="2" rx="1" fill={TEXT_DIM}/>
        <rect x="96" y="98" width="100" height="2" rx="1" fill={TEXT_DIM}/>
        <rect x="96" y="106" width="120" height="2" rx="1" fill={TEXT_DIM}/>
        {/* tool call */}
        <rect x="88" y="124" width="160" height="20" rx="4" fill={accentSoft} stroke={accent} strokeWidth="0.5"/>
        <text x="94" y="137" fill={accent} fontFamily="JetBrains Mono, monospace" fontSize="7">▸ search.run("translation logs")</text>
        {/* streaming */}
        <rect x="88" y="152" width="180" height="8" rx="2" fill={PANEL}/>
        <rect x="88" y="152" width="110" height="8" rx="2" fill={accent} opacity="0.4"/>
        {/* input */}
        <rect x="88" y="270" width="216" height="22" rx="6" fill={PANEL_2} stroke={LINE}/>
        <rect x="96" y="278" width="80" height="2.5" rx="1" fill={TEXT_MUTE}/>
        <circle cx="294" cy="281" r="6" fill={accent}/>
        {/* right — trace */}
        <rect x="310" y="20" width="90" height="280" fill={PANEL}/>
        <text x="318" y="36" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">TRACE</text>
        {[0,1,2,3,4,5].map(i => (
          <g key={i}>
            <line x1="324" y1={48 + i*26} x2="324" y2={62 + i*26} stroke={LINE} strokeWidth="1"/>
            <circle cx="324" cy={50 + i*26} r="2" fill={i<3 ? accent : TEXT_DIM}/>
            <rect x="332" y={47 + i*26} width="56" height="2.5" rx="1" fill={i<3 ? accent : TEXT_DIM} opacity={i<3 ? 1 : 0.5}/>
            <rect x="332" y={54 + i*26} width="40" height="2" rx="1" fill={TEXT_MUTE} opacity="0.6"/>
          </g>
        ))}
      </svg>
    );
  }

  // TMT — Windows desktop app: ribbon, file browser, queue table
  if (vibe === 'desktop') {
    return (
      <svg viewBox="0 0 400 300" style={common} preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="300" fill="#1f1f24"/>
        {/* window chrome (Windows-style) */}
        <rect x="0" y="0" width="400" height="22" fill="#2a2a30"/>
        <text x="10" y="14" fill={TEXT_MUTE} fontFamily="Inter, sans-serif" fontSize="8" fontWeight="500">Translation Management Tool</text>
        <text x="360" y="14" fill={TEXT_MUTE} fontFamily="Inter, sans-serif" fontSize="9">— ☐ ✕</text>
        {/* menu bar */}
        <rect x="0" y="22" width="400" height="16" fill="#252529"/>
        {['File','Edit','View','Tools','Help'].map((m,i)=>(
          <text key={i} x={10 + i*36} y="33" fill={TEXT_MUTE} fontFamily="Inter, sans-serif" fontSize="8">{m}</text>
        ))}
        {/* toolbar */}
        <rect x="0" y="38" width="400" height="32" fill={PANEL_2}/>
        {[0,1,2,3,4].map(i=>(
          <rect key={i} x={10 + i*40} y="46" width="32" height="18" rx="2" fill={i===0 ? accentSoft : '#2d2d33'} stroke={i===0 ? accent : 'transparent'} strokeWidth="0.5"/>
        ))}
        <rect x="220" y="46" width="170" height="18" rx="2" fill="#1a1a1e" stroke={LINE}/>
        <text x="226" y="58" fill={TEXT_MUTE} fontFamily="Inter, sans-serif" fontSize="8">🔍 Search files…</text>
        {/* sidebar tree */}
        <rect x="0" y="70" width="100" height="210" fill="#1a1a1e"/>
        <text x="8" y="84" fill={TEXT_MUTE} fontFamily="Inter, sans-serif" fontSize="8" fontWeight="600">PROJECTS</text>
        {['▾ Sprint 29','  EN→JA','  EN→ZH','▸ Sprint 28','▸ Archive'].map((t,i)=>(
          <text key={i} x="8" y={102 + i*16} fill={i===1 ? accent : TEXT_DIM} fontFamily="Inter, sans-serif" fontSize="8" fontWeight={i===1?500:400}>{t}</text>
        ))}
        {/* main — queue table */}
        <rect x="100" y="70" width="300" height="210" fill={BG}/>
        {/* table header */}
        <rect x="100" y="70" width="300" height="18" fill={PANEL}/>
        {['File','Status','Words','Owner'].map((t,i)=>(
          <text key={i} x={108 + [0,110,180,230][i]} y="82" fill={TEXT_MUTE} fontFamily="Inter, sans-serif" fontSize="7" fontWeight="600">{t}</text>
        ))}
        {/* rows */}
        {[
          {n:'ch01_intro.xml', s:'Validating', w:'1,420', sc:accent},
          {n:'ch02_setup.xml', s:'In Review',  w:'2,210', sc:'#62c554'},
          {n:'ch03_combat.xml',s:'Translating',w:'3,890', sc:'#f5bf4f'},
          {n:'ch04_world.xml', s:'Queued',     w:'1,765', sc:TEXT_DIM},
          {n:'ch05_end.xml',   s:'Queued',     w:'  920', sc:TEXT_DIM},
          {n:'glossary.xml',   s:'Done',       w:'  340', sc:'#62c554'},
        ].map((r,i)=>(
          <g key={i}>
            <rect x="100" y={88 + i*22} width="300" height="22" fill={i%2 ? PANEL : 'transparent'}/>
            <text x="108" y={102 + i*22} fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">{r.n}</text>
            <circle cx="222" cy={99 + i*22} r="2.5" fill={r.sc}/>
            <text x="228" y={102 + i*22} fill={r.sc} fontFamily="Inter, sans-serif" fontSize="7">{r.s}</text>
            <text x="288" y={102 + i*22} fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">{r.w}</text>
            <rect x="338" y={94 + i*22} width="40" height="10" rx="2" fill={r.sc} opacity="0.25"/>
          </g>
        ))}
        {/* status bar */}
        <rect x="0" y="280" width="400" height="20" fill="#2a2a30"/>
        <text x="10" y="293" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">● Connected · 6 files · 10,545 words</text>
      </svg>
    );
  }

  // Scrum Memories — recall-style memory feed
  if (vibe === 'memory') {
    return (
      <svg viewBox="0 0 400 300" style={common} preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="300" fill={BG}/>
        {/* top bar */}
        <rect x="0" y="0" width="400" height="40" fill={PANEL}/>
        <circle cx="20" cy="20" r="8" fill={accent} opacity="0.2" stroke={accent} strokeWidth="1"/>
        <text x="20" y="23" textAnchor="middle" fill={accent} fontFamily="Space Grotesk, sans-serif" fontSize="9" fontWeight="700">SM</text>
        <text x="36" y="24" fill="#e8e6e0" fontFamily="Space Grotesk, sans-serif" fontSize="11" fontWeight="600">Scrum Memories</text>
        <rect x="160" y="13" width="180" height="14" rx="7" fill={PANEL_2} stroke={LINE}/>
        <text x="168" y="23" fill={TEXT_MUTE} fontFamily="Inter, sans-serif" fontSize="7">recall: "decisions about caching"</text>
        <circle cx="370" cy="20" r="8" fill={accentSoft}/>
        {/* sidebar */}
        <rect x="0" y="40" width="80" height="260" fill={PANEL}/>
        <text x="10" y="56" fill={TEXT_MUTE} fontFamily="Inter, sans-serif" fontSize="7" fontWeight="600">SPRINTS</text>
        {[
          {n:'Sprint 29', a:true},
          {n:'Sprint 28', a:false},
          {n:'Sprint 27', a:false},
          {n:'Sprint 26', a:false},
        ].map((s,i)=>(
          <g key={i}>
            <rect x="6" y={64 + i*22} width="68" height="18" rx="3" fill={s.a ? accentSoft : 'transparent'}/>
            <text x="12" y={76 + i*22} fill={s.a ? accent : TEXT_DIM} fontFamily="Inter, sans-serif" fontSize="8" fontWeight={s.a?500:400}>{s.n}</text>
          </g>
        ))}
        {/* main feed */}
        <rect x="80" y="40" width="320" height="260" fill={BG}/>
        <text x="92" y="60" fill="#e8e6e0" fontFamily="Space Grotesk, sans-serif" fontSize="13" fontWeight="600">Sprint 29 · memories</text>
        <text x="240" y="60" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">14 entries</text>
        {[
          {tag:'DECISION', tagC:accent, t:'Use Redis for session cache', meta:'Mar 14 · 3 mentions'},
          {tag:'BLOCKER',  tagC:'#ed6a5e', t:'Auth flow breaks on Safari 16', meta:'Mar 13 · resolved'},
          {tag:'STANDUP',  tagC:'#f5bf4f', t:'API contract aligned with backend', meta:'Mar 12'},
        ].map((m,i)=>(
          <g key={i}>
            <rect x="92" y={74 + i*64} width="296" height="54" rx="6" fill={PANEL} stroke={LINE}/>
            <rect x="100" y={82 + i*64} width="48" height="11" rx="2" fill={m.tagC} opacity="0.18"/>
            <text x="124" y={90 + i*64} textAnchor="middle" fill={m.tagC} fontFamily="JetBrains Mono, monospace" fontSize="6" fontWeight="600">{m.tag}</text>
            <text x="100" y={108 + i*64} fill="#e8e6e0" fontFamily="Inter, sans-serif" fontSize="9" fontWeight="500">{m.t}</text>
            <text x="100" y={120 + i*64} fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">{m.meta}</text>
          </g>
        ))}
      </svg>
    );
  }

  // Translation Verifier — CLI / diff output style
  if (vibe === 'verify') {
    return (
      <svg viewBox="0 0 400 300" style={common} preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="300" fill="#0a0b0d"/>
        {/* terminal chrome */}
        <rect x="0" y="0" width="400" height="22" fill="#1a1c20"/>
        <circle cx="12" cy="11" r="3" fill="#2a2a2a"/><circle cx="22" cy="11" r="3" fill="#2a2a2a"/><circle cx="32" cy="11" r="3" fill="#2a2a2a"/>
        <text x="200" y="14" textAnchor="middle" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="8">verify · sprint-29</text>
        {/* command */}
        <text x="14" y="42" fill={accent} fontFamily="JetBrains Mono, monospace" fontSize="9">$</text>
        <text x="26" y="42" fill="#e8e6e0" fontFamily="JetBrains Mono, monospace" fontSize="9">verify --spec game-strict ./out/ja</text>
        {/* output lines */}
        <text x="14" y="62" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="8">› Loading 142 files...</text>
        <text x="14" y="76" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="8">› Running 8 validators</text>

        {/* check rows */}
        {[
          {ok:true,  t:'tag-balance.spec',     n:'142 / 142'},
          {ok:true,  t:'placeholder-parity',   n:'142 / 142'},
          {ok:true,  t:'length-budget',        n:'140 / 142', warn:true},
          {ok:false, t:'glossary.consistency', n:'138 / 142'},
          {ok:true,  t:'no-orphan-tokens',     n:'142 / 142'},
        ].map((r,i)=>(
          <g key={i}>
            <text x="14" y={100 + i*18} fill={r.ok ? (r.warn ? '#f5bf4f' : '#62c554') : '#ed6a5e'} fontFamily="JetBrains Mono, monospace" fontSize="9">{r.ok ? '✓' : '✗'}</text>
            <text x="28" y={100 + i*18} fill="#e8e6e0" fontFamily="JetBrains Mono, monospace" fontSize="8">{r.t}</text>
            <text x="320" y={100 + i*18} fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="8">{r.n}</text>
          </g>
        ))}

        {/* diff block */}
        <rect x="14" y="200" width="372" height="82" rx="4" fill={PANEL} stroke={LINE}/>
        <text x="22" y="216" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">› ch04_world.xml:128  glossary mismatch</text>
        <rect x="22" y="222" width="356" height="14" fill="rgba(237,106,94,0.10)"/>
        <text x="28" y="232" fill="#ed6a5e" fontFamily="JetBrains Mono, monospace" fontSize="8">- 戦士  (warrior)</text>
        <rect x="22" y="238" width="356" height="14" fill="rgba(98,197,84,0.10)"/>
        <text x="28" y="248" fill="#62c554" fontFamily="JetBrains Mono, monospace" fontSize="8">+ 兵士  (soldier — per glossary)</text>
        <text x="22" y="270" fill={accent} fontFamily="JetBrains Mono, monospace" fontSize="8">  fix? (y/n) ▎</text>
      </svg>
    );
  }

  // Translation Model Dashboard — stats panel with metrics, sparkline, model rows
  if (vibe === 'dashboard') {
    return (
      <svg viewBox="0 0 400 300" style={common} preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="300" fill={BG}/>
        {/* top bar */}
        <rect x="0" y="0" width="400" height="32" fill={PANEL}/>
        <text x="14" y="20" fill="#e8e6e0" fontFamily="Space Grotesk, sans-serif" fontSize="11" fontWeight="600">Model Dashboard</text>
        <text x="120" y="20" fill={TEXT_MUTE} fontFamily="Inter, sans-serif" fontSize="8">EN→JA · last 30d</text>
        <rect x="320" y="8" width="70" height="16" rx="3" fill={accentSoft} stroke={accent} strokeWidth="0.5"/>
        <text x="355" y="19" textAnchor="middle" fill={accent} fontFamily="Inter, sans-serif" fontSize="8" fontWeight="500">Compare</text>
        {/* metric cards */}
        {[
          {l:'BLEU',    v:'42.7', d:'+1.2'},
          {l:'chrF++',  v:'68.4', d:'+0.8'},
          {l:'COMET',   v:'0.84', d:'+0.02'},
          {l:'Latency', v:'0.31s',d:'-12%'},
        ].map((m,i)=>(
          <g key={i}>
            <rect x={14 + i*94} y="44" width="86" height="56" rx="6" fill={PANEL} stroke={LINE}/>
            <text x={22 + i*94} y="58" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">{m.l}</text>
            <text x={22 + i*94} y="82" fill="#e8e6e0" fontFamily="Space Grotesk, sans-serif" fontSize="18" fontWeight="600">{m.v}</text>
            <text x={22 + i*94} y="94" fill={accent} fontFamily="JetBrains Mono, monospace" fontSize="7">▲ {m.d}</text>
          </g>
        ))}
        {/* big chart */}
        <rect x="14" y="110" width="240" height="120" rx="6" fill={PANEL} stroke={LINE}/>
        <text x="22" y="124" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">BLEU OVER TIME</text>
        {/* gridlines */}
        {[0,1,2,3].map(i=>(
          <line key={i} x1="22" y1={146 + i*18} x2="246" y2={146 + i*18} stroke={LINE} strokeWidth="0.5" strokeDasharray="2 2"/>
        ))}
        {/* line */}
        <polyline points="26,200 50,190 74,180 98,185 122,165 146,170 170,150 194,148 218,140 242,135" stroke={accent} strokeWidth="1.5" fill="none"/>
        <polyline points="26,200 50,190 74,180 98,185 122,165 146,170 170,150 194,148 218,140 242,135 242,220 26,220" stroke="none" fill={accentSoft}/>
        {[26,74,122,170,218].map((x,i)=>(
          <circle key={i} cx={x} cy={[200,180,165,150,140][i]} r="2" fill={accent}/>
        ))}
        {/* rankings */}
        <rect x="262" y="110" width="124" height="120" rx="6" fill={PANEL} stroke={LINE}/>
        <text x="270" y="124" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">MODEL LEADERBOARD</text>
        {[
          {n:'mt-v4.2', s:'42.7'},
          {n:'mt-v4.1', s:'41.5'},
          {n:'mt-v3.8', s:'40.2'},
          {n:'baseline',s:'37.1'},
        ].map((r,i)=>(
          <g key={i}>
            <rect x="270" y={134 + i*22} width="108" height="18" rx="3" fill={i===0 ? accentSoft : 'transparent'}/>
            <text x="276" y={147 + i*22} fill={i===0 ? accent : TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="8" fontWeight={i===0?600:400}>0{i+1}</text>
            <text x="294" y={147 + i*22} fill={i===0 ? '#e8e6e0' : TEXT_DIM} fontFamily="Inter, sans-serif" fontSize="8">{r.n}</text>
            <text x="362" y={147 + i*22} textAnchor="end" fill={i===0 ? accent : TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="8">{r.s}</text>
          </g>
        ))}
        {/* footer band */}
        <rect x="14" y="240" width="372" height="48" rx="6" fill={PANEL} stroke={LINE}/>
        <text x="22" y="254" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">REGRESSIONS · 4 segments</text>
        {[0,1,2,3,4,5,6,7,8,9,10,11].map(i=>(
          <rect key={i} x={22 + i*30} y="262" width="22" height="20" rx="2" fill={[2,7].includes(i) ? '#ed6a5e' : accent} opacity={[2,7].includes(i) ? 0.7 : 0.3 + Math.random()*0.5}/>
        ))}
      </svg>
    );
  }

  // Notes Collector — pipeline / job runner
  if (vibe === 'scrape') {
    return (
      <svg viewBox="0 0 400 300" style={common} preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="300" fill={BG}/>
        {/* top bar */}
        <rect x="0" y="0" width="400" height="28" fill={PANEL}/>
        <text x="14" y="18" fill="#e8e6e0" fontFamily="Space Grotesk, sans-serif" fontSize="10" fontWeight="600">notes-collector</text>
        <circle cx="120" cy="14" r="3" fill="#2a2a2a"/>
        <text x="128" y="17" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">running · next in 04:12</text>
        <text x="380" y="18" textAnchor="end" fill={accent} fontFamily="JetBrains Mono, monospace" fontSize="8">▶ Run now</text>
        {/* pipeline diagram */}
        <text x="14" y="46" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">PIPELINE</text>
        {[
          {x:14, t:'Cron', s:'*/15'},
          {x:90, t:'Fetch', s:'14 src'},
          {x:166,t:'Parse', s:'PW'},
          {x:242,t:'Normalize', s:'schema'},
          {x:328,t:'Sink', s:'DB'},
        ].map((n,i)=>(
          <g key={i}>
            <rect x={n.x} y="54" width="60" height="36" rx="4" fill={PANEL} stroke={i<3 ? accent : LINE} strokeWidth={i<3 ? 1 : 0.5}/>
            <text x={n.x + 30} y="70" textAnchor="middle" fill={i<3 ? accent : '#e8e6e0'} fontFamily="Inter, sans-serif" fontSize="8" fontWeight="500">{n.t}</text>
            <text x={n.x + 30} y="82" textAnchor="middle" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="6.5">{n.s}</text>
            {i<4 && <path d={`M ${n.x + 60} 72 L ${n.x + 76} 72`} stroke={i<2 ? accent : LINE} strokeWidth="1"/>}
            {i<4 && <path d={`M ${n.x + 73} 69 L ${n.x + 76} 72 L ${n.x + 73} 75`} stroke={i<2 ? accent : LINE} strokeWidth="1" fill="none"/>}
          </g>
        ))}
        {/* recent runs table */}
        <text x="14" y="112" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">RECENT RUNS</text>
        <rect x="14" y="118" width="372" height="22" fill={PANEL}/>
        {['Time','Source','Records','Duration','Status'].map((h,i)=>(
          <text key={i} x={[22,90,200,260,330][i]} y="132" fill={TEXT_MUTE} fontFamily="Inter, sans-serif" fontSize="7" fontWeight="600">{h}</text>
        ))}
        {[
          {t:'14:30', s:'notion + 3', r:'1,247', d:'42s', st:'OK', sc:'#62c554'},
          {t:'14:15', s:'notion + 3', r:'1,238', d:'39s', st:'OK', sc:'#62c554'},
          {t:'14:00', s:'notion + 3', r:'1,220', d:'1m04', st:'WARN', sc:'#f5bf4f'},
          {t:'13:45', s:'notion + 3', r:'1,201', d:'38s', st:'OK', sc:'#62c554'},
          {t:'13:30', s:'notion + 3', r:'  892', d:'53s', st:'RETRY', sc:accent},
          {t:'13:15', s:'notion + 3', r:'1,184', d:'40s', st:'OK', sc:'#62c554'},
        ].map((r,i)=>(
          <g key={i}>
            <rect x="14" y={140 + i*22} width="372" height="22" fill={i%2 ? PANEL : 'transparent'}/>
            <text x="22" y={154 + i*22} fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">{r.t}</text>
            <text x="90" y={154 + i*22} fill="#e8e6e0" fontFamily="Inter, sans-serif" fontSize="7">{r.s}</text>
            <text x="200" y={154 + i*22} fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">{r.r}</text>
            <text x="260" y={154 + i*22} fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">{r.d}</text>
            <circle cx="334" cy={150 + i*22} r="2.5" fill={r.sc}/>
            <text x="342" y={154 + i*22} fill={r.sc} fontFamily="JetBrains Mono, monospace" fontSize="7" fontWeight="600">{r.st}</text>
          </g>
        ))}
      </svg>
    );
  }

  // Gengo Timer — focus app, large timer + task list
  if (vibe === 'timer') {
    return (
      <svg viewBox="0 0 400 300" style={common} preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="300" fill={BG}/>
        {/* top bar */}
        <rect x="0" y="0" width="400" height="28" fill={PANEL}/>
        <text x="14" y="18" fill="#e8e6e0" fontFamily="Space Grotesk, sans-serif" fontSize="10" fontWeight="600">Gengo Timer</text>
        <text x="380" y="18" textAnchor="end" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="8">2 of 4 sessions · 1h 23m today</text>
        {/* big timer card */}
        <rect x="14" y="42" width="220" height="244" rx="8" fill={PANEL} stroke={LINE}/>
        <text x="124" y="64" textAnchor="middle" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="8">FOCUS · SESSION 2</text>
        {/* circular progress */}
        <circle cx="124" cy="160" r="74" fill="none" stroke={LINE} strokeWidth="6"/>
        <circle cx="124" cy="160" r="74" fill="none" stroke={accent} strokeWidth="6" strokeDasharray="465" strokeDashoffset="165" transform="rotate(-90 124 160)" strokeLinecap="round"/>
        <text x="124" y="158" textAnchor="middle" fill="#e8e6e0" fontFamily="Space Grotesk, sans-serif" fontSize="34" fontWeight="600">17:42</text>
        <text x="124" y="178" textAnchor="middle" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="8">remaining</text>
        {/* controls */}
        <circle cx="86" cy="252" r="14" fill={PANEL_2} stroke={LINE}/>
        <rect x="82" y="248" width="3" height="8" fill={TEXT_DIM}/><rect x="89" y="248" width="3" height="8" fill={TEXT_DIM}/>
        <circle cx="124" cy="252" r="18" fill={accent}/>
        <polygon points="119,246 119,258 132,252" fill={BG}/>
        <circle cx="162" cy="252" r="14" fill={PANEL_2} stroke={LINE}/>
        <polygon points="158,247 158,257 167,252" fill={TEXT_DIM}/><rect x="167" y="247" width="2" height="10" fill={TEXT_DIM}/>
        {/* task pane */}
        <rect x="244" y="42" width="142" height="128" rx="6" fill={PANEL} stroke={LINE}/>
        <text x="252" y="58" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">TODAY · 4 TASKS</text>
        {[
          {t:'Translate ch04', d:true,  cur:false},
          {t:'Review ch03',    d:false, cur:true},
          {t:'QA pass · ch02', d:false, cur:false},
          {t:'Glossary review',d:false, cur:false},
        ].map((task,i)=>(
          <g key={i}>
            <rect x="252" y={66 + i*22} width="126" height="20" rx="3" fill={task.cur ? accentSoft : 'transparent'}/>
            <rect x="258" y={72 + i*22} width="8" height="8" rx="2" fill={task.d ? accent : 'transparent'} stroke={task.d ? accent : TEXT_DIM}/>
            {task.d && <path d={`M 259 ${77 + i*22} L 261 ${79 + i*22} L 265 ${75 + i*22}`} stroke={BG} strokeWidth="1.2" fill="none"/>}
            <text x="272" y={80 + i*22} fill={task.d ? TEXT_DIM : (task.cur ? accent : '#e8e6e0')} fontFamily="Inter, sans-serif" fontSize="8" textDecoration={task.d ? 'line-through' : 'none'}>{task.t}</text>
          </g>
        ))}
        {/* stats */}
        <rect x="244" y="178" width="142" height="108" rx="6" fill={PANEL} stroke={LINE}/>
        <text x="252" y="194" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">THIS WEEK</text>
        {/* mini bar chart */}
        {['M','T','W','T','F','S','S'].map((d,i)=>{
          const h = [22,32,18,40,28,12,0][i];
          return (
            <g key={i}>
              <rect x={252 + i*18} y={244 - h} width="12" height={h} rx="2" fill={i===4 ? accent : TEXT_DIM} opacity={i===4 ? 1 : 0.5}/>
              <text x={258 + i*18} y="258" textAnchor="middle" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="6">{d}</text>
            </g>
          );
        })}
        <text x="252" y="278" fill="#e8e6e0" fontFamily="Space Grotesk, sans-serif" fontSize="11" fontWeight="600">8h 42m</text>
        <text x="296" y="278" fill={accent} fontFamily="JetBrains Mono, monospace" fontSize="7">▲ 18%</text>
      </svg>
    );
  }
  if (vibe === 'stitch') {
    // StitchKit — clothing pack IDE: sidebar with component slots,
    // grid of drawables with texture variants, validation panel.
    const slots = [
      { id: '03', code: 'uppr', n: 12, on: false },
      { id: '04', code: 'lowr', n: 8,  on: false },
      { id: '06', code: 'feet', n: 6,  on: false },
      { id: '11', code: 'jbib', n: 18, on: true  },
      { id: '08', code: 'accs', n: 4,  on: false },
    ];
    return (
      <svg viewBox="0 0 400 300" style={common} preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="300" fill={BG}/>
        {/* top bar */}
        <rect x="0" y="0" width="400" height="26" fill={PANEL}/>
        <text x="14" y="17" fill="#e8e6e0" fontFamily="Space Grotesk, sans-serif" fontSize="10" fontWeight="600">StitchKit</text>
        <text x="380" y="17" textAnchor="end" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="8">midnight_pack · 48 drawables</text>
        {/* left sidebar — slot list */}
        <rect x="0" y="26" width="92" height="274" fill={PANEL}/>
        <text x="10" y="44" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">SLOTS</text>
        {slots.map((s, i) => (
          <g key={i}>
            <rect x="6" y={52 + i*32} width="80" height="26" rx="3" fill={s.on ? accentSoft : 'transparent'} stroke={s.on ? accent : 'transparent'}/>
            <text x="12" y={62 + i*32} fill={s.on ? accent : TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">{s.id} · {s.code}</text>
            <text x="12" y={72 + i*32} fill={s.on ? '#e8e6e0' : TEXT_DIM} fontFamily="Inter, sans-serif" fontSize="8">{s.n} drawables</text>
          </g>
        ))}
        {/* main pane — drawable grid for jbib (active slot) */}
        <rect x="100" y="34" width="294" height="180" rx="6" fill={PANEL} stroke={LINE}/>
        <text x="110" y="50" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">jbib · 11 / TORSO 2 (TOP)</text>
        <text x="386" y="50" textAnchor="end" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">18 / 128</text>
        {/* drawable tiles 4x3 */}
        {Array.from({length: 12}).map((_, i) => {
          const c = i % 4, r = Math.floor(i / 4);
          const x = 110 + c * 70, y = 60 + r * 48;
          const isActive = i === 5;
          const variantBars = [3, 5, 2, 4, 6, 5, 3, 4, 2, 5, 3, 4][i];
          return (
            <g key={i}>
              <rect x={x} y={y} width="64" height="42" rx="4"
                fill={isActive ? accentSoft : PANEL_2}
                stroke={isActive ? accent : LINE}/>
              <text x={x + 6} y={y + 12} fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="6">{String(i).padStart(3,'0')}_u</text>
              {/* mock garment shape */}
              <path d={`M ${x+22} ${y+18} L ${x+30} ${y+16} L ${x+38} ${y+18} L ${x+42} ${y+30} L ${x+34} ${y+34} L ${x+26} ${y+34} L ${x+18} ${y+30} Z`}
                fill={isActive ? accent : TEXT_DIM} opacity={isActive ? 0.85 : 0.4}/>
              {/* texture variant dots */}
              {Array.from({length: variantBars}).map((_, vi) => (
                <circle key={vi} cx={x + 8 + vi * 6} cy={y + 38} r="1.5"
                  fill={isActive && vi < 4 ? accent : TEXT_DIM}/>
              ))}
            </g>
          );
        })}
        {/* validation panel */}
        <rect x="100" y="222" width="294" height="68" rx="6" fill={PANEL} stroke={LINE}/>
        <text x="110" y="238" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">VALIDATION · 2 ISSUES</text>
        <circle cx="114" cy="252" r="3" fill="#f59e0b"/>
        <text x="124" y="255" fill="#e8e6e0" fontFamily="Inter, sans-serif" fontSize="8">jbib_005 — texture <tspan fill={accent}>_d</tspan> missing variant</text>
        <circle cx="114" cy="268" r="3" fill="#ef4444"/>
        <text x="124" y="271" fill="#e8e6e0" fontFamily="Inter, sans-serif" fontSize="8">lowr — index gap at 003 · re-index suggested</text>
        <text x="386" y="255" textAnchor="end" fill={accent} fontFamily="JetBrains Mono, monospace" fontSize="7">re-index →</text>
        <text x="386" y="271" textAnchor="end" fill={TEXT_DIM} fontFamily="JetBrains Mono, monospace" fontSize="7">export FiveM →</text>
      </svg>
    );
  }
  if (vibe === 'city') {
    // Mantra RP — FiveM RP server status board: live player count,
    // factions roster, recent activity feed, server rules nav.
    const factions = [
      { code: 'PD',  name: 'Police Dept',     count: 14 },
      { code: 'EMS', name: 'Emergency Med',   count: 6  },
      { code: 'DOJ', name: 'Justice Dept',    count: 4  },
      { code: 'CIV', name: 'Civilian',        count: 38 },
      { code: 'BIZ', name: 'Player Biz',      count: 11 },
    ];
    const events = [
      { t: 'just now',  c: '#10b981', txt: 'Officer Reyes signed in to PD shift' },
      { t: '2m ago',    c: '#10b981', txt: 'New civilian application — approved' },
      { t: '5m ago',    c: '#f59e0b', txt: 'Bank robbery — Vinewood · resolved' },
      { t: '12m ago',   c: '#10b981', txt: 'Mantra Coffee — opened for the day' },
    ];
    return (
      <svg viewBox="0 0 400 300" style={common} preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="300" fill={BG}/>
        {/* top bar */}
        <rect x="0" y="0" width="400" height="26" fill={PANEL}/>
        <text x="14" y="17" fill="#e8e6e0" fontFamily="Space Grotesk, sans-serif" fontSize="10" fontWeight="600">Mantra RP</text>
        <circle cx="358" cy="13" r="3" fill="#10b981"/>
        <text x="365" y="17" fill="#10b981" fontFamily="JetBrains Mono, monospace" fontSize="8">LIVE</text>
        <text x="306" y="17" textAnchor="end" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="8">73 / 128 online</text>
        {/* hero bar — server status */}
        <rect x="14" y="36" width="372" height="46" rx="6" fill={PANEL} stroke={LINE}/>
        <text x="24" y="52" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">SERVER · BETA · v0.42</text>
        <text x="24" y="72" fill="#e8e6e0" fontFamily="Space Grotesk, sans-serif" fontSize="20" fontWeight="600">Welcome to the city</text>
        <rect x="298" y="48" width="78" height="22" rx="11" fill={accent}/>
        <text x="337" y="63" textAnchor="middle" fill={BG} fontFamily="Space Grotesk, sans-serif" fontSize="9" fontWeight="600">CONNECT →</text>
        {/* factions panel */}
        <rect x="14" y="92" width="180" height="194" rx="6" fill={PANEL} stroke={LINE}/>
        <text x="24" y="108" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">FACTIONS · 5</text>
        {factions.map((f, i) => (
          <g key={i}>
            <rect x="22" y={118 + i*30} width="164" height="24" rx="4" fill={i === 0 ? accentSoft : 'transparent'} stroke={i === 0 ? accent : 'transparent'}/>
            <rect x="28" y={124 + i*30} width="22" height="12" rx="2" fill={i === 0 ? accent : PANEL_2} stroke={LINE}/>
            <text x="39" y={133 + i*30} textAnchor="middle" fill={i === 0 ? BG : TEXT_DIM} fontFamily="JetBrains Mono, monospace" fontSize="6" fontWeight="600">{f.code}</text>
            <text x="56" y={133 + i*30} fill="#e8e6e0" fontFamily="Inter, sans-serif" fontSize="8">{f.name}</text>
            <text x="180" y={133 + i*30} textAnchor="end" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">{f.count}</text>
          </g>
        ))}
        {/* live activity feed */}
        <rect x="204" y="92" width="182" height="124" rx="6" fill={PANEL} stroke={LINE}/>
        <text x="214" y="108" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="7">ACTIVITY · LIVE</text>
        {events.map((e, i) => (
          <g key={i}>
            <circle cx="218" cy={122 + i*22} r="2.5" fill={e.c}/>
            <text x="226" y={125 + i*22} fill="#e8e6e0" fontFamily="Inter, sans-serif" fontSize="7.5">{e.txt}</text>
            <text x="226" y={134 + i*22} fill={TEXT_DIM} fontFamily="JetBrains Mono, monospace" fontSize="6">{e.t}</text>
          </g>
        ))}
        {/* nav cards */}
        <rect x="204" y="226" width="86" height="60" rx="6" fill={PANEL} stroke={LINE}/>
        <text x="214" y="242" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="6">RULES</text>
        <text x="214" y="258" fill="#e8e6e0" fontFamily="Space Grotesk, sans-serif" fontSize="11" fontWeight="600">City</text>
        <text x="214" y="272" fill={TEXT_DIM} fontFamily="Inter, sans-serif" fontSize="6">conduct · OOC · combat</text>
        <text x="282" y="278" textAnchor="end" fill={accent} fontFamily="JetBrains Mono, monospace" fontSize="7">→</text>
        <rect x="300" y="226" width="86" height="60" rx="6" fill={PANEL} stroke={LINE}/>
        <text x="310" y="242" fill={TEXT_MUTE} fontFamily="JetBrains Mono, monospace" fontSize="6">APPLY</text>
        <text x="310" y="258" fill="#e8e6e0" fontFamily="Space Grotesk, sans-serif" fontSize="11" fontWeight="600">Whitelist</text>
        <text x="310" y="272" fill={TEXT_DIM} fontFamily="Inter, sans-serif" fontSize="6">civilian · faction · biz</text>
        <text x="378" y="278" textAnchor="end" fill={accent} fontFamily="JetBrains Mono, monospace" fontSize="7">→</text>
      </svg>
    );
  }
  return null;
}

/* =====================================================================
   ProjectStage — Rescale-style "stage" wrapper around ProjectThumb.
   - Saturated pink gradient background
   - Vertical project wordmark down the left edge
   - Glassy 3D blob shapes (Rescale-style)
   - Window chrome (macOS or Windows) around the actual UI mockup
   - Optional 3D tilt for featured cards
   - Featured cards include title overlay inside the stage
   ===================================================================== */
function GGMark() {
  return (
    <svg viewBox="0 0 32 32" width="22" height="22" aria-hidden="true">
      <circle cx="16" cy="16" r="15" fill="none" stroke="currentColor" strokeWidth="1.2" opacity="0.5"/>
      <text x="16" y="21" textAnchor="middle" fontFamily="Newsreader, serif" fontStyle="italic" fontSize="15" fontWeight="500" fill="currentColor">gg</text>
    </svg>
  );
}

function GlassBlob({ variant = 1 }) {
  // Rescale-style 3D candy shape: gradient orb with soft highlight + shadow.
  const id = `gb${variant}`;
  return (
    <svg className={`gblob gblob-${variant}`} viewBox="0 0 200 200" aria-hidden="true">
      <defs>
        <radialGradient id={`${id}-grad`} cx="35%" cy="30%" r="80%">
          <stop offset="0%" stopColor="rgba(255,240,248,0.95)"/>
          <stop offset="40%" stopColor="rgba(255,200,225,0.75)"/>
          <stop offset="80%" stopColor="rgba(236,94,148,0.55)"/>
          <stop offset="100%" stopColor="rgba(196,72,120,0.30)"/>
        </radialGradient>
        <radialGradient id={`${id}-hl`} cx="30%" cy="25%" r="40%">
          <stop offset="0%" stopColor="rgba(255,255,255,0.95)"/>
          <stop offset="60%" stopColor="rgba(255,255,255,0.30)"/>
          <stop offset="100%" stopColor="rgba(255,255,255,0)"/>
        </radialGradient>
        <filter id={`${id}-soft`} x="-10%" y="-10%" width="120%" height="120%">
          <feGaussianBlur stdDeviation="0.5"/>
        </filter>
      </defs>
      {variant === 1 && (
        <>
          {/* Cross / plus shape */}
          <path d="M 100 20 Q 130 20 130 50 L 130 70 L 150 70 Q 180 70 180 100 Q 180 130 150 130 L 130 130 L 130 150 Q 130 180 100 180 Q 70 180 70 150 L 70 130 L 50 130 Q 20 130 20 100 Q 20 70 50 70 L 70 70 L 70 50 Q 70 20 100 20 Z" fill={`url(#${id}-grad)`} filter={`url(#${id}-soft)`}/>
          <ellipse cx="80" cy="55" rx="18" ry="10" fill={`url(#${id}-hl)`}/>
        </>
      )}
      {variant === 2 && (
        <>
          {/* Round orb */}
          <circle cx="100" cy="100" r="80" fill={`url(#${id}-grad)`}/>
          <ellipse cx="78" cy="72" rx="24" ry="14" fill={`url(#${id}-hl)`}/>
        </>
      )}
      {variant === 3 && (
        <>
          {/* Smaller plus */}
          <path d="M 100 30 Q 122 30 122 52 L 122 78 L 148 78 Q 170 78 170 100 Q 170 122 148 122 L 122 122 L 122 148 Q 122 170 100 170 Q 78 170 78 148 L 78 122 L 52 122 Q 30 122 30 100 Q 30 78 52 78 L 78 78 L 78 52 Q 78 30 100 30 Z" fill={`url(#${id}-grad)`}/>
          <ellipse cx="80" cy="62" rx="14" ry="8" fill={`url(#${id}-hl)`}/>
        </>
      )}
      {variant === 4 && (
        <>
          {/* Small bean */}
          <path d="M 60 90 Q 60 50 100 50 Q 140 50 140 90 Q 140 130 130 140 Q 110 160 80 150 Q 60 130 60 90 Z" fill={`url(#${id}-grad)`}/>
          <ellipse cx="86" cy="72" rx="14" ry="8" fill={`url(#${id}-hl)`}/>
        </>
      )}
    </svg>
  );
}

function ProjectStage({ p, featured = false, chrome = "mac" }) {
  const tiltClass = featured ? "stage-tilt" : "";
  const word = p.slug.split('-')[0];

  return (
    <div className={`proj-stage ${featured ? 'feat' : ''}`}>
      {/* glassy 3D blobs */}
      <div className="gblobs">
        <GlassBlob variant={1}/>
        <GlassBlob variant={2}/>
        {featured && <GlassBlob variant={3}/>}
        {featured && <GlassBlob variant={4}/>}
      </div>

      {/* vertical wordmark down the left */}
      <div className="stage-wordmark" aria-hidden="true">{word}</div>

      {/* GG monogram top-left */}
      <div className="stage-mark" aria-hidden="true">
        <GGMark/>
      </div>

      {/* small index pill top-right */}
      <div className="stage-num" aria-hidden="true">
        <span>{p.num}</span>
        <span className="sep">/</span>
        <span className="t">07</span>
      </div>

      {/* the actual UI mockup, framed in window chrome */}
      <div className={`stage-screen ${tiltClass}`}>
        <WindowChrome kind={chrome} title={p.title}>
          <ProjectThumb vibe={p.thumbVibe} accent={p.accent} accentSoft={p.accentSoft}/>
        </WindowChrome>
      </div>

      {/* Featured: full-bleed title overlay sits below the screen */}
      {featured && (
        <div className="stage-feat-overlay">
          <div className="ft-eyebrow">★ Featured · {p.role} · {p.year}</div>
          <div className="ft-title">
            {p.title.split(p.italWord)[0]}
            <span className="ital">{p.italWord}</span>
            {p.title.split(p.italWord)[1]}
          </div>
          <div className="ft-desc">{p.summary}</div>
          <div className="ft-foot">
            <span>{p.tags.slice(0,3).join(' · ')}</span>
            <span className="cta">Read case study →</span>
          </div>
        </div>
      )}

      {/* Smaller cards: caption strip at the bottom */}
      {!featured && (
        <div className="stage-caption">
          <span className="cap-l">{p.role} · {p.year}</span>
          <span className="cap-r">{p.tags.slice(0,2).join(' · ')}</span>
        </div>
      )}
    </div>
  );
}

function WindowChrome({ kind = "mac", title = "", children }) {
  if (kind === "win") {
    return (
      <div className="win-chrome">
        <div className="win-bar">
          <span className="win-title">{title}</span>
          <span className="win-controls">
            <span>—</span><span>☐</span><span>✕</span>
          </span>
        </div>
        <div className="win-body">{children}</div>
      </div>
    );
  }
  return (
    <div className="mac-chrome">
      <div className="mac-bar">
        <span className="mac-dots">
          <span className="d r"/><span className="d y"/><span className="d g"/>
        </span>
        <span className="mac-title">{title}</span>
        <span className="mac-spacer"/>
      </div>
      <div className="mac-body">{children}</div>
    </div>
  );
}

window.Titlebar = Titlebar;
window.Statusbar = Statusbar;
window.ThemeToggle = ThemeToggle;
window.ProjectThumb = ProjectThumb;
window.ProjectStage = ProjectStage;
window.WindowChrome = WindowChrome;
window.GGMark = GGMark;
window.applyTweaks = applyTweaks;
