/* THE FROST LOUNGE'S MACHINES -- their sheet, and the page base they were drawn on (moved out of lounge.html's
   <style> on 2026-10-29, verbatim, so cabinet.html?game=<id> can dress one machine without the room: lounge.html
   links this first and keeps only the room's own rules after it). */
  /* ---- the other half of the zoom lock: double-tap ----
     touch-action:manipulation means "panning and pinch still work, the extra
     legacy gestures don't", and double-tap-to-zoom is the one it drops. It is
     the only thing iOS Safari honours here - it ignores user-scalable=no, and
     the gesture events in the head script never fire for a double tap - so
     without this a stray two-tap on any chrome magnifies the page with no
     pinch-out to undo it.

     Deliberately the universal selector rather than html/body: the cabinets are
     absolutely-placed 100vh overlays and the effective touch behaviour is the
     intersection over the ancestor chain, so blanketing every element is what
     makes "no zoom anywhere in the lounge" actually true no matter where the
     tap lands. Specificity 0, so the boards' own touch-action:none below still
     wins where a canvas needs raw touches; that value is stricter anyway and
     already blocked double-tap over the games, which is why this only ever bit
     on the chrome around them. Scrolling the room list is unaffected. */
  *{ touch-action:manipulation; }

  body{
    margin:0; background:#0a0a0a; color:#fff8dc;
    /* Colour-emoji fonts sit ahead of the generic family for the same reason
       they do in css/style.css: on Windows, Segoe UI Symbol (monochrome) wins
       the fallback race before Segoe UI Emoji unless it is named outright,
       and the snowflakes here would render as flat grey glyphs. */
    font-family:ui-monospace,Consolas,
      "Apple Color Emoji","Segoe UI Emoji","Noto Color Emoji",monospace;
    display:flex; flex-direction:column; align-items:center; gap:14px;
    padding:24px 12px;
  }
  h1{ font-size:1rem; letter-spacing:2px; color:#ffe873; margin:0; }
  /* Styled to echo .navbar__btn on the main site so the trip back feels like
     the same place, without pulling in the whole stylesheet. */
  .back{
    font-size:.78rem; letter-spacing:1px; text-decoration:none;
    color:#fff8dc; padding:6px 14px; border-radius:6px;
    background:linear-gradient(180deg,#5a4a00,#2a2300);
    border:2px outset #e6c200;
    text-shadow:0 1px 0 #050505;
    transition:filter .12s ease, transform .08s ease;
  }
  .back:hover{ filter:brightness(1.25); color:#fff; transform:translateY(-1px); }
  .back:active{ border-style:inset; transform:translateY(1px); }
  .back:focus-visible{ outline:2px solid #ffd800; outline-offset:2px; }
  p{ font-size:.8rem; color:#d6b000; margin:0; text-align:center; }
  canvas{
    /* Native pixel buffer, upscaled by CSS - never resample pixel art. */
    image-rendering:pixelated;
    width:min(680px,96vw); height:auto;
    /* No frame: the room reads as the page itself, not a picture hung on it.
       Every other canvas here (#bs, #sn, #wc, #wm) sets its own border, since
       those really are screens inside a cabinet. */
    background:#0a0a0a; cursor:pointer;
  }
  /* ================= BRICK SMASH overlay =================
     A modal cabinet that covers the room while you play. Kept in this file's
     own idiom (deep-blue chrome, ridged cyan borders, monospace) so it reads
     as the same place as the lounge, not a bolted-on widget. */
  /* THE ONLY CABINET YOU STEER WITH A THUMB HAS TO OUTRANK ITS OWN CABINET.
     .arcade below is a full-screen z-index 50 overlay with an opaque backdrop,
     and js/frost-stick.js puts the thumbstick and pedals at z-index 3 — so
     CRAZY UBER called FrostStick.mode("drive"), the controls came up exactly
     as asked, and the cabinet then painted straight over them. A phone got a
     car it could not steer; a desktop never noticed, because the keys work.
     Scoped to #uber alone: every other machine here is played with its own
     on-screen controls and SHOULD keep the stick underneath. */
  body:has(#uber.is-open){ --fstick-z:60; }
  .arcade{
    position:fixed; inset:0; z-index:50;
    display:none; align-items:center; justify-content:center;
    padding:16px; box-sizing:border-box;
    background:rgba(6,6,6,.94);
    /* The room behind is busy; a soft blur keeps the eye on the game. */
    backdrop-filter:blur(3px);
  }
  .arcade.is-open{ display:flex; }
  /* The windowed cabinet: a bordered box centred on the dimmed room. The two
     make-a-thing fixtures - THE DECKS and the WARDROBE - live in it, because a
     sequencer and a dress-up screen are things you lean in to fiddle with, not
     games that want the whole viewport. The four actual games opt out via
     `.is-game` below, which hands the board the full screen. */
  .arcade__cab{
    width:min(720px,100%); max-height:100%; overflow:auto;
    box-sizing:border-box; position:relative;
    display:flex; flex-direction:column; gap:12px;
    padding:14px; border-radius:10px;
    background:linear-gradient(180deg,#0b2a40,#061826);
    border:3px ridge #d6b000;
    box-shadow:0 0 0 2px #050505, 0 0 26px rgba(255,216,0,.35);
  }
  /* ---- the games: full-viewport, always ----
     There is no windowed cabinet chrome for a game to fall back to. See
     js/arcade-immersive.js for why: the real OS fullscreen it also requests is
     best-effort, but this layout is the one thing "nothing can take away", so
     it is simply what a game cabinet IS rather than a mode a player can toggle
     out of mid-game. */
  .arcade.is-game{ padding:0; background:#050505; backdrop-filter:none; }
  .arcade.is-game .arcade__cab{
    width:100%; height:100%; max-height:none; overflow:hidden;
    gap:0; padding:0; border:0; border-radius:0;
    background:#050505; box-shadow:none;
  }
  .arcade__marquee{
    text-align:center; font-size:1rem; letter-spacing:4px; font-weight:bold;
    color:#fff8dc; padding:8px; border-radius:6px;
    background:linear-gradient(180deg,#5a4a00,#2a2300);
    border:2px outset #e6c200; text-shadow:0 0 8px rgba(255,216,0,.7);
  }
  /* BRICK SMASH? wears its question. Two coloured clones of the title sit dead
     over it and, every few seconds, shudder apart into a red/cyan split - the
     same broken-signal tell the in-game countdown uses - so the cabinet reads
     as slightly wrong before you've even pressed start. */
  .arcade__marquee--haunt{ position:relative; overflow:hidden; }
  .arcade__marquee--haunt::before,
  .arcade__marquee--haunt::after{
    content:attr(data-text); position:absolute; inset:0; padding:8px;
    pointer-events:none; opacity:0; mix-blend-mode:screen;
  }
  .arcade__marquee--haunt::before{ color:#ff3c6e;
    animation:marqueeGhost 4.4s steps(2) infinite; }
  .arcade__marquee--haunt::after{ color:#ffd800;
    animation:marqueeGhost 4.4s steps(2) infinite reverse; }
  @keyframes marqueeGhost{
    0%,84%,100%{ transform:translate(0,0); opacity:0; }
    86%{ transform:translate(-2px,1px); opacity:.7; }
    90%{ transform:translate(2px,-1px); opacity:.7; }
    94%{ transform:translate(-1px,0); opacity:.4; }
  }
  @media (prefers-reduced-motion:reduce){
    .arcade__marquee--haunt::before,.arcade__marquee--haunt::after{ animation:none; }
  }
  .arcade__body{ display:block; position:absolute; inset:0; }
  .arcade__stage{ display:block; }
  /* Floats over the board rather than sitting in flow above it - the safe-area
     insets keep it clear of a notch or the home indicator in landscape. */
  .arcade__hud{
    position:absolute; z-index:3; display:flex; flex-wrap:wrap; gap:2px 14px;
    justify-content:flex-start;
    top:calc(8px + env(safe-area-inset-top));
    left:calc(10px + env(safe-area-inset-left));
    /* AND IT STOPS SHORT OF THE SETTINGS GEAR. The gear is 34px at a 10px inset
       in the same corner (see .arcade__pill), and this strip was shrink-to-fit
       with nothing to stop it: on a 320px phone HELIS/GUN ran straight under it
       and the word GUN lost its N. A CEILING rather than a `right`, because a
       box with both insets set stretches, and this is a pill that hugs its
       content - so it stays shrink-to-fit and simply cannot reach the gear, and
       the row WRAPS inside its own corner instead. 64 is this strip's own 10 px
       inset plus the gear's 44 plus a 10 px gap between them.

       On any wider screen the ceiling is roomier than the one row and nothing
       about the layout moves. js/heli-attack.js measures this strip's real
       height rather than assuming one line, so the painted readouts get out of
       the way of two as readily as of one.

       box-sizing WITH IT, or the ceiling is the CONTENT box and the pill lands
       22px of padding and 2px of border past where it was told to stop - which
       is most of the gap and put it back under the gear. */
    box-sizing:border-box;
    max-width:calc(100% - 64px - env(safe-area-inset-left)
                          - env(safe-area-inset-right));
    font-size:.7rem; letter-spacing:1px; color:#d6b000; padding:6px 11px; border-radius:20px;
    background:rgba(4,4,4,.72); border:1px solid rgba(214,176,0,.35);
    backdrop-filter:blur(2px); pointer-events:none;
  }
  .arcade__hud b{ color:#ffd800; }
  /* ---- Heli Attack's armour readout ----
     It used to be painted on the canvas, at 14,14 - directly under this strip,
     which sits over the board rather than above it. The strip won, and ate the
     label and most of the bar. Moving the painted bar down would only have
     fixed it at one size: the strip is laid out in CSS pixels and the board is
     scaled to fit, so how much canvas the strip covers changes with the
     viewport. A readout that lives IN the strip cannot be covered by it at any
     scale, and the strip was already carrying SCORE/WAVE/GUN. The canvas keeps
     TIME and LIFT, which clear the strip and read as a pair without it. */
  /* The label and the number sit ON the fill, which is how the painted one
     read and is also what keeps this affordable: a bar with the words beside
     it added 175px to the strip, and the strip is a fixed CSS width over a
     board that scales - every pixel it gains covers more canvas the smaller
     the screen gets, and the weapon cells the canvas paints at x 282 are what
     it would reach first. Overlaid, the whole readout costs ~104px. */
  .hl__armour{
    position:relative; display:flex; align-items:center; gap:6px;
    min-width:104px; padding:2px 7px; border-radius:2px;
    background:rgba(6,6,6,.72); border:1px solid rgba(255,232,115,.5);
  }
  .hl__bar{ position:absolute; inset:0; overflow:hidden; }
  /* Colour is set from the game - it keys on the same thirds the painted bar
     keyed on, so "I am in trouble" still reads without looking at the number. */
  .hl__bar i{ display:block; height:100%; width:100%; background:#ffd800;
    transition:width .12s linear; }
  /* Above the fill, and legible over both the lit and the unlit part of it. */
  .hl__armour em, .hl__armour b{
    position:relative; z-index:1; color:#fff8dc; text-shadow:0 1px 2px #050505;
  }
  .hl__armour em{ font-style:normal; letter-spacing:1px; }
  #hl-hp{ margin-left:auto; font-variant-numeric:tabular-nums; }
  @media (prefers-reduced-motion: reduce){ .hl__bar i{ transition:none; } }
  /* The strip loses the word and keeps the bar, the way the maul's resource
     strip drops its labels and keeps its icons at this width. A phone letter-
     boxes the board smallest, which is where a fixed-width strip covers the
     most canvas, and the bar is what the corner is read by anyway. */
  @media (max-width:560px), (max-height:520px){
    .hl__armour{ min-width:62px; gap:0; }
    .hl__armour em{ display:none; }
  }
  /* A centring box rather than a bare frame: Brick Smash's board is portrait
     and Snake's is square, so filling a landscape viewport with either would
     stretch the bitmap. They scale up as far as fits and letterbox. The maul
     opts out via its own #wm-canvas-3d rule below - it has a camera, and
     framing is that camera's job. */
  .arcade__screen{
    position:absolute; inset:0; line-height:0;
    display:flex; align-items:center; justify-content:center;
  }
  /* Beats the file-wide canvas{} rule: this one is a smooth game, not pixel
     art, and it sizes to its stage instead of the 680px room canvas. */
  #bs-canvas{
    image-rendering:auto;
    max-width:100%; max-height:100%; width:auto; height:auto; display:block;
    border:0; background:#0a0a0a; cursor:none;
    /* Own the touch: dragging the paddle must never scroll or zoom the page. */
    touch-action:none;
  }
  /* Snake is grid/blocky, so unlike bs-canvas it keeps the file-wide
     pixelated rendering and pointer cursor - just needs the modal's sizing. */
  #sn-canvas{
    max-width:100%; max-height:100%; width:auto; height:auto; display:block;
    border:0; background:#0a0a0a;
    /* Own the touch: swipe-to-steer must never scroll or zoom the page. */
    touch-action:none;
  }
  /* Pinball is a smooth game like Brick Smash (round bumpers, a lit ball), so it
     turns the file-wide pixelated rendering off and sizes to its stage. */
  #pb-canvas{
    image-rendering:auto;
    max-width:100%; max-height:100%; width:auto; height:auto; display:block;
    border:0; background:#0a0a0a;
    /* Own every gesture: two-thumb flippers must never scroll or zoom the page. */
    touch-action:none;
  }
  /* Heli Attack is a smooth game too - a stick figure, a rotor disc and lit
     explosions - and it is the widest board in the room, so it leans hardest
     on the letterboxing every cabinet canvas gets here. */
  #hl-canvas{
    image-rendering:auto;
    max-width:100%; max-height:100%; width:auto; height:auto; display:block;
    border:0; background:#040c16;
    /* Own every gesture: a thumb on the run pad and a thumb aiming must
       never scroll or zoom the page out from under the fight. */
    touch-action:none;
  }
  /* ================= HELI ATTACK on an upright phone =================
     THE PROBLEM IT SOLVES. Every cabinet here letterboxes a fixed board into
     the viewport, and js/arcade-immersive.js asks the OS to lock landscape so
     a phone gets the same picture a desktop does. That lock is best-effort and
     iPhone Safari does not have it at all, so an upright phone fell back to
     the letterbox and no further: a 960x540 board in a 390x844 window is 219px
     tall and uses a QUARTER of the screen, with the man 15 real px high.

     THE PICTURE USED TO BE TURNED - the frame laid out landscape and rotated a
     quarter turn, which handed back the exact picture the OS would have given
     and asked the player to turn the phone. It read as a game lying on its side
     with an upright menu in front of it, so it is gone; there is nothing left
     here to hold, which is why this section is one rule shorter than the
     comment above it.

     THE BOARD IS PORTRAIT INSTEAD, and there is no CSS in it at all: a narrower
     window onto the valley is a pair of numbers, not a layout - see fitPhone()
     and PORT_W in js/heli-attack.js. The canvas letterboxes into the same
     .arcade__screen it always did, the readouts float over the sky the portrait
     board puts above the map, and the deck below sits on the rock apron it puts
     under the floor. .is-upright is on .arcade__cab for anything that ever does
     need to know; nothing does yet. */

  /* ---- the thumb deck ----
     Anchored to the SCREEN's corners, not the picture's, and sized in real px
     so a button is a thumb whatever the board is scaled to. It sits over the
     picture by design: on the portrait board the bottom band is the rock apron
     below the snow line, which is the one part of the valley nothing can ever
     be hiding in, and on a landscape phone it lands on the ~75px letterbox
     margins plus a lap of sky and snow. */
  .hl__deck{
    position:absolute; inset:0; pointer-events:none; z-index:4;
    display:flex; align-items:flex-end; justify-content:space-between;
    padding:0 calc(10px + env(safe-area-inset-right))
            calc(14px + env(safe-area-inset-bottom))
            calc(10px + env(safe-area-inset-left));
  }
  .hl__deck[hidden]{ display:none; }
  .hl__deck-l{ display:flex; flex-direction:column; gap:10px; }
  .hl__deck-row{ display:flex; gap:10px; }
  .hl__key{
    pointer-events:auto; touch-action:none; -webkit-tap-highlight-color:transparent;
    width:68px; height:68px; border-radius:50%;
    display:flex; align-items:center; justify-content:center;
    font:bold .78rem Impact,Haettenschweiler,sans-serif; letter-spacing:1px;
    /* undo .arcade__screen's line-height:0, which is only there to kill the
       canvas baseline gap and would otherwise give every label zero height */
    line-height:1;
    color:rgba(210,238,250,.78); background:rgba(6,6,6,.42);
    border:2px solid rgba(255,232,115,.4);
    /* the picture must stay readable THROUGH the deck */
    backdrop-filter:blur(1px);
  }
  .hl__key--sm{ width:50px; height:50px; font-size:.62rem; }
  /* the jump BAR, the shape the in-canvas pad settled on: it spans both run
     buttons so a thumb rolling up off either one lands on it */
  .hl__key--bar{ width:146px; height:54px; border-radius:27px; font-size:.86rem; }
  /* .is-on rather than :active — a held thumb that slides off the button
     still counts as held, the same way the in-canvas pad's pointer roles do,
     so the class is driven from js and not from the pointer's own state. */
  .hl__key.is-on{
    color:#fff8dc; background:rgba(255,216,0,.26); border-color:#ffd800;
  }
  /* Faint CRT scanlines, echoing the video-vault TVs on the main site. */
  .arcade__screen::after{
    content:""; position:absolute; inset:0; pointer-events:none;
    background:repeating-linear-gradient(
      to bottom, rgba(0,0,0,0) 0 2px, rgba(0,0,0,.16) 2px 3px);
  }
  .arcade__msg{
    position:absolute; inset:0; display:flex; pointer-events:none;
    flex-direction:column; align-items:center; justify-content:center; gap:10px;
    text-align:center; color:#fff8dc; padding:16px; box-sizing:border-box;
    background:rgba(6,6,6,.72);
    /* Undo the screen's line-height:0 (that one is only there to kill the
       canvas baseline gap). Inherited, it gives every line here zero height,
       so the title and the blinking hint pile up on the same pixel row. */
    line-height:1.4;
  }
  .arcade__msg[hidden]{ display:none; }
  .arcade__msg b{ font-size:1.1rem; letter-spacing:3px; color:#ffd800;
    text-shadow:0 0 10px rgba(255,216,0,.6); }
  .arcade__msg small{ font-size:.72rem; color:#d6b000; letter-spacing:1px; }
  .arcade__msg .blink{ animation:arcadeBlink 1s steps(1) infinite; }
  @keyframes arcadeBlink{ 50%{ opacity:.25; } }
  @media (prefers-reduced-motion:reduce){
    .arcade__msg .blink, .arcade__scores li.is-new .who{ animation:none; }
  }
  /* ---- CRAZY UBER's two floating pieces. The dial is js/drive-gauge.js's
     SVG, hung in the same top-right corner _drive.html hangs it in, and it is
     sized here rather than in that file because the instrument derives its
     own face and takes its box from whatever it is mounted in. The attribution
     is not decoration: the city is ODbL survey data and the notice travels
     with anything drawn from it. ---- */
  /* THE MOUNT SETS --dgauge-size AND NOTHING ELSE, which is js/drive-gauge.js's
     own contract - the instrument sizes ITSELF off that variable and the peg it
     hangs on only says where. Sized here with width/height instead, the box
     shrank on a phone and the 132px dial inside it did not: the needle, the 60,
     the 80 and both telltales ran off the right edge of a landscape frame, and
     it looked like a clipping bug in the cabinet rather than the gauge being
     asked in a language it does not speak. _drive.html's #speed does it right
     and this is the same two lines. */
  /* z-index 5 against the street blades' 4, which is _drive.html's own pairing:
     the dial is the one thing that must never be painted over, and the blades
     now share this box. */
  /* ---- IT IS THE CAR'S INSTRUMENT AND IT GOES WITH THE CAR. The owner,
       2026-08-19: "when you are out of the car it is still has the
       spedometer". On foot it read 0 MPH and a D gear at a man standing on a
       pavement — the one thing on the glass still claiming he was driving —
       and it holds the corner the walk chrome moves into. `.ub-foot` is set
       on #uber by js/crazy-uber.js's own walking() test. ---- */
  #uber.ub-foot .ub__dial{ display:none; }
  .ub__dial{ position:absolute; top:10px; right:12px; z-index:5;
    pointer-events:none; --dgauge-size:132px; }
  @media (max-height:520px){ .ub__dial{ --dgauge-size:96px; } }
  /* the gear pill, in the LEFT corner because the dial owns the right.

     IT IS THE ONLY PILL IN THIS CABINET AGAIN, which is what let three rules
     collapse into one. There was an X above it and a stack of reasoning under
     it about two pills sharing a column - a `.arcade__pill + .arcade__pill`
     rule further down this sheet re-seats a SECOND pill against the right
     edge, which is right for the eight cabinets whose right corner is empty
     and wrong here where it would land under the dial, so the exit needed an
     id-strength rule to stay put and the gear needed a 50px offset to sit
     clear beneath it. With the X gone none of that is true: one pill, one
     seat, the corner it always wanted.

     STATED AT (1,2,0) ANYWAY. The sibling rule is still in the sheet and
     still outranks a bare class on source order, and this cabinet is one
     added pill away from that fight starting again. The id costs nothing and
     it is the difference between a control in the corner and a control
     underneath the speedometer.

     WHY NOT THE RIGHT CORNER, now that portrait has taken the dial out of it:
     because it is only empty in portrait. Landscape still hangs the dial and
     the taxi meter there, and a control that changes corners when you turn
     the phone is one a thumb has to hunt for. */
  #uber .arcade__pill.ub__gear{
    right:auto; left:calc(10px + env(safe-area-inset-left));
  }
  /* AND BOTH PILLS COME UP OFF THE BLADES. `.arcade__pill` is z-index 4 and so
     is js/drive-signs.js's layer, and the sign layer is appended to the screen
     at runtime - so it is later in the DOM and wins the tie. The exit never
     showed it because the top-left corner is above the rail; the gear sits
     exactly in the band the pink venue marks sweep through, and the first
     landscape shot of it had a blade painted across its face. Same argument
     the dial's z-index 5 is written on, one step further: a LABEL may be
     covered by a CONTROL, never the other way round. Below .arcmenu's 90,
     which is meant to cover both. */
  #uber .arcade__pill{ z-index:8; }
  /* ---- GET OUT / GO IN / GET IN — ROADMAP-CITY C6 ----
     BOTTOM CENTRE, and that is the only free strip. js/frost-stick.js seats
     the steering stick bottom-LEFT and the pedals bottom-RIGHT while driving,
     and the walk stick bottom-RIGHT on foot — so every corner is a thumb in at
     least one of the two modes and the middle is a thumb in neither. It also
     puts the prompt where the player is already looking: at the door, not at
     the chrome column.

     Z-INDEX 8 IS THE PILLS' OWN, and for their reason: js/drive-signs.js's
     blade layer is appended to this screen at runtime and so wins any tie at
     4. A label may be covered by a control, never the other way round. Below
     .arcmenu's 90, which is meant to cover everything. */
  .ub__act{
    position:absolute; z-index:8;
    left:50%; transform:translateX(-50%);
    bottom:calc(14px + env(safe-area-inset-bottom));
    font:700 .78rem/1 var(--font-mono, ui-monospace, Menlo, Consolas, monospace);
    letter-spacing:2px; text-transform:uppercase; white-space:nowrap;
    /* CAPPED, BECAUSE THE LABEL CARRIES A VENUE NAME AND VENUE NAMES ARE
       SOMEBODY ELSE'S DATA. "GO IN · THE DIZZY ROOSTER" is already 25
       characters and the roster has longer; unbounded, a centred button grows
       both ways until it reaches the radar in the bottom-left corner and the
       pedals in the right. It may still overlap the radar's rim, and that is
       the sanctioned direction — this sheet's own rule, three rules up: a
       LABEL may be covered by a CONTROL, never the other way round. */
    max-width:min(52vw, 360px); overflow:hidden; text-overflow:ellipsis;
    color:#fff8dc; background:rgba(6,6,6,.82);
    border:2px outset #4a4a30; border-radius:8px;
    padding:11px 18px; cursor:pointer;
    text-shadow:0 0 8px rgba(255,216,0,.5);
    box-shadow:0 0 14px rgba(255,216,0,.22);
  }
  .ub__act[hidden]{ display:none; }
  .ub__act:hover{ filter:brightness(1.25); }
  .ub__act:active{ border-style:inset; }
  .ub__act:focus-visible{ outline:2px solid #ffd800; outline-offset:2px; }
  /* A THUMB IS 44px AND A CURSOR IS NOT. Same tier the pills grow on. */
  @media (pointer:coarse){
    .ub__act{ font-size:.9rem; padding:14px 22px;
      bottom:calc(max(12px,env(safe-area-inset-bottom)) + 6px); }
  }
  /* THE PEDALS COME UP OVER THE HOME BAR ON A SHORT SCREEN (frost-stick.js's
     own `+34px` tier at max-height:430px), and the button has to clear the
     same bar or it lands under it. */
  @media (pointer:coarse) and (max-height:430px){
    .ub__act{ bottom:calc(max(12px,env(safe-area-inset-bottom)) + 34px); }
  }
  /* THE TAXI HUD HAS TO GO ROUND BOTH OF THEM. js/drive-fares.js puts its
     clock top left and its meter top right because that is where the
     reference's are, and in this cabinet both corners are already spoken for:
     the exit pill (34px at a 10px inset) and the dial (132px, 96px on a short
     screen). Rather than move controls a player reaches for, the HUD takes the
     push — the same --var idiom #speed uses to get off the thumbstick.
     Clock: 44px clears the pill and leaves a gap. Meter: 142/106px is the
     dial's own top offset plus its face. */
  .dct{ --dct-time-left:44px; --dct-meter-top:142px; }
  @media (max-height:520px){ .dct{ --dct-meter-top:106px; } }

  /* ---- THE RADAR'S SEAT. js/drive-map.js hangs itself off two custom
     properties for the same reason drive-fares.js does: this file is the only
     one that knows what else is on this glass.

     BOTTOM LEFT is where a radar goes and where forty years of games have put
     one, and on a DESKTOP that corner is genuinely empty — the wheel and the
     pedals are `display:none` until a thumb asks for them. So the default
     stands and the move is gated on the thumb cluster actually being up,
     exactly the way the dial's is two blocks down (and for the reason written
     out there: a short desktop window is short, has no wheel, and furniture
     that fled the corner for no reason is a bug).

     AND IT IS NOT GATED ON HEIGHT, which is where this rule parts company with
     the dial's. The dial's collision was VERTICAL — three things stacked in the
     right-hand column of a 265px screen — so it only had to move when the
     screen was short. A steering wheel is in the bottom-left corner at every
     height, so a height gate would leave the radar buried under it on an
     upright phone, which is exactly the case arcade-immersive.js cannot fix
     (iOS Safari has no orientation lock).

     180px INBOARD is the wheel's own footprint out of js/frost-stick.js: WHEEL
     is 168px at left:max(14px,safe), so it ends at 182 and a radar starting at
     12+180=192 clears it. It ends at 288, a long way short of the dial's
     inboard seat, which begins 216px off the RIGHT edge. The bottom band on an
     852x265 phone then reads, left to right: wheel, radar, dial, pedals. */
  @media (pointer:coarse){
    .dmap{ --dmap-left:calc(max(12px, env(safe-area-inset-left)) + 180px); }
  }
  body:has(.fstick--on) .dmap{
    --dmap-left:calc(max(12px, env(safe-area-inset-left)) + 180px); }

  /* ================= THE SHORT-SCREEN TIER =================
     The argument is written out once, at the foot of js/frost-stick.js's CSS
     block. In one line: iOS Safari in landscape keeps its URL and tab rows up
     and hands the page about 265px while REPORTING 393, so every max-height
     tier in this cabinet — including the 520px one two lines above — is tuned
     against a screen a third taller than the driver has. 100svh is the small
     viewport, the band actually on the glass, so the cabinet sizes to that.

     THE RIGHT EDGE CANNOT HOLD ALL THREE, and shrinking is not enough to make
     it. Measured here at 852x265 with only the sizes fixed: the dial ran 10-123
     down that edge, the meter 116-176 under it, and the pedal cluster starts at
     132 — a 44px collision between the money and the accelerator, with the dial
     and the meter already 7px into each other by design. Dial + meter + pedals
     is 292px of furniture in a 265px column and no amount of type-scaling pays
     that back.

     SO THE DIAL LEAVES THE CORNER, which is not a new idea — it is the one
     _drive.html's #speed already reached and documented: "LANDSCAPE it slides
     INBOARD along the floor, into the gap between the two thumbs." That gap is
     real and it is still measured — the wheel ends at 128 and the pedals begin
     at 708 — and the dial is 72px of it. The cabinet never got that treatment
     because its dial is pinned top-right; this is the same two lines. The top
     right is then the meter's alone and the collision is gone rather than
     shuffled, and the driver reads speed where a dashboard puts it.

     Caps are the values the 520px tier sets and fractions are cap/390, so at
     390svh nothing renders differently than it did before this block.

     THE SIZE AND THE MOVE ARE GATED DIFFERENTLY, and that split is the point.
     Shrinking is safe on any short screen. MOVING is only right when there is
     a thumb cluster down there to get out of the way of — a 500px-tall desktop
     window is short, has no wheel and no pedals, and a dial that fled to the
     middle of its floor for no reason is a bug. So the move carries the same
     pointer:coarse gate (and the same :has() repeat for a hybrid that grows a
     stick mid-session) that _drive.html's #speed carries, for the same reason
     and in the same order. */
  @media (max-height:520px){
    .ub__dial{ --dgauge-size: clamp(72px, calc(var(--dhud-h, 100svh) * 96 / 390), 96px); }
  }
  /* THE MOVE ALSO CARRIES A WIDTH FLOOR, and the SHRINK above deliberately
     does not. THE UPRIGHT PHONE tier below gates on max-width:560px and seats
     this same dial at top:102px in its three-column band, so between 561px and
     that gate the two tiers would both be talking about the dial: 560x500 on a
     coarse pointer fires BOTH, and measured there the dial came out with
     top:102px from the tier below AND bottom:42px still standing from this one
     — pinned at both edges and stretched down the screen, because the upright
     rule sets a top and never releases the bottom. The floor makes the two
     mutually exclusive rather than making the upright rule reset three
     properties it should not have to know about. It is the same argument the
     comment above already makes: shrinking is safe on any short screen, and
     MOVING is only right when there is a thumb cluster to clear — under 561px
     the upright tier has already rearranged the whole band and there is no
     landscape cluster left to get out of the way of. */
  @media (pointer:coarse) and (max-height:520px) and (min-width:561px){
    /* 216px inboard is _drive.html's measured number for this exact cluster:
       the pedals are the wider of the two thumb controls and this clears them. */
    .ub__dial{ top:auto; right:calc(12px + 216px);
      bottom:calc(max(12px, env(safe-area-inset-bottom)) + 30px); }
    /* and the meter takes the whole corner back: its push was "the dial's own
       top offset plus its face", and the dial is no longer up there */
    .dct{ --dct-meter-top:0px; }
  }
  @media (max-height:520px) and (min-width:561px){
    body:has(.fstick--on) .ub__dial{ top:auto; right:calc(12px + 216px);
      bottom:calc(max(12px, env(safe-area-inset-bottom)) + 30px); }
    body:has(.fstick--on) .dct{ --dct-meter-top:0px; }
  }
  /* ================= THE UPRIGHT PHONE =================
     Every tier above gates on HEIGHT, and a portrait phone is TALL — so
     upright kept getting the full desktop cabinet on 390px of width. The
     owner's verdict on it, after two passes that only shrank things:
     *"the vertical crazy uber screen still is clogged … none of these
     should get in the way of the center of the screen that needs to be
     reserved for the actual gameplay"*, with the layout named outright —
     arrow top centre, radar top right, score and time top left, and the
     place names off the glass.

     So on a narrow screen the top band is THREE COLUMNS and the rest of the
     glass is road:

       LEFT    the clock, and the taxi meter stacked under it. The meter is
               the score, and a score belongs beside the time rather than
               diagonally opposite it. 66px is where the clock and its GAME
               TIME label bottom out; --dct-meter-left is the var
               js/drive-fares.js exposes for taking the box off its own
               right-hand anchor.
       CENTRE  the arrow, flat, as js/drive-arrow.js's compass. That corner
               was the street rail's and the rail is gone (below).
       RIGHT   the radar, in the corner forty years of games did NOT put one
               — because on this screen the bottom-left corner it wants is
               under a steering wheel and the seat that dodged it (180px
               inboard, up 224) is under the pedals or in the road. The dial
               hangs beneath it at 72px, which is the only thing on this
               list the owner did not name: an instrument, kept, out of the
               way. 10 + 84 + 8 = 102 is the radar's own face plus air.

     AND THE PLACE NAMES GO. js/drive-signs.js's whole layer — the top rail,
     the pink venue marks and the street blades out on the tarmac — is the
     single biggest thing between the player and the road on 390px of width,
     and it is the one layer that is pure labelling: the fare's own name is
     on the call ribbon and its distance is under the arrow, so nothing that
     tells you what to do is lost with it. It stays on every wider screen,
     where there is room for a city to be signposted. Scoped to #uber so the
     rule cannot reach a walked street page that happens to be narrow. */
  @media (max-width:560px){
    /* under the radar now, not in the corner — see the RIGHT column above */
    .ub__dial{ top:102px; --dgauge-size:72px; }
    .dct{ --dct-meter-top:66px;
          --dct-meter-left:max(12px, env(safe-area-inset-left)); }
    /* the arrow's badge sits in the band the rail used to hold */
    .darw{ --darw-top:0px; --darw-size:62px; }
    #uber .dsign-layer{ display:none; }
    /* THE GEAR STAYS IN THE CORNER UPRIGHT TOO, and it used to be pushed down
       to 184 - below the clock and the whole meter - because the corner was
       the X's and the gear was the second thing down the column. It is the
       first thing now. The clock is unaffected: `--dct-time-left:44px` seats
       it at x=56 in both orientations, which was always measured to clear a
       34px pill at a 10px inset, and one pill is what is there. */
  }
  /* the radar's corner. Both offsets it defaults to have to be released or
     they win the axis — see the note in js/drive-map.js. Both selector forms,
     AFTER the pair above, because body:has() outranks the bare class and
     source order is what lets these win at all. */
  @media (max-width:560px){
    .dmap, body:has(.fstick--on) .dmap{
      --dmap-left:auto;
      --dmap-bottom:auto;
      --dmap-right:max(12px, env(safe-area-inset-right));
      --dmap-top:max(10px, env(safe-area-inset-top)); }
  }
  .ub__attr{ position:absolute; right:10px; bottom:8px; margin:0; z-index:2;
    font-size:.58rem; letter-spacing:1px; color:#7fa8c0; opacity:.75;
    pointer-events:none; }
  /* ================= THE CLOCK-OFF QUESTION =================
     Two buttons over the middle of the glass. Everything about the box is
     downstream of one decision: it is a QUESTION, not a screen. It does not
     dim the city (.arcade__msg already owns that scrim and uses it for the
     payout plate a second later), it does not take the thumb controls down,
     and it sits above the marquee so a message arriving underneath cannot
     swallow it — z-index over .dct's 6, since the meter is fixed/absolute
     across the whole screen too.

     pointer-events:auto because the screen's chrome is otherwise transparent
     to the pointer: the canvas underneath is the game. */
  .ub__ask{
    position:absolute; z-index:7; left:50%; top:50%;
    transform:translate(-50%,-50%); width:min(340px, 82%);
    box-sizing:border-box; padding:14px 16px; border-radius:10px;
    text-align:center; pointer-events:auto; line-height:1.4;
    /* NEARLY OPAQUE, and it is the venue blades that set the number: they
       are DOM labels UNDER this panel (z 4 against 7) and at .88 the street
       names read straight through the question. */
    background:rgba(4,4,4,.96); border:1px solid rgba(214,176,0,.45);
    box-shadow:0 10px 30px rgba(0,0,0,.45);
    /* NO backdrop-filter, unlike .arcade__scores beside it, and it is not an
       oversight: that aside is a 150px box in a corner and this is a panel over
       the middle of a live 3D city. A blur there is a full-surface read-back
       every frame of a game that is still running underneath — and the city
       IS still running, which is this panel's whole design. It cost measurably
       enough that the C6 walk gate changed its answer; 0.88 of an already dark
       colour is what makes the type readable, not the blur. */
  }
  .ub__ask[hidden]{ display:none; }
  .ub__ask-q{ margin:0 0 5px; font-size:1rem; letter-spacing:3px; color:#ffd800;
    text-shadow:0 0 10px rgba(255,216,0,.6); }
  .ub__ask-s{ margin:0 0 12px; font-size:.72rem; letter-spacing:1px; color:#d6b000; }
  /* WRAPS, and that is the phone tier rather than a media query: the two
     answers are a long pair of words and a 390px phone in portrait has a
     letterboxed screen barely wider than one of them. */
  .ub__ask-row{ display:flex; gap:8px; justify-content:center; flex-wrap:wrap; }
  .ub__ask-row .arcade__ok{ font-size:.72rem; padding:8px 14px; }
  /* THE SECOND ANSWER IS NOT THE SECOND-BEST ANSWER. Both are real choices —
     one ends the night, one keeps it — so the alternative is quieter in
     colour and identical in size, and never a link or a small x. */
  .ub__ask-alt{ background:linear-gradient(180deg,#123f5a,#0a2437); }
  @media (max-width:560px){
    .ub__ask{ width:min(300px, 88%); padding:12px 12px; }
    .ub__ask-q{ font-size:.86rem; letter-spacing:2px; }
    .ub__ask-s{ font-size:.66rem; margin-bottom:10px; }
    .ub__ask-row .arcade__ok{ font-size:.66rem; padding:7px 11px; }
  }
  /* The new-high-score initials entry re-enters pointer events. */
  .arcade__enter{ pointer-events:auto; display:flex; gap:8px; align-items:center; }
  .arcade__enter input{
    width:4.5em; text-transform:uppercase; text-align:center;
    font:bold 1.1rem ui-monospace,Consolas,monospace; letter-spacing:6px;
    color:#0a0a0a; background:#fff8dc; border:2px inset #e6c200;
    border-radius:4px; padding:4px 6px;
  }
  .arcade__hint{ font-size:.66rem; color:#8a7200; letter-spacing:1px;
    text-align:center; margin:0; }
  .arcade__scores{
    position:absolute; z-index:3; width:150px;
    max-height:calc(100% - 96px); overflow-y:auto;
    top:calc(8px + env(safe-area-inset-top));
    right:calc(52px + env(safe-area-inset-right));
    padding:6px 9px; border-radius:8px;
    background:rgba(4,4,4,.72); border:1px solid rgba(214,176,0,.35);
    backdrop-filter:blur(2px);
  }
  .arcade__scores h3{
    margin:0 0 8px; font-size:.78rem; letter-spacing:2px; color:#ffe873;
    text-align:center;
  }
  .arcade__scores ol{ margin:0; padding:0; list-style:none;
    font-size:.76rem; color:#d6b000; }
  .arcade__scores li{
    display:flex; justify-content:space-between; gap:8px;
    padding:3px 0; border-bottom:1px dotted rgba(214,176,0,.25);
  }
  .arcade__scores li .rank{ color:#8a7200; width:1.4em; }
  /* Board names run to 12 chars now that accounts sign their own rows, and
     this aside is 150px wide - a long name trims to an ellipsis rather than
     shoving the points out of the box. min-width:0 is what lets flex shrink
     the cell below the name's own width at all. */
  .arcade__scores li .who{ flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis;
    white-space:nowrap; letter-spacing:2px; color:#fff8dc; }
  .arcade__scores li .pts{ color:#ffd800; font-variant-numeric:tabular-nums; }
  .arcade__scores li.is-new .who{ color:#ffd68a; animation:arcadeBlink 1s steps(1) infinite; }
  /* THE KEYS USED TO PRINT HERE. A capsule legend and Pinball's table legend
     floated in this aside, beside the board, the way a real machine prints its
     table on the bezel. A phone has no bezel: the aside IS the screen there,
     and a column of rules stood over the play from the first ball. They live
     in HOW TO PLAY now - see .arcade__pu down in the arcmenu section - which
     leaves every cabinet's aside holding nothing but the high scores, and the
     :has rule below therefore folds all four of them away mid-run. */
  /* Reuse the .back button's look for the exit control, and for the OK that
     submits a set of initials. They look identical and are deliberately two
     classes: immersive hides the cabinet's chrome, EXIT TO LOUNGE included,
     and OK must not go with it - it is the only way to post a high score that
     isn't a keypress, and it appears at the one moment immersive is still on. */
  .arcade__exit, .arcade__ok{
    align-self:center; font:inherit; font-size:.78rem; letter-spacing:1px;
    color:#fff8dc; padding:7px 16px; border-radius:6px; cursor:pointer;
    background:linear-gradient(180deg,#5a4a00,#2a2300);
    border:2px outset #e6c200; text-shadow:0 1px 0 #050505;
    transition:filter .12s ease, transform .08s ease;
  }
  .arcade__exit:hover, .arcade__ok:hover{ filter:brightness(1.25); color:#fff; transform:translateY(-1px); }
  .arcade__exit:active, .arcade__ok:active{ border-style:inset; transform:translateY(1px); }
  .arcade__exit:focus-visible,
  .arcade__ok:focus-visible{ outline:2px solid #ffd800; outline-offset:2px; }

  /* ================= FROST MAUL =================
     Reuses the .arcade backdrop, cabinet, marquee and exit chrome like every
     other fixture. What's new is the control deck under the screen: a maul is
     the one game here you play with your hands off the keyboard, so the tower
     palette, the wave call and the sends are real buttons rather than canvas
     art - they tab, they announce their state, and they stay tappable on a
     phone where a 20px grid cell does not. */
  /* Beats the file-wide canvas{} rule for the same reason #bs-canvas does:
     this board is isometric, so it's all diagonals, and pixelated rendering
     would put a staircase on every edge. It's drawn into a buffer twice the
     logical size instead, which stays clean at any scale. */
  #wm-canvas-3d{
    image-rendering:auto;
    box-sizing:border-box;
    width:100%; height:100%; display:block;
    border:0; background:#0a0a0a; cursor:crosshair;
    /* Own every gesture: one finger builds, two run the camera, and neither
       may reach the browser as a scroll or a page zoom. */
    touch-action:none;
  }
  #wm-canvas-3d[hidden]{ display:none; }
  /* A drag moves the camera, so it says so while it's moving. The crosshair
     stays for the resting state: a tap there still builds. */
  #wm-canvas-3d.is-panning{ cursor:grabbing; }
  /* The camera row, under the board and floated over it now that the board
     always fills the viewport. */
  .maul__cam{
    display:flex; gap:6px; margin:0;
    position:absolute; z-index:3;
    left:calc(10px + env(safe-area-inset-left));
    /* Clear of the console below it, which is a fixed-height bar now rather
       than the wrapping pill this used to dodge. */
    bottom:calc(128px + env(safe-area-inset-bottom));
  }
  .maul__cam[hidden]{ display:none; }
  /* ---- the seat row ----
     Across the top on the opposite side to the resource strip: six cards, your
     team then theirs, and tapping one flies the camera to that player's ground.
     It exists because a funnel map is the only board here you cannot see all of
     at once - your own region fills the screen by design, and what your two
     fronts are failing to hold is the single most useful thing off it.
       A card is a NAME and a COUNT. The count is bodies standing on that
     player's ground, which on a front seat is the warning and on your own is
     just the truth; the back seat of each team wears a mark, because it is the
     one holding the lives and that asymmetry IS this map.
       And every bit of that is SAID, not left to be inferred. The row spent a
     while as six unexplained cards of letters and numbers - readable only to
     somebody who already knew the map - so now the plates are captioned YOUR
     TEAM and ENEMY rather than trusting a border colour to carry it, the count
     wears a creep dot so it reads as BODIES rather than as a score, the seat
     you are playing wears the word YOU, the seat holding the lives says HOLDS
     LIVES, and a legend under the row names what the numbers are and what a tap
     does. Nothing up here is a glyph you have to learn from the manual, because
     there is no manual - and nothing up here is a badge with no number after it
     either, which is the same mistake wearing a nicer coat. */
  .maul__seats{
    display:flex; flex-direction:column; align-items:flex-start; gap:3px;
    position:absolute; z-index:3;
    top:calc(8px + env(safe-area-inset-top));
    left:calc(10px + env(safe-area-inset-left));
    font-size:.58rem; letter-spacing:1px;
  }
  .maul__seats[hidden]{ display:none; }
  .maul__seatrow{ display:flex; gap:10px; align-items:flex-start; }
  .maul__team{
    display:flex; flex-direction:column; gap:2px; padding:3px 4px 4px;
    border-radius:4px;
    background:linear-gradient(180deg,#0d2b3f,#04121d);
    border:2px solid #16405c;
  }
  .maul__team--foe{ border-color:#5c1f2e; }
  /* Which plate is whose, in words. The border colours already say it to
     anyone who has played a match; this is for the first one. */
  .maul__teamlab{
    font-size:.8em; letter-spacing:1.5px; text-align:center;
    color:#5f93ad; line-height:1;
  }
  .maul__team--foe .maul__teamlab{ color:#b06b7c; }
  .maul__teamrow{ display:flex; gap:3px; }
  .maul__seats button{
    display:flex; flex-direction:column; align-items:center; gap:1px;
    min-width:38px; padding:3px 5px 2px; cursor:pointer;
    border:1px solid transparent; border-radius:3px; background:transparent;
    color:#7fa6bb; font:inherit; letter-spacing:1px; line-height:1.1;
  }
  .maul__seats button b{ color:#cfe9f5; font-weight:700; }
  /* The dot and the number are siblings in a wrapper rather than the dot being
     a child of the number: syncSeatCards writes the count with textContent, and
     anything living INSIDE that element gets wiped on the first tick. */
  .maul__cnt{ display:flex; align-items:center; gap:3px; }
  .maul__seats button i{
    font-style:normal; font-size:.9em; color:#a08a20;
    font-variant-numeric:tabular-nums;
  }
  /* The creep dot beside the count. A bare number on a card could be anything -
     a score, a level, a gold price - and this is the mark that says it is
     something ALIVE and standing on that ground. */
  .maul__cnt s{
    text-decoration:none; flex:0 0 5px; width:5px; height:5px;
    border-radius:50%; background:#3c6f8c;
  }
  /* Bodies on that board: the number that turns a name into a warning. Warm
     the moment there are any, because "some" and "none" is the whole read. */
  .maul__seats button.has-creeps i{ color:#ffd68a; }
  .maul__seats button.has-creeps .maul__cnt s{
    background:#ffd68a; box-shadow:0 0 5px #ffd68a;
  }
  .maul__seats button:hover{ border-color:#2f7ea8; }
  /* Where the camera is looking. Separate from is-mine, because watching a
     team-mate must not make it look like you are playing their board. */
  .maul__seats button.is-on{ border-color:#ffd800; background:#0b2c40; }
  .maul__seats button.is-mine b{ color:#ffd800; }
  .maul__team--foe button.is-mine b{ color:#ff7b9c; }
  /* The mark line under a card. Always present even when it is empty, so a
     marked card and a plain one are the same height and the row does not
     jog - the alternative is six cards sitting at two different baselines.
       Stacked rather than run together on one line: YOU and HOLDS LIVES are
     two different kinds of statement about a seat, and side by side they read
     as one phrase. */
  .maul__seats u{
    text-decoration:none; display:flex; flex-direction:column;
    align-items:center; gap:1px;
    min-height:7px; font-size:.72em; letter-spacing:.5px; line-height:1.15;
    color:#a08a20;
  }
  /* The seat you are playing. A word, because the cyan was doing this job
     alone and colour alone cannot say WHICH of the two cyan things it means -
     the camera is also cyan up here. */
  .maul__you{ color:#ffd800; }
  .maul__team--foe button.is-mine .maul__you{ color:#ff7b9c; }
  /* The seat with the drain behind it. A VERB, and no glyph.
       This mark wore the resource strip's LIVES shield for one build, on the
     theory that it tied the mark to the counter it talks about. It did the
     opposite. Everywhere else on this HUD that shield is followed by a NUMBER,
     so a shield followed by the bare word LIVES reads as a stat whose value
     failed to render - a label waiting for a count that never arrives. No
     amount of drawing the shield better fixes that, because the shield was
     never the problem: the problem is that a badge plus a noun is the shape of
     a readout, and this is not a readout. HOLDS LIVES cannot be parsed as one,
     because a verb is not a field. Do not put a glyph back on this line. */
  .maul__lives{ color:#e8c84a; }
  /* The legend. One line, once, under both plates - the row's numbers are the
     one thing on this HUD with no label of their own, and the tap is the one
     interaction nothing else on screen advertises.
       On its own dark plate, like every other readout on this cabinet. It spent
     one shot as bare text with a shadow, floating straight on the board, and
     the board up here is SNOW: dim blue on white ice is not dim, it is
     invisible. Nothing on this HUD may rely on what happens to be behind it. */
  .maul__seatnote{
    margin:0; padding:3px 7px; border-radius:4px;
    /* The cabinet centres its text and a centred second line reads as a
       caption for the plates rather than as the rest of the sentence. */
    text-align:left;
    font-size:.85em; letter-spacing:.5px; line-height:1.4;
    color:#7fb6d2; max-width:min(340px,52vw);
    background:linear-gradient(180deg,rgba(10,13,18,.94),rgba(4,6,10,.94));
    border:1px solid #16405c;
  }
  .maul__seatnote b{ color:#ffe873; font-weight:normal; }
  @media (max-width:560px){
    .maul__seats{ font-size:.5rem; gap:2px; }
    .maul__seatrow{ gap:6px; }
    .maul__seats button{ min-width:30px; padding:2px 3px; }
    .maul__seatnote{ max-width:62vw; }
    /* AND IT DROPS OUT OF THE TOP BAND ENTIRELY. Measured on a 390 phone: the
       resource strip is 193px wide and anchored 52px off the right for the
       gear, which leaves 135px of gutter on the left - and this row needs 248px
       even shrunk. It was overlapping the strip before any of this was added
       and simply hiding it under GOLD; captioning the plates only made a
       collision that was already there wider.
         So on a phone it goes UNDER both right-hand plates instead of beside
       them. 84px clears the wave clock's own worst case: the strip is one row
       at top 8, the clock is one row at top 40, and the tallest that clock ever
       gets is the CALL IT NOW button inside it - both are fixed-top single-line
       readouts, which is what makes a number safe to clear them with. */
    .maul__seats{ top:calc(84px + env(safe-area-inset-top)); }
  }
  /* Sits inside .arcade__screen rather than replacing it, so the
     start/pause banner and the scanlines stay pinned over the visible
     board instead of scrolling off with it. */
  .maul__pan{ overflow:hidden; width:100%; height:100%; line-height:0; }
  /* ---- the console ----
     One stone bar across the bottom edge holding three panels: minimap,
     selection, command card. The materials do the RTS half of the work -
     a slate face, a bevelled gold lip along the top, and rivets at the
     corners - in the room's own ice tones rather than Azeroth's browns, so
     the cabinet still belongs to the lounge it is standing in.

     Full width, unlike the floating pill it replaces: a console that stops
     short of the edges reads as a widget sitting on the game, and the whole
     point of this shape is that it reads as the machine you are playing
     through. The camera gives up the strip it covers - measureInsets() reads
     this box by class, which is why it kept the name. */
  .maul__deck{
    /* The command card sets the console's height and everything else is cut to
       it: two rows of seat plus the gap between them. Written as custom
       properties because the minimap has to be squared off against the same
       figure - sized from its own parent it would resolve against a height
       that its own height is what decides, and settle on its 204px backing
       buffer, which is twice the bar's depth. */
    --seat:46px;
    --deck-h:calc(var(--seat) * 2 + 3px);
    /* The command swatch's height, up here beside the seat because the two are
       spent out of the same budget: a seat is a fixed grid track, so whatever
       the icon takes is height the name underneath it does not get - and the
       name needs two lines that clear the price. 12 leaves room at both the
       46px seat and the 44px one the portrait block cuts it to. */
    --swatch:12px;
    display:flex; position:absolute; z-index:3;
    flex-direction:row; align-items:stretch; flex-wrap:nowrap;
    /* Packed to the middle rather than spread edge to edge: the three panels
       are a fixed width between them, and the stone either side of them is the
       console's own face - which is exactly what the shape is copied from. */
    justify-content:center; gap:8px;
    left:0; right:0; bottom:0;
    padding:7px calc(10px + env(safe-area-inset-right))
            calc(6px + env(safe-area-inset-bottom))
            calc(10px + env(safe-area-inset-left));
    /* The Warcraft 3 console, from the reference in reference/style/
       WinterMaulHud.jpg: not a flat navy strip but STONE - a blue-slate
       face with a mottle in it, carved bevels, and a double gold lip. The
       mottle is two big soft radial washes over the vertical shade; at
       console size they read as tooling marks in the rock rather than as
       gradients, which is all a stone face is at this distance. The old
       note here said "ice tones rather than Azeroth's browns" - that
       ruling is reversed by instruction, and the reference's console is
       blue-grey stone anyway, so the two rooms meet in the middle. */
    background:
      radial-gradient(140px 30px at 18% 0%, rgba(150,180,210,.10), rgba(0,0,0,0) 70%),
      radial-gradient(180px 34px at 64% 100%, rgba(20,30,44,.55), rgba(0,0,0,0) 70%),
      radial-gradient(120px 26px at 88% 20%, rgba(140,170,200,.08), rgba(0,0,0,0) 70%),
      linear-gradient(180deg,#22303f 0%,#141f2b 40%,#0a121c 100%);
    border-top:3px solid #b98b3c;
    box-shadow:
      0 -1px 0 #5a4318,
      inset 0 1px 0 rgba(255,214,138,.55),
      inset 0 3px 0 rgba(0,0,0,.5),
      0 -10px 22px rgba(0,0,0,.45);
  }
  /* The gold lip's rivets - four studs along the trim, the one detail that
     stops the bar reading as a flat CSS gradient. Drawn as a background on a
     pseudo-element rather than as elements, since they are pure decoration and
     must never be in the way of a thumb. */
  .maul__deck::before{
    content:""; position:absolute; left:0; right:0; top:0; height:7px;
    pointer-events:none;
    background:
      radial-gradient(circle at 14px 4px, #ffd68a 0 1.6px, rgba(0,0,0,0) 2px),
      radial-gradient(circle at calc(100% - 14px) 4px, #ffd68a 0 1.6px, rgba(0,0,0,0) 2px),
      radial-gradient(circle at 33.3% 4px, rgba(255,214,138,.6) 0 1.4px, rgba(0,0,0,0) 2px),
      radial-gradient(circle at 66.6% 4px, rgba(255,214,138,.6) 0 1.4px, rgba(0,0,0,0) 2px);
  }
  /* A panel of the console: a sunken well cut into the stone, framed in
     bronze the way every plate on the reference console is. The frame is a
     solid dark gold with a light edge above and a shadow edge below - the
     two-line bevel that reads as METAL SET INTO ROCK rather than as a CSS
     border - and the well behind it drops nearly to black, because the
     reference's wells are darker than its stone by a full stop. */
  .maul__mmap, .maul__info, .maul__card{
    box-sizing:border-box; border-radius:4px;
    background:linear-gradient(180deg,#050a12,#0a1520);
    border:2px solid #7d6228;
    box-shadow:
      0 1px 0 rgba(255,214,138,.18), 0 -1px 0 rgba(0,0,0,.7),
      inset 0 0 0 1px rgba(0,0,0,.7), inset 0 2px 8px rgba(0,0,0,.65);
  }
  /* ---- left: the minimap ----
     Board and meters side by side rather than stacked. Stacked, the square
     minimap alone is taller than the command card beside it, and the console
     would take its height from the one panel that is only there to be glanced
     at - so the map is sized by the console's height instead of setting it. */
  .maul__mmap{
    flex:0 0 auto; display:flex; flex-direction:row; align-items:stretch;
    gap:5px; padding:4px;
  }
  /* ---- middle: what is selected ----
     Portrait on the left, name and stats to the right of it, exactly the
     shape an RTS unit panel has. Takes the slack in the row so the card and
     the minimap keep their sizes on every screen. */
  .maul__info{
    flex:0 1 480px; min-width:0; display:flex; align-items:center; gap:8px;
    padding:5px 8px; font-size:.6rem; letter-spacing:1px; color:#d6b000;
  }
  /* The portrait box. Square, framed, and lit in whatever the selection's own
     colour is - a tower's swatch, the builder's crest, the pit's crimson - so
     the panel says WHAT you have selected before a word of it is read. */
  .maul__port{
    flex:0 0 46px; height:46px; border-radius:3px; position:relative;
    display:flex; align-items:center; justify-content:center;
    font-size:1.1rem; font-weight:bold; font-style:normal;
    color:var(--port,#ffd800); text-shadow:0 0 10px var(--port,#ffd800);
    background:linear-gradient(180deg,#0b2c40,#03121d);
    /* The gold portrait ring is the most recognisable single shape on the
       reference console, so this frame is the deepest gold on the cabinet. */
    border:2px solid #c69a4a;
    /* The colour wash is an inset shadow in the selection's own hue rather
       than a gradient stop, so one custom property drives both the glyph and
       the light behind it and there is no colour arithmetic to support. */
    box-shadow:inset 0 0 14px -2px var(--port,#ffd800), inset 0 0 0 1px rgba(0,0,0,.6);
  }
  /* The rank pips under a portrait - a tower's level, in the gold an RTS uses
     for veterancy. */
  .maul__port u{
    position:absolute; left:0; right:0; bottom:-1px; text-decoration:none;
    font-size:.5rem; letter-spacing:1px; text-align:center; color:#ffd68a;
    text-shadow:0 1px 2px #000;
  }
  .maul__info-t{ min-width:0; flex:1 1 auto; }
  .maul__info-t > b{ display:block; color:#fff8dc; font-size:.68rem;
    letter-spacing:2px; }
  /* Direct children only - the crest and any other inline mark inside the
     stat line have to stay inline, and a bare descendant selector would
     out-specify their own rules and stack them into blocks. */
  .maul__info-t > span{ display:block; color:#ffe560; margin-top:2px; }
  .maul__info-t em{ font-style:normal; color:#ffd68a; }
  /* The stat line wraps rather than scrolls: a tower's traits are a list of
     short clauses and losing the last one off the edge is losing the one that
     usually matters (pierces armour, hits air). */
  .maul__info-t > i{ font-style:normal; display:block; margin-top:2px;
    color:#a08a20; font-size:.55rem; }
  /* ---- right: the command card ----
     A fixed four-column grid, which is what makes it a command card and not a
     toolbar: every command lands in the same seat every time, so the hand
     learns positions rather than labels. Four wide holds the widest roster
     (seven towers) in two rows with a spare seat, and the grid does not
     reflow when the selection changes - a card that resized under the cursor
     would move the button you were reaching for. */
  .maul__card{
    flex:0 0 auto; display:grid; gap:3px; padding:4px;
    grid-template-columns:repeat(4, var(--seat));
    grid-auto-rows:var(--seat);
  }
  /* One command. Square, bevelled, and carrying its own three annotations in
     the corners the way an RTS icon does: cost bottom-left, hotkey
     bottom-right, and any flag (hits air) top-right.

     Those three are absolutely positioned, so the column down the middle has to
     be TOLD they are there. --tag is the band the two bottom ones stand in and
     it is reserved as bottom padding: without it the column centres itself in
     the whole seat, and every two-word name on the card - SLEET ARROW, GLACIER
     WARD, SLAG MORTAR - wrapped its second line straight down through the price
     and the hotkey. Padding rather than a margin on the name, so the swatch
     stays centred in what is LEFT rather than in what was.

     None of it may cost the seat a pixel of height: the row is a fixed grid
     track, .maul__deck is measured by measureInsets(), and the strip the
     console covers is board the camera does not get. The room the band needs
     comes OUT of the icon (--swatch), never added underneath.

     No side padding either. The corner labels carry their own 2px inset and
     stand only in the bottom band, so padding the whole seat for them charged
     the name four pixels it needed - at this size four pixels is a whole
     letter, and it is the letter that decides whether a nine-letter word
     (CORRUPTOR) sits on one line or wraps. */
  .maul__slot{
    --tag:9px;
    position:relative; display:flex; flex-direction:column;
    align-items:center; justify-content:center; gap:1px; overflow:hidden;
    font:inherit; font-size:.46rem; letter-spacing:.5px; color:#fff8dc;
    padding:2px 0 var(--tag); border-radius:3px; cursor:pointer; text-align:center;
    background:linear-gradient(180deg,#0e3d5c,#07253a);
    /* Bronze seats, not ice: on the reference every command sits in the
       same gold-brown bevel whatever the command is, and the CONTENT is
       what carries the meaning. The per-set tints below keep their fills
       for exactly that reason - they recolour the well, not the frame. */
    border:2px outset #9a7a35; text-shadow:0 1px 0 #050505;
    transition:filter .12s ease, transform .08s ease;
  }
  .maul__slot:hover{ filter:brightness(1.3); transform:translateY(-1px); }
  .maul__slot:active{ border-style:inset; transform:translateY(1px); }
  .maul__slot:focus-visible{ outline:2px solid #ffd800; outline-offset:1px; }
  /* The held tower. Lit rather than merely pressed, because in build mode this
     is the one thing on the card that says what a tap on the board will do. */
  .maul__slot[aria-pressed="true"]{
    border-color:#ffd800; border-style:inset;
    background:linear-gradient(180deg,#1a6a99,#2a2a2a);
    box-shadow:inset 0 0 10px rgba(255,216,0,.5), 0 0 8px rgba(255,216,0,.45);
  }
  /* Priced out, the slot dims rather than disappearing: the point of a maul
     economy is seeing what you are saving toward. */
  .maul__slot.is-broke{ opacity:.42; }
  /* The icon: the tower's own swatch colour as a lit lozenge, which is the
     same colour its crown is painted on the board. */
  .maul__slot i{
    flex:0 0 var(--swatch); width:16px; height:var(--swatch); border-radius:3px;
    font-style:normal; box-shadow:0 0 7px currentColor, inset 0 -3px 5px rgba(0,0,0,.45);
  }
  .maul__slot b{ font-weight:bold; color:#fff8dc; line-height:1.05;
    /* No tracking on the name. The slot's own half-pixel is right for the
       corner numbers and wrong here: it costs an eleven-letter word five and a
       half pixels it has not got, which is most of why THUNDERHEAD was being
       sliced through its last letters inside its own box. */
    letter-spacing:0;
    /* A word with no space in it that is wider than the seat wraps rather than
       being sliced - a command card that cannot say a tower's name is not a
       command card. It fires only on a word that cannot fit a line of its own,
       so every name that already fits is left exactly as it is. */
    overflow-wrap:break-word;
    /* Two lines and no more. The reserve above is cut for two, so a third would
       land in the band however the column is packed - better cropped than
       printed over the price. Two of this element's own line boxes is 2.1em
       (line-height 1.05); the cap is a twentieth over so it never rounds
       against itself and shaves a pixel off a name that fits. */
    max-width:100%; max-height:2.15em; overflow:hidden; }
  /* Cost, bottom-left, in the gold every price in this cabinet is written in.
     line-height:1 on both corner labels so the band --tag reserves is the type
     itself and not the leading around it - nearly two pixels back. */
  .maul__slot s{
    position:absolute; left:2px; bottom:1px; text-decoration:none;
    color:#ffd68a; font-size:.46rem; line-height:1; font-variant-numeric:tabular-nums;
    text-shadow:0 1px 2px #000;
  }
  /* Hotkey, bottom-right. */
  .maul__slot u{
    position:absolute; right:2px; bottom:1px; text-decoration:none;
    color:#7fb6d4; font-size:.46rem; line-height:1; text-shadow:0 1px 2px #000;
  }
  /* Hits air. A wyrm flight ignores the maze entirely, so "which of these can
     shoot up" has to be readable without hovering for the tooltip. */
  .maul__slot em{
    position:absolute; right:2px; top:1px; font-style:normal; font-size:.5rem;
    color:#ffe560; text-shadow:0 0 6px rgba(158,232,255,.9);
  }
  /* The send commands, in the crimson their creeps are painted - the card
     changes colour with the building you have selected, which is most of how
     you know you are looking at a different set of commands. */
  .maul__slot--send{
    background:linear-gradient(180deg,#5c1a35,#2f0d1c);
  }
  .maul__slot--send b{ color:#ffd9e4; }
  /* Sell is the one command you can't take back, so it is the one that isn't
     painted like the rest of the card. */
  .maul__slot--sell{
    background:linear-gradient(180deg,#5a2733,#2c1119);
  }
  .maul__slot--up{
    background:linear-gradient(180deg,#5a4a00,#2a2300);
  }
  /* What a WALL becomes. Gold rather than the card's ice, because it is the one
     command on this cabinet that turns a piece into a DIFFERENT piece rather
     than a bigger one - and because it sits beside UPGRADE, which is the thing
     it most needs not to be mistaken for. */
  .maul__slot--path{
    background:linear-gradient(180deg,#6b4a10,#2f2007); border-color:#d8a63f;
  }
  .maul__slot--path b{ color:#ffe6ac; }
  /* BUILD, on the pit's card: the way back to the palette. Painted in the
     board's own ice rather than the sends' crimson precisely because it is not
     one of them - it is the door out of the card you are looking at, and it has
     to read as belonging to the set you are going back to. */
  .maul__slot--back{
    background:linear-gradient(180deg,#0f4c6d,#06283d);
  }
  .maul__slot--back b{ color:#cdf1ff; }
  /* A seat with nothing in it. An empty command card would leave the grid
     collapsed and the panel a different size to the one beside it, so the
     unused seats are drawn as the recessed sockets they are. */
  .maul__hole{
    border-radius:3px; background:rgba(0,0,0,.34);
    box-shadow:inset 0 0 0 1px rgba(198,154,74,.14), inset 0 2px 5px rgba(0,0,0,.6);
  }
  /* Whose towers these are. Rides the info panel's portrait in build mode, and
     the name line beside it, because at five rosters the palette alone stops
     telling you. */
  .maul__crest{
    display:inline-block; font-size:.58rem; letter-spacing:2px;
    font-weight:bold; color:var(--crest,#ffd800);
    text-shadow:0 0 8px var(--crest,#ffd800);
  }
  /* The wave clock, docked under the resource strip in the top-right - the
     owner's call, and it is where WC3 itself puts this information: the
     upkeep line and the day/night clock live with the resources, not down
     on the console. Right-aligned to the strip's own edge so the two read
     as one column of numbers. It used to float bottom-centre over the
     board, where it sat on the region the camera fights hardest to keep
     clear. */
  .maul__wave{
    display:flex; align-items:center; justify-content:center; gap:8px;
    position:absolute; z-index:3;
    right:calc(96px + env(safe-area-inset-right));
    top:calc(40px + env(safe-area-inset-top));
    padding:4px 12px; border-radius:4px;
    /* Styled off the reference's Income tooltip: black glass, one thin
       gold rule, and yellow text. It is the only floating plate over the
       board, exactly as the tooltip is the only floating plate over the
       world. */
    font-size:.6rem; letter-spacing:1px; color:#d9c07a;
    background:linear-gradient(180deg,rgba(10,13,18,.94),rgba(4,6,10,.94));
    border:1px solid #b98b3c;
    box-shadow:inset 0 1px 0 rgba(255,214,138,.25), 0 2px 10px rgba(0,0,0,.5);
  }
  .maul__wave b{ color:#ffd800; font-variant-numeric:tabular-nums; }
  /* The air-wave warning on the clock. Warm against a cabinet of cyan, because
     it is the one line that means "the maze you just built does not apply". */
  .maul__wave b.maul__warn{ color:#ffd68a; text-shadow:0 0 8px rgba(255,214,138,.8); }
  .maul__call{
    font:inherit; font-size:.6rem; letter-spacing:1px; color:#050505;
    padding:6px 10px; border-radius:5px; cursor:pointer;
    background:linear-gradient(180deg,#ffd68a,#c99a3e);
    border:2px outset #ffd68a; font-weight:bold;
  }
  .maul__call:hover{ filter:brightness(1.15); }
  .maul__call:active{ border-style:inset; }
  .maul__call:focus-visible{ outline:2px solid #ffd800; outline-offset:2px; }
  .maul__call[hidden]{ display:none; }
  /* Waiting on the board to clear before the next wave can be pulled forward
     (see MAX_AHEAD). Cold and flat, so it reads as "not yet" rather than as
     the gold "press me". */
  .maul__call:disabled{
    cursor:default; color:#a08a20; font-weight:normal;
    background:rgba(4,4,4,.6); border:2px solid rgba(138,114,0,.5);
  }
  .maul__call:disabled:hover{ filter:none; }
  /* The camera row's buttons, and anything else that wants to look like a
     console control without being a command slot. */
  .maul__act{
    /* The reference's Menu/Allies/Quests chips: bevelled grey stone with
       parchment-gold lettering, not ice. */
    font:inherit; font-size:.6rem; letter-spacing:1px; color:#e8d9a8;
    padding:6px 9px; border-radius:5px; cursor:pointer;
    background:linear-gradient(180deg,#4a4a4a,#242c35);
    border:2px outset #6b6b6b; text-shadow:0 1px 0 #0a0d12;
  }
  .maul__act:hover{ filter:brightness(1.25); }
  .maul__act:active{ border-style:inset; }
  .maul__act:focus-visible{ outline:2px solid #ffd800; outline-offset:2px; }
  .maul__act.is-broke{ opacity:.42; }

  /* ---- the race meters, beside the minimap in the console ---- */
  .maul__rival{
    display:flex; flex-direction:column; justify-content:center;
    width:94px; font-size:.6rem; color:#d6b000; letter-spacing:1px;
  }
  .maul__meter{
    position:relative; height:11px; margin:2px 0 0;
    background:#080808; border:1px inset #4a3e00; border-radius:3px;
    overflow:hidden;
  }
  .maul__meter i{
    position:absolute; inset:0 auto 0 0; display:block;
    background:linear-gradient(180deg,#ff7b9c,#a11f5a);
    transition:width .3s ease;
  }
  /* Your own lives ride the same meter in the room's cyan, so the two bars
     read as one race at a glance rather than two unrelated readouts. */
  .maul__meter.is-you i{ background:linear-gradient(180deg,#ffd800,#5a4a00); }
  .maul__meter b{
    position:relative; display:block; text-align:center; font-size:.5rem;
    line-height:11px; color:#fff8dc; text-shadow:0 1px 0 #050505;
  }
  .maul__note{ font-size:.5rem; color:#a08a20; text-align:center;
    margin:2px 0 0; letter-spacing:0; }
  .maul__note.is-hit{ color:#ffd68a; }
  /* The rival's live board, in the minimap's seat. The backing buffer is a
     fixed 12px-per-tile grid, scaled down by the browser with
     nearest-neighbour so the maze stays crisp instead of blurring. */
  .maul__mini{
    display:block; flex:0 0 auto; margin:0;
    width:var(--deck-h); height:var(--deck-h); image-rendering:pixelated;
    border:1px inset #4a3e00; border-radius:3px; background:#080808;
  }
  /* ---- survival mode: no rival, so the minimap of its board and the race
     meters under it have nothing to show, and the pit that targets it is never
     built. The console keeps its other two panels and simply loses the left
     one - a minimap frame painted flat black for a board that doesn't exist
     reads as a bug. ---- */
  .arcade.is-survival .maul__mmap{ display:none; }
  #maul.arcade.is-survival:not(.is-over) .arcade__scores{ display:none; }
  @media (prefers-reduced-motion:reduce){
    .maul__slot, .maul__meter i{ transition:none; }
  }

  /* ---- the resource strip ----
     Top-right, which is where an RTS states what you have to spend, and where
     this cabinet now has room for it: the race panel that used to sit in that
     corner has moved into the console. */
  #maul .arcade__hud{
    left:auto; gap:12px; padding:5px 12px; border-radius:4px;
    right:calc(96px + env(safe-area-inset-right));
    /* The reference's resource row is black glass with gold numerals - the
       one part of the WC3 chrome that is NOT stone - so this plate goes
       nearly black and hands all its colour to the counters. */
    background:linear-gradient(180deg,rgba(10,13,18,.94),rgba(4,6,10,.94));
    border:1px solid #7d6228; border-top-color:#b98b3c;
    box-shadow:inset 0 1px 0 rgba(255,214,138,.25), 0 2px 8px rgba(0,0,0,.5);
    font-size:.62rem;
  }
  .maul__res-x{ display:flex; align-items:center; gap:5px; }
  .maul__res-x em{ font-style:normal; color:#b9a05c; letter-spacing:1px; }
  .maul__res b{ color:#fff8dc; font-variant-numeric:tabular-nums;
    min-width:2.2em; text-align:right; }
  /* The resource icons. Gold is the coin every price on the board is quoted
     in, so it gets the only warm colour up here; the rest are cool, because
     they are counters rather than currency and only one of them is spendable. */
  .maul__ico{
    flex:0 0 11px; width:11px; height:11px; font-style:normal;
    box-shadow:0 0 6px currentColor;
  }
  .maul__ico--gold{ border-radius:50%; background:#ffd68a; color:#ffd68a;
    box-shadow:0 0 7px #ffd68a, inset 0 -3px 4px rgba(120,80,10,.65); }
  /* A shield: the lives you have left are the wall the drain is behind. */
  .maul__ico--life{ background:#ffd800; color:#ffd800;
    clip-path:polygon(50% 0,100% 22%,100% 62%,50% 100%,0 62%,0 22%); }
  /* The rival's shield, in the far side's own pink - and only where the
     minimap panel that carries its meter is folded away (the two phone
     blocks below turn it on). */
  .maul__ico--rival{ background:#ff7b9c; color:#ff7b9c; }
  .maul__res-rival{ display:none; }
  /* A chevron, for the wave marching down the board. */
  .maul__ico--wave{ background:#ffe560; color:#ffe560;
    clip-path:polygon(0 0,50% 42%,100% 0,100% 34%,50% 76%,0 34%); }
  .maul__ico--score{ background:#ffe873; color:#ffe873;
    clip-path:polygon(50% 0,61% 35%,98% 35%,68% 57%,79% 92%,50% 70%,21% 92%,32% 57%,2% 35%,39% 35%); }

  /* ---- the high-score board ----
     Shown on the cabinet's main menu - the CLICK-TO-START screen every game
     opens on - and again once the run is over: the two moments the board is the
     thing you want to read. Held back only during active play, when it is the
     one panel on screen you can do nothing about: it doesn't tell you where the
     ball is, how long your maze is, or what the snake is about to hit. is-menu
     is set by each game while it sits in that opening idle state - see the
     syncOver() functions - and the absolutely-positioned aside means showing or
     hiding it never nudges the board. */
  .arcade__hi{ display:none; }
  .arcade.is-over .arcade__hi,
  .arcade.is-menu .arcade__hi{ display:block; }
  /* An aside holding nothing but the board would, mid-play (board held back),
     sit there as an empty bordered box - so any aside whose only child is the
     hidden board goes with it. That is now every cabinet: the legends that used
     to give Brick Smash and Pinball live content have moved into HOW TO PLAY,
     and the race and the sends went down into the maul's console. The rule
     stays written for content rather than by name, so an aside that grows a
     live panel again keeps its seat without anyone remembering this. The menu
     and over states keep every aside, since the board is showing then. */
  .arcade:not(.is-over):not(.is-menu) .arcade__scores:not(:has(> :not(.arcade__hi))){
    display:none;
  }

  /* ---- chrome a walk-up cabinet needs and a full-viewport game doesn't ----
     A marquee, a hint line and a text EXIT button belong to a machine standing
     in a room; hidden in a GAME because the board owns the whole viewport
     instead, and EXIT TO LOUNGE now lives inside the settings panel (see
     .maul__menuexit and its Brick Smash/Snake/Pinball equivalents). Scoped to
     .is-game so THE DECKS and the WARDROBE - still windowed - keep their
     marquee and their exit button. Note this reaches every .arcade__exit
     inside a game, which is why the initials form's OK carries .arcade__ok
     instead - see that rule for the whole story. */
  .arcade.is-game .arcade__marquee,
  .arcade.is-game .arcade__hint,
  .arcade.is-game .arcade__exit,
  .arcade.is-game .arcade__scores h3{ display:none; }
  /* ...except the board's own heading on the menu and once the run is over,
     the two moments the panel is the point. */
  .arcade.is-over .arcade__hi h3,
  .arcade.is-menu .arcade__hi h3{ display:block; }

  /* Wider once the run is over, because that is the moment it stops being a
     sidebar and becomes the thing you are reading. */
  .arcade.is-over .arcade__scores{ width:200px; }

  /* The corner pill: every cabinet's settings button, and Frost Maul's
     2D/3D toggle sharing the same small square shape beside it. */
  .arcade__pill{
    position:absolute; z-index:4; padding:0; width:34px; height:34px;
    font-size:.9rem; line-height:1; border-radius:8px; cursor:pointer;
    top:calc(8px + env(safe-area-inset-top));
    right:calc(10px + env(safe-area-inset-right));
    color:#e8d9a8; background:linear-gradient(180deg,#4a4a4a,#242c35);
    border:2px outset #6b6b6b; text-shadow:0 1px 0 #0a0d12;
  }
  .arcade__pill:hover{ filter:brightness(1.25); }
  .arcade__pill:active{ border-style:inset; }
  .arcade__pill:focus-visible{ outline:2px solid #ffd800; outline-offset:2px; }
  /* A second pill (only Frost Maul has one) steps left instead of stacking
     on the first. */
  .arcade__pill + .arcade__pill{ right:calc(52px + env(safe-area-inset-right)); }
  /* The footer's own box goes away - display:contents hands each button
     straight to .arcade__cab, which is what the pill's absolute position is
     measured against - but the buttons themselves stay in the DOM for focus
     order and the sibling rule above. Games only: the windowed fixtures keep
     their footer in flow. */
  .arcade.is-game .arcade__foot{ display:contents; }

  /* ---- what a FINGER needs, as opposed to a cursor ----
     A mouse hits a 34px pill every time; a thumb is about 9mm across and the
     platforms both ask for 44px. Scoped to pointer:coarse so it is the touch
     device that grows, and every desktop cabinet keeps the small square it was
     drawn with - this is an input-device question, not a width question, and a
     narrow desktop window is still driven with a mouse.

     The pill's own width is the anchor for the lane beside it, so it becomes a
     property rather than a number in four places: the second pill steps left by
     it, and the maul's resource strip, wave clock and score board all clear it
     by it. 10 (the pill's own inset) + 44 + 8 (a gap) = 62. */
  @media (pointer:coarse){
    .arcade__pill{ --pill:44px; width:var(--pill); height:var(--pill); font-size:1.05rem; }
    .arcade__pill + .arcade__pill{ right:calc(62px + env(safe-area-inset-right)); }
  }

  /* ---- the board, played upright ----
     There used to be a "turn the phone sideways" prompt here, because the flat
     iso board was a fixed 17-wide bitmap that was all letterbox in portrait.
     The board is WebGL only now and its camera pulls back to fit whatever
     shape the screen is (see fitDist in js/maul3d.js), so the prompt is gone
     and a phone plays upright.

     What is left is the re-flow: the overlays are laid out for a landscape
     screen, and stacked into a 375px-wide portrait they pile onto each other -
     the palette runs off both edges, the rival panel lands on the HUD, and the
     camera row sits on the deck. */
  @media (orientation:portrait){
    /* Three panels side by side don't fit across 375px, so the console gives
       up the one that is scenery rather than controls: the minimap goes, and
       the info panel and the command card - the two you actually play through
       - keep their full size. The meters it was carrying are covered by the
       resource strip and the note line in the info panel. */
    #maul.arcade .maul__mmap{ display:none; }
    #maul.arcade:not(.is-survival) .maul__res-rival{ display:flex; }
    #maul.arcade .maul__inc{ display:none; }
    /* 44, not the 40 this used to be: at 40 a two-line tower name landed on
       its own cost in the corner, and the fix is headroom, not smaller
       type - the four extra pixels are exactly one line of .44rem text.
       Four columns of 44 plus the info panel still clear 390px, because
       the info panel is the flexible one. */
    #maul.arcade .maul__deck{ --seat:44px; }
    /* The HOTKEY goes - a hotkey is a keyboard affordance, and a portrait
       phone has no keyboard to press it on. The COST stays, because seeing
       what you are saving toward is the economy. */
    #maul.arcade .maul__slot{ font-size:.44rem; }
    #maul.arcade .maul__slot u{ display:none; }
    /* ---- and the info panel, which is what actually sets the depth here ----
       The card is a fixed grid and cannot grow; the panel beside it can, and
       .maul__deck is a stretch row, so the panel's tallest selection decides
       how deep the console is - and measureInsets() hands the camera whatever
       is left under it. Across the five palettes that ran 119px to 144px: a
       SPORE ROOST took twenty-five pixels of board off the screen just by being
       selected.

       The panel had already been given the same-facts-fewer-words treatment in
       statTraits(), and it was not enough, because the problem is not the
       wording - it is that 165px of panel spends 50 on the portrait and 16 on
       its own padding, leaving the longest string in the cabinet an 87px
       column. So take the width back rather than take the words out:

         - the tracking goes. A stat line is 41 characters and the panel's 1px
           of letter-spacing is therefore 41px of it - 16% of the line, spent
           making a monospace face airier on the one screen with no air to
           spare. The name keeps a half pixel because it is the one line read as
           a shape rather than as figures.
         - the portrait comes down to 32px, which is still square, still lit in
           the selection's own colour, and still says WHAT is selected before a
           word is read - it just stops being a third of the panel.

       Between them the column goes 87px -> 105px and every string loses a
       sixth of its width, which is worth about two lines on the worst tower in
       the game. No wording changes, no clipping, and the console holds at its
       119px floor on all thirty. */
    #maul.arcade .maul__info{ letter-spacing:0; }
    #maul.arcade .maul__info-t > b{ letter-spacing:.5px; }
    #maul.arcade .maul__port{ flex:0 0 32px; height:32px; font-size:.85rem; }
    /* The resource strip loses its words and keeps its icons: at this width
       GOLD/LIVES/WAVE/SCORE spelled out is wider than the numbers they label,
       and the icons were always what the corner was read by. */
    /* Still clear of the settings pill in the corner - the strip is narrower
       without its labels, but it is not the pill that moved. */
    #maul .arcade__hud{
      right:calc(52px + env(safe-area-inset-right)); gap:8px; padding:4px 8px;
    }
    #maul .maul__res-x em{ display:none; }
    /* The wave clock keeps its seat under the strip, on the strip's own
       portrait edge. */
    #maul.arcade .maul__wave{
      right:calc(52px + env(safe-area-inset-right));
    }
    /* The high-score aside drops below the resource strip rather than sharing
       its row. */
    #maul.arcade .arcade__scores{
      top:calc(46px + env(safe-area-inset-top));
      right:calc(8px + env(safe-area-inset-right));
    }
    /* There used to be a bottom: here, lifting the clock clear of the taller
       portrait console back when it floated bottom-centre. The clock lives
       under the resource strip now, positioned by top: - and a leftover
       bottom: alongside a top: does not move an absolute box, it STRETCHES
       it, which briefly drew this plate as a gold-framed column down the
       whole screen. */
    /* The three offsets above are the pill's LANE: 10 (its inset) + 34 (its
       width) + 8 (a gap) = 52. On a touch device the pill is 44 for a thumb,
       so the lane is 62 and the aside drops to clear a 44-tall pill rather
       than a 34-tall one. Nested so it applies only where the pill grew. */
    @media (pointer:coarse){
      #maul .arcade__hud,
      #maul.arcade .maul__wave{ right:calc(62px + env(safe-area-inset-right)); }
      #maul.arcade .arcade__scores{ top:calc(58px + env(safe-area-inset-top)); }
    }
  }

  /* ---- a phone turned SIDEWAYS ----
     Everything above is keyed to orientation:portrait, so turning the phone
     handed it the DESKTOP console - 46px seats and a full-size info panel - on
     a screen 393px tall. Measured: 146px of console on a 14 Pro, 37% of the
     screen, and the playfield reduced to a strip. The console is not too WIDE
     here, it is too DEEP, and it is deep because the command card is two rows.

     A landscape phone has width it cannot use and height it cannot spare, so
     the card lies down: one row of eight instead of two of four. That is the
     one change that actually pays - two rows of 44 plus the gap is 91px of
     card, one row is 44. Everything else follows from it, --deck-h included,
     since the minimap squares itself off that figure and would otherwise hold
     the console open at the height the card just gave up.

     Bounded by max-height so it is a SHORT screen this answers, not a wide
     one: a desktop window is not 520px tall, and one that is has the same
     problem and is welcome to the same answer. */
  @media (orientation:landscape) and (max-height:520px){
    #maul.arcade .maul__deck{
      --seat:44px;
      --deck-h:var(--seat);
    }
    #maul.arcade .maul__card{
      grid-template-columns:repeat(8, var(--seat));
    }
    /* AND THE MINIMAP GOES, for the same reason it goes in portrait and for a
       sharper one here: a row of eight seats is 385px of fixed, unshrinkable
       card, and .maul__info is the only flexible thing in the row. On a 667px
       landscape the map's ~55px was the difference between the panel having
       200px to set its longest line in and having almost none - and a panel
       squeezed to nothing does not clip, it WRAPS, which drove the console to
       290px, three times what it started at. Measured before this line, not
       reasoned about after it. */
    #maul.arcade .maul__mmap{ display:none; }
    #maul.arcade:not(.is-survival) .maul__res-rival{ display:flex; }
    #maul.arcade .maul__inc{ display:none; }
    /* AND THE WAVE CLOCK KEEPS ONE WIDTH. It grew with its text - "BUILD
       YOUR MAZE" before wave one, "WAVE 5 IN 23s · RIME GOLEM" after - by
       about 120px leftwards, over the board's far corner, after the camera
       had framed the board clear of the narrower plate. One fixed width,
       padding included, so the plate the camera frames around before wave
       one is the plate it is all game; the text wraps instead. */
    #maul.arcade .maul__wave{ width:300px; box-sizing:border-box; }
    #maul.arcade .maul__kind{ display:block; }
    #maul.arcade .maul__kind .maul__sep{ display:none; }
    /* AND THE HIGH-SCORE ASIDE STANDS DOWN at the end of a run. On a 390px
       landscape it covered the initials prompt's own line ("enter your in-")
       and the resource strip under it; the board it shows is the one the menu
       opens on, new row lit, the moment OK is pressed. */
    #maul.arcade.is-over .arcade__scores{ display:none; }
    /* the same thumb and the same absent keyboard as portrait */
    #maul.arcade .maul__slot{ font-size:.44rem; }
    #maul.arcade .maul__slot u{ display:none; }
    /* and the same width-back treatment for the panel, which is the other
       thing that can hold the console open */
    #maul.arcade .maul__info{ letter-spacing:0; }
    #maul.arcade .maul__info-t > b{ letter-spacing:.5px; }
    #maul.arcade .maul__port{ flex:0 0 32px; height:32px; font-size:.85rem; }
    @media (pointer:coarse){
      #maul .arcade__hud,
      #maul.arcade .maul__wave{ right:calc(62px + env(safe-area-inset-right)); }
    }
  }

  /* ================= the maul menu scene =================
     Frost Maul's own front screen. The other cabinets drop you onto the board
     behind a line of "press start" text floated over it; this covers the whole
     cabinet instead - home/play, the leaderboard, and settings - and the run
     lives behind it until PLAY. Absolute over .arcade__cab so it fills the
     cabinet windowed and the whole viewport in immersive with the same rule. */
  /* CENTRED BY AUTO MARGIN, NOT BY align-items. This box scrolls, and a flex
     item centred on the cross axis of a scroll container overflows equally in
     BOTH directions - the half that goes off the bottom is scrollable and the
     half that goes off the top is simply unreachable, because scrollTop cannot
     go negative. With six HOW items nothing here was ever tall enough to notice;
     the effects glossary put the panel at 1394px in a 640px cabinet and stranded
     the first 377px of it, tabs and title included. An auto margin centres while
     there is slack and collapses to zero when there is not, which is the same
     look with none of that. The narrow breakpoint below already had to go
     flex-start for this reason. */
  .maul__menu{
    position:absolute; inset:0; z-index:90;
    display:flex; align-items:flex-start; justify-content:center;
    padding:22px; box-sizing:border-box; overflow-y:auto;
    background:
      radial-gradient(120% 78% at 50% 0%, rgba(255,216,0,.12), transparent 62%),
      linear-gradient(180deg,#072033,#080808);
  }
  .maul__menu[hidden]{ display:none; }
  .maul__menuwrap{
    width:100%; max-width:460px; display:flex; flex-direction:column;
    align-items:stretch; gap:14px; text-align:center;
    margin-top:auto; margin-bottom:auto;
  }
  .maul__menutitle{
    margin:0; font-size:1.5rem; letter-spacing:6px; font-weight:bold; color:#fff8dc;
    text-shadow:0 0 14px rgba(255,216,0,.7), 0 0 3px rgba(255,216,0,.9);
  }
  .maul__result{
    font-size:.72rem; letter-spacing:1.5px; color:#ffd68a; line-height:1.5;
    border:1px solid rgba(255,214,138,.4); border-radius:6px;
    padding:9px 11px; background:rgba(255,214,138,.08);
  }
  .maul__result[hidden]{ display:none; }
  .maul__result b{ display:block; color:#fff; font-size:1rem; letter-spacing:3px; margin-bottom:3px; }
  .maul__menutabs{
    display:flex; gap:4px; justify-content:center;
    border-bottom:2px solid rgba(214,176,0,.25);
  }
  /* Four tabs since HOW TO PLAY joined - the .66rem/2px fit of three would
     wrap the bar on a 460px wrap, so the buttons wear the arcmenu's size. */
  .maul__menutabs button{
    flex:1 1 auto; cursor:pointer; font:inherit; margin-bottom:-2px;
    padding:9px 4px; font-size:.6rem; letter-spacing:1.5px;
    color:#d6b000; background:transparent; border:0; border-bottom:2px solid transparent;
  }
  .maul__menutabs button:hover{ color:#ffe873; }
  .maul__menutabs button.is-on{
    color:#ffd800; border-bottom-color:#ffd800; text-shadow:0 0 8px rgba(255,216,0,.6);
  }
  .maul__panel{ display:flex; flex-direction:column; gap:12px; }
  .maul__panel[hidden]{ display:none; }
  .maul__tag{ margin:0; font-size:.74rem; color:#e8c84a; letter-spacing:.5px; line-height:1.55; }
  .maul__play{
    cursor:pointer; font:inherit; font-weight:bold; font-size:1.05rem; letter-spacing:4px;
    color:#080808; padding:14px 18px; border-radius:8px; border:2px outset #ffe14d;
    background:linear-gradient(180deg,#ffe14d,#d6b000);
    box-shadow:0 0 18px rgba(255,216,0,.45);
  }
  .maul__play:hover{ background:linear-gradient(180deg,#9ff6ff,#4ec6e6); }
  /* ---- PLAY STAYS ON SCREEN ON A SHORT ONE ----
     Opening the menu at its top (see the preventScroll note in showMenu) is what
     makes the tab bar reachable, and it costs the other end: on a 667px SE the
     button sits at 668px and on a phone held sideways at 540px of a 393px
     viewport, i.e. below the fold in both. Trading a hidden tab bar for a hidden
     PLAY is not a fix, so the button sticks to the bottom of the scrollport
     while the list moves under it - the wrap is align-items:stretch, so it is
     full width and covers what passes beneath rather than letting text show
     through. Sticky is a no-op when the content already fits, which is every
     desktop and the taller phones, so this only ever fires where it is needed.
     Bounded by max-height anyway: 812px and up are left exactly as drawn. */
  @media (max-height:760px){
    .maul__play{ position:sticky; bottom:10px; z-index:2; }
  }
  .maul__play:active{ border-style:inset; }
  /* The player's own record under PLAY - same gold and size as the arcmenu
     cabinets' YOUR BEST line, so the four home screens read as one family. */
  .maul__best{ margin:0; font-size:.68rem; letter-spacing:1.5px; color:#ffd68a; }
  .maul__best[hidden]{ display:none; }
  .maul__hint2{ margin:0; font-size:.62rem; color:#a08a20; letter-spacing:.5px; line-height:1.7; }
  .maul__hint2 b{ color:#e8c84a; }
  .maul__scores2{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:3px; text-align:left; }
  .maul__scores2 li{
    display:flex; align-items:center; gap:10px; padding:6px 11px; border-radius:5px;
    font-size:.8rem; background:rgba(4,4,4,.5); border:1px solid rgba(138,114,0,.4);
  }
  .maul__scores2 li.is-new{ background:rgba(255,216,0,.14); border-color:#ffd800; }
  .maul__scores2 .rank{ width:1.6em; color:#a08a20; font-size:.7rem; }
  .maul__scores2 .who{ flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis;
    white-space:nowrap; color:#fff8dc; letter-spacing:2px; }
  .maul__scores2 .pts{ color:#ffd68a; font-variant-numeric:tabular-nums; }
  .maul__scores2 .maul__empty{ display:block; color:#a08a20; letter-spacing:1px; }
  /* The builder picker. The pick decides the whole run - which towers exist,
     which wave you laugh at, which one ends you - so it is the biggest thing
     on the home panel. Only the CHOSEN card opens up its strengths and
     weaknesses: five cards each shouting four lines is a wall of text nobody
     reads, and the only one that matters is the one you are about to play. */
  .maul__sethead{
    margin:2px 0 -4px; font-size:.6rem; letter-spacing:3px; font-weight:bold;
    color:#d6b000; text-align:left;
  }
  .maul__builders{ display:flex; flex-direction:column; gap:5px; }
  .maul__bcard{
    cursor:pointer; font:inherit; text-align:left;
    display:flex; flex-direction:column; gap:2px;
    padding:8px 11px; border-radius:6px;
    background:rgba(4,4,4,.5);
    border:1px solid rgba(138,114,0,.4); border-left:4px solid var(--bc,#ffd800);
    transition:background .12s ease, border-color .12s ease;
  }
  .maul__bcard:hover{ background:rgba(255,216,0,.09); }
  .maul__bcard:focus-visible{ outline:2px solid #ffd800; outline-offset:2px; }
  .maul__bcard b{ font-size:.74rem; letter-spacing:2px; color:var(--bc,#fff8dc); }
  .maul__bcard i{
    font-style:normal; font-size:.58rem; letter-spacing:.3px;
    color:#e8c84a; line-height:1.5;
  }
  .maul__bcard u, .maul__bcard s{
    display:none; text-decoration:none; font-size:.56rem;
    letter-spacing:.3px; line-height:1.5;
  }
  .maul__bcard.is-on{
    background:rgba(255,216,0,.12); border-color:#ffd800;
    box-shadow:0 0 10px rgba(255,216,0,.22);
  }
  .maul__bcard.is-on u{ display:block; color:#9ff0c4; }
  .maul__bcard.is-on s{ display:block; color:#ffa9bd; }
  .maul__setrow{
    display:flex; align-items:center; justify-content:space-between; gap:12px;
    padding:10px 12px; border-radius:6px; text-align:left;
    background:rgba(4,4,4,.5); border:1px solid rgba(138,114,0,.4);
  }
  .maul__setrow > span{ font-size:.72rem; letter-spacing:1px; color:#ffe873; }
  .maul__setrow small{ display:block; margin-top:2px; font-size:.58rem; letter-spacing:.3px; color:#a08a20; }
  .maul__seg{ display:flex; flex:none; border:1px solid #8a7200; border-radius:6px; overflow:hidden; }
  .maul__seg button{
    cursor:pointer; font:inherit; font-size:.6rem; letter-spacing:1px;
    padding:8px 9px; border:0; color:#d6b000; background:transparent;
  }
  .maul__seg button + button{ border-left:1px solid #8a7200; }
  .maul__seg button.is-on{ background:#5a4a00; color:#fff8dc; }
  /* The mode picker rides the home panel rather than a setrow, so it stretches
     full width and its options split the row evenly instead of hugging their
     text the way the right-aligned DIFFICULTY segment does. Three of them now,
     not two, so the labels are short and the type drops a notch: a mode's
     sentence is the tag underneath, and the button only has to name it. */
  .maul__mode{ align-self:stretch; }
  .maul__mode button{
    flex:1; padding:10px 4px; font-size:.6rem; line-height:1.3;
    white-space:nowrap;
  }
  @media (max-width:420px){
    .maul__mode button{ font-size:.54rem; letter-spacing:.5px; padding:10px 2px; }
  }
  .maul__switch{
    cursor:pointer; font:inherit; font-size:.64rem; letter-spacing:1px; flex:none; min-width:72px;
    padding:8px 14px; border-radius:6px; border:1px solid #8a7200; color:#d6b000; background:transparent;
  }
  .maul__switch.is-on{ background:#5a4a00; color:#fff8dc; border-color:#e6c200; }
  .maul__menuexit{
    cursor:pointer; font:inherit; font-size:.66rem; letter-spacing:2px; margin-top:2px;
    color:#ff9db4; background:transparent; border:0; padding:8px;
  }
  .maul__menuexit:hover{ color:#ff7b9c; text-decoration:underline; }
  /* ---- the menu's own controls, for a thumb ----
     The tabs, the mode picker and EXIT were all 30-32px tall: they are sized by
     their padding and their type, and neither knows anything about fingers.
     A min-height is the whole fix - the type and the paddings stay exactly as
     drawn, the box just stops being shorter than the thing pressing it. Again
     pointer:coarse, so no desktop menu changes shape. */
  @media (pointer:coarse){
    .maul__menutabs button,
    .maul__mode button,
    .maul__seg button,
    .maul__switch,
    .maul__menuexit{ min-height:44px; }
    /* the segmented controls centre their label once they are taller than it */
    .maul__seg button, .maul__mode button{ display:flex; align-items:center; justify-content:center; }
  }
  /* Short landscape phones in immersive: tighten so it never needs scrolling. */
  @media (max-height:520px){
    .maul__menu{ padding:12px; align-items:flex-start; }
    .maul__menutitle{ font-size:1.15rem; letter-spacing:4px; }
    .maul__menuwrap{ gap:10px; max-width:420px; }
    .maul__play{ padding:11px 16px; font-size:.95rem; }
    .maul__tag{ font-size:.68rem; }
  }

  /* ================= the cabinet home screens =================
     Frost Maul's menu scene grew into the front door for EVERY cabinet:
     Brick Smash, Snake and Frost Pinball each carry one of these now, wired
     by js/arcade-menu.js. Same bones as .maul__menu - a scene over the whole
     cabinet, the run behind it until PLAY - plus a HOW TO PLAY tab, because
     a game should teach itself before it starts, not only in the aside. The
     .maul__how selectors ride along so Frost Maul's own HOW TO PLAY panel
     reads the same without importing the whole foreign namespace. */
  /* Auto-margin centring, for the reason written out over .maul__menu above -
     these two boxes are the same box, and the other three cabinets are one long
     HOW TO PLAY away from stranding their own titles the same way. */
  /* ---- AND THE GAME IS STILL THERE UNDER IT. The owner's ruling of
       2026-08-19 on what a main menu stands on: the world, held, the way Tony
       Hawk's front screen stands on a rendered scene rather than on a panel.
       This background used to be OPAQUE, so the cabinet you were looking at
       vanished the moment you touched the gear - the city paused behind a
       painted-out rectangle, which reads as having left rather than paused.

       AN ALPHA GRADIENT AND NOTHING ELSE - no backdrop-filter. A blur over a
       whole-viewport WebGL canvas is a per-frame cost on the one page that has
       none to spare, and this project has already lost a walker 13 ft to one
       (see the note on overlays in the harnesses). Over the black of a cabinet
       that has not racked its board yet the alpha reads exactly as the old
       solid did; over Austin you can see Austin. ---- */
  .arcmenu{
    position:absolute; inset:0; z-index:90;
    display:flex; align-items:flex-start; justify-content:center;
    padding:22px; box-sizing:border-box; overflow-y:auto;
    background:
      radial-gradient(120% 78% at 50% 0%, rgba(255,216,0,.14), transparent 62%),
      linear-gradient(180deg,rgba(24,24,24,.58),rgba(8,8,8,.74));
  }
  .arcmenu[hidden]{ display:none; }
  /* ---- AND THE LIST STANDS ON A CARD, because the world behind it is not a
       backdrop, it is a GAME, and a game has writing on it. The first render
       of the scrim put HOW TO PLAY straight through the middle of the board's
       own "CLICK OR PRESS SPACE TO START", which reads as a rendering fault
       rather than as depth. Tony Hawk's list stands on the same thing: a dark
       panel over the scene, not the scene itself. Translucent, so the room is
       still visibly behind the card and not merely around it. ---- */
  .arcmenu__wrap{
    width:100%; max-width:460px; display:flex; flex-direction:column;
    align-items:stretch; gap:14px; text-align:center;
    padding:18px 16px; border-radius:14px;
    background:linear-gradient(180deg,rgba(12,12,12,.72),rgba(2,12,20,.86));
    box-shadow:0 0 0 1px rgba(214,176,0,.18), 0 18px 50px rgba(0,0,0,.5);
    margin-top:auto; margin-bottom:auto;
  }
  .arcmenu__title{
    margin:0; font-size:1.5rem; letter-spacing:6px; font-weight:bold; color:#fff8dc;
    text-shadow:0 0 14px rgba(255,216,0,.7), 0 0 3px rgba(255,216,0,.9);
  }
  .arcmenu__result{
    font-size:.72rem; letter-spacing:1.5px; color:#ffd68a; line-height:1.5;
    border:1px solid rgba(255,214,138,.4); border-radius:6px;
    padding:9px 11px; background:rgba(255,214,138,.08);
  }
  .arcmenu__result[hidden]{ display:none; }
  .arcmenu__result b{ display:block; color:#fff; font-size:1rem; letter-spacing:3px; margin-bottom:3px; }
  /* THE QUESTION UNDER THE FIGURES - Crazy Uber's summary asks one (see THE
     TWO DOORS OFF THE SUMMARY in js/crazy-uber.js) and it has to read as a
     sentence rather than as one more figure, so it drops to its own line and
     out of the takings' gold. */
  .arcmenu__result small{
    display:block; margin-top:5px; font-size:.64rem; letter-spacing:.6px;
    color:#e8c84a; text-transform:none;
  }
  .arcmenu__tabs{
    display:flex; gap:4px; justify-content:center;
    border-bottom:2px solid rgba(214,176,0,.25);
  }
  .arcmenu__tabs button{
    flex:1 1 auto; cursor:pointer; font:inherit; margin-bottom:-2px;
    padding:9px 4px; font-size:.6rem; letter-spacing:1.5px;
    color:#d6b000; background:transparent; border:0; border-bottom:2px solid transparent;
  }
  .arcmenu__tabs button:hover{ color:#ffe873; }
  .arcmenu__tabs button.is-on{
    color:#ffd800; border-bottom-color:#ffd800; text-shadow:0 0 8px rgba(255,216,0,.6);
  }
  .arcmenu__panel{ display:flex; flex-direction:column; gap:12px; }
  .arcmenu__panel[hidden]{ display:none; }
  .arcmenu__tag{ margin:0; font-size:.74rem; color:#e8c84a; letter-spacing:.5px; line-height:1.55; }
  .arcmenu__play{
    cursor:pointer; font:inherit; font-weight:bold; font-size:1.05rem; letter-spacing:4px;
    color:#080808; padding:14px 18px; border-radius:8px; border:2px outset #ffe14d;
    background:linear-gradient(180deg,#ffe14d,#d6b000);
    box-shadow:0 0 18px rgba(255,216,0,.45);
  }
  .arcmenu__play:hover{ background:linear-gradient(180deg,#9ff6ff,#4ec6e6); }
  .arcmenu__play:active{ border-style:inset; }
  /* A BOARD THAT IS NOT RACKED YET. Only Crazy Uber has one - it loads a city
     before there is anything to drive - and the button says LOADING AUSTIN…
     through it (see setPlay in js/arcade-menu.js). Greyed AND :disabled, so a
     keyboard cannot tab onto a button that would do nothing either. */
  .arcmenu__play:disabled{
    filter:grayscale(.7) brightness(.62); cursor:default;
    letter-spacing:2px; font-size:.86rem;
  }
  .arcmenu__play:disabled:hover{ background:linear-gradient(180deg,#ffe14d,#d6b000); }
  /* CASH OUT: PLAY's shape, in the meter's own money-green rather than the
     cabinet's ice, because it is the opposite verb. Only Crazy Uber has one -
     see the note on the button in the markup. */
  .arcmenu__cash{
    cursor:pointer; font:inherit; font-weight:bold; font-size:.82rem; letter-spacing:2px;
    color:#04220f; padding:11px 16px; border-radius:8px; border:2px outset #7fe6a0;
    background:linear-gradient(180deg,#8df3b4,#35b46a);
    box-shadow:0 0 14px rgba(70,230,140,.35);
  }
  .arcmenu__cash:hover{ background:linear-gradient(180deg,#a6f8c6,#46c47c); }
  .arcmenu__cash:active{ border-style:inset; }
  .arcmenu__cash[hidden]{ display:none; }
  /* FREE ROAM: CASH OUT's shape in the cabinet's own ice rather than the
     meter's money-green, because it is the one answer on this screen that is
     not about money - it drops the meter and hands back the city. Paler than
     PLAY beside it: the two are a question's two answers and CONTINUE is the
     one the button under the summary is already shouting. */
  .arcmenu__roam{
    cursor:pointer; font:inherit; font-weight:bold; font-size:.82rem; letter-spacing:2px;
    color:#080808; padding:11px 16px; border-radius:8px; border:2px outset #ffe14d;
    background:linear-gradient(180deg,#ffeaa0,#5c9ec2);
    box-shadow:0 0 14px rgba(214,176,0,.28);
  }
  .arcmenu__roam:hover{ background:linear-gradient(180deg,#dcf4ff,#6eb2d4); }
  .arcmenu__roam:active{ border-style:inset; }
  .arcmenu__roam[hidden]{ display:none; }
  .arcmenu__hint{ margin:0; font-size:.62rem; color:#a08a20; letter-spacing:.5px; line-height:1.7; }
  .arcmenu__hint b{ color:#e8c84a; }
  .arcmenu__how, .maul__how{
    list-style:none; margin:0; padding:0;
    display:flex; flex-direction:column; gap:5px; text-align:left;
  }
  .arcmenu__how li, .maul__how li{
    padding:8px 11px; border-radius:5px;
    font-size:.64rem; letter-spacing:.3px; line-height:1.55; color:#e8c84a;
    background:rgba(4,4,4,.5); border:1px solid rgba(138,114,0,.4);
  }
  .arcmenu__how b, .maul__how b{ color:#e8c84a; letter-spacing:1px; }
  .arcmenu__how i, .maul__how i{ font-style:normal; color:#ffd68a; }
  /* ---- the keys, nested in the rule each one belongs to ----
     A letter stamped on a falling capsule is terse by necessity - 22px of
     canvas - so the cabinet has to spell it out somewhere, and Pinball has the
     same problem with the shapes on its table. They used to print on the bezel:
     a legend in the score aside, beside the board. That works at a desk and
     nowhere else - on a phone the aside is most of the screen, so the "bezel"
     lay across the play from the first ball. Here instead, under CAPSULES and
     THE SQUADRON and THE TABLE, where a rule and its key read as one thing.
     Chips carry the colours the canvas actually paints. */
  .arcade__pu{ list-style:none; }
  /* Shed the card the surrounding .arcmenu__how li wears - a key inside a rule
     must not read as a box inside a box - and take the row layout back off it. */
  .arcmenu__how .arcade__pu{ margin:7px 0 0; padding:0; }
  .arcmenu__how .arcade__pu li{
    display:flex; align-items:center; gap:7px; justify-content:flex-start;
    padding:2px 0; border:0; border-radius:0; background:none;
    font-size:.62rem; letter-spacing:1px; line-height:1.5; color:#d6b000;
  }
  .arcade__pu i{
    flex:0 0 15px; text-align:center; font-style:normal; font-weight:bold;
    color:#050505; border-radius:2px; line-height:1.5;
  }
  .arcade__pu .pu-w{ background:#ffd800; }
  .arcade__pu .pu-m{ background:#fff8dc; }
  .arcade__pu .pu-s{ background:#ffe873; }
  .arcade__pu .pu-c{ background:#ffe560; }
  .arcade__pu .pu-l{ background:#ffd68a; }
  .arcade__pu .pu-n{ background:#ff7b9c; }
  .arcade__pu .pu-f{ background:#ff9d5c; }
  /* Squadron hulls, in the colours the canvas actually paints them. */
  .arcade__pu .en-g{ background:#ffd800; }
  .arcade__pu .en-d{ background:#7dffc4; }
  .arcade__pu .en-u{ background:#ffd68a; }
  .arcade__pu .en-h{ background:#c2a3ff; }
  .arcade__pu .en-f{ background:#ff5c7a; }
  /* The second half of a legend row - what a capsule becomes once the wall has
     ruptured, or what a hull costs you. Dimmer, so the row still reads as one
     name with a footnote rather than two competing labels. */
  .arcade__pu span{ color:#8a7200; }
  /* The two hazards are named in their own colour so the list reads at a
     glance as "mostly good, watch these two". Scoped past the row rule above,
     which is one class heavier than the plain .bad this replaces. */
  .arcmenu__how .arcade__pu li.bad{ color:#ff9d5c; }
  .arcade__pu .bad span{ color:#a8552f; }
  .arcmenu__scores{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:3px; text-align:left; }
  .arcmenu__scores li{
    display:flex; align-items:center; gap:10px; padding:6px 11px; border-radius:5px;
    font-size:.8rem; background:rgba(4,4,4,.5); border:1px solid rgba(138,114,0,.4);
  }
  .arcmenu__scores li.is-new{ background:rgba(255,216,0,.14); border-color:#ffd800; }
  .arcmenu__scores .rank{ width:1.6em; color:#a08a20; font-size:.7rem; }
  .arcmenu__scores .who{ flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis;
    white-space:nowrap; color:#fff8dc; letter-spacing:2px; }
  .arcmenu__scores .pts{ color:#ffd68a; font-variant-numeric:tabular-nums; }
  .arcmenu__setrow{
    display:flex; align-items:center; justify-content:space-between; gap:12px;
    padding:10px 12px; border-radius:6px; text-align:left;
    background:rgba(4,4,4,.5); border:1px solid rgba(138,114,0,.4);
  }
  .arcmenu__setrow > span{ font-size:.72rem; letter-spacing:1px; color:#ffe873; }
  .arcmenu__setrow small{ display:block; margin-top:2px; font-size:.58rem; letter-spacing:.3px; color:#a08a20; }
  .arcmenu__seg{ display:flex; flex:none; border:1px solid #8a7200; border-radius:6px; overflow:hidden; }
  .arcmenu__seg button{
    cursor:pointer; font:inherit; font-size:.6rem; letter-spacing:1px;
    padding:8px 9px; border:0; color:#d6b000; background:transparent;
  }
  .arcmenu__seg button + button{ border-left:1px solid #8a7200; }
  .arcmenu__seg button.is-on{ background:#5a4a00; color:#fff8dc; }
  /* the third control on a settings row — see js/arcade-menu.js's range branch.
     The caption is tabular and width-reserved so the track does not shuffle
     sideways as the number under the thumb changes width mid-drag. */
  .arcmenu__range{ display:flex; align-items:center; gap:10px; flex:none; }
  .arcmenu__range input[type=range]{ -webkit-appearance:none; appearance:none;
    width:132px; height:22px; background:transparent; cursor:pointer; margin:0; }
  .arcmenu__range input[type=range]::-webkit-slider-runnable-track{ height:5px;
    border-radius:999px; background:rgba(138,114,0,.45); border:1px solid #8a7200; }
  .arcmenu__range input[type=range]::-moz-range-track{ height:5px;
    border-radius:999px; background:rgba(138,114,0,.45); border:1px solid #8a7200; }
  .arcmenu__range input[type=range]::-webkit-slider-thumb{ -webkit-appearance:none;
    appearance:none; width:16px; height:16px; margin-top:-7px; border-radius:50%;
    background:#ffd800; border:2px solid #050505; }
  .arcmenu__range input[type=range]::-moz-range-thumb{ width:16px; height:16px;
    border-radius:50%; background:#ffd800; border:2px solid #050505; }
  .arcmenu__range b{ font-size:.6rem; letter-spacing:1px; color:#fff8dc;
    min-width:3.4em; text-align:right; font-variant-numeric:tabular-nums; }
  .arcmenu__switch{
    cursor:pointer; font:inherit; font-size:.64rem; letter-spacing:1px; flex:none; min-width:72px;
    padding:8px 14px; border-radius:6px; border:1px solid #8a7200; color:#d6b000; background:transparent;
  }
  .arcmenu__switch.is-on{ background:#5a4a00; color:#fff8dc; border-color:#e6c200; }
  .arcmenu__exit{
    cursor:pointer; font:inherit; font-size:.66rem; letter-spacing:2px; margin-top:2px;
    color:#ff9db4; background:transparent; border:0; padding:8px;
  }
  .arcmenu__exit:hover{ color:#ff7b9c; text-decoration:underline; }

  /* ===================================================================
     THE MAIN MENU IS A LIST — reference/main menu reference.jpeg

     The owner's ruling of 2026-08-19: the front screen is the Tony Hawk's Pro
     Skater main menu. A title plate, one centred column of big items, a
     diamond either side of the one you are standing on, and a legend along the
     bottom. js/arcade-menu.js assembles it out of the markup each cabinet
     already carries — see its header — so everything down here is the SKIN on
     controls that were a tab bar and four differently-shaped buttons.

     THE DIAMOND IS #ff2d95 AND THAT IS THE PALETTE'S ANSWER TO A PS1 RED.
     `--warm` in css/style.css, *"used sparingly"*: one mark on one row is the
     definition of sparingly, and importing a foreign red into an ice room to
     match a screenshot is the restyle-by-taste the art rules forbid. Written
     as a literal here rather than as var(--warm) on purpose — this block is
     copied into no other document, but the lesson that a bare var() paints
     NOTHING on a page that links no stylesheet has cost this project a HUD
     already.

     EACH ROW KEEPS ITS OWN COLOUR. Tony Hawk's list is one colour with the
     selection in yellow; ours is not, because these rows are not peers — DRIVE,
     CASH OUT and EXIT are three different kinds of decision and the cabinet has
     said so in ice, money-green and pink since before there was a list. The
     selection is the two diamonds, a faint plate and a brightening, all of
     which work on top of any of them.
     =================================================================== */
  .arcmenu__tabs[hidden]{ display:none; }
  /* the cabinet's own plate, while the menu is over it — see screenClass() in
     js/arcade-menu.js for why this is a class and not a blanked element */
  .is-arcmenu .arcade__msg{ display:none; }
  .arcmenu__hd{
    margin:0; font-size:.7rem; letter-spacing:4px; color:#ffd68a;
    text-shadow:0 0 10px rgba(255,214,138,.35);
  }
  .arcmenu__hd[hidden]{ display:none; }
  .arcmenu__list{ display:flex; flex-direction:column; align-items:stretch; gap:0; }
  /* The skins the four moved buttons wore are still theirs OUTSIDE this box —
     .arcmenu__list wins on specificity, not on being written later. */
  /* THE ROW IS FULL WIDTH AND THE DIAMONDS ARE NOT PINNED TO ITS EDGES. Both
     halves of that matter and they pull opposite ways: a thumb wants the whole
     460px strip to be the target, and the reference wants the two marks either
     side of the WORDS. Absolutely-positioned marks gave the first render a pair
     of pink specks stranded 200px away from the label they were pointing at.
     Flex items instead — they hug the text, and they hold their space at
     opacity 0, so nothing on the list shifts as the selection moves. */
  .arcmenu__list .arcmenu__row{
    display:flex; align-items:center; justify-content:center; gap:15px;
    width:100%; box-sizing:border-box;
    cursor:pointer; font:inherit; font-weight:bold; text-transform:uppercase;
    font-size:1rem; letter-spacing:3px; line-height:1.25;
    padding:8px 12px; margin:0; border:0; border-radius:8px;
    background:transparent; box-shadow:none; filter:none;
    color:#ffe873; text-shadow:0 2px 8px rgba(0,0,0,.7);
  }
  /* AND A HIDDEN ROW IS STILL HIDDEN. `.arcmenu__cash[hidden]{display:none}`
     up above is 0,2,0 and so is `.arcmenu__list .arcmenu__row` — equal
     specificity, later rule wins, so the display:flex on the line above
     UN-HID Crazy Uber's CASH OUT and FREE ROAM the moment they moved into the
     list. Caught by the row dump disagreeing with the photograph: the DOM said
     `[hidden]`, the PNG had it on the glass. */
  .arcmenu__list .arcmenu__row[hidden]{ display:none; }
  .arcmenu__list .arcmenu__row::before,
  .arcmenu__list .arcmenu__row::after{
    content:"\25C6"; flex:none;
    font-size:.62em; letter-spacing:0; color:#ff2d95; opacity:0;
    text-shadow:0 0 10px rgba(255,45,149,.75);
  }
  .arcmenu__list .arcmenu__row.is-sel{ background:rgba(255,216,0,.12); filter:brightness(1.2); }
  .arcmenu__list .arcmenu__row.is-sel::before,
  .arcmenu__list .arcmenu__row.is-sel::after{ opacity:1; }
  .arcmenu__list .arcmenu__row:focus-visible{ outline:2px solid #ffd800; outline-offset:-2px; }
  /* THE FIRST ROW IS THE VERB THE MACHINE IS FOR, so it is bigger and it is the
     cabinet's own cyan. Everything under it is navigation. */
  .arcmenu__list .arcmenu__play{
    font-size:1.18rem; letter-spacing:5px; color:#ffe14d;
    text-shadow:0 0 16px rgba(255,216,0,.5), 0 2px 8px rgba(0,0,0,.7);
  }
  .arcmenu__list .arcmenu__play:hover,
  .arcmenu__list .arcmenu__cash:hover,
  .arcmenu__list .arcmenu__roam:hover{ background:rgba(255,216,0,.12); }
  /* LOADING AUSTIN… — still a row, still where the selection sits, and
     visibly not pressable. */
  .arcmenu__list .arcmenu__play:disabled{
    color:#a08a20; cursor:default; filter:none;
    font-size:.86rem; letter-spacing:2px; text-shadow:none;
  }
  .arcmenu__list .arcmenu__play:disabled:hover{ background:transparent; }
  .arcmenu__list .arcmenu__cash{ color:#8df3b4; font-size:.92rem; }
  .arcmenu__list .arcmenu__roam{ color:#ffeaa0; font-size:.92rem; }
  .arcmenu__list .arcmenu__page{ color:#ffe873; font-size:.92rem; }
  .arcmenu__list .arcmenu__back{ color:#d6b000; font-size:.82rem; letter-spacing:2px; }
  .arcmenu__list .arcmenu__exit{
    color:#ff9db4; font-size:.82rem; letter-spacing:2px; margin-top:4px;
    text-decoration:none;
  }
  .arcmenu__list .arcmenu__exit:hover{ text-decoration:none; background:rgba(255,45,149,.12); }
  /* THE LEGEND IS A KEYBOARD LEGEND. A phone has none of these three keys, and
     a row of instructions for hardware that is not there is the kind of chrome
     ground rule 3 exists to keep off the glass. */
  .arcmenu__legend{
    margin:4px 0 0; font-size:.55rem; letter-spacing:2px; color:#a08a20;
  }
  @media (pointer:coarse){
    .arcmenu__legend{ display:none; }
    .arcmenu__list .arcmenu__row{ min-height:44px; }
  }
  /* ===================================================================
     THE CAR SELECT'S ONE PIECE OF GLASS.

     There was a fanned crate of cards here and it is gone — the owner's ruling
     on seeing it was that the picker belongs IN THE WORLD, over 3D Austin, with
     the car standing on the road and the avatar beside it, and that even his
     own reference frame had too much HUD on it. js/drive-select.js is the
     stage; this is the ONLY thing drawn on top of it: the car's name.

     BOTTOM CENTRE, which is the one strip free in both orientations — the
     bottom corners are thumbs (js/frost-stick.js), top left is the gear and top
     right is the dial. It is the same rectangle the GET OUT button uses, and
     the two are never up at once.
     =================================================================== */
  .ub__pick{
    position:absolute; left:0; right:0; bottom:7%; z-index:24;
    margin:0; text-align:center; pointer-events:none;
    font-size:1.15rem; letter-spacing:7px; font-weight:bold; color:#fff8dc;
    text-shadow:0 0 16px rgba(255,216,0,.75), 0 2px 10px rgba(0,0,0,.85);
  }
  .ub__pick[hidden]{ display:none; }
  /* ===== AND EVERYTHING ELSE GETS OUT OF THE WAY.
     "even that has too much hud" was the ruling on a reference frame that had
     a character list, a car list, stat bars and two buttons on it. The stage
     itself draws none of that — but the CABINET's own glass is still up, and
     the first render had the speedo, the GET OUT prompt and the steering wheel
     over a parked car nobody was in. One class on the screen takes the lot
     down; the gear stays, because it is the only way out of this cabinet and a
     screen with no exit is worse than a screen with one control. */
  .arcade__screen.is-select .ub__dial,
  .arcade__screen.is-select .ub__act,
  .arcade__screen.is-select .ub__ask,
  .arcade__screen.is-select .dct__call,
  .arcade__screen.is-select .dct__time,
  .arcade__screen.is-select .dct__meter,
  .arcade__screen.is-select .dmap,
  .arcade__screen.is-select .fstick,
  .arcade__screen.is-select .fstick-ped{ display:none !important; }
  @media (max-height:520px){
    .ub__pick{ font-size:.95rem; letter-spacing:5px; bottom:6%; }
  }

  @media (max-height:520px){
    .arcmenu{ padding:12px; align-items:flex-start; }
    .arcmenu__title{ font-size:1.15rem; letter-spacing:4px; }
    .arcmenu__wrap{ gap:10px; max-width:420px; }
    .arcmenu__play{ padding:11px 16px; font-size:.95rem; }
    .arcmenu__tag{ font-size:.68rem; }
    /* A LANDSCAPE PHONE IS THE TEST THE LIST HAS TO PASS: seven rows down one
       column is the shape most likely to run off the bottom of 390px, and the
       one the FREE ROAM door already fell off once. */
    .arcmenu__list .arcmenu__row{ font-size:.86rem; letter-spacing:2px; padding:7px 34px; }
    .arcmenu__list .arcmenu__play{ font-size:1rem; letter-spacing:3px; }
    .arcmenu__list .arcmenu__cash,
    .arcmenu__list .arcmenu__roam,
    .arcmenu__list .arcmenu__page{ font-size:.82rem; }
    .arcmenu__list .arcmenu__exit,
    .arcmenu__list .arcmenu__back{ font-size:.74rem; }
    /* ---- AND THE DOORS STAY ON THE GLASS. Measured, not guessed: Crazy Uber's
       SETTINGS page is seven rows, and on an 844x390 landscape phone that put
       BACK TO THE CAR at y=673 and END THE SHIFT at y=721 in a 390px viewport —
       both of them about 300px below the bottom of the world, on the one screen
       whose entire job is to offer two choices. It is the same failure the FREE
       ROAM door had on the summary (ROADMAP-DRIVE §Item 1b), arriving from the
       other direction: there it was four lines of advertisement, here it is the
       settings themselves.

       STICKY RATHER THAN SHORTER. The rows are the cabinet's and none of them
       is spare, so the list rides the bottom of the scrollport and the panel
       scrolls behind it. The fade is not decoration: without a background the
       setting rows read straight THROUGH the doors as they pass under. ---- */
    .arcmenu__list{
      /* -12px, NOT 0: the scrollport is `.arcmenu` and it wears 12px of padding
         in this query, so a list stuck at 0 leaves a 12px strip of the panel
         still scrolling past UNDER the doors. The negative offset plus the
         matching padding puts the box's own background over that strip. */
      position:sticky; bottom:-12px; z-index:2; padding:12px 0;
      background:linear-gradient(180deg,rgba(2,12,20,0) 0,#020c14 12px);
    }
  }

