// gallery.jsx — Project gallery with real device frames per project.
// Layouts: grid (uniform 3-col), showcase (hero + grid), bento (mosaic).
// Each project renders inside its native device — iPad for JTR (with the
// case-film YouTube embed looping), iPhone for Architect Scaler (real
// screenshot), Android for Basilisk, macOS windows for Hallway/Pinefall,
// browser window for Vesper.

// Pull device frame components from the global window scope.
// These are populated by ios-frame.jsx, android-frame.jsx, macos-window.jsx,
// browser-window.jsx — index.html loads them BEFORE gallery.jsx.
const D = {
  IOSDevice:     window.IOSDevice,
  AndroidDevice: window.AndroidDevice,
  MacWindow:     window.MacWindow,
  ChromeWindow:  window.ChromeWindow,
};

const GL_FONTS = {
  sans: "'Inter Tight', system-ui, sans-serif",
  mono: "'JetBrains Mono', ui-monospace, monospace",
};
const gv = (k) => `var(--sig-${k})`;

if (typeof document !== 'undefined' && !document.getElementById('gl-styles')) {
  const s = document.createElement('style'); s.id = 'gl-styles';
  s.textContent = `
    /* Devices float on the page surface — no card chrome around them. */
    .gl-tile { display: flex; flex-direction: column; gap: 32px; }
    .gl-tile.row { flex-direction: row; align-items: center; gap: 64px; }
    .gl-device-stage {
      display: flex; align-items: center; justify-content: center;
      transition: transform .45s cubic-bezier(.22,.61,.36,1);
      will-change: transform;
    }
    .gl-tile:hover .gl-device-stage { transform: translateY(-6px); }
    .gl-tile:hover .gl-cta { color: var(--sig-accent); }
    .gl-cta { transition: color .2s; }
    .gl-stage { position: relative; display: flex; align-items: center; justify-content: center; overflow: hidden; }
    .gl-info-dot { display:inline-block; width:6px; height:6px; border-radius:50%; }
    .gl-info-rule { height: 1px; background: var(--sig-line); width: 100%; }
  `;
  document.head.appendChild(s);
}

// ─── Scaling wrapper ───────────────────────────────────────────
// Devices render at native size, then we transform: scale() them to fit.
function ScaledDevice({ children, deviceWidth, deviceHeight, fitWidth, fitHeight, anchor = 'center' }) {
  const sw = fitWidth / deviceWidth;
  const sh = fitHeight ? fitHeight / deviceHeight : Infinity;
  const scale = Math.min(sw, sh, 1.5);
  const renderedHeight = Math.ceil(deviceHeight * scale);
  return (
    <div className="gl-stage" style={{
      width: fitWidth,
      height: fitHeight || renderedHeight,
      alignItems: anchor === 'top' ? 'flex-start' : 'center',
    }}>
      <div style={{
        width: deviceWidth, height: deviceHeight,
        transform: `scale(${scale})`,
        transformOrigin: anchor === 'top' ? 'top center' : 'center center',
        flexShrink: 0,
      }}>{children}</div>
    </div>
  );
}

// ─── Simple macOS window (no sidebar) ────────────────────────────────
// The starter MacWindow always renders a Finder-style sidebar; we want
// a chromeless full-bleed app window for the Hallway/Pinefall tiles.
function SimpleMacWindow({ children, width = 760, height = 480, title = '—', dark = true }) {
  const dot = (bg) => (
    <div style={{ width: 13, height: 13, borderRadius: '50%', background: bg, boxShadow: 'inset 0 0 0 0.5px rgba(0,0,0,0.18)' }} />
  );
  return (
    <div style={{
      width, height,
      borderRadius: 12,
      overflow: 'hidden',
      background: dark ? '#1a1d21' : '#f4f1ea',
      boxShadow: '0 40px 80px rgba(0,0,0,0.45), 0 0 0 1px rgba(0,0,0,0.55), inset 0 0 0 1px rgba(255,255,255,0.04)',
      fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro", system-ui, sans-serif',
      position: 'relative',
      display: 'flex', flexDirection: 'column',
    }}>
      {/* Title bar */}
      <div style={{
        height: 36, flexShrink: 0,
        display: 'flex', alignItems: 'center', gap: 10,
        padding: '0 14px',
        background: dark ? '#222629' : '#e9e4d8',
        borderBottom: `1px solid ${dark ? '#0e1012' : '#cdc6b3'}`,
        position: 'relative',
      }}>
        <div style={{ display: 'flex', gap: 8 }}>
          {dot('#ff5f57')}
          {dot('#febc2e')}
          {dot('#28c840')}
        </div>
        <div style={{
          position: 'absolute', left: '50%', top: '50%',
          transform: 'translate(-50%, -50%)',
          fontSize: 12.5, color: dark ? 'rgba(255,255,255,0.74)' : 'rgba(0,0,0,0.7)',
          letterSpacing: '-0.005em', fontWeight: 500,
          whiteSpace: 'nowrap',
        }}>{title}</div>
      </div>
      {/* Content */}
      <div style={{ flex: 1, position: 'relative', overflow: 'hidden' }}>
        {children}
      </div>
    </div>
  );
}

// ─── Custom iPad frame ─────────────────────────────────────────
// Realistic iPad Pro bezel: aluminum-feel edge, camera lens with glint,
// power + volume buttons on the top edge, speaker grilles on the short
// edges, multi-layer shadow. Designed for the Spotlight hero.
function IPadFrame({ children, width = 980, height = 720, dark = true, landscape = true }) {
  const bezel = 22;
  // Subtle gradient on the bezel reads as anodized aluminum
  const bgFrame = dark
    ? 'linear-gradient(160deg, #2a2a2d 0%, #1a1a1c 28%, #161618 55%, #1d1d20 80%, #232326 100%)'
    : 'linear-gradient(160deg, #ece8df 0%, #e1ddd2 50%, #e6e2d7 100%)';
  const buttonBg  = dark ? '#0a0a0a' : '#8a8576';
  const buttonHl  = dark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.08)';

  // Top-edge controls (landscape orientation)
  const topBtn = (left, w) => (
    <div style={{
      position: 'absolute', top: -3, left, width: w, height: 4,
      background: buttonBg, borderRadius: '0 0 2px 2px',
      boxShadow: `inset 0 -1px 0 ${buttonHl}, 0 1px 0 rgba(0,0,0,0.4)`,
    }} />
  );

  // Speaker grille — vertical slit pattern on short edges
  const speakerGrille = (side) => (
    <div style={{
      position: 'absolute',
      top: bezel + 60, height: height - bezel * 2 - 120, width: 2,
      [side]: -1,
      background: 'repeating-linear-gradient(180deg, rgba(0,0,0,0.7) 0, rgba(0,0,0,0.7) 2.5px, transparent 2.5px, transparent 5px)',
      borderRadius: side === 'left' ? '1px 0 0 1px' : '0 1px 1px 0',
      pointerEvents: 'none',
    }} />
  );

  return (
    <div style={{
      width, height,
      borderRadius: 34, padding: bezel,
      background: bgFrame,
      boxShadow: [
        '0 0 0 1px rgba(0,0,0,0.75)',                       // tight outer rim
        '0 1px 0 rgba(255,255,255,0.06) inset',             // top inner highlight
        '0 -1px 0 rgba(0,0,0,0.7) inset',                   // bottom inner shadow
        '0 90px 110px -32px rgba(0,0,0,0.55)',              // ambient drop
        '0 36px 70px -12px rgba(0,0,0,0.45)',               // mid drop
        '0 10px 24px -10px rgba(0,0,0,0.45)',               // contact shadow
      ].join(', '),
      position: 'relative',
      fontFamily: '-apple-system, system-ui, sans-serif',
    }}>
      {/* Volume + Power buttons (top edge, landscape) */}
      {topBtn(bezel + 38, 42)}
      {topBtn(bezel + 92, 42)}
      {topBtn(width - bezel - 96, 58)}

      {/* Apple Pencil magnetic strip (top edge) — extremely subtle */}
      <div style={{
        position: 'absolute', top: 6, left: '38%', right: '38%',
        height: 1,
        background: dark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.06)',
        pointerEvents: 'none',
      }} />

      {/* Camera lens */}
      <div style={{
        position: 'absolute',
        ...(landscape
          ? { left: '50%', top: 8, transform: 'translateX(-50%)' }
          : { top: '50%', left: 8, transform: 'translateY(-50%)' }),
        width: 11, height: 11, borderRadius: '50%',
        background: 'radial-gradient(circle at 35% 30%, #2c2c2e 0%, #0a0a0a 55%, #000 100%)',
        boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.06), 0 0 0 1px rgba(0,0,0,0.7)',
      }}>
        <div style={{
          position: 'absolute', top: 2, left: 2,
          width: 2.5, height: 2.5, borderRadius: '50%',
          background: 'rgba(255,255,255,0.5)',
        }} />
      </div>

      {/* Speaker grilles on left + right edges */}
      {speakerGrille('left')}
      {speakerGrille('right')}

      {/* Screen — deep black with subtle inner vignette */}
      <div style={{
        width: '100%', height: '100%',
        background: '#000',
        borderRadius: 10,
        overflow: 'hidden',
        position: 'relative',
        boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.05), inset 0 0 30px rgba(0,0,0,0.5)',
      }}>{children}</div>
    </div>
  );
}

