// G+2 Floors explorer — the screen at #/gplus.
// Three tabs (GROUND · FIRST · SECOND); each shows the full podium floor drawing
// in a pan / zoom viewer (pointer-drag pan · wheel / pinch zoom · double-tap
// reset · +/− buttons) with a scrollable legend rail beside it listing the
// verbatim amenity legend for that level. Back → inventory.
// Legend text is transcribed verbatim from The Universe Plan.pdf (pages 1–3),
// see _build-docs/05-uplan-2026-07-06.md § "G+2 amenity legends".

const GP_KEYS_ID = 'uni-gplus-keys-v1';
function ensureGpKeys() {
  if (typeof document === 'undefined' || document.getElementById(GP_KEYS_ID)) return;
  const s = document.createElement('style');
  s.id = GP_KEYS_ID;
  s.textContent = `
    @keyframes gpFadeUp { from { opacity:0; transform: translateY(20px); } to { opacity:1; transform: translateY(0); } }
    @keyframes gpRowIn  { from { opacity:0; transform: translateX(14px); } to { opacity:1; transform: translateX(0); } }
    .gp-tab   { transition: background 240ms ease, color 240ms ease, border-color 240ms ease, box-shadow 240ms ease; }
    .gp-tab:active { transform: scale(0.98); }
    .gp-zoom-btn { transition: background 180ms ease, color 180ms ease, border-color 180ms ease, transform 140ms ease; }
    .gp-zoom-btn:hover { background: var(--gold) !important; color:#1a130a !important; border-color: var(--gold) !important; }
    .gp-zoom-btn:active { transform: scale(0.92); }
    .gp-zoom-btn:disabled { opacity:0.4; cursor:not-allowed; }
    .gp-legrow { transition: background 200ms ease, border-color 200ms ease, transform 140ms ease; }
    .gp-legrow.gp-clickable { cursor:pointer; }
    .gp-legrow.gp-clickable:active { transform: scale(0.99); }
    .gp-pin-tok { transition: transform 140ms ease; }
    .gp-pin:active .gp-pin-tok { transform: scale(0.94); }
    @keyframes gpPinPulse { 0% { transform: scale(1); opacity:0.72; } 70% { transform: scale(1.85); opacity:0; } 100% { transform: scale(1.85); opacity:0; } }
  `;
  document.head.appendChild(s);
}

// Verbatim legends (The Universe Plan.pdf p1–3).
const GP_TABS = [
  {
    key:'ground', label:'GROUND', short:'G', img:'assets/plans/gplus-ground.jpg', aspect:'3082 / 3082',
    groups:[{ items:[
      ['01','Double Height Foyer'], ['02','Drop Off'], ['03','Entrance Lobby'],
      ['04','Basketball Court'], ['05','Water Feature'], ['06','Lawn'],
      ['07','Seating Garden'], ['08','Step Seating'], ['09','Mound Garden'],
      ['10','Walking Track'], ['11','Badminton Court'],
    ]}],
  },
  {
    key:'first', label:'FIRST', short:'1', img:'assets/plans/gplus-first.jpg', aspect:'3082 / 3081',
    groups:[
      { title:'Podium Amenities', items:[
        ['01','Crystal Garden'], ['02','Step Seating'], ['03','Box Cricket'],
        ['04','Accent Trees'], ['05','Banquet Garden'], ['06','Petal Garden'],
        ['07','Swimming Pool'], ['08','Jogging Track'], ['09','Outdoor Fitness'],
        ['10',"Children's Playground"], ['11','Garden Theatre'], ['12','The Grand Stairway'],
      ]},
      { title:'Covered Amenities', items:[
        ['13','Guest Rooms'], ['14','Banquet'], ['15','The Lounge'], ['16','Podcast Pod'],
        ['17','Meeting Room'], ['18','Learning Room'], ['19','The Studio'], ['20','Café Universe'],
        ['21',"Toddler's Den"], ['22','Indoor Games'], ['23','Gen-z Lounge'], ['24','Restrooms'],
        ['25','The Wellness Club Lower Level – Strength Training, Yoga, Crossfit'],
        ['26','Restrooms & Changing Rooms'], ['27','Utility Rooms'], ['28','Society Management Office'],
      ]},
    ],
  },
  {
    key:'second', label:'SECOND', short:'2', img:'assets/plans/gplus-second.jpg', aspect:'3082 / 3081',
    groups:[{ items:[
      ['01','The Grand Stairway'], ['02','Pickleball Court'], ['03','Garden & Walkway'],
      ['04','The Wellness Club Upper Level – Cardio, Circuit Training'],
    ]}],
  },
];

