// shell.jsx — chrome (scanlines, marquee, topbar, sidebar) + route container

const { useState: useStateS, useEffect: useEffectS, useRef: useRefS } = React;

// ─── CRT scanlines + marquee ──────────────────────────────────────
function CrtOverlay({ palette }) {
  return (
    <>
      <div style={{position:'fixed', inset:0, pointerEvents:'none', backgroundImage:`repeating-linear-gradient(0deg, ${palette.green}08 0px, ${palette.green}08 1px, transparent 1px, transparent 4px)`, zIndex:80}}/>
      <div style={{position:'fixed', inset:0, pointerEvents:'none', boxShadow:'inset 0 0 240px rgba(0,0,0,0.85)', zIndex:81}}/>
    </>
  );
}

function Marquee({ palette: M }) {
  const items = [
    '99% OF GAMBLERS QUIT RIGHT BEFORE THEY STRIKE IT BIG',
    'YOU MISS 100% OF THE FLIPS YOU DON\'T TAKE',
    'MY WIFE DOESN\'T KNOW I\'M HERE AND THAT\'S HER PROBLEM',
    'I TOLD MY KIDS DINNER IS COMING SOON · 4 HOURS AGO',
    'THE COIN HAS NO MEMORY · NEITHER DO I AFTER THIS LOSS',
    'FLIPPED HEADS 7 TIMES · STATISTICALLY DUE FOR HEADS AGAIN',
    'I\'M NOT A GAMBLER, I\'M AN UNPAID PROBABILITY RESEARCHER',
    'MY BUDGET FOR THE MONTH WAS $50 · I AM AHEAD OF SCHEDULE',
    'WIFE: WHERE\'S THE GROCERY MONEY · ME: ON BLACK',
    'THERAPIST SAID FACE YOUR FEARS · I\'M FACING TAILS',
    'JUST ONE MORE FLIP · — ME, 14 FLIPS AGO',
    'IF I LOSE THIS ONE I QUIT · IF I WIN THIS ONE I QUIT',
    'CALLED IN SICK TO WORK TO RECOVER FROM LAST NIGHT\'S LOSSES',
    'MY ALGORITHM: PICK HEADS · IT\'S FOOLPROOF',
    'I HAVE A GUT FEELING ABOUT THIS NEXT FLIP · I HAVE 47 GUT FEELINGS A DAY',
    'BREAKING: LOCAL MAN WINS 0.04 SOL · HOSTS PARADE',
    'I COULD STOP · I CHOOSE NOT TO · THAT\'S CHARACTER',
    'CHASING LOSSES IS BAD · I\'M HUNTING THEM',
    'HEADS UP I\'M NEVER LEAVING THIS WEBSITE',
    'MY SLEEP SCHEDULE IS DICTATED BY VARIANCE',
    'BOUGHT GROCERIES · PUT THEM BACK · FLIPPED INSTEAD',
    'MOM SAID I CAN\'T COME OUT TILL I HIT GREEN',
    'IF GAMBLING IS A PROBLEM, I AM THE SOLUTION',
    'I\'M NOT TILTED · TILTED IS A CHOICE · THIS IS WHO I AM',
    '52% HOUSE EDGE · I\'M PART OF THE 48% MOVEMENT',
    'I DON\'T NEED A FINANCIAL ADVISOR · I HAVE A FEELING',
    'COIN FLIP STREAK: 0W 12L · GROWTH MINDSET',
    'WIFE LEFT WITH THE KIDS · MORE TIME TO FLIP',
    'I HAVE A DEGREE IN THIS · NO I DON\'T',
    'NEXT ONE\'S GREEN · I CAN FEEL IT IN MY KNEES',
    'MY DOCTOR SAYS I HAVE LOW IRON · I SAY HE\'S NEVER FELT 100X',
    'PROVABLY FAIR · PROVABLY POOR',
    'INVESTED IN MYSELF · MYSELF PICKED TAILS',
    'I\'M NOT BROKE I\'M PRE-RICH',
    'THE HOUSE NEVER LOSES · I AM CO-OWNER OF THE HOUSE',
    'MY HOROSCOPE SAID TODAY IS MY DAY · IT WAS NOT',
    'SOLD MY GUITAR FOR THIS FLIP · WORTH IT',
    'MY GIRLFRIEND THINKS I\'M IN A BOOK CLUB',
    'I FLIPPED ON THE TOILET · ALSO LOST ON THE TOILET',
    'MOMMY DOES NOT KNOW I FOUND THE LAPTOP',
    'I HAVE NEVER BEEN THIS CLOSE TO HEADS',
    'CALL THAT A FLIP-LE NEGATIVE',
    'GET RICH OR FLIP TRYING',
    'I\'D RATHER BE LUCKY THAN SMART · I AM NEITHER',
    'TODAY I AM EXIT LIQUIDITY · TOMORROW I WILL BE EXIT LIQUIDITY',
    'I QUIT GAMBLING EVERY DAY · IT\'S A DAILY ACHIEVEMENT',
    'TIME ZONES MEAN I CAN ALWAYS FLIP SOMEWHERE',
    'NEW YEAR NEW ME · SAME COIN SAME L\'S',
    'I LOST MY JOB BUT GAINED A LIFESTYLE',
    'NOT FINANCIAL ADVICE · DEFINITELY NOT MEDICAL ADVICE EITHER',
    // Round 2 — 50 more
    'I ASKED CHATGPT FOR FLIP ADVICE · IT SAID HEADS · THANKS BRO',
    'STARTED THE DAY WITH 1 SOL · ENDED WITH 0 LEARNINGS',
    'MY ACCOUNTANT QUIT WHEN HE SAW MY HISTORY',
    'COULD HAVE PAID RENT · DECIDED TO SEE WHAT HAPPENS',
    'I HAVE A SYSTEM · THE SYSTEM IS LOSING',
    'GOD SHUT THE DOOR · I FOUND A WINDOW · IT WAS TAILS',
    'TURNED 21 · STILL CAN\'T HIT 21',
    'I AM THE 1 IN ONE-IN-A-MILLION · BUT FOR LOSING',
    'JUST WON 0.02 SOL · BLACKED OUT FROM JOY',
    'WIFE: WE NEED TO TALK · ME: AFTER THIS NEXT FLIP',
    'I AM NOT ADDICTED · MY GUT IS · BLAME MY GUT',
    'BARTENDER WANTS TO CUT ME OFF · I CUT MYSELF OFF FIRST',
    'WHEN MY THERAPIST ASKS HOW I FEEL · UP 0.3 SOL',
    'I LOSE WITH GRACE · I WIN WITH RAGE',
    'I FLIPPED THE LAST OF MY SAVINGS · TWICE',
    'MY MEDIUM SAYS THE SPIRITS PICK TAILS · WHO AM I TO ARGUE',
    'STREAK INTERRUPTED ONLY BY MY WIFE LEAVING',
    'PEOPLE SAY MONEY CAN\'T BUY HAPPINESS · I AM HERE TO TEST',
    'PUT THE COIN DOWN · NEVER · NOT EVEN ONCE',
    'TONIGHT\'S PLAN: DINNER WITH FAMILY · I MEAN, FLIP',
    'PEPSI OR COKE · BOTH GIVE ME LESS REGRET THAN COIN FLIPS',
    'IF AT FIRST YOU DON\'T SUCCEED · KEEP DOING THE EXACT SAME THING',
    'I CHECK MY BALANCE LIKE OTHER PEOPLE BREATHE',
    'ONE FLIP TO RULE THEM ALL · 47 SO FAR · STILL RULING',
    'MY KIDS HAD A SAVINGS ACCOUNT · IT WAS USEFUL',
    'I DON\'T HAVE A LOSING PROBLEM · LOSING HAS ME PROBLEM',
    'TOLD MY DAD I WORK IN FINANCE · NOT EVEN A LIE',
    'JUST ONE MORE · — ME · NEVER · NEVER · NEVER',
    'WROTE A WILL · LEFT EVERYTHING TO THE COIN',
    'I DON\'T HAVE A SOUL ANYMORE · I HAVE A WAGER',
    'SCAMMED BY THE COIN · WHO DO I REPORT THIS TO',
    'IT\'S NOT GAMBLING IF I FEEL CONFIDENT · I FEEL CONFIDENT',
    'COIN STAYED · LOYAL FRIEND',
    'I AM IN A LONG DISTANCE RELATIONSHIP WITH HEADS',
    'OFFICIAL DIAGNOSIS · DEGEN · TAKE TWO FLIPS AND CALL ME',
    'PRAYED TO ALL SEVEN GODS · ONLY THE COIN ANSWERED',
    'WIFE: WE\'RE GOING ON VACATION · ME: I AM ON VACATION',
    'I MEDITATE BEFORE EACH FLIP · I CALL IT TILT-PREVENTION',
    'BORROWED FROM MY MOM · SHE\'S A MAJOR INVESTOR NOW',
    'I AM AN ARTIST · MY CANVAS IS LOSING',
    'MY KIDS DRAW STICK FIGURES · I DRAW PORTFOLIO LIQUIDATIONS',
    'BOUGHT A CHAIR ON MARKETPLACE · ALSO LOST IT',
    'WHO NEEDS RETIREMENT · I HAVE THIS NEXT FLIP',
    'BANK ASKED IF MY ACCOUNT WAS HACKED · NO · NO ACCOUNT',
    'ANY DAY THE COIN HITS HEADS IS A GOOD DAY · ONE EVERY THREE WEEKS',
    'NEVER GIVE UP ON YOUR DREAMS · MY DREAM IS A 2X',
    'WIFE: HOW DID YOU SLEEP · ME: SOLD THE BED LAST WEEK',
    'WORLD\'S OLDEST MAN ALSO LOST 50% OF HIS FLIPS · WE\'RE EQUALS',
    'MY DOG IS MORE FINANCIALLY RESPONSIBLE THAN ME · HE IS',
    'I FLIP THEREFORE I AM · DESCARTES MIGHT BE A DEGEN',
  ];
  return (
    <div style={{
      background:M.green, color:M.bg, height:24, overflow:'hidden',
      display:'flex', alignItems:'center', flex:'0 0 auto',
      borderBottom:`2px solid ${M.bg}`,
      // sticky pin at the top of the flex container so it never scrolls out of view
      position:'sticky', top:0, zIndex:50,
    }}>
      <style>{`@keyframes mMarq{from{transform:translateX(0)}to{transform:translateX(-50%)}}`}</style>
      <div style={{display:'flex', whiteSpace:'nowrap', animation:'mMarq 480s linear infinite', fontSize:16, fontWeight:700, letterSpacing:'0.05em'}}>
        {[...items, ...items].map((t,i)=>(<span key={i} style={{padding:'0 24px'}}>► {t}</span>))}
      </div>
    </div>
  );
}