// ─── Custom iPhone frame ───────────────────────────────────────
// Realistic iPhone 15 Pro bezel: titanium-feel edge, Dynamic Island with
// front camera + sensor, side buttons (action / volume / power), home
// indicator. Same physical-device language as IPadFrame.
function IPhoneFrame({ children, width = 402, height = 874, dark = true }) {
  const bezel = 14;
  const bgFrame = dark
    ? 'linear-gradient(160deg, #2a2a2d 0%, #1a1a1c 30%, #161618 58%, #1f1f22 100%)'
    : 'linear-gradient(160deg, #ece8df 0%, #e1ddd2 60%, #e6e2d7 100%)';

  // Side button — vertical pill protruding from the side
  const sideBtn = (side, top, h) => (
    <div style={{
      position: 'absolute',
      [side]: -3, top, width: 4, height: h,
      background: '#0a0a0a',
      borderRadius: side === 'left' ? '2px 0 0 2px' : '0 2px 2px 0',
      boxShadow:
        side === 'left'
          ? 'inset -1px 0 0 rgba(255,255,255,0.06), -1px 0 0 rgba(0,0,0,0.5)'
          : 'inset  1px 0 0 rgba(255,255,255,0.06),  1px 0 0 rgba(0,0,0,0.5)',
    }} />
  );

  return (
    <div style={{
      width, height,
      borderRadius: 52, padding: bezel,
      background: bgFrame,
      boxShadow: [
        '0 0 0 1px rgba(0,0,0,0.75)',
        '0 1px 0 rgba(255,255,255,0.06) inset',
        '0 -1px 0 rgba(0,0,0,0.7) inset',
        '0 70px 90px -24px rgba(0,0,0,0.55)',
        '0 28px 56px -10px rgba(0,0,0,0.45)',
        '0 8px 20px -8px rgba(0,0,0,0.45)',
      ].join(', '),
      position: 'relative',
      fontFamily: '-apple-system, system-ui, sans-serif',
    }}>
      {/* Left side: action button + volume up + volume down */}
      {sideBtn('left',  110, 32)}
      {sideBtn('left',  160, 62)}
      {sideBtn('left',  234, 62)}
      {/* Right side: power button */}
      {sideBtn('right', 190, 96)}

      {/* Screen */}
      <div style={{
        width: '100%', height: '100%',
        background: '#000',
        borderRadius: 40,
        overflow: 'hidden',
        position: 'relative',
        boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.05), inset 0 0 22px rgba(0,0,0,0.5)',
      }}>
        {children}

        {/* Dynamic Island */}
        <div style={{
          position: 'absolute', top: 11, left: '50%', transform: 'translateX(-50%)',
          width: 122, height: 36, borderRadius: 24,
          background: '#000',
          boxShadow: '0 0 0 0.5px rgba(255,255,255,0.04)',
          zIndex: 50, pointerEvents: 'none',
        }}>
          {/* Camera lens */}
          <div style={{
            position: 'absolute', right: 13, top: '50%', transform: 'translateY(-50%)',
            width: 10, height: 10, borderRadius: '50%',
            background: 'radial-gradient(circle at 32% 28%, #232328 0%, #050505 60%, #000 100%)',
            boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.05), 0 0 0 1px rgba(0,0,0,0.7)',
          }}>
            <div style={{
              position: 'absolute', top: 2, left: 2,
              width: 2.5, height: 2.5, borderRadius: '50%',
              background: 'rgba(255,255,255,0.45)',
            }} />
          </div>
          {/* Face ID sensor cluster — small dim spot left of camera */}
          <div style={{
            position: 'absolute', left: 18, top: '50%', transform: 'translateY(-50%)',
            width: 6, height: 6, borderRadius: '50%',
            background: '#0d0d10',
            boxShadow: 'inset 0 0 0 0.5px rgba(255,255,255,0.04)',
          }} />
        </div>

        {/* Home indicator */}
        <div style={{
          position: 'absolute', bottom: 8, left: '50%', transform: 'translateX(-50%)',
          width: 134, height: 5, borderRadius: 100,
          background: 'rgba(255,255,255,0.6)',
          zIndex: 60, pointerEvents: 'none',
        }} />
      </div>
    </div>
  );
}

// ─── Content components ────────────────────────────────────────

// JTR Command Center — case-film as iPad screen content.
// Autoplay requires muted in modern browsers, so the iframe is mounted on
// load with mute=1 and loops via playlist=VID. Playback speed is bumped to
// 1.3x via the YouTube IFrame API (enablejsapi=1 + setPlaybackRate over
// postMessage) since speed isn't a supported URL param.
function JTRContent() {
  const VID = 'uJaOHdDOrKM';
  const PLAYBACK_RATE = 1.3;
  const iframeRef = React.useRef(null);

  React.useEffect(() => {
    const iframe = iframeRef.current;
    if (!iframe) return;

    function sendCommand(func, args) {
      iframe.contentWindow?.postMessage(
        JSON.stringify({ event: 'command', func, args }),
        '*'
      );
    }

    function onMessage(e) {
      if (e.source !== iframe.contentWindow) return;
      let data;
      try { data = typeof e.data === 'string' ? JSON.parse(e.data) : e.data; }
      catch { return; }
      // 'onReady' fires once after we send the 'listening' handshake;
      // 'infoDelivery' fires repeatedly with state updates. Setting the
      // rate on either covers cold-start and the loop boundary (YouTube
      // resets to 1x when a looped video restarts).
      if (data && (data.event === 'onReady' || data.event === 'infoDelivery')) {
        sendCommand('setPlaybackRate', [PLAYBACK_RATE]);
      }
    }
    window.addEventListener('message', onMessage);

    function onLoad() {
      iframe.contentWindow?.postMessage(JSON.stringify({ event: 'listening' }), '*');
    }
    iframe.addEventListener('load', onLoad);
    // If the iframe loaded before this effect ran, kick the handshake now.
    if (iframe.contentDocument?.readyState === 'complete') onLoad();

    return () => {
      window.removeEventListener('message', onMessage);
      iframe.removeEventListener('load', onLoad);
    };
  }, []);

  return (
    <div style={{ position:'absolute', inset:0, background:'#000' }}>
      <iframe
        ref={iframeRef}
        src={`https://www.youtube-nocookie.com/embed/${VID}?autoplay=1&mute=1&loop=1&playlist=${VID}&playsinline=1&controls=0&rel=0&modestbranding=1&enablejsapi=1`}
        title="JTR Command Center"
        style={{
          position:'absolute', top:'50%', left:'50%',
          width:'177.8%', height:'100%',
          transform:'translate(-50%,-50%)',
          border: 0,
          pointerEvents: 'none',
        }}
        allow="autoplay; encrypted-media; picture-in-picture"
      />
    </div>
  );
}

