*{box-sizing:border-box;margin:0;padding:0}
body{background:#070710;color:#c8c0b4;font-family:'Courier New',monospace;min-height:100vh;display:flex;justify-content:center;padding:16px}
#app{width:100%;max-width:720px;position:relative;transition:filter .5s}
#fx{position:fixed;inset:0;pointer-events:none;z-index:200}
/* ── password gate (remove when going public) ── */
#gate{position:fixed;inset:0;background:#070710;display:flex;align-items:center;justify-content:center;z-index:9999}
.gate-box{display:flex;flex-direction:column;align-items:center;gap:14px;padding:40px 48px;border:1px solid #1a1a28;background:#0b0b16}
.gate-title{font-size:1.6rem;letter-spacing:6px;color:#c8c0b4}
.gate-sub{font-size:.7rem;letter-spacing:2px;color:#44445a;text-transform:uppercase}
.gate-input{background:#050510;border:1px solid #242436;color:#c8c0b4;font-family:'Courier New',monospace;font-size:.9rem;padding:8px 14px;width:220px;text-align:center;outline:none}
.gate-input:focus{border-color:#4a4a6a}
.gate-err{font-size:.65rem;letter-spacing:2px;color:#c03020;text-transform:uppercase;min-height:1em}
@keyframes gateshake{0%,100%{transform:translateX(0)}20%{transform:translateX(-8px)}40%{transform:translateX(8px)}60%{transform:translateX(-5px)}80%{transform:translateX(5px)}}
.gate-shake{animation:gateshake .38s ease-out}
.ftext{position:fixed;font-weight:bold;font-size:1.5rem;animation:rise .9s ease-out forwards;text-shadow:0 0 8px #000,0 0 4px #000}
@keyframes rise{0%{opacity:0;transform:translateY(8px) scale(.7)}20%{opacity:1;transform:translateY(0) scale(1.15)}100%{opacity:0;transform:translateY(-36px) scale(1)}}
h1{text-align:center;font-size:2.3rem;letter-spacing:11px;color:#e8c038;text-shadow:0 0 26px #c09010,0 0 64px #80600022;margin-bottom:2px}
.sub{text-align:center;color:#55556a;font-size:.7rem;letter-spacing:4px;margin-bottom:18px}
.btn{background:#0c0c18;border:1px solid #252535;color:#a8a098;padding:6px 14px;cursor:pointer;font-family:inherit;font-size:.83rem;transition:all .12s}
.btn:hover:not(:disabled){background:#12121e;border-color:#e8c038;color:#e8c038}
.btn:disabled{opacity:.22;cursor:default}
.btn-gold{border-color:#e8c038;color:#e8c038}
.btn-big{font-size:.95rem;padding:9px 26px;letter-spacing:2px}
.btn-roll{background:#100c1a;border:1px solid #6a4ca0;color:#b090e0;font-size:1.15rem;padding:14px 40px;letter-spacing:4px}
.btn-roll:hover{background:#160e26;border-color:#a070ff;color:#d0b0ff;box-shadow:0 0 20px #6040a040}
.btn-commit{background:#0a1610;border:1px solid #205030;color:#50c070;font-size:.93rem;padding:9px 24px;letter-spacing:2px}
.btn-commit:hover:not(:disabled){background:#081410;border-color:#50c070;color:#80e090}
.btn-reroll{border-color:#4a6a8a;color:#70a0d0}
.btn-reroll:hover:not(:disabled){border-color:#80c0ff;color:#a0d0ff;background:#0a1420}
.row{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
.spacer{flex:1}
.sbar{display:flex;gap:14px;padding:7px 12px;background:#040408;border:1px solid #121218;margin-bottom:10px;font-size:.83rem;flex-wrap:wrap;align-items:center}
.s-hp{color:#d84040}.s-gold{color:#e8c038}.s-floor{color:#4870c0;margin-left:auto}
.panel{background:#06060e;border:1px solid #181826;padding:12px 14px;margin-bottom:10px;position:relative}
.panel.boss-panel{border-color:#584010;box-shadow:inset 0 0 24px #30200410}
.ptitle{color:#8585a0;font-size:.68rem;letter-spacing:3px;text-transform:uppercase;margin-bottom:8px}
.hpbar{height:6px;background:#101018;margin-top:5px;overflow:hidden}
.hpfill{height:100%;transition:width .4s}
.hpfill.en{background:#2a6a2a}.hpfill.pl{background:#602020}.hpfill.low{background:#901010!important}
.focusbar{height:4px;background:#0a1a12;margin-top:3px}.focusfill{height:100%;background:#30c070;transition:width .3s}
@keyframes tumble{0%{transform:rotate(-18deg) scale(.85)}50%{transform:rotate(12deg) scale(1.08)}100%{transform:rotate(0) scale(1)}}
.dice-area{display:flex;gap:8px;flex-wrap:wrap;min-height:64px;align-items:center;margin:10px 0}
.die{width:56px;height:56px;border:2px solid #3a3a52;display:flex;flex-direction:column;align-items:center;justify-content:center;cursor:pointer;user-select:none;background:#14141f;position:relative;transition:transform .1s,border-color .12s,background .12s}
.die:hover:not(.die-off){transform:translateY(-3px)}
.die.tumble{animation:tumble .28s ease-out}
.die.ds{border-color:#a02818;background:#140608;color:#e05040}
.die.dw{border-color:#183098;background:#080a18;color:#4880d8}
.die.df{border-color:#186040;background:#060e0a;color:#40c070}
.die.die-off{cursor:default;opacity:.45}
.die.die-blank{border-style:dashed;border-color:#2a2a3a;color:#2a2a3a;cursor:default}
.die-val{font-size:1.7rem;font-weight:bold;line-height:1}
.die-num{position:absolute;top:2px;left:4px;font-size:.62rem;font-weight:bold;opacity:.9}
.die-slot{position:absolute;bottom:2px;right:3px;font-size:.44rem;opacity:.7}
.die-insc{position:absolute;top:1px;right:2px;font-size:.62rem}
.die.ds.ig-spiked{box-shadow:0 0 10px #c0281866}.die.dw.ig-warded{box-shadow:0 0 10px #18309866}
.die.df.ig-channeled{box-shadow:0 0 10px #18604066}.die.ds.ig-volatile{box-shadow:0 0 10px #c060b066}
.die.ig-gilded{border-color:#a88020!important;box-shadow:0 0 8px #806010}
/* selected die: bright ring */
.die.die-sel{border-color:#e8d040!important;box-shadow:0 0 14px #e8d04066;transform:translateY(-4px)!important;opacity:1!important;z-index:3}
/* slot becomes a tap target when a die is selected */
.slt.slt-target{cursor:pointer;transition:border-color .12s,background .12s,box-shadow .12s}
.slt.slt-target.slt-s{border-color:#7a2810;box-shadow:0 0 10px #c0281822}
.slt.slt-target.slt-w{border-color:#1a3a8a;box-shadow:0 0 10px #18309822}
.slt.slt-target.slt-f{border-color:#1a6a3a;box-shadow:0 0 10px #18604022}
.slt.slt-target:hover,.slt.slt-target:active{box-shadow:0 0 16px #e8d04044}
.slt.slt-target.slt-s:hover,.slt.slt-target.slt-s:active{border-color:#e05040;background:#120608}
.slt.slt-target.slt-w:hover,.slt.slt-target.slt-w:active{border-color:#4880d8;background:#060810}
.slt.slt-target.slt-f:hover,.slt.slt-target.slt-f:active{border-color:#40c070;background:#060e08}
/* small inline unassign button (only shown while a die is selected) */
.unassign-btn{background:#0a0a14;border:1px solid #2c2c38;color:#7a7a8e;font-family:inherit;font-size:.6rem;letter-spacing:1px;padding:2px 8px;margin-left:6px;cursor:pointer;text-transform:uppercase;vertical-align:middle}
.unassign-btn:hover{border-color:#666;color:#aaa}
@keyframes matchpop{0%{transform:scale(1)}30%{transform:scale(1.06);box-shadow:0 0 16px #46c24a66;border-color:#46c24a}100%{transform:scale(1);box-shadow:0 0 0 #46c24a00}}
.slt.match-pop{animation:matchpop .42s ease-out}
.slots{display:flex;gap:7px;margin:4px 0 8px}
.slt{flex:1;min-width:90px;border:1px solid #141420;padding:6px 10px;background:#050510}
.slt.slt-s{border-color:#3a1008}.slt.slt-w{border-color:#0c1840}.slt.slt-f{border-color:#081e10}
.slt-name{font-size:.66rem;letter-spacing:2px;color:#9090a6;margin-bottom:2px;text-transform:uppercase}
.slt-val{font-size:1.35rem;font-weight:bold}.vvs{color:#e05040}.vvw{color:#4880d8}.vvf{color:#40c070}
.slt-note{font-size:.63rem;color:#6a6a82;margin-top:1px}.crit-note{color:#46c24a;font-weight:bold}
/* commit reveal: each build step pops a chip; the running total pops as it ticks up */
@keyframes rvpop{0%{transform:scale(.55);opacity:0}55%{transform:scale(1.16)}100%{transform:scale(1);opacity:1}}
.rv-chip{display:inline-block;font-weight:bold;font-size:.66rem;animation:rvpop .32s cubic-bezier(.2,1.3,.5,1)}
.rv-crit{color:#46c24a}.rv-spell{color:#7aa0e0}.rv-item{color:#e0a040}.rv-mult{color:#e060c0}
/* number increment: subtle tick — the SOURCE (dice/item) gets the loud pop, not the total */
@keyframes valpop{0%{transform:scale(1)}45%{transform:scale(1.1);text-shadow:0 0 7px currentColor}100%{transform:scale(1)}}
.slt-val.val-pop{animation:valpop .3s ease-out}
/* the dice / item responsible for the active step flash big */
@keyframes diceflash{0%{transform:scale(1)}30%{transform:scale(1.22);box-shadow:0 0 18px #e8d04088;border-color:#e8d040}100%{transform:scale(1);box-shadow:0 0 0 #e8d04000}}
.die.rv-diceflash{animation:diceflash .4s cubic-bezier(.2,1.3,.5,1);opacity:1!important;z-index:2}
@keyframes chipflash{0%{transform:scale(1)}30%{transform:scale(1.18);box-shadow:0 0 16px #e0a04088;border-color:#e0a040;color:#ffd080}100%{transform:scale(1);box-shadow:0 0 0 #e0a04000}}
.ichip.rv-flash{animation:chipflash .4s cubic-bezier(.2,1.3,.5,1)}
.spells-row{display:flex;gap:7px;flex-wrap:wrap;margin:8px 0 4px}
.spell{border:1px solid #181828;padding:6px 10px;background:#060614;cursor:pointer;min-width:118px;transition:all .12s;position:relative}
.spell:hover:not(.spell-off){border-color:#302838}
.spell.spell-can{border-color:#208048;background:#060e0a}.spell.spell-can:hover{border-color:#40c070;background:#081410}
.spell.spell-on{border-color:#40c070;background:#0a1c10;box-shadow:0 0 12px #20804022}
.spell.spell-off{opacity:.28;cursor:default}
.spell-name{font-size:.78rem;color:#a4a4b6;margin-bottom:2px}.spell-name.sr{color:#80d0a0}
.spell-cost{font-size:.66rem;color:#6e7e6e}.spell-cost.sr{color:#40c070}
.spell-desc{font-size:.64rem;color:#787890;margin-top:2px;line-height:1.4}.spell-desc.sr{color:#6caa6c}
@keyframes shk{0%,100%{transform:translateX(0)}25%{transform:translateX(-6px)}75%{transform:translateX(6px)}}
.shaking{animation:shk .3s ease-out}
.enemy-head{display:flex;gap:12px;align-items:flex-start}
.enemy-art{width:78px;height:78px;flex-shrink:0;border:1px solid #1c1c2a;background:radial-gradient(circle at 50% 38%,#0c0c18,#04040a);border-radius:3px;image-rendering:pixelated;animation:idle 2.6s ease-in-out infinite}
.enemy-art.boss-art{width:96px;height:96px;border-color:#5a4410;animation-duration:3.4s}
.enemy-art.enraged{animation:enrage 1s ease-in-out infinite;filter:drop-shadow(0 0 3px #ff3018) saturate(1.4)}
@keyframes idle{0%,100%{transform:translateY(0) rotate(0deg)}50%{transform:translateY(-3px) rotate(-1deg)}}
@keyframes enrage{0%,100%{transform:translateY(0) scale(1)}50%{transform:translateY(-2px) scale(1.05)}}
@keyframes hitreact{0%{transform:translate(0,0) scale(1)}12%{transform:translate(8px,-3px) scale(1.16);filter:brightness(2.4) drop-shadow(0 0 7px #ff5a40)}32%{transform:translate(-6px,1px) scale(.95)}55%{transform:translate(4px,0) scale(1.03)}78%{transform:translate(-2px,0) scale(1)}100%{transform:translate(0,0) scale(1)}}
.enemy-art.hitreact{animation:hitreact .44s ease-out!important}
.en-name{font-size:1.1rem;letter-spacing:2px}
.boss-glow{color:#e8c038;text-shadow:0 0 12px #c0901030}
.intent-card{display:inline-flex;align-items:center;gap:7px;padding:5px 11px;border:1px solid #222;background:#080810;margin-top:6px;font-size:.83rem;cursor:help}
.ic-atk{border-color:#6a1a10;color:#d04030}.ic-arm{border-color:#10286a;color:#3060b0}.ic-heal{border-color:#104a18;color:#309040}
.ic-charge{border-color:#5a3010;color:#b06020}.ic-burn{border-color:#6a2808;color:#c04010}.ic-multi{border-color:#602060;color:#c040a0}
.ic-drain{border-color:#502050;color:#9040a0}.ic-stun{border-color:#505010;color:#909020}
.dtag{font-size:.54rem;letter-spacing:1px;padding:1px 5px;margin-left:7px;border:1px solid;text-transform:uppercase;white-space:nowrap;align-self:center}
.dtag-phys{color:#8aa0c0;border-color:#33425a}.dtag-elem{color:#e0883c;border-color:#6a3c14}
.srow{display:flex;gap:5px;flex-wrap:wrap;margin:5px 0}
.spill{font-size:.65rem;padding:1px 7px;border:1px solid;display:inline-flex;align-items:center;gap:3px;cursor:help}
.sp-burn{border-color:#7a2808;color:#c04818;background:#100400}.sp-weak{border-color:#5a3070;color:#9050b0;background:#100818}
.sp-vuln{border-color:#6a2020;color:#b04040;background:#100808}.sp-barrier{border-color:#184870;color:#3880b8;background:#040810}
.sp-stun{border-color:#484808;color:#888818;background:#080800}.sp-armor{border-color:#183070;color:#3060a0;background:#040810}
.items-wrap{display:flex;gap:5px;flex-wrap:wrap}
.ichip{background:#0c0c18;border:1px solid #242436;padding:4px 9px;font-size:.74rem;cursor:help;color:#cfcabd}
.ichip:hover{border-color:#3a3a52}
.en-special{font-size:.6rem;letter-spacing:1px;color:#9a7ad8;border:1px solid #2e2348;background:#0a0814;padding:1px 7px;margin-left:8px;white-space:nowrap;cursor:help;vertical-align:middle}
.en-enraged{font-size:.6rem;letter-spacing:1px;color:#ff5040;border:1px solid #6a1810;background:#1a0604;padding:1px 7px;margin-left:8px;white-space:nowrap;cursor:help;vertical-align:middle}
.sitem{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid #121220;margin-bottom:5px;background:#050510}
.sitem:hover{border-color:#222232}
.si-icon{width:22px;text-align:center;font-size:.9rem;color:#6a6058}
.si-info{flex:1}.si-name{font-size:.85rem}.si-desc{font-size:.7rem;color:#8a8a96;margin-top:1px}
.overlay{position:fixed;inset:0;background:#000000d8;z-index:100;display:flex;align-items:center;justify-content:center}
.modal{background:#070712;border:1px solid #282840;padding:20px;max-width:480px;width:92%}
.modal-title{font-size:.85rem;color:#e8c038;letter-spacing:2px;margin-bottom:14px}
.die-select-row{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px}
.die-sel-btn{width:54px;height:54px;border:1px solid #222;display:flex;align-items:center;justify-content:center;flex-direction:column;cursor:pointer;background:#0c0c18;font-size:.65rem}
.die-sel-btn:hover{border-color:#555}.die-sel-btn.chosen{border-color:#e8c038}
.face-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:5px;margin-bottom:12px}
.face-btn{border:1px solid #1e1e2e;padding:7px 4px;text-align:center;cursor:pointer;background:#0a0a18;font-size:.8rem}
.face-btn:hover{border-color:#555}.face-btn.fi{border-color:#40c070;color:#40c070}
.blog{font-size:.72rem;display:flex;flex-direction:column;gap:0;min-height:34px;max-height:228px;overflow-y:auto;padding:3px 9px;background:#030308;border:1px solid #0e0e16}
.logturn{border-top:1px solid #15151f;padding:4px 0 5px}
.logturn:first-child{border-top:none}
.lt-head{display:flex;align-items:center;gap:9px;margin-bottom:2px}
.lt-tn{color:#9a9ab0;font-weight:bold;letter-spacing:1px;font-size:.64rem;text-transform:uppercase}
.logturn:first-child .lt-tn{color:#d4d4e6}
.lt-hp{font-size:.74rem;color:#9a9aae}
.lt-hp.lt-dn{color:#e0604a}.lt-hp.lt-up{color:#4aae5a}
.lt-eh{font-size:.7rem;color:#666680;margin-left:auto}
.ble{color:#83839a;padding-left:9px;line-height:1.5}
.lt-quiet{color:#44445a;font-style:italic}
.b-w{color:#46c24a!important}.b-l{color:#e0604a!important}.b-sp{color:#56a4d4!important}.b-it{color:#9a7ad8!important}.b-st{color:#d68c34!important}
.fbadge{font-size:.65rem;letter-spacing:3px;display:inline-block;padding:2px 9px;margin-bottom:7px}
.fbadge.boss{border:1px solid #b89020;color:#d0a830}.fbadge.normal{border:1px solid #2c2c3c;color:#7a7a8e}.fbadge.endless{border:1px solid #6a2080;color:#b060d0}
.mbox{background:#03030a;border:1px solid #101018;padding:14px;margin-bottom:10px}
.mstat-l{font-size:.6rem;color:#55556a;letter-spacing:1px}.mstat-v{color:#cfcfe0;font-size:1.1rem}
.ms-list{display:flex;flex-direction:column;gap:2px}
.ms-row{display:flex;align-items:center;gap:9px;padding:7px 4px;border-bottom:1px solid #0e0e16}
.ms-check{color:#33334a;font-size:1rem;width:14px;text-align:center}
.ms-done .ms-check{color:#46c24a}
.ms-name{font-size:.78rem;color:#7a7a90}.ms-done .ms-name{color:#cfcfe0}
.ms-cond{font-size:.64rem;color:#6e6e84}
.ms-grant{font-size:.64rem;color:#8585a0;text-align:right;flex-shrink:0;max-width:46%}.ms-done .ms-grant{color:#b0a0d0}
.end-unlocks{background:#0a0814;border:1px solid #2a2440;padding:12px 16px;margin:14px auto;max-width:360px}
.end-unlock{display:flex;justify-content:space-between;gap:12px;padding:3px 0;font-size:.76rem}.eu-name{color:#cfcfe0}.eu-cards{color:#b0a0d0;text-align:right}
.die-config{display:flex;gap:8px;flex-wrap:wrap;margin-top:6px}
.die-preview{border:1px solid #1e1e2a;padding:6px 8px;background:#070710;min-width:70px}
.dp-title{font-size:.62rem;color:#6a6a82;margin-bottom:4px}.dp-faces{display:flex;gap:2px}
.dp-face{width:17px;height:17px;border:1px solid #1a1a24;display:flex;align-items:center;justify-content:center;font-size:.55rem;background:#0a0a14}
.dp-face.inscribed{border-color:#40c070;color:#40c070}
.toggle{display:inline-flex;border:1px solid #252535}
.toggle button{background:#0a0a14;border:none;color:#555;padding:4px 12px;cursor:pointer;font-family:inherit;font-size:.75rem}
.toggle button.on{background:#1a1630;color:#b090e0}
.hint{font-size:.68rem;color:#6e6e84;margin-top:6px}
/* ── mobile: bigger touch targets, no off-screen tooltips, readable text ── */
@media (max-width:560px){
  body{padding:9px;font-size:15px}
  .die{width:50px;height:50px}.die-val{font-size:1.5rem}
  .slt{min-width:0;padding:6px 7px}.slt-val{font-size:1.2rem}
  .spell{min-width:104px}
  .ms-grant{max-width:42%}
  .intent-card{font-size:.78rem}
  .en-name{font-size:1rem}
  .si-desc{font-size:.74rem}
}
/* ── global tap tooltip ── */
#tt-box{position:fixed;display:none;background:#04040e;border:1px solid #3a3a50;padding:8px 10px;font-size:.78rem;line-height:1.5;color:#d8d2c6;box-shadow:0 4px 20px #000c;z-index:9000;pointer-events:none;font-family:'Courier New',monospace;max-width:260px}

/* ═══ LOBBY ═══ crooked.gg's front door — the game lives at #/play, not here.
   #lobby escapes #app's 720px cap so its side margins can span the full viewport. */
#lobby{display:none}
/* The page itself never scrolls — overflow:hidden here, not auto. Header and
   ticker stay pinned; only .lobby-center (the middle column) scrolls its own
   content, via its own overflow-y:auto further down. */
body.lobby-mode #lobby{display:block;position:fixed;inset:0;overflow:hidden;z-index:1;background:#06060c}
body.lobby-mode #app,body.lobby-mode #fx{display:none}

/* CRT terminal palette — pushed hard on contrast vs the original (borders sat
   almost on top of the background; secondary text was too close in luminance
   to read comfortably). Same hue family (warm gold + cool purple-leaning
   greys) throughout, just legible. See memory: lobby redesign 2026-07-14. */
.lobby-page{color:#ece7d8;font-family:'Courier New',monospace;height:100%;display:flex;flex-direction:column;position:relative;overflow-x:hidden;user-select:none}
/* Nothing in the lobby should be draggable-highlightable except things that
   are actually text you typed or wrote — form fields, and the messages
   people left (chat lines, guestbook tickets). Everything else (labels,
   the suggested-name placeholder, panel chrome) stays inert to a click-drag,
   scoped to the lobby only — this doesn't touch the game screen. */
.lobby-page input,.lobby-page textarea{user-select:text}
.lobby-msg{user-select:text}
.gbt-entry-body{user-select:text}
/* No page-wide texture here on purpose — the center column stays flat so the
   secretly-interactive margin grid is the ONLY textured/recessed surface on
   the page. A competing wash behind the content fought with it for the same
   visual role and made the margins read as foreground instead of background. */
.lobby-page a{color:#f5cc48;text-decoration:none}
.lobby-page a:hover{text-decoration:underline}
.lobby-tab-nums{font-variant-numeric:tabular-nums}

.lobby-crt{position:fixed;inset:0;pointer-events:none;z-index:80;background:repeating-linear-gradient(0deg,rgba(0,0,0,.15) 0 1px,transparent 1px 3px),radial-gradient(ellipse at 50% 45%,transparent 55%,rgba(0,0,0,.42) 100%);mix-blend-mode:multiply}
.lobby-crt::before{content:"";position:absolute;inset:0;box-shadow:inset 0 0 0 1px rgba(255,60,40,.06),inset 0 0 40px 8px rgba(255,60,40,.05),inset 0 0 40px 8px rgba(60,180,255,.05)}
/* slow ambient light sweeping down the page — barely-there, more felt than seen */
.lobby-scan-sweep{position:fixed;inset:0;pointer-events:none;z-index:79;overflow:hidden}
.lobby-scan-sweep::after{content:"";position:absolute;left:0;right:0;height:340px;background:linear-gradient(180deg,transparent,rgba(255,255,255,.014) 40%,rgba(255,255,255,.026) 50%,rgba(255,255,255,.014) 60%,transparent);animation:lobbySweep 11s linear infinite}
@keyframes lobbySweep{from{top:-340px}to{top:100%}}

/* Boot sequence: two earlier attempts scrapped before this (a filter/opacity
   flicker on the page itself was too subtle to read as anything; a bars-open
   CRT mask read as a slow PowerPoint wipe). A screen-flicker intro on top of
   THIS was also tried and dropped — never landed the intended effect, and
   flicker effects carry real photosensitivity risk, so not worth chasing further.
   Lands as: a short "crooked_os" boot screen with a loading bar that advances in
   chunky, uneven jumps (steps(1,end) on the width keyframes) rather than a smooth
   continuous fill. Under two seconds — quick enough not to be annoying on every
   load, slow enough to actually register. */
.lobby-boot{position:fixed;inset:0;z-index:95;pointer-events:none;background:#06060c;display:flex;align-items:center;justify-content:center;animation:lobbyBootOut .25s ease-in 1.8s both}
@keyframes lobbyBootOut{to{opacity:0}}
.lobby-boot-screen{position:relative;display:flex;flex-direction:column;align-items:center;gap:9px;opacity:0;animation:lobbyBootFadeIn .35s ease-out .15s both}
@keyframes lobbyBootFadeIn{from{opacity:0}to{opacity:1}}
.lobby-boot-name{color:#f5cc48;font-size:1rem;letter-spacing:.08em;text-shadow:0 0 6px rgba(245,204,72,.55),0 0 16px rgba(245,204,72,.25)}
.lobby-boot-sub{color:#7c709e;font-size:.72rem;letter-spacing:.08em}
.lobby-boot-bar{width:180px;height:4px;border:1px solid #4a4278;background:#0a0a16;overflow:hidden}
.lobby-boot-fill{height:100%;width:0;background:#f5cc48;box-shadow:0 0 6px rgba(245,204,72,.6);animation:lobbyBootFill 1.2s steps(1,end) .55s both}
@keyframes lobbyBootFill{0%{width:0%}14%{width:20%}32%{width:20%}40%{width:55%}58%{width:55%}68%{width:80%}86%{width:94%}100%{width:100%}}

@keyframes lobbyBlink{0%,49%{opacity:1}50%,100%{opacity:0}}
.lobby-cursor{display:inline-block;animation:lobbyBlink 1s steps(1,end) infinite;color:#f5cc48;margin-left:2px}

.lobby-top{border-bottom:1px solid #4a4278;background:#0a0a16;position:relative;z-index:2;box-shadow:0 1px 0 rgba(245,204,72,.08);flex:none}
.lobby-top-inner{max-width:920px;margin:0 auto;padding:16px 20px;display:flex;align-items:center;justify-content:space-between;gap:16px}
.lobby-brand{font-size:1rem;letter-spacing:.06em;color:#f5cc48!important;text-decoration:none!important;text-shadow:0 0 6px rgba(245,204,72,.55),0 0 16px rgba(245,204,72,.25)}

/* continuous stream: each "run" (22 message+gap units, from lobby.ts) is wide
   enough on its own to comfortably exceed any real screen, so duplicating a run
   and looping at -50% stays seamless regardless of viewport width — unlike
   duplicating a single short message, which broke this before. */
/* LED-matrix scoreboard look: the track's content is now a single <canvas>
   (built in lobby.ts) that renders every dot explicitly — lit AND unlit — from
   real sampled text, not a mask over normal antialiased type (that approach
   chops letters into unreadable fragments since the mask grid has no relation
   to the glyph shapes). image-rendering keeps the canvas's own pixels crisp if
   the browser ever scales it. */
/* scroll position is JS-driven now (initTickerDrag in lobby.ts sets transform
   directly every frame) — no CSS animation, and deliberately no hover-pause
   anymore: it always wants to keep drifting, drag is the only way to disturb it. */
.lobby-ticker{border-bottom:1px solid #4a4278;background:#0a0a16;overflow:hidden;position:relative;z-index:2;cursor:grab;touch-action:pan-y;flex:none}
.lobby-ticker:active{cursor:grabbing}
.lobby-ticker-track{display:flex;align-items:center;width:max-content;padding:2px 0}
.lobby-ticker-track canvas{display:block;image-rendering:pixelated}

.lobby-layout{display:grid;grid-template-columns:minmax(0,1fr) min(920px,100%) minmax(0,1fr);position:relative;z-index:1;flex:1 1 auto;min-height:0}
.lobby-side{position:relative;min-height:100%;overflow:hidden;background:#06060c;--mx:-9999px;--my:-9999px}
.lobby-side::before{content:"";position:absolute;inset:0;z-index:3;pointer-events:none;background:radial-gradient(180px circle at var(--mx) var(--my),rgba(245,204,72,.09),transparent 72%)}
/* the entire margin IS the grid — one crisp layer only, deliberately NOT the
   blurred page-wide wash removed earlier, so it stays visually distinct as
   the "secret". every cell is interactive: no hover state, no cursor change,
   no title — completed blocks stay tinted forever. */
.lobby-grid{position:absolute;inset:0;z-index:1;display:grid}
/* "flashlight": blurs the crisp grid behind it except in a circle that follows
   the cursor (reuses --mx/--my, already tracked for the ambient glow above).
   Purely a visual overlay — pointer-events:none, so the real cells underneath
   are never actually blurred and stay perfectly clickable regardless. */
.lobby-blur-mask{position:absolute;inset:0;z-index:2;pointer-events:none;backdrop-filter:blur(2.5px);-webkit-backdrop-filter:blur(2.5px);mask-image:radial-gradient(140px circle at var(--mx) var(--my),transparent 0%,transparent 55%,black 100%);-webkit-mask-image:radial-gradient(140px circle at var(--mx) var(--my),transparent 0%,transparent 55%,black 100%)}
/* deliberately quieter than the foreground UI borders (#4a4278) — this grid is
   meant to read as recessed background texture, not a second layer of chrome */
.lobby-cell{background:#06060c;border-right:1px solid rgba(106,95,176,.15);border-bottom:1px solid rgba(106,95,176,.15);box-sizing:border-box;user-select:none;transition:background .12s ease}
.lobby-cell.lit{background:rgba(245,204,72,.55)}

/* right margin: ONE continuous Dots & Boxes board filling the bar edge-to-edge,
   using uniform 1fr boxes — the EXACT same CELL constant and rounding formula
   as the left side's cell grid, so the two margins are pixel-identical. The
   visible lines are real `border` properties
   (same as the left side's cell borders) — NOT a positioned background-filled
   div, which doesn't get the same hairline pixel-snapping treatment and
   caused visibly inconsistent line thickness across the grid. Whole boxes are
   the click target (see boxClick's triangle math in lobby.ts), not separate
   catcher elements — there's nothing left to position/pad here at all. */
.dab-board{position:absolute;inset:0;z-index:1;display:grid}
.dab-box{position:relative;box-sizing:border-box;background:transparent;transition:background .1s ease;border-right:1px solid rgba(106,95,176,.15);border-bottom:1px solid rgba(106,95,176,.15)}
.dab-box-top{border-top:1px solid rgba(106,95,176,.15)}
.dab-box-left{border-left:1px solid rgba(106,95,176,.15)}
.dab-box.p1{background:rgba(255,93,71,.18)}
.dab-box.p2{background:rgba(90,146,242,.18)}
/* claimed borders (3px solid, set inline per-side in lobby.ts) are thicker
   than the 1px neutral ones so they don't get lost against the box fill —
   box-sizing:border-box keeps that from shifting the grid, growing only
   inward into the box's own content area. */

@media (max-width:1080px){.lobby-side{display:none}.lobby-layout{grid-template-columns:1fr}}

/* surface hierarchy: margins/void (#06060c, recede completely — same color as
   the page background, no separate tier of their own) < this canvas < toolbar
   strips < panels (most elevated). Each step is ~8-10 per RGB channel, chosen
   to actually be perceptible — an earlier pass used steps of only 2-4 between
   margins and canvas, which was too subtle to read as a real hierarchy at these
   low brightness levels (the eye's contrast sensitivity is weakest in the dark).
   A soft inward shadow at both edges sells "the margins recede behind this
   column" more convincingly than the border line alone. */
/* the ONE scrolling surface on the whole page — min-height:0 overrides the
   grid item's implicit min-height:auto (same trap as the flexbox version),
   which would otherwise let this column's own content stretch the grid ROW
   taller instead of scrolling within it. */
.lobby-center{background:#100f24;padding:32px 20px 60px;display:flex;flex-direction:column;gap:22px;border-left:1px solid #4a4278;border-right:1px solid #4a4278;box-shadow:inset 14px 0 16px -14px rgba(0,0,0,.5),inset -14px 0 16px -14px rgba(0,0,0,.5);min-height:0;overflow-y:auto}
/* same custom scrollbar as the guestbook's ticket list (.gbt-list), for
   continuity now that this is also a scrolling surface. */
.lobby-center::-webkit-scrollbar{width:8px}
.lobby-center::-webkit-scrollbar-track{background:transparent}
.lobby-center::-webkit-scrollbar-thumb{background:#4a4278}
.lobby-center::-webkit-scrollbar-thumb:hover{background:#6a5fb0}
.lobby-center{scrollbar-width:thin;scrollbar-color:#4a4278 transparent}

.lobby-chat{background:#201e40;border:1px solid #4a4278;box-shadow:0 14px 36px rgba(0,0,0,.6),0 0 0 1px rgba(245,204,72,.04);flex:1}
/* Fixed total height, but ONLY once actually joined — pre-join (or mid
   name-pick) there's barely any content, and locking that same tall height
   before there's anything to fill it just reads as a big empty box next to
   jukebox/guestbook's tidy ones. chat.ts adds this class the moment a name
   is confirmed. display:flex column + flex:1 on the log (below) is what
   makes the log the ONLY part that absorbs/scrolls once locked; head and
   input stay pinned to their natural content height regardless.
   320px is sized to show exactly ~7 single-line messages before scrolling
   (measured against real rendered message rows, not guessed) — enough to
   read as an actual conversation without chat dominating the four-panel
   layout the way a taller box would. */
.lobby-chat.is-joined{height:320px;display:flex;flex-direction:column}
.lobby-chat-head{display:flex;align-items:center;justify-content:space-between;padding:11px 16px;border-bottom:1px solid #4a4278;background:#191730;flex:none}
.lobby-chat-channel{color:#f5cc48;font-size:.92rem;letter-spacing:.03em;text-shadow:0 0 5px rgba(245,204,72,.4)}
.lobby-chat-online{display:flex;align-items:center;gap:.5em;font-size:.78rem;color:#a89cd4}
.lobby-dot{width:6px;height:6px;border-radius:50%;background:#46d485;box-shadow:0 0 6px #46d485;animation:lobbyPulse 1.8s ease-in-out infinite}
@keyframes lobbyPulse{0%,100%{opacity:1}50%{opacity:.35}}
.lobby-log{list-style:none;margin:0;padding:14px 16px;display:flex;flex-direction:column;gap:8px;flex:1;min-height:0;overflow-y:auto}
.lobby-msg{font-size:.87rem;line-height:1.55}
.lobby-t{color:#7c709e;margin-right:.6em;font-size:.77rem}
.lobby-h{font-weight:700;margin-right:.6em}
.lobby-h1{color:#f5cc48}.lobby-h2{color:#5a92f2}.lobby-h3{color:#46d485}.lobby-h4{color:#b092f0}
.lobby-m{color:#ece7d8}
.lobby-sys{color:#7c709e;font-size:.8rem;font-style:italic}
.lobby-chat-input{display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid #4a4278;padding:11px 16px;background:#191730;flex:none}
.lobby-chat-fake{color:#7c709e;font-size:.85rem}
.lobby-send{background:none;border:1px solid #4a4278;color:#a89cd4;font-family:inherit;font-size:.78rem;letter-spacing:.06em;padding:6px 14px;cursor:pointer;transition:all .15s ease}
.lobby-send:hover{border-color:#f5cc48;color:#f5cc48;box-shadow:0 0 12px rgba(245,204,72,.25)}
.lobby-send:disabled{opacity:.4;cursor:default}
.lobby-send:disabled:hover{border-color:#4a4278;color:#a89cd4;box-shadow:none}

/* live chat's real input row — same command-prompt framing as the mockup's
   "Native MOTD" direction. One input styles both the name-picker and the
   message box (name>/crooked://lobby$ prompts, respectively), swapped by
   chat.ts depending on whether a display name has been confirmed yet. */
.lobby-prompt{color:#f5cc48;font-size:.85rem;white-space:nowrap}
.lobby-name-input,.lobby-msg-input{
  background:none;border:none;outline:none;flex:1;min-width:0;
  color:#ece7d8;font-family:inherit;font-size:.85rem;
  caret-color:#f5cc48; /* the same yellow as the old static placeholder's fake cursor */
  caret-shape:block; /* thick terminal-block caret, matching the mockup — new-ish CSS, only landed in Chromium so far; degrades to a normal thin bar (still yellow) anywhere it's unsupported, not broken, just thinner */
}
.lobby-name-input::placeholder,.lobby-msg-input::placeholder{color:#7c709e}

.lobby-gametile{background:#201e40;border:1px solid #4a4278;box-shadow:0 14px 36px rgba(0,0,0,.6),0 0 0 1px rgba(245,204,72,.04);flex:1}
.lobby-gt-body{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;padding:17px 18px}
.lobby-gt-tag{color:#a89cd4;font-size:.8rem;margin:0 0 9px}
.lobby-gt-stats{display:flex;gap:9px}
.lobby-chip{border:1px solid currentColor;width:26px;height:26px;display:flex;align-items:center;justify-content:center;font-size:.95rem;line-height:1}
.lobby-chip-s{color:#e05040}.lobby-chip-w{color:#4880d8}.lobby-chip-f{color:#40c070}
.lobby-gt-cta{background:#f5cc48;color:#06060c!important;text-decoration:none!important;font-size:.84rem;letter-spacing:.05em;padding:10px 17px;white-space:nowrap;display:inline-block;font-weight:700;transition:all .15s ease}
.lobby-gt-cta:hover{background:#ffd968;box-shadow:0 0 22px rgba(245,204,72,.55)}

/* every panel (chat, game tile, jukebox, guestbook) lives in ONE ordered
   list and gets packed into rows automatically — a "long" panel takes a
   full row alone, "narrow" panels pair up two-at-a-time (see computeRows in
   panels.ts). Only two panel sizes exist: full row and half row — jukebox
   and guestbook are pinned to the same fixed half-row width in CSS
   (flex:0, see .jb-panel/.gb-panel) regardless of whether they're paired
   or alone. Height still needs a JS assist (see syncGuestbookHeight in
   panels.ts): the guestbook's scrollable ticket list has no natural height
   of its own to stretch to, so it borrows the jukebox's. */
.lobby-row{display:flex;align-items:stretch;gap:22px;flex-wrap:wrap}

/* drag-to-reorder: grabbed by a panel's own header bar, not the whole
   panel, so clicking the jukebox's play button or typing into the
   guestbook's form never starts a drag. */
.lobby-drag-handle{cursor:grab;touch-action:none}
.lobby-drag-handle:active{cursor:grabbing}
/* iOS-style lift: fully opaque, slightly scaled up, heavy shadow — the held
   tile reads as a physical object floating above the page, not a ghost.
   (An earlier version used opacity, which read as the browser's native
   image-drag ghost — wrong association entirely.) */
.lobby-panel-dragging{box-shadow:0 22px 44px rgba(0,0,0,.7),0 0 0 1px rgba(245,204,72,.15)!important;transform:scale(1.02);pointer-events:none;will-change:transform}
/* the gap the held panel would drop into — a real flex item carrying the
   dragged panel's own flex sizing (set inline at pickup), so rows pack
   exactly as they will after the drop and the other panels physically
   slide around it as the gap moves. */
.lobby-panel-placeholder{box-sizing:border-box;border:1px dashed rgba(106,95,176,.4);background:rgba(106,95,176,.05);transition:width .22s ease}
/* dragging over other panels' text otherwise triggers the browser's native
   text-selection highlight on every pointer move */
.lobby-panel-dragging-active,.lobby-panel-dragging-active *{user-select:none!important;-webkit-user-select:none!important}

/* jukebox — mocked up and iterated as a standalone artifact before landing
   here; values below are the final agreed sizing, not first guesses. */
/* Only two panel sizes exist: "long" (full row — chat/gametile) and
   "narrow" (half row — jukebox/guestbook). flex:0 so a narrow panel is
   ALWAYS exactly half the row (minus half the gap), whether paired with
   the other narrow panel or sitting alone in a row of its own — no growth
   negotiation, no dependence on what else is in the row. max-width is a
   sanity cap for very wide viewports. transition:width still covers a
   width change smoothly (e.g. the row's own width changing on resize) — a
   pure width change with the left edge anchored has no position delta at
   all, so the JS FLIP animation (transform-based, position only) can't
   represent it. */
.jb-panel{flex:0 0 calc(50% - 11px);max-width:440px;background:#201e40;border:1px solid #4a4278;box-shadow:0 14px 36px rgba(0,0,0,.6),0 0 0 1px rgba(245,204,72,.04);transition:width .22s ease}
.jb-head{display:flex;align-items:center;justify-content:space-between;padding:11px 16px;border-bottom:1px solid #4a4278;background:#191730}
.jb-title{color:#f5cc48;font-size:.92rem;letter-spacing:.03em;text-shadow:0 0 5px rgba(245,204,72,.4)}
.jb-status{font-size:.78rem;color:#a89cd4;display:flex;align-items:center;gap:6px}
.jb-dot{width:6px;height:6px;border-radius:50%;background:#46d485;box-shadow:0 0 6px #46d485;opacity:.25;transition:opacity .2s ease}
.jb-dot.on{animation:lobbyPulse 1.8s ease-in-out infinite}
.jb-body{padding:14px 16px;display:flex;flex-direction:column;gap:10px}
.jb-track-row{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}
.jb-track{color:#ece7d8;font-size:.85rem;margin:0;min-height:1.2em}
.jb-artist{color:#7c709e;font-size:.7rem;margin:2px 0 0}
.jb-track-num{color:#7c709e;font-size:.72rem;white-space:nowrap}
.jb-screen{background:#06060c;border:1px solid rgba(106,95,176,.3);box-shadow:inset 0 0 10px rgba(0,0,0,.5);padding:14px 16px;display:flex;flex-direction:column;gap:12px}
.jb-tape{display:flex;align-items:center;justify-content:center;gap:14px}
.jb-reel{width:68px;height:68px;border-radius:50%;background:repeating-conic-gradient(#4a4278 0deg 9deg,transparent 9deg 30deg);border:2px solid #6a5fb0;position:relative;flex:none}
.jb-reel::after{content:"";position:absolute;inset:21px;border-radius:50%;background:#06060c;border:2px solid #f5cc48}
.jb-reel.spin{animation:jbspin 2.6s linear infinite}
@keyframes jbspin{to{transform:rotate(360deg)}}
.jb-strip{flex:none;width:160px;height:16px;background:linear-gradient(180deg,#0a0a16,#141228,#0a0a16);border:1px solid rgba(106,95,176,.25)}
.jb-vu{display:flex;align-items:flex-end;gap:3px;height:40px;margin:0 14px}
/* height is driven every animation frame by the JS envelope (see vuFrame in
   jukebox.ts) — deliberately NOT transitioned. A CSS height transition here
   was a second smoothing stage stacked on the analyser's, rounding off the
   sharp beat attacks the JS is specifically shaping. Only the on/off colour
   still eases. */
.jb-vu span{flex:1;background:#4a4278;height:6%;transition:background .12s ease}
.jb-vu span.on{background:#f5cc48;box-shadow:0 0 4px rgba(245,204,72,.5)}
.jb-controls-row{display:flex;align-items:center;gap:12px}
.jb-time{font-size:.7rem;color:#7c709e;font-variant-numeric:tabular-nums;min-width:34px}
.jb-bar{flex:1;height:4px;background:#0a0a16;border:1px solid rgba(106,95,176,.25);position:relative;overflow:hidden;cursor:pointer}
.jb-bar-fill{position:absolute;left:0;top:0;bottom:0;width:0%;background:#f5cc48;box-shadow:0 0 5px rgba(245,204,72,.5);pointer-events:none}
.jb-controls{display:flex;align-items:center;gap:8px;flex:none}
.jb-btn{background:none;border:1px solid #4a4278;color:#a89cd4;font-family:inherit;font-size:.8rem;width:30px;height:30px;cursor:pointer;transition:all .15s ease;display:flex;align-items:center;justify-content:center}
.jb-btn:hover{border-color:#f5cc48;color:#f5cc48;box-shadow:0 0 10px rgba(245,204,72,.2)}
.jb-btn-play{width:34px;height:34px;color:#f5cc48;font-size:.85rem}

/* guestbook — same mockup-then-landed history as the jukebox. Ticket-stub
   list: every entry its own stub, scattered with a slight randomized tilt/
   shift/spacing at rest and jiggling both vertically and rotationally off
   scroll velocity (see initGuestbook in guestbook.ts for the spring math). */
/* align-self:flex-start opts OUT of the row's default stretch — the ticket
   list's own content must never influence this panel's height, or a long
   list growing taller pulls the JUKEBOX taller right along with it (stretch
   sizes every row item to the tallest, in either direction). Height is
   instead pinned to the jukebox's own height via JS (see the ResizeObserver
   in lobby.ts), which only ever depends on the jukebox's own content.
   Width needs no such JS trick: flex:0 (matching .jb-panel exactly) pins
   this panel at exactly half a row regardless of whether it's paired with
   the jukebox or sitting alone — there's no grow negotiation left to go
   sideways. */
.gb-panel{flex:0 0 calc(50% - 11px);max-width:440px;min-height:0;align-self:flex-start;background:#201e40;border:1px solid #4a4278;box-shadow:0 14px 36px rgba(0,0,0,.6),0 0 0 1px rgba(245,204,72,.04);display:flex;flex-direction:column;transition:width .22s ease}
.gbt-body{padding:0;display:flex;flex-direction:column;gap:0;flex:1;min-height:0}
.gbt-list{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;padding:14px 22px}
.gbt-list::-webkit-scrollbar{width:8px}
.gbt-list::-webkit-scrollbar-track{background:transparent}
.gbt-list::-webkit-scrollbar-thumb{background:#4a4278}
.gbt-list::-webkit-scrollbar-thumb:hover{background:#6a5fb0}
.gbt-list{scrollbar-width:thin;scrollbar-color:#4a4278 transparent}
.gbt-empty{color:#7c709e;font-size:.8rem;font-style:italic;text-align:center;margin:auto;padding:0 20px}
/* real drop shadow so tickets visibly float above the panel background */
.gbt-ticket{display:flex;border:1px dashed rgba(106,95,176,.4);background:#06060c;box-shadow:0 3px 7px rgba(0,0,0,.5);position:relative;flex:none;will-change:transform}
/* full circle straddling the edge, half hanging out over the void on either
   side — plain filled dot, no outline (an earlier dashed-ring version left a
   corrugated edge floating over the void on the outer half; removing the
   border entirely reads as a clean punched notch instead). */
.gbt-ticket::before,.gbt-ticket::after{content:"";position:absolute;top:50%;width:10px;height:10px;background:#201e40;border-radius:50%;transform:translateY(-50%)}
.gbt-ticket::before{left:-6px}
.gbt-ticket::after{right:-6px}
.gbt-num-col{width:46px;flex:none;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;border-right:1px dashed rgba(106,95,176,.4);padding:8px 4px}
.gbt-num{color:#f5cc48;font-size:.92rem;font-weight:700;text-shadow:0 0 5px rgba(245,204,72,.4)}
.gbt-num-label{color:#5a5474;font-size:.5rem;letter-spacing:.06em;text-transform:uppercase}
.gbt-entry-body{flex:1;padding:8px 26px 8px 12px;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:2px}
.gbt-msg{color:#ece7d8;font-size:.78rem;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.gbt-name{color:#7c709e;font-size:.66rem;margin:0}
/* hole punch — a genuine punched-through look (shows the panel's own
   background color, like light coming through) rather than a flat dot,
   placed at a slightly different height/side-offset per ticket via inline
   style at creation. */
.gbt-punch{position:absolute;right:12px;top:50%;width:8px;height:8px;border-radius:50%;background:#201e40;box-shadow:inset 0 1px 2px rgba(0,0,0,.6),0 1px 0 rgba(255,255,255,.03);transform:translateY(-50%)}
/* sign bar is a real footer, exactly like .lobby-chat-input relative to the
   chat log: same toolbar background + border-top as #guestbook's own header,
   so the list reads as the body sandwiched between two chrome bars. */
.gbt-body .gbc-form{border-top:1px solid #4a4278;background:#191730;padding:11px 16px;margin:0}
.gbc-form{display:flex;align-items:center;gap:8px}
.gbc-input{background:#06060c;border:1px solid rgba(106,95,176,.35);color:#ece7d8;font-family:inherit;font-size:.78rem;padding:7px 9px;outline:none}
.gbc-input::placeholder{color:#5a5474}
.gbc-input-name{width:78px;flex:none}
.gbc-input-msg{flex:1;min-width:0}
.gbc-sign{background:none;border:1px solid #4a4278;color:#a89cd4;font-family:inherit;font-size:.72rem;letter-spacing:.05em;padding:8px 12px;cursor:pointer;flex:none;transition:all .15s ease}
.gbc-sign:hover{border-color:#f5cc48;color:#f5cc48}

@media (prefers-reduced-motion:reduce){
  .lobby-page *{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}
  /* the ticker's own auto-drift is skipped in JS (see naturalVel in
     initTickerDrag) when this is set — dragging still works, it just won't
     drift on its own. */
  .lobby-scan-sweep{display:none}
  .lobby-boot{display:none}
}
#tt-box.tt-visible{display:block}