// ─── Sidebar ─────────────────────────────────────────────────────
// Icons are kept to text-style Unicode glyphs — emoji-prone characters
// like ⛓/☻ render double-wide via the OS color emoji font, which breaks
// alignment with the rest of the row.
const NAV = [
  { sec:'CASINO', items: [
    { k:'home', l:'home', g:'⌂' },
    { k:'flip', l:'coin flip', g:'◐', new:false },
    { k:'slots', l:'slots', g:(
      // Three vertical rectangles → slot-machine reels.
      // currentColor inherits the active/idle color from the wrapping span.
      <svg width="20" height="16" viewBox="0 0 20 16" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden="true">
        <rect x="1"     y="1.5" width="4.5" height="13"/>
        <rect x="7.75"  y="1.5" width="4.5" height="13"/>
        <rect x="14.5"  y="1.5" width="4.5" height="13"/>
      </svg>
    ) },
    { k:'roulette', l:'roulette', g:'◉' },
    { k:'baccarat', l:'baccarat', g:'♣' },
    { k:'blackjack', l:'blackjack', g:'♠' },
    { k:'hilo', l:'hi-lo', g:'⇅', new:true },
    { k:'poker', l:'video poker', g:'♥', new:true },
    { k:'mines', l:'mines', g:'▣', new:true },
  ]},
  { sec:'INFO', items: [
    { k:'fairness', l:'provably fair', g:'#' },
    { k:'how', l:'how it works', g:'?' },
    { k:'leaderboard', l:'leaderboard', g:'♔' },
  ]},
  { sec:'YOU', items: [
    { k:'profile', l:'profile', g:'@' },
    { k:'wallet', l:'bag/wallet', g:'$' },
  ]},
];