// Architect Scaler — real App Store screenshot.
function ArchitectScalerContent() {
  return (
    <img
      src="assets/architect-scaler-cropped.jpg"
      alt="Architect Scaler"
      draggable={false}
      style={{ width:'100%', height:'100%', objectFit:'cover', display:'block', userSelect:'none' }}
    />
  );
}

// Basilisk — Godot mobile game mockup rendered at full-bleed.
function BasiliskContent() {
  return (
    <div style={{
      position:'absolute', inset:0,
      background:'linear-gradient(180deg, #2a1f3d 0%, #5a3a7a 50%, #1a1226 100%)',
      overflow:'hidden',
    }}>
      {[[40,90],[160,60],[220,180],[80,240],[190,330],[30,420],[140,510],[210,600],[60,720],[180,810]].map((p,i)=>(
        <div key={i} style={{ position:'absolute', left:p[0], top:p[1], width:3, height:3, background:'#fff', opacity:0.7 }} />
      ))}
      <div style={{
        position:'absolute', left:'50%', top:'45%', transform:'translate(-50%,-50%)',
        width:46, height:56, background:'#c8fb59',
        clipPath:'polygon(50% 0,100% 100%,50% 80%,0 100%)',
        filter:'drop-shadow(0 0 12px rgba(200,251,89,0.5))',
      }} />
      <div style={{ position:'absolute', left:60, top:160, width:34, height:34, background:'#ff6a4a', borderRadius:'50%', boxShadow:'0 0 12px rgba(255,106,74,0.4)' }} />
      <div style={{ position:'absolute', right:46, top:280, width:24, height:24, background:'#ff6a4a', borderRadius:'50%' }} />
      <div style={{ position:'absolute', left:80, top:520, width:18, height:18, background:'#ff6a4a', borderRadius:'50%' }} />
      <div style={{ position:'absolute', top:62, left:18, fontFamily: GL_FONTS.mono, fontSize:14, color:'#fff' }}>HP&nbsp;&nbsp;████░░░</div>
      <div style={{ position:'absolute', top:62, right:18, fontFamily: GL_FONTS.mono, fontSize:14, color:'#fff' }}>14 320</div>
      <div style={{ position:'absolute', bottom:110, left:36, width:90, height:90, border:'3px solid rgba(255,255,255,0.5)', borderRadius:'50%' }}>
        <div style={{ position:'absolute', top:'50%', left:'50%', width:42, height:42, background:'rgba(255,255,255,0.4)', borderRadius:'50%', transform:'translate(-50%,-50%)' }} />
      </div>
      <div style={{ position:'absolute', bottom:110, right:36, width:80, height:80, background:'rgba(200,251,89,0.25)', border:'3px solid #c8fb59', borderRadius:'50%', display:'flex', alignItems:'center', justifyContent:'center', fontFamily: GL_FONTS.mono, fontSize:13, color:'#fff', letterSpacing:'0.08em' }}>FIRE</div>
      <div style={{ position:'absolute', bottom:24, left:'50%', transform:'translateX(-50%)', fontFamily: GL_FONTS.mono, fontSize:10, color:'rgba(255,255,255,0.5)', letterSpacing:'0.14em' }}>SEED · 8A4F-219C-EE</div>
    </div>
  );
}

// Hallway — KiCad-like CAD viewer with the 4-layer PCB.
function HallwayContent() {
  return (
    <div style={{ width:'100%', height:'100%', background:'#0c0e10', display:'flex', fontFamily: GL_FONTS.mono }}>
      <div style={{ width: 150, padding: '14px 12px', borderRight:'1px solid #1d2226', background:'#0e1014' }}>
        <div style={{ fontSize:9, color:'#8a8576', letterSpacing:'0.14em', marginBottom:10 }}>LAYERS</div>
        {[['F.Cu', true, '#c8fb59'], ['B.Cu', true, '#5b8eff'], ['Edge.Cuts', true, '#a89c8c'], ['F.SilkS', true, '#f5f1ea'], ['F.Mask', false, '#a8331f'], ['B.SilkS', false, '#9c9a94']].map(([l, on, c], i)=>(
          <div key={i} style={{ display:'flex', alignItems:'center', gap:7, padding:'6px 5px', borderRadius:2, background: i===0?'rgba(200,251,89,0.08)':'transparent' }}>
            <div style={{ width:9, height:9, background: c, opacity: on?1:0.3, borderRadius:1 }} />
            <span style={{ fontSize:10, color: on ? '#ecedef' : '#5e5d57' }}>{l}</span>
          </div>
        ))}
        <div style={{ marginTop:18, fontSize:9, color:'#8a8576', letterSpacing:'0.14em', marginBottom:6 }}>DRC</div>
        <div style={{ fontSize:10, color:'#5cff95' }}>● 0 errors</div>
        <div style={{ fontSize:10, color:'#8a8576', marginTop:3 }}>2 warnings</div>
      </div>
      <div style={{ flex:1, position:'relative', display:'flex', alignItems:'center', justifyContent:'center', background:'#0a0c0e' }}>
        <svg viewBox="0 0 400 280" width="86%" height="86%" style={{ overflow:'visible' }}>
          <defs>
            <linearGradient id="pcbg-gallery" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0" stopColor="#1d4a2a" />
              <stop offset="1" stopColor="#0f2a18" />
            </linearGradient>
          </defs>
          <g transform="translate(200,150) skewX(-30) scale(1,0.55)">
            <rect x="-150" y="-100" width="300" height="200" fill="url(#pcbg-gallery)" stroke="#2d6b3a" strokeWidth="1.5" />
            {[-80,-40,0,40,80].map(y=>(
              <line key={y} x1="-140" y1={y} x2="140" y2={y} stroke="#c8fb59" strokeWidth="0.7" opacity="0.5" />
            ))}
            {[-120,-70,-20,30,80,120].map(x=>(
              <line key={x} x1={x} y1="-90" x2={x} y2="90" stroke="#c8fb59" strokeWidth="0.7" opacity="0.4" />
            ))}
            <rect x="-50" y="-40" width="60" height="44" fill="#0a0a0b" stroke="#7a8a55" />
            <rect x="40" y="20" width="40" height="28" fill="#0a0a0b" stroke="#7a8a55" />
            <rect x="-110" y="50" width="26" height="32" fill="#0a0a0b" stroke="#7a8a55" />
            <rect x="-150" y="-15" width="18" height="28" fill="#2a2a2a" stroke="#888" />
            {Array.from({length:16}).map((_,i)=>(
              <rect key={i} x={-140 + i*18} y="98" width="6" height="6" fill="#0c0c10" stroke="#2d6b3a" />
            ))}
          </g>
        </svg>
        <div style={{ position:'absolute', top:10, left:14, fontSize:10, color:'#8a8576' }}>HALLWAY · rev D · 4-layer · 64 × 38 mm</div>
        <div style={{ position:'absolute', bottom:10, right:14, fontSize:10, color:'#8a8576' }}>3D · DRC clean</div>
      </div>
    </div>
  );
}

