/* eslint-disable */
/* =========================================================
   TwinFloor — operations view for a site model (light theme).
   Alerts rail · Plan/Volume twin with channel status-bands
   (Comfort/Temp/Humidity/Occupancy) · rich device inspector ·
   room utilisation + entrance footfall. Learnings adapted from
   the Wirepas ops mockup — data & structure, TEO palette.
   ========================================================= */
const TwinFloor = ({ site, devices, onOpenDevice }) => {
  const B = window.SITE_BUILT;
  const model = B && B.model;
  const [selId, setSelId] = React.useState('S15');
  const [view, setView]   = React.useState('plan');       // plan | volume
  const [chan, setChan]   = React.useState('comfort');     // comfort | temp | humidity | occupancy
  const [az, setAz]       = React.useState(0);             // volume orbit (azimuth)
  const [tiltK, setTiltK] = React.useState(0.5);           // volume tilt factor
  const drag = React.useRef(null);

  if (!model) return <div style={{ padding:32, color:'#5A5E6E' }}>No site model loaded.</div>;

  const env = model.envelope_mm, W = env.width, H = env.depth, PAD = 1200;
  const fy = y => H - y;
  const byId = {}; devices.forEach(d=>byId[d.id]=d);
  const sel = selId ? byId[selId] : null;
  const chanBandKey = chan === 'comfort' ? 'comfort' : chan === 'temp' ? 'temp' : chan;

  // status band palette (semantic, not brand accent)
  const BAND = {
    nominal:{ flat:'rgba(46,158,107,0.16)', top:'rgba(46,158,107,0.55)', wall:'rgba(46,158,107,0.36)', stroke:'#2E9E6B', dot:'#2E9E6B', label:'Nominal' },
    watch:  { flat:'rgba(224,161,59,0.22)', top:'rgba(224,161,59,0.60)', wall:'rgba(224,161,59,0.40)', stroke:'#D99A2B', dot:'#E0A13B', label:'Watch' },
    out:    { flat:'rgba(196,74,74,0.18)',  top:'rgba(196,74,74,0.55)',  wall:'rgba(196,74,74,0.38)',  stroke:'#C44A4A', dot:'#C44A4A', label:'Out of band' },
    nodata: { flat:'rgba(154,163,178,0.14)',top:'rgba(154,163,178,0.42)',wall:'rgba(154,163,178,0.28)',stroke:'#B8C0CE', dot:'#9AA3B2', label:'No data' },
  };
  const STATE_BAND = { ok:'nominal', warn:'watch', down:'out', idle:'nodata' };
  const roomBand = (rm) => (B.roomStatus[rm.id] && B.roomStatus[rm.id].band[chanBandKey]) || 'nodata';

  // ---------- furniture (grey line drawings, read on coloured rooms) ----------
  const bbox = (rm) => { const xs=rm.polygon_mm.map(p=>p[0]), ys=rm.polygon_mm.map(p=>p[1]);
    const nx=Math.min.apply(null,xs), xx=Math.max.apply(null,xs), ny=Math.min.apply(null,ys), xy=Math.max.apply(null,ys);
    return { x:nx, y:fy(xy), w:xx-nx, h:xy-ny, cx:(nx+xx)/2, cy:fy((ny+xy)/2) }; };
  const seat=(k,x,y)=><circle key={k} cx={x} cy={y} r={190} fill="none" stroke="#9AA2B2" strokeWidth={26}/>;
  const deskLine='#8A93A6';
  function pod(key,cx,cy,w,per){ const e=[],bh=820; e.push(<rect key={key+'b'} x={cx-w/2} y={cy-bh/2} width={w} height={bh} rx={50} fill="none" stroke={deskLine} strokeWidth={28}/>);
    for(let i=0;i<per;i++){const sx=cx-w/2+w*(i+0.5)/per; e.push(seat(key+'t'+i,sx,cy-bh/2-280)); e.push(seat(key+'d'+i,sx,cy+bh/2+280));} return e; }
  function table(key,cx,cy,tw,th,n){ const e=[]; e.push(<rect key={key} x={cx-tw/2} y={cy-th/2} width={tw} height={th} rx={160} fill="none" stroke={deskLine} strokeWidth={28}/>);
    const side=Math.ceil(n/2); for(let i=0;i<side;i++){const sx=cx-tw/2+tw*(i+0.5)/side; e.push(seat(key+'a'+i,sx,cy-th/2-280)); if(i<n-side) e.push(seat(key+'c'+i,sx,cy+th/2+280));} return e; }
  function furniture(rm){ const b=bbox(rm),out=[]; const banks=model.desk_banks.filter(d=>d.zone_id===rm.id);
    if(banks.length){ banks.forEach((bk,i)=>{ const cy=b.y+b.h*(i+0.5)/banks.length; pod(bk.id,b.cx,cy,Math.min(b.w*0.62,3600),3).forEach(x=>out.push(x)); }); return out; }
    switch(rm.id){ case 'boardroom_10p': table('bd',b.cx,b.cy,1500,3400,10).forEach(x=>out.push(x)); break;
      case 'meeting_6p': table('m6',b.cx,b.cy,1300,2000,6).forEach(x=>out.push(x)); break;
      case 'meeting_3p_w': table('mw',b.cx,b.cy,1100,1100,3).forEach(x=>out.push(x)); break;
      case 'meeting_3p_e': table('me',b.cx,b.cy,1100,1100,3).forEach(x=>out.push(x)); break;
      case 'office_1p': out.push(<rect key="o1" x={b.cx-850} y={b.cy-380} width={1700} height={760} rx={50} fill="none" stroke={deskLine} strokeWidth={28}/>, seat('o1s',b.cx,b.cy+560)); break;
      case 'workbench_3p': pod('wb',b.cx,b.cy,Math.min(b.w*0.7,3000),3).forEach(x=>out.push(x)); break;
      case 'tea_point': out.push(<rect key="tp" x={b.x+380} y={b.cy-260} width={b.w*0.5} height={620} rx={60} fill="none" stroke="#7FA9BE" strokeWidth={30}/>); break;
      case 'hot_desks': [0,1].forEach(j=>{const cx=b.x+b.w*(j*0.5+0.28); table('hd'+j,cx,b.cy,1200,820,4).forEach(x=>out.push(x));}); break;
      case 'arrival_lounge': for(let i=0;i<4;i++) out.push(<rect key={'al'+i} x={b.cx-1600+i*850} y={b.cy-320} width={640} height={640} rx={80} fill="none" stroke="#B7B195" strokeWidth={32}/>); break;
      default: break; } return out; }

  // ---------- markers ----------
  const anchors=devices.filter(d=>d.kind==='anchor'), gw=devices.find(d=>d.kind==='gateway');
  const dist=(a,b2)=>Math.hypot(a.position_mm[0]-b2.position_mm[0], a.position_mm[1]-b2.position_mm[1]);
  const links=[]; devices.forEach(d=>{ if(['anchor','gateway','router'].includes(d.kind)||!anchors.length) return; let n=anchors[0]; anchors.forEach(a=>{if(dist(d,a)<dist(d,n))n=a;}); links.push([d,n]); });
  if(gw) anchors.forEach(a=>links.push([a,gw]));
  const TYPE={ sensor:{shape:'circle',r:400}, door:{shape:'square',r:380}, anchor:{shape:'circle',r:440}, gateway:{shape:'square',r:440}, router:{shape:'square',r:380} };
  const marker=(d)=>{ const t=TYPE[d.kind]||TYPE.sensor, x=d.position_mm[0], y=fy(d.position_mm[1]);
    const bd=BAND[STATE_BAND[d.state]||'nominal'], active=selId===d.id, shift=d.kind==='router'?700:0;
    return (<g key={d.id} transform={`translate(${x+shift},${y})`} style={{cursor:'pointer'}} onClick={()=>setSelId(d.id)}>
      {active && <circle r={t.r+300} fill="none" stroke="#0A0F1F" strokeWidth={70}/>}
      {t.shape==='circle' ? <circle r={t.r} fill={bd.dot} stroke="#FFF" strokeWidth={70}/> : <rect x={-t.r} y={-t.r} width={t.r*2} height={t.r*2} rx={100} fill={bd.dot} stroke="#FFF" strokeWidth={70}/>}
      <text textAnchor="middle" dy={t.r*0.34} fill="#FFF" style={{font:`600 ${Math.round(t.r*0.8)}px 'IBM Plex Sans'`}}>{d.id}</text>
    </g>); };

  // ---------- PLAN svg ----------
  const planSvg = (
    <svg viewBox={`${-PAD} ${-PAD} ${W+2*PAD} ${H+2*PAD}`} style={{width:'100%',height:'auto',maxHeight:'62vh',display:'block'}} preserveAspectRatio="xMidYMid meet">
      {model.rooms.map(rm=>{ const bd=BAND[roomBand(rm)]; const pts=rm.polygon_mm.map(p=>`${p[0]},${fy(p[1])}`).join(' ');
        return <polygon key={rm.id+'r'} points={pts} fill={bd.flat} stroke={bd.stroke} strokeWidth={rm.enclosed?40:30} strokeDasharray={!rm.enclosed?'150 110':undefined}/>; })}
      {model.rooms.map(rm=><g key={rm.id+'f'} opacity="0.7">{furniture(rm)}</g>)}
      {links.map(([a,b2],i)=><line key={'k'+i} x1={a.position_mm[0]} y1={fy(a.position_mm[1])} x2={b2.position_mm[0]} y2={fy(b2.position_mm[1])} stroke="#9FB4E8" strokeWidth={20} strokeDasharray="80 80" opacity="0.4"/>)}
      {devices.map(marker)}
      {model.rooms.map(rm=>{ if(model.desk_banks.some(d=>d.zone_id===rm.id)) return null; const topY=Math.max.apply(null,rm.polygon_mm.map(p=>p[1]));
        return <text key={rm.id+'n'} x={rm.centroid_mm[0]} y={fy(topY)+420} textAnchor="middle" fill="#3B4150" style={{font:"500 300px 'IBM Plex Sans'"}}>{rm.name}</text>; })}
    </svg>
  );

  // ---------- VOLUME svg (rotatable axonometric extrusion) ----------
  const HT=2600, VCX=W/2, VCY=H/2;
  const proj=(px,py,pz)=>{ const dx=px-VCX, dy=py-VCY, ca=Math.cos(az), sa=Math.sin(az);
    const rx=dx*ca-dy*sa, ry=dx*sa+dy*ca; return [ (rx-ry)*0.866, (rx+ry)*tiltK - pz ]; };
  const roomsByDepth=model.rooms.map(rm=>({ rm, depth: proj(rm.centroid_mm[0],rm.centroid_mm[1],0)[1] }))
    .sort((a,b)=>a.depth-b.depth).map(o=>o.rm);
  const prism=(rm)=>{ const xs=rm.polygon_mm.map(p=>p[0]), ys=rm.polygon_mm.map(p=>p[1]);
    const nx=Math.min.apply(null,xs),xx=Math.max.apply(null,xs),ny=Math.min.apply(null,ys),xy=Math.max.apply(null,ys);
    const bd=BAND[roomBand(rm)];
    const c=[[nx,ny],[xx,ny],[xx,xy],[nx,xy]];
    const base=c.map(p=>proj(p[0],p[1],0)), top=c.map(p=>proj(p[0],p[1],HT));
    const poly=a=>a.map(p=>`${p[0]},${p[1]}`).join(' ');
    // draw the two walls incident to the nearest base corner (front-facing under any orbit)
    let ni=0; for(let i=1;i<4;i++) if(base[i][1]>base[ni][1]) ni=i;
    const pairs=[[(ni+3)%4,ni],[ni,(ni+1)%4]];
    const cP=proj(rm.centroid_mm[0], rm.centroid_mm[1], HT);
    return (<g key={rm.id+'p'}>
      {pairs.map(([i,j],k)=><polygon key={rm.id+'w'+k} points={poly([base[i],base[j],top[j],top[i]])} fill={bd.wall} stroke={bd.stroke} strokeWidth={70}/>)}
      <polygon points={poly(top)} fill={bd.top} stroke={bd.stroke} strokeWidth={80} style={{cursor:'pointer'}} onClick={()=>{ const s=devices.find(d=>d.room_id===rm.id&&d.kind==='sensor'); if(s) setSelId(s.id); }}/>
      <text x={cP[0]} y={cP[1]-240} textAnchor="middle" fill="#2A2F3C" style={{font:"600 560px 'IBM Plex Sans'"}}>{rm.name}</text>
    </g>); };
  const onDrag=(e)=>{ if(!drag.current) return; const dx=e.clientX-drag.current.x, dy=e.clientY-drag.current.y;
    setAz(a=>a+dx*0.008); setTiltK(t=>Math.max(0.18,Math.min(0.62, t - dy*0.0016))); drag.current={x:e.clientX,y:e.clientY}; };
  const volumeSvg=(
    <div style={{ userSelect:'none', touchAction:'none' }}
      onMouseDown={e=>{ drag.current={x:e.clientX,y:e.clientY}; }} onMouseMove={onDrag}
      onMouseUp={()=>{ drag.current=null; }} onMouseLeave={()=>{ drag.current=null; }}
      onWheel={e=>{ setTiltK(t=>Math.max(0.18,Math.min(0.62, t + e.deltaY*0.0006))); }}
      onDoubleClick={()=>{ setAz(0); setTiltK(0.5); }}>
      <svg viewBox="-26000 -20000 52000 40000" style={{width:'100%',height:'auto',maxHeight:'62vh',display:'block',cursor: drag.current?'grabbing':'grab'}} preserveAspectRatio="xMidYMid meet">
        {roomsByDepth.map(prism)}
      </svg>
    </div>
  );

  // ---------- pieces ----------
  const seg=(v,set,opts)=>(<div style={{display:'inline-flex',border:'1px solid #E2E5EC',borderRadius:0,overflow:'hidden'}}>
    {opts.map(o=><button key={o.k} onClick={()=>set(o.k)} style={{padding:'7px 13px',border:'none',background:v===o.k?'#0A0F1F':'#FFF',color:v===o.k?'#FFF':'#3B4150',font:"500 12.5px 'IBM Plex Sans'",cursor:'pointer'}}>{o.label}</button>)}
  </div>);

  const sevColor={ out:'#C44A4A', watch:'#E0A13B', nodata:'#9AA3B2', info:'#3A6FF8' };
  const spark=(base)=>{ const s=window.MOCK.makeSeries?window.MOCK.makeSeries(base, base*0.06):[]; const mn=Math.min.apply(null,s),mx=Math.max.apply(null,s),rng=(mx-mn)||1;
    const pts=s.map((v,i)=>`${i/(s.length-1)*260},${34-((v-mn)/rng)*30}`).join(' '); return <polyline points={pts} fill="none" stroke="#C44A4A" strokeWidth={2}/>; };

  const fmtLast=(s)=>{ if(s>=86400) return Math.floor(s/86400)+' d'; const m=Math.floor(s/60),ss=s%60; return ('0'+m).slice(-2)+':'+('0'+ss).slice(-2); };

  return (
    <div style={{ padding:'20px 28px', background:'#F6F7F9', minHeight:'calc(100vh - 73px)', display:'flex', flexDirection:'column', gap:16 }}>
      {/* context header */}
      <div style={{ display:'flex', alignItems:'baseline', justifyContent:'space-between', flexWrap:'wrap', gap:10 }}>
        <div>
          <span style={{ fontSize:20, letterSpacing:'-0.02em', color:'#0A0F1F' }}>Exchange Quay — Level 02</span>
          <span style={{ fontSize:13, color:'#5A5E6E', marginLeft:12 }}>Teltonika UK · Building 08 · {B.stats.total} devices · Wirepas mesh</span>
        </div>
        <span style={{ display:'inline-flex', alignItems:'center', gap:7, fontSize:12.5, color:'#5A5E6E', fontFamily:"'IBM Plex Mono', monospace" }}>
          <span style={{ width:8, height:8, borderRadius:'50%', background:'#2E9E6B' }} /> LIVE · last packet {fmtLast(B.stats.lastPacketS)} ago
        </span>
      </div>

      {/* main grid */}
      <div style={{ display:'grid', gridTemplateColumns:'240px minmax(0,1fr) 290px', gap:16, alignItems:'start' }} data-collapse-md>
        {/* Alerts rail */}
        <div style={{ background:'#FFF', border:'1px solid #EDEDED' }}>
          <div style={{ padding:'12px 16px', borderBottom:'1px solid #EDEDED', fontSize:11, letterSpacing:'0.1em', textTransform:'uppercase', color:'#5A5E6E' }}>Alerts</div>
          <div style={{ display:'flex', flexDirection:'column' }}>
            {B.alerts.map(a=>(
              <div key={a.id} style={{ padding:'12px 16px', borderBottom:'1px solid #F0F0F2', borderLeft:'3px solid '+ (sevColor[a.sev]||'#E2E5EC') }}>
                <div style={{ display:'flex', justifyContent:'space-between', gap:8 }}>
                  <span style={{ fontSize:13.5, color:'#0A0F1F', fontWeight:600 }}>{a.where}<span style={{ fontWeight:400, color:'#5A5E6E' }}> · {a.msg}</span></span>
                  <span style={{ fontSize:13, color:sevColor[a.sev]||'#5A5E6E', fontFamily:"'IBM Plex Mono', monospace", whiteSpace:'nowrap' }}>{a.metric}</span>
                </div>
                <div style={{ fontSize:11, color:'#9AA3B2', marginTop:3, fontFamily:"'IBM Plex Mono', monospace" }}>{a.ago}</div>
              </div>
            ))}
          </div>
        </div>

        {/* Twin */}
        <div style={{ background:'#FFF', border:'1px solid #EDEDED' }}>
          <div style={{ padding:'12px 16px', borderBottom:'1px solid #EDEDED', display:'flex', alignItems:'center', gap:12, flexWrap:'wrap' }}>
            {seg(view,setView,[{k:'plan',label:'Plan'},{k:'volume',label:'Volume'}])}
            {seg(chan,setChan,[{k:'comfort',label:'Comfort'},{k:'temp',label:'Temp'},{k:'humidity',label:'Humidity'},{k:'occupancy',label:'Occupancy'}])}
            <div style={{ marginLeft:'auto', display:'flex', gap:12, flexWrap:'wrap' }}>
              {['nominal','watch','out','nodata'].map(b=>(
                <span key={b} style={{ display:'inline-flex', alignItems:'center', gap:6, fontSize:11.5, color:'#5A5E6E' }}>
                  <span style={{ width:10, height:10, borderRadius:2, background:BAND[b].dot }} />{BAND[b].label}
                </span>
              ))}
            </div>
          </div>
          <div style={{ padding:16, background:'#FBFCFD' }}>{view==='plan'?planSvg:volumeSvg}</div>
          {view==='volume' && <div style={{ padding:'0 16px 12px', fontSize:11, color:'#9AA3B2', fontFamily:"'IBM Plex Mono', monospace" }}>Drag to orbit · drag up/down (or scroll) to tilt · double-click to reset · shaded by {chan}</div>}
        </div>

        {/* Inspector */}
        <div style={{ background:'#FFF', border:'1px solid #EDEDED' }}>
          {!sel ? <div style={{ padding:18, color:'#9AA3B2', fontSize:13 }}>Select a device on the plan.</div> : (()=>{
            const L = sel.live||{}; const isSensor = sel.kind==='sensor';
            const tBand = BAND[isSensor ? STATE_BAND[sel.state] : 'nominal'];
            return (
              <div>
                <div style={{ padding:'16px 18px 10px' }}>
                  <div style={{ fontSize:16, color:'#0A0F1F' }}>{sel.zone}</div>
                  <div style={{ fontSize:12, color:'#5A5E6E', marginTop:2 }}>{sel.id} · {sel.model}{sel.part_number?(' '+sel.part_number):''}</div>
                </div>
                {isSensor && (
                  <div style={{ padding:'0 18px' }}>
                    {[['Temperature', (L.temperature_c!=null?L.temperature_c+' °C':'—'), STATE_BAND[sel.state]==='out'||L.temperature_c>27],
                      ['Humidity', (L.humidity_pct!=null?L.humidity_pct+' %':'—'), L.humidity_pct>65],
                      ['Movement', (L.reporting? (L.movement_min>0?('none '+L.movement_min+'m'):'active'):'—'), false],
                      ['Battery', (L.battery_years!=null?L.battery_years+' yr':'—'), false],
                      ['Last packet', (L.reporting?fmtLast(L.last_packet_s):fmtLast(L.last_packet_s)), !L.reporting],
                    ].map((row,i)=>(
                      <div key={i} style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', padding:'11px 0', borderBottom:'1px solid #F0F0F2' }}>
                        <span style={{ fontSize:13, color:'#5A5E6E' }}>{row[0]}</span>
                        <span style={{ fontSize:18, letterSpacing:'-0.01em', color: row[2]?'#C44A4A':'#0A0F1F', fontFamily:"'IBM Plex Mono', monospace" }}>{row[1]}</span>
                      </div>
                    ))}
                    <div style={{ padding:'12px 0 6px' }}><svg viewBox="0 0 260 36" width="100%" height="46">{spark(L.temperature_c||22)}</svg></div>
                    {sel.id==='S15' && <div style={{ fontSize:12, color:'#5A5E6E', lineHeight:1.5, paddingBottom:14 }}>Server room band is 18–27 °C. Above 27 °C raises a priority alert to the on-call list.</div>}
                  </div>
                )}
                {!isSensor && (
                  <div style={{ padding:'0 18px 14px' }}>
                    <div style={{ display:'flex', justifyContent:'space-between', padding:'11px 0', borderBottom:'1px solid #F0F0F2' }}>
                      <span style={{ fontSize:13, color:'#5A5E6E' }}>{sel.metric.label}</span>
                      <span style={{ fontSize:18, color:'#0A0F1F', fontFamily:"'IBM Plex Mono', monospace" }}>{sel.metric.value}{sel.metric.unit}</span>
                    </div>
                    {sel.metric.open_min>0 && <div style={{ display:'flex', justifyContent:'space-between', padding:'11px 0', borderBottom:'1px solid #F0F0F2' }}>
                      <span style={{ fontSize:13, color:'#5A5E6E' }}>Held open</span><span style={{ fontSize:18, color:'#C44A4A', fontFamily:"'IBM Plex Mono', monospace" }}>{sel.metric.open_min}m</span></div>}
                    <div style={{ marginTop:10, display:'flex', flexWrap:'wrap', gap:6 }}>
                      {(sel.channels||[]).map(c=><span key={c} style={{ fontSize:11, padding:'3px 8px', background:'#F1F3F9', border:'1px solid #E3E7F0', color:'#39404F', fontFamily:"'IBM Plex Mono', monospace" }}>{c}</span>)}
                    </div>
                  </div>
                )}
                {onOpenDevice && <div style={{ padding:'0 18px 16px' }}><button onClick={()=>onOpenDevice(sel)} style={{ width:'100%', padding:'9px 12px', background:'#0A0F1F', color:'#FFF', border:'none', cursor:'pointer', font:"500 13px 'IBM Plex Sans'" }}>Open full detail →</button></div>}
              </div>
            );
          })()}
        </div>
      </div>

      {/* bottom widgets */}
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:16 }} data-collapse-md>
        <div style={{ background:'#FFF', border:'1px solid #EDEDED', padding:'16px 20px' }}>
          <div style={{ fontSize:11, letterSpacing:'0.1em', textTransform:'uppercase', color:'#5A5E6E', marginBottom:14 }}>Room utilisation — today</div>
          {B.utilisation.map(u=>(
            <div key={u.id} style={{ display:'grid', gridTemplateColumns:'150px 1fr 44px', alignItems:'center', gap:12, marginBottom:11 }}>
              <span style={{ fontSize:13, color:'#3B4150', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{u.name}</span>
              <span style={{ height:8, background:'#EEF0F4', borderRadius:4, overflow:'hidden' }}><span style={{ display:'block', height:'100%', width:u.pct+'%', background:BAND[u.band].dot, borderRadius:4 }} /></span>
              <span style={{ fontSize:13, color:'#5A5E6E', textAlign:'right', fontFamily:"'IBM Plex Mono', monospace" }}>{u.pct}%</span>
            </div>
          ))}
        </div>
        <div style={{ background:'#FFF', border:'1px solid #EDEDED', padding:'16px 20px' }}>
          <div style={{ fontSize:11, letterSpacing:'0.1em', textTransform:'uppercase', color:'#5A5E6E', marginBottom:14 }}>Entrance footfall — D1 arrival</div>
          <div style={{ display:'flex', alignItems:'flex-end', gap:5, height:150 }}>
            {B.footfall.map(f=>{ const mx=Math.max.apply(null,B.footfall.map(x=>x.value));
              return <div key={f.h} style={{ flex:1, display:'flex', flexDirection:'column', alignItems:'center', gap:5 }}>
                <div title={f.value} style={{ width:'100%', height:Math.max(3,f.value/mx*120), background:f.peak?'#3A6FF8':'#9FB4E8', borderRadius:'2px 2px 0 0' }} />
                <span style={{ fontSize:10, color:'#9AA3B2', fontFamily:"'IBM Plex Mono', monospace" }}>{f.label}</span>
              </div>; })}
          </div>
        </div>
      </div>
    </div>
  );
};

Object.assign(window, { TwinFloor });