function Sidebar({ page, setPage, palette: M, wallet, mobileOpen, onClose }) {
  return (
    <>
      {/* Mobile backdrop — visible only when the drawer is open. Click dismisses. */}
      {mobileOpen && (
        <div className="rc-sidebar-backdrop" onClick={onClose} style={{
          display:'none', position:'fixed', inset:0, background:'rgba(0,0,0,0.55)', zIndex:90,
        }}/>
      )}
    <aside className={`rc-sidebar${mobileOpen ? ' rc-sidebar-open' : ''}`} style={{width:200, borderRight:`2px solid ${M.green}`, background:M.panel, display:'flex', flexDirection:'column', flex:'0 0 200px', padding:'12px 0', overflowY:'auto'}}>
      {NAV.map(group => (
        <div key={group.sec} style={{marginBottom:14}}>
          <div style={{padding:'4px 14px', color:M.green, fontSize:14, letterSpacing:'0.1em'}}>┤ {group.sec} ├</div>
          {group.items.map(it => {
            const active = page === it.k;
            return (
              <button key={it.k} onClick={()=>setPage(it.k)} style={{
                width:'100%', display:'flex', alignItems:'center', gap:10, padding:'6px 14px', background:active?M.green:'transparent', color:active?M.bg:M.ink, border:0, fontFamily:'inherit', fontSize:18, cursor:'pointer', textAlign:'left',
              }}>
                <span style={{
                  flex:'0 0 24px', width:24, height:24,
                  display:'inline-flex', alignItems:'center', justifyContent:'center',
                  color:active?M.bg:M.green, fontSize:18, lineHeight:1,
                  fontFamily:'"JetBrains Mono", ui-monospace, monospace',
                }}>{it.g}</span>
                <span style={{textTransform:'lowercase', flex:1}}>{it.l}</span>
                {it.new && <span style={{marginLeft:'auto', fontSize:10, background:M.hot, color:M.bg, padding:'1px 5px'}}>NEW</span>}
              </button>
            );
          })}
        </div>
      ))}
      <div style={{marginTop:'auto', padding:'10px 14px', borderTop:`2px dashed ${M.green}`, fontSize:14, color:M.ink2, lineHeight:1.4}}>
        <div style={{color:M.green}}>build 0009</div>
        <div>uptime: {(Math.floor(Date.now()/1000) % 99999).toString()}s</div>
        <div style={{color:M.yellow, marginTop:4}}>v1.4.6-beta</div>
      </div>
    </aside>
    </>
  );
}