// Pinefall — robotics console (live ops surface).
function PinefallContent() {
  return (
    <div style={{ width:'100%', height:'100%', background:'#0a0d12', padding:'16px 20px', display:'flex', flexDirection:'column', gap:12, fontFamily: GL_FONTS.mono }}>
      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center' }}>
        <div style={{ fontSize:11, color:'#7ea88c', letterSpacing:'0.14em' }}>UNIT-07 · LIVE · 41ms</div>
        <div style={{ fontSize:11, color:'#5cff95' }}>● 6 nodes connected</div>
      </div>
      <div style={{ height: 200, background:'linear-gradient(135deg, #0f1418 0%, #1a2229 100%)', border:'1px solid #1a241e', position:'relative', overflow:'hidden' }}>
        <svg viewBox="0 0 500 200" width="100%" height="100%" preserveAspectRatio="none">
          <path d="M0,150 L100,120 L200,130 L300,90 L400,100 L500,60 L500,200 L0,200 Z" fill="#1a2229" />
          <circle cx="300" cy="90" r="6" fill="#5cff95" />
          <line x1="300" y1="90" x2="400" y2="100" stroke="#5cff95" strokeWidth="1.5" strokeDasharray="3 3" />
        </svg>
        <div style={{ position:'absolute', bottom:12, left:16, fontSize:11, color:'#fff' }}>FRONT_CAM · 30fps</div>
        <div style={{ position:'absolute', top:12, right:16, fontSize:11, color:'#7ea88c' }}>REC · 02:14</div>
      </div>
      <div style={{ display:'grid', gridTemplateColumns:'repeat(4, 1fr)', gap:10 }}>
        {[['BATT','78%','#5cff95'],['TEMP','41°C','#ffce5c'],['CPU','24%','#7ea88c'],['SIG','97%','#5cff95']].map(([l,v,c],i)=>(
          <div key={i} style={{ background:'#0f1217', border:'1px solid #1a241e', padding:'10px 12px' }}>
            <div style={{ fontSize:9, color:'#476054', letterSpacing:'0.1em' }}>{l}</div>
            <div style={{ fontSize:19, color: c, marginTop:4, fontWeight:500 }}>{v}</div>
            <svg viewBox="0 0 80 16" width="100%" height="14" style={{ marginTop:4 }}>
              <polyline points="0,10 12,8 24,11 36,6 48,8 60,4 72,7 80,3" fill="none" stroke={c} strokeWidth="1.2" opacity="0.8" />
            </svg>
          </div>
        ))}
      </div>
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:10 }}>
        <div style={{ background:'#0f1217', border:'1px solid #1a241e', padding:'10px 14px' }}>
          <div style={{ fontSize:9, color:'#476054', letterSpacing:'0.1em' }}>WAYPOINT</div>
          <div style={{ fontSize:12, color:'#d6f5e1', marginTop:4 }}>04 / 12 · ETA 03:42</div>
        </div>
        <div style={{ background:'#0f1217', border:'1px solid #1a241e', padding:'10px 14px' }}>
          <div style={{ fontSize:9, color:'#476054', letterSpacing:'0.1em' }}>MODE</div>
          <div style={{ fontSize:12, color:'#5cff95', marginTop:4 }}>● AUTONOMOUS</div>
        </div>
      </div>
    </div>
  );
}

// Vesper — research tool, citation graph + selected note.
function VesperContent() {
  return (
    <div style={{ width:'100%', height:'100%', background:'#0a0a0c', padding:'22px 28px', display:'flex', gap:18, fontFamily: GL_FONTS.sans }}>
      <div style={{ width:'40%', display:'flex', flexDirection:'column', gap:12 }}>
        <div style={{ fontFamily: GL_FONTS.mono, fontSize:11, color:'#5e5d57', letterSpacing:'0.14em' }}>NOTE · 14 SOURCES</div>
        <div style={{ fontSize:15, color:'#ecedef', lineHeight:1.6 }}>
          On-device inference unlocks a new class of <span style={{ background:'rgba(92,255,149,0.18)', color:'#5cff95', padding:'1px 4px' }}>privacy-first</span> product surfaces, where the model never leaves the user's device.
        </div>
        <div style={{ display:'flex', gap:5, flexWrap:'wrap', marginTop:8 }}>
          {['[01]','[02]','[03]','[07]','[09]','[14]'].map(c=>(
            <span key={c} style={{ fontFamily: GL_FONTS.mono, fontSize:10, color:'#5cff95', border:'1px solid #1a241e', padding:'3px 7px' }}>{c}</span>
          ))}
        </div>
      </div>
      <div style={{ flex:1, position:'relative', background:'#0f0f12', border:'1px solid #1a241e' }}>
        <svg viewBox="0 0 320 260" width="100%" height="100%" style={{ position:'absolute', inset:0 }} preserveAspectRatio="none">
          <line x1="80"  y1="60"  x2="180" y2="100" stroke="#1a241e" />
          <line x1="140" y1="170" x2="180" y2="100" stroke="#1a241e" />
          <line x1="140" y1="170" x2="250" y2="140" stroke="#5cff95" strokeOpacity="0.6" strokeWidth="1.5" />
          <line x1="110" y1="240" x2="140" y2="170" stroke="#1a241e" />
          <line x1="220" y1="240" x2="250" y2="140" stroke="#1a241e" />
          <line x1="290" y1="80"  x2="250" y2="140" stroke="#1a241e" />
        </svg>
        {[[60,40,'A'],[160,80,'B'],[120,150,'C'],[230,120,'D'],[90,220,'E'],[200,220,'F'],[270,60,'G']].map(([x,y,l],i)=>(
          <div key={i} style={{
            position:'absolute', left:`${(x/320)*100}%`, top:`${(y/260)*100}%`, width:32, height:32,
            transform:'translate(-50%,-50%)',
            borderRadius:'50%',
            background: i===2 ? '#5cff95' : '#1a1a1e',
            border: `1.5px solid ${i===2 ? '#5cff95' : '#2a3a30'}`,
            display:'flex', alignItems:'center', justifyContent:'center',
            fontSize:13, color: i===2 ? '#050807' : '#7ea88c',
            fontFamily: GL_FONTS.mono, fontWeight:500,
          }}>{l}</div>
        ))}
      </div>
    </div>
  );
}

// ─── Project → device registry ─────────────────────────────────
// Each entry knows its native device size (so ScaledDevice can scale-fit it)
// and how to render the device+content.

const PROJECT_DEVICES = {
  'JTR': {
    deviceWidth: 980, deviceHeight: 720, orientation: 'landscape',
    render: () => (
      <IPadFrame width={980} height={720} dark={true} landscape={true}>
        <JTRContent />
      </IPadFrame>
    ),
  },
  'ARCHITECT SCALER': {
    deviceWidth: 402, deviceHeight: 874, orientation: 'portrait',
    render: () => (
      <IPhoneFrame width={402} height={874} dark={true}>
        <ArchitectScalerContent />
      </IPhoneFrame>
    ),
  },
  'BASILISK': {
    deviceWidth: 412, deviceHeight: 892, orientation: 'portrait',
    render: () => D.AndroidDevice ? (
      <D.AndroidDevice width={412} height={892} dark={true}>
        <BasiliskContent />
      </D.AndroidDevice>
    ) : null,
  },
  'HALLWAY': {
    deviceWidth: 760, deviceHeight: 480, orientation: 'landscape',
    render: () => (
      <SimpleMacWindow width={760} height={480} title="hallway.kicad_pcb — KiCad">
        <HallwayContent />
      </SimpleMacWindow>
    ),
  },
  'PINEFALL': {
    deviceWidth: 760, deviceHeight: 480, orientation: 'landscape',
    render: () => (
      <SimpleMacWindow width={760} height={480} title="Pinefall · Fleet Operations">
        <PinefallContent />
      </SimpleMacWindow>
    ),
  },
  'VESPER': {
    deviceWidth: 900, deviceHeight: 540, orientation: 'landscape',
    render: () => D.ChromeWindow ? (
      <D.ChromeWindow
        width={900} height={540}
        tabs={[{ title: 'Vesper · Citation Graph', active: true }]}
        url="vesper.local/notes/on-device-inference"
      >
        <VesperContent />
      </D.ChromeWindow>
    ) : null,
  },
};

