  /* Display face. Bebas Neue (SIL OFL) — free for commercial use, unlike the
     demo-only face this replaced.

     It ships one weight only. Declaring the range 100-900 maps every weight the
     stylesheet asks for onto that single file, so the browser never synthesises a
     fake bold (which smears the condensed stems at title sizes).

     The ?v= is a cache buster: /asset/* carries a one-week max-age. */
  @font-face {
    font-family: 'Bebas Neue';
    src: url('asset/fonts/BebasNeue-Regular.woff2?v=1') format('woff2'),
         url('asset/fonts/BebasNeue-Regular.ttf?v=1') format('truetype');
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                   U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD;
  }
  @font-face {
    font-family: 'Bebas Neue';
    src: url('asset/fonts/BebasNeue-Latin-Ext.woff2?v=1') format('woff2');
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                   U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+2113, U+A720-A7FF;
  }
  /* ═══════════════════════════════════════════════════════════════════════
     DESIGN TOKENS

     One block, one place, at the top. Colour is named for what it MEANS, not
     what it looks like, so "value" can never drift into two accents again.

     The line that mattered: --amber was #ffffff. The token that means money was
     white, so nine sites hardcoded #ffd34d to get a reward colour back and the
     product shipped two accents with the same job. It is gold now, and the two
     consumers that must stay neutral (.eyebrow, .title small) point at --chrome.
     ═══════════════════════════════════════════════════════════════════════ */
  :root{
    /* ── ink: the only whites permitted on text ────────────────────────────
       AA over #05070a runs out around .46 alpha, so .50 is the floor and
       nothing sits on the line. --ink-ghost is for marks that carry no
       information — rules, pips, corner slugs — never a glyph that informs. */
    --ink:       #ffffff;
    --ink-2:     rgba(255,255,255,.82);
    --ink-3:     rgba(255,255,255,.62);
    --ink-4:     rgba(255,255,255,.50);
    --ink-ghost: rgba(255,255,255,.30);

    /* ── accents: four meanings, no more ──────────────────────────────── */
    --amber:      #ffd34d;                /* VALUE — money, reward, your row  */
    --amber-dim:  #6b5a24;
    --amber-wash: rgba(255,211,77,.09);   /* the only chromatic fill we ship  */
    --live:       #7ee081;                /* FREE / SAFE / SETTLED / REFUNDED */
    --red:        #ff3b30;                /* DEATH / LOSS / DANGER            */
    --red-soft:   #ff8a80;                /* danger affordances that need AA  */
    --alert:      #ff9f43;                /* RECOVERABLE error — not death    */
    --chrome:     #ffffff;                /* chrome that must stay neutral    */

    /* ── hairlines: was sixteen white alphas, now three ───────────────── */
    --hair:     rgba(255,255,255,.10);    /* every panel border               */
    --hair-lit: rgba(255,255,255,.22);    /* brackets, active edges, rims     */
    --hair-dim: rgba(255,255,255,.05);    /* internal row dividers            */

    /* ── three families: display, prose, data ─────────────────────────── */
    --f-display:'Bebas Neue', sans-serif;
    --f-text:   "Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
    --f-mono:   ui-monospace,"SF Mono","Cascadia Mono",Menlo,Consolas,monospace;

    /* ── display ramp. Tracking is INVERSE to size: the bigger the type the
         less it needs. The file had that backwards. ───────────────────── */
    --d-hero:clamp(38px,6vw,76px);
    --d-xl:  clamp(30px,4.2vw,46px);
    --d-lg:  clamp(26px,3vw,34px);
    --d-md:  22px;
    --d-sm:  19px;

    /* ── prose ramp: three steps, one measure ─────────────────────────── */
    --t-lg:16px; --t-md:14px; --t-sm:12.5px;
    --measure:68ch;

    /* ── label ramp: mono, tracked caps ──────────────────────────────── */
    --l-lg:12px; --l-md:11px; --l-sm:10px; --l-xs:9px;

    /* ── numeric ramp: mono + tabular-nums ───────────────────────────── */
    --n-hero:clamp(34px,7vw,56px); --n-lg:26px; --n-md:20px; --n-sm:13px;

    /* ── spacing: 4px base, nine steps, one gutter, one content width ── */
    --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px;
    --s6:32px; --s7:48px; --s8:64px; --s9:96px;
    --gutter:24px;
    --measure-w:1100px;

    /* ── corners. There is no radius scale here, there is a chamfer scale.
         Held in a custom property because clip-path accepts one, so a plate
         opts in without a markup change and without an !important war. ── */
    --cut-sm:polygon(5px 0,100% 0,100% calc(100% - 5px),calc(100% - 5px) 100%,0 100%,0 5px);
    --cut-md:polygon(9px 0,100% 0,100% calc(100% - 9px),calc(100% - 9px) 100%,0 100%,0 9px);
    --cut-lg:polygon(14px 0,100% 0,100% calc(100% - 14px),calc(100% - 14px) 100%,0 100%,0 14px);
    --cut-xl:polygon(16px 0,100% 0,100% calc(100% - 16px),calc(100% - 16px) 100%,0 100%,0 16px);

    /* ── elevation. Per CSS render order a clipped element's own box-shadow
         never paints, so lift is built from insets on the plate and from
         drop-shadow() on an unclipped parent. ─────────────────────────── */
    --e1:inset 0 1px 0 rgba(255,255,255,.06);
    --e2:inset 0 1px 0 rgba(255,255,255,.08),
         inset 0 -22px 30px -26px rgba(0,0,0,.9);
    --e3:0 40px 90px -34px rgba(0,0,0,.95);
    --lift-white:drop-shadow(0 10px 26px rgba(255,255,255,.28));
    --lift-amber:drop-shadow(0 10px 26px rgba(255,211,77,.30));

    /* ── motion: transform and opacity only. Never animate box-shadow,
         background or filter in a loop over a live canvas. ───────────── */
    --t-press:80ms; --t-fast:140ms; --t-base:220ms; --t-slow:420ms; --t-cine:950ms;
    --ease:     cubic-bezier(.2,.8,.3,1);
    --ease-in:  cubic-bezier(.2,.9,.25,1);
    --ease-gate:cubic-bezier(.65,0,.2,1);

    /* Legacy aliases. The HUD and the tactical layer still resolve through
       these while the lobby and in-game surfaces are migrated; they are the
       old names for tokens above, not extra values. */
    --hud:#ffffff;
    --ok:  var(--live);
    --warn:var(--amber);
    --mono:var(--f-mono);
  }
  *{margin:0;padding:0;box-sizing:border-box;}
  html,body{width:100%;height:100%;overflow:hidden;background:#05070a;}
  body{
    font-family:var(--f-text);
    color:var(--hud);
    -webkit-font-smoothing:antialiased;
    cursor:default;
    user-select:none;
  }
  canvas{display:block;position:absolute;inset:0;}
  .mono{font-family:var(--f-mono);}

  /* ---------------- HUD ---------------- */
  #hud{position:absolute;inset:0;pointer-events:none;opacity:0;transition:opacity .35s ease;}
  #hud.on{opacity:1;}

  /* Crosshair. Drawn to a canvas rather than built from boxes: at this size
     every edge has to land on a whole device pixel, and translated HTML
     elements sit on fractions of one and smear a 2px line into a grey smudge.
     Placement comes from JS so the canvas centre rounds to a whole pixel too. */
  #cross{position:absolute;pointer-events:none;}
  #cross.hidden{opacity:0;}

  /* ---- sniper scope ---- */
  #scope{position:absolute;inset:0;opacity:0;pointer-events:none;}
  /* the black surround is one radial stop so the lens stays perfectly round
     at any aspect; vmin keeps it circular rather than elliptical */
  #scope .shade{
    position:absolute;inset:0;
    background:radial-gradient(circle at 50% 50%,
      rgba(0,0,0,0) 0 36vmin,
      rgba(0,0,0,.55) 36vmin 36.7vmin,
      rgba(0,0,0,.99) 37.4vmin);
  }
  #scope .lens{
    position:absolute;left:50%;top:50%;width:72vmin;height:72vmin;
    transform:translate(-50%,-50%);border-radius:50%;
    box-shadow:inset 0 0 9vmin 2vmin rgba(0,0,0,.72),
               inset 0 0 1.6vmin .4vmin rgba(150,180,200,.14),
               0 0 0 .35vmin rgba(0,0,0,.9);
    background:radial-gradient(circle at 38% 30%,rgba(190,215,235,.07),rgba(0,0,0,0) 55%);
  }
  /* the reticle is near-black like a real one, so every element carries a
     faint light halo to stay readable against shadowed targets */
  #scope .retWrap{position:absolute;left:50%;top:50%;width:0;height:0;
    filter:drop-shadow(0 0 1px rgba(215,230,240,.55));}
  #scope .ret{position:absolute;background:rgba(8,10,12,.92);}
  /* hairlines stop short of the middle so the aim point stays readable */
  #scope .ret.h{height:1px;width:28vmin;top:-.5px;}
  #scope .ret.hL{left:-36vmin;} #scope .ret.hR{left:8vmin;}
  #scope .ret.v{width:1px;height:28vmin;left:-.5px;}
  #scope .ret.vT{top:-36vmin;} #scope .ret.vB{top:8vmin;}
  #scope .ret.thin{background:rgba(8,10,12,.8);}
  #scope .ret.hIn{height:1px;width:6.2vmin;top:-.5px;left:-8vmin;}
  #scope .ret.hInR{left:1.8vmin;}
  #scope .ret.vIn{width:1px;height:6.2vmin;left:-.5px;top:-8vmin;}
  #scope .ret.vInB{top:1.8vmin;}
  /* mil-dots: evenly spaced ticks down the lower post and across the h-line */
  #scope .mil{position:absolute;background:rgba(8,10,12,.9);}
  #scope .mil.d{width:.85vmin;height:.85vmin;border-radius:50%;margin-left:-.42vmin;margin-top:-.42vmin;}
  #scope .mil.t{width:1px;height:1.5vmin;margin-left:-.5px;}
  #scope .mil.tv{height:1px;width:1.5vmin;margin-top:-.5px;}
  #scope .center{position:absolute;width:2px;height:2px;left:-1px;top:-1px;
    background:rgba(255,70,50,.9);border-radius:50%;}
  #scope .breath{
    position:absolute;left:50%;bottom:8vmin;transform:translateX(-50%);
    font-size:10px;letter-spacing:.28em;color:rgba(223,230,236,.5);opacity:0;
  }
  #scope .breath.on{opacity:1;}

  #hitmark{position:absolute;left:50%;top:50%;width:0;height:0;opacity:0;}
  #hitmark i{position:absolute;width:2px;height:11px;background:#fff;left:-1px;top:-5.5px;
    box-shadow:0 0 4px rgba(0,0,0,.9);transform-origin:50% 50%;}
  #hitmark i:nth-child(1){transform:rotate(45deg) translate(0,-7px);}
  #hitmark i:nth-child(2){transform:rotate(-45deg) translate(0,-7px);}
  #hitmark i:nth-child(3){transform:rotate(135deg) translate(0,-7px);}
  #hitmark i:nth-child(4){transform:rotate(-135deg) translate(0,-7px);}
  #hitmark.show{animation:hm .22s ease-out;}
  #hitmark.kill i{background:var(--red);}
  @keyframes hm{0%{opacity:1;transform:scale(.55)}100%{opacity:0;transform:scale(1.5)}}

  /* generic panel. Deliberately NOT the landing page's S2 plate: the minimap is
     the one HUD cluster that owns a surface, and giving it the marketing plate
     would put a gradient sheet over a live scene for no legibility gain. */
  .panel{
    background:linear-gradient(180deg,rgba(10,14,18,.72),rgba(6,9,12,.6));
    border:1px solid var(--hair-lit);
    backdrop-filter:blur(2px);
  }
  .lbl{font-size:var(--l-sm);letter-spacing:.22em;text-transform:uppercase;color:var(--ink-4);font-weight:600;}

  /* ── S∅: the HUD has no plate ──────────────────────────────────────────
     Every cluster except the minimap sits directly on the render. Legibility
     comes from two things and neither of them is a box: a keyline on the
     glyph itself, and a corner wash anchored to the cluster's own corner —
     a vignette that fades to nothing, so it darkens the scene under the text
     without drawing an edge the eye has to read as chrome.

     The z-index is load-bearing: it makes each cluster its own stacking
     context so the `z-index:-1` wash can never escape behind the canvas. */
  #vitals,#ammo,#top,#feed,#comms{z-index:1;}
  #vitals::before,#ammo::before,#top::before,#feed::before{
    content:"";position:absolute;inset:-16px -22px -14px;z-index:-1;pointer-events:none;
    background:radial-gradient(135% 155% at var(--ox,0%) var(--oy,100%),
      rgba(0,0,0,.62),rgba(0,0,0,.26) 46%,transparent 82%);
  }
  #ammo::before{--ox:100%;--oy:100%;}
  #top::before  {--ox:50%; --oy:0%;}
  #feed::before {--ox:100%;--oy:0%;}
  /* Blur-only shadows have no edge: at 30–44px a 6px blur leaves the glyph
     grey-on-grey against a sunlit wall. A 1px hard ring in four directions
     gives every numeral a cut edge no matter what is behind it. */
  #hpNum,#apNum,#magNum,#resNum,#timer,#solBal,.bank-tag{
    text-shadow:0 1px 0 rgba(0,0,0,.95),0 -1px 0 rgba(0,0,0,.95),
                1px 0 0 rgba(0,0,0,.95),-1px 0 0 rgba(0,0,0,.95),
                0 3px 14px rgba(0,0,0,.9);
    font-variant-numeric:tabular-nums;
  }
  #vitals .lbl,#ammo #wname,#ammo #wmode,#objective,#mapWrap .lbl{
    text-shadow:0 1px 3px rgba(0,0,0,.95),0 0 10px rgba(0,0,0,.8);
  }

  /* health / armor */
  #vitals{position:absolute;left:26px;bottom:26px;width:250px;}
  .bar{position:relative;height:13px;margin-top:4px;background:rgba(0,0,0,.55);
    border:1px solid rgba(223,230,236,.2);overflow:hidden;}
  .bar > span{position:absolute;inset:0;width:100%;transform-origin:left center;transition:transform .18s cubic-bezier(.2,.8,.3,1);}
  .bar::after{content:"";position:absolute;inset:0;pointer-events:none;
    background:repeating-linear-gradient(90deg,transparent 0 9px,rgba(0,0,0,.55) 9px 11px);}
  #hpFill{background:linear-gradient(180deg,#8ef29a,#39b556);}
  #hpFill.low{background:linear-gradient(180deg,#ff8d7a,#e0231a);}
  #apFill{background:linear-gradient(180deg,#8ecbff,#2f7fd4);}
  #vitals .row{display:flex;justify-content:space-between;align-items:flex-end;}
  /* Ammo used to shout louder than health — 44px of magazine against 30px of
     HP, in a shooter where dying costs SOL. The relationship is swapped: the
     number that ends the round is the bigger one. Both stay in the condensed
     sans: these are glanced in a firefight, and mono at this size is too wide
     for the corner. tabular-nums (above) fixes the actual defect, the jitter. */
  #hpNum{font-size:38px;font-weight:700;line-height:.9;letter-spacing:-.01em;}
  #hpNum.low{color:var(--red-soft);}
  #apNum{font-size:var(--n-sm);font-weight:600;color:#9fc9f2;}
  #vitals .sub{margin-top:9px;}

  /* ammo */
  #ammo{position:absolute;right:26px;bottom:26px;text-align:right;min-width:250px;}
  #wname{font-size:var(--l-lg);letter-spacing:.28em;font-weight:700;color:var(--amber);}
  #wmode{font-size:var(--l-xs);letter-spacing:.2em;color:var(--ink-4);margin-top:2px;}
  #ammoRow{display:flex;align-items:flex-end;justify-content:flex-end;gap:8px;margin-top:2px;}
  #magNum{font-size:34px;font-weight:700;line-height:.85;letter-spacing:-.02em;}
  #magNum.empty{color:var(--red);}
  #resNum{font-size:16px;font-weight:600;color:var(--ink-3);padding-bottom:4px;}
  #wicon{margin-top:6px;height:26px;display:flex;justify-content:flex-end;align-items:center;opacity:.85;}
  #reloadHint{font-size:10px;letter-spacing:.24em;color:var(--red);height:12px;font-weight:700;margin-top:5px;}
  #reloadHint.blink{animation:bl .7s steps(1,end) infinite;}
  @keyframes bl{50%{opacity:.15}}

  /* slots */
  #slots{position:absolute;right:26px;bottom:150px;display:flex;flex-direction:column;gap:5px;align-items:flex-end;}
  .slot{display:flex;align-items:center;gap:8px;padding:4px 9px;font-size:var(--l-sm);letter-spacing:.16em;font-weight:600;
    color:var(--ink-4);border-right:2px solid var(--hair-lit);background:rgba(6,9,12,.55);
    text-shadow:0 1px 3px rgba(0,0,0,.9);transition:color var(--t-fast) var(--ease),background var(--t-fast) var(--ease);}
  .slot b{font-size:var(--l-xs);opacity:.7;}
  /* was a magenta fill — a colour that appears in no stated system, on the
     element that tells you which gun is in your hands */
  .slot.act{color:var(--ink);border-right-color:var(--amber);background:var(--amber-wash);}

  /* killfeed */
  #feed{position:absolute;right:26px;top:26px;display:flex;flex-direction:column;gap:4px;align-items:flex-end;}
  /* The accent rail marks YOUR kill. It used to sit on every row, which was
     invisible while --amber was white and would have meant nothing the moment
     it turned gold. Rows the player is in get .mine (see the killfeed pass). */
  /* A killfeed is a record, so it is mono. `.other` had no rule at all — every
     networked kill fell through to inherited white and read exactly like your
     own, in a mode where telling those apart is the whole point of the feed. */
  .kf{display:flex;align-items:center;gap:7px;padding:5px 10px;
    font-family:var(--f-mono);font-size:var(--l-md);font-weight:600;letter-spacing:.06em;
    color:var(--ink-3);text-shadow:0 1px 3px rgba(0,0,0,.95);
    background:rgba(6,9,12,.74);border-right:2px solid var(--hair-lit);animation:kfin .22s ease-out;}
  .kf.mine{border-right-color:var(--amber);background:var(--amber-wash);}
  .kf .me{color:var(--amber);}
  .kf .other{color:var(--ink-3);}
  .kf .vic{color:var(--red-soft);}
  .kf .ic{opacity:.9;display:flex;}
  @keyframes kfin{from{opacity:0;transform:translateX(24px)}to{opacity:1;transform:none}}
  .kf.out{opacity:0;transform:translateX(24px);transition:all .3s ease-in;}

  /* intercepted squad comms.
     Re-homed under the minimap. It used to sit at bottom:150px in the vitals
     column, which is the left thumb's whole arc on a phone and the HP readout's
     airspace on a desktop. Two lines, not four: this is a channel, not a log. */
  #comms{position:absolute;left:26px;top:238px;display:flex;flex-direction:column;
    gap:3px;align-items:flex-start;max-width:300px;}
  .cm{font-family:var(--f-mono);font-size:var(--l-sm);letter-spacing:.06em;font-weight:600;
    color:var(--ink-2);text-shadow:0 1px 0 rgba(0,0,0,.95),0 -1px 0 rgba(0,0,0,.95),
      1px 0 0 rgba(0,0,0,.95),-1px 0 0 rgba(0,0,0,.95),0 0 12px rgba(0,0,0,.9);
    animation:cmin .18s ease-out;white-space:nowrap;}
  .cm b{color:var(--red-soft);font-weight:700;}
  .cm i{font-style:normal;color:var(--ink-ghost);}
  @keyframes cmin{from{opacity:0;transform:translateX(-10px)}to{opacity:1;transform:none}}
  .cm.out{opacity:0;transition:opacity .5s linear;}

  /* minimap */
  /* No chamfer here. The global `canvas{position:absolute;inset:0}` rule takes
     #minimap out of flow, so #mapWrap is only as tall as its label and the
     canvas overflows it — a clip-path would cut away the minimap itself. */
  #mapWrap{position:absolute;left:26px;top:26px;padding:6px;}
  #mapWrap .lbl{margin-bottom:4px;display:flex;justify-content:space-between;}
  #minimap{display:block;image-rendering:auto;}

  /* top center. The clock is the one HUD numeral that goes mono: it changes
     every second, and the width jitter of proportional digits is visible. */
  #top{position:absolute;left:50%;top:20px;transform:translateX(-50%);text-align:center;}

  /* ──── ROSTER STRIP ────
     Sits above the clock, your squad on the left, theirs on the right, reading
     outward from the centre so both counts land next to the timer where the eye
     already is. Hidden until there is something to disambiguate: in a 1v1 the
     only other body in the yard is the opponent. */
  #rosterStrip{display:none;align-items:center;justify-content:center;
    gap:var(--s4);margin-bottom:6px;}
  #rosterStrip.on{display:flex;}
  .rs-side{display:flex;align-items:center;gap:7px;}
  .rs-side b{font-family:var(--f-mono);font-size:13px;font-weight:700;
    letter-spacing:.04em;min-width:32px;}
  .rs-side.us b{color:var(--live);text-align:left;}
  .rs-side.them b{color:var(--red-soft);text-align:right;}
  .rs-pips{display:flex;gap:4px;}
  .rs-pip{width:9px;height:9px;border-radius:2px;
    background:transparent;box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.30);
    transition:background .18s,box-shadow .18s,opacity .18s;}
  .rs-side.us   .rs-pip.up{background:var(--live);box-shadow:0 0 7px rgba(126,224,129,.55);}
  .rs-side.them .rs-pip.up{background:var(--red-soft);box-shadow:0 0 7px rgba(255,138,128,.45);}
  /* Your own pip is ringed so you can find yourself without counting. */
  .rs-pip.you{outline:1.5px solid rgba(255,255,255,.85);outline-offset:2px;}
  /* Left the match entirely — distinct from merely being dead, which is
     temporary. */
  .rs-pip.gone{opacity:.30;box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.18);}
  #timer{font-family:var(--f-mono);font-size:26px;font-weight:700;letter-spacing:.04em;line-height:1;}
  #timer.warn{color:var(--red);animation:bl 1s steps(1,end) infinite;}
  #objective{font-size:var(--l-sm);letter-spacing:.26em;color:var(--ink-3);margin-top:6px;font-weight:600;}
  #objective b{color:var(--amber);font-size:var(--l-lg);}

  /* damage direction */
  #dmgDirs{position:absolute;left:50%;top:50%;width:0;height:0;}
  .dmgArc{position:absolute;left:-160px;top:-160px;width:320px;height:320px;opacity:0;}
  .dmgArc::before{
    content:"";position:absolute;left:50%;top:0;width:150px;height:60px;transform:translateX(-50%);
    background:radial-gradient(ellipse at 50% 100%,rgba(255,40,30,.85),rgba(255,40,30,0) 72%);
    clip-path:polygon(0% 100%,100% 100%,78% 0%,22% 0%);
  }

  /* low health */
  #lowhp{position:absolute;inset:0;opacity:0;
    background:radial-gradient(ellipse at center,transparent 42%,rgba(140,0,0,.62) 100%);}

  /* Kill confirm. This was a 140px inset bloom on a .35s fade held for 130ms —
     a ~480ms full-screen white wash, larger and brighter than the red damage
     vignette right next to it, and inside a three-kill streak it never finished
     fading. Same visual form as taking damage, opposite meaning. A rim reads as
     confirmation; a wash reads as injury. */
  #edgeGlow{position:absolute;inset:0;opacity:0;
    box-shadow:inset 0 0 0 3px rgba(255,255,255,.55),inset 0 0 60px rgba(255,255,255,.14);
    transition:opacity 90ms linear;}

  /* ---------------- touch controls ----------------
     Only ever shown on a real touchscreen; a desktop with a touch-capable
     monitor still gets the keyboard layout because the game is pointer-locked. */
  #touchUI{position:absolute;inset:0;z-index:9;display:none;pointer-events:none;}
  #touchUI.on{display:block;}
  /* An invisible HUD still hit-tests. Without this the fire button eats taps on
     the menus, where it is sitting at opacity 0 under the start screen. */
  #hud:not(.on) #touchUI{display:none;}

  /* Insets are written as max(px, env()) so they are correct under
     viewport-fit=cover and unchanged without it — every env() here resolves to
     0 today. The floor is what actually moves the stick out of the bottom
     gesture strip; the env() is what keeps it out on a notched device if the
     landing page ever takes cover. */
  #tStick{position:absolute;
    left:max(20px,calc(env(safe-area-inset-left) + 14px));
    bottom:max(30px,calc(env(safe-area-inset-bottom) + 22px));
    width:min(32vw,152px,42vh);aspect-ratio:1;
    border-radius:50%;border:2px solid var(--hair-lit);background:rgba(0,0,0,.34);
    pointer-events:auto;touch-action:none;}
  #tStickNub{position:absolute;left:50%;top:50%;width:38%;aspect-ratio:1;
    transform:translate(-50%,-50%);border-radius:50%;
    background:rgba(255,255,255,.32);border:1px solid rgba(255,255,255,.5);}

  /* The look surface. Look used to be a window-level pointer that refused to
     start on anything inside #touchUI, so the right thumb was either dragging
     to aim OR holding FIRE — to shoot a moving target you had to stop aiming.
     This is the pad the thumb slides onto; the buttons are painted above it,
     which is why it has to come FIRST in the markup. Transparent by design:
     the centre of the play area stays uncovered. */
  #tLookPad{position:absolute;right:0;top:0;bottom:0;width:56%;
    pointer-events:auto;touch-action:none;background:none;}

  /* Sparse auto-placement dropped the 2-wide FIRE to row 3, left (2,2) empty
     forever and orphaned ADS at row 4 — the furthest cell from the thumb. Named
     areas make DOM order irrelevant and put FIRE in the corner, double height. */
  #tButtons{position:absolute;
    right:max(16px,calc(env(safe-area-inset-right) + 12px));
    bottom:max(26px,calc(env(safe-area-inset-bottom) + 18px));
    display:grid;gap:10px;grid-template-columns:repeat(2,minmax(68px,1fr));
    grid-template-areas:"ads fire" "rld fire" "crch jump";
    pointer-events:auto;}
  .tbtn{min-height:58px;padding:12px 10px;border-radius:0;clip-path:var(--cut-sm);
    font-family:var(--f-mono);font-weight:800;
    font-size:var(--l-md);letter-spacing:.1em;color:var(--ink);touch-action:none;
    text-shadow:0 1px 3px rgba(0,0,0,.95);
    background:rgba(0,0,0,.5);border:1px solid var(--hair-lit);}
  /* FIRE is marked by rim weight, not by a pale fill: a light slab in the
     corner of a sunlit scene turns its own white label to mush, and it is the
     one control that must never be hard to read. */
  .tbtn[data-act="fire"]  {grid-area:fire;border-color:var(--ink);border-width:2px;
    background:rgba(0,0,0,.58);letter-spacing:.16em;}
  .tbtn[data-act="ads"]   {grid-area:ads;}
  .tbtn[data-act="reload"]{grid-area:rld;}
  .tbtn[data-act="crouch"]{grid-area:crch;}
  .tbtn[data-act="jump"]  {grid-area:jump;}
  .tbtn.held{background:rgba(255,255,255,.34);border-color:var(--ink);}
  /* Latched fire reads as amber: the trigger is down and no finger is on it. */
  .tbtn[data-act="fire"].latched{background:var(--amber-wash);border-color:var(--amber);color:var(--amber);}

  /* Without this a touch player cannot pause, open settings, quit or leave the
     match for its whole duration: #pause only ever opened on pointerlockchange,
     and pointer lock is never requested on touch. Top-left, clear of both
     thumbs' resting arcs, above the look pad. */
  #tPause{position:absolute;
    left:max(14px,calc(env(safe-area-inset-left) + 10px));
    top:max(12px,calc(env(safe-area-inset-top) + 10px));
    z-index:2;width:46px;height:38px;display:flex;align-items:center;justify-content:center;gap:4px;
    pointer-events:auto;background:rgba(0,0,0,.5);border:1px solid var(--hair-lit);
    clip-path:var(--cut-sm);cursor:pointer;}
  #tPause i{display:block;width:3px;height:13px;background:var(--ink);}

  /* ---------------- scoreboard & death cam ---------------- */
  /* S3 document — the one HUD element that is genuinely a table of record, so
     it takes the surface the whitepaper and the result box use. */
  #scoreboard{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
    min-width:340px;max-width:min(560px,92vw);padding:var(--s5);z-index:8;
    background:rgba(12,15,19,.94);border:1px solid var(--hair);box-shadow:var(--e2);
    border-radius:0;clip-path:var(--cut-lg);
    display:none;pointer-events:none;}
  #scoreboard.on{display:block;}
  /* 'Code Predators' is the wordmark and appears exactly once, on the gate.
     Spending the identity face on a transient overlay is what makes it stop
     meaning anything, so this joins the display ramp instead. */
  #scoreboard .sb-teams{text-align:center;font-family:var(--f-display);
    font-size:var(--d-md);letter-spacing:.18em;line-height:1;color:var(--ink);margin-bottom:var(--s4);}
  #scoreboard table{width:100%;border-collapse:collapse;}
  #scoreboard td{padding:7px 6px;font-size:var(--l-lg);color:var(--ink-2);
    border-bottom:1px solid var(--hair-dim);font-family:var(--f-mono);}
  #scoreboard td:last-child{text-align:right;font-size:var(--l-sm);letter-spacing:.14em;}
  #scoreboard tr.you td{color:var(--amber);}
  #scoreboard tr.down td{color:var(--ink-4);}
  #scoreboard .sb-hint{margin-top:var(--s3);text-align:center;font-family:var(--f-mono);
    font-size:var(--l-xs);letter-spacing:.3em;color:var(--ink-4);}
  /* ---- round status ----
     Your own line, above the squad table. A label/value stack per cell rather
     than a sentence: the numbers are what the player is scanning for, and they
     have to be readable in the half-second a held key is worth. The row wraps
     instead of scrolling — a status board that needs a swipe is not a glance. */
  .sb-stats{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--s3) var(--s5);
    margin-bottom:var(--s4);padding-bottom:var(--s3);border-bottom:1px solid var(--hair-dim);}
  .sb-stats:empty{display:none;}
  .sb-stats > div{display:flex;flex-direction:column;align-items:center;gap:3px;min-width:52px;}
  .sb-stats b{font-family:var(--f-mono);font-size:var(--l-lg);font-weight:700;color:var(--ink);
    font-variant-numeric:tabular-nums;line-height:1;}
  .sb-stats b.amber{color:var(--amber);}
  .sb-stats span{font-family:var(--f-mono);font-size:var(--l-xs);letter-spacing:.18em;
    color:var(--ink-4);line-height:1;}
  /* The middle column carries the duel record; only the status column is
     right-aligned, so this one is centred to keep the three from crowding. */
  #scoreboard td.sb-mid{text-align:center;font-size:var(--l-sm);color:var(--ink-4);
    letter-spacing:.08em;}
  #scoreboard td.sb-mid em{font-style:normal;color:var(--red-soft);}
  /* The death cam's headline. It was an 11px label announcing the one event
     that just cost the player money. */
  #spectateTag{position:absolute;left:50%;top:18%;transform:translate(-50%,-50%);
    z-index:8;opacity:0;transition:opacity .25s;pointer-events:none;text-align:center;
    max-width:88vw;
    font-family:var(--f-display);font-size:var(--d-md);letter-spacing:.22em;line-height:1.1;
    color:var(--red-soft);text-shadow:0 1px 0 rgba(0,0,0,.95),0 2px 14px rgba(0,0,0,.95);}
  #spectateTag.on{opacity:1;}

  /* ---------------- damage numbers & killstreak ---------------- */
  .dmg-num{position:absolute;transform:translate(-50%,-50%);pointer-events:none;
    font-family:var(--f-mono);font-size:15px;font-weight:800;
    color:var(--ink);font-variant-numeric:tabular-nums;
    text-shadow:0 1px 0 rgba(0,0,0,.95),0 -1px 0 rgba(0,0,0,.95),
                1px 0 0 rgba(0,0,0,.95),-1px 0 0 rgba(0,0,0,.95),0 2px 10px rgba(0,0,0,.9);
    opacity:0;z-index:6;}
  .dmg-num.head{color:var(--amber);font-size:20px;}
  .dmg-num.go{animation:dmgFloat .7s cubic-bezier(.2,.7,.3,1) forwards;}
  @keyframes dmgFloat{
    0%  {opacity:0;transform:translate(-50%,-50%) scale(.7);}
    18% {opacity:1;transform:translate(calc(-50% + var(--dx,0px)),-72%) scale(1.12);}
    100%{opacity:0;transform:translate(calc(-50% + var(--dx,0px)),-160%) scale(.95);}
  }
  #streakBanner{position:absolute;left:50%;top:26%;transform:translate(-50%,-50%);
    pointer-events:none;z-index:7;opacity:0;white-space:nowrap;
    font-family:var(--f-display);font-size:var(--d-md);font-weight:900;letter-spacing:.3em;
    color:var(--ink);text-shadow:0 0 24px rgba(255,255,255,.5),0 1px 0 rgba(0,0,0,.95),0 2px 12px rgba(0,0,0,.95);}
  #streakBanner.big{font-size:var(--d-xl);color:var(--amber);
    text-shadow:0 0 30px rgba(255,211,77,.65),0 1px 0 rgba(0,0,0,.95),0 2px 12px rgba(0,0,0,.95);}
  #streakBanner.show{animation:streakPop 1.4s ease-out forwards;}
  @keyframes streakPop{
    0%  {opacity:0;transform:translate(-50%,-50%) scale(.75);}
    12% {opacity:1;transform:translate(-50%,-50%) scale(1.06);}
    22% {transform:translate(-50%,-50%) scale(1);}
    72% {opacity:1;}
    100%{opacity:0;transform:translate(-50%,-62%) scale(.98);}
  }

  /* ---------------- debrief: grade, career, personal bests ---------------- */
  #endExtras{display:flex;flex-direction:column;align-items:center;gap:var(--s4);margin:18px 0 6px;}
  #endExtras .grade{display:flex;flex-direction:column;align-items:center;line-height:1;}
  /* The grade is the verdict, and DOM order already puts four stat tiles above
     it. If it is going to arrive second it has to arrive loud. */
  #endExtras .grade b{font-family:var(--f-display);font-size:clamp(58px,10vh,104px);
    font-weight:900;letter-spacing:.04em;}
  #endExtras .grade span{font-family:var(--f-mono);font-size:var(--l-xs);letter-spacing:.5em;
    color:var(--ink-4);margin-top:var(--s2);}
  /* Six steps, all drawn from the system: gold, green, white, grey, then the two
     warnings. The blue and the brown were the only two of their kind in the file. */
  .grade-S b{color:var(--amber);text-shadow:0 0 26px rgba(255,211,77,.5);}
  .grade-A b{color:var(--live);text-shadow:0 0 22px rgba(126,224,129,.4);}
  .grade-B b{color:var(--ink);}
  .grade-C b{color:var(--ink-3);}
  .grade-D b{color:var(--alert);}
  .grade-F b{color:var(--red);}
  .debrief-meta{display:grid;grid-template-columns:1fr 1fr;gap:4px var(--s5);
    font-family:var(--f-mono);font-size:var(--l-md);letter-spacing:.1em;}
  .debrief-meta p{display:flex;justify-content:space-between;gap:18px;padding:4px 0;
    border-bottom:1px solid var(--hair-dim);min-width:210px;}
  .debrief-meta span{color:var(--ink-4);}
  .debrief-meta b{color:var(--ink-2);font-weight:700;font-variant-numeric:tabular-nums;}
  .debrief-meta p.hot b{color:var(--amber);}
  /* levelProgress() was already computed and thrown away — NEXT LEVEL rendered
     as a text row typographically identical to DAY STREAK. The maths was sitting
     right there; this is the bar it was for. */
  .debrief-lvl{width:min(440px,100%);}
  .debrief-lvl .dl-head{display:flex;justify-content:space-between;gap:var(--s3);
    font-family:var(--f-mono);font-size:var(--l-xs);letter-spacing:.24em;
    color:var(--ink-4);margin-bottom:var(--s2);}
  .debrief-lvl .dl-head b{color:var(--ink-2);font-weight:700;}
  .debrief-lvl .dl-track{height:3px;background:var(--hair);overflow:hidden;}
  .debrief-lvl .dl-fill{display:block;height:100%;width:100%;background:var(--amber);
    transform-origin:left center;transform:scaleX(0);
    transition:transform var(--t-slow) var(--ease-in) 120ms;}
  /* Award badge. The flat pill it replaced read as a disabled chip rather than a
     reward, so the weight is carried by a chamfered plate, a lit rim, and a shine
     that sweeps once on arrival — the corner cuts echo the HEXOPS mark. */
  .pb-banner{
    --hue:var(--amber); --hue-lit:#ffeaa6;
    position:relative;display:inline-flex;align-items:center;gap:12px;
    font-family:var(--f-mono);
    font-size:var(--l-sm);letter-spacing:.3em;font-weight:800;text-transform:uppercase;
    color:var(--hue-lit);
    padding:11px 24px;
    background:
      linear-gradient(180deg,color-mix(in srgb,var(--hue) 18%,transparent),
                             color-mix(in srgb,var(--hue) 5%,transparent)),
      rgba(0,0,0,.35);
    border:1px solid color-mix(in srgb,var(--hue) 48%,transparent);
    clip-path:var(--cut-md);
    text-shadow:0 0 14px color-mix(in srgb,var(--hue) 55%,transparent);
    box-shadow:
      0 10px 30px -12px color-mix(in srgb,var(--hue) 65%,transparent),
      inset 0 1px 0 rgba(255,255,255,.16),
      inset 0 -1px 0 rgba(0,0,0,.4);
    overflow:hidden;
    animation:pbIn .55s cubic-bezier(.2,.9,.25,1) both;
  }
  /* Rotated square: an insignia pip, and the only non-text mark in the debrief. */
  .pb-banner::before{
    content:"";flex:none;width:7px;height:7px;transform:rotate(45deg);
    background:var(--hue);
    box-shadow:0 0 10px color-mix(in srgb,var(--hue) 80%,transparent);
  }
  .pb-banner::after{
    content:"";position:absolute;inset:0;pointer-events:none;
    background:linear-gradient(105deg,transparent 38%,
      color-mix(in srgb,var(--hue-lit) 42%,transparent) 50%,transparent 62%);
    transform:translateX(-130%);
    animation:pbShine 1.5s cubic-bezier(.3,0,.2,1) .35s 1 both;
  }
  .pb-banner.rank{--hue:var(--live);--hue-lit:#c8f5ca;}
  @keyframes pbIn{
    from{opacity:0;transform:translateY(9px) scale(.94);}
    to  {opacity:1;transform:none;}
  }
  @keyframes pbShine{ to{transform:translateX(130%);} }
  /* The sweep is decorative; the badge must still land without it. */
  @media (prefers-reduced-motion:reduce){
    .pb-banner{animation:none;}
    .pb-banner::after{animation:none;opacity:0;}
  }
  .practice-note{font-family:var(--f-mono);font-size:var(--l-xs);letter-spacing:.28em;color:var(--ink-4);}
  @media (max-width:640px){ .debrief-meta{grid-template-columns:1fr;} }

  /* ---------------- screens ---------------- */
  /* A centred flex column overflows in BOTH directions once its content is taller
     than the viewport, so the top of the report — the title — gets cut off with no
     way to scroll back to it. `safe center` degrades to flex-start exactly in that
     case; the plain `center` before it is the fallback for browsers without it. */
  .screen{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;
    justify-content:center;justify-content:safe center;
    overflow-y:auto;overscroll-behavior:contain;padding:28px 20px;
    background:radial-gradient(ellipse at 50% 45%,rgba(18,24,30,.86),rgba(3,5,7,.97));z-index:20;}
  .screen.hide{display:none;}
  .screen::after{content:"";position:absolute;inset:0;pointer-events:none;
    background:repeating-linear-gradient(0deg,rgba(255,255,255,.024) 0 1px,transparent 1px 3px);}

  /* the menu runs over a live camera move, so the scrim only darkens the side
     the text sits on and leaves the yard readable on the right */
  #startScreen{
    cursor:pointer;align-items:flex-start;justify-content:center;
    padding:clamp(14px,3.5vh,40px) clamp(28px,7vw,110px);
    background:
      linear-gradient(180deg,rgba(3,5,7,.78) 0%,rgba(3,5,7,0) 26%,rgba(3,5,7,0) 82%,rgba(3,5,7,.55) 100%),
      linear-gradient(95deg,rgba(4,6,9,.94) 0%,rgba(4,6,9,.86) 30%,rgba(4,6,9,.48) 56%,rgba(4,6,9,.16) 80%,rgba(4,6,9,.28) 100%);
  }
  #startScreen .col{display:flex;flex-direction:column;align-items:flex-start;
    width:min(600px,62vw);max-height:100%;position:relative;z-index:1;}
  #startScreen .title,#startScreen .eyebrow{text-align:left;}
  .brand-mark{width:clamp(56px,7vh,88px);height:auto;display:block;
    margin-bottom:clamp(10px,2vh,20px);filter:drop-shadow(0 0 22px rgba(255,255,255,.28));}
  #startScreen .rule{margin:clamp(10px,2.6vh,26px) 0 clamp(10px,2.4vh,24px);}
  /* --amber means money. The start screen's eyebrow and subtitle are chrome, so
     they name --chrome explicitly rather than riding whatever the accent is. */
  .eyebrow{font-family:var(--f-mono);font-size:var(--l-md);letter-spacing:.55em;color:var(--chrome);font-weight:700;
    margin-bottom:clamp(6px,1.4vh,14px);}
  /* type is clamped against height as well as width: on a short, wide window a
     purely vw-driven title grows until the panel runs off both ends of the screen */
  .title{font-family:var(--f-display); font-size:clamp(32px,min(7.4vw,12vh),86px);font-weight:800;letter-spacing:0.22em;line-height:.92;text-align:center;
    background:linear-gradient(180deg,#fff 20%,#7d8996 100%);-webkit-background-clip:text;background-clip:text;color:transparent;
    text-shadow:0 10px 40px rgba(0,0,0,.6);}
  .title small{display:block;font-size:.26em;letter-spacing:.44em;font-weight:600;color:var(--chrome);
    -webkit-text-fill-color:var(--chrome);margin-top:clamp(7px,1.6vh,16px);}
  .rule{width:min(560px,74vw);height:1px;background:linear-gradient(90deg,rgba(255,255,255,.75),transparent);
    margin:clamp(11px,2.8vh,28px) 0;}
  /* fixed key column so every label starts on the same x */
  .keys{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(3px,.9vh,7px) 30px;width:100%;}
  .key{display:grid;grid-template-columns:96px 1fr;align-items:center;gap:11px;
    font-family:var(--f-mono);font-size:var(--l-sm);letter-spacing:.14em;color:var(--ink-3);}
  .key kbd{font-family:var(--f-mono);font-size:var(--l-xs);font-weight:700;letter-spacing:.06em;padding:4px 0;text-align:center;
    background:rgba(255,255,255,.06);border:1px solid var(--hair-lit);border-bottom-width:2px;color:var(--ink);
    white-space:nowrap;}
  /* A <button>, not a <div> with a click handler: this is the primary control
     of the entire game and it had no tab stop while the wallet modal shipped a
     full focus trap. The glow is a composited ::after opacity fade — the old
     keyframe animated box-shadow AND background on a 40px-padded box, forever,
     on the one screen where the WebGL scene is also running. */
  .cta{position:relative;margin-top:clamp(14px,3.4vh,34px);
    font-family:var(--f-mono);font-size:14px;letter-spacing:.42em;font-weight:700;color:var(--ink);
    padding:clamp(10px,1.5vh,15px) 40px;border:1px solid rgba(255,255,255,.5);background:rgba(255,255,255,.08);
    border-radius:0;cursor:pointer;pointer-events:auto;text-align:center;}
  /* No background on the pseudo: an outer box-shadow is clipped out of its own
     border box, so the glow lands around the button and never washes the label. */
  .cta::after{content:"";position:absolute;inset:-1px;pointer-events:none;
    box-shadow:0 0 34px rgba(255,255,255,.28);
    opacity:0;animation:pulse 2.1s ease-in-out infinite;}
  @keyframes pulse{0%,100%{opacity:0}50%{opacity:1}}
  .hint{margin-top:clamp(7px,1.5vh,15px);font-family:var(--f-mono);font-size:var(--l-sm);letter-spacing:.2em;color:var(--ink-4);}
  /* corner slugs sell the frame as a screen rather than a web page.
     Decoration, so they sit at --ink-ghost and never carry a fact on their own. */
  .slug{position:absolute;font-family:var(--f-mono);font-size:var(--l-xs);letter-spacing:.28em;color:var(--ink-ghost);z-index:1;}
  .slug.tl{top:26px;left:30px;} .slug.tr{top:26px;right:30px;text-align:right;}
  .slug.br{bottom:24px;right:30px;text-align:right;}
  .slug b{color:var(--ink-3);font-weight:700;}

  #endScreen .title{font-size:clamp(34px,6vw,68px);}
  #endScreen.win .title{background:linear-gradient(180deg,#fff,#888);-webkit-background-clip:text;background-clip:text;}
  #endScreen.lose .title{background:linear-gradient(180deg,#fff,#c4342a);-webkit-background-clip:text;background-clip:text;}
  #stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:1px;background:var(--hair);
    width:min(720px,88vw);border:1px solid var(--hair);}
  /* Stepped back from 34px: the grade below is the verdict, and the eye was
     landing on "7/10 ELIMINATIONS" first. */
  .stat{background:rgba(9,11,14,.94);padding:20px 12px;text-align:center;}
  .stat .v{font-size:26px;font-weight:800;letter-spacing:-.02em;color:var(--ink);line-height:1;
    font-variant-numeric:tabular-nums;}
  .stat .v span{font-size:.5em;color:var(--ink-4);margin-left:1px;}
  .stat .k{font-family:var(--f-mono);font-size:var(--l-sm);letter-spacing:.24em;color:var(--ink-4);margin-top:10px;font-weight:600;}
  #endSub{font-family:var(--f-mono);font-size:var(--l-lg);letter-spacing:.24em;color:var(--ink-3);margin-top:6px;}
  button.btn{pointer-events:auto;cursor:pointer;margin-top:34px;font-family:var(--f-mono);font-size:13px;letter-spacing:.34em;font-weight:700;
    color:#000;padding:15px 42px;background:rgba(255,255,255,.9);border:1px solid #fff;border-radius:0;
    transition:background var(--t-fast) var(--ease),box-shadow var(--t-fast) var(--ease),transform var(--t-press) var(--ease);}
  button.btn:hover{background:#fff;box-shadow:0 0 30px rgba(255,255,255,.35);color:#000;}
  button.btn:active{transform:translateY(1px);}

  #pause{position:absolute;inset:0;display:none;align-items:center;justify-content:center;flex-direction:column;
    background:rgba(3,5,7,.78);z-index:15;}
  #pause.on{display:flex;}
  #pause .t{font-family:var(--f-display);font-size:var(--d-xl);font-weight:800;letter-spacing:.3em;line-height:.92;}
  #pause .s{font-family:var(--f-mono);font-size:var(--l-sm);letter-spacing:.3em;color:var(--ink-4);margin-top:10px;}
  /* The card stops backdrop clicks reaching the resume handler, so the buttons
     inside it are actually clickable. S4 sheet: --e3 is an outer shadow and
     clip-path would eat it, so the lift is carried by insets and the scrim. */
  .pause-card{display:flex;flex-direction:column;align-items:center;
    padding:var(--s7) var(--s6) var(--s6);
    background:linear-gradient(180deg,rgba(21,27,33,.98),rgba(10,13,17,.98));
    border:1px solid var(--hair-lit);box-shadow:var(--e2);clip-path:var(--cut-xl);}
  /* Alt-tab drops the pointer lock, so this card is what a player is looking at
     the moment they leave the match. Reusing the scoreboard's stat cells keeps
     the two readings of the same round identical rather than merely similar. */
  /* A fixed 3-up grid rather than a wrapping row: flex-wrap left the six cells
     as 4 + 2, which reads as a row that ran out of space instead of a block. */
  .pause-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s4) var(--s3);
    margin-top:26px;padding-top:var(--s4);border-top:1px solid var(--hair-dim);
    width:290px;}
  .pause-stats:empty{display:none;}
  .pause-stats > div{display:flex;flex-direction:column;align-items:center;gap:4px;min-width:58px;}
  .pause-stats b{font-family:var(--f-mono);font-size:var(--t-lg);font-weight:700;color:var(--ink);
    font-variant-numeric:tabular-nums;line-height:1;}
  .pause-stats b.amber{color:var(--amber);}
  .pause-stats span{font-family:var(--f-mono);font-size:var(--l-xs);letter-spacing:.18em;
    color:var(--ink-4);line-height:1;}
  .pause-actions{display:flex;flex-direction:column;gap:9px;margin-top:30px;width:250px;}
  /* Leaving a match needs a visible exit that isn't the marketing page. */
  .start-actions{display:flex;gap:10px;margin-top:22px;flex-wrap:wrap;}
  .start-actions .pbtn{width:230px;}
  .end-actions{display:flex;gap:12px;margin-top:8px;flex-wrap:wrap;justify-content:center;}
  .btn.ghost{background:transparent;border:1px solid var(--hair-lit);color:var(--ink);}
  .btn.ghost:hover{border-color:var(--ink);background:rgba(255,255,255,.07);}
  @media (max-width:640px){
    .start-actions{flex-direction:column;}
    .start-actions .pbtn{width:100%;}
    .end-actions{flex-direction:column;width:100%;}
  }
  .pbtn{pointer-events:auto;cursor:pointer;font-family:var(--f-mono);font-size:var(--l-md);letter-spacing:.26em;
    font-weight:800;padding:13px 18px;color:var(--ink);background:rgba(255,255,255,.06);
    border:1px solid var(--hair-lit);
    transition:background var(--t-fast) var(--ease),border-color var(--t-fast) var(--ease),transform var(--t-press) var(--ease);
    clip-path:var(--cut-sm);}
  .pbtn:hover{background:rgba(255,255,255,.14);border-color:rgba(255,255,255,.38);}
  .pbtn:active{transform:translateY(1px);}
  .pbtn.primary{background:var(--ink);color:#000;border-color:var(--ink);}
  /* clip-path eats an outer shadow, so the lift is a hairline rise instead. */
  .pbtn.primary:hover{transform:translateY(-1px);}
  .pbtn.primary:active{transform:translateY(1px);}
  .pbtn.danger{color:var(--red-soft);border-color:rgba(255,59,48,.32);}
  .pbtn.danger:hover{background:rgba(255,59,48,.14);border-color:rgba(255,59,48,.6);}

  /* ---------------- closed beta access ---------------- */
  #accessModal{position:fixed;inset:0;z-index:120;display:none;align-items:center;justify-content:center;
    padding:24px;background:rgba(3,5,7,.88);backdrop-filter:blur(9px);}
  #accessModal.on{display:flex;}
  .acc-card{position:relative;width:min(520px,100%);max-height:90vh;overflow-y:auto;overscroll-behavior:contain;
    background:linear-gradient(180deg,rgba(22,28,35,.99),rgba(11,14,18,.99));
    border:1px solid rgba(255,255,255,.14);
    clip-path:polygon(16px 0,100% 0,100% calc(100% - 16px),calc(100% - 16px) 100%,0 100%,0 16px);
    box-shadow:0 50px 110px -36px rgba(0,0,0,.95);
    animation:accIn .42s cubic-bezier(.2,.9,.25,1) both;}
  @keyframes accIn{from{opacity:0;transform:translateY(14px) scale(.97);}to{opacity:1;transform:none;}}
  .acc-top{padding:30px 32px 0;}
  .acc-badge{display:inline-flex;align-items:center;gap:9px;font-size:9px;letter-spacing:.28em;font-weight:800;
    color:#ffd34d;padding:7px 14px;border:1px solid rgba(255,211,77,.4);background:rgba(255,211,77,.08);
    clip-path:polygon(7px 0,100% 0,100% calc(100% - 7px),calc(100% - 7px) 100%,0 100%,0 7px);}
  .acc-badge::before{content:"";width:6px;height:6px;transform:rotate(45deg);background:#ffd34d;
    box-shadow:0 0 9px rgba(255,211,77,.9);}
  .acc-card h2{font-family:'Bebas Neue',sans-serif;font-size:38px;letter-spacing:.16em;font-weight:800;
    color:#fff;margin:18px 0 10px;line-height:1;}
  .acc-card .lead{font-size:13px;line-height:1.65;color:rgba(255,255,255,.55);}
  .acc-count{display:flex;align-items:baseline;gap:9px;margin-top:20px;padding:13px 16px;
    background:rgba(255,255,255,.035);border:1px solid rgba(255,255,255,.08);}
  .acc-count b{font-family:ui-monospace,"SF Mono",Menlo,monospace;font-size:19px;color:#fff;}
  .acc-count span{font-size:9px;letter-spacing:.2em;font-weight:700;color:rgba(255,255,255,.35);}
  .acc-form{padding:22px 32px 0;display:flex;flex-direction:column;gap:14px;}
  .acc-field label{display:block;font-size:9px;letter-spacing:.22em;font-weight:800;
    color:rgba(255,255,255,.45);margin-bottom:7px;}
  .acc-field input{width:100%;padding:12px 14px;font-family:inherit;font-size:13px;color:#fff;
    background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.14);outline:none;
    transition:border-color .15s,background .15s;}
  .acc-field input:focus{border-color:rgba(255,255,255,.5);background:rgba(255,255,255,.08);}
  .acc-field input::placeholder{color:rgba(255,255,255,.22);}
  .acc-field .sub{font-size:9px;letter-spacing:.05em;color:rgba(255,255,255,.28);margin-top:6px;font-weight:600;}
  .acc-msg{font-size:11px;line-height:1.6;font-weight:600;padding:11px 14px;display:none;}
  .acc-msg.on{display:block;}
  .acc-msg.err{color:#ff8a80;background:rgba(255,77,106,.1);border:1px solid rgba(255,77,106,.3);}
  .acc-msg.ok{color:#a8f0aa;background:rgba(126,224,129,.1);border:1px solid rgba(126,224,129,.3);}
  .acc-actions{padding:20px 32px 26px;display:flex;flex-direction:column;gap:10px;}
  .acc-btn{cursor:pointer;font-family:inherit;font-size:11px;letter-spacing:.24em;font-weight:800;
    padding:15px 18px;border:1px solid #fff;background:#fff;color:#000;transition:box-shadow .16s,transform .08s;
    clip-path:polygon(9px 0,100% 0,100% calc(100% - 9px),calc(100% - 9px) 100%,0 100%,0 9px);}
  .acc-btn:hover{box-shadow:0 0 30px rgba(255,255,255,.36);}
  .acc-btn:active{transform:translateY(1px);}
  .acc-btn:disabled{opacity:.4;cursor:not-allowed;box-shadow:none;}
  .acc-btn.ghost{background:transparent;color:rgba(255,255,255,.75);border-color:rgba(255,255,255,.2);}
  .acc-btn.ghost:hover{background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.45);color:#fff;}
  .acc-alt{text-align:center;font-size:10px;letter-spacing:.1em;color:rgba(255,255,255,.3);font-weight:600;}
  .acc-alt button{background:none;border:none;cursor:pointer;font:inherit;color:rgba(255,255,255,.75);
    text-decoration:underline;text-underline-offset:3px;padding:0;}
  .acc-alt button:hover{color:#fff;}
  .acc-close{position:absolute;top:16px;right:18px;background:none;border:none;cursor:pointer;
    font-size:20px;line-height:1;color:rgba(255,255,255,.3);padding:6px;}
  .acc-close:hover{color:#fff;}
  .acc-pane{display:none;} .acc-pane.on{display:block;}
  @media (max-width:560px){
    .acc-top,.acc-form,.acc-actions{padding-left:20px;padding-right:20px;}
    .acc-card h2{font-size:30px;}
  }

  /* ---------------- settings ---------------- */
  #settings{position:absolute;inset:0;display:none;align-items:center;justify-content:center;
    z-index:45;background:rgba(3,5,7,.84);backdrop-filter:blur(7px);padding:22px;}
  #settings.on{display:flex;}
  .set-card{width:min(700px,100%);max-height:min(88vh,780px);display:flex;flex-direction:column;
    background:linear-gradient(180deg,rgba(21,27,33,.99),rgba(10,13,17,.99));
    border:1px solid rgba(255,255,255,.13);
    clip-path:polygon(15px 0,100% 0,100% calc(100% - 15px),calc(100% - 15px) 100%,0 100%,0 15px);
    box-shadow:0 46px 100px -34px rgba(0,0,0,.95);}
  .set-head{display:flex;align-items:baseline;justify-content:space-between;gap:16px;
    padding:22px 28px 14px;}
  .set-head h2{font-family:'Bebas Neue',sans-serif;font-size:30px;letter-spacing:.24em;font-weight:800;color:#fff;}
  .set-head .hintline{font-size:9px;letter-spacing:.22em;color:rgba(255,255,255,.3);font-weight:700;}
  .set-tabs{display:flex;gap:2px;padding:0 28px;border-bottom:1px solid rgba(255,255,255,.09);}
  .set-tab{cursor:pointer;font-family:inherit;font-size:10px;letter-spacing:.2em;font-weight:800;
    padding:11px 16px;color:rgba(255,255,255,.4);background:none;border:none;border-bottom:2px solid transparent;
    transition:color .14s,border-color .14s;}
  .set-tab:hover{color:rgba(255,255,255,.75);}
  .set-tab.on{color:#fff;border-bottom-color:#fff;}
  .set-body{overflow-y:auto;overscroll-behavior:contain;padding:6px 28px 22px;}
  .set-pane{display:none;}
  .set-pane.on{display:block;}
  .set-row{display:grid;grid-template-columns:1fr 190px 46px;align-items:center;gap:18px;
    padding:13px 0;border-bottom:1px solid rgba(255,255,255,.055);}
  .set-row:last-child{border-bottom:none;}
  .set-row .lbl{font-size:10.5px;letter-spacing:.16em;font-weight:800;color:rgba(255,255,255,.86);}
  .set-row .hint{display:block;font-size:9px;letter-spacing:.05em;font-weight:600;line-height:1.5;
    color:rgba(255,255,255,.3);margin-top:5px;text-transform:none;}
  .set-val{font-family:ui-monospace,"SF Mono",Menlo,monospace;font-size:11px;font-weight:700;
    text-align:right;color:#fff;}
  /* Native range chrome differs per engine and none of it matches the HUD. */
  .set-row input[type=range]{-webkit-appearance:none;appearance:none;width:100%;height:3px;
    background:rgba(255,255,255,.15);outline:none;cursor:pointer;}
  .set-row input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:13px;height:13px;
    background:#fff;cursor:pointer;box-shadow:0 0 12px rgba(255,255,255,.5);
    clip-path:polygon(50% 0,100% 50%,50% 100%,0 50%);}
  .set-row input[type=range]::-moz-range-thumb{width:13px;height:13px;background:#fff;border:none;
    cursor:pointer;box-shadow:0 0 12px rgba(255,255,255,.5);}
  .set-toggle{justify-self:stretch;cursor:pointer;position:relative;height:22px;border:1px solid rgba(255,255,255,.2);
    background:rgba(255,255,255,.05);transition:background .16s,border-color .16s;}
  .set-toggle::after{content:"";position:absolute;top:2px;left:2px;width:calc(50% - 3px);height:calc(100% - 4px);
    background:rgba(255,255,255,.28);transition:transform .16s cubic-bezier(.3,.8,.3,1),background .16s;}
  .set-toggle.on{border-color:rgba(255,255,255,.5);background:rgba(255,255,255,.1);}
  .set-toggle.on::after{transform:translateX(100%);background:#fff;}
  /* The knob is a ::after, which paints above earlier siblings — without a
     stacking context of their own the labels disappear underneath it. */
  .set-toggle span{position:absolute;top:0;z-index:1;line-height:20px;font-size:8px;letter-spacing:.16em;
    font-weight:800;width:50%;text-align:center;color:rgba(255,255,255,.45);}
  .set-toggle span.o{left:0;} .set-toggle span.i{right:0;}
  .set-toggle.on span.i{color:#000;} .set-toggle:not(.on) span.o{color:#fff;}
  .set-keys{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:8px;padding:16px 0 4px;}
  .set-keys div{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 12px;
    background:rgba(255,255,255,.035);border:1px solid rgba(255,255,255,.07);}
  .set-keys b{font-family:ui-monospace,Menlo,monospace;font-size:10px;font-weight:700;color:#fff;
    background:rgba(255,255,255,.09);padding:3px 8px;border-radius:2px;white-space:nowrap;}
  .set-keys span{font-size:9px;letter-spacing:.14em;font-weight:700;color:rgba(255,255,255,.5);}
  .set-note{font-size:9px;letter-spacing:.1em;line-height:1.7;color:rgba(255,255,255,.3);
    font-weight:600;padding-top:14px;text-transform:none;}
  .set-foot{display:flex;align-items:center;justify-content:space-between;gap:12px;
    padding:15px 28px;border-top:1px solid rgba(255,255,255,.09);}
  .set-foot .right{display:flex;gap:9px;}
  #fpsMeter{position:absolute;top:10px;right:12px;z-index:14;display:none;
    font-family:ui-monospace,"SF Mono",Menlo,monospace;font-size:11px;font-weight:700;
    letter-spacing:.06em;color:rgba(126,224,129,.9);text-shadow:0 0 10px rgba(0,0,0,.9);
    pointer-events:none;}
  #fpsMeter.on{display:block;}
  @media (max-width:560px){
    .set-row{grid-template-columns:1fr 120px 44px;gap:10px;}
    .set-head,.set-tabs,.set-body,.set-foot{padding-left:16px;padding-right:16px;}
  }

  /* ═══════════ ENTRANCE GATE ═══════════
     Black first, then a hex lattice ignites and dissolves outward while two
     blast doors part. The whole thing is ~2.6s and skippable; a returning
     visitor gets the short form. */
  #gate{position:fixed;inset:0;z-index:60;background:#000;overflow:hidden;
    display:flex;align-items:center;justify-content:center;
    opacity:1;transition:opacity .95s cubic-bezier(.4,0,.2,1) .3s;}
  /* The hand-off: the gate cross-fades over the parting doors rather than being
     switched off the instant they finish. That hard cut was the seam. */
  #gate.opening{opacity:0;}
  #gate.done{display:none;}
  #gate.hurry{transition-duration:.35s;transition-delay:0s;}

  /* Above the doors. The doors are the black the lattice sits on; painting them
     over it was why the hexes never showed. */
  #gateHex{position:absolute;inset:0;width:100%;height:100%;z-index:1;
    mask-image:radial-gradient(ellipse at center, #000 34%, transparent 78%);
    -webkit-mask-image:radial-gradient(ellipse at center, #000 34%, transparent 78%);}
  /* The lattice ignites on load and then breathes, so a slow connection still
     has something alive on screen. It only blows outward once we open — the
     dissolve is the exit, not part of the loop. */
  #gateHex polygon{
    fill:none;stroke:#ffffff;stroke-width:1.1;
    opacity:0;transform-box:fill-box;transform-origin:center;
    animation:hexIn .5s ease-out forwards, hexPulse 3.4s ease-in-out infinite;
    animation-delay:var(--d), calc(var(--d) + .5s);
  }
  #gate.opening #gateHex polygon{
    animation:hexOut .85s ease-in forwards;
    animation-delay:calc(var(--d) * .55);
  }
  #gate.hurry #gateHex polygon{animation-duration:.3s;animation-delay:0s;}
  @keyframes hexPulse{
    0%,100%{opacity:var(--o,.4);}
    50%    {opacity:calc(var(--o,.4) * .45);}
  }
  @keyframes hexIn{
    from{opacity:0;transform:scale(.55);}
    to  {opacity:var(--o,.4);transform:scale(1);}
  }
  @keyframes hexOut{
    to{opacity:0;transform:scale(1.9);}
  }

  /* blast doors */
  .gate-doors{position:absolute;inset:0;pointer-events:none;z-index:0;}
  /* Doors are transition-driven, not timed: they part when the scene reports it
     is drawable, so the wait is hidden behind the choreography instead of the
     page popping in mid-animation. */
  /* Flat #000 on both halves and no border: any gradient or hairline at the
     meeting edge shows up as a seam down the middle of the screen while the
     doors are shut. They must be invisible until they move. */
  .gate-doors .gd{position:absolute;top:0;bottom:0;width:50.5%;
    background:#000;border:0;
    transform:translateX(0);
    transition:transform 1.15s cubic-bezier(.65,0,.2,1);}
  .gate-doors .gd.left{left:0;}
  .gate-doors .gd.right{right:0;}
  #gate.opening .gate-doors .gd.left { transform:translateX(-101%); }
  #gate.opening .gate-doors .gd.right{ transform:translateX(101%); }
  #gate.hurry .gate-doors .gd{transition-duration:.4s;}

  /* centre stack */
  /* Stays put for the whole load — it carries the progress bar, so it cannot be
     on a timer. It leaves only when the doors do. */
  .gate-core{position:relative;z-index:2;text-align:center;
    opacity:1;transform:none;filter:blur(0);
    transition:opacity .5s ease-in, transform .6s ease-in, filter .5s ease-in;}
  #gate.opening .gate-core{opacity:0;transform:scale(1.14);filter:blur(3px);}
  #gate.hurry .gate-core{transition-duration:.2s;}
  /* No logo mark here — the hex lattice already carries the identity, and the
     wordmark is enough. Leads sooner without it. */
  .gate-word{font-family:'Bebas Neue',sans-serif;font-size:clamp(34px,7vw,62px);
    letter-spacing:.5em;text-indent:.5em;color:#fff;opacity:0;
    animation:wordIn 1s cubic-bezier(.2,.7,.3,1) forwards;animation-delay:.15s;}
  .gate-sub{margin-top:10px;font-size:10px;letter-spacing:.42em;
    color:rgba(255,255,255,.42);opacity:0;
    animation:coreIn .7s ease-out forwards;animation-delay:.55s;}
  @keyframes coreIn{to{opacity:1;transform:none;}}
  @keyframes wordIn{
    from{opacity:0;letter-spacing:.95em;filter:blur(6px);}
    to  {opacity:1;letter-spacing:.5em;filter:blur(0);}
  }
  .gate-bar{width:190px;height:2px;margin:26px auto 0;background:rgba(255,255,255,.13);
    overflow:hidden;opacity:0;animation:coreIn .4s ease-out forwards;animation-delay:.65s;}
  /* Width is set by gateProgress() from real milestones — no keyframe here, or
     the bar would race ahead of the work it claims to represent. */
  .gate-bar i{display:block;height:100%;width:0;background:#fff;
    transition:width .45s cubic-bezier(.4,0,.2,1);}
  .gate-status{margin-top:12px;font-family:ui-monospace,monospace;font-size:9px;
    letter-spacing:.3em;color:rgba(255,255,255,.3);opacity:0;
    animation:coreIn .4s ease-out forwards;animation-delay:.75s;}

  .gate-skip{position:absolute;right:22px;bottom:20px;z-index:3;
    background:none;border:1px solid rgba(255,255,255,.22);color:rgba(255,255,255,.5);
    font-family:inherit;font-size:10px;letter-spacing:.24em;padding:8px 16px;
    border-radius:999px;cursor:pointer;opacity:0;
    animation:coreIn .4s ease-out forwards;animation-delay:.6s;}
  .gate-skip:hover{color:#fff;border-color:#fff;}

  /* A visitor who has already been through it gets the doors and nothing else. */

  @media (prefers-reduced-motion: reduce){
    #gate{animation:none;}
    #gate .gate-doors .gd, #gate #gateHex polygon,
    #gate .gate-core, #gate .gate-word,
    #gate .gate-sub, #gate .gate-bar i, #gate .gate-status, #gate .gate-skip{
      animation:none !important;opacity:1;transform:none;}
    #gate .gate-doors{display:none;}
  }

  #boot{position:absolute;inset:0;display:flex;flex-direction:column;gap:var(--s5);align-items:center;justify-content:center;z-index:30;background:#05070a;
    font-family:var(--f-mono);font-size:var(--l-md);letter-spacing:.35em;color:var(--ink-4);}
  #boot img{width:72px;height:auto;opacity:.85;filter:drop-shadow(0 0 24px rgba(255,255,255,.25));}
  #boot.hide{display:none;}
  #err{position:absolute;left:0;right:0;bottom:0;max-height:42%;overflow:auto;z-index:99;
    background:rgba(70,0,0,.94);color:#ffd9d5;font-family:var(--f-mono);font-size:11px;line-height:1.55;
    padding:10px 14px;display:none;white-space:pre-wrap;}

  /* ──── SOL REWARD SYSTEM ────
     This was the only rounded, chromatic, gradient-text island in a chamfered
     monochrome build, and the balance itself was gradient-clipped — the bottom
     half of every digit faded toward #14F195 and lost contrast against the
     scene behind it. Gradient text is the wrong tool for a number the player is
     trying to read under fire. The Solana purple/green survives on the coin
     mark, which is the one place it is actually identifying Solana. */
  #solBar{position:absolute;left:50%;bottom:26px;transform:translateX(-50%);
    display:flex;align-items:center;gap:10px;
    padding:7px 16px;border-radius:0;clip-path:var(--cut-sm);
    background:rgba(6,9,12,.62);
    border:1px solid var(--hair);}
  #solBar .sol-icon{width:20px;height:20px;border-radius:50%;
    background:linear-gradient(135deg,#9945FF,#14F195);
    display:flex;align-items:center;justify-content:center;
    font-weight:900;font-size:11px;color:#fff;flex-shrink:0;
    box-shadow:0 0 12px rgba(153,69,255,.4);}
  #solBal{font-family:var(--f-mono);
    font-size:var(--n-sm);font-weight:700;letter-spacing:.02em;color:var(--ink);}
  #solLabel{font-family:var(--f-mono);font-size:var(--l-xs);letter-spacing:.22em;font-weight:700;
    color:var(--ink-4);text-transform:uppercase;margin-top:1px;}

  /* ──── BANK METER ────
     Your stake is your life bar, so it reads like one: a fraction of what you
     started with rather than an absolute figure. Full width is the starting
     pool; overfilling past it means you are up on the match — which, because
     the bounty is a percentage of what you hold, also means you are now the
     most expensive head in the yard. */
  /* The meter carries the state; the palette carries the meaning. Amber is the
     stake you are holding, --alert is the recoverable warning, --red is bust,
     --live is settled profit. Same four meanings as everywhere else. */
  #bankWrap{display:flex;flex-direction:column;gap:3px;min-width:96px;}
  #bankTrack{position:relative;height:5px;overflow:hidden;
    background:rgba(255,255,255,.14);}
  .bank-meter{position:absolute;left:0;top:0;height:100%;width:100%;
    background:var(--amber);
    transition:width .28s cubic-bezier(.2,.8,.3,1),background .28s;}
  .bank-meter.low{background:var(--alert);}
  .bank-meter.bust{background:var(--red);}
  .bank-meter.up{background:var(--live);box-shadow:0 0 10px rgba(126,224,129,.55);}
  .bank-tag{font-family:var(--f-mono);
    font-size:var(--l-sm);font-weight:700;letter-spacing:.04em;color:var(--ink-4);}
  .bank-tag.up{color:var(--live);}
  .bank-tag.down{color:var(--red-soft);}

  /* floating reward popup.
     Moved off the kill axis. At top:42% dead centre it landed on the same
     column as the hitmarker and the streak banner at 26%, so a reward read as
     combat feedback. Anchored to the right edge it reads as economy. */
  #solPopups{position:absolute;right:26px;top:34%;width:0;height:0;pointer-events:none;z-index:10;}
  .sol-popup{position:absolute;right:0;top:0;text-align:right;
    font-family:var(--f-mono);
    font-size:17px;font-weight:800;letter-spacing:.04em;font-variant-numeric:tabular-nums;
    text-shadow:0 1px 0 rgba(0,0,0,.95),0 -1px 0 rgba(0,0,0,.95),
                1px 0 0 rgba(0,0,0,.95),-1px 0 0 rgba(0,0,0,.95),0 2px 12px rgba(0,0,0,.9);
    white-space:nowrap;pointer-events:none;
    animation:solFloat 1.6s ease-out forwards;}
  .sol-popup.gain{color:var(--amber);}
  .sol-popup.loss{color:var(--red-soft);}
  .sol-popup .sol-sub{display:block;font-size:var(--l-sm);letter-spacing:.12em;font-weight:600;
    text-align:right;margin-top:2px;color:var(--ink-4);}
  @keyframes solFloat{
    0%{opacity:1;transform:translate(0,0) scale(1.1);}
    20%{opacity:1;transform:translate(0,-20px) scale(1);}
    100%{opacity:0;transform:translate(0,-70px) scale(.85);}
  }

  /* end screen SOL earnings — S5, the value plate. The one chromatic fill in
     the product, spent on the number the player just won or lost. */
  #solEarnings{margin-top:18px;padding:var(--s4) var(--s6);border-radius:0;clip-path:var(--cut-md);
    background:linear-gradient(180deg,var(--amber-wash),rgba(255,211,77,.02));
    border:1px solid rgba(255,211,77,.30);box-shadow:var(--e1);
    display:flex;align-items:center;gap:16px;justify-content:center;}
  #solEarnings .sol-icon-lg{width:34px;height:34px;border-radius:50%;
    background:linear-gradient(135deg,#9945FF,#14F195);
    display:flex;align-items:center;justify-content:center;
    font-weight:900;font-size:15px;color:#fff;flex-shrink:0;
    box-shadow:0 0 18px rgba(153,69,255,.5);}
  #solEarnings .sol-total{font-family:var(--f-mono);
    font-size:var(--n-lg);font-weight:800;font-variant-numeric:tabular-nums;
    background:none;-webkit-text-fill-color:var(--live);color:var(--live);}
  #solEarnings .sol-total.negative{
    background:none;-webkit-text-fill-color:var(--red);color:var(--red);}
  #solEarnings .sol-detail{font-family:var(--f-mono);font-size:var(--l-sm);letter-spacing:.14em;font-weight:600;
    color:var(--ink-3);text-align:left;line-height:1.7;}

  /* ── the HUD on a touchscreen ────────────────────────────────────────────
     No media query in this file touched a single in-game selector before this
     one. On a 667×375 landscape phone the stick sat directly on the HP number
     and the button grid sat directly on the magazine counter — every readout
     the player needs was behind their own thumbs. The bottom third of the
     screen now belongs to the controls and nothing that has to be read lives
     there; the readouts move to the top band, and the three panels that cannot
     earn their space on a phone go away entirely. */
  @media (pointer:coarse){
    #mapWrap,#comms,#slots,#wicon{display:none;}

    #vitals{bottom:auto;
      top:max(10px,calc(env(safe-area-inset-top) + 8px));
      left:max(70px,calc(env(safe-area-inset-left) + 66px));
      width:120px;}
    #vitals::before{--ox:0%;--oy:0%;}
    #hpNum{font-size:24px;}
    #vitals .sub{margin-top:5px;}

    #ammo{bottom:auto;min-width:0;
      top:max(10px,calc(env(safe-area-inset-top) + 8px));
      right:max(16px,calc(env(safe-area-inset-right) + 12px));}
    #ammo::before{--oy:0%;}
    #magNum{font-size:24px;}
    #resNum{font-size:13px;padding-bottom:2px;}
    #wname{font-size:var(--l-sm);letter-spacing:.18em;}
    #wmode{display:none;}
    #reloadHint{font-size:var(--l-xs);letter-spacing:.16em;margin-top:3px;}

    #top{top:max(10px,calc(env(safe-area-inset-top) + 8px));max-width:38vw;}
    #timer{font-size:21px;}
    #objective{font-size:var(--l-xs);letter-spacing:.16em;margin-top:3px;}

    #feed{top:max(76px,calc(env(safe-area-inset-top) + 72px));
      right:max(16px,calc(env(safe-area-inset-right) + 12px));}
    .kf{font-size:var(--l-sm);padding:4px 8px;}

    /* The stake follows the readouts up: bottom-centre is the gap between the
       stick and the button grid, and in portrait there is no gap at all. */
    /* max-content, because a shrink-to-fit box at left:50% is capped at half
       the viewport and the stake label wraps mid-word on a phone. */
    #solBar{bottom:auto;top:max(70px,calc(env(safe-area-inset-top) + 66px));
      padding:5px 12px;gap:8px;width:max-content;max-width:calc(100vw - 24px);}
    #solPopups{top:46%;right:max(16px,calc(env(safe-area-inset-right) + 12px));}
    .sol-popup{font-size:15px;}

    #scoreboard{min-width:0;width:min(460px,92vw);padding:var(--s4);}
    #spectateTag{font-size:var(--d-sm);}
  }
  /* A landscape phone is 320–390px tall and the killfeed shares the right-hand
     column with the thumb grid. Two rows, not five — the newest two: this is a
     recent-events strip, and rows three to five were sitting on the fire
     button. The grid tightens to match. */
  @media (pointer:coarse) and (max-height:430px){
    #feed > *:nth-last-child(n+3){display:none;}
    .kf{padding:3px 8px;}
    .tbtn{min-height:52px;}
    #tButtons{bottom:max(14px,calc(env(safe-area-inset-bottom) + 12px));}
    #tStick{bottom:max(18px,calc(env(safe-area-inset-bottom) + 14px));}
  }
  /* Portrait has height to spare and no width at all, so the top band stops
     being a row and becomes a stack. Laid out side by side at 390px the vitals
     block and the clock overlap by 50px, and the killfeed lands on the stake. */
  @media (pointer:coarse) and (orientation:portrait){
    #vitals{width:min(46vw,180px);
      top:max(62px,calc(env(safe-area-inset-top) + 58px));
      left:max(16px,calc(env(safe-area-inset-left) + 12px));}
    #solBar{top:max(142px,calc(env(safe-area-inset-top) + 138px));}
    #feed{top:max(190px,calc(env(safe-area-inset-top) + 186px));}
  }

  /* ──── MONOCHROME LANDING OVERLAY STYLES ──── */
  /* No backdrop-filter. It blurred a live WebGL scene — the most persuasive
     asset the product has — to make room for a static JPEG of the same game,
     and it forced a full-screen blur pass on every canvas frame and every
     scroll frame. A positional scrim does the legibility work for nothing:
     light at the top where the yard should read through, opaque by the time
     the long-form copy starts. `absolute`, not fixed — the overlay is itself
     the scroll container, and a fixed scrim would not stay put inside it. */
  #landingOverlay {
    position: absolute; inset: 0; z-index: 25;
    overflow-y: auto;
    background: linear-gradient(180deg, rgba(4,6,10,.22) 0, rgba(4,6,10,.55) 42vh,
                                        rgba(4,6,10,.93) 88vh, #04060a 120vh);
    transition: opacity var(--t-slow) var(--ease), transform var(--t-slow) var(--ease);
  }
  #landingOverlay.hide {
    opacity: 0; pointer-events: none; transform: scale(1.05);
  }

  /* ──── ONE GUTTER, ONE MEASURE ────────────────────────────────────────────
     Six sections were `max-width:1100px; margin:0 auto` with no side padding,
     so below 1100px every card touched the bezel — and on desktop the nav
     wordmark sat 24px inside its wrapper while the section cards below started
     at 0. The whole page was off by exactly one gutter. Both values are
     written here and nowhere else; the sections below carry block padding only. */
  .landing-nav .container,
  .status-banner .container,
  .hero-landing .container,
  .how-sec, .modes-sec, .roadmap-sec, .faq-sec,
  .vision-grid, .wp-landing, .leaderboard-sec,
  .landing-footer .container{
    max-width: var(--measure-w); margin-inline: auto; padding-inline: var(--gutter);
  }

  /* Navbar */
  /* The one blur that survives: the nav is small, it is the only element that
     has to separate from whatever scrolls under it, and it does not cover the
     canvas. */
  .landing-nav {
    position: sticky; top: 0; z-index: 100;
    padding: var(--s5) 0; background: rgba(5, 5, 5, 0.85);
    border-bottom: 1px solid var(--hair);
    backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  }
  .landing-nav .container {
    display: flex; align-items: center; justify-content: space-between; gap: var(--s5);
  }
  .landing-logo { display: flex; align-items: center; gap: var(--s3); text-decoration: none; flex-shrink: 0; }
  .landing-logo .logo-icon {
    width: 36px; height: 36px; border-radius: 10px;
    object-fit: contain; display: block;
    box-shadow: 0 0 20px rgba(255, 255, 255, 0.25);
  }
  .landing-logo .logo-text {
    font-family: var(--f-display); font-weight: 800; font-size: var(--d-sm);
    letter-spacing: 0.18em; color: var(--ink);
  }
  .landing-nav-links { display: flex; gap: var(--s6); list-style: none; flex-shrink: 0; }
  /* Hidden on desktop; the breakpoint below turns it on. */
  .nav-toggle {
    display: none; align-items: center; justify-content: center;
    flex-direction: column; gap: 4px; width: 40px; height: 40px;
    background: none; border: 1px solid var(--hair-lit); cursor: pointer;
    clip-path: var(--cut-sm, none); flex-shrink: 0;
  }
  .nav-toggle i {
    display: block; width: 17px; height: 1.5px; background: var(--ink);
    transition: transform .22s ease, opacity .18s ease;
  }
  .nav-toggle[aria-expanded="true"] i:nth-child(1){ transform: translateY(5.5px) rotate(45deg); }
  .nav-toggle[aria-expanded="true"] i:nth-child(2){ opacity: 0; }
  .nav-toggle[aria-expanded="true"] i:nth-child(3){ transform: translateY(-5.5px) rotate(-45deg); }
  .landing-nav-links a { white-space: nowrap; }
  .landing-nav-links a {
    color: var(--ink-3); text-decoration: none;
    font-size: var(--l-lg); font-weight: 600; letter-spacing: 0.08em;
    text-transform: uppercase; transition: color var(--t-fast) var(--ease);
  }
  .landing-nav-links a:hover { color: var(--ink); }
  .landing-nav-links a.active { color: var(--ink); }

  /* Wallet Button & Connected HUD */
  .btn-connect-landing {
    display: inline-flex; align-items: center; gap: var(--s2);
    padding: 10px 20px; border: 1px solid rgba(255, 255, 255, 0.4);
    border-radius: 10px; background: rgba(255, 255, 255, 0.1);
    color: var(--ink); font-family: inherit; font-size: var(--l-lg); font-weight: 600;
    cursor: pointer; transition: background var(--t-fast) var(--ease),
                                 border-color var(--t-fast) var(--ease),
                                 transform var(--t-press) var(--ease);
  }
  /* The chamfer clips box-shadow, so the old glow here rendered nothing at all.
     A 1px rise is honest and composites. */
  .btn-connect-landing:hover {
    background: rgba(255, 255, 255, 0.2); border-color: var(--ink);
    transform: translateY(-1px);
  }
  .wallet-connected-landing {
    display: none; align-items: center; gap: 10px; flex-shrink: 0; white-space: nowrap;
    padding: var(--s2) var(--s4); border-radius: 10px;
    background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.2);
  }
  .wallet-connected-landing.show { display: flex; }
  .wallet-connected-landing .wallet-bal { font-family: var(--f-mono); font-size: var(--l-lg);
    font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
  .wallet-connected-landing .wallet-addr { font-family: var(--f-mono); font-size: var(--l-lg); color: var(--ink-4); }
  .wallet-disconnect {
    background: none; border: none; cursor: pointer; font-family: inherit;
    font-size: var(--l-sm); letter-spacing: .12em; color: var(--ink-4);
    padding: 2px 4px; margin-left: var(--s1);
  }
  .wallet-disconnect:hover { color: var(--red); }

  /* Hero Section */
  .hero-landing {
    padding-block: var(--s9) var(--s8);
  }
  .hero-grid { display: grid; grid-template-columns: 1.1fr 0.9fr; gap: var(--s7); align-items: center; }
  /* Tracking is inverse to size. At .25em the headline measured wider than its
     own column, so line one wrapped and line two did not — and it read as
     texture rather than as words, which lost the scan-order fight to the JPEG
     beside it. */
  .hero-landing h1 {
    font-family: var(--f-display); font-size: var(--d-hero);
    font-weight: 900; line-height: .90; letter-spacing: .09em; margin-bottom: var(--s5);
    color: var(--ink);
  }
  .hero-landing h1 span { display: block; }
  .hero-landing h1 .accent-text { color: var(--ink-3); }
  .hero-desc-landing {
    font-size: var(--t-lg); line-height: 1.65; color: var(--ink-2);
    max-width: var(--measure); margin-bottom: var(--s6);
  }
  .hero-desc-landing strong { color: var(--ink); font-weight: 600; }

  /* The outer box-shadow this used to carry never painted — the tactical layer
     chamfers this button, and clip-path clips a shadow along with everything
     else outside the polygon. Depth is an inset bevel; the lift lives on the
     unclipped .cta-frame wrapper, where drop-shadow() follows the cut corners. */
  .btn-play-landing {
    display: inline-flex; align-items: center; gap: 10px;
    padding: var(--s4) 36px; border-radius: 12px;
    background: var(--ink); color: #000000;
    font-family: inherit; font-size: var(--t-lg); font-weight: 800;
    cursor: pointer; border: none;
    box-shadow: inset 0 -3px 0 rgba(0,0,0,.22);
    transition: transform var(--t-press) var(--ease);
  }
  .btn-play-landing:hover { transform: translateY(-1px); }
  /* inline-flex, not inline-block: an inline-block wrapper sits on the baseline
     and adds descender space under the button, which pushes it off centre from
     the secondary CTA beside it. */
  .cta-frame { display: inline-flex; transition: filter var(--t-fast) var(--ease); }
  .cta-frame:hover { filter: var(--lift-white); }

  /* ═══════════ routed overlays: lobby / queue / match-found / result ═══════════
     The lobby predates the tactical layer and never received it: pill radii, six
     different panel fills, and two lists with no CSS at all. It is rebuilt here
     on the surfaces the landing already uses — S2 plate, S3 document, S4 sheet —
     plus one the landing has no use for: S5, the value plate. S5 appears exactly
     four times in the whole product (stake modal, queue rail, match-found pot,
     result receipt) and always on the number the player is risking or receiving.
     Its power is its scarcity; the moment it also marks "you are broke" it stops
     meaning anything. */
  .route-overlay{
    position:fixed;inset:0;z-index:26;display:none;overflow-y:auto;
    /* The blur is gone: it cost a full-screen pass on every frame the WebGL
       scene rendered underneath, for separation a scrim can buy outright. It
       was doing something at .93, though — the scene still resolved into
       readable containers behind the panels — so the alpha absorbs the job the
       blur was doing. Depth without a legible second image to read. */
    background:rgba(4,5,7,.97);
  }
  .route-overlay.show{display:block;}
  /* align-items:center clips the top of anything taller than the viewport, which
     the result screen is on a short window. auto margins centre without clipping. */
  .route-overlay.center.show{display:flex;align-items:flex-start;justify-content:center;
    padding:var(--s5) var(--s4);}

  .lobby-shell{max-width:1180px;margin:0 auto;padding:var(--s5) var(--gutter) var(--s8);}
  .lobby-head{display:flex;align-items:center;justify-content:space-between;gap:var(--s4);
    padding-bottom:var(--s4);border-bottom:1px solid var(--hair);}
  .lobby-head-right{display:flex;align-items:center;gap:var(--s4);}
  .lobby-addr{font-family:var(--f-mono);font-size:var(--l-md);letter-spacing:.06em;color:var(--ink-4);}

  .lobby-grid{display:grid;grid-template-columns:280px minmax(0,1fr) 280px;
    gap:var(--s5);margin-top:var(--s5);align-items:start;}

  /* S2 — plate. Same fill, hairline, inset and chamfer as the landing cards. */
  .lobby-panel{
    background:linear-gradient(180deg,rgba(255,255,255,.04),transparent 90px), rgba(9,11,14,.76);
    border:1px solid var(--hair);border-radius:0;box-shadow:var(--e1);
    clip-path:var(--cut-lg);padding:var(--s5);
  }
  .panel-h{font-family:var(--f-mono);font-size:var(--l-sm);letter-spacing:.22em;
    font-weight:700;text-transform:uppercase;color:var(--ink-4);}
  .panel-h.mt{margin-top:var(--s6);}

  /* The balance sat at position nine of eleven, below a chart, in plain white,
     while the header carried a second copy of it. It is the figure a player
     opens the lobby to read: one copy, at the top, in the value colour. */
  .prof-bal{margin:var(--s4) 0 var(--s3);padding:var(--s3) 0;
    border-top:1px solid var(--hair);border-bottom:1px solid var(--hair);}
  .prof-bal span{display:block;font-family:var(--f-mono);font-size:var(--l-xs);
    letter-spacing:.28em;text-transform:uppercase;color:var(--ink-4);}
  .prof-bal b{display:block;margin-top:var(--s2);font-family:var(--f-mono);
    font-size:var(--n-lg);font-weight:800;line-height:1;color:var(--amber);
    font-variant-numeric:tabular-nums;}
  .prof-actions{display:flex;gap:var(--s2);}
  .prof-note{margin-top:var(--s3);font-size:var(--t-sm);line-height:1.6;color:var(--ink-3);}

  .btn-sm{flex:1;padding:12px;border:none;border-radius:0;clip-path:var(--cut-md);
    cursor:pointer;background:var(--ink);color:#000;font-family:var(--f-mono);
    font-weight:800;font-size:var(--l-md);letter-spacing:.16em;text-transform:uppercase;
    transition:transform var(--t-press) var(--ease),background var(--t-fast) var(--ease),
      border-color var(--t-fast) var(--ease);}
  .btn-sm:hover{transform:translateY(-1px);}
  .btn-sm:active{transform:translateY(1px);}
  .btn-sm.ghost{background:transparent;color:var(--ink);border:1px solid var(--hair-lit);}
  .btn-sm.ghost:hover{background:rgba(255,255,255,.06);border-color:var(--ink);}
  .btn-sm:disabled{opacity:.4;cursor:not-allowed;transform:none;}
  /* clip-path clips an outline exactly as it clips a shadow, so every chamfered
     control in this flow takes the inset ring instead. */
  .btn-sm:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--amber);}

  .prof-badge{display:flex;align-items:center;gap:var(--s2);margin-top:var(--s4);}
  .badge-chip{font-family:var(--f-mono);font-size:var(--l-sm);font-weight:800;
    letter-spacing:.22em;padding:5px 10px;border-radius:0;clip-path:var(--cut-sm);
    border:1px solid currentColor;}
  .badge-level{font-family:var(--f-display);font-size:var(--d-sm);letter-spacing:.20em;
    line-height:1;color:var(--ink-2);}
  .lvl-bar{height:4px;margin-top:var(--s3);border-radius:0;
    background:rgba(255,255,255,.08);overflow:hidden;}
  .lvl-bar i{display:block;height:100%;width:0;border-radius:0;transition:width var(--t-slow) var(--ease);}
  .lvl-note{margin:var(--s2) 0 var(--s4);font-family:var(--f-mono);font-size:var(--l-xs);
    letter-spacing:.20em;color:var(--ink-4);}

  /* ELO and K/D are the two figures a player actually checks. They get the same
     1px-gap-on-a-background construction the debrief tiles already use rather
     than two more rows of an eight-row list. */
  .prof-kpi{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--hair);
    border:1px solid var(--hair);margin:0 0 var(--s4);}
  .prof-kpi div{background:rgba(9,11,14,.92);padding:var(--s3) var(--s2);text-align:center;}
  .prof-kpi b{display:block;font-family:var(--f-mono);font-size:var(--n-md);font-weight:800;
    line-height:1;color:var(--ink);font-variant-numeric:tabular-nums;}
  .prof-kpi span{display:block;margin-top:6px;font-family:var(--f-mono);font-size:var(--l-xs);
    letter-spacing:.28em;color:var(--ink-4);}

  .prof-stats{list-style:none;padding:0;margin:0 0 var(--s4);display:grid;gap:1px;}
  .prof-stats li{display:flex;justify-content:space-between;align-items:baseline;gap:var(--s3);
    padding:7px 0;border-bottom:1px solid var(--hair-dim);font-family:var(--f-mono);}
  .prof-stats span{font-size:var(--l-sm);letter-spacing:.18em;text-transform:uppercase;color:var(--ink-4);}
  .prof-stats b{font-size:var(--l-md);color:var(--ink-2);font-variant-numeric:tabular-nums;}

  /* earnings figure — one series, so the heading names it and there is no legend */
  .earn-fig{margin:var(--s3) 0 0;}
  .earn-hero{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s2);margin-bottom:6px;}
  .earn-hero b{font-family:var(--f-mono);font-size:var(--n-md);font-weight:800;
    line-height:1;white-space:nowrap;font-variant-numeric:tabular-nums;}
  .earn-hero span{font-family:var(--f-mono);font-size:var(--l-sm);letter-spacing:.16em;color:var(--ink-4);}
  .earn-chart{position:relative;}
  .earn-chart svg{width:100%;height:auto;display:block;touch-action:none;}
  .earn-tip{position:absolute;top:-4px;transform:translateX(-50%);
    background:rgba(6,7,9,.96);border:1px solid var(--hair-lit);border-radius:0;
    clip-path:var(--cut-sm);padding:5px 9px;font-family:var(--f-mono);
    font-size:var(--l-sm);letter-spacing:.04em;color:var(--ink-2);white-space:nowrap;
    font-variant-numeric:tabular-nums;opacity:0;pointer-events:none;
    transition:opacity var(--t-fast) var(--ease);}
  .earn-tip.on{opacity:1;}
  .earn-fig figcaption{margin-top:var(--s2);font-size:var(--l-sm);line-height:1.55;
    letter-spacing:.02em;color:var(--ink-4);}

  /* The leaderboard and the history list shipped with no CSS whatsoever: browser
     default <ol>, decimal markers, 40px indent, 16px body prose, inside a panel
     where everything else is tracked caps at ten to twelve pixels. */
  .mini-lb,.mini-hist{list-style:none;padding:0;margin:var(--s3) 0 0;display:grid;gap:1px;}
  .mini-lb li,.mini-hist li{display:flex;justify-content:space-between;align-items:baseline;
    gap:var(--s3);padding:7px 0;border-bottom:1px solid var(--hair-dim);
    font-family:var(--f-mono);font-size:var(--l-md);letter-spacing:.04em;
    color:var(--ink-2);font-variant-numeric:tabular-nums;}
  .mini-lb{counter-reset:rank;}
  .mini-lb li::before{counter-increment:rank;content:counter(rank,decimal-leading-zero);
    margin-right:var(--s2);font-size:var(--l-sm);color:var(--ink-4);}
  .mini-lb li:nth-child(1)::before{color:var(--amber);}
  .mini-lb li span,.mini-hist li span{min-width:0;margin-right:auto;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ink-3);}
  .mini-lb li b,.mini-hist li b{font-weight:700;color:var(--ink);}
  .mini-hist li b.pos{color:var(--live);}
  .mini-hist li b.neg{color:var(--red-soft);}
  /* Depends on the lists being cleared with textContent='' — append a wrapper
     element here in a future refactor and :empty silently stops matching. */
  .mini-lb:empty::after,.mini-hist:empty::after{content:"NO DATA";display:block;
    padding:var(--s4) 0;font-family:var(--f-mono);font-size:var(--l-xs);
    letter-spacing:.28em;color:var(--ink-4);}
  /* A dead server and a genuinely new player used to produce the same empty
     panel, because both fetches swallowed their errors. */
  .mini-lb li.list-err,.mini-hist li.list-err{display:block;border-bottom:0;
    font-size:var(--l-sm);letter-spacing:.22em;text-transform:uppercase;color:var(--alert);}

  .lobby-modes{display:grid;gap:var(--s4);align-content:start;}

  /* A refusal, shown where the click happened. Collapsed to nothing until it
     has something to say, so it never reserves empty space. */
  .lobby-msg{display:none;margin-bottom:var(--s3);padding:10px 14px;
    border-left:2px solid var(--alert);background:rgba(255,159,67,.10);
    color:var(--alert);font-size:12px;line-height:1.5;letter-spacing:.02em;}
  .lobby-msg.show{display:block;}

  /* A tier this account cannot enter yet. The card stays readable — the point
     is to say what unlocks it, not to hide that it exists. */
  .lobby-mode-card.locked{opacity:.62;}
  .lmc-lock{margin-top:6px;font-size:10px;letter-spacing:.14em;font-weight:700;
    text-transform:uppercase;color:var(--alert);}
  /* S2 plate plus a 2px rail. The staked card carries the value colour and the
     free card the safe colour — it was the other way round, so the mode that is
     the entire proposition of the product was the one with no identity. */
  .lobby-mode-card{
    background:linear-gradient(180deg,rgba(255,255,255,.04),transparent 90px), rgba(9,11,14,.76);
    border:1px solid var(--hair);border-left:2px solid rgba(255,211,77,.55);
    border-radius:0;box-shadow:var(--e1);clip-path:var(--cut-lg);padding:var(--s5);
    display:grid;grid-template-columns:minmax(0,1fr) auto;gap:var(--s4);align-items:center;}
  .lobby-mode-card.free{border-left-color:rgba(126,224,129,.55);}
  .lmc-name{font-family:var(--f-display);font-size:var(--d-sm);letter-spacing:.20em;
    line-height:1;color:var(--ink);}
  .lmc-desc{margin-top:6px;font-size:var(--t-sm);line-height:1.6;color:var(--ink-3);max-width:54ch;}
  /* The price used to be the smallest, dimmest text on the card, inside a run-on
     sentence. It is the decision the card exists to support. */
  .lmc-meta{margin-top:var(--s3);}
  .lmc-stake{display:flex;align-items:baseline;gap:6px;font-family:var(--f-mono);
    font-size:var(--n-lg);font-weight:800;line-height:1;color:var(--amber);
    font-variant-numeric:tabular-nums;}
  .lmc-stake span{font-size:var(--l-md);letter-spacing:.18em;font-weight:700;}
  .lobby-mode-card.free .lmc-stake{color:var(--live);}
  .lmc-terms{margin-top:6px;font-family:var(--f-mono);font-size:var(--l-md);
    letter-spacing:.18em;text-transform:uppercase;color:var(--ink-4);}
  .lmc-queued{margin-top:var(--s3);font-family:var(--f-mono);font-size:var(--l-sm);
    letter-spacing:.22em;text-transform:uppercase;color:var(--live);
    font-variant-numeric:tabular-nums;}
  .lmc-btn{padding:14px 22px;border:none;border-radius:0;clip-path:var(--cut-md);
    cursor:pointer;white-space:nowrap;background:var(--ink);color:#000;
    font-family:var(--f-mono);font-weight:800;font-size:var(--l-md);letter-spacing:.20em;
    text-transform:uppercase;transition:transform var(--t-press) var(--ease),
      background var(--t-fast) var(--ease),border-color var(--t-fast) var(--ease);}
  .lmc-btn:hover{transform:translateY(-1px);}
  .lmc-btn:active{transform:translateY(1px);}
  /* Amber at full saturation used to fire only when the player was too poor to
     play: the colour that means value everywhere else, spent on the blocked
     state. Outline, not slab — and the free button matches the landing's, which
     the tactical layer already de-slabbed. */
  .lmc-btn.deposit{background:transparent;color:var(--amber);border:1px solid rgba(255,211,77,.55);}
  .lmc-btn.deposit:hover{background:rgba(255,211,77,.12);border-color:var(--amber);}
  .lmc-btn.free{background:transparent;color:var(--live);border:1px solid rgba(126,224,129,.5);}
  .lmc-btn.free:hover{background:rgba(126,224,129,.12);border-color:var(--live);}
  .lmc-btn:disabled{opacity:.45;cursor:not-allowed;transform:none;}

  /* "Cannot reach the match server." was a bare text node dropped straight into
     the grid in default body sans, in the most important region of the lobby. */
  .lobby-empty{background:rgba(9,11,14,.6);border:1px dashed var(--hair);
    clip-path:var(--cut-lg);padding:var(--s6) var(--s5);text-align:center;}
  .lobby-empty b{display:block;font-family:var(--f-display);font-size:var(--d-md);
    letter-spacing:.22em;color:var(--ink-2);}
  .lobby-empty p{margin:var(--s3) auto var(--s5);max-width:46ch;font-size:var(--t-sm);
    line-height:1.7;color:var(--ink-3);}
  .lobby-empty .btn-sm{flex:0 0 auto;padding:12px var(--s5);}

  /* S3 — document. */
  .queue-box,.mf-box,.result-box{
    background:rgba(12,15,19,.90);border:1px solid var(--hair);box-shadow:var(--e2);
    border-radius:0;clip-path:var(--cut-lg);padding:var(--s6);text-align:center;
    width:100%;max-width:640px;margin:auto;}
  .queue-mode:focus,.mf-title:focus,.result-title:focus{outline:none;}

  /* The spinner is gone. An indeterminate spinner is the universal signal for
     "no information available", and the information exists — the server
     broadcasts the live queue on every join and every leave. Seats, filled from
     QUEUE_STATE, so the slot lights up when a real person arrives. */
  .queue-seats{display:flex;justify-content:center;gap:var(--s2);margin:var(--s5) 0;}
  .queue-seat{width:24px;height:32px;background:rgba(255,255,255,.05);
    border:1px solid var(--hair);clip-path:polygon(0 0,100% 0,100% 64%,50% 100%,0 64%);
    transition:background var(--t-base) var(--ease),border-color var(--t-base) var(--ease);}
  .queue-seat.on{background:var(--amber-wash);border-color:rgba(255,211,77,.55);}
  .queue-seat.you{background:rgba(255,211,77,.28);border-color:var(--amber);}
  .queue-mode{font-family:var(--f-display);font-size:var(--d-xl);letter-spacing:.16em;
    line-height:1;color:var(--ink);}
  .queue-live{display:flex;align-items:baseline;justify-content:center;gap:var(--s4);}
  .queue-count{font-family:var(--f-mono);font-size:var(--n-md);font-weight:800;
    letter-spacing:.04em;color:var(--ink);font-variant-numeric:tabular-nums;}
  .queue-count small{margin-left:6px;font-size:var(--l-sm);letter-spacing:.24em;
    font-weight:700;color:var(--ink-4);}
  .queue-timer{font-family:var(--f-mono);font-size:var(--l-md);letter-spacing:.16em;
    color:var(--ink-4);font-variant-numeric:tabular-nums;}

  /* S5 — the player confirmed a stake and then watched a screen that never
     mentioned money again for up to sixty seconds. */
  .queue-risk{display:flex;align-items:baseline;justify-content:space-between;gap:var(--s3);
    margin:var(--s5) 0 var(--s3);padding:var(--s3) var(--s4);text-align:left;
    background:linear-gradient(180deg,var(--amber-wash),rgba(255,211,77,.02));
    border:1px solid rgba(255,211,77,.30);clip-path:var(--cut-md);}
  .queue-risk span{font-family:var(--f-mono);font-size:var(--l-xs);letter-spacing:.28em;
    text-transform:uppercase;color:var(--ink-4);}
  .queue-risk b{font-family:var(--f-mono);font-size:var(--n-md);font-weight:800;
    line-height:1;color:var(--amber);font-variant-numeric:tabular-nums;}
  /* Determinate, and one style write for the whole wait: the fill is handed a
     transition over the full timeout and a single scaleX(0). No per-frame work,
     no second copy of the elapsed clock, and it answers the only question the
     player has — how long before the stake comes back. */
  .queue-refund{height:1px;background:var(--hair);overflow:hidden;}
  .queue-refund i{display:block;height:100%;background:var(--amber);
    transform:scaleX(1);transform-origin:left;}
  /* The global guard collapses every transition to .01ms, which would snap this
     to empty and tell the player their refund was seconds away. A determinate
     bar that cannot run is worse than no bar — and the timeout is stated in
     words directly below it, with a live elapsed clock above. */
  @media (prefers-reduced-motion:reduce){ .queue-refund{display:none;} }
  .queue-est{margin:var(--s3) 0 var(--s5);font-family:var(--f-mono);font-size:var(--l-md);
    letter-spacing:.06em;line-height:1.6;color:var(--ink-4);}
  /* A failure written into the status line in the status colour reads as status. */
  .queue-est.err{color:var(--alert);}

  /* Five seconds, the emotional peak of the flow, and it had no entrance at all
     — less choreography than the access-request modal. Both of these are
     one-shot and both base states are already visible, so the global
     reduced-motion guard collapses them to nothing without hiding anything. */
  .route-overlay.show .mf-box{animation:mfSlam 380ms var(--ease-in) both;}
  @keyframes mfSlam{from{opacity:0;transform:translateY(16px) scale(.985);}to{opacity:1;transform:none;}}
  .mf-title{font-family:var(--f-display);font-size:var(--d-xl);letter-spacing:.20em;
    line-height:1;color:var(--amber);}
  .route-overlay.show .mf-title{animation:mfTrack 620ms var(--ease-in) both;}
  @keyframes mfTrack{from{letter-spacing:.6em;opacity:0;}to{letter-spacing:.20em;opacity:1;}}
  .mf-mode{margin-top:var(--s2);font-family:var(--f-mono);font-size:var(--l-md);
    letter-spacing:.24em;text-transform:uppercase;color:var(--ink-4);}
  /* S5 — the pot. It used to be a clause inside a 12px muted line, set exactly
     like the mode name beside it, under a title smaller than the debrief's. */
  .mf-pot{display:inline-flex;flex-direction:column;align-items:center;gap:3px;
    margin:var(--s4) 0 0;padding:var(--s3) var(--s6);
    background:linear-gradient(180deg,var(--amber-wash),rgba(255,211,77,.02));
    border:1px solid rgba(255,211,77,.30);clip-path:var(--cut-md);}
  .mf-pot span{font-family:var(--f-mono);font-size:var(--l-xs);letter-spacing:.28em;
    text-transform:uppercase;color:var(--ink-4);}
  .mf-pot b{font-family:var(--f-mono);font-size:var(--n-lg);font-weight:800;line-height:1;
    color:var(--amber);font-variant-numeric:tabular-nums;}
  .mf-pot i{font-style:normal;font-family:var(--f-mono);font-size:var(--l-sm);
    letter-spacing:.18em;text-transform:uppercase;color:var(--ink-4);}
  .mf-teams{display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);
    gap:var(--s4);align-items:center;margin:var(--s5) 0;}
  .mf-team{display:grid;gap:var(--s1);}
  .mf-team .mf-p{padding:8px 10px;background:rgba(255,255,255,.04);border-radius:0;
    clip-path:var(--cut-sm);font-family:var(--f-mono);font-size:var(--n-sm);
    letter-spacing:.04em;color:var(--ink-2);font-variant-numeric:tabular-nums;}
  .mf-team .mf-p.you{color:var(--amber);background:var(--amber-wash);
    box-shadow:inset 0 0 0 1px rgba(255,211,77,.40);}
  .mf-vs{font-family:var(--f-display);font-size:var(--d-md);letter-spacing:.14em;color:var(--ink-ghost);}
  .mf-elo{margin-bottom:var(--s1);font-family:var(--f-mono);font-size:var(--l-xs);
    letter-spacing:.28em;text-transform:uppercase;color:var(--ink-4);}
  .mf-count{font-family:var(--f-display);font-size:var(--d-xl);line-height:1;
    color:var(--ink);font-variant-numeric:tabular-nums;}
  .mf-count.tick{animation:mfTick 320ms var(--ease) both;}
  @keyframes mfTick{from{transform:scale(1.26);opacity:.6;}to{transform:scale(1);opacity:1;}}

  /* The verdict becomes a banner and the payout becomes the largest thing on the
     screen. It was 30px of plain white under a 32px title, with the accent spent
     on a table-row tint and a 9px MVP suffix — the colour marking the label
     rather than the value. */
  .result-title{font-family:var(--f-display);font-size:var(--d-md);letter-spacing:.30em;
    line-height:1;padding-bottom:var(--s3);border-bottom:1px solid var(--hair);}
  .result-box.win .result-title{color:var(--live);}
  .result-box.lose .result-title{color:var(--red);}
  .result-sub{margin-top:var(--s3);font-family:var(--f-mono);font-size:var(--l-xs);
    letter-spacing:.28em;text-transform:uppercase;color:var(--ink-4);}
  /* S5 — the receipt: the headline figure and the transfers that produced it,
     inside one plate, so the arithmetic reads as backing for the number rather
     than as a second unrelated block. */
  .result-payout{margin:var(--s5) 0;padding:var(--s5) var(--s4);
    background:linear-gradient(180deg,var(--amber-wash),rgba(255,211,77,.02));
    border:1px solid rgba(255,211,77,.30);clip-path:var(--cut-md);}
  .result-box.lose .result-payout{background:none;border-color:var(--hair);}
  .result-sol{margin:0;font-family:var(--f-mono);font-size:var(--n-hero);
    font-weight:800;letter-spacing:-.02em;line-height:1;color:var(--ink-3);
    font-variant-numeric:tabular-nums;}
  .result-sol.gain{color:var(--amber);text-shadow:0 0 40px rgba(255,211,77,.42);}
  .result-sol.loss{color:var(--red-soft);}

  /* Itemised money. Each line is a transfer that actually happened, in the
     order it happened, so the headline figure can be checked rather than
     believed. */
  .result-breakdown{margin:var(--s4) auto 0;max-width:320px;display:flex;
    flex-direction:column;gap:1px;}
  .result-breakdown:empty{display:none;}
  .rb-row{display:flex;justify-content:space-between;gap:var(--s4);padding:5px 0;
    border-bottom:1px solid var(--hair-dim);font-family:var(--f-mono);
    font-size:var(--l-sm);letter-spacing:.16em;text-transform:uppercase;
    color:var(--ink-4);font-variant-numeric:tabular-nums;}
  .rb-row span:last-child{font-weight:700;color:var(--ink-2);letter-spacing:.04em;}
  .rb-row.pos span:last-child{color:var(--live);}
  .rb-row.neg span:last-child{color:var(--red-soft);}
  .rb-note{margin-top:var(--s2);font-size:var(--t-sm);line-height:1.6;
    letter-spacing:.02em;color:var(--ink-3);text-align:left;}
  .result-table{width:100%;border-collapse:collapse;margin-top:var(--s2);
    font-family:var(--f-mono);font-size:var(--l-md);font-variant-numeric:tabular-nums;}
  /* The box centres its text, so the cells used to drift out from under their
     own headers. Operator reads left, every figure reads right. */
  .result-table th{padding:var(--s2);text-align:right;font-size:var(--l-sm);
    letter-spacing:.18em;text-transform:uppercase;color:var(--ink-4);
    border-bottom:1px solid var(--hair);}
  .result-table td{padding:var(--s2);text-align:right;
    border-bottom:1px solid var(--hair-dim);color:var(--ink-2);}
  .result-table th:first-child,.result-table td:first-child{text-align:left;}
  /* The money column is the scoreboard now, so it gets to be legible at a
     glance: a losing side is no longer a row of zeroes. */
  .result-table td.pos{color:var(--live);font-weight:700;}
  .result-table td.neg{color:var(--red-soft);font-weight:700;}
  .result-table tr.you td{color:var(--amber);background:var(--amber-wash);}
  .result-table tr.you td.pos,.result-table tr.you td.neg{color:var(--amber);}
  .result-table tr.mvp td:first-child::after{content:' MVP';color:var(--amber);
    font-size:var(--l-xs);letter-spacing:.24em;}
  .result-link{display:inline-block;margin-top:var(--s4);font-family:var(--f-mono);
    font-size:var(--l-md);letter-spacing:.14em;color:var(--amber);}
  /* Retoned off amber: this is a caveat about a settlement that did not reach a
     validator, not a figure the player earned. Amber stays money. */
  .result-sim{margin-top:var(--s4);padding:var(--s3) var(--s4);text-align:left;
    font-family:var(--f-mono);font-size:var(--l-md);line-height:1.6;letter-spacing:.03em;
    color:var(--alert);background:rgba(255,159,67,.07);
    border:1px solid rgba(255,159,67,.28);border-radius:0;clip-path:var(--cut-sm);}
  .result-actions{display:flex;gap:var(--s3);margin-top:var(--s5);}

  /* The commitment moment was the softest surface in the product — a 16px radius
     with 10px buttons — and inside it six rows of identical 13px type, in which
     "You stake 0.050" and "stake is gone" were set exactly the same. */
  .stake-lines{display:grid;gap:1px;margin:var(--s4) 0 var(--s5);text-align:left;}
  .stake-lines div{display:flex;justify-content:space-between;align-items:baseline;
    gap:var(--s3);padding:9px 0;border-bottom:1px solid var(--hair-dim);
    font-family:var(--f-mono);font-size:var(--n-sm);font-variant-numeric:tabular-nums;}
  .stake-lines span{font-size:var(--l-sm);letter-spacing:.18em;text-transform:uppercase;color:var(--ink-4);}
  .stake-lines b{font-weight:700;color:var(--ink-2);white-space:nowrap;text-align:right;}
  .stake-lines div.stake b{color:var(--ink);}
  /* S5 — the number the player is playing for. */
  .stake-lines div.win{margin-top:var(--s3);padding:var(--s3);border-bottom:0;
    background:linear-gradient(180deg,var(--amber-wash),rgba(255,211,77,.02));
    border:1px solid rgba(255,211,77,.30);clip-path:var(--cut-sm);}
  .stake-lines div.win b{font-size:var(--n-md);color:var(--amber);}
  .stake-lines div.lose{padding:var(--s3);border-bottom:0;
    background:rgba(255,59,48,.06);border:1px solid rgba(255,59,48,.32);clip-path:var(--cut-sm);}
  .stake-lines div.lose b{color:var(--red-soft);}
  /* A terminal entry, not a form field. */
  .funds-input{width:100%;margin:var(--s4) 0;padding:var(--s4);border-radius:0;
    clip-path:var(--cut-md);background:rgba(255,255,255,.05);
    border:1px solid rgba(255,211,77,.30);color:var(--amber);font-family:var(--f-mono);
    font-size:var(--n-lg);font-weight:800;letter-spacing:.04em;text-align:center;
    font-variant-numeric:tabular-nums;}
  .funds-input::placeholder{color:var(--ink-ghost);font-weight:400;}
  .funds-input:focus{outline:none;box-shadow:inset 0 0 0 2px var(--amber);}

  @media (max-width:1000px){
    .lobby-grid{grid-template-columns:minmax(0,1fr);}
    /* Collapsing to one column hands the order to the DOM, which put a ~600px
       profile aside — badge, level bar, five stats, a 96px chart, balance and
       two buttons — above the button that starts a match. */
    .lobby-modes{order:-1;}
  }
  @media (max-width:640px){
    .lobby-shell{padding:var(--s4) var(--s4) var(--s7);}
    .lobby-head{flex-wrap:wrap;gap:var(--s3);}
    .lobby-mode-card{grid-template-columns:minmax(0,1fr);}
    .lmc-btn{width:100%;}
    .queue-box,.mf-box,.result-box{padding:var(--s5);}
    .queue-live{flex-direction:column;gap:var(--s1);}
    .mf-teams{grid-template-columns:minmax(0,1fr);}
    .result-actions{flex-direction:column;}
    .result-table th,.result-table td{padding:6px 4px;font-size:var(--l-sm);}
  }

  .dialog-btn .wallet-state {
    margin-left: auto; font-size: var(--l-xs); letter-spacing: .24em;
    color: var(--ink-4); font-weight: 700; line-height: inherit;
  }
  .dialog-btn.absent { opacity: .55; }
  .dialog-btn[disabled] { opacity: .4; cursor: wait; }
  .wallet-msg {
    display: none; margin-top: var(--s3); font-size: var(--t-sm); line-height: 1.7;
    color: var(--ink-3); text-align: left;
  }
  .wallet-msg.show { display: block; }
  .wallet-msg a { color: var(--amber); text-decoration: underline; margin-left: 4px; }

  /* A beta disclaimer must not outrank the product name. This was #ffc400 — a
     second amber 200px from the real one — filled at 9%, which made it the only
     chromatic element above the fold. One amber, a hairline band instead of a
     slab, and the claim is now stated once: the hero deck and the microcopy
     used to restate "closed beta" at two more sizes inside 400px of page. */
  .status-banner {
    background: rgba(255, 211, 77, 0.05);
    border-bottom: 1px solid rgba(255, 211, 77, 0.24);
    padding: var(--s3) 0; font-size: var(--t-sm); line-height: 1.55;
    color: var(--ink-3);
  }
  .status-banner .container { display: flex; gap: var(--s3); align-items: baseline; }
  .status-banner strong {
    flex: 0 0 auto; color: var(--amber); font-weight: 800;
    font-family: var(--f-mono);
    letter-spacing: 0.18em; font-size: var(--l-md);
  }

  /* How it works */
  .how-sec, .modes-sec, .roadmap-sec { padding-block: var(--s9); }
  .how-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s4); margin-top: var(--s6); }
  .how-card {
    background: linear-gradient(180deg,rgba(255,255,255,.04),transparent 90px), rgba(9,11,14,.76);
    border: 1px solid var(--hair); box-shadow: var(--e1);
    border-radius: 0; padding: var(--s5); position: relative;
  }
  .how-card b {
    font-family: var(--f-mono); font-size: var(--l-sm);
    color: var(--ink-4); letter-spacing: 0.22em; font-variant-numeric: tabular-nums;
  }
  /* One treatment for every card title on the page. how / modes / faq used to
     run system-sans-15, Bebas-20 and mono-11.5 — three separate designs. */
  .how-card h4 { font-family: var(--f-display); font-size: var(--d-sm); font-weight: 700;
    letter-spacing: .20em; line-height: 1; color: var(--ink); margin: var(--s3) 0 var(--s2); }
  .how-card p { font-size: var(--t-sm); line-height: 1.6; color: var(--ink-3); }

  /* Modes */
  .modes-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s4); margin-top: var(--s6); }
  .mode-card {
    background: linear-gradient(180deg,rgba(255,255,255,.04),transparent 90px), rgba(9,11,14,.76);
    border: 1px solid var(--hair); box-shadow: var(--e1);
    border-radius: 0; padding: var(--s5); display: flex; flex-direction: column;
  }
  .mode-card.free { border-color: rgba(126,224,129,0.35); }
  .mode-top { display: flex; align-items: center; justify-content: space-between; }
  .mode-top h4 { font-family: var(--f-display); font-size: var(--d-sm);
    letter-spacing: 0.20em; line-height: 1; color: var(--ink); }
  .mode-tag {
    font-size: var(--l-md); letter-spacing: 0.18em; font-weight: 800; padding: var(--s1) 10px;
    border-radius: 999px; border: 1px solid var(--hair-lit); color: var(--ink-3);
  }
  .mode-card.free .mode-tag { border-color: rgba(126,224,129,0.5); color: var(--live); }
  .mode-desc { font-size: var(--t-sm); line-height: 1.6; color: var(--ink-3); margin: var(--s3) 0 var(--s4); }
  .mode-meta { list-style: none; padding: 0; margin: 0 0 var(--s4); display: grid; gap: 7px; }
  .mode-meta li { display: flex; justify-content: space-between; font-size: var(--l-lg); }
  .mode-meta span { color: var(--ink-4); }
  .mode-meta b { color: var(--ink); font-weight: 700; }
  .mode-queue { margin-top: auto; font-size: var(--l-md); letter-spacing: 0.12em; color: var(--ink-4); }
  .mode-play {
    margin-top: auto; padding: var(--s3); border-radius: 10px; border: none; cursor: pointer;
    background: var(--live); color: #04240a; font-family: inherit; font-weight: 800; font-size: var(--l-lg);
  }
  /* DUEL and SQUAD used to terminate in an 11px status line while TRAINING
     terminated in a 46px button — three cards of equal height ending in three
     visually unequal things, and two of them with no call to action at all. */
  .mode-play.locked { background: transparent; color: var(--ink-4);
    border: 1px solid var(--hair); cursor: not-allowed; margin-top: var(--s3); }
  .modes-note {
    margin-top: var(--s5); font-size: var(--t-sm); line-height: 1.7;
    color: var(--ink-3); max-width: var(--measure);
  }

  /* Roadmap */
  .roadmap-list { list-style: none; padding: 0; margin: var(--s6) 0 0; display: grid; gap: 10px; }
  .roadmap-list li {
    display: flex; gap: 18px; align-items: baseline; padding: 14px 18px;
    border: 1px solid var(--hair); border-radius: 0;
    background: linear-gradient(180deg,rgba(255,255,255,.04),transparent 90px), rgba(9,11,14,.76);
    box-shadow: var(--e1);
  }
  .roadmap-list b {
    flex: 0 0 132px; font-size: var(--l-sm); letter-spacing: 0.22em; font-weight: 800;
    color: var(--ink-4); text-transform: uppercase; line-height: 1.5;
  }
  .roadmap-list li.done  b { color: var(--live); }
  .roadmap-list li.wip   b { color: var(--amber); }
  .roadmap-list span { font-size: var(--t-md); line-height: 1.65; color: var(--ink-3); max-width: var(--measure); }
  .roadmap-list span i { display:block; font-style:normal; font-weight:800; font-size:var(--t-sm);
    letter-spacing:.06em; color:var(--ink); margin-bottom:5px; }

  .faq-sec { padding-block: var(--s9); }
  .faq-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s4); margin-top: var(--s6); }
  .faq-item {
    background: linear-gradient(180deg,rgba(255,255,255,.04),transparent 90px), rgba(9,11,14,.76);
    border: 1px solid var(--hair); box-shadow: var(--e1);
    border-radius: 0; padding: var(--s5);
  }
  .faq-item h4 { font-family: var(--f-display); font-size: var(--d-sm); font-weight: 700;
    letter-spacing: .20em; line-height: 1; color: var(--ink); margin-bottom: var(--s3); }
  .faq-item p { font-size: var(--t-md); line-height: 1.7; color: var(--ink-3); max-width: var(--measure); }

  .footer-links { display: inline-flex; gap: 18px; }
  .footer-links a { color: var(--ink-3); text-decoration: none; font-size: var(--l-lg); }
  .footer-links a:hover { color: var(--ink); }
  .risk-note {
    margin-top: 18px; padding-top: 18px;
    border-top: 1px solid var(--hair);
    font-size: var(--t-sm); line-height: 1.6; color: var(--ink-3); max-width: var(--measure);
  }

  .hero-visual img {
    width: 100%; border-radius: 14px; display: block;
    border: 1px solid var(--hair);
    box-shadow: 0 20px 50px rgba(0,0,0,0.55);
  }
  .how-steps { list-style: none; margin: 20px 0 0; padding: 0; display: grid; gap: 10px; }
  .how-steps li {
    display: flex; align-items: center; gap: var(--s3);
    font-size: var(--t-md); color: var(--ink-2);
  }
  .how-steps li span {
    flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%;
    background: rgba(255,255,255,0.1); border: 1px solid var(--hair-lit);
    display: inline-flex; align-items: center; justify-content: center;
    font-family: var(--f-mono); font-size: var(--l-md); font-weight: 800; color: var(--ink);
  }

  .hero-cta-row { display: flex; gap: 14px; flex-wrap: wrap; align-items: center; }
  .btn-play-secondary {
    display: inline-flex; align-items: center; justify-content: center;
    padding: var(--s4) 28px; border-radius: 12px;
    background: transparent; color: var(--ink);
    border: 1px solid var(--hair-lit);
    font-family: inherit; font-size: var(--t-lg); font-weight: 700;
    cursor: pointer; transition: border-color var(--t-fast) var(--ease),
                                 background var(--t-fast) var(--ease),
                                 transform var(--t-press) var(--ease);
  }
  .btn-play-secondary:hover { border-color: var(--ink); background: rgba(255,255,255,0.07);
    transform: translateY(-1px); }
  .hero-microcopy { margin-top: 14px; font-size: var(--t-sm); color: var(--ink-4); letter-spacing: 0.02em; }

  .hero-stats-landing { display: flex; gap: 36px; margin-top: var(--s7); }
  .hero-stat-landing .value { font-family: var(--f-mono); font-size: var(--n-md); font-weight: 800;
    color: var(--ink); font-variant-numeric: tabular-nums; }
  .hero-stat-landing .label { font-size: var(--l-sm); letter-spacing: .22em; color: var(--ink-4);
    margin-top: var(--s1); text-transform: uppercase; }

  /* Vision Section */
  .vision-landing { padding-block: var(--s9); background: rgba(5, 5, 5, 0.6); border-top: 1px solid var(--hair-dim); }
  .vision-grid {
    display: grid; grid-template-columns: 1fr 1fr; gap: var(--s7);
  }
  .vision-item h3 {
    font-family: var(--f-mono); font-size: var(--l-md); font-weight: 700; color: var(--ink);
    letter-spacing: .1em; text-transform: uppercase;
    margin-bottom: 10px; display: flex; align-items: center; gap: 10px;
  }
  .vision-item h3 span { color: var(--ink-4); }
  .vision-item p { font-size: var(--t-md); line-height: 1.7; color: var(--ink-3);
    max-width: var(--measure); margin-bottom: 30px; }

  /* Embedded Whitepaper */
  .wp-landing { padding-block: var(--s9); }
  .wp-header { text-align: center; margin-bottom: var(--s7); }
  .wp-header h2 { font-family: var(--f-display); font-size: var(--d-lg); font-weight: 800;
    letter-spacing: .16em; text-indent: .16em; line-height: .94; color: var(--ink); }
  .wp-header p { font-size: var(--t-md); color: var(--ink-3); margin-top: var(--s2); }
  /* minmax(0,…) and min-width:0 on the pane, because a grid item's automatic
     minimum size is its min-content — and the settlement table's min-content is
     ~350px, which is wider than the whole column on a phone. Without these the
     track grows to fit the table and `overflow:hidden` eats the prose. */
  .wp-box {
    display: grid; grid-template-columns: 240px minmax(0, 1fr);
    background: rgba(12, 15, 19, 0.90); border: 1px solid var(--hair);
    box-shadow: var(--e2);
    border-radius: 0; overflow: hidden;
  }
  .wp-menu { list-style: none; background: rgba(5, 5, 5, 0.5); padding: var(--s5) var(--s4);
    border-right: 1px solid var(--hair); }
  .wp-menu li { margin-bottom: 6px; }
  .wp-menu-btn {
    width: 100%; text-align: left; background: none; border: none;
    padding: 10px 14px; border-radius: 8px; color: var(--ink-3);
    font-size: var(--l-lg); font-weight: 600; cursor: pointer;
    transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
  }
  .wp-menu-btn:hover { color: var(--ink); background: rgba(255, 255, 255, 0.02); }
  .wp-menu-btn.active { color: #000; background: var(--ink); }

  .wp-body { padding: var(--s7) 48px; min-height: 380px; min-width: 0; }
  .wp-section { display: none; }
  .wp-section.active { display: block; animation: fadeIn 0.3s ease-out; }
  /* Referenced since the whitepaper shipped and never defined, so the pane
     swap has been a hard cut. */
  @keyframes fadeIn{ from{opacity:0;} to{opacity:1;} }
  .wp-section h3 { font-family: var(--f-display); font-size: var(--d-md); font-weight: 800;
    letter-spacing: .18em; line-height: 1; color: var(--ink); margin-bottom: var(--s4); }
  .wp-section p { font-size: var(--t-md); line-height: 1.7; color: var(--ink-2);
    margin-bottom: var(--s4); max-width: var(--measure); }
  .wp-section ul { padding-left: 20px; color: var(--ink-2); margin-bottom: var(--s4);
    font-size: var(--t-md); max-width: var(--measure); }
  .wp-section li { margin-bottom: 6px; }

  .wp-table { width: 100%; border-collapse: collapse; margin-top: var(--s4); }
  .wp-table th, .wp-table td { padding: 10px 14px; text-align: left; border-bottom: 1px solid var(--hair-dim); }
  .wp-table th { font-family: var(--f-mono); font-size: var(--l-lg); letter-spacing: .16em;
    color: var(--ink-4); text-transform: uppercase; }
  /* Status pill. Every technical claim in the paper carries one, so a reader can
     tell what runs today from what is still design. */
  .wp-st{display:inline-block;font-size:var(--l-xs);letter-spacing:.18em;font-weight:800;padding:3px 8px;
    border:1px solid currentColor;vertical-align:middle;margin-left:9px;white-space:nowrap;}
  .wp-st.live{color:var(--live);} .wp-st.wip{color:var(--amber);} .wp-st.plan{color:var(--ink-4);}
  .wp-callout{margin:var(--s4) 0;padding:13px var(--s4);border-left:2px solid rgba(255,211,77,.6);
    background:var(--amber-wash);font-size:var(--t-sm);line-height:1.7;color:var(--ink-2);
    max-width:var(--measure);}
  .wp-callout b{color:var(--amber);}
  .wp-section h4{font-family:var(--f-mono);font-size:var(--l-md);letter-spacing:.18em;font-weight:800;
    color:var(--ink-4);margin:22px 0 10px;}
  .wp-section code{font-family:var(--f-mono);font-size:var(--l-lg);
    background:rgba(255,255,255,.07);padding:2px 6px;color:#e6edf3;}
  .wp-table td { font-size: var(--t-sm); color: var(--ink-2); }
  /* The leaderboard is a record, not prose: wallets and ratings are data and
     want the machine face plus tabular figures so the columns line up. */
  #leaderboardTable td { font-family: var(--f-mono); font-size: var(--l-md);
    letter-spacing: .04em; font-variant-numeric: tabular-nums; }

  /* Wallet Dialog Overlay */
  .dialog-overlay {
    position: fixed; inset: 0; z-index: 1000;
    background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(8px);
    display: flex; align-items: center; justify-content: center;
    opacity: 0; pointer-events: none; transition: all 0.25s ease;
  }
  .dialog-overlay.open { opacity: 1; pointer-events: auto; }
  /* S4 — sheet. This is the surface the player is standing on when they commit
     money, and it was the softest thing in the product: a 16px radius holding
     10px-radius buttons. clip-path eats an outer shadow, so the lift is inset
     and the overlay's own scrim does the separating — no wrapper needed. */
  .dialog-box {
    width: min(420px, 92vw); border-radius: 0; clip-path: var(--cut-xl);
    background: linear-gradient(180deg, rgba(21,27,33,.98), rgba(10,13,17,.98));
    border: 1px solid var(--hair-lit); box-shadow: var(--e2);
    padding: var(--s6); text-align: center;
  }
  .dialog-box h3 { font-family: var(--f-display); font-size: var(--d-md); letter-spacing: .20em;
    font-weight: 800; margin-bottom: var(--s2); color: var(--ink); }
  .dialog-box p { font-size: var(--t-sm); line-height: 1.7; color: var(--ink-3); margin-bottom: var(--s5); }
  .dialog-btn {
    width: 100%; display: flex; align-items: center; gap: var(--s3);
    padding: 14px 18px; border-radius: 0; clip-path: var(--cut-md);
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid var(--hair); color: var(--ink);
    font-family: var(--f-mono); font-size: var(--l-lg); letter-spacing: .14em;
    font-weight: 700; text-transform: uppercase;
    cursor: pointer; margin-bottom: var(--s2);
    transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease),
                transform var(--t-press) var(--ease);
  }
  .dialog-btn:hover { background: rgba(255, 255, 255, 0.1); border-color: var(--hair-lit); transform: translateY(-1px); }
  .dialog-btn:active { transform: translateY(1px); }
  /* The wallet buttons carry a label and a state and want them apart; the
     confirm button carries one word and wants it centred. */
  .dialog-btn.primary { background: var(--ink); color: #000; font-weight: 800;
    letter-spacing: .20em; justify-content: center; }
  .dialog-close { background: none; border: none; color: var(--ink-4);
    font-family: var(--f-mono); font-size: var(--l-md); letter-spacing: .18em;
    text-transform: uppercase; cursor: pointer; margin-top: var(--s4); }
  .dialog-close:hover { color: var(--ink); }

  /* Footer */
  .landing-footer {
    background: #050505; padding: var(--s8) 0 30px; border-top: 1px solid var(--hair);
  }
  .landing-footer .container {
    display: flex; justify-content: space-between; align-items: center;
    font-size: var(--l-lg); color: var(--ink-3);
  }

  /* Ticker */
  .landing-ticker {
    background: #000; color: var(--ink-4);
    font-family: var(--f-mono); font-size: var(--l-md); letter-spacing: 0.20em;
    text-align: center; padding: 10px var(--gutter); border-bottom: 1px solid var(--hair);
    text-transform: uppercase;
  }
  .landing-ticker span { color: var(--ink); font-weight: 700; font-variant-numeric: tabular-nums; }

  /* ---------------- responsive ----------------
     The landing page shipped with no media queries at all; every grid was a hard
     two-column. Most inbound traffic for a game like this is mobile. */
  @media (max-width: 900px) {
    .hero-grid { grid-template-columns: 1fr; gap: var(--s6); }
    .vision-grid { grid-template-columns: 1fr; }
    .faq-grid { grid-template-columns: 1fr; }
    .hero-visual { order: -1; }
    /* Collapsed into the toggle rather than simply removed. It becomes a
       drop-down panel under the bar when .open is set. */
    .nav-toggle { display: inline-flex; }
    .landing-nav-links { display: none; }
    .landing-nav-links.open {
      display: flex; flex-direction: column; gap: 0;
      position: absolute; left: 0; right: 0; top: 100%;
      background: var(--chrome, rgba(6,7,9,.98));
      border-top: 1px solid var(--hair);
      border-bottom: 1px solid var(--hair);
      padding: var(--s2) var(--gutter, 24px);
      max-height: calc(100vh - 100%); overflow-y: auto;
    }
    .landing-nav-links.open li { border-bottom: 1px solid var(--hair-dim); }
    .landing-nav-links.open li:last-child { border-bottom: 0; }
    .landing-nav-links.open a { display: block; padding: var(--s4) 0; }
    .landing-nav .container { position: relative; }
    .how-grid { grid-template-columns: 1fr 1fr; }
    /* Was 3 columns straight to 1 with nothing in between, which is what
       .how-grid already gets right one line up. */
    .modes-grid { grid-template-columns: repeat(2, 1fr); }
    /* The whitepaper was a hard `240px 1fr` with `overflow:hidden` and its only
       responsive rule was `.wp-menu{flex-wrap:wrap}` — a no-op, because the
       menu is a block-level <ul>. At 390px the prose column computed to 54px
       and the clip swallowed it. The nav becomes a scrolling tab strip. */
    .wp-box { grid-template-columns: minmax(0, 1fr); }
    .wp-menu { display: flex; gap: var(--s2); overflow-x: auto; border-right: 0;
               border-bottom: 1px solid var(--hair); padding: var(--s3) var(--s4); min-width: 0; }
    .wp-menu li { margin: 0; flex: 0 0 auto; }
    .wp-menu-btn { white-space: nowrap; }
    .wp-body { padding: var(--s5); }
    .wp-table { display: block; overflow-x: auto; }
  }
  @media (max-width: 640px) {
    /* The footer may stack; the nav must not. Stacking it put the toggle on its
       own row below the logo and pushed the drop-down panel past the wallet
       chip. A bar stays a bar: logo left, toggle right. */
    .landing-footer .container { flex-direction: column; gap: 14px; text-align: center; }
    .landing-nav .container { gap: var(--s3); }
    .hero-landing { padding-block: var(--s8) var(--s7); }
    .hero-cta-row { flex-direction: column; align-items: stretch; }
    .cta-frame { width: 100%; }
    .btn-play-landing, .btn-play-secondary { width: 100%; justify-content: center; }
    .hero-stats-landing { gap: 18px; justify-content: space-between; margin-top: var(--s6); }
    .hero-stat-landing .value { font-size: var(--n-sm); }
    .status-banner .container { flex-direction: column; gap: 6px; }
    /* .vision-landing and .wp-landing were missing from this list, so two of the
       eight sections kept their desktop rhythm on a phone. */
    .leaderboard-sec, .faq-sec, .how-sec, .modes-sec, .roadmap-sec,
    .vision-landing, .wp-landing { padding-block: var(--s7); }
    .how-grid, .modes-grid { grid-template-columns: 1fr; }
    .roadmap-list li { flex-direction: column; gap: 6px; }
    /* Wide content scrolls inside its own box rather than the page. */
    .leaderboard-sec > div { overflow-x: auto; }
    /* ID-scoped so it outranks the tactical layer's own ticker rule — the
       unscoped version silently lost once that rule started matching. */
    #landingOverlay .landing-ticker { font-size: var(--l-xs); letter-spacing: 0.06em; }
  }
  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
  }
  /* The `//` between readouts is punctuation, not information. */
  .landing-ticker i { font-style: normal; color: var(--ink-ghost); margin: 0 var(--s4); }

  /* Leaderboard */
  .leaderboard-sec { padding-block: var(--s9); }
  /* This surface was written inline on the markup, which is why it was the one
     panel on the page that never picked up any of the plate work. Same document
     surface as the whitepaper box. The chamfer stays off the scroll container —
     clipping a scrolling box clips its scrollbar — so it sits on the wrapper. */
  .lb-box {
    margin-top: var(--s6); padding: var(--s5); overflow-x: auto;
    background: rgba(12, 15, 19, 0.90); border: 1px solid var(--hair);
    box-shadow: var(--e2);
  }

  /* ═══════════════════════════════════════════════════════════════════════
     TACTICAL LAYER

     Overrides the consumer-app defaults the landing shipped with. Three moves
     carry it: chamfered plates instead of pill radii (a 12px round corner reads
     SaaS, not ops), monospace for anything that is data rather than prose, and
     corner brackets that frame a panel the way a HUD frames a target.

     Appended as a layer rather than edited in place so the base cascade stays
     readable and any one move can be reverted on its own.

     The :root that used to sit here has moved into the single token block at
     the top of the stylesheet; --mono, --hair, --hair-lit, --live and --warn
     survive there as aliases so nothing below had to change.
     ═══════════════════════════════════════════════════════════════════════ */

  /* ── 1. Kill the rounded-corner language ─────────────────────────────────
     The !important is gone: the base rules now ship `border-radius:0` on the
     plates themselves, so there is nothing left to fight. */
  #landingOverlay .hero-visual img,
  #landingOverlay .btn-connect-landing,
  #landingOverlay .wallet-connected-landing,
  #landingOverlay .btn-play-landing,
  #landingOverlay .btn-play-secondary,
  #landingOverlay .mode-play{ border-radius: 0; }

  /* Chamfer: one cut corner is enough to read as machined. Both diagonals on a
     small control looks like a mistake.
     .vision-item is deliberately not in either list — chamfering a block that
     has no surface is what nicked the corner off its SVG icon. Not every
     paragraph is a card. */
  #landingOverlay .how-card,
  #landingOverlay .mode-card,
  #landingOverlay .wp-box,
  #landingOverlay .faq-item{ clip-path: var(--cut-lg); }
  #landingOverlay .btn-play-landing,
  #landingOverlay .btn-play-secondary,
  #landingOverlay .mode-play,
  #landingOverlay .btn-connect-landing{ clip-path: var(--cut-md); }
  #landingOverlay .landing-logo .logo-icon{ border-radius: 2px; }

  /* ── 2. Monospace for data, prose stays prose ──────────────────────────── */
  /* .hero-badge and .step-num are gone from this list: neither element has ever
     existed in the markup (the steps use `.how-steps li span`). */
  #landingOverlay .hero-stat-landing .label,
  #landingOverlay .mode-tag,
  #landingOverlay .mode-meta,
  #landingOverlay .roadmap-list b,
  #landingOverlay .wp-table th,
  #landingOverlay .landing-nav-links a,
  #landingOverlay .landing-ticker{
    font-family: var(--mono);
    letter-spacing: .12em;
    text-transform: uppercase;
  }
  /* Sentence-length strings: keep the machine face, drop the shouting. */
  #landingOverlay .wp-header p,
  #landingOverlay .modes-note,
  #landingOverlay .hero-microcopy{
    font-family: var(--mono); text-transform: none; letter-spacing: .04em;
  }
  #landingOverlay .hero-stat-landing .value,
  #landingOverlay .wp-table td:last-child{ font-family: var(--mono); }
  /* Prose must NOT go mono — long monospace paragraphs are hostile to read. */
  #landingOverlay .hero-desc-landing,
  #landingOverlay .mode-desc,
  #landingOverlay .faq-item p,
  #landingOverlay .roadmap-list span,
  #landingOverlay .wp-section p,
  #landingOverlay .wp-section li,
  #landingOverlay .vision-item p{ font-family: var(--f-text); text-transform:none; }
  /* These three stacked `opacity:.5` onto colours that already carried alpha,
     so the deck under every section header rendered at ~.25 — about 1.8:1, and
     no colour change upstream could reach it. Alpha lives in the token now. */
  #landingOverlay .wp-header p{ font-size: var(--t-sm); max-width: 62ch; margin-left:auto; margin-right:auto; }
  #landingOverlay .modes-note{ font-size: var(--t-sm); line-height: 1.8; }
  #landingOverlay .hero-microcopy{ font-size: var(--l-md); }

  /* ── 3. Numbered section headers, briefing-document style ──────────────── */
  /* A CSS counter cannot number these: the landing is hash-routed, and a
     display:none section does not increment one — so every router page restarted
     at 01. The number is authored on the section instead, which is stable
     regardless of which page happens to be mounted. */
  #landingOverlay .wp-header h2{
    position: relative; display: inline-block; padding-top: 20px;
  }
  #landingOverlay .wp-header h2::before{
    content: attr(data-sec);
    position: absolute; top: 0; left: 50%; transform: translateX(-50%);
    font-family: var(--mono); font-size: 10px; font-weight: 700;
    letter-spacing: .3em; color: var(--warn); opacity: .8;
  }
  /* The tick rule that used to sit here was 56px at bottom:-12px, and
     `.wp-header p` has margin-top:8px — so it struck through the cap height of
     the deck under every single section title. Removed rather than nudged: the
     counter above the heading is already the landmark. */
  #landingOverlay .wp-header{ padding-bottom: 8px; }

  /* ── 4. Corner brackets: HUD framing on the panels that carry weight ───── */
  #landingOverlay .hero-visual,
  #landingOverlay .wp-box{ position: relative; }
  #landingOverlay .hero-visual::before,
  #landingOverlay .hero-visual::after,
  #landingOverlay .wp-box::before,
  #landingOverlay .wp-box::after{
    content: ""; position: absolute; width: 18px; height: 18px; pointer-events: none;
    border-color: var(--hair-lit); border-style: solid; border-width: 0;
  }
  #landingOverlay .hero-visual::before,
  #landingOverlay .wp-box::before{ top: -6px; left: -6px; border-top-width: 1px; border-left-width: 1px; }
  #landingOverlay .hero-visual::after,
  #landingOverlay .wp-box::after{ bottom: -6px; right: -6px; border-bottom-width: 1px; border-right-width: 1px; }

  /* ── 5. opsPulse. The `.hero-badge` this block was written for has never
         existed in the markup, so the whole live-readout badge — dot included —
         was unreachable. The keyframe stays: its real consumer is the roadmap
         WIP pip in section 10. ───────────────────────────────────────────── */
  @keyframes opsPulse{ 0%,100%{opacity:1;} 50%{opacity:.25;} }

  /* ── 6. Mode cards: stake figures are the point, so set them like readouts ─ */
  #landingOverlay .mode-meta b{ font-family: var(--mono); color: var(--ink); }
  #landingOverlay .mode-tag{ border-radius: 0; clip-path: var(--cut-sm); }
  /* box-shadow on a clipped element paints nothing, so the old hover glow was
     invisible. A 1px rise is real feedback and composites on the GPU. */
  #landingOverlay .mode-play{
    background: var(--ink); color: #000; font-size: var(--l-md); letter-spacing: .2em; font-weight: 800;
    padding: 14px 12px; font-family: var(--mono); text-transform: uppercase;
    transition: transform var(--t-press) var(--ease), background var(--t-fast) var(--ease),
                border-color var(--t-fast) var(--ease);
  }
  #landingOverlay .mode-play:hover{ transform: translateY(-1px); }
  #landingOverlay .mode-play:active{ transform: translateY(1px); }
  #landingOverlay .mode-play.locked,
  #landingOverlay .mode-play.locked:hover{ background: transparent; color: var(--ink-4);
    border: 1px solid var(--hair); transform: none; }
  /* The free card keeps its green, as a border accent rather than a slab. */
  #landingOverlay .mode-card.free .mode-play{
    background: transparent; color: var(--live); border: 1px solid rgba(126,224,129,.5);
  }
  #landingOverlay .mode-card.free .mode-play:hover{
    background: rgba(126,224,129,.12); border-color: var(--live);
  }

  /* ── 7. Telemetry strip along the top ────────────────────────────────────
     Was written against `.stat-ticker`, which is not the class the markup
     carries — so the strip has been sitting in the wrong typeface since it
     shipped. The element is `.landing-ticker`. */
  #landingOverlay .landing-ticker{ font-size: var(--l-sm); letter-spacing: .2em; }

  /* ── 8. The survey grid that used to live here is gone. Four ::before layers
         with radial masks, painted over a live canvas at an opacity nobody can
         resolve, each one creating a stacking context that then needed a
         `z-index:1` patch on every child of four sections. The section-counter
         rule is the landmark; texture is not. ───────────────────────────── */

  /* ── 9. Nav: squared, with a live-link underline that grows from the left ─ */
  #landingOverlay .landing-nav-links a{ position: relative; font-size: var(--l-lg); letter-spacing: .16em; }
  #landingOverlay .landing-nav-links a::after{
    content: ""; position: absolute; left: 0; bottom: -6px; height: 1px; width: 0;
    background: var(--warn); transition: width .22s cubic-bezier(.3,.8,.3,1);
  }
  #landingOverlay .landing-nav-links a:hover::after{ width: 100%; }

  /* ── 10. Roadmap rail gets a status LED per phase ──────────────────────── */
  #landingOverlay .roadmap-list li{ position: relative; padding-left: 30px; }
  #landingOverlay .roadmap-list li::before{
    content: ""; position: absolute; left: 14px; top: 22px;
    width: 6px; height: 6px; transform: rotate(45deg);
    background: rgba(255,255,255,.22);
  }
  #landingOverlay .roadmap-list li.done::before{ background: var(--live); box-shadow: 0 0 10px rgba(126,224,129,.7); }
  #landingOverlay .roadmap-list li.wip::before{ background: var(--warn); box-shadow: 0 0 10px rgba(255,211,77,.7);
    animation: opsPulse 2.4s ease-in-out infinite; }

  /* ── 11. Vision headings stay mono — they label a claim, they do not title a
         card. `.faq-item h4` left this rule and joined the card-title step, so
         how / modes / faq stop reading as three separate designs. ────────── */
  #landingOverlay .vision-item h3{ font-family: var(--mono); font-size: var(--l-md); letter-spacing: .1em;
    text-transform: uppercase; }

  /* ── 12. Hex decode ────────────────────────────────────────────────────
     Cycling cells sit in the warning amber and settle to white, so the eye
     tracks the decode front rather than reading noise. `sc-hit` is the frame a
     cell locks: a brief bloom that makes the settle feel mechanical. */
  .sc-ch{ display: inline-block !important; text-align: center; transition: color .18s ease; }
  .sc-live{ color: var(--warn) !important; opacity: .82; text-shadow: 0 0 14px rgba(255,211,77,.5); }
  .sc-hit{ text-shadow: 0 0 22px rgba(255,255,255,.85), 0 0 44px rgba(255,211,77,.35); }
  #landingOverlay .hero-landing h1 .accent-text .sc-live{ color: var(--warn) !important; }

  /* ── 13. Focus. There was no :focus-visible anywhere in this file — the
     primary CTA of the whole game had no visible tab stop while the wallet
     modal shipped a full focus trap. And clip-path clips `outline` exactly as
     it clips `box-shadow`, so every chamfered control needs an inset ring
     instead of an outline. ───────────────────────────────────────────────── */
  :is(a,button,[role="button"],input,select,textarea):focus-visible{
    outline: 2px solid var(--amber); outline-offset: 3px;
  }
  :is(.btn-play-landing,.btn-play-secondary,.mode-play,.btn-connect-landing,
      .wp-menu-btn,.lmc-btn,.pbtn,.dialog-btn,.acc-btn,.tbtn):focus-visible{
    outline: none; box-shadow: inset 0 0 0 2px var(--amber);
  }

  @media (prefers-reduced-motion: reduce){
    #landingOverlay .roadmap-list li.wip::before{ animation: none; }
    .sc-live,.sc-hit{ text-shadow: none; }
  }

  /* ── 14. Reduced motion must still confirm a hit. ────────────────────────
     The global guard above collapses every animation to .01ms. These three
     elements have `opacity:0` as their BASE state and only become visible via
     a keyframe, so a player with the preference set got no hit confirmation,
     no damage number and no streak banner for an entire match — unable to tell
     a hit from a miss with SOL on the line. They land, they just do not move.
     This block has to stay AFTER the `*` guard in source order, and carry its
     own !important, or the guard wins. */
  @media (prefers-reduced-motion: reduce){
    #hitmark.show     { animation: none !important; opacity: 1; transform: scale(1.1); }
    .dmg-num.go       { animation: none !important; opacity: 1; transform: translate(-50%,-86%); }
    #streakBanner.show{ animation: none !important; opacity: 1; transform: translate(-50%,-50%); }
    .pb-banner::after { animation: none !important; opacity: 0; }
  }

  /* Shown only when the WebGL context could not be created. The site keeps
     working; the game does not. */
  .webgl-warning{
    background: rgba(255,196,0,.09);
    border-bottom: 1px solid rgba(255,196,0,.3);
    color: var(--ink-2, rgba(255,255,255,.78));
    font-size: 12.5px; line-height: 1.55;
    padding: 12px var(--gutter, 24px); text-align: center;
  }
  /* Nothing that launches the engine should look available. */
  html.no-webgl .btn-play-landing,
  html.no-webgl .mode-play,
  html.no-webgl .lmc-btn{ opacity: .45; pointer-events: none; }

  /* Lobby, signed out: the room is browsable, the money panel is not live. */
  #lobbyOverlay.signed-out .prof-actions { opacity: .5; pointer-events: none; }
  .mini-hist .list-empty,
  .mini-lb .list-empty {
    color: var(--ink-4, rgba(255,255,255,.35));
    font-size: 11px; letter-spacing: .1em; padding: 8px 0;
  }

  /* A locked mode is built and browsable, not absent — it reads as "later",
     never as "broken". */
  .lmc-btn.locked{
    background: transparent; color: var(--ink-4, rgba(255,255,255,.4));
    border: 1px solid var(--hair-lit); cursor: not-allowed;
  }

  /* ── radio wheel ──
     Opens over the HUD but never blocks the crosshair: the middle is left
     empty so you can still see what you are aiming at while calling out. */
  #radioWheel{
    position:absolute; inset:0; z-index:9; display:none;
    align-items:center; justify-content:center; pointer-events:none;
  }
  #radioWheel.on{ display:flex; }
  .rw-ring{ position:relative; width:min(52vh,420px); aspect-ratio:1; }
  .rw-item{
    position:absolute; left:50%; top:50%; width:132px; margin:-19px 0 0 -66px;
    padding:9px 10px; text-align:center;
    background:rgba(6,7,9,.86); border:1px solid var(--hair-lit,rgba(255,255,255,.28));
    clip-path:var(--cut-sm,none);
    font-size:10.5px; letter-spacing:.14em; font-weight:700;
    color:var(--ink-2,rgba(255,255,255,.75));
    transition:background .1s, border-color .1s, color .1s, transform .1s;
  }
  .rw-item b{ display:block; font-size:9px; color:var(--ink-4,rgba(255,255,255,.35)); margin-bottom:3px; }
  .rw-item.sel{
    background:var(--amber,#ffd34d); border-color:var(--amber,#ffd34d); color:#000;
    transform:scale(1.06);
  }
  .rw-item.sel b{ color:rgba(0,0,0,.55); }
  .rw-hint{
    position:absolute; left:50%; bottom:14%; transform:translateX(-50%);
    font-size:9.5px; letter-spacing:.28em; color:var(--ink-4,rgba(255,255,255,.35));
  }
  .rw-hint kbd{
    border:1px solid var(--hair-lit,rgba(255,255,255,.28)); padding:1px 5px;
    font-family:var(--f-mono,ui-monospace,monospace);
  }

  /* ── cosmetics ──
     Locked entries stay visible: seeing what the next tier gives is the whole
     reason the level bar means anything. */
  .cosmetics{ display:flex; flex-wrap:wrap; gap:7px; margin-top:10px; }
  .cos-swatch{
    width:26px; height:26px; border-radius:50%; cursor:pointer; padding:0;
    border:2px solid transparent; background:transparent; position:relative;
    transition:transform .12s, border-color .12s;
  }
  .cos-swatch i{
    position:absolute; inset:4px; border-radius:50%; display:block;
  }
  .cos-swatch:hover{ transform:scale(1.12); }
  .cos-swatch.on{ border-color:var(--ink,#fff); }
  .cos-swatch.locked{ cursor:not-allowed; opacity:.3; }
  .cos-swatch.locked::after{
    content:attr(data-at); position:absolute; inset:0;
    display:flex; align-items:center; justify-content:center;
    font-size:8px; font-weight:800; color:var(--ink,#fff);
  }
  .cos-swatch.locked i{ opacity:.25; }
  .cos-next{
    margin-top:9px; font-size:10px; letter-spacing:.12em;
    color:var(--ink-4,rgba(255,255,255,.4));
  }

  /* Demo capture badge. Deliberately unmissable: footage that shows simulated
     balances must say so, and its absence is how you know a build is clean. */
  #demoBadge{
    position:fixed; left:50%; top:10px; transform:translateX(-50%); z-index:70;
    background:rgba(255,77,106,.14); border:1px solid rgba(255,77,106,.55);
    color:#ff8fa0; padding:6px 16px; border-radius:999px;
    font-size:10px; letter-spacing:.22em; font-weight:800; pointer-events:none;
    font-family:var(--f-mono,ui-monospace,monospace);
  }

  /* ── pre-match freeze ── */
  #freezeOverlay{
    position:absolute; left:50%; top:34%; transform:translate(-50%,-50%);
    z-index:8; text-align:center; pointer-events:none; display:none;
  }
  #freezeOverlay.on{ display:block; }
  #freezeOverlay .fz-label{
    font-size:11px; letter-spacing:.4em; color:var(--ink-3,rgba(255,255,255,.55));
    margin-bottom:8px;
  }
  #freezeCount{
    font-family:var(--f-display,'Bebas Neue',sans-serif);
    font-size:clamp(64px,12vh,110px); color:#fff; line-height:1;
    text-shadow:0 0 34px rgba(255,255,255,.35), 0 4px 14px rgba(0,0,0,.8);
  }
  #freezeCount.pop{ animation:fzPop .5s cubic-bezier(.2,.7,.3,1); }
  @keyframes fzPop{ from{ transform:scale(1.22); opacity:.4; } to{ transform:scale(1); opacity:1; } }

  /* SOL popup anchored at the kill. Same look as the stacked version; the
     float animation carries it up and away from the body. */
  .sol-popup.world{
    position:absolute; transform:translate(-50%,-50%);
    right:auto; z-index:7; text-align:center; white-space:nowrap;
  }

  /* Armed gate: the load is done, the doors wait for the visitor's hand.
     The prompt breathes so it reads as an invitation, not a stall. */
  #gate.armed{ cursor: pointer; }
  #gate.armed .gate-status{
    color:#fff; opacity:1; font-size:12px; letter-spacing:.4em;
    animation: gateInvite 1.6s ease-in-out infinite;
  }
  @keyframes gateInvite{ 0%,100%{ opacity:.45; } 50%{ opacity:1; } }