// ─── Topbar ──────────────────────────────────────────────────────
function Topbar({ page, setPage, palette: M, wallet, fairness, onConnect, sol, onMenuClick, sidebarOpen, isOwner, chatHidden, onShowChat }) {
  const c = wallet.activeCoin;
  const [walletMenuOpen, setWalletMenuOpen] = useStateS(false);
  const walletRef = useRefS(null);
  // Sound mute toggle — initial value pulled from the shared module so it
  // matches whatever __playFile is using (localStorage-backed).
  const [muted, setMuted] = useStateS(() => getSoundsMuted());
  function toggleMute() {
    const next = !muted;
    setMuted(next);
    setSoundsMuted(next);
  }

  // Close the wallet dropdown when clicking outside it.
  useEffectS(() => {
    if (!walletMenuOpen) return;
    const onDown = (e) => {
      if (walletRef.current && !walletRef.current.contains(e.target)) {
        setWalletMenuOpen(false);
      }
    };
    document.addEventListener('mousedown', onDown);
    return () => document.removeEventListener('mousedown', onDown);
  }, [walletMenuOpen]);

  // Close the menu automatically if the user disconnects from elsewhere.
  useEffectS(() => { if (!wallet.connected) setWalletMenuOpen(false); }, [wallet.connected]);

  function copyAddress() {
    if (!wallet.address) return;
    try { navigator.clipboard?.writeText(wallet.address); } catch {}
  }

  return (
    <header className="rc-topbar" style={{display:'flex', alignItems:'center', gap:12, padding:'8px 18px', borderBottom:`2px dashed ${M.green}`, flex:'0 0 auto', position:'relative', zIndex:5, background:M.bg}}>
      {/* Hamburger — mobile-only sidebar toggle */}
      <button
        className="rc-hamburger rc-tap"
        onClick={onMenuClick}
        aria-label={sidebarOpen ? 'close menu' : 'open menu'}
        aria-expanded={!!sidebarOpen}
        style={{
          display:'none', flexDirection:'column', justifyContent:'center', alignItems:'center', gap:4,
          background:M.bg, color:M.green, border:`2px solid ${M.green}`,
          width:38, height:32, padding:0, cursor:'pointer',
        }}
      >
        <span style={{display:'block', width:18, height:2, background:'currentColor'}}/>
        <span style={{display:'block', width:18, height:2, background:'currentColor'}}/>
        <span style={{display:'block', width:18, height:2, background:'currentColor'}}/>
      </button>

      <div onClick={()=>setPage('home')} style={{cursor:'pointer', display:'flex', alignItems:'center', gap:10}}>
        <span className="rc-topbar-logo" style={{
          fontSize:36, fontWeight:700, color:M.green,
          textShadow:`0 0 12px ${M.green}, 3px 3px 0 ${M.hot}`,
          letterSpacing:'-0.02em', lineHeight:1, fontFamily:'inherit',
        }}>RUN!</span>
      </div>
      <span className="rc-hide-mobile" style={{fontSize:14, color:M.ink2, padding:'2px 6px', border:`1px solid ${M.ink2}`}}>v1.4.6</span>

      {/* Live SOL/USD price pill — polled every 5 minutes; drives the
          USDC/USDT bet-cap conversions across all games. */}
      {sol && (
        <div className="rc-hide-mobile" title={`live SOL/USD price · updates every 5 min${sol.updatedAt ? ` · last @ ${new Date(sol.updatedAt).toLocaleTimeString()}` : ' · using seed price until first fetch'}`} style={{
          display:'flex', alignItems:'center', gap:6, padding:'3px 10px',
          border:`1px solid ${M.cyan}`, color:M.cyan, fontSize:14,
        }}>
          <CoinIcon coin="SOL" size={14}/>
          <span style={{color:M.ink2}}>SOL</span>
          <span style={{color:M.yellow, fontVariantNumeric:'tabular-nums'}}>${sol.price.toFixed(2)}</span>
        </div>
      )}

      <div className="rc-topbar-spacer" style={{flex:1}}/>

      {/* Active fairness pill — click to open fairness panel */}
      <div
        className="rc-hide-mobile"
        onClick={()=>setPage('fairness')}
        title="open fairness panel"
        style={{
          display:'flex', alignItems:'center', gap:6, padding:'3px 10px',
          border:`1px solid ${M.green}`, color:M.green, fontSize:14, cursor:'pointer',
        }}
      >
        <span>⛓</span>
        <span>seed:</span>
        <span style={{color:M.yellow}}>{fairness.serverHash.slice(0,6)}…{fairness.serverHash.slice(-4)}</span>
        <span style={{color:M.ink2}}>n={fairness.nonce}</span>
      </div>

      {/* Show-chat pill — appears only when the chat panel has been
          collapsed (§5.63). Clicking restores t.showChat to true. On mobile
          the chat is always hidden by .rc-chat { display: none } in the
          @media block, so this button hides on mobile too via rc-hide-mobile
          (mobile users can't bring it back here — they'd toggle via the
          Tweaks panel if they ever want it). */}
      {chatHidden && (
        <button
          className="rc-topbar-btn rc-tap rc-hide-mobile"
          onClick={onShowChat}
          title="show chat panel"
          aria-label="show chat"
          style={{
            display:'flex', alignItems:'center', gap:6,
            background:M.bg, color:M.cyan, border:`2px solid ${M.cyan}`,
            padding:'3px 10px', cursor:'pointer', fontFamily:'inherit',
            fontSize:14, fontWeight:700, letterSpacing:'0.04em',
          }}
        >💬 CHAT</button>
      )}

      {/* Owner-only admin gear. Visible only when the connected wallet matches
          OWNER_PUBKEY (HANDOFF §16). Invisible to all other visitors. The
          page itself is also reachable by URL (?page=admin) for owner devices
          where the wallet isn't connected yet. */}
      {isOwner && (
        <button
          className="rc-topbar-btn rc-tap"
          onClick={()=>setPage('admin')}
          title="open admin dashboard"
          aria-label="admin"
          style={{
            display:'flex', alignItems:'center', justifyContent:'center',
            background: page === 'admin' ? M.green : M.bg,
            color:      page === 'admin' ? M.bg    : M.green,
            border:`2px solid ${M.green}`, padding:'3px 10px',
            cursor:'pointer', fontFamily:'inherit', fontSize:16, fontWeight:700,
            boxShadow: page === 'admin' ? `3px 3px 0 ${M.bg}` : 'none',
          }}
        >⚙ ADMIN</button>
      )}

      {/* Sound mute toggle */}
      <button className="rc-topbar-btn rc-topbar-sound rc-tap" onClick={toggleMute} title={muted ? 'click to unmute all sounds' : 'click to mute all sounds'} style={{
        display:'flex', alignItems:'center', gap:6,
        background: muted ? M.bg : M.cyan,
        color:      muted ? M.cyan : M.bg,
        border:`2px solid ${M.cyan}`, padding:'3px 10px',
        cursor:'pointer', fontFamily:'inherit', fontSize:14, fontWeight:700, letterSpacing:'0.05em',
        boxShadow: muted ? 'none' : `3px 3px 0 ${M.bg}`,
      }}>{muted ? '♪ MUTED' : '♪ SOUND'}</button>

      {/* Fun-mode toggle — lets you play with house play-money, no wallet needed */}
      <button className="rc-topbar-btn rc-tap" onClick={wallet.toggleFunMode} title={wallet.funMode?'switch back to real bag':'play with 10 SOL of house play-money — no wallet required'} style={{
        display:'flex', alignItems:'center', gap:6, background: wallet.funMode ? M.yellow : M.bg,
        color: wallet.funMode ? M.bg : M.yellow, border:`2px solid ${M.yellow}`, padding:'3px 10px',
        cursor:'pointer', fontFamily:'inherit', fontSize:14, fontWeight:700, letterSpacing:'0.05em',
        boxShadow: wallet.funMode ? `3px 3px 0 ${M.bg}` : 'none',
      }}>◉ FUN{wallet.funMode ? ' · ON' : ''}</button>
      {wallet.funMode && (
        <button className="rc-topbar-btn rc-tap rc-hide-mobile" onClick={wallet.resetFun} title="reset fun bag to 10 SOL" style={{
          background:M.bg, color:M.green, border:`1px solid ${M.green}`, padding:'3px 8px',
          cursor:'pointer', fontFamily:'inherit', fontSize:14,
        }}>↻ reset</button>
      )}

      {/* Bag + wallet cluster — wrapped together so mobile can pin them to
          the topbar's right edge as one unit (see .rc-topbar-walletcluster
          rules in index.html). On desktop the cluster uses display:contents
          so BAG + WALLET remain direct flex children of the topbar — layout
          unchanged. On mobile the cluster becomes a real flex container with
          margin-left:auto so it anchors to the right edge of its row. */}
      <div className="rc-topbar-walletcluster">
      {/* Bag */}
      <div className="rc-topbar-bag" style={{display:'flex', alignItems:'center', gap:8, border:`1px solid ${wallet.funMode?M.yellow:M.green}`, padding:'3px 8px 3px 10px', fontSize:18, position:'relative'}}>
        {wallet.funMode && <span style={{position:'absolute', top:-9, left:6, background:M.bg, color:M.yellow, fontSize:10, padding:'0 4px', letterSpacing:'0.1em', fontWeight:700}}>PLAY</span>}
        <CoinIcon coin={c} size={16}/>
        <span style={{color: wallet.funMode?M.yellow:M.green, fontWeight:700, fontVariantNumeric:'tabular-nums'}}>{wallet.balance[c].toFixed(c==='SOL'?3:2)}</span>
        <select value={c} onChange={e=>wallet.setActiveCoin(e.target.value)} style={{background:M.bg, color:M.ink, border:0, fontFamily:'inherit', fontSize:18, cursor:'pointer'}}>
          {COINS.map(x=><option key={x} value={x}>{x}</option>)}
        </select>
      </div>

      {/* Wallet — click pill to open dropdown with view-profile + disconnect */}
      {wallet.connected ? (
        <div ref={walletRef} style={{position:'relative'}}>
          <button className="rc-topbar-btn rc-tap" onClick={()=>setWalletMenuOpen(o=>!o)} title="wallet menu" style={{
            display:'flex', alignItems:'center', gap:8, background: walletMenuOpen?M.green:M.bg, color: walletMenuOpen?M.bg:M.green,
            border:`2px solid ${M.green}`, padding:'4px 10px', cursor:'pointer', fontFamily:'inherit', fontSize:16,
            boxShadow: walletMenuOpen?`3px 3px 0 ${M.bg}`:`3px 3px 0 ${M.green}`,
          }}>
            <span style={{width:18, height:18, background: walletMenuOpen?M.bg:M.green, color: walletMenuOpen?M.green:M.bg, display:'flex', alignItems:'center', justifyContent:'center', fontWeight:700, fontSize:13}}>{wallet.walletId==='phantom'?'P':'S'}</span>
            <span style={{fontFamily:'JetBrains Mono, monospace', fontSize:14}}>{shortAddr(wallet.address)}</span>
            <span style={{fontSize:11, marginLeft:2}}>{walletMenuOpen ? '▲' : '▼'}</span>
          </button>

          {walletMenuOpen && (
            <div className="rc-wallet-menu" style={{
              position:'absolute', top:'calc(100% + 8px)', right:0, minWidth:280, zIndex:120,
              background:M.bg, border:`3px solid ${M.green}`, boxShadow:`6px 6px 0 ${M.green}`,
              padding:'10px 12px', fontFamily:'inherit',
            }}>
              <div style={{position:'absolute', top:-12, left:14, background:M.bg, padding:'0 8px', color:M.green, fontSize:13}}>┤ wallet ├</div>

              <div style={{display:'flex', alignItems:'center', gap:8, marginBottom:8}}>
                <span style={{width:24, height:24, background:M.green, color:M.bg, display:'flex', alignItems:'center', justifyContent:'center', fontWeight:700, fontSize:14}}>{wallet.walletId==='phantom'?'P':'S'}</span>
                <div>
                  <div style={{fontSize:14, color:M.green, fontWeight:700, lineHeight:1.1, textTransform:'capitalize'}}>{wallet.walletId}</div>
                  <div style={{fontSize:12, color:M.ink2}}>connected</div>
                </div>
              </div>

              <div style={{fontSize:12, color:M.ink2, marginTop:10}}>address</div>
              <button onClick={copyAddress} title="copy full address" style={{
                width:'100%', textAlign:'left', background:M.panel, border:`1px solid ${M.green}55`,
                color:M.ink, padding:'6px 8px', fontFamily:'JetBrains Mono, monospace', fontSize:12,
                cursor:'pointer', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap',
              }}>{wallet.address} ⧉</button>

              <button onClick={()=>{ setPage('profile'); setWalletMenuOpen(false); }} style={{
                width:'100%', marginTop:10, background:M.bg, color:M.cyan, border:`2px solid ${M.cyan}`,
                padding:'6px 0', fontFamily:'inherit', fontSize:14, cursor:'pointer', fontWeight:700,
              }}>☻ view profile</button>

              <button onClick={()=>{ wallet.disconnect(); setWalletMenuOpen(false); }} style={{
                width:'100%', marginTop:6, background:M.bg, color:M.red, border:`2px solid ${M.red}`,
                padding:'6px 0', fontFamily:'inherit', fontSize:14, cursor:'pointer', fontWeight:700,
                boxShadow:`3px 3px 0 ${M.red}33`,
              }}>✗ disconnect wallet</button>
            </div>
          )}
        </div>
      ) : (
        <button className="rc-topbar-btn rc-tap" onClick={onConnect} style={{
          background:M.hot, color:M.bg, border:`2px solid ${M.bg}`, padding:'6px 14px', fontFamily:'inherit', fontWeight:700, fontSize:18,
          cursor:'pointer', boxShadow:`3px 3px 0 ${M.green}`,
        }}>► connect wallet</button>
      )}
      </div>
    </header>
  );
}