// ─── Project tile (device + info) ──────────────────────────────

const STATUS_LABEL = { live: 'Running', dev: 'In development', hold: 'Maintained', fab: 'In fabrication' };
const STATUS_TONE  = { live: 'live',    dev: 'amber',          hold: 'mute',       fab: 'amber' };

// Spec strips used by the Editorial layout. Two or three facts per project,
// kept short — think "Specifications" callouts in Apple's product pages.
const PROJECT_SPECS = {
  'JTR': [
    ['Platform',     'iPadOS · native SwiftUI'],
    ['Intelligence', 'On-device CoreML · GPT integration'],
    ['Integrations', 'SMS · CRM · estimates · invoicing'],
  ],
  'ARCHITECT SCALER': [
    ['Platform',     'iOS · pure SwiftUI'],
    ['Footprint',    'No analytics · no account · one purchase'],
    ['Availability', 'App Store · maintained · since 2025'],
  ],
  'BASILISK': [
    ['Engine',  'Godot 4 · deterministic seed multiplayer'],
    ['Target',  'iOS · Android · WebGL build'],
    ['Stage',   'Controlled soft-launch · telemetry on'],
  ],
  'HALLWAY': [
    ['Form',   '4-layer FR4 · 64 × 38 mm · ruggedised'],
    ['Stack',  'ESP32-S3 · USB-C · cellular fallback'],
    ['Stage',  'rev D · design-for-manufacture review'],
  ],
  'PINEFALL': [
    ['Stack',     'WebRTC · ROS · React control surface'],
    ['Latency',   'Sub-100 ms telemetry · sub-200 ms command'],
    ['Fleet',     'Multi-unit · autonomous + manual'],
  ],
  'VESPER': [
    ['Architecture', 'Local-first · model-agnostic synthesis'],
    ['Surface',      'Citation graph · full-text export'],
    ['Status',       'v0.9 · maintenance mode'],
  ],
};

// Floating tile: device on top (no card chrome), info typeset below.
function FloatingTile({ project, deviceAreaWidth, deviceAreaHeight, infoPosition = 'below', compact = false }) {
  const dev = PROJECT_DEVICES[project.code];
  const tone = STATUS_TONE[project.status];
  if (!dev) return null;

  const deviceBlock = (
    <div className="gl-device-stage" style={{ width: deviceAreaWidth, height: deviceAreaHeight, flexShrink: 0 }}>
      <ScaledDevice
        deviceWidth={dev.deviceWidth}
        deviceHeight={dev.deviceHeight}
        fitWidth={deviceAreaWidth}
        fitHeight={deviceAreaHeight}
      >
        {dev.render()}
      </ScaledDevice>
    </div>
  );

  const isHero = infoPosition === 'right';
  const infoBlock = (
    <div style={{
      display:'flex', flexDirection:'column',
      gap: compact ? 8 : 11,
      maxWidth: isHero ? 380 : deviceAreaWidth,
    }}>
      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', gap:12 }}>
        <div className="sig-mono" style={{
          fontSize: isHero ? 16 : 14,
          color: gv('text'), letterSpacing:'0.06em',
          whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis',
        }}>{project.code}</div>
        <div className="sig-mono" style={{ fontSize:10, color: gv('text-mute'), letterSpacing:'0.14em', textTransform:'uppercase', whiteSpace:'nowrap' }}>{project.label || 'Codename'}</div>
      </div>
      {isHero && project.realName && (
        <h3 className="sig-h2" style={{ fontSize: 36, marginTop:6, marginBottom:0 }}>{project.realName}</h3>
      )}
      <div className="sig-mono" style={{ fontSize:10, color: gv('text-dim'), letterSpacing:'0.14em', textTransform:'uppercase' }}>{project.kind}</div>
      {!compact && (
        <div style={{ fontSize: isHero ? 16 : 13.5, color: gv('text-dim'), lineHeight:1.65, marginTop:6, maxWidth: isHero ? 460 : deviceAreaWidth }}>{project.blurb}</div>
      )}
      <div className="gl-info-rule" style={{ marginTop: compact ? 6 : 12 }} />
      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', paddingTop:2 }}>
        <a
          className="sig-link sig-mono gl-cta"
          href={project.href || undefined}
          target={project.href ? '_blank' : undefined}
          rel={project.href ? 'noopener noreferrer' : undefined}
          style={{ fontSize:11, color: gv('text-dim'), borderBottomColor: gv('line') }}
        >{project.cta} →</a>
        <span className="sig-mono" style={{ fontSize:10, color: gv('text-mute'), letterSpacing:'0.08em' }}>
          <span className="gl-info-dot" style={{ background: tone === 'live' ? gv('accent') : tone === 'amber' ? gv('accent-2') : gv('text-mute'), marginRight:6 }} />
          {project.ver} · {STATUS_LABEL[project.status]}
        </span>
      </div>
    </div>
  );

  if (isHero) {
    return (
      <div className="gl-tile row">
        {deviceBlock}
        {infoBlock}
      </div>
    );
  }
  return (
    <div className="gl-tile">
      {deviceBlock}
      {infoBlock}
    </div>
  );
}

// ─── Layouts ───────────────────────────────────────────────────

// Grid: 3-col grid, devices float on bare surface, info below each.
function GalleryGridLayout({ projects }) {
  return (
    <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:'88px 48px' }}>
      {projects.map(p => (
        <FloatingTile key={p.code} project={p} deviceAreaWidth={400} deviceAreaHeight={480} />
      ))}
    </div>
  );
}

// Showcase: hero project displayed large with info to the side; rest in 2-col.
function GalleryShowcaseLayout({ projects }) {
  const [hero, ...rest] = projects;
  return (
    <div style={{ display:'flex', flexDirection:'column', gap:104 }}>
      <FloatingTile
        project={hero}
        deviceAreaWidth={720} deviceAreaHeight={560}
        infoPosition="right"
      />
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:'96px 64px' }}>
        {rest.map(p => (
          <FloatingTile key={p.code} project={p} deviceAreaWidth={580} deviceAreaHeight={440} />
        ))}
      </div>
    </div>
  );
}