// Normalized pin positions — fraction of image width/height — for the small
// numbered markers printed on each plan drawing. Authored by inspecting the
// three source JPGs (all ≈3082² square). Every legend number is locatable, so
// each legend row is focusable. See _build-docs for the derivation.
const GP_PINS = {
  ground: {
    '01':[0.562,0.302], '02':[0.558,0.329], '03':[0.497,0.292],
    '04':[0.388,0.795], '05':[0.210,0.780], '06':[0.273,0.795],
    '07':[0.251,0.851], '08':[0.262,0.769], '09':[0.312,0.776],
    '10':[0.320,0.929], '11':[0.429,0.283],
  },
  first: {
    '01':[0.586,0.570], '02':[0.508,0.460], '03':[0.425,0.638], '04':[0.612,0.632],
    '05':[0.689,0.709], '06':[0.641,0.528], '07':[0.573,0.410], '08':[0.676,0.440],
    '09':[0.406,0.326], '10':[0.514,0.588], '11':[0.573,0.492], '12':[0.718,0.360],
    '13':[0.736,0.619], '14':[0.642,0.697], '15':[0.515,0.698], '16':[0.580,0.687],
    '17':[0.581,0.703], '18':[0.568,0.723], '19':[0.515,0.722], '20':[0.440,0.702],
    '21':[0.441,0.592], '22':[0.442,0.475], '23':[0.415,0.463], '24':[0.441,0.432],
    '25':[0.562,0.463], '26':[0.540,0.369], '27':[0.698,0.320], '28':[0.788,0.630],
  },
  second: {
    '01':[0.723,0.360], '02':[0.773,0.319], '03':[0.741,0.291], '04':[0.581,0.452],
  },
};