// ─── Chat sidebar ────────────────────────────────────────────────
// Channel is owned by App; messages come live from Firebase via
// useFirebaseChat. Each game has its own room ('flip', 'slots', 'roulette',
// 'baccarat', 'blackjack', 'hilo', 'poker', 'mines'); non-game pages share
// 'general'. `me` is the local user's chosen username — used to color
// own-messages differently.
function ChatSidebar({ palette: M, channel, messages, onSend, me, onHide }) {
  const ref = useRefS();
  const [draft, setDraft] = useStateS('');
  useEffectS(()=>{ if(ref.current) ref.current.scrollTop = ref.current.scrollHeight; }, [messages, channel]);
  useEffectS(()=>{ setDraft(''); }, [channel]);
  function send() {
    const t = draft.trim();
    if (!t) return;
    onSend(t);
    setDraft('');
  }
  const channelColor = (
    channel === 'flip'      ? M.yellow :
    channel === 'slots'     ? M.hot    :
    channel === 'roulette'  ? M.red    :
    channel === 'baccarat'  ? M.green  :
    channel === 'blackjack' ? M.cyan   :
    channel === 'hilo'      ? M.cyan   :
    channel === 'poker'     ? M.hot    :
    channel === 'mines'     ? M.yellow :
                              M.green
  );
  return (
    <aside className="rc-chat" style={{width:230, borderLeft:`2px solid ${M.green}`, background:M.panel, display:'flex', flexDirection:'column', flex:'0 0 230px'}}>
      <div style={{padding:'8px 14px', borderBottom:`2px dashed ${M.green}`, display:'flex', alignItems:'center', justifyContent:'space-between', gap:8}}>
        <span style={{fontSize:16, color:channelColor}}>#{channel}</span>
        <div style={{display:'flex', alignItems:'center', gap:8}}>
          <span style={{fontSize:13, color:M.yellow}}>[live]</span>
          {onHide && (
            <button
              onClick={onHide}
              title="hide chat panel"
              aria-label="hide chat"
              style={{
                background:M.bg, color:M.green, border:`1px solid ${M.green}`,
                width:22, height:22, padding:0, cursor:'pointer',
                display:'flex', alignItems:'center', justifyContent:'center',
                fontFamily:'inherit', fontSize:14, lineHeight:1,
              }}
            >_</button>
          )}
        </div>
      </div>
      <div ref={ref} style={{flex:1, overflowY:'auto', padding:'8px 12px', fontSize:13, lineHeight:1.5}}>
        {messages.length === 0 && (
          <div style={{color:M.ink2, fontStyle:'italic', padding:'8px 0'}}>{'>'} #{channel} is empty. say something.</div>
        )}
        {messages.map(m=>(
          <div key={m.id}>
            <span style={{color: m.u === me ? M.yellow : M.cyan}}>&lt;{m.u}&gt;</span>
            <span style={{color:M.ink}}> {m.m}</span>
          </div>
        ))}
      </div>
      <div style={{padding:8, borderTop:`2px dashed ${M.green}`, display:'flex', gap:6}}>
        <span style={{color:M.green, fontSize:14, alignSelf:'center'}}>{'>'}</span>
        <input value={draft} onChange={e=>setDraft(e.target.value)} onKeyDown={e=>{ if(e.key==='Enter') send(); }} placeholder={`message #${channel}`} style={{flex:1, background:M.bg, border:`1px solid ${M.green}`, color:M.ink, padding:'4px 8px', fontFamily:'inherit', fontSize:13, outline:'none', minWidth:0}}/>
      </div>
    </aside>
  );
}

// ─── Frame: a window-chrome wrapper for content panels ───────────
function Frame({ title, accent, children, style, className, ...rest }) {
  const M = window.MEMEColors;
  const c = accent || M.green;
  return (
    <div {...rest} className={`rc-frame${className ? ' ' + className : ''}`} style={{
      border:`2px solid ${c}`, background:M.panel, padding:'18px 20px', position:'relative', ...style,
    }}>
      {title && (
        <>
          <div style={{position:'absolute', top:-12, left:14, background:M.bg, padding:'0 8px', color:c, fontSize:14}}>┤ {title} ├</div>
          <div style={{position:'absolute', top:-10, right:14, display:'flex', gap:5}}>
            {['_','□','×'].map((g,i)=>(<span key={i} style={{background:M.bg, color:c, border:`1px solid ${c}`, width:16, height:16, display:'flex', alignItems:'center', justifyContent:'center', fontSize:11}}>{g}</span>))}
          </div>
        </>
      )}
      {children}
    </div>
  );
}

Object.assign(window, { CrtOverlay, Marquee, Sidebar, Topbar, ChatSidebar, Frame });