// Bento: varied sizes. Tall phones span two rows; wide devices span two cols.
function GalleryBentoLayout({ projects }) {
  const byCode = Object.fromEntries(projects.map(p => [p.code, p]));
  return (
    <div style={{
      display:'grid',
      gridTemplateColumns:'repeat(3, 1fr)',
      gridTemplateRows: '460px 360px 360px',
      gridTemplateAreas: `
        "jtr jtr arch"
        "hall basi arch"
        "pine basi vesp"
      `,
      gap: 56,
    }}>
      <div style={{ gridArea:'jtr' }}>
        {byCode['JTR'] && <FloatingTile project={byCode['JTR']} deviceAreaWidth={820} deviceAreaHeight={340} compact />}
      </div>
      <div style={{ gridArea:'arch' }}>
        {byCode['ARCHITECT SCALER'] && <FloatingTile project={byCode['ARCHITECT SCALER']} deviceAreaWidth={340} deviceAreaHeight={700} compact />}
      </div>
      <div style={{ gridArea:'hall' }}>
        {byCode['HALLWAY'] && <FloatingTile project={byCode['HALLWAY']} deviceAreaWidth={400} deviceAreaHeight={240} compact />}
      </div>
      <div style={{ gridArea:'basi' }}>
        {byCode['BASILISK'] && <FloatingTile project={byCode['BASILISK']} deviceAreaWidth={340} deviceAreaHeight={620} compact />}
      </div>
      <div style={{ gridArea:'pine' }}>
        {byCode['PINEFALL'] && <FloatingTile project={byCode['PINEFALL']} deviceAreaWidth={400} deviceAreaHeight={240} compact />}
      </div>
      <div style={{ gridArea:'vesp' }}>
        {byCode['VESPER'] && <FloatingTile project={byCode['VESPER']} deviceAreaWidth={400} deviceAreaHeight={240} compact />}
      </div>
    </div>
  );
}

// Mobile: horizontal snap-scroll carousel. Each project = one full-width
// slide. Saves enormous vertical space as the project count grows.
// - CSS scroll-snap drives the snap; we just observe scroll position to
//   light the active dot indicator and project label.
// - Devices are scaled to fit the viewport width with no horizontal scroll
//   inside a single slide.
function GalleryMobileLayout({ projects }) {
  const scrollerRef = React.useRef(null);
  const [active, setActive] = React.useState(0);

  // Available width per slide = viewport − 40px (20px gutter each side).
  const [slideW, setSlideW] = React.useState(() => typeof window !== 'undefined' ? Math.min(window.innerWidth - 40, 360) : 320);
  React.useEffect(() => {
    const on = () => setSlideW(Math.min(window.innerWidth - 40, 360));
    window.addEventListener('resize', on);
    return () => window.removeEventListener('resize', on);
  }, []);

  // Track scroll → active slide.
  const onScroll = () => {
    const el = scrollerRef.current;
    if (!el) return;
    const idx = Math.round(el.scrollLeft / (slideW + 16));
    if (idx !== active) setActive(Math.max(0, Math.min(projects.length - 1, idx)));
  };

  const goTo = (i) => {
    const el = scrollerRef.current;
    if (!el) return;
    el.scrollTo({ left: i * (slideW + 16), behavior: 'smooth' });
  };

  return (
    <div style={{ position: 'relative' }}>
      {/* Header strip: active project name + counter */}
      <div style={{
        display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
        marginBottom: 14, gap: 12,
      }}>
        <div className="sig-mono" style={{ fontSize: 11, color: gv('text'), letterSpacing: '0.08em' }}>
          {projects[active]?.code}
        </div>
        <div className="sig-mono" style={{ fontSize: 10, color: gv('text-mute'), letterSpacing: '0.12em' }}>
          {String(active + 1).padStart(2, '0')} / {String(projects.length).padStart(2, '0')} · swipe →
        </div>
      </div>

      {/* Snap scroller. Margin-left negative + padding-left positive keeps
          the slides flush with the section's content edge while letting the
          scroll spill to the screen edge for a native swipe feel. */}
      <div
        ref={scrollerRef}
        onScroll={onScroll}
        className="gl-snap-scroll"
        style={{
          display: 'flex', gap: 16,
          overflowX: 'auto', overflowY: 'visible',
          scrollSnapType: 'x mandatory',
          scrollPadding: '0',
          WebkitOverflowScrolling: 'touch',
          paddingBottom: 8,
          // Hide native scrollbar (Firefox / Chromium)
          scrollbarWidth: 'none',
          msOverflowStyle: 'none',
        }}
      >
        {projects.map((p) => {
          const dev = PROJECT_DEVICES[p.code];
          if (!dev) return null;
          const isPortrait = dev.deviceHeight > dev.deviceWidth;
          const stageH = isPortrait ? Math.round(slideW * 1.45) : Math.round(slideW * 0.72);
          return (
            <div
              key={p.code}
              style={{
                flex: `0 0 ${slideW}px`, width: slideW,
                scrollSnapAlign: 'start',
                display: 'flex', flexDirection: 'column', gap: 18,
              }}
            >
              <div className="gl-device-stage" style={{ width: slideW, height: stageH }}>
                <ScaledDevice
                  deviceWidth={dev.deviceWidth}
                  deviceHeight={dev.deviceHeight}
                  fitWidth={slideW}
                  fitHeight={stageH}
                >
                  {dev.render()}
                </ScaledDevice>
              </div>
              <MobileTileInfo project={p} />
            </div>
          );
        })}
      </div>

      {/* Dot indicators — tap to jump */}
      <div style={{ display: 'flex', gap: 8, justifyContent: 'center', marginTop: 18 }}>
        {projects.map((p, i) => (
          <button
            key={p.code}
            type="button"
            aria-label={`Show ${p.code}`}
            onClick={() => goTo(i)}
            style={{
              width: i === active ? 22 : 8, height: 4, borderRadius: 2,
              border: 0, cursor: 'pointer', padding: 0,
              background: i === active ? gv('accent') : gv('line-hi'),
              transition: 'width .2s ease, background-color .2s ease',
            }}
          />
        ))}
      </div>
    </div>
  );
}

function MobileTileInfo({ project }) {
  const tone = STATUS_TONE[project.status];
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 12 }}>
        <div className="sig-mono" style={{ fontSize: 13, color: gv('text'), letterSpacing: '0.06em' }}>{project.code}</div>
        <div className="sig-mono" style={{ fontSize: 9, color: gv('text-mute'), letterSpacing: '0.14em', textTransform: 'uppercase' }}>{project.label || 'Codename'}</div>
      </div>
      {project.realName && (
        <div style={{ fontSize: 22, color: gv('text'), letterSpacing: '-0.02em', fontWeight: 500, lineHeight: 1.15 }}>{project.realName}</div>
      )}
      <div className="sig-mono" style={{ fontSize: 10, color: gv('text-dim'), letterSpacing: '0.14em', textTransform: 'uppercase' }}>{project.kind}</div>
      <div style={{ fontSize: 14, color: gv('text-dim'), lineHeight: 1.6, marginTop: 4 }}>{project.blurb}</div>
      <div className="gl-info-rule" style={{ marginTop: 10 }} />
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', paddingTop: 4, gap: 12, flexWrap: 'wrap' }}>
        <a
          className="sig-link sig-mono gl-cta"
          href={project.href || undefined}
          target={project.href ? '_blank' : undefined}
          rel={project.href ? 'noopener noreferrer' : undefined}
          style={{ fontSize: 11, color: gv('text-dim'), borderBottomColor: gv('line') }}
        >{project.cta} →</a>
        <span className="sig-mono" style={{ fontSize: 10, color: gv('text-mute'), letterSpacing: '0.08em' }}>
          <span className="gl-info-dot" style={{ background: tone === 'live' ? gv('accent') : tone === 'amber' ? gv('accent-2') : gv('text-mute'), marginRight: 6 }} />
          {project.ver} · {STATUS_LABEL[project.status]}
        </span>
      </div>
    </div>
  );
}

// ─── Public orchestrator ───────────────────────────────────────