function GPlus() {
  ensureGpKeys();
  const t = useLoop();
  const e = clamp(t/0.5);
  const [tab, setTab] = React.useState('ground');
  const active = GP_TABS.find(x => x.key === tab) || GP_TABS[0];

  // ── pan / zoom state (reset on tab change) ──────────────────────
  const [zoom, setZoom] = React.useState(1);
  const [pan, setPan] = React.useState({ x:0, y:0 });
  const [hintFaded, setHintFaded] = React.useState(false);
  const [focus, setFocus] = React.useState(null);   // active pin/legend item number, or null
  const containerRef = React.useRef(null);           // gesture frame
  const wrapperRef = React.useRef(null);             // contain-fit transformed layer
  const focusRef = React.useRef(null);
  focusRef.current = focus;                          // keep in sync for gesture closures
  const pinMap = GP_PINS[active.key] || {};
  const g = React.useRef({
    active:false, mode:null, startD:0, startZoom:1,
    startPanX:0, startPanY:0, startMidX:0, startMidY:0,
    lastTapAt:0, lastTapX:0, lastTapY:0, dragStartX:0, dragStartY:0,
  });

  React.useEffect(() => { setZoom(1); setPan({x:0,y:0}); setFocus(null); }, [tab]);
  const fadeHint = () => { if (!hintFaded) setHintFaded(true); };

  // When a pin focus is active, relax the clamp so edge pins can reach centre
  // (default relax follows the current focus state).
  const clampPan = (p, z, relax) => {
    const el = containerRef.current; if (!el) return p;
    const r = el.getBoundingClientRect();
    const loose = relax === undefined ? !!focusRef.current : relax;
    const maxX = loose ? (z/2)*r.width  + 140 : Math.max(0, ((z-1)/2)*r.width)  + 80;
    const maxY = loose ? (z/2)*r.height + 140 : Math.max(0, ((z-1)/2)*r.height) + 80;
    return { x: Math.max(-maxX, Math.min(maxX, p.x)), y: Math.max(-maxY, Math.min(maxY, p.y)) };
  };
  const resetView = () => { setZoom(1); setPan({x:0,y:0}); setFocus(null); focusRef.current=null; };

  // Fly the plan so the numbered pin lands at the centre of the viewer at a
  // comfortable zoom (keep the current zoom if the user is already deeper).
  const focusItem = (n) => {
    if (!pinMap[n]) return;
    if (focus === n) { resetView(); return; }   // toggle off → fit view
    const wrap = wrapperRef.current; if (!wrap) return;
    const W = wrap.offsetWidth, H = wrap.offsetHeight;   // laid-out contain-fit size (unscaled)
    const tz = Math.max(zoom, 2.1);
    const [fx, fy] = pinMap[n];
    const dx = (fx - 0.5) * W, dy = (fy - 0.5) * H;
    focusRef.current = n; setFocus(n);
    try{ if(window.RDA){ var _lbl=null; (active.groups||[]).forEach(function(gp){ (gp.items||[]).forEach(function(it){ if(it[0]===n) _lbl=it[1]; }); }); window.RDA.track('amenity_focus', _lbl||n, 1, {floor: active.key, num: n}); } }catch(e){}
    setZoom(tz);
    setPan(clampPan({ x: -dx*tz, y: -dy*tz }, tz, true));
    fadeHint();
  };

  const onTouchStart = (ev) => {
    const c = g.current; const touches = ev.touches;
    const el = containerRef.current; const rect = el ? el.getBoundingClientRect() : {left:0,top:0};
    if (touches.length === 2) {
      const [a,b] = [touches[0], touches[1]];
      const dx = b.clientX-a.clientX, dy = b.clientY-a.clientY;
      c.mode='pinch'; c.active=true; c.startD=Math.hypot(dx,dy)||1; c.startZoom=zoom;
      c.startPanX=pan.x; c.startPanY=pan.y;
      c.startMidX=(a.clientX+b.clientX)/2-rect.left; c.startMidY=(a.clientY+b.clientY)/2-rect.top;
      fadeHint();
    } else if (touches.length === 1) {
      const now = performance.now(); const t0 = touches[0];
      const tx=t0.clientX, ty=t0.clientY;
      if (now-c.lastTapAt < 320 && Math.hypot(tx-c.lastTapX, ty-c.lastTapY) < 40) {
        resetView(); c.lastTapAt=0; c.active=false; c.mode=null; fadeHint(); return;
      }
      c.lastTapAt=now; c.lastTapX=tx; c.lastTapY=ty;
      if (zoom > 1) { c.mode='pan-touch'; c.active=true; c.dragStartX=tx; c.dragStartY=ty; c.startPanX=pan.x; c.startPanY=pan.y; fadeHint(); }
      else { c.mode=null; c.active=false; }
    }
  };
  const onTouchMove = (ev) => {
    const c = g.current; if (!c.active) return;
    if (ev.cancelable) ev.preventDefault();
    const touches = ev.touches;
    if (c.mode==='pinch' && touches.length===2) {
      const [a,b]=[touches[0],touches[1]];
      const dx=b.clientX-a.clientX, dy=b.clientY-a.clientY;
      const newD=Math.hypot(dx,dy)||1; const ratio=newD/c.startD;
      const newZoom=Math.max(1, Math.min(5, c.startZoom*ratio));
      const el=containerRef.current; const rect=el?el.getBoundingClientRect():{left:0,top:0,width:1,height:1};
      const cx=(a.clientX+b.clientX)/2-rect.left; const cy=(a.clientY+b.clientY)/2-rect.top;
      const k=newZoom/c.startZoom;
      const offX=(cx-c.startMidX)+c.startPanX*k; const offY=(cy-c.startMidY)+c.startPanY*k;
      setZoom(newZoom); setPan(clampPan({x:offX,y:offY}, newZoom));
    } else if (c.mode==='pan-touch' && touches.length===1) {
      const t0=touches[0]; const dx=t0.clientX-c.dragStartX; const dy=t0.clientY-c.dragStartY;
      setPan(clampPan({x:c.startPanX+dx, y:c.startPanY+dy}, zoom));
    }
  };
  const onTouchEnd = (ev) => {
    const c = g.current;
    if (ev.touches.length===0) { c.active=false; c.mode=null; }
    else if (ev.touches.length===1 && c.mode==='pinch') {
      const t0=ev.touches[0]; c.mode = zoom>1?'pan-touch':null; c.active = c.mode==='pan-touch';
      c.dragStartX=t0.clientX; c.dragStartY=t0.clientY; c.startPanX=pan.x; c.startPanY=pan.y;
    }
  };
  const onMouseDown = (ev) => {
    if (zoom<=1) return; const c=g.current;
    c.mode='pan-mouse'; c.active=true; c.dragStartX=ev.clientX; c.dragStartY=ev.clientY;
    c.startPanX=pan.x; c.startPanY=pan.y; fadeHint();
  };
  const onMouseMove = (ev) => {
    const c=g.current; if (!c.active || c.mode!=='pan-mouse') return;
    const dx=ev.clientX-c.dragStartX, dy=ev.clientY-c.dragStartY;
    setPan(clampPan({x:c.startPanX+dx, y:c.startPanY+dy}, zoom));
  };
  const endMouse = () => { const c=g.current; if (c.mode==='pan-mouse'){ c.active=false; c.mode=null; } };
  const onWheel = (ev) => {
    if (ev.cancelable) ev.preventDefault(); fadeHint();
    const el=containerRef.current; const rect=el?el.getBoundingClientRect():{left:0,top:0,width:1,height:1};
    const cx=ev.clientX-rect.left-rect.width/2; const cy=ev.clientY-rect.top-rect.height/2;
    const delta=ev.deltaY>0?-0.12:0.12; const newZoom=Math.max(1, Math.min(5, zoom*(1+delta))); const k=newZoom/zoom;
    setZoom(newZoom); setPan(clampPan({x:cx-(cx-pan.x)*k, y:cy-(cy-pan.y)*k}, newZoom));
  };
  React.useEffect(() => {
    const el=containerRef.current; if (!el) return;
    const tm=(ev)=>onTouchMove(ev); const wh=(ev)=>onWheel(ev);
    el.addEventListener('touchmove', tm, {passive:false});
    el.addEventListener('wheel', wh, {passive:false});
    return () => { el.removeEventListener('touchmove', tm); el.removeEventListener('wheel', wh); };
  }, [zoom, pan.x, pan.y, hintFaded, tab]);

  const step = (dir) => { fadeHint(); setZoom(z => {
    const nz = dir>0 ? Math.min(5, +(z*1.25).toFixed(3)) : Math.max(1, +(z/1.25).toFixed(3));
    if (nz <= 1) setPan({x:0,y:0});
    return nz;
  }); };

  const RAIL_W = 600;
  const railRight = RAIL_W + 72 + 48;   // rail width + right inset + gutter

  return (
    <div style={{position:'absolute', inset:0, background:'transparent', color:'var(--ink)', overflow:'hidden'}}>

      {/* ── HEADER (matches FloorSelect / FloorPlate) ─────────────── */}
      <div style={{position:'absolute', top:54, left:72, right:72, display:'flex', justifyContent:'space-between', alignItems:'flex-end', opacity:e, transform:`translateY(${(1-e)*-12}px)`, zIndex:14}}>
        <div style={{display:'flex', alignItems:'flex-end', gap:34}}>
          <button onClick={()=>navigate('inventory')} style={fsBackBtn}
            onMouseEnter={ev=>{ev.currentTarget.style.background='var(--gold)'; ev.currentTarget.style.color='#0a0807'; ev.currentTarget.style.borderColor='var(--gold)';}}
            onMouseLeave={ev=>{ev.currentTarget.style.background='transparent'; ev.currentTarget.style.color='var(--ink)'; ev.currentTarget.style.borderColor='var(--line)';}}>
            <Icons.back width={28} height={28}/>
          </button>
          <div>
            <Breadcrumb crumbs={[
              {label:'Inventory', go:()=>navigate('inventory')},
              {label:'G+2 Floors'},
            ]}/>
            <div className="serif" style={{fontSize:84, fontWeight:300, letterSpacing:'-0.022em', lineHeight:1, marginTop:12}}>G+2 Floors</div>
            <div style={{fontSize:23, color:'var(--slate)', marginTop:12, fontStyle:'italic'}}>Ground, First &amp; Second — the podium amenity levels</div>
          </div>
        </div>
        <div role="button" tabIndex={0} aria-label="Go to home"
          onClick={()=>navigate('home')}
          onKeyDown={ev=>{ if(ev.key==='Enter'||ev.key===' '){ ev.preventDefault(); navigate('home'); } }}
          onMouseEnter={ev=>{ ev.currentTarget.style.opacity='0.6'; ev.currentTarget.style.transform='scale(1.03)'; }}
          onMouseLeave={ev=>{ ev.currentTarget.style.opacity='1'; ev.currentTarget.style.transform='scale(1)'; }}
          style={{display:'flex', alignItems:'center', gap:22, cursor:'pointer', transformOrigin:'right center', transition:'opacity 200ms ease, transform 200ms ease'}}>
          <UniverseMonogram size={68} progress={1} color="var(--gold-deep)"/>
          <div style={{display:'flex', flexDirection:'column', gap:8, alignItems:'flex-end'}}>
            <UniverseWordmark size={26} color="var(--ink)" tight/>
            <VenusCredit size={13}/>
          </div>
        </div>
      </div>

      {/* ── TABS (mono pills, gold active) ─────────────────────────── */}
      <div style={{position:'absolute', top:262, left:88, right:railRight, display:'flex', gap:16, zIndex:12,
        opacity:e, transform:`translateY(${(1-e)*-8}px)`}}>
        {GP_TABS.map((tb, i) => {
          const on = tb.key === tab;
          return (
            <button key={tb.key} className="gp-tab" onClick={()=>{ try{ window.RDA && window.RDA.track('gplus_floor', tb.key); }catch(e){} setTab(tb.key); }}
              style={{display:'inline-flex', alignItems:'center', gap:12, padding:'16px 30px', borderRadius:105, cursor:'pointer',
                border:`1.4px solid ${on ? 'var(--gold-deep)' : 'var(--line)'}`,
                background: on ? 'linear-gradient(180deg, var(--gold) 0%, var(--gold-deep) 100%)' : 'rgba(255,253,248,0.6)',
                color: on ? '#1a130a' : 'var(--graphite)',
                boxShadow: on ? '0 14px 30px rgba(176,138,63,0.30), inset 0 1px 0 rgba(255,255,255,0.4)' : 'none'}}>
              <span className="serif" style={{fontSize:26, fontWeight:600, opacity:0.85, lineHeight:1}}>{tb.short}</span>
              <span className="mono" style={{fontSize:16, letterSpacing:'0.26em', fontWeight:700}}>{tb.label}</span>
            </button>
          );
        })}
      </div>

      {/* ── PAN / ZOOM VIEWER ──────────────────────────────────────── */}
      <div style={{position:'absolute', top:352, left:88, right:railRight, bottom:78, zIndex:5,
        borderRadius:20, overflow:'hidden', background:'#fbf6ec', border:'1px solid rgba(176,138,63,0.30)',
        boxShadow:'0 24px 60px rgba(90,64,20,0.10), inset 0 1px 0 rgba(255,255,255,0.7)',
        animation:'gpFadeUp .6s cubic-bezier(0.22,1,0.36,1) both'}}>
        <div ref={containerRef}
          onTouchStart={onTouchStart} onTouchEnd={onTouchEnd} onTouchCancel={onTouchEnd}
          onMouseDown={onMouseDown} onMouseMove={onMouseMove} onMouseUp={endMouse} onMouseLeave={endMouse}
          style={{position:'absolute', inset:0, overflow:'hidden', touchAction:'none',
            cursor: zoom>1 ? (g.current.mode==='pan-mouse'?'grabbing':'grab') : 'default'}}>
          {/* contain-fit centring frame; pins live inside the transformed layer */}
          <div style={{position:'absolute', inset:0, display:'flex', alignItems:'center', justifyContent:'center',
            padding:24, pointerEvents:'none'}}>
            <div ref={wrapperRef} style={{position:'relative', height:'100%', maxHeight:'100%', maxWidth:'100%',
              aspectRatio: active.aspect, margin:'auto',
              transform:`translate(${pan.x}px, ${pan.y}px) scale(${zoom})`, transformOrigin:'50% 50%',
              transition: g.current.active ? 'none' : 'transform 320ms cubic-bezier(0.22,1,0.36,1)'}}>
              <img key={active.key} src={active.img} alt={`${active.label} floor plan`} draggable={false}
                style={{position:'absolute', inset:0, width:'100%', height:'100%', objectFit:'fill', display:'block',
                  userSelect:'none', WebkitUserSelect:'none', pointerEvents:'none', mixBlendMode:'multiply'}}
                onError={(ev)=>{ ev.currentTarget.style.opacity=0.15; }}/>

              {Object.entries(pinMap).map(([n, [fx, fy]]) => {
                const on = focus === n;
                const ps = Math.max(0.55, Math.min(1, 1/zoom));   // counter-scale so pins stay legible
                return (
                  <button key={n} className="gp-pin" aria-label={`Focus item ${n}`} aria-pressed={on}
                    onClick={(ev)=>{ ev.stopPropagation(); focusItem(n); }}
                    onMouseDown={(ev)=>ev.stopPropagation()} onTouchStart={(ev)=>ev.stopPropagation()}
                    style={{position:'absolute', left:`${fx*100}%`, top:`${fy*100}%`, zIndex: on?4:3,
                      width:46, height:46, margin:0, padding:0, border:'none', background:'transparent',
                      cursor:'pointer', pointerEvents:'auto', transformOrigin:'center',
                      transform:`translate(-50%,-50%) scale(${ps})`,
                      transition: g.current.active ? 'none' : 'transform 220ms cubic-bezier(0.22,1,0.36,1)'}}>
                    {on && <span aria-hidden="true" style={{position:'absolute', inset:-5, borderRadius:'50%',
                      border:'2px solid var(--gold-deep)', animation:'gpPinPulse 1.8s ease-out infinite'}}/>}
                    <span className="gp-pin-tok" style={{position:'absolute', inset:0, borderRadius:'50%',
                      display:'flex', alignItems:'center', justifyContent:'center', transformOrigin:'center',
                      background:'radial-gradient(circle at 50% 36%, #fffdf8, #e9d6b0)', border:'1.5px solid var(--gold-deep)',
                      boxShadow: on ? '0 0 0 3px rgba(201,160,94,0.38), 0 8px 20px rgba(120,86,28,0.30)'
                                    : '0 6px 16px rgba(120,86,28,0.26)'}}>
                      <span className="serif" style={{fontSize:19, fontWeight:600, color:'var(--gold-deep)',
                        fontVariantNumeric:'tabular-nums', lineHeight:1}}>{n}</span>
                    </span>
                  </button>
                );
              })}
            </div>
          </div>
        </div>

        {/* zoom controls */}
        <div style={{position:'absolute', bottom:22, left:22, display:'flex', gap:8, zIndex:3}}>
          {[['−', ()=>step(-1), zoom<=1],
            ['+', ()=>step(1),  zoom>=5]].map(([lbl, fn, dis]) => (
            <button key={lbl} className="gp-zoom-btn" onClick={fn} disabled={dis} aria-label={lbl==='+'?'Zoom in':'Zoom out'}
              style={{width:54, height:54, borderRadius:14, cursor:'pointer', fontSize:26, lineHeight:1,
                display:'flex', alignItems:'center', justifyContent:'center',
                border:'1px solid rgba(176,138,63,0.5)', background:'rgba(255,253,248,0.92)', color:'var(--gold-deep)',
                boxShadow:'0 8px 20px rgba(120,86,28,0.16)'}}>{lbl}</button>
          ))}
          <button className="gp-zoom-btn" onClick={resetView} disabled={zoom<=1} aria-label="Reset view"
            style={{width:'auto', padding:'0 20px', height:54, borderRadius:14, cursor:'pointer',
              display:'flex', alignItems:'center', justifyContent:'center',
              border:'1px solid rgba(176,138,63,0.5)', background:'rgba(255,253,248,0.92)', color:'var(--gold-deep)',
              boxShadow:'0 8px 20px rgba(120,86,28,0.16)'}}>
            <span className="mono" style={{fontSize:14, letterSpacing:'0.2em'}}>RESET</span>
          </button>
        </div>

        {/* zoom % readout */}
        <div className="mono" style={{position:'absolute', bottom:24, right:22, padding:'12px 20px', borderRadius:105, zIndex:3,
          background:'rgba(20,16,11,0.86)', border:'1px solid rgba(232,215,168,0.30)', fontSize:15, letterSpacing:'0.2em', color:'rgba(232,215,168,0.95)'}}>
          {Math.round(zoom*100)}%
        </div>

        {/* gesture hint */}
        <div className="mono" style={{position:'absolute', top:22, left:'50%', transform:'translateX(-50%)', padding:'10px 19px', borderRadius:105,
          background:'rgba(20,16,11,0.78)', border:'1px solid rgba(232,215,168,0.32)', fontSize:13, letterSpacing:'0.26em', color:'var(--gold-deep)',
          opacity: hintFaded?0:1, transition:'opacity 480ms ease', pointerEvents:'none', zIndex:2, whiteSpace:'nowrap'}}>
          PINCH OR SCROLL TO ZOOM · DRAG TO PAN · DOUBLE-TAP TO RESET
        </div>
      </div>

      {/* ── LEGEND RAIL (scrollable) ──────────────────────────────── */}
      <div style={{position:'absolute', top:262, right:72, width:RAIL_W, bottom:78, zIndex:6,
        opacity:e, transform:`translateY(${(1-e)*14}px)`,
        borderRadius:18, overflow:'hidden', display:'flex', flexDirection:'column',
        border:'1px solid rgba(176,138,63,0.40)', background:'rgba(255,252,246,0.66)', backdropFilter:'blur(3px)',
        boxShadow:'0 24px 60px rgba(90,64,20,0.10), inset 0 1px 0 rgba(255,255,255,0.7)'}}>

        <div style={{padding:'24px 28px 16px', borderBottom:'1px solid rgba(176,138,63,0.26)'}}>
          <div style={{display:'flex', alignItems:'center', gap:13}}>
            <span style={{width:30, height:2, background:'var(--gold-deep)'}}/>
            <span className="mono" style={{fontSize:18, letterSpacing:'0.22em', color:'var(--gold-deep)'}}>{active.label} FLOOR · LEGEND</span>
          </div>
        </div>

        <div className="scroll" style={{flex:1, overflowY:'auto', WebkitOverflowScrolling:'touch', padding:'20px 28px 26px'}}>
          {active.groups.map((grp, gi) => (
            <div key={gi} style={{marginTop: gi ? 26 : 0}}>
              {grp.title && (
                <div className="mono" style={{fontSize:15, letterSpacing:'0.24em', color:'var(--slate)', textTransform:'uppercase', marginBottom:14, fontWeight:700}}>
                  {grp.title}
                </div>
              )}
              <div style={{display:'flex', flexDirection:'column', gap:10}}>
                {grp.items.map(([n, label], i) => {
                  const hasPin = !!pinMap[n];
                  const on = focus === n;
                  return (
                    <div key={n} className={`gp-legrow${hasPin ? ' gp-clickable' : ''}`}
                      role={hasPin ? 'button' : undefined} tabIndex={hasPin ? 0 : undefined} aria-pressed={hasPin ? on : undefined}
                      onClick={hasPin ? (()=>focusItem(n)) : undefined}
                      onKeyDown={hasPin ? (ev=>{ if(ev.key==='Enter'||ev.key===' '){ ev.preventDefault(); focusItem(n); } }) : undefined}
                      style={{display:'flex', alignItems:'center', gap:16, padding:'11px 14px', borderRadius:12,
                      background: on ? 'rgba(201,160,94,0.20)' : (i%2 ? 'rgba(255,255,255,0.42)' : 'rgba(255,250,240,0.18)'),
                      border: on ? '1px solid var(--gold-deep)' : '1px solid rgba(176,138,63,0.20)',
                      boxShadow: on ? '0 8px 22px rgba(176,138,63,0.18)' : 'none',
                      animation:`gpRowIn .42s cubic-bezier(0.22,1,0.36,1) ${0.04+i*0.02}s both`}}>
                      <span style={{width:44, height:44, borderRadius:11, flexShrink:0, display:'flex', alignItems:'center', justifyContent:'center',
                        background: on ? 'radial-gradient(circle at 50% 38%, #fff6df, #cfa456)' : 'radial-gradient(circle at 50% 38%, #fffdf8, #e9d6b0)',
                        border:'1.5px solid var(--gold-deep)', boxShadow: on ? '0 0 0 3px rgba(201,160,94,0.30)' : 'none'}}>
                        <span className="serif" style={{fontSize:20, fontWeight:600, color:'var(--gold-deep)', fontVariantNumeric:'tabular-nums'}}>{n}</span>
                      </span>
                      <span className="serif" style={{fontSize:22, fontWeight:400, color: on ? 'var(--gold-deep)' : 'var(--ink)', lineHeight:1.3}}>{label}</span>
                    </div>
                  );
                })}
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

window.GPlus = GPlus;