function EditorialSection({ project, index }) {
  const flipped = index % 2 === 1;
  const tone = STATUS_TONE[project.status];
  const specs = PROJECT_SPECS[project.code] || [];
  const dev = PROJECT_DEVICES[project.code];
  if (!dev) return null;

  const deviceArea = (
    <div style={{
      position:'sticky', top:0,
      height:'100vh',
      display:'flex', alignItems:'center', justifyContent:'center',
      gridColumn: flipped ? 2 : 1, gridRow: 1,
    }}>
      <ScaledDevice
        deviceWidth={dev.deviceWidth}
        deviceHeight={dev.deviceHeight}
        fitWidth={620}
        fitHeight={620}
      >
        {dev.render()}
      </ScaledDevice>
    </div>
  );

  const copyArea = (
    <div style={{
      gridColumn: flipped ? 1 : 2, gridRow: 1,
      display:'flex', flexDirection:'column', justifyContent:'center',
      minHeight:'110vh',
      padding:'120px 0',
      gap: 32,
    }}>
      <div className="sig-mono" style={{ fontSize:11, color: gv('text-mute'), letterSpacing:'0.18em', textTransform:'uppercase' }}>
        {String(index + 1).padStart(2,'0')}&nbsp;·&nbsp;{project.label || 'Codename'}
      </div>
      <h2 className="sig-h2" style={{ fontSize: 60, lineHeight: 1.02, letterSpacing:'-0.03em' }}>
        {project.realName || project.code}
      </h2>
      <div className="sig-mono" style={{ fontSize:12, color: gv('text-dim'), letterSpacing:'0.14em', textTransform:'uppercase' }}>{project.kind}</div>
      <p style={{ fontSize:18, color: gv('text-dim'), lineHeight:1.6, maxWidth:520, margin:0 }}>{project.blurb}</p>
      <div style={{ marginTop:8, display:'grid', gridTemplateColumns:'1fr', maxWidth:520 }}>
        {specs.map(([k, val], i) => (
          <div key={k} style={{
            display:'grid', gridTemplateColumns:'160px 1fr', gap:20,
            padding:'16px 0',
            borderTop: i === 0 ? `1px solid ${gv('line-hi')}` : `1px solid ${gv('line')}`,
          }}>
            <div className="sig-mono" style={{ fontSize:10, color: gv('text-mute'), letterSpacing:'0.14em', textTransform:'uppercase' }}>{k}</div>
            <div style={{ fontSize:14, color: gv('text-dim'), lineHeight:1.55 }}>{val}</div>
          </div>
        ))}
      </div>
      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginTop:8, paddingTop:24, borderTop:`1px solid ${gv('line')}`, maxWidth:520 }}>
        <a
          className="sig-link sig-mono"
          href={project.href || undefined}
          target={project.href ? '_blank' : undefined}
          rel={project.href ? 'noopener noreferrer' : undefined}
          style={{ fontSize:12, color: gv('text') }}
        >{project.cta} →</a>
        <span className="sig-mono" style={{ fontSize:11, color: gv('text-mute'), letterSpacing:'0.08em' }}>
          <span className="gl-info-dot" style={{ background: tone === 'live' ? gv('accent') : tone === 'amber' ? gv('accent-2') : gv('text-mute'), marginRight:8 }} />
          {project.ver} · {STATUS_LABEL[project.status]}
        </span>
      </div>
    </div>
  );

  return (
    <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap: 80 }}>
      {deviceArea}
      {copyArea}
    </div>
  );
}

function GalleryEditorialLayout({ projects }) {
  return (
    <div style={{ display:'flex', flexDirection:'column' }}>
      {projects.map((p, i) => (
        <EditorialSection key={p.code} project={p} index={i} />
      ))}
    </div>
  );
}

function DeviceGlyph({ kind, active }) {
  const c = active ? gv('accent') : gv('text-dim');
  const stroke = { fill:'none', stroke: c, strokeWidth: 1.4 };
  if (kind === 'phone')   return (<svg width="38" height="54" viewBox="0 0 38 54"><rect x="4" y="2" width="30" height="50" rx="5" {...stroke} /><rect x="15" y="4" width="8" height="2" rx="1" fill={c} /></svg>);
  if (kind === 'tablet')  return (<svg width="54" height="42" viewBox="0 0 54 42"><rect x="2" y="2" width="50" height="38" rx="4" {...stroke} /></svg>);
  if (kind === 'macwin')  return (<svg width="54" height="38" viewBox="0 0 54 38"><rect x="2" y="2" width="50" height="34" rx="3" {...stroke} /><circle cx="7" cy="7" r="1.4" fill={c} /><circle cx="11" cy="7" r="1.4" fill={c} /><circle cx="15" cy="7" r="1.4" fill={c} /></svg>);
  if (kind === 'browser') return (<svg width="54" height="38" viewBox="0 0 54 38"><rect x="2" y="2" width="50" height="34" rx="3" {...stroke} /><circle cx="7" cy="7" r="1.4" fill={c} /><circle cx="11" cy="7" r="1.4" fill={c} /><circle cx="15" cy="7" r="1.4" fill={c} /><rect x="20" y="4.5" width="30" height="5" rx="1" fill={c} fillOpacity="0.18" /></svg>);
  return null;
}

function SpotlightThumb({ project, active, onSelect }) {
  const dev = PROJECT_DEVICES[project.code];
  const glyph = !dev ? null
    : dev.orientation === 'portrait' && dev.deviceWidth < 500 ? 'phone'
    : dev.deviceWidth >= 900 ? 'browser'
    : dev.deviceWidth >= 700 ? 'macwin'
    : 'tablet';
  return (
    <button
      onClick={onSelect}
      style={{
        display:'flex', flexDirection:'column', alignItems:'flex-start', gap:12,
        padding:'18px 18px 16px',
        background: active ? gv('surface') : 'transparent',
        border: `1px solid ${active ? gv('accent') : gv('line')}`,
        cursor:'pointer',
        textAlign:'left',
        transition:'border-color .2s, background-color .2s, transform .2s',
        transform: active ? 'translateY(-2px)' : 'none',
        fontFamily: GL_FONTS.sans,
      }}
    >
      <DeviceGlyph kind={glyph} active={active} />
      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', width:'100%' }}>
        <span className="sig-mono" style={{ fontSize:11, color: active ? gv('text') : gv('text-dim'), letterSpacing:'0.06em' }}>{project.code}</span>
        <span className="sig-mono" style={{ fontSize:9, color: gv('text-mute'), letterSpacing:'0.1em' }}>{project.label || 'Codename'}</span>
      </div>
      <div className="sig-mono" style={{ fontSize:9, color: gv('text-mute'), letterSpacing:'0.1em', textTransform:'uppercase' }}>{project.ver} · {STATUS_LABEL[project.status]}</div>
    </button>
  );
}

function GallerySpotlightLayout({ projects }) {
  const [activeIdx, setActiveIdx] = React.useState(0);
  const active = projects[activeIdx] || projects[0];
  const dev = PROJECT_DEVICES[active.code];
  const tone = STATUS_TONE[active.status];
  if (!dev) return null;
  return (
    <div>
      {/* Stage — ambient halo behind hero device + subtle floor reflection */}
      <div style={{
        position:'relative',
        display:'grid', gridTemplateColumns:'1.45fr 1fr',
        gap: 96, alignItems:'center',
        minHeight: 660,
        padding:'40px 0 60px',
      }}>
        {/* Halo backdrop — soft radial accent wash */}
        <div aria-hidden style={{
          position:'absolute', inset:0,
          background: `radial-gradient(ellipse 760px 540px at 32% 50%, color-mix(in srgb, ${gv('accent')} 9%, transparent) 0%, transparent 70%)`,
          pointerEvents:'none', zIndex:0,
        }} />
        {/* Device */}
        <div style={{ position:'relative', zIndex:1, display:'flex', justifyContent:'center', alignItems:'center' }}>
          <div style={{ position:'relative' }}>
            <ScaledDevice
              deviceWidth={dev.deviceWidth}
              deviceHeight={dev.deviceHeight}
              fitWidth={700}
              fitHeight={580}
            >
              {dev.render()}
            </ScaledDevice>
            {/* Floor reflection — faint downward fade beneath the device */}
            <div aria-hidden style={{
              position:'absolute', left:'50%', bottom:-22,
              transform:'translateX(-50%)',
              width: 520, height: 28,
              background: `radial-gradient(ellipse at center top, color-mix(in srgb, ${gv('text')} 14%, transparent) 0%, transparent 70%)`,
              filter: 'blur(2px)',
              pointerEvents:'none',
            }} />
          </div>
        </div>
        {/* Copy */}
        <div style={{ position:'relative', zIndex:1, display:'flex', flexDirection:'column', gap:24, paddingRight:24 }}>
          <div className="sig-mono" style={{ fontSize:11, color: gv('text-mute'), letterSpacing:'0.18em', textTransform:'uppercase' }}>
            {String(activeIdx + 1).padStart(2,'0')}&nbsp;·&nbsp;{active.label || 'Codename'}
          </div>
          <h3 className="sig-h2" style={{ fontSize: 52, lineHeight: 1.02 }}>{active.realName || active.code}</h3>
          <div className="sig-mono" style={{ fontSize:12, color: gv('text-dim'), letterSpacing:'0.14em', textTransform:'uppercase' }}>{active.kind}</div>
          <p style={{ fontSize:17, color: gv('text-dim'), lineHeight:1.65, maxWidth:480, margin:0 }}>{active.blurb}</p>
          <div style={{ marginTop:8, paddingTop:18, borderTop:`1px solid ${gv('line')}`, display:'flex', justifyContent:'space-between', alignItems:'center' }}>
            <a
              className="sig-link sig-mono"
              href={active.href || undefined}
              target={active.href ? '_blank' : undefined}
              rel={active.href ? 'noopener noreferrer' : undefined}
              style={{ fontSize:12, color: gv('text') }}
            >{active.cta} →</a>
            <span className="sig-mono" style={{ fontSize:11, color: gv('text-mute'), letterSpacing:'0.08em' }}>
              <span className="gl-info-dot" style={{ background: tone === 'live' ? gv('accent') : tone === 'amber' ? gv('accent-2') : gv('text-mute'), marginRight:8 }} />
              {active.ver} · {STATUS_LABEL[active.status]}
            </span>
          </div>
        </div>
      </div>

      {/* Filmstrip — flex with bounded thumb width so it adapts to any count. */}
      <div style={{ marginTop: 64, paddingTop: 32, borderTop: `1px solid ${gv('line')}` }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', marginBottom:20 }}>
          <div className="sig-mono" style={{ fontSize:10, color: gv('text-mute'), letterSpacing:'0.16em', textTransform:'uppercase' }}>
            ◇ Select a project
          </div>
          <div className="sig-mono" style={{ fontSize:10, color: gv('text-mute'), letterSpacing:'0.12em' }}>
            {String(activeIdx + 1).padStart(2,'0')} / {String(projects.length).padStart(2,'0')} shown · further engagements held under NDA
          </div>
        </div>
        <div style={{ display:'flex', gap:14, flexWrap:'wrap' }}>
          {projects.map((p, i) => (
            <div key={p.code} style={{ flex:'1 1 220px', maxWidth: 280, minWidth: 200 }}>
              <SpotlightThumb project={p} active={i === activeIdx} onSelect={() => setActiveIdx(i)} />
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function SignalGalleryV2({ projects, layout = 'grid' }) {
  // Mobile detection — duplicated here to keep gallery.jsx self-contained.
  // Falls back to false on SSR.
  const [isMobile, setIsMobile] = React.useState(() => typeof window !== 'undefined' && window.innerWidth <= 760);
  React.useEffect(() => {
    const on = () => setIsMobile(window.innerWidth <= 760);
    window.addEventListener('resize', on);
    return () => window.removeEventListener('resize', on);
  }, []);

  const filters = ['All work', 'Apple', 'Games', 'AI', 'Robotics', 'Hardware', 'Web'];
  const Layout =
      isMobile               ? GalleryMobileLayout
    : layout === 'showcase'  ? GalleryShowcaseLayout
    : layout === 'bento'     ? GalleryBentoLayout
    : layout === 'editorial' ? GalleryEditorialLayout
    : layout === 'spotlight' ? GallerySpotlightLayout
    :                          GalleryGridLayout;
  const layoutLabel =
      layout === 'showcase'  ? '◊ Showcase'
    : layout === 'bento'     ? '◊ Bento'
    : layout === 'editorial' ? '◊ Editorial'
    : layout === 'spotlight' ? '◊ Spotlight'
    :                          '◊ Grid';

  if (isMobile) {
    return (
      <div className="gl-section" style={{ padding:'56px 20px 24px' }}>
        <div className="sig-eyebrow" style={{ marginBottom: 14 }}>◇ The work</div>
        <h2 className="sig-h2" style={{ marginBottom: 24 }}>
          Every project, on the device that runs it.<br />
          <span style={{ color: gv('text-dim') }}>Two products in the open. The rest under non-disclosure.</span>
        </h2>
        <div style={{ display:'flex', gap:6, flexWrap:'wrap', marginBottom: 32 }}>
          {filters.map((f, i) => (
            <span key={f} className={`sig-chip ${i === 0 ? 'on' : ''}`}>{f}</span>
          ))}
        </div>
        <GalleryMobileLayout projects={projects} />
        <div style={{ marginTop: 28, paddingTop: 18, borderTop: `1px solid ${gv('line')}` }}>
          <div className="sig-mono" style={{ fontSize: 11, color: gv('text-mute'), lineHeight: 1.6 }}>
            The remainder of our active work is held under non-disclosure.
          </div>
        </div>
      </div>
    );
  }

  return (
    <div style={{ padding:'112px 56px 32px', maxWidth:1400, margin:'0 auto' }}>
      <div style={{ display:'flex', alignItems:'end', justifyContent:'space-between', marginBottom:48, gap:32, flexWrap:'wrap' }}>
        <div>
          <div className="sig-eyebrow" style={{ marginBottom:18 }}>◇ The work</div>
          <h2 className="sig-h2">
            Every project, on the device that runs it.<br />
            <span style={{ color: gv('text-dim') }}>Two products in the open. The rest under non-disclosure.</span>
          </h2>
        </div>
        <div style={{ display:'flex', gap:6, flexWrap:'wrap', maxWidth:520, justifyContent:'flex-end', alignItems:'center' }}>
          <span className="sig-mono" style={{ fontSize:10, color: gv('text-mute'), letterSpacing:'0.14em', marginRight:8 }}>{layoutLabel}</span>
          {filters.map((f,i) => (
            <span key={f} className={`sig-chip ${i===0?'on':''}`}>{f}</span>
          ))}
        </div>
      </div>

      <Layout projects={projects} />

      <div style={{ marginTop:36, display:'flex', alignItems:'center', justifyContent:'space-between' }}>
        <div className="sig-mono" style={{ fontSize:11, color: gv('text-mute') }}>
          The remainder of our active work is held under non-disclosure.
        </div>
        <a className="sig-link sig-mono" style={{ fontSize:12 }}>View full index →</a>
      </div>
    </div>
  );
}

window.SignalGalleryV2 = SignalGalleryV2;
window.GALLERY_LAYOUTS = [
  { value: 'grid',      label: 'Grid · uniform' },
  { value: 'showcase',  label: 'Showcase · hero + grid' },
  { value: 'bento',     label: 'Bento · mosaic' },
  { value: 'editorial', label: 'Editorial · sticky scroll' },
  { value: 'spotlight', label: 'Spotlight · filmstrip' },
];
