
/* ============================================================
   COIN DICE — the 0–100 slider, built to
   ../../work/casino/docs/producer/dice-build-spec.md.

   Classic script, no build step, runs by double-clicking this file
   over file://. No type="module" anywhere: over file:// a module is
   fetched with CORS from origin "null" and Chrome refuses it, so
   NOTHING would run — not the game, not the shared panels.

   THE CHROME IS COPIED, NOT DESIGNED. The lockup, the four-button
   corner rail and the data-chromedock compressions are Mines'
   (games/Mines/index.html:251-375 and :10150) with only the game's
   own word changed. Two corrections the owner already made once are
   carried over verbatim and are marked where they live: settings is
   LAST, and its icon is Plinko's sliders and not a gear.

   WHAT IS THIS GAME'S OWN: the track, the travelling result, the
   four bet modes, the odds dial, the session ledger and the cap
   line. Those are commented where the reasoning is not obvious.
   ============================================================ */
:root{
  /* brand — the same block Mines and Plinko declare, unchanged */
  --cp-red:#C0180C;   --cp-red-hi:#F24235;   --cp-red-lo:#8E1108;
  --cp-border:#35394A;
  --e3:#262A35;
  --ink:#E7E9F0; --off:#7E8498;

  /* TASK 4: THE ACCENT IS THE HOUSE RED, AND NOTHING ELSE IS RED. v1 marked
     the winning band, the travelling result, the odds dial's lit sector and
     the cap line in four gold custom properties this build owned outright —
     a cream, a mid-tone, a low tone and an edge tone — never v1's literal
     ember, which never reached this file. All four tokens and every one of
     their fourteen call sites are deleted; the sites that were marking THE
     ACCENT now read --acc / --acc2 below, a VAR ON A VAR on purpose: the
     goo CSS names --acc, so a tenant swap stays a token edit and never
     reaches a component. */
  --acc:var(--cp-red);
  --acc2:var(--cp-red-lo);

  /* the stage's radial ground: warm at the foot, near-black at the head */
  --bg0:#2A0E0C;
  --bg1:#090A0F;

  /* MONEY UP IS ITS OWN COLOUR, NOT THE ACCENT'S. The profit-on-win figure
     and the ledger's "up" values were also gold in v1, for the same reason
     the band was: gold was the only colour in the file. Left on --acc they
     would say "money" and "brand" in the same hue, so they read --win
     instead. */
  /* TASK 9: THE WIN IS MINES' GOLD, BY THE OWNER'S OWN WORDS ("the winning
     colors should be yellowish like mines", sent with v1's gold win state).
     #F3C33F is Mines' --gold, lifted verbatim from games/Mines/src/css/
     00-tokens.css:37 rather than re-derived, so the two games cannot drift
     apart a shade at a time. The TOKEN is repointed rather than a second
     gold introduced beside it: --win is the one semantic name every win
     surface already reads (the figure, the blob, the bead, the history
     pips, the balance delta, the Last-rounds up-states), so one edit moves
     all of them together and no call site holds a hex. The green that stood
     here (#5EE39B) leaves the file entirely. Contrast, verified in the
     render rather than assumed: gold on this near-black board is Mines' own
     shipped pairing, and the two rgba glow mirrors in 40-track.css moved to
     243,195,63 in the same change. (--loss has since become a text-only
     ink and the line's loss is the ember --acc-out — see both below.) */
  --win:#F3C33F;

  /* THE LOSS INK — EMBER LAW (colors gauntlet, owner-ratified 2026-08-26).
     Task 7's #5A6A8F blue-grey did two jobs and failed one, measured: as
     TEXT on the panel ground it was 3.63:1 (below AA) while --win on
     identical rows read 11.82:1 — losing rows were harder to read than
     winning ones. Repointed to #8E93A3: the same cool family (hue 220→226),
     half the saturation, +14 L — 6.38:1 on the panel, and the hex
     80-auto.css's run readout already used, now unified through the token.
     --loss is a TEXT colour ONLY now — the figure, #chipn, #dl, the
     ledger/Last-rounds down-states. The BOARD's loss is not this hue or
     any hue: it is the house red going out (--acc-out below); the foreign
     blue-grey leaves the line entirely. */
  --loss:#8E93A3;

  /* THE ACCENT, OUT — the ember the whole filtered line banks to on a
     loss (40-track.css's [data-res="lose"] readers, all inside the goo
     group). SOLVED, NOT PICKED: >=1.5:1 above the board ground so the
     blob stays an object (#48201B fails at 1.35), and <=0.5x the
     dead-zone tick's luminance so the inversion holds — on a loss the
     paying region falls BELOW the dead zone and the pale ruler ticks the
     roll landed among become the brightest marks on the line (#6B332B
     fails at 0.55+). The player never has to learn a hue: the verdict is
     how much light the line carries (rest 2.0x the dead zone, win 9.9x,
     loss 0.67x). */
  --acc-out:#5A2A24;

  /* --icoin IS BACK — task (r2) 2. Deleted in the final whole-branch review
     for having zero readers (see the block comment two groups up); the
     CoinPoker mark riding the blob is the reader now. A warm off-white
     rather than plain #fff: the blob under it is --acc/--win/--acc-out in
     turn, and a pure white mark went flat and paper-cut against all three,
     while this cream keeps the same warmth as --bg0/--ink without tipping
     into any one of the three verdict colours. Constant across win/lose —
     see the r2 report for why the mark doesn't recolour with the blob. */
  --icoin:#FBEAE0;

  /* THE MODE PILL IS RED WITH WHITE INK — the owner corrected the previous
     round's reading of their mark: "I meant the text to be white the button
     should be red." So r2 task 4's accent fill was right all along; what was
     wrong was the ACTIVE LABEL's ink (#2B0805, "the red's own shadow", near-
     invisible on the fill). The pair still lives HERE so the pill has one
     writer and 80-auto.css holds no stray hexes. */
  --pill:var(--cp-red);
  --pill-ink:#FFFFFF;

  --panel:#111319; --frame-line:#22252F;
  --hair:#20232C;

  /* WF-UI (UI-CHOICE grafts B2/§1): THE WELL'S TWO BLACKS AND THE CARD'S
     PADDING. --well-hi/--well-lo are graft B2's inset stake well — the
     field blacks the winning render used, named once so the gradient's two
     stops cannot drift apart. --floorpad WAS the console's standoff from
     the horizon line; the horizon is retired (owner: "We can have centered
     with the UI organized in a container") and the same token is the
     console CARD's own padding now — 70-dock.css still steps it per
     density, so the rungs that were solved against it keep their sums.
     --skygap is the play group's standoff above the card — layout()
     writes it (28/18/2, scaled by --sc at big frames) and counts it in
     the budget. */
  --well-hi:#06070B;
  --well-lo:#0C0E14;
  --floorpad:20px;
  --skygap:28px;

  /* layout(), and only layout(), writes these */
  /* --floorh IS DELETED WITH THE HORIZON — the ground it measured (frame
     bottom to console top) no longer exists: the unit centres in the frame
     and the console carries its own card. #hzGround and the .stars
     stop-line were its only readers and both left with it. */
  /* --sc IS THE INSTRUMENT'S OWN SCALE — playw/760, floored at 1, written
     by layout() when the big-frame cap reopens (2544-class monitors; see
     layoutPass). Every game-object size that was solved at the 760
     measure multiplies by it (track height, dots, mark, bubble, ticks) so
     the unit grows as one drawing. --scc is the CONSOLE and readouts'
     damped copy (1 + (sc-1)*.62): control type scaled 1:1 read as a
     poster at 2544, measured on the render. Both are exactly 1 at every
     frame below the 1382px reopen line, so the approved frames cannot
     move a pixel. */
  --sc:1;
  --scc:1;
  --pad:22px;
  /* --dockw IS DELETED AGAIN, this time with its layout: the owner chose the
     centred column (candidate A), the side dock is retired, and a width token
     for a column that no longer exists has no reader. --dockh stayed dead
     through both lives of the column; neither returns. */
  /* --num, not --fig: type.css already owns --fig as the figure FONT
     FAMILY and a second meaning for the same name would resolve the family
     to "52px" in every rule that reads it — a whole frame of Times New Roman
     with no error anywhere. */
  --num:52px;      /* the travelling result's type size */
  /* --railh IS DELETED, not retired. It stood in for #tk's height in the
     spent budget layout() computes, at a value (10/13/20px) that stopped
     matching #tk (110px, 40-track.css) the day the goo track shipped —
     game.js reads #tk's own offsetHeight now instead. */
  /* --wellpad IS DELETED, not retired. It padded the well, which is what made
     the rail 122px narrower than everything around it and put the result on top
     of the bet panel; the room the chip needs is now solved as --chipmin /
     --chipmax against the board's own margin. A token with no reader is deleted
     the same day its reader is. */
  /* --icoin, --winink, --stage, --dim, --e0, --e1, --e2 ARE DELETED, same
     rule as --wellpad above — final whole-branch review, checked with grep
     across every file in this folder (css/*.css and js/*.js) before
     deleting any of them: zero readers, none. --e3 stays (70-dock.css:193
     reads it); --panel stays too, despite reading zero here — the Games/
     Dice/Tokens Storybook story (packages/ui/src/games/dice/
     DiceTokens.stories.tsx) imports this file directly and uses --panel as
     every swatch card's own background, a real external reader this
     folder's own grep cannot see. */
}

*{box-sizing:border-box}
html,body{margin:0;height:100%;background:#000;overflow:hidden}
/* the tap-highlight reset (casino-manager, fix round 2026-08-27): Chromium
   paints its own unstyled pale rectangle over any tappable box whose
   pointerdown does not preventDefault, and the S1-acknowledge branch
   shipped one gesture that didn't — a full-width foreign flash over the
   goo track (culprit-A-deadtap.png). The branch now takes preventDefault
   (the brace); this is the belt, and it is what every reference operator
   ships as a base style: NO browser-drawn highlight anywhere on a board
   whose every acknowledgement is designed. Inherited, so one write covers
   every current and future coarse-pointer target. */
body{font-family:var(--ui);color:var(--ink);-webkit-font-smoothing:antialiased;
  -webkit-tap-highlight-color:transparent}
button,input{font-family:inherit;color:inherit}
button{background:none;border:0;padding:0;cursor:pointer}
:focus-visible{outline:2px solid var(--cp-red-hi);outline-offset:2px}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}


#app{position:fixed;inset:0;background:#000}
/* THE FRAME IS THE HOUSE'S, AND ROUND 1 PUT IT ON THE WRONG OBJECT.
   Measured against both siblings at 1400x860: Mines and Plinko are inset 14px,
   radius 20, `1px solid #22252F`, with a FRAMELESS dock floating on the board.
   Round 1 shipped a bleeding frame with no border and a bordered, rounded dock
   — the house's window treatment moved onto the panel inside it. Both halves
   are corrected here and in #dock below.

   ONE ROOM, TWO GRADIENTS, NO CANVAS. Mines bakes a plate because it dithers a
   25-tile field; this frame has one bright object on it and a CSS radial is the
   whole requirement. --cx/--cy are written by layout().

   THE GROUND'S TEMPERATURE IS MEASURED, NOT ASSUMED. All three reviewers called
   a "red-tinted vignette" the strongest not-our-operator tell and said both
   house games have one. Sampled instead — the darkest 40% of 6px patches across
   each full frame — they do not: Plinko is R-B -4.05 (COOL), Bird Crash -2.36,
   and only Mines is warm at +1.02. Round 1's Dice was -4.94, i.e. 0.89 cooler
   than Plinko and inside the siblings' own 5.07-unit spread. So a red vignette
   would put this game further from the family than it was, and it would reverse
   an owner ruling Mines records twice ("the owner removed it from the
   background TWICE... mode 'a' carries no hue at all").
   What the ground DOES do is move to the middle of the measured spread rather
   than sit at its cold end. The real cause of the tell is treated where it
   lives: the CTA, which is this frame's only red mass and which round 1 shipped
   as a gradient at 52px against the siblings' flat 60px slab. */
/* AND IT BLEEDS ON A SMALL FRAME. A 14px inset with a border costs 28px on
   both axes, which is 9.3% of the height of a 380x300 docked window — the size
   where the board already has the least to spend. Both siblings ship exactly
   this escape (`#frame[data-bleed]{border:0;border-radius:0;box-shadow:none}`)
   for exactly this reason, so it arrives here as the house rule rather than as
   a local exception. The window chrome is the frame at that size. */
#frame[data-bleed]{inset:0;border:0;border-radius:0;box-shadow:none}
/* ROUND 3: THE GROUND IS THE WHOLE FRAME'S, AND IT IS TWO LAYERS.
   Owner: "have the background everywhere". r2 had TWO grounds — this cool
   measured one on #frame and a warm one scoped to #board — so the sky
   stopped at the stage's border and the bet panel sat on flat black. They
   are one stack now, under everything.
   The BASE layer is unchanged, and deliberately: the sampling above is why
   it is cool, and a red vignette over the whole window is the one thing all
   three reviewers named as the not-our-operator tell.
   What sits on top of it is not a vignette — it is a BLOOM, and it is
   tied to the object that emits it. --cx/--cy are written by layout() and
   point at the goo track's own centre (they used to point at the middle
   of the board column), so the warm light in the room reads as coming from
   the one bright object on the frame rather than being painted on the walls.
   It is held under .17 alpha and fades out by 72%, which keeps the corners
   and the console on the measured cool ground.
   WIN ROUND (2026-08-27): THE BLOOM'S PAINTING ELEMENT MOVED — the red
   radial that was this background's FIRST layer now lives on #roomwarm
   (20-stage.css), a frame child under the sky, same geometry, same alphas,
   same --cx/--cy, so the room's light can CHANGE HANDS on a win: warm
   CROSSFADES to the gold #roomlit, never stacks under it — the stacked
   version was rendered and it is sepia mud (W4's own proof,
   work/dice/shots/win-round/p4-room). Only the painting element moved; the
   measured cool-ground argument above still governs what the base layer is,
   and the idle/loss frame is pixel-identical to what this rule painted. */
#frame{
  position:absolute;inset:14px;overflow:hidden;
  border:1px solid var(--frame-line);border-radius:20px;
  box-shadow:0 24px 70px -20px rgba(0,0,0,.9), inset 0 0 0 1px rgba(255,255,255,.02);
  background:
    radial-gradient(120% 90% at var(--cx,50%) var(--cy,46%), #16151A 0%, #0C0B0F 46%, #060608 100%),
    #000;
}


/* ==========================================================================
   1. THE CHROME — games/Mines/index.html:251-375, copied.
   The lockup floats in one top corner and the doors in the other; there is
   no rail and no horizontal rule, because Plinko has neither and the rule
   was the loudest not-Plinko tell on the first Mines frame.
   ========================================================================== */
.brand{
  position:absolute;top:18px;left:26px;z-index:7;
  display:grid;grid-template-columns:auto auto;column-gap:11px;row-gap:3px;
  align-items:center;pointer-events:none;user-select:none;
}
.brand .mk{grid-column:1;grid-row:1 / span 2;width:26px;height:26px;display:block;
  filter:drop-shadow(0 0 10px rgba(192,24,12,.55))}
/* THE WORDMARK IS SIZED BY ITS INK, NOT BY ITS BOX — Mines' measurement of the
   shipped file, kept: the ink sits at y 9.90-25.85 of a 37.5-unit viewBox, so
   26.4% of the box above the caps and 31.1% below is transparent air, and the
   two negative margins collapse the box back onto the ink. Both files are used
   unmodified as <img>, which is the house pattern and the only spelling that is
   font-proof over file://. */
.brand .wm{grid-column:2;grid-row:1;align-self:end;display:block;
  width:68.6px;height:25.6px;margin:-6.8px 0 -8px}
.brand span{grid-column:2;grid-row:2;align-self:start;
  font:600 10.5px/1 var(--ui);color:#6E7484;letter-spacing:.22em;text-transform:uppercase}

/* THE DOORS CENTRE ON THE MARK (owner, 2026-09-02: "the wordmark and icons
   are not vertically aligned, move icons a bit higher"). The shared row
   shares the lockup's TOP EDGE (--top:18px, chrome.css), but its doors are
   44px tall on the touch rung and 36 on desktop against the 26px mark, so
   the icons' centre sat 9px under the mark's on the phone, 5px on desktop
   — and 3px ABOVE it on the tight rungs, whose --top is 12. Measured at
   five frames. The centre line is the mark's: 18 (the lockup's top) + 13
   (half the mark) = 31px, and the row's top is that less half its own
   door. RISE, NEVER SINK: the shared row min()s that against the rung's own
   designed top, so the row moves only where the ask points — up 5 on
   desktop, up 9 on the touch rung — and the tight rungs (door 32-34, top
   12, already 3px ABOVE the mark's centre) stay byte-identical. The suite's
   baseline table caught the unbounded version: --band follows the row's
   real bottom (layout()), and centring the tight rungs DOWN by 3px cost
   the 380x300 result figure a pixel it does not have. The mechanism is the
   shared row's (chrome.css, --lockup-mid); this game only declares its
   line. The brand has no rungs here, so one number. */
#frame{--lockup-mid:31px}

/* THE DOORS ARE THE SHARED ROW NOW — games/_shared/chrome.css. What stood
   here was Plinko's ladder on Mines' mechanism: eight rungs keyed off
   data-chromedock / data-tiny / data-port / data-touch, which no other game
   spelled the same way. The geometry and the ladder are one file for all
   four games; the rung is an attribute on #corner written from the
   cabinet's measured box, so it cannot disagree with layout()'s own view
   of the frame the way two independent opinions did. */


/* ==========================================================================
   2. THE SPINE — board and dock. Two layouts and layout() picks between
   them on ONE test (is there room for a flank?), never on taste.
   ========================================================================== */
/* ROUND 3, TASK 7: ONE CONTAINER, STRETCHED. The floating round centred a
   pane-plus-controls pair as a block and let the sky own the rest; the owner
   looked at it and named the real problem ("the UI looks so poorly organized,
   get inspired by Plinko and bird"). Both references share one grammar: ONE
   bordered surface holds the whole game, the board is the hero inside it, and
   the bet controls are a single tight strip of that same surface - Plinko's a
   ~300px column on the flank, Bird's a band along the floor. So #stage is one
   stretched cell and #board - the container - fills it; the sky still paints
   the frame behind it and reads through the glass. */
#stage{position:absolute;inset:0;display:grid;
  grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr);
  padding:var(--band,62px) var(--pad) var(--pad)}

/* TASK (r2) 5: THE STAGE AND THE BET UI GET THEIR OWN CONTAINERS. The
   owner's words: "I also like there is a container for the game and a
   different one for the bet UI." Measured against what was actually
   shipping: #stage — the GRID, spanning both the board column and the dock
   column — carried the radial ground and the starfield, so both bled under
   the dock as much as under the board and neither object read as bounded.
   #dock (in the flank) never had a panel of its own either — see 70-dock.css's
   own "FRAMELESS IN THE FLANK" ruling, which answered a different complaint
   (round 1's dock wore the FRAME's own window treatment one level too deep)
   and is not reopened here. The fix moves the ground and the stars off
   #stage — which stays a pure grid, no ground of its own — and onto #board,
   which is now a bounded panel exactly the way v1's `.stage` is: its own
   border, its own radius, its own ground. #dock keeps ITS existing panel
   language (bordered card in the stack, sized-to-content card centred on a
   scrim in the flank — 70-dock.css, untouched) rather than gaining a new
   one, which is what "keep the dock's own panel distinct from it" asks for:
   two different treatments reading as two different objects, not one
   treatment copied onto both. */
/* THE CONTAINER IS SINGULAR AND IT IS #frame — the owner, this round: "no
   multiple containers a single one with the background". The glass card that
   stood here (border, radius, lit top edge, its own wash) was a second
   container inside the bordered frame, and it is DELETED, not dimmed: #frame
   already carries the one border, the one radius, the radial ground and the
   starfield (05-frame.css), so #board is a pure layout region now — no
   border, no background, no shadow of its own. overflow:hidden survives
   because the travelling chip is clamped to this box (§14 of the check) and
   the clip is the backstop the clamp is measured against.
   ONE ARRANGEMENT, the owner's candidate A: the game above, the console
   beneath, one centred column at every width. The side-column lay
   (data-lay="side", the left dock with its darker wash and hairline) is
   retired with the ruling that chose A; the two console DENSITIES that
   remain are 70-dock.css's business. */
/* THE CLUSTER IS ONE OBJECT AND THE SKY TAKES THE SLACK — the owner, at
   2516x1261: "we should always center the game and UI". The 1fr-game-row
   arrangement this replaces pinned the console to the frame's foot and let
   the game region keep every spare pixel, so at a big monitor the modes,
   the track and the console drifted hundreds of px apart (measured: 263px
   between modes and track at 2560x1261, against 22.8 at 1280x760) — three
   strata, not a game. Now the playable unit — modes, track, readouts,
   console — keeps its own fixed rhythm (--pgap/--ppadv, per data-room,
   never per frame height) and the two minmax(0,1fr) SKY rows above and
   below it split ALL the slack equally: the cluster floats centred in the
   region at every size. minmax(0,*) rather than align-content:center so a
   frame too small for the cluster collapses the sky to zero and overflows
   the way the demotion pass has always measured it, instead of leaking out
   of both ends of a centred box. */
/* THE UNIT CENTRES — THE OWNER'S RULING, NOW TWICE. Q1's bottom anchor
   (one sky row above, the cluster landed on a horizon at the frame's
   foot) went to the owner's real screen and came back with its verdict
   verbatim: "How is this good? I'm literally playing on bottom 20%
   screen." / "We can have centered with the UI organized in a
   container." So the two-sky-row centring returns AND KEEPS the fix the
   bottom anchor was made for: the cluster — modes, track, readouts,
   console card — is still one block with a fixed internal rhythm
   (--pgap/--skygap, layout()-written, never a function of frame height),
   and the two minmax(0,1fr) sky rows split ALL the slack equally — sky
   above AND below, at every size. minmax(0,*) rather than
   align-content:center so a frame too small for the cluster collapses
   both skies to zero and overflows the way the demotion pass has always
   measured it. The horizon, its ground layer and the star stop-line are
   retired with the anchor (their rules below are deleted, not dimmed) —
   the console's ground is its own card now, 70-dock.css. */
/* THE CLUSTER'S ROW ORDER IS THE OWNER'S (2026-08-27): history — track
   region — mode rail — console, still one block between the two sky rows.
   The rail's row ("move the under, over selector to beneath the game
   above the betting UI") is the BOARD's, never the console card's: the
   rendered in-dock variant cost the chip lane 25px at 380x300 and
   layout() deleted the WIN bubble — the rail's height re-enters the same
   budget it left, which is what keeps the bubble alive (asserted). */
#board{position:relative;overflow:hidden;width:100%;height:100%;
  display:grid;
  grid-template-rows:minmax(0,1fr) auto auto auto auto minmax(0,1fr);
  grid-template-areas:"." "hist" "game" "modes" "dock" "."}
/* the sky is a FRAME child now (see index.html) — same rule, one level up,
   and z-index:0 keeps it under #stage's own stacking context */
/* the sky is FULL-BLEED again — the horizon it used to stop at is retired
   with the bottom anchor (see #board above): the unit floats centred in
   open sky, stars above and below it, and the console's own card is what
   separates the money surface from the sky now */
.stars{position:absolute;inset:0;
  overflow:hidden;pointer-events:none;opacity:.55;z-index:0}
.stars i{position:absolute;left:0;top:0;display:block;background:transparent}
.stars i::after{content:"";position:absolute;top:2000px;left:0;
  width:inherit;height:inherit;box-shadow:inherit;border-radius:inherit}
.st1{width:2px;height:2px;border-radius:50%;animation:animStar 60s linear infinite}
.st2{width:3.5px;height:3.5px;border-radius:50%;animation:animStar 120s linear infinite}
@keyframes animStar{from{transform:translateY(0)}to{transform:translateY(-2000px)}}

/* ==========================================================================
   THE ROOM CELEBRATES — win round (2026-08-27), W4 merged with the T4 seam
   RULED. Light rises through the tiers and REVERSES at the top:
     tier 1  the room does nothing (Plinko's rung-0 rule — a ~1x arrives on
             half of all rounds and must not move the room it lands in)
     tier 2  the light changes hands, contained: warm crossfades out, the
             gold #roomlit rises at the landing, .66 / scale 1.10
     tier 3  it FLOODS (opacity 1 / 1.30) and the sky answers — #roomsky's
             70 gold twins catch the light
     tier 4  the room goes OUT — W4's flood does not escalate again; W5's
             #roomdim darkness takes the frame and the strike, the lit
             ruler residue (.40/.62) and the WON figure carry it. This is
             the ruling on the preview's known-bad >=50x combo (W4 never
             read data-mag 4; the dim covered a still-red warm layer).
   ---- 1. the warm light: 05-frame.css's old FIRST background layer, on
   its own element so it can CROSSFADE (never stack — the stacked version
   renders as sepia mud; W4's rendered proof). Sits BEFORE .stars in the
   DOM: the frame's background painted under the sky, and the idle/loss
   frame stays pixel-identical to the shipped stacking. */
#roomwarm{position:absolute;inset:0;pointer-events:none;z-index:0;opacity:1;
  background:radial-gradient(62% 46% at var(--cx,50%) var(--cy,44%),
    rgba(192,24,12,.17) 0%, rgba(192,24,12,.055) 44%, rgba(192,24,12,0) 72%);
  transition:opacity .42s linear}
/* ---- 2. the gold light, born at the LANDING. The gradient is painted once
   at the layer's own centre and the LAYER is translated onto --wxpx
   (settle()'s one write per win, off setMk's own formula), so moving the
   emitter is a composited transform, never a full-frame repaint. What the
   shift uncovers is transparent — the physically right answer: a light by
   one wall does not reach past the other. */
#roomlit{position:absolute;inset:0;pointer-events:none;z-index:0;opacity:0;
  transform-origin:50% var(--cy,44%);
  transform:translateX(calc(var(--wxpx,50%) - 50%)) scale(.86);
  background:radial-gradient(54% 40% at 50% var(--cy,44%),
    rgba(243,195,63,.34) 0%, rgba(243,195,63,.10) 30%, rgba(243,195,63,0) 60%);
  transition:opacity .42s linear, transform .42s linear}
/* ---- 3. the sky's own stars catch it: same seed, same LCG, same call
   order as the shipped starfield, every 6th entry — 70 of the sky's 420
   stars, at their own coordinates, in gold (built by the starfield IIFE,
   index.html). */
#roomsky{position:absolute;inset:0;overflow:hidden;pointer-events:none;z-index:0;opacity:0;
  transform-origin:var(--wxpx,50%) var(--cy,44%);transform:scale(.945);
  transition:opacity .42s linear, transform .42s linear}
#roomsky i{position:absolute;left:0;top:0;display:block;background:transparent;
  width:3.5px;height:3.5px;border-radius:50%;
  animation:animStar 60s linear infinite}
#roomsky i::after{content:"";position:absolute;top:2000px;left:0;
  width:inherit;height:inherit;box-shadow:inherit;border-radius:inherit}
/* ---- 4. the darkness — W5's #dim, a SUBTRACTION, over the sky and the
   room layers and never over a control (#stage paints after it). */
#roomdim{position:absolute;inset:0;pointer-events:none;z-index:0;opacity:0;
  background:#05060A;transition:opacity .42s ease-out}
/* ---- 5. the ladder is REACH, and it reverses at the top */
#frame[data-res="win"][data-mag="2"] #roomwarm,
#frame[data-res="win"][data-mag="3"] #roomwarm,
#frame[data-res="win"][data-mag="4"] #roomwarm{opacity:0;
  transition:opacity calc(.24s * var(--celk,1)) linear}
#frame[data-res="win"][data-mag="2"] #roomlit{opacity:.66;
  transform:translateX(calc(var(--wxpx,50%) - 50%)) scale(1.10)}
#frame[data-res="win"][data-mag="3"] #roomlit{opacity:1;
  transform:translateX(calc(var(--wxpx,50%) - 50%)) scale(1.30)}
#frame[data-res="win"][data-mag="2"] #roomlit,
#frame[data-res="win"][data-mag="3"] #roomlit{
  transition:opacity calc(.30s * var(--celk,1)) ease-out,
             transform calc(.44s * var(--celk,1)) cubic-bezier(.2,.72,.3,1)}
#frame[data-res="win"][data-mag="3"] #roomsky{opacity:.85;transform:scale(1);
  transition:opacity calc(.32s * var(--celk,1)) ease-out calc(.10s * var(--celk,1)),
             transform calc(.64s * var(--celk,1)) cubic-bezier(.15,.75,.3,1) calc(.10s * var(--celk,1))}
#frame[data-res="win"][data-mag="4"] #roomdim{opacity:.72;
  transition:opacity calc(.34s * var(--celk,1)) ease-out}
/* ---- 6. the leave is the house's celebration-yield clock */
#frame[data-resfade] #roomwarm{opacity:1;transition:opacity .42s linear}
#frame[data-resfade] #roomlit,
#frame[data-resfade] #roomsky,
#frame[data-resfade] #roomdim{opacity:0;transition:opacity .42s linear}

/* ---- THE GROUND IS DELETED, NOT DIMMED ----------------------------------
   #hzGround (the full-bleed horizon at the frame's foot, its border-top
   stroke, graft B1's catch-light and the full-room warm lift) left with
   the bottom anchor — the owner's centring ruling removes the frame-foot
   line the ground stood on, and its two jobs moved: separating the money
   surface from the sky is the console CARD's (70-dock.css), and the
   machined chamfer went with it onto the card's own top edge. */

/* THE PAD IS SPENT ONCE PER AXIS, which is Mines' rule and it is here for the
   same reason: #stage pads horizontally, #board pads vertically, so the board's
   content box is exactly `w - dockw - 2*pad` by `h - band - pad` and nothing is
   double-counted. 68 = the corner's bottom edge (18+42) plus 8px of air —
   the wf-ui chromeClear rebudget; layout()'s --band comment is the story. */
/* THE BOARD IS ONE CENTRED COLUMN ON ONE MEASURE, and that is a correction
   made from a screenshot rather than from the geometry. The first cut gave the
   well a 1fr row and let the other four rows sit wherever the grid put them:
   every assertion passed - nothing overlapped, nothing overflowed - and the
   picture was four unrelated objects scattered down 780px of black, with the
   mode rail 300px above the track it repaints and the figures pinned to the
   floor. A collision check cannot see that.
   So the play surface is a GROUP: modes, track and figures share one width
   (--playw) and one vertical stack, centred in the board (history rode this
   group too until the owner retired the board strip — see index.html at
   #play). Everything on
   it is measured against the same two verticals, which is the only reason a
   composition reads as designed. */
/* THE PLAY GROUP CENTRES IN THE GAME REGION on one definite measure. The
   place-content:center lesson from earlier in the round still holds (an auto
   track sizes to max-content and a percentage width resolves against it, so
   deleting a readout cell once shrank the rail 190px) - the width is min(playw,
   region minus breathing room), stated, never derived from content. */
#play{position:relative;z-index:1;grid-area:game;place-self:center;
  width:min(var(--playw,760px),100% - 2*var(--bpad,22px));display:grid;
  grid-template-rows:auto auto auto;gap:var(--pgap,20px);
  /* THE FULL-ROOM STRETCH IS DELETED, AND ITS OWN REASONING IS WHY. It
     answered candidate A's quiet upper half at 1280x760 by stretching the
     group and giving the track's row the slack — which at 1280x760 was
     ~1px of drift and at the owner's 2516x1261 monitor was 263px between
     the mode rail and the track it repaints: the "deliberate" anchoring
     scaled with the frame and became three strata in a sky. The ruling
     that replaces it lives on #board above: the group keeps ALL-auto rows
     at every room, so its internal gaps are exactly --pgap and never a
     function of frame height, and the cluster (this group + the console)
     centres as one block between #board's two sky rows. */
  /* --ppadv, not --bpad: at the docked minimum the vertical half of the
     breathing room is the difference between the group fitting its region
     and the result figure poking out through the container top - measured,
     21px over at 380x300 with the dock now inside the card. layout() writes
     0px there (2 until R1 item 5's rung repayment) and bpad everywhere
     else, and budgets with the same number. */
  /* WF-UI: the BOTTOM half of the padding is deleted with the bottom sky
     row — the group's standoff from the horizon is --skygap now (28/18/2,
     layout()-written, counted in the same budget), a stated gap to a
     stated line rather than symmetric breathing room in a centred box. */
  /* margin-bottom is --pgap now, not --skygap: the rail row sits directly
     beneath the track region (the owner's row order) and the sky gap moved
     onto the rail's own margin to the console card below */
  padding:var(--ppadv,22px) 0 0;margin-bottom:var(--pgap,20px);min-height:0}
/* (no #well rule needed: 40-track.css's align-self:center on it is inert in
   an auto row — kept there because it is the track's own file's business) */

/* THE LAST-ROLLS ROW — the cluster's top line, on the play group's own
   measure (the formula copied, not paraphrased — the one-measure rule).
   THE LANE IS RESERVED FROM BOOT: min-height holds the pill row's box
   while it is empty, so the first settle cannot shift the centred cluster
   — the layout half of the entrance defect that got the old strip removed
   ("random writting tab appearing"; the pill half is the hin entrance in
   30-history.css). Empty it PAINTS nothing: no background, no caption, no
   furniture — the reserved sky is indistinguishable from the sky around
   it, which is what the no-orphaned-glyph rule asks of an empty record. */
/* FLUSH RIGHT, LIKE THE PRECEDENT IT CITES — casino-manager, this round:
   the first cut centred the row, so for the session's first six rolls the
   newest pill was a moving target while the row grew symmetric. BirdCrash's
   ticker (games/BirdCrash/index.html: .tick__track{justify-content:
   flex-end}) pins the line to the right so the newest pill's position
   never moves — the glance a multi-tabling player actually makes. With
   overflow:hidden this also clips the OLDEST end first if the row ever
   outgrows the measure, which is the correct end to lose. */
#hist{position:relative;z-index:1;grid-area:hist;justify-self:center;
  width:min(var(--playw,760px),100% - 2*var(--bpad,22px));
  min-height:calc(24px*var(--scc,1));margin-bottom:var(--pgap,20px);
  display:flex;justify-content:flex-end;align-items:center;
  gap:calc(6px*var(--scc,1));min-width:0;overflow:hidden}

/* THE MODE RAIL'S ROW — beneath the game, above the console card (the
   owner's placement), on the same measure as everything else in the
   cluster. The control itself is 80-auto.css's, untouched; this is only
   its position in the spine. */
#modes{grid-area:modes;position:relative;z-index:1;justify-self:center;
  width:min(var(--playw,760px),100% - 2*var(--bpad,22px));
  margin-bottom:var(--skygap,28px)}

/* ==========================================================================
   PORTRAIT — THE WATERLINE (mobile round, 2026-08-27; layout()'s data-port
   is the gate, its own comment the reasoning). One line draws it: #board's
   BOTTOM sky row goes to 0, so every spare pixel is sky ABOVE the game and
   there is none under the console. Below the line the finger works and
   nothing is under 44px; above it the eye works, and the only touchable
   things are the two figures that price the bet, which cross the line on
   purpose. */
#frame[data-port] #board{grid-template-rows:minmax(0,1fr) auto auto auto auto 0}
/* THE SLACK GETS A SECOND SEAT, INSIDE THE CLUSTER'S OWN JOINT (mobile
   polish, 2026-08-28 — the owner, from a live phone: "We could use the
   space better"). The waterline put ALL slack above the game — right
   against dead-ground-under-ROLL, but the baseline render showed the cost:
   129-172px of empty sky over a cluster whose mode rail sat 12px off the
   console card while the top third held nothing. The phone rung now splits
   the slack 1 : 2 across the sky and the rail->console joint (portrait
   round 2026-09-02: 1 : .25 beached the block ~45px low; the approved
   mock's 1 : .6 was still "a bit" low on the owner's phone, so the joint
   now takes two thirds — a 35px sky over the history row at 390x844
   against a 71px joint under the rail, which is the rail reading as the
   instrument's foot rather than the console's lid) — the game
   block (history, track, rail) floats up toward the room's centre, the
   console stays the thumb bar, and the WATERLINE ITSELF HOLDS: the last
   row is still 0, nothing sits under the CTA, and §27a's phone leg is
   green verbatim. The tablet's .6fr split below is the precedent grammar
   (centred-low, thumb zone kept); this is its phone-scaled cousin at the
   OTHER joint. [data-narrow] scopes it to phones: the 7-row template must
   not reach the tablet rule, whose 6 row values would put the dock in the
   joint's own row. Short phones have no slack — both fr rows collapse to
   0 and the frame is byte-identical. layout()'s budget is untouched: the
   fr rows only distribute what the solve already left over. */
#frame[data-port][data-narrow] #board{
  grid-template-rows:minmax(0,.5fr) auto auto auto minmax(0,1fr) auto 0;
  grid-template-areas:"." "hist" "game" "modes" "." "dock" "."}
/* THE TABLET EXCEPTION — the rendered B variant (768x1024): at a tablet's
   width the all-sky-above pose beached the cluster at the foot of a huge
   frame; a .6fr bottom row splits the slack 62/38 and the cluster reads
   centred-low, thumb zone kept. :not([data-narrow]) is layout()'s own
   w>=560 line — every portrait PHONE is narrow and keeps the waterline. */
#frame[data-port]:not([data-narrow]) #board{grid-template-rows:minmax(0,1fr) auto auto auto auto minmax(0,.6fr)}
/* THE SAFE AREA IS CONSUMED HERE (mobile round): viewport-fit=cover has
   been declared since the first build and nothing ever spent the insets.
   env() is 0 on every notchless frame, so this computes to the base rule
   exactly there; layoutPass subtracts the same four numbers from the
   budget (safeInsets), so the pair cannot disagree about where the board
   ends. */
#frame[data-port] #stage{
  padding:calc(var(--band,62px) + env(safe-area-inset-top,0px))
          calc(var(--pad,22px) + env(safe-area-inset-right,0px))
          calc(var(--pad,22px) + env(safe-area-inset-bottom,0px))
          calc(var(--pad,22px) + env(safe-area-inset-left,0px))}


/* ---- THE LAST-ROLLS ROW (collar round, owner 2026-08-27: "add last
   numbers to the top like most dice games") ------------------------------
   The row's RETURN ruling, its entrance fix and its feed are documented at
   #hist in index.html and paintHist() in game.js; this file is only the
   pills' own material. The number carries the verdict's colour and the
   pill stays quiet — dice-build-spec §5's rule ("colour on the number
   only, win/loss") relaxed by exactly one step: the win pill takes a low
   gold wash so a glance reads the row's shape without reading digits,
   the way the reference operators' rows do. Losses are the ledger grey
   (--loss, the TEXT ink — never the board's ember, which belongs inside
   the goo filter and nowhere else). Newest is LAST, flush right; the
   entrance is the hin pop the door pills already use, on the newest pill
   only (data-new — paintHist marks exactly one per settle). */
/* THE ROW'S OWN LABEL (owner, 2026-08-27: "add something for last rounds at
   the top (small text)"). The house caption face — the WIN CHANCE caption's
   exact recipe (50-readouts .orwCap) — reading LAST ROUNDS, the clock door's
   own sheet title: one name for one concept. LABEL AND PILLS ARE ONE
   RIGHT-ANCHORED LOCKUP (review, 2026-08-27): the first cut pinned the
   label to the measure's LEFT edge with margin-right:auto, which made the
   gap between name and named a function of the ROW'S WIDTH — 560px of sky
   at the 720x405 spawn with one roll, an orphan naming nothing in exactly
   the state a fresh window is in most. The label now sits immediately
   left of the leading (oldest) pill — the row's flex gap plus the 6px
   here, ~12px total — travelling inward as pills accumulate, so at one
   pill the name is right there next to it and the newest pill's flush-
   right glance target never moves (§25f asserts the gap with a floor AND
   a ceiling, at 1 and 3 pills, at both spawn frames).
   IT IS WRITTEN BY paintHist() WITH THE PILLS AND ONLY WITH THEM — no
   pills, no label, no node (the no-orphaned-glyph rule; the orphan
   state-walk in check-dice asserts it). */
.hlab{margin-right:calc(6px*var(--scc,1));
  font:600 calc(9px*var(--scc,1))/1 var(--ui);
  letter-spacing:.14em;text-transform:uppercase;color:#666C7E;
  white-space:nowrap}
.hp{flex:none;display:inline-flex;align-items:center;justify-content:center;
  min-width:calc(52px*var(--scc,1));height:calc(24px*var(--scc,1));
  padding:0 calc(11px*var(--scc,1));border-radius:999px;
  background:rgba(255,255,255,.05);
  font:700 calc(12px*var(--scc,1))/1 var(--fig);font-variant-numeric:tabular-nums;
  color:var(--loss)}
.hp[data-w="1"]{background:rgba(243,195,63,.13);color:var(--win)}
/* WIN ROUND (W5): the wash reads the win's TIER — paintHist stamps
   data-mag off the same EDGE_NUM/cU pricing settle() uses, so the row's
   shape says not just where the wins are but how big they were. T1 and T2
   keep the base .13 (an ordinary win is an ordinary pill); T3 steps to
   .20; T4 to .30 with a gold hairline — the one win a glance should snag
   on. The ink stays var(--win) throughout: the ladder lives in the
   GROUND, the number never changes voice. */
.hp[data-mag="3"]{background:rgba(243,195,63,.20)}
.hp[data-mag="4"]{background:rgba(243,195,63,.30);
  box-shadow:inset 0 0 0 1px rgba(243,195,63,.35)}
.hp[data-new]{animation:hin .3s cubic-bezier(.2,1.4,.4,1)}

/* THE SMALL-FRAME ANSWER IS COMPACTION, NOT SHEDDING — decided on renders
   (work/dice/shots/collar-land): at 380x300 the compact row costs the
   budget ~17px and the figure keeps its floor, while shedding it would
   make the docked window — the frame a multi-tabling player actually sits
   at — the one size without the record the owner just ruled back. The
   pills step down with the chrome's own density flag. */
#frame[data-tiny] .hp{min-width:38px;height:17px;padding:0 8px;font-size:10px}
#frame[data-tiny] #hist{min-height:17px;gap:4px}
/* AT data-narrow THE LABEL SHEDS — the WIDTH rung, and only that one.
   Found rendering the lockup fix at 380x300 with 7 pills: the compact
   pills alone are ~314px in a 340px measure, so ANY label — the
   left-pinned first cut included, which was already clipped to a "DS"
   remnant at this state — overflows the row's left edge and is cut to
   garbage lettering by the overflow:hidden clip. Nothing may render as
   a fragment: the record keeps every pill unclipped, and the name waits
   for a frame with room to say it (§25f-2's 380x300 leg asserts both).
   The gate is data-narrow (w<560), NOT data-room=min or data-tiny —
   both of those are set at the 720x405 DEFAULT SPAWN, where the lockup
   fits with 278px to spare; game.js's own scar at the flag split says
   it: "720x405 is short but not narrow, and answering it as narrow is
   what deleted ... the history strip from the default window". It is
   also 70-dock's own precedent: "what sheds at data-narrow is the
   caption, never a key" (.hzCap) — and this is a caption; every
   not-narrow frame holds the full 7-pill lockup (>=402px in a >=516px
   measure), while the narrow rung mostly cannot. */
#frame[data-narrow] .hlab{display:none}

/* PORTRAIT (mobile round): the strip is READ on a phone — it is the only
   record on the board — so it steps back UP from the tiny rung's 17px
   pills (portrait phones are all data-tiny by width) to a legible 26px row.
   Same specificity as the tiny rung, later in the file: wins by order.
   THE COUNT PAYS FOR THIS SIZE (casino-manager fix round, 2026-08-27):
   seven 52px pills never fit a phone measure, and the flat 7 cap left the
   overflow clip painting a sliver of the second-oldest pill at every port
   frame from 6 rolls on. paintHist() now fits whole pills to the row's
   real width (oldest evicted whole, newest kept, the label yielding
   first) — this file keeps only the pills' material, never their count. */
#frame[data-port] #hist{min-height:26px;gap:6px}
#frame[data-port] .hp{min-width:52px;height:26px;padding:0 11px;font-size:13px}


/* ---- the well: the result, the rail, the ticks -------------------------- */
/* THE HERO IS THE WIDEST OBJECT, NOT THE NARROWEST. --wellpad padded the WELL,
   so the rail came out 638px inside a 760px group while #hist and #stats stayed
   at 760 — the readout cards overhung the track by 61px on each side and the
   subordinate row framed the hero. The padding is gone; the rail spans the
   group, and the four objects finally share the two verticals the comment
   claimed they already did.
   It also fixes the collision: the chip's half-width at "99.99" is num*1.25
   (94.5px at 76px type) against 61px of padding, so the result overlapped the
   bet panel by 11.4px at 1024x720 and sat on its rounded corner about once a
   minute. The chip now overhangs into the BOARD's own margin and is clamped to
   it — see --chipmin/--chipmax, solved in layout(). */
/* --bs/--r LIVE HERE, NOT ON #tk — task (r2) 6: #ticks (the 0/100 end labels)
   is #tk's SIBLING, not its descendant, and a custom property set on #tk
   is invisible to a sibling regardless of how it's nested elsewhere. Moved up
   one level, to the ancestor both share, #ticks can position itself off the
   exact same var(--r) inset the dots/blob/grip already use, so the labels
   sit on the 0 and 100 dots rather than on a second, independently-guessed
   number. #tk keeps reading --bs/--r as normal inheritance — nothing that
   already used them changes. */
/* --brs is the BEAD's diameter (wf-anim F5): .72 of the blob, so the marker
   reads as matter thrown from the target, not as a second target. It rides
   --bs, so everything that shrinks the blob shrinks the bead with it —
   and DELIBERATELY larger than a rope bead (--bead, bs*.56), so the roll
   marker outranks the tens it passes (VERDICT collision 4).
   ROPE ROUND: --bs/--rope/--bead/--tick are all written by layout()'s one
   sizing solver (track width in, no data-room read); the values here are
   the designed-measure (W=760) solve, present only so the first paint
   before layout() has real numbers. */
#well{--bs:57px;--rope:19.4px;--bead:31.9px;--tick:6.3px;
  --r:var(--bs);--brs:calc(var(--bs) * .72);position:relative;align-self:center;min-width:0}

/* THE RESULT RIDES THE MARKER. dice-research-conclusions §8: every marker in
   the filmed set travels for 200-750ms and every one starts from where the
   last result stopped — "nobody teleports a result onto a track". Making the
   NUMBER the travelling object rather than a field beside the track is the
   cheapest way to honour that: the sequence of independent rolls becomes one
   continuous object, and there is never a frame in which nothing has a
   position. The chip overhangs the rail at 0 and 100, which is what
   --wellpad buys. */
/* --lanex, NOT a constant: the lane must hold the chip's SETTLED face (the
   figure plus the WIN sub-line growing upward off the bottom anchor), and
   layout() is the one solver that knows that sum — see the --lanex block in
   layoutPass(). The old +14 held only the tick and let a win paint the
   figure into the mode rail at every room below full. */
/* WIN ROUND: z-index 4 — THE RING PASSES BEHIND THE MONEY. #chiplane and
   #tk are siblings with no z-index, so the track painted over the chip and
   the strike ring drew a stroke straight through the payout figure
   (measured overshoot of the ring's top over the WON line's baseline:
   2.2px at 1280x760, 43.3px at 720x405). 4, NOT 1: #tk has z-index:auto
   and creates no stacking context, so .cel's own z-index:2 competes with
   #chiplane directly in the shared parent context — a 1 loses to it and
   the ring still draws over the payout (rendered at 720x405 and caught).
   4 clears the grips' 3 as well. One declaration turns a collision into a
   composition. */
#chiplane{position:relative;z-index:4;height:calc(var(--num) + var(--lanex,14px));margin-bottom:6px;overflow:visible}
/* TRANSFORM, NEVER `left`. Both of these move sixty times a second during a
   roll and `left` is a LAYOUT property: every frame re-ran layout and paint for
   the whole board over a canvas game, and `will-change:left` promotes nothing
   because there is nothing to promote. --mkpx is the marker in pixels from the
   rail's left edge, and the chip's is clamped so it can overhang the rail
   without leaving the board. */
#chip{
  position:absolute;bottom:0;left:0;
  transform:translateX(calc(clamp(var(--chipmin,0px), var(--mkpx,0px), var(--chipmax,0px)) - 50%));
  display:grid;justify-items:center;gap:2px;white-space:nowrap;will-change:transform;
}
#chipv{font-family:var(--fig);font-weight:700;font-size:var(--num);line-height:1;
  letter-spacing:-.02em;font-variant-numeric:tabular-nums;color:var(--ink);
  /* Hacksaw's 240ms bleed, and it is the whole loss frame. ONLY the colour is
     transitioned now: a 34px text-shadow interpolated over 240ms, thirty times
     a minute, on five glyphs at 76px, over a canvas game, is a per-frame large
     blur, and the house motion budget's hard limit is ONE STATIC shadow. The
     glow now arrives with the colour instead of being animated into place, and
     16px is enough to separate the figure from the board. */
  transition:color .24s linear}
/* TASK 7 fix-round, overturning task 4's ruling: was --acc. Task 4 repointed
   the win figure, the history pips and the winning band to --acc because
   nothing on the board used --win yet, so "the accent marks a win" was
   defensible. Task 7 then wired the blob itself to --win (dead CSS until
   then), which made a settled win read red on the number and green on the
   blob at the same time — two different colours claiming the same verdict.
   The dock was never part of that mistake: PROFIT ON WIN, the balance
   delta and the streak were --win all along. The stage now agrees with the
   dock. Red goes back to being the accent, idle and brand colour only. The
   shadow's rgba mirrors --win (243,195,63 — Mines' gold since task 9) the
   same way the old one
   mirrored --cp-red: a custom property carries no alpha of its own into an
   rgba() component list without color-mix. */
#chip[data-r="win"] #chipv{color:var(--win);text-shadow:0 0 16px rgba(243,195,63,.5)}
#chip[data-r="lose"] #chipv{color:var(--loss);text-shadow:none}
/* BEFORE THE FIRST ROLL. Three placeholders were tried and photographed: a
   full-size em dash reads as a stray 30px bar hanging over the track; "--.--"
   at 76px is four bars and a fat round dot; a half-size dash is still a bar,
   only smaller, and the review named it again. A short caption at chrome size
   is the one that reads as an EMPTY STATE rather than as a broken figure, it
   occupies the lane the result will arrive in, and it cannot be mistaken for a
   roll of 0.00. It is gone for good after the first roll of the session. */
#chip[data-r="none"] #chipv{color:#4A5062;font-family:var(--ui);font-weight:600;
  font-size:clamp(10px, calc(var(--num) * .17), 13px);letter-spacing:.2em;
  text-transform:uppercase;text-shadow:none}
/* ITEM 1 (casino-manager CRITICAL): THE CAPTION AND THE BUBBLE SHARE ONE
   LANE, AND layout() IS THE ONE WRITER OF ITS SPLIT. The idle caption
   (this rule, bottom of #chiplane) and the .sp price bubble (50-readouts,
   anchored off #tk's own height) each solved their vertical position
   alone, and as --bs/--num/#tk shrank they compressed into each other —
   at 720x405 (the shell's real popup, js/shell.js:556) the bubble sat ON
   the caption's own line. layout() now solves the pair: --splift lifts
   the caption to sit just clear of the bubble's measured top (rock sweep
   included), and where even that stack cannot fit the chip lane —
   380x300, ~59px needed against a ~24px lane — data-spyield makes the
   BUBBLE yield the lane to the caption instead (the reviewer's own
   second option). The yield keys on the caption actually having text:
   post-decay the chip's data-r="none" is EMPTY (armResDecay clears
   #chipv), so the bubble returns to its idle pricing job the moment the
   caption has nothing to say. The tick (#chipn) needs no rule here any
   more: it is verdict-gated at its own block below — data-r="none" (any
   caption state) never shows it. */
#well:has(#chip[data-r="none"] #chipv:not(:empty)) #chip{bottom:var(--splift,0px)}
/* PORTRAIT: THE IDLE CAPTION CENTRES IN THE FIGURE'S LANE (portrait round,
   2026-09-02). The lane reserves the settled figure's full height so the
   first roll moves nothing (§25's own no-pop-in rule), and at a phone's
   104px cap that left ~90px of empty lane ABOVE a caption hugging the
   track. Same clamp translateX as #chip's base rule — the transform is
   restated whole because translateY(-50%) has to join it, never replace
   it. The --splift lift is not needed here: the bubble sits under the
   lane's foot and the caption now sits at its middle. */
#frame[data-port] #well:has(#chip[data-r="none"] #chipv:not(:empty)) #chip{bottom:auto;top:50%;
  transform:translate(calc(clamp(var(--chipmin,0px), var(--mkpx,0px), var(--chipmax,0px)) - 50%), -50%)}
#frame[data-spyield] #well:has(#chip[data-r="none"] #chipv:not(:empty)) .sp{opacity:0;animation:none}
/* TASK 9: THE SETTLED RESULT DECAYS. The owner: "roll of last round should
   disappear after". settle() arms a hold timer (readable, ~2.8s, 1.2s in
   turbo) and then this attribute carries the ~.4s fade; the JS half clears
   the verdict once the fade lands. Only the figure and the bead fade -
   the history pill is the record and it stays. (wf-anim F2: the bead took
   the needle's slot in this pair when the needle was deleted.) */
#frame[data-resfade] #chip,
#frame[data-resfade] #bead{opacity:0;transition:opacity .42s linear}

/* the win line: hidden unless the chip is wearing a win. Sized off --num so
   it scales with the figure it captions; kept under the figure, above the
   chip's tick, so the unit reads figure -> amount -> pointer. */
#chipw{display:none;font:700 clamp(10px, calc(var(--num) * .22), 15px)/1 var(--ui);
  letter-spacing:.08em;color:var(--win)}
#chip[data-r="win"] #chipw:not(:empty){display:block}
/* THE TICK POINTS AT THE TRUE PIXEL (fix round M2b). The chip's translate
   is CLAMPED (--chipmin/--chipmax), so at the rail ends the figure drifts
   off the pixel it names by up to 7.7 track units at 380/720 — which is
   the precision gap this tick (kept by verdict graft 11) exists to close,
   and it was riding the clamped chip. The counter-translate re-derives
   the clamp's error — --mkpx minus the clamped position — so the tick
   sits on the landing pixel exactly while the figure parks; where the
   clamp is slack the correction is zero and nothing moves. Same
   fallbacks as the chip's own clamp: pre-layout they resolve wide open
   and the correction is 0. */
#chipn{display:none;width:2px;height:calc(9px*var(--sc,1));border-radius:1px;background:var(--off);
  transform:translateX(calc(var(--mkpx,0px) -
    clamp(var(--chipmin,-9999px), var(--mkpx,0px), var(--chipmax,9999px))));
  transition:background .24s linear}
/* THE TICK EXISTS ONLY WHILE THERE IS A VERDICT TO POINT AT (owner, goo r7:
   the post-decay "quiet echo" rendered as a lone bar floating in the sky —
   armResDecay empties the caption, which un-suppressed the old :has gate,
   and the counter-translate parked the orphan on a stale --mkpx). display,
   not visibility: gated on the chip's own win/lose, the same lifetime as
   the landing mark, so idle, rolling (data-r=""), post-decay (data-r="none")
   and a post-settle mode/bet change (data-r cleared) all draw NO tick. */
/* WIN ROUND (W1's dropTick, WIN-SCOPED): on a win the tick is retired —
   the landing already keeps two marks (#wigland at 1.34x and the strike
   burst's own anchor), and the preview every final W1 capture ran with
   drops it. The LOSS KEEPS ITS TICK: on a loss nothing else points at the
   landing pixel when the chip's clamp bites, and the merge's own gate is
   a loss frame byte-comparable to shipped — so the win half of the old
   display rule is deleted and the lose half stands untouched. */
#chip[data-r="lose"] #chipn{display:block}
#chip[data-r="win"] #chipn{background:var(--win)}
#chip[data-r="lose"] #chipn{background:var(--loss)}
#frame[data-rolling]:not([data-res]) #chipv{color:var(--ink);text-shadow:none;transition:none}
/* v1's landing beat, ported (wf-anim §2.1): the figure arrives with nland's
   .85 → 1.05 → 1 spring on the strike frame — on #chipv, the INNER element,
   never #chip, whose transform is the clamp translateX and must not be
   animated. data-r is rewritten '' mid-roll and win/lose on settle, so the
   animation re-fires per round with no JS. The `scale` property, not
   transform: it composes under the grid placement and cannot fight the
   clamp. Both verdicts land the same way — the loss stays restrained in
   colour, not in physics. */
#chip[data-r="win"] #chipv,
#chip[data-r="lose"] #chipv{animation:nland .4s cubic-bezier(.2,1.5,.4,1)}
@keyframes nland{0%{scale:.85}55%{scale:1.05}100%{scale:1}}
/* WIN ROUND (W1): THE FIGURE LANDS GOLD. Colour 240 -> 100ms, win only —
   on a win the number IS the verdict and arrives with its spring, not a
   quarter second behind it; the loss keeps the base .24s bleed (that bleed
   is the loss frame's whole idea). And the win spring out-punches the
   loss's by a hair: 6% more overshoot, 60ms shorter, finished before the
   WON line lands. */
#chip[data-r="win"] #chipv{
  transition:color .10s linear;
  animation:nlandwin .34s cubic-bezier(.18,1.62,.36,1)}
@keyframes nlandwin{0%{scale:.82}46%{scale:1.075}100%{scale:1}}

/* THE LOSING HALF HAS TO BE VISIBLE. The rail was #181B22, which measures
   1.15:1 against this board — the control whose entire job is saying which
   numbers pay was drawing half the sentence in a colour you cannot see, while
   the winning half sat at 10.55:1.
   #565B6E is solved for, not picked: relative luminance 0.1082 against the
   ground's 0.00318 gives (0.1082+0.05)/(0.00318+0.05) = 2.97:1. The graphite
   the review suggested, #3A3F4D, computes to 0.0506 and reaches only
   1.89:1 — it would have left the finding half-fixed, so the ratio is honoured
   rather than the hex. No hue is added: R<G<B by a hair, which is the same
   neutral the panel family already uses. */
/* ---- the goo track ------------------------------------------------------
   THE BLOB IS POSITIONED IN THE TRACK'S OWN COORDINATES, not in percent of
   the track: at 0 and at 100 a percent-positioned blob hangs half its
   width off each end. The `--r` inset reserves the blob's radius at both
   ends so 0 and 100 are reachable and still centred on their dots.

   ROPE ROUND (owner-ratified 2026-08-26): #tk's height is --tkh, layout()'s
   TKMIN + slack budget — the box floors at the 24px the docked minimum was
   solved at and absorbs whatever the region has left after the figure is
   paid, capped at the designed 110 (scaled). The three data-room height
   rungs that stood here (110/70/24) are absorbed by that budget: the rungs
   answered "how tall may the box be" with three constants, and the budget
   answers it with the region's own arithmetic — see layoutPass()'s TKMIN
   note for the no-overflow proof. touch-action/cursor carry over: the drag
   listener is still on this element. */
#tk{--p:50;position:relative;height:var(--tkh,calc(110px*var(--sc,1)));touch-action:none;cursor:pointer}

/* THE ROPE. #bandA/#bandB moved INSIDE #dots (index.html) and are the
   liquid BODY: one connected rope whose length IS the paying region, ending
   in the drops the player drags. Painted before the drops (DOM order),
   under them (z-index 0 against the blob's 1), full accent — the 12% wash
   this element used to be is DELETED, and with it the owner's 2026-08-25
   Outside/Between tint ruling that wash implemented: superseded because the
   rope delivers what the tint was for (the paying region reads as a filled
   area), not because anyone disagreed with it. Geometry is paint()'s —
   calc() strings on the drops' own inset scale, reading the same --p/--p2
   the spring writes, so the rope's draggable end and its drop are one
   number. Every mode shows its rope now; only #bandB keeps [hidden]
   (Outside's second segment, toggled by paint() exactly as before).
   COLOUR LAW (VERDICT collision 2): everything inside the #dots filter
   carries ONE tone per state — this rope, the beads, the drops and the
   roll marker recolour together or not at all, because the filter merges
   shape, never colour; any subset recolour paints a hard two-tone seam
   through the merged body (the "red crescent" defect, three harnesses,
   one cause). The win/lose readers further down are the law's whole
   implementation. */
/* THE ANCHOR FIX (win round, W2's — prerequisite for everything that
   scales in the filter): centring moves off `transform` onto the
   INDEPENDENT `translate` property for every filter-group member. The
   individual properties compose in the fixed order translate → rotate →
   scale → transform, so a `scale` arriving from anywhere (strikeDot's
   element.animate kick, the bead's squash) multiplies AFTER the centring
   instead of scaling the -50% offset itself — which is exactly the
   measured 11.1px strike-drift: the struck dot's centre slid mid-kick
   because its own half-size translation was riding the 1.55x scale. */
.band{position:absolute;top:50%;height:var(--rope,calc(var(--bs) * .34));
  translate:0 -50%;border-radius:999px;pointer-events:none;z-index:0;
  background:var(--acc);
  /* goo r7 LOCKSTEP (see .dot): one background clock for everything the
     filter can merge — now scaled by --bankk so turbo's ember bank
     completes inside its own 210ms gap (EMBER LAW; game.js setTurbo/boot
     are the two writers of --bankk). */
  transition:background calc(.35s * var(--bankk,1)) linear}
.band[hidden]{display:none}
/* TASK (r2) 6 / rope round: THE 0/100 END LABELS' ROW IS ABSORBED. The
   spans are positioned off the SAME var(--r) inset the dots/blob/grip use,
   so "0" sits under the v0 tick and "100" under the v100 tick at every
   breakpoint — but the ROW is height:0 everywhere now (the rope round's
   deletion 2, which data-room="min" shipped early): the two figures draw
   just under the end beads, inside the track box's own lower band, and
   the collapsed row costs the budget nothing — layout() no longer
   measures this row at all (fix round M3): it SOLVES the band-mode lane
   itself as --tlane and spends exactly that; outside the band modes the
   lane is zero by construction. The lift is
   solved from the box's own tokens (--tkh/--bead), one expression at every
   room. EXCEPT the band modes: there the row keeps a real height because
   it is the lane item 7's From/To ball values land in — the honest
   reconciliation of the two claims on this region is that the space is
   PAID FOR where the values need it and reclaimed where they don't, and
   the end figures dim to a whisper under them (a band edge dragged to the
   rail's end layers over that whisper, not over a second number). */
#ticks{position:relative;height:0;margin-top:0;overflow:visible;pointer-events:none}
/* COLLAR ROUND: anchored to the TRACK's own geometry (top of the lane box,
   which is #tk's bottom edge), not to the lane's bottom — the old
   bottom:calc(tkh/2 - bead/2 - 13px) offset was measured from a box whose
   height (--tlane) the budget grows, so every pixel the lane gained pushed
   the labels a pixel DEEPER, and with the mode rail now directly below the
   play group the "100" printed onto the rail's cap at 720x405 (rendered,
   pass 1). Anchored to the geometry they caption — just under the end
   beads — they stay put whatever the lane does, and the lane solve in
   layout() bounds them like every other hanger. */
#ticks span{position:absolute;bottom:auto;
  top:calc(var(--bead,32px)/2 + 4px*var(--sc,1) - var(--tkh,110px)/2);
  font:600 calc(8.5px*var(--sc,1))/1 var(--ui);color:#4A5062;
  transform:translateX(-50%);
  /* paint() fades an end label to 0 while a drop is parked on it (VERDICT
     collision 3, third condition) - inline opacity, one writer; this is
     just the fade's clock */
  transition:opacity .2s}
#ticks span:first-child{left:calc(var(--r)/2)}
#ticks span:last-child{left:calc(100% - var(--r)/2)}
/* ITEM 7's lane: in band modes the scale row is where the ball values land
   (short/min rooms — at the full room they fit inside #tk's own box), so
   it holds its height — --tlane, solved and spent by layout() itself
   (fix round M3; the rowH('ticks') read-back this sentence used to cite
   is deleted), which is what "create more space" pays with. The end
   figures dim under them. */
/* --tlane (fix round M3): layout() is the lane's one writer now — the
   designed constants (16*sc, 14 at min) are its FLOOR, and where the
   solved track box leaves the .hv values overhanging further (the min
   rooms: 28-39px boxes under 51-57px drops) the lane grows to hold them,
   paid out of the same budget that used to let them print into the
   readout cards (+5.5px at 720x405, +3.0 at 900x430). The fallbacks are
   the old constants, for the first pre-layout paint. */
/* COLLAR ROUND: THE LANE IS PERMANENT IN EVERY MODE — the chance satellite
   hangs under the drop at every frame now, not only the band modes' ball
   values, so the band-mode gate on this height is gone and layout()'s
   budget spends --tlane unconditionally (the deleted readout row is what
   funds it). The fallbacks are the designed constants, for the first
   pre-layout paint. */
#ticks{height:var(--tlane,calc(16px*var(--sc,1)))}
#well:has(#gripB:not([hidden])) #ticks span{opacity:.45}
#frame[data-room="min"] #ticks{height:var(--tlane,14px);margin-top:0}
/* R1 item 5: the chip lane's margin steps down with the room — 2, not 6, at
   the docked minimum. layout()'s spent budget carries the same pair
   (laneMargin), and the two must move together or the budget lies by 4px.
   Part of the ~12px repayment that lets Between/Outside's second readout
   row fit the 380x300 budget without touching any threshold. */
#frame[data-room="min"] #chiplane{margin-bottom:2px}
/* PORTRAIT (mobile round): the 0/100 END LABELS ARE DELETED at data-port —
   they caption the ends of a scale, and on a 342px rail the ends are the
   frame's own edges. They cost: at a 2% chance the "2.00 % / WIN CHANCE"
   satellite sat square on the "0" (rendered — a SHIPPED defect at every
   frame including 1280x760; paint()'s occ graft is the desktop half), and
   at the default 48.50 the multiplier's underline abutted the "100" (v2
   render). Moving them above the rope was tried and rejected on the maths:
   the price bubble already lives there and lands on them at the extremes.
   Nothing is lost — the dry ruler (#dots2's 21 ticks) is the object that
   stops the rope reading as a filled bar, and it is untouched; the exact
   position is named to 2dp by the satellite six pixels below. layout()'s
   lane solve zeroes the end-label term at port to match — the pair moves
   together or the lane reserves pixels nothing draws. */
#frame[data-port] #ticks span{display:none}
/* the CSS half of layout()'s portrait laneMargin (30) — 40-track's own
   min-room rung above is margin-bottom:2px, and the pair MUST move
   together or the budget reserves 30px that nothing spends (measured: the
   settled figure still overlapped the bubble by 12.1px with only the JS
   half changed). Portrait phones are room="min" by width, so this rule
   must sit after that rung and win by order. */
#frame[data-port] #chiplane{margin-bottom:30px}

.goo,.dots2{position:absolute;inset:0;pointer-events:none}
.goo{filter:url(#goo)}
/* THE RULER PAINTS OVER THE LIQUID (fix round D1). #dots2 precedes #dots in
   the DOM, so the rope used to bury every tick it covered — at a 95% chance
   ZERO ticks survived and "liquid with a ruler beside it" collapsed back to
   a filled bar. z-index lifts the whole unfiltered ruler above the goo
   (the filter is a stacking context, so nothing inside it can interleave):
   rendered over red, gold and the ember, the .13/.26 white ticks read as a
   scale etched over the liquid and never as a seam — the ruler is
   unfiltered and never takes a verdict tone, so there is no colour for the
   law to object to. The masked-to-the-dry-region alternative was not
   needed. */
.dots2{z-index:1}
/* THE IGNITION LAYER (fix round M1): the win's outward wave, moved OUT of
   the filter. It used to be a transition-delay stagger on the filtered
   dots — a subset recolour, i.e. the exact defect the colour law names:
   hard red discs punched through gold for ~600ms of every win, nearly the
   whole round in turbo. The wave is now eleven unfiltered glow twins
   (built beside the dots, same inset positions): a staggered gold flash
   travelling outward from the landing (settle() writes --wd and mirrors
   .off) while the filtered mass banks to gold below on its one uniform
   lockstep clock. Above the ruler (2), below the grips (3). --celk
   compresses the wave in turbo exactly as it does the celebration. */
.wig{position:absolute;inset:0;pointer-events:none;z-index:2}
.wig i{position:absolute;opacity:0}
/* WIN ROUND (W5): THE IGNITION LEAVES THE PAYING SIDE AND CROSSES THE
   DEAD ZONE. The old wave flashed the PAYING majors — light laid over a
   region that was already announcing itself in gold, reaching further the
   SMALLER the win (reach = chance). Inverted, the wave departs the paying
   region's edge and lands on the .off twins across the dead side, so
   reach = 1 - chance: a strictly increasing function of the multiplier,
   and the ladder is monotone by construction. The lamps are #wigland's
   own warm white at 1.15x the bead (the final capture's TIGHT sizing) —
   LIGHT over the scale, never payline gold, so the wave can never be
   misread as the paying region growing. settle() writes --wd as
   dist-from-the-nearest-paying-edge x 5.5ms/unit; turbo compresses
   through --celk below (5.5 x .45 = 2.5ms/unit, the rig's own turbo kd). */
.wig i[data-v]{top:50%;width:calc(var(--bead,30px) * 1.15);height:calc(var(--bead,30px) * 1.15);
  border-radius:50%;transform:translate(-50%,-50%);
  background:radial-gradient(closest-side, rgba(255,246,214,.98),
    rgba(255,246,214,.52) 38%, rgba(255,246,214,0) 74%);
  /* the celebration's own yield grammar (celYield freezes an in-flight
     glow and this clock fades it) — a mid-wave autobet strip dissolves,
     never snaps */
  transition:opacity .18s ease-out}
#frame[data-res="win"] .wig i[data-v].off{
  animation:wigf calc(.5s * var(--celk,1)) ease-out both;
  animation-delay:calc(var(--wd,0s) * var(--celk,1))}
@keyframes wigf{
  0%{opacity:0;transform:translate(-50%,-50%) scale(.4)}
  30%{opacity:1}
  100%{opacity:0;transform:translate(-50%,-50%) scale(1.25)}}
/* THE RESIDUE (W5): the dry ticks the wave crossed hold a brighter level —
   --rlev/--rlevt off the tier ladder — for the whole verdict: the still
   half of the wave, and the only rung a reduced-motion player and a 210ms
   turbo strip both keep. The delay rides the same --wd the lamps use, so
   the residue lights in the wave's own footsteps. The ruler still never
   takes a verdict TONE — this is its own white at a higher level, and the
   colour law still stops at the filter's edge. */
.dots2 .tick{transition:opacity calc(.26s * var(--celk,1)) ease-out}
#frame[data-res="win"] .dots2 .tick.dry{opacity:var(--rlev,.13);
  transition-delay:calc(var(--wd,0s) * var(--celk,1))}
#frame[data-res="win"] .dots2 .tick.dry.ten{opacity:var(--rlevt,.26)}
/* THE LANDING KEEPS A MARK (fix round M2a). After a deep win the rope is
   uniform gold and the strike bead has banked with it — only the numeral
   said where the roll hit. This is the persistent half of the ignition:
   one unfiltered white-gold highlight parked on the landing pixel
   (--mkpx, the bead's own number) for as long as the verdict holds. It is
   LIGHT over the liquid, not a colour inside it — the law stops at the
   filter's edge — and it arrives just behind the wave's first flash,
   decays with the chip/bead pair (the resfade rule below), and yields on
   its base clock when a new roll strips the verdict. */
/* WIN ROUND (W1): 1.15 -> 1.34x and .92 -> .96. Measured: on a 9.70%-chance
   win the whole rope is gold and the old mark was invisible in every frame
   after +1.2s — bigger and earlier is the entire fix (the earlier half is
   the .22s/.04s clock below). */
#wigland{left:0;top:50%;width:calc(var(--bead,30px) * 1.34);height:calc(var(--bead,30px) * 1.34);
  border-radius:50%;
  transform:translate(-50%,-50%) translateX(var(--mkpx,0px));
  background:radial-gradient(closest-side, rgba(255,246,214,.95), rgba(255,213,106,.55) 42%, rgba(243,195,63,0) 72%);
  transition:opacity .18s ease-out}
/* --celk-SCALED, like everything else the win clock owns (producer audit,
   fix round): un-scaled, the .1s+.3s arrival could not complete inside
   turbo autobet's ~210ms strip window and the "persistent" mark was a
   ~40%-opacity flicker at cadence — the exact failure mode the EMBER LAW's
   --bankk closed for the bank. At .45 the arrival is 45+135ms, inside the
   window with room to spare. */
#frame[data-res="win"] #wigland{opacity:.96;
  transition:opacity calc(.22s * var(--celk,1)) ease-out calc(.04s * var(--celk,1))}
#frame[data-resfade] #wigland{opacity:0;transition:opacity .42s linear}

/* THE MAJORS ARE BEADS IN THE LIQUID (rope round): sized --bead — solved
   as clamp(rope+9, bs*.56, 34) so a ten always swells the rope by a real
   scallop — and OFF majors are opacity:0, not white: the r2 probe already
   proved a .30-alpha disc inside the goo filter is deleted by the alpha
   threshold, not drawn, so the white was eleven styled elements painting
   nothing (colors lens, measured). The dead zone is the dry ruler's to
   draw. --g, the proximity swell, is deleted from the transform — the
   rope merges for real. */
/* --dk (fix round M5, "the paw"): the bead's size ramps with its own
   region's width — 0 (rope-thin) at ≤1.1 pitches, 1 (full bead) at ≥2.3,
   written by paint() per dot off the FROZEN aim. In the 4-13% band the
   region is one-to-two majors wide and a full terminal scallop there read
   as a second head on a short rope (two-lobed at 10%, an off-round blob at
   5%, symmetric in Over); rope-thin, the shape resolves to drop + tapering
   stub. The .2s width/height clock smooths a typed jump; a drag re-writes
   --dk continuously anyway. */
.dot{position:absolute;top:50%;
     width:calc(var(--rope,19px) + (var(--bead,30px) - var(--rope,19px)) * var(--dk,1));
     height:calc(var(--rope,19px) + (var(--bead,30px) - var(--rope,19px)) * var(--dk,1));
     border-radius:50%;
     /* translate, not transform — the anchor fix (see .band): strikeDot's
        `scale` kick must not scale the centring offset */
     background:var(--acc);translate:-50% -50%;
     /* goo r7 LOCKSTEP: one background clock — .35s LINEAR, zero delay,
        --bankk-scaled — and the SAME values on .blob and .band: the owner
        caught the goo half-gold half-red on the win→roll revert because
        the dots faded while the blob and band snapped. Everything the
        filter can merge moves on one clock; the win-side stagger lives
        ONLY on the [data-res="win"] rule further down, so it applies
        entering the win and never on the exit. */
     transition:opacity .3s,background calc(.35s * var(--bankk,1)) linear,
       width .2s ease,height .2s ease}
.dot.off{opacity:0}
/* THE DRY RULER (rope round): 21 ticks in #dots2, one --tick size, white —
   .13 with the tens at .26. Never off, never filtered, never deleted, and
   NEVER the verdict tone (the colour law stops at the filter's edge): the
   ruler is the constant the verdict is read against — on a loss the line
   falls BELOW it and the dead zone's TENS become the brightest things on
   the board by inversion, not by lighting up (the .26 tens are what the
   ember's <=0.5x bound is solved against; the .13 minors sit near the
   ember's own luminance and invert nothing — the tens carry the read).
   Mid-roll the RULER dims instead
   of the hero going quiet ("not draggable" said by the scale, not by the
   goo — the owner's own complaint, r7). */
.dots2{transition:opacity .2s}
.dots2 .tick{position:absolute;top:50%;width:var(--tick,6px);height:var(--tick,6px);border-radius:50%;
  background:#fff;opacity:.13;transform:translate(-50%,-50%)}
.dots2 .tick.ten{opacity:.26}
#frame[data-rolling] .dots2{opacity:.54}

/* THE BLOB — what you see. --sx/--sy are written by the spring loop from its
   velocity, so the shape stretches into the direction of travel and recovers
   when it stops. That squash is the entire reason the effect reads as liquid.

   task 5 fix-round: z-index:1. The dot builder IIFE appends every dot INTO
   #dots, which already contains blobA/blobB in the markup — appendChild puts
   new nodes last, so without this the dots painted ON TOP of the blob. An
   "on" dot is the same red as the blob so that was invisible, but an "off"
   dot is rgba(255,255,255,.30) and punched a visible pale disc through the
   blob wherever one sat inside its radius (confirmed by removing the #50
   dot in isolation — the disc went with it). z-index makes the blob the top
   layer regardless of DOM order, so any dot under it is fully absorbed
   (consistent for lit and dim dots alike) and the meniscus to a NEIGHBOURING
   dot — a separate effect, from the filter's blur, not from paint order —
   is unaffected. */
.blob{position:absolute;top:50%;z-index:1;
  left:calc(var(--r)/2 + (100% - var(--r)) * var(--p,50) / 100);
  width:var(--bs);height:var(--bs);border-radius:50%;
  background:var(--acc);
  /* goo r7 LOCKSTEP (see .dot): the blob recolours on the dots' exact clock.
     It had no transition at all, so on the revert it snapped back to red
     while the dot it merges with was still mid-fade to gold — the filter
     painted the seam as a two-tone teardrop. Background only: the spring
     loop owns transform every frame and must never be interpolated twice. */
  transition:background calc(.35s * var(--bankk,1)) linear;
  /* the anchor fix (see .band): centring on `translate`, the squash alone
     on transform — scale multiplies after the centring, never through it */
  translate:-50% -50%;
  transform:scale(var(--sx,1),var(--sy,1))}
#blobB{left:calc(var(--r)/2 + (100% - var(--r)) * var(--p2,74.25) / 100);
  transform:scale(var(--sx2,1),var(--sy2,1))}
.blob[hidden]{display:none}
/* TASK 7: nothing ever added .win/.lose to blobA/blobB in JS — the engine
   signals a result by setting data-res on #frame (game.js settle()), which
   is how the bead and #chip already recolour. These read the same
   attribute rather than a class no code ever wrote.
   EMBER LAW (colors gauntlet, owner-ratified 2026-08-26): a loss is not a
   colour that arrives — it is the house red GOING OUT. The whole filtered
   line banks together to --acc-out, the cooled ember (solved, not picked:
   >=1.5:1 above the board ground so the blob stays an object, <=0.5x the
   dead-zone tick so the inversion holds) — rope, beads, drops and the
   roll marker as ONE mass, uniform, zero stagger (the stagger is the
   win's entrance and nothing else's). The foreign blue-grey the loss line
   used to wear leaves the board entirely; --loss survives as a TEXT
   colour only (the figure, #chipn, #dl, the ledger down-states). The
   uniform bank is also the colour law's floor: recolouring any subset of
   the filter group — the old blob-only rule below — painted the two-tone
   crescent through the merged goo. */
#frame[data-res="win"] .blob{background:var(--win)}
#frame[data-res="lose"] .blob{background:var(--acc-out)}
#frame[data-res="lose"] .dot:not(.off){background:var(--acc-out)}
#frame[data-res="lose"] .band{background:var(--acc-out)}
/* WIN ROUND (W1): THE BANK LEADS. On the WIN ONLY, the filter group's
   shared background clock drops 350 -> 160ms with an ease-out, so the
   rope, beads, drops and roll bead are gold under the ring's first frame —
   the gold lands ON the strike frame and everything else is its wake. The
   LOSS keeps the base .35s: Hacksaw's ember bleed IS the loss frame's
   whole idea. The revert-to-red also reads the base rule (the destination
   style owns a transition), so neither moves. --bankk still scales it:
   turbo 72ms. One clock for everything the filter can merge — the colour
   law's lockstep holds at the faster speed exactly as it held at .35s. */
#frame[data-res="win"] .band{
  transition:background calc(.16s * var(--bankk,1)) cubic-bezier(.25,.8,.4,1)}
#frame[data-res="win"] .dot{
  transition:opacity .3s,background calc(.16s * var(--bankk,1)) cubic-bezier(.25,.8,.4,1),
    width .2s ease,height .2s ease}
#frame[data-res="win"] .blob{
  transition:background calc(.16s * var(--bankk,1)) cubic-bezier(.25,.8,.4,1)}
#frame[data-res="win"] #bead{
  transition:opacity 55ms linear,background calc(.16s * var(--bankk,1)) cubic-bezier(.25,.8,.4,1)}

/* THE BEAD — the marker that runs the rail (wf-anim F5, replacing #needle,
   which was invisible while it mattered and visible only after: the dead
   marker-after-verdict pattern, deleted in F2). The bead starts from the
   last result (--pr === S.lastRoll after every settle), is gathered out of
   the blob on the commit frame (the 55ms opacity fade below — already
   moving when it lands at full opacity), runs the rail white, and wears
   the verdict from the strike frame on, through the SAME [data-res] .blob
   rules the target blob reads — one state, one token.
   INSIDE #dots (C5): outside the filter nothing merges, and the
   absorb/release read — and the free near-miss neck — is gone. z-index 2
   puts the white bead over the blob it passes through (the blob is 1).
   THE SQUASH LIVES IN --bsx/--bsy, written only by travel()/beadRecover():
   overshoot lives in SHAPE, never in position — the figure rides the bead,
   and a positional overshoot prints a wrong number at 72px. background
   keeps the goo's own .35s lockstep clock, so the strike recolour can
   never seam against a dot it is merged with. */
#bead{z-index:2;
  left:calc(var(--r)/2 + (100% - var(--r)) * var(--pr,50) / 100);
  width:var(--brs);height:var(--brs);
  background:#FFF;
  /* the anchor fix (see .band): the squash's scale must not scale the
     centring — the strike frame is the frame the verdict is read on */
  translate:-50% -50%;
  transform:scale(var(--bsx,1),var(--bsy,1));
  will-change:transform;
  transition:opacity 55ms linear,background calc(.35s * var(--bankk,1)) linear}
/* the decay guard (bead prototype bug 2): no verdict on the board and no
   roll in flight means no marker — an idle board shows the target blob
   alone, exactly as it did before the first roll. */
#frame:not([data-res]):not([data-rolling]) #bead{opacity:0}

/* THE GRIP — what you drag. Transparent, the same 60px box as the blob it
   stands in for, and ABOVE the filter. Its box must match the blob's or the
   player grabs at a place the blob is not. touch-action carries over from
   the old .grip rule — the drag listener still uses setPointerCapture. */
#gripA,#gripB{position:absolute;top:50%;
  left:calc(var(--r)/2 + (100% - var(--r)) * var(--p,50) / 100);
  width:var(--bs);height:var(--bs);margin:0;border:0;border-radius:50%;
  background:none;transform:translate(-50%,-50%);
  cursor:grab;touch-action:none;z-index:3}
#gripB{left:calc(var(--r)/2 + (100% - var(--r)) * var(--p2,74.25) / 100)}
.grip[hidden]{display:none}
#gripA:active,#gripB:active{cursor:grabbing}
#gripA:focus-visible,#gripB:focus-visible{outline:2px solid var(--cp-red-hi);outline-offset:2px}
#frame[data-rolling] .grip{pointer-events:none}
/* task 6 fix-round dimmed the blob (and the mark riding it) to .5 here as a
   "locked while rolling" affordance. GOO R7 OVERTURNS IT on the owner's own
   words with a mid-roll frame in hand — "Why does the goo disappear when
   rolling": on this dark board a half-transparent blob does not read as
   locked, it reads as the hero vanishing mid-travel. The blob and its mark
   stay at full opacity for the whole roll. The not-draggable state loses no
   coverage: the grips are pointer-events:none above (cursor with them),
   #modes dims (80-auto), the pricing bubble dims to .6 (50-readouts — it is
   pricing, not the hero, so its dim stays), and the CTA itself says ROLLING.
   No opacity rule on .blob or .ico under [data-rolling].
   RECORDED AS A DELIBERATE DEPARTURE (fix-round D2): the verdict item that
   covered this said REPOINT the dim, not delete it — the owner's mid-roll
   frame ("Why does the goo disappear when rolling") is the ruling that
   overturned it, and deletion, not repointing, is what shipped. */

/* ---- THE MARK — task (r2) 2. Outside #dots, same rule as the grip: a
   goo-filtered container merges only what is inside it, and #ico is meant to
   stay a crisp mark riding the liquid, not another shape for the filter to
   absorb. Positioned off the SAME --p/--p2 the blob/grip already read, so it
   can never drift off the surface it marks — one set of numbers, three
   readers. z-index 2: above the blob (1), below the grip (3) the player
   actually drags, which is correct since #ico is decorative and must never
   intercept a pointer. */
.ico{position:absolute;top:50%;z-index:2;pointer-events:none;
  left:calc(var(--r)/2 + (100% - var(--r)) * var(--p,50) / 100);
  width:calc(26px*var(--sc,1));height:calc(26px*var(--sc,1));transform:translate(-50%,-50%);
  color:var(--icoin);filter:drop-shadow(0 2px 2px rgba(0,0,0,.3));
  transition:opacity .25s}
.ico svg{width:100%;height:100%;display:block}
#icoB{left:calc(var(--r)/2 + (100% - var(--r)) * var(--p2,74.25) / 100)}
.ico[hidden]{display:none}
#frame[data-tiny] .ico{width:15px;height:15px}
/* v1's icon wob (wf-anim §2.5), adapted to our centred transform: fired by
   game.js on grip RELEASE (pointerup, never per-input-frame) and again on
   the paying blob's figure at a winning strike — remove/reflow/add re-fires
   the class per event. */
.ico.wob{animation:wob .5s cubic-bezier(.2,1.5,.4,1)}
@keyframes wob{0%{transform:translate(-50%,-50%) scale(.6) rotate(-18deg)}
  100%{transform:translate(-50%,-50%) scale(1) rotate(0)}}
/* S1's ACKNOWLEDGE (casino-manager fix round, 2026-08-27): the coarse-
   pointer tap that S1 rightly ignores gets ONE small answer — the nearer
   grip's mark pulses 1.12 -> 1 in 200ms. Scale only, the mark's own ink:
   no gold, no rotation, no overshoot — deliberately NOT the wob above,
   which is the release/strike voice and would make a dead tap read as a
   win cue. game.js's fireBeck is the one writer, and it sits behind the
   drag handler's rolling/auto/paused bail, so this cannot play over live
   money. 95-motion stills it entirely. */
.ico.beck{animation:beck .2s ease-out}
@keyframes beck{0%{transform:translate(-50%,-50%) scale(1.12)}
  100%{transform:translate(-50%,-50%) scale(1)}}
/* the amplitude steps UP where the mark steps DOWN: at data-tiny the mark
   is 15px (60-density) — every portrait phone is tiny by width — and 1.12
   of 15px is under 2px of growth, a pulse that exists only in the numbers.
   1.35 of 15px is the same ~5px of optical movement the 26px mark gets at
   1.12+shadow, so the gesture survives the density rung instead of being
   quietly deleted by it. Rendered at 390x844 before trusting the ratio.
   Only ever seen on touch frames — fireBeck fires on data-touch alone —
   so no mouse frame gains a louder pulse from the tiny rung. */
#frame[data-tiny] .ico.beck{animation:beckt .2s ease-out}
@keyframes beckt{0%{transform:translate(-50%,-50%) scale(1.35)}
  100%{transform:translate(-50%,-50%) scale(1)}}

/* ---- ITEM 7: THE BAND EDGES' VALUES RIDE THE BALLS ----------------------
   The owner, twice in one round: "the numbers should be below the bubble
   even if we need to create a bit more space", then sharper: "From and to
   for between and outside should sit just below the balls if needed
   create more space." So each edge's value sits DIRECTLY under its own
   blob — the same --p/--p2 the blob, grip and mark read, one set of
   numbers, five readers — in the figure face, live during drag. Band
   modes only: in Under/Over the single blob's edge is already printed
   under the track by the Win chance card, and a third copy under the ball
   read as clutter on the render, not as information. The verdict never
   recolours these — they are AIM, like the mode rail, not outcome.
   THE SPACE IS PAID FOR, NOT STOLEN: below the designed track the values
   land in the scale row's lane (#ticks), whose height layout() SOLVES
   (--tlane — the designed constant or the hv overhang, whichever is
   larger) and spends in the same budget. The measure-the-row version of
   this sentence claimed the budget "self-corrects"; it did not — the
   constant lane was short at the min rooms and the values printed into
   the readout cards (+5.5px at 720x405, +3.0 at 900x430) until fix
   round M3 made the lane the solver's own number. The 0/100 end figures dim in
   band modes so a band edge dragged to the rail's end layers over a
   whisper, not a second number. */
/* COLLAR ROUND: the values share the satellite's own line — same standoff
   (--orbgap), a step down from its face (.86), a whisper against the
   chance figure at the band's centre (.66). The old per-room constants
   (5px*sc / 12.5px*sc, 2px/9px at min) are absorbed by --orbgap/--orbfs,
   which layout() already scales per frame. .orbmute is paint()'s crowd
   yield: when the band narrows under 1.15 drops the values would mush
   into the chance figure between them, so they yield to it — E-i3
   measured zero collisions with the values present outside that band. */
.hv{position:absolute;z-index:2;pointer-events:none;display:none;
  left:calc(var(--r)/2 + (100% - var(--r)) * var(--p,50) / 100);
  top:calc(50% + var(--bs)/2 + var(--orbgap,6px));
  transform:translateX(-50%);
  font:700 calc(var(--orbfs,13px) * .86)/1 var(--fig);font-variant-numeric:tabular-nums;
  color:var(--ink);opacity:.66;text-shadow:0 1px 6px rgba(0,0,0,.65);
  transition:opacity .2s}
#hvB{left:calc(var(--r)/2 + (100% - var(--r)) * var(--p2,74.25) / 100)}
#tk:has(#gripB:not([hidden])) .hv{display:block}
/* mid-roll the aim dims exactly like the mode rail — the board is answering */
#frame[data-rolling] .hv{opacity:.45}
/* the crowd yield outranks the roll dim (two ids beat id+attr): a muted
   value stays muted mid-roll rather than ghosting back at .45 */
#frame #hvA.orbmute,#frame #hvB.orbmute{opacity:0}

/* ==========================================================================
   GOO R6 TASK 2 — THE LINE GOES GOLD, AND THE WIN GETS ITS MOMENT.
   The owner, with a settled win in hand: "All red on the line should be on
   yellow" and "can we have some celebration like plinko".

   (a) ONE STATE, ONE TOKEN. [data-res="win"] — the same attribute the blob,
   bead and chip already read — turns EVERYTHING inside the filter gold:
   the rope, the paying majors, the drops. All of it reads --win, so the
   token edit that repointed the gold once moves this too. The revert is
   the attribute leaving (decay, next roll, bet change) and the line's own
   lockstep background transition — no celebration state survives the
   verdict, which settle()/doRoll() guarantee by removing data-mag with
   data-res. The dry ruler in #dots2 NEVER turns — the colour law stops at
   the filter's edge, and the ruler is the constant the verdict is read
   against. (The loss half lives with the blob rules above: the whole line
   banks to --acc-out — the r6 claim that "the loss line is untouched" is
   superseded by the ratified ember law.) */
#frame[data-res="win"] .dot:not(.off){background:var(--win)}
/* THE DOT STAGGER IS DELETED (fix round M1), not retired: the
   transition-delay rule that stood here staggered full discs INSIDE the
   goo filter — a subset recolour, the colour law's own named defect — so
   for ~600ms of every win (nearly the whole round in turbo) the merged
   rope was hard red discs punched through gold. The dots now bank with
   the rope and drops on the one uniform lockstep clock, and the outward
   ignition lives on the unfiltered .wig layer above (settle() writes its
   --wd there), which can stagger freely because nothing it touches is
   ever merged. */
/* the rope IS the paying region now, so on a win it goes full gold with
   the beads and drops it is merged with — one tone per state, the colour
   law's other half (a .12 wash inside the filter would paint the seam) */
#frame[data-res="win"] .band{background:var(--win)}

/* (b) THE CELEBRATION — Plinko's grammar in Dice's materials. Plinko's win
   moment (its boardMoment) is: an impact STRIKE of countable rings leaving
   the slot (1-4, the rung ladder), a burst of brand light behind them, the
   board's own furniture (the peg lattice) igniting outward from the hit,
   and everything scaled by the size of the win — no confetti, no foreign
   props. Translated: the rings leave the BLOB, the light is a gold bloom
   under it, the furniture that ignites is the DOT LINE (the --wd stagger
   above), and the thrown matter is GOO — droplets of the blob's own liquid,
   in the win gold.

   THE ANCHOR IS ZERO-SIZE AND RIDES --mkpx, the bead's own landing pixel — the
   burst happens where the roll LANDED. Every child is position:absolute and
   animates transform/opacity only: nothing here can reflow anything.
   NO JS CLOCK: the whole show is CSS animations keyed on [data-res="win"]
   arriving, which doRoll() removes and settle() re-sets every round, so it
   re-fires per win and can never delay the controls ("live after the
   colour lands" is roll()'s timer, untouched).

   THE LADDER IS COUNTABLE, like Plinko's rings: data-mag (settle(): 1
   under 4x, 2 under 10x, 3 at 10x+) shows 1/2/3 rings and 6/8/10 droplets.
   TURBO COMPRESSES, NEVER REMOVES: every duration/delay is scaled by
   --celk (.45 in turbo), the same cut TURBO's own timings take, so the
   show fits the 1200ms hold with room to spare. */
.cel{position:absolute;top:50%;left:0;width:0;height:0;z-index:2;
  pointer-events:none;transform:translateX(var(--mkpx,0px))}
/* THE REVERT RULE — stated here because its absence is what let the autobet
   cut ship: the dots above declare their revert (one uniform .35s fade) and
   the celebration declared nothing, so when doRoll() stripped data-res/
   data-mag mid-flight (autobet's cadence: ~440ms between commits, ~200ms in
   turbo, both shorter than the 620-840ms show) every child SNAPPED from its
   in-flight opacity to base 0 in one frame.
   The rule: AN INTERRUPTED CELEBRATION YIELDS — a .18s opacity fade from
   wherever it was, the same grammar as the dots' uniform revert. This
   transition on the BASE state is half of it; while [data-res="win"] holds,
   the keyframe animation owns opacity outright (animations beat
   transitions), so the uninterrupted show is untouched frame for frame.
   The other half is celYield() in game.js: Blink does NOT hand a removed
   animation's value to a transition (measured — the attribute strip alone
   still snapped 1.0 -> 0), so the strip freezes each in-flight child's
   animated opacity/transform inline, commits that state, then releases
   opacity to 0, and THIS transition carries the fade. Deliberately NOT
   scaled by --celk: like the dots' .35s, the yield is one fixed manner at
   every speed, and .18s clears turbo's 280ms roll-to-settle window before
   the next arm. Opacity only — the pose stays frozen while it dissolves. */
.cel i{position:absolute;left:0;top:0;opacity:0;transition:opacity .18s ease-out}

/* THE BLOOM IS DELETED (win round, W4's ruling, kept exactly as the owner
   previewed it): a 2.8x--bs gold radial under the blob is a smaller
   version of the room's light 100px away — the rings are the strike; the
   ROOM is the light (#roomlit, 20-stage.css). The element stays in the
   markup because the ring and droplet :nth-child indices below are
   position-counted against it (1 the bloom, 2-4 the rings, 5-14 the
   droplets, 15 the tier-4 ring); only its paint goes. The celbloom
   keyframes and the --celbl sizing ladder died with it. */
.celBloom{display:none}

/* THE TIER LADDER'S TOKENS (win round, W5): data-mag is 1-4 now, priced
   off the CHOSEN multiplier (settle(): EDGE_NUM / r.cU — never pay vs
   stake, which mis-tiered small stakes through payout rounding).
     T1 <2x   T2 2-10x   T3 10-50x   T4 >=50x
   --rmax   the ring's terminal scale (W1's size-as-well-as-count ladder;
            T4 holds T3's value — the ring has said its biggest word and
            the dim, the fourth ring and the residue carry the top rung)
   --celr   the spray's radius multiplier on --bs (W5)
   --rlev/--rlevt   the dry ruler's residue levels, minors/tens (W5) */
#frame[data-mag="1"]{--rmax:1.55;--celr:.90;--rlev:.13;--rlevt:.26}
#frame[data-mag="2"]{--rmax:1.75;--celr:1.35;--rlev:.20;--rlevt:.34}
#frame[data-mag="3"]{--rmax:1.95;--celr:1.80;--rlev:.28;--rlevt:.44}
#frame[data-mag="4"]{--rmax:1.95;--celr:2.30;--rlev:.40;--rlevt:.62}

/* the strike (W1's grammar): hard thin rings, BORN CLEAR OF THE BLOB and
   still white — start at scale .68 with opacity ramping in over 12%, hold
   white to 45%, and kill the opacity by the end instead of letting a big
   pale circle linger (the lingering circle is what read as a reticle in
   the mag-3 frames). A countable ladder, not a smear: 40/88ms at
   220/260/300ms — the leading ring is gone before the eye can measure it.
   Stroke scales with the board: 3px flat was 3px at --bs 34 and at 65.
   EVERY .cel CHILD IS AN <i>, so :nth-child indexes the one flat run:
   1 the bloom (dark), 2-4 the rings, 5-14 the droplets; the tier-4 ring
   is LAST and addressed by class so no index above it shifts. */
.celRing{width:var(--bs);height:var(--bs);
  margin:calc(var(--bs) / -2) 0 0 calc(var(--bs) / -2);border-radius:50%;
  border:solid var(--win);border-width:max(3px, calc(var(--bs) * .072))}
#frame[data-res="win"] .celRing{
  animation:celring calc(.22s * var(--celk,1)) cubic-bezier(.3,.55,.35,1)}
#frame[data-res="win"] .celRing:nth-child(3){
  animation-delay:calc(.040s * var(--celk,1));animation-duration:calc(.26s * var(--celk,1))}
#frame[data-res="win"] .celRing:nth-child(4){
  animation-delay:calc(.088s * var(--celk,1));animation-duration:calc(.30s * var(--celk,1))}
/* the fourth ring — tier 4 only, late and slow: the top rung is countable
   as a FOURTH event, not a bigger smear */
#frame[data-res="win"][data-mag="4"] .celR4{
  animation:celring calc(.74s * var(--celk,1)) cubic-bezier(.17,.67,.35,1);
  animation-delay:calc(.27s * var(--celk,1))}
@keyframes celring{
  0%{opacity:0;border-color:#fff;transform:scale(.68)}
  12%{opacity:1}
  45%{border-color:#fff;opacity:.78}
  100%{opacity:0;border-color:var(--win);transform:scale(var(--rmax,1.75))}}

/* the spray (W1's arc on W5's vectors): the blob throws off ten droplets
   of its own goo. The old celfly translated in a straight line to a stop
   and vanished — a firework, not thrown liquid; three stops on the same
   transform now: born small at the bead, pinched off at 12%, apex at 52%,
   falling back through 100%. The VECTORS are unit rays x --bs x --celr —
   the old absolute px put the same 75-98px fan on every frame (2.6x --bs
   at 380x300, droplets leaving the well) and parked eight of ten at one
   radius (a dotted ring, not a spray). Two shells (x1 / x1.21), three of
   ten thrown DOWNWARD — struck liquid throws in every direction. The
   radius is what the tier ladder spends (--celr above); THE COUNT RUNGS
   DIE — all ten fly at every tier, reach carries the magnitude. Fixed
   vectors, not random: same win, same throw, every window. */
.celD{width:8px;height:8px;margin:-4px 0 0 -4px;border-radius:50%;
  background:var(--win)}
.celD:nth-child(odd){width:6px;height:6px;margin:-3px 0 0 -3px}
#frame[data-res="win"] .celD{
  animation:celfly calc(.60s * var(--celk,1)) cubic-bezier(.16,.72,.3,1)}
.celD:nth-child(5) {--fx:calc(var(--bs) * var(--celr,1.35) * -.8155);--fy:calc(var(--bs) * var(--celr,1.35) * -.5787)}
.celD:nth-child(6) {--fx:calc(var(--bs) * var(--celr,1.35) * -.4366);--fy:calc(var(--bs) * var(--celr,1.35) * -.8997)}
.celD:nth-child(7) {--fx:calc(var(--bs) * var(--celr,1.35) *  .0256);--fy:calc(var(--bs) * var(--celr,1.35) * -.9997)}
.celD:nth-child(8) {--fx:calc(var(--bs) * var(--celr,1.35) *  .4789);--fy:calc(var(--bs) * var(--celr,1.35) * -.8779)}
.celD:nth-child(9) {--fx:calc(var(--bs) * var(--celr,1.35) *  .8321);--fy:calc(var(--bs) * var(--celr,1.35) * -.5547)}
.celD:nth-child(10){--fx:calc(var(--bs) * var(--celr,1.35) * -.9919);--fy:calc(var(--bs) * var(--celr,1.35) * -.1272)}
.celD:nth-child(11){--fx:calc(var(--bs) * var(--celr,1.35) *  .9884);--fy:calc(var(--bs) * var(--celr,1.35) * -.1521)}
.celD:nth-child(12){--fx:calc(var(--bs) * var(--celr,1.35) * -.2373);--fy:calc(var(--bs) * var(--celr,1.35) * -1.1865)}
.celD:nth-child(13){--fx:calc(var(--bs) * var(--celr,1.35) *  .2681);--fy:calc(var(--bs) * var(--celr,1.35) * -1.1800)}
.celD:nth-child(14){--fx:calc(var(--bs) * var(--celr,1.35) *  .6189);--fy:calc(var(--bs) * var(--celr,1.35) * -1.0398)}
@keyframes celfly{
  0%{opacity:1;transform:translate(0,0) scale(.55)}
  12%{transform:translate(calc(var(--fx) * .18),calc(var(--fy) * .22)) scale(1.12)}
  52%{opacity:1;transform:translate(calc(var(--fx) * .82),calc(var(--fy) * 1.06)) scale(.78)}
  100%{opacity:0;transform:translate(var(--fx),calc(var(--fy) * .42)) scale(.24)}}

/* THE RING-COUNT SUPPRESSIONS, RESTATED LAST so they win among equals
   (W1's own ordering note: .celRing:nth-child(3)'s delay rule would
   otherwise resurrect a ring the ladder switched off): one ring at T1,
   two at T2, three at T3, four at T4 — and the fourth ring exists ONLY
   at T4. */
#frame[data-mag="1"] .celRing:nth-child(3),
#frame[data-mag="1"] .celRing:nth-child(4),
#frame[data-mag="2"] .celRing:nth-child(4){animation:none}
#frame:not([data-mag="4"]) .celR4{animation:none}

/* THE WON LINE — the owner's A/B pick, "Bigger" (win round, 2026-08-27):
   ~1.8x the shipped face (clamp 15..27 off --num at .40 against the old
   10..15 at .22), weight 800, and an entrance that holds at 1.12 through
   62% before settling — the amount is readable at a glance from the far
   side of a multi-tabled desk, which was the owner's complaint verbatim
   ("a lot of them it's hard to see how much I won"). Every hold rides
   --celk so turbo compresses it with the rest of the show; opacity is
   held 0 until 20% so the line arrives AFTER the hit. The loss is
   untouched — every rule keys on data-r="win", and #chipw is empty on a
   loss. layout()'s lane solver prices this exact clamp (winH), so the
   taller line is paid for in the vertical budget, never stolen from the
   mode rail. Replaces wpop, deleted with the shipped face. */
#chip[data-r="win"] #chipw:not(:empty){
  font-size:clamp(15px, calc(var(--num) * .40), 27px);
  font-weight:800;letter-spacing:.05em;
  animation:wonin calc(.6s * var(--celk,1)) cubic-bezier(.16,1.4,.3,1) both}
@keyframes wonin{
  0%{transform:scale(.4);opacity:0}
  20%{opacity:1;transform:scale(1.12)}
  62%{transform:scale(1.12)}
  100%{transform:scale(1)}}


/* ---- THE COLLAR AND THE END-STOP ETCH (owner-approved 2026-08-27:
   "Great job I really like it, implement") -------------------------------
   The readout row is DELETED — #stats, both .sf cards and the odds dial —
   and the two figures it printed moved onto the instrument itself:

   THE COLLAR: the win chance is an arc ENGRAVED IN THE DROP'S RIM — an
   annulus between the mark and the rim, --icoin stroke, lit for exactly
   chance x 3.6 degrees from 12 o'clock. The chance FIGURE hangs under the
   drop as an editable south satellite (the band modes hang it at the
   band's centre), with its WIN CHANCE caption beneath — the owner's own
   addition ("whould have win% in small so users know... low is beneath").

   THE ETCH: the multiplier is etched into the dry ruler hard against the
   rail's end — the #dots2 mask below cuts the cadence so it terminates in
   the figure. In Outside it centres in the dead zone; at the docked band
   frames it reunites with the chance figure under the drop (.det).

   This supersedes the owner's boxed-readouts ruling ("Multiplier and win%
   should also be in containers") — superseded by their approval of the
   collar render, not overruled. What that ruling was FOR survives in a
   different grammar: editability is still visible — the frameless figures
   carry an underline that brightens on hover and goes the house red with
   focus (the harness's own grammar; §19 of the check asserts it).
   game.js's solveFigures() is the ONE writer of every custom property
   here; positions ride the same spring-written --p/--p2 every other track
   object reads, so nothing here needs per-frame JS. */
.orb{position:absolute;top:50%;z-index:2;pointer-events:none;display:block;
  left:calc(var(--r)/2 + (100% - var(--r)) * var(--p,50) / 100);
  width:var(--orbd,49px);height:var(--orbd,49px);
  transform:translate(-50%,-50%) rotate(-90deg);
  transition:opacity .18s linear}
#orbB{left:calc(var(--r)/2 + (100% - var(--r)) * var(--p2,74.25) / 100)}
/* when the drops merge into one mass the rings merge with them — paint()
   writes .orbmid off the same frozen targets that hide #orbB */
.orb.orbmid{left:calc(var(--r)/2 + (100% - var(--r)) * ((var(--p,50) + var(--p2,74.25)) / 2) / 100)}
.orb[hidden]{display:none}
.orb circle{fill:none;stroke-linecap:butt;stroke:var(--icoin)}
.orb .otrk{opacity:.30;transition:opacity .18s linear}
.orb .oarc{opacity:.96}
#frame[data-drag] .orb .otrk{opacity:.46}
#frame[data-rolling] .orb,#frame[data-res] .orb{opacity:.28}

/* the chance figure — south satellite, editable, no box. The lockup is
   figure over caption (the owner: "low is beneath"); both dim together
   under rolling/verdict, so the caption can never outlive its figure. */
.orw{position:absolute;z-index:4;display:grid;justify-items:center;gap:2px;
  top:calc(50% + var(--bs)/2 + var(--orbgap,6px));
  left:clamp(var(--owmin,-9999px),
    calc(var(--r)/2 + (100% - var(--r)) * var(--p,50) / 100 + var(--owdx,0px)),
    var(--owmax,9999px));
  transform:translateX(-50%);
  text-shadow:0 1px 6px rgba(0,0,0,.7);transition:opacity .2s}
#tk:has(#gripB:not([hidden])) .orw{
  left:clamp(var(--owmin,-9999px),
    calc(var(--r)/2 + (100% - var(--r)) * ((var(--p,50) + var(--p2,74.25)) / 2) / 100 + var(--owdx,0px)),
    var(--owmax,9999px))}
.orwRow{display:flex;align-items:baseline;gap:1px}
/* THE UNDERLINE IS VISIBLE AT REST — casino-manager MAJOR on the landing:
   the harness's transparent-at-rest underline left a bare number with no
   affordance at all (cursor:text is invisible until the mouse is already
   there), and every operator a dice player has seen boxes these fields.
   The fix stays inside the approved frameless grammar: a quiet hairline
   under the figure at rest, brighter under the hand, the house red with
   focus — a whisper of "typeable", never a box. */
.orw input{background:none;border:0;padding:0;margin:0;text-align:right;
  font:700 var(--orbfs,13px)/1 var(--fig);font-variant-numeric:tabular-nums;
  color:var(--ink);caret-color:var(--cp-red-hi);width:var(--orbw,34px);
  box-shadow:inset 0 -1px 0 rgba(255,255,255,.16);transition:box-shadow .15s,color .15s}
@media (hover:hover){.orw input:hover{box-shadow:inset 0 -1px 0 #5A6172}}
.orw input:focus{outline:none;color:#fff;box-shadow:inset 0 -1px 0 var(--cp-red-hi)}
.orw .u{font:700 calc(var(--orbfs,13px) * .70)/1 var(--ui);color:#8B91A3}
/* the caption: the house caption face, muted — identification, not a
   competing label. It sheds at the docked minimum (the % unit still
   identifies the number there); the small-frame call was made on renders. */
.orwCap{font:600 calc(9px*var(--scc,1))/1 var(--ui);letter-spacing:.14em;
  text-transform:uppercase;color:#666C7E;white-space:nowrap}
#frame[data-room="min"] .orwCap{display:none}
#frame[data-rolling] .orw,#frame[data-res] .orw{opacity:.42}

/* THE RULER TERMINATES IN THE FIGURE: the mask cuts the dot cadence from
   just short of the digits to the rail's stop, so the scale counts up and
   ends at the payout — never a window with an orphan dot outboard of it.
   solveFigures() writes --etchL/--etchR; the -60px defaults resolve to a
   fully visible ruler (the det state's own spelling).
   .wig JOINED THE MASK in the win round (casino-manager, rendered at
   380x300 Under 9.70): the inverted wave's lamps travel the DEAD zone —
   the exact region the etch sits in — and a 1.15x-bead glow parked on
   "10.0000"'s zeros read as a defect the masked ruler ticks were already
   protected from. Same window, same vars, so the lamps and the residue
   terminate in the figure exactly as the cadence does. #wigland is safe
   inside this mask by construction: it marks the LANDING, a win's landing
   is always in the PAYING region, and the etch window is always cut into
   the dead side (end-stop or dead-zone centre; in det the defaults leave
   everything visible). */
#dots2,.wig{-webkit-mask-image:linear-gradient(90deg,#000 var(--etchL,-60px),
    transparent calc(var(--etchL,-60px) + 9px),
    transparent calc(var(--etchR,-60px) - 9px),#000 var(--etchR,-60px));
  mask-image:linear-gradient(90deg,#000 var(--etchL,-60px),
    transparent calc(var(--etchL,-60px) + 9px),
    transparent calc(var(--etchR,-60px) - 9px),#000 var(--etchR,-60px))}

/* the multiplier — etched at the rail's end, anchored by --mrx through a
   TRANSLATE (never `left`: the etch repositions on mode changes and
   resizes, and a transform stays off the layout path) */
.mrm{position:absolute;z-index:3;top:50%;left:0;
  transform:translate(calc(var(--mrx,0px) - 50%),-50%);
  display:flex;align-items:baseline;gap:3px;
  white-space:nowrap;transition:opacity .18s linear}
.mrm input{background:none;border:0;padding:0;margin:0;text-align:right;
  width:var(--mrw,96px);
  font:700 var(--mrfs,28px)/1 var(--fig);font-variant-numeric:tabular-nums;
  color:var(--ink);letter-spacing:-.015em;caret-color:var(--cp-red-hi);
  text-shadow:0 1px 8px rgba(9,10,15,.95);
  /* visible at rest — the same MAJOR as the satellite's rule above */
  box-shadow:inset 0 -1px 0 rgba(255,255,255,.16);transition:box-shadow .15s,color .15s}
@media (hover:hover){.mrm input:hover{box-shadow:inset 0 -1px 0 #5A6172}}
.mrm input:focus{outline:none;color:#fff;box-shadow:inset 0 -1px 0 var(--cp-red-hi)}
.mrm .x{font:700 calc(var(--mrfs,28px) * .50)/1 var(--ui);color:#7C8396;
  text-shadow:0 1px 6px rgba(9,10,15,.9)}
/* THE MULTIPLIER'S CAPTION (owner, 2026-08-27: "multiplier at bottom of
   multiplier") — OVERRULING the collar round's no-caption call ("a floating
   PAYOUT broke the etched read"; spec §13 carries the note). The overruled
   render's defect was the WORD, not the idea: PAYOUT floated a second money
   concept over the scale, where MULTIPLIER names exactly what the × already
   is. Same face, same 2px gap, same muted ink as .orwCap above, so the pair
   reads as one system; ABSOLUTE below the row (not in flow) so the digits
   stay centred on the ruler line the mask cuts — the etch does not rise.
   It dims with the figure through .mrm's own [data-rolling]/[data-res]
   rule below, and it sheds at the docked minimum on the chance caption's
   exact rule — the × identifies the figure there, as the % does the
   chance. solveFigures() folds the caption's width into the etch box (the
   det pair clamps and the mask window), and the lane solve pays its
   height; both in game.js. */
.mrmCap{position:absolute;top:calc(100% + 2px);left:50%;
  transform:translateX(-50%);
  font:600 calc(9px*var(--scc,1))/1 var(--ui);letter-spacing:.14em;
  text-transform:uppercase;color:#666C7E;white-space:nowrap;
  text-shadow:0 1px 6px rgba(0,0,0,.7)}
#frame[data-room="min"] .mrmCap{display:none}
/* THE REUNION: when the dead run is shorter than the digits there is no
   end-stop left to sit on, so the multiplier joins the chance figure under
   the drop as a matched pair — the same face, the same baseline. The pair
   is centred on the drop by solveFigures' --owdx/--mrdx split and clamped
   inside the rail as one unit. */
.mrm.det{top:calc(50% + var(--bs)/2 + var(--orbgap,6px));
  left:clamp(var(--mrmin,-9999px),
    calc(var(--r)/2 + (100% - var(--r)) * var(--p,50) / 100 + var(--mrdx,0px)),
    var(--mrmax,9999px));
  transform:translateX(-50%);text-shadow:0 1px 6px rgba(0,0,0,.7)}
#tk:has(#gripB:not([hidden])) .mrm.det{
  left:clamp(var(--mrmin,-9999px),
    calc(var(--r)/2 + (100% - var(--r)) * ((var(--p,50) + var(--p2,74.25)) / 2) / 100 + var(--mrdx,0px)),
    var(--mrmax,9999px))}
.mrm.det input{font-size:var(--orbfs,13px);width:var(--mrdw,60px);text-shadow:none}
.mrm.det .x{font-size:calc(var(--orbfs,13px) * .70);color:#8B91A3;text-shadow:none}
#frame[data-rolling] .mrm,#frame[data-res] .mrm{opacity:.42}

/* ---- the bubble, over the RESULT -----------------------------------------
   TASK (r2) 3: VISIBLE AT IDLE, LIVE WHILE DRAGGING. The owner's ruling on
   this round: v1 ships the pill showing at rest, reading the current
   profit-on-win figure, and ours only ever appeared on a settled win. game.js
   paint() now writes the live payout into #sp's text every pass that isn't
   holding a settled verdict (see paint()'s own comment); this file's job is
   making that text visible and putting it in the right PLACE for each of the
   three states.

   TWO DIFFERENT "RIGHT PLACE"S, ON PURPOSE. Idle and mid-drag, there is no
   result yet — the honest position is over the blob the player is actually
   aiming, so the base rule reads the SAME var(--p) left:calc() #blobA/#gripA
   use, and rides the drag exactly like they do.

   final whole-branch review, fix-round (task 7, kept here): a SETTLED win is
   a different question. This used to stay on that same --p left:calc() and a
   reviewer measured a settled Outside win 331px away from the marker that
   actually drew the result, because a wide Outside band can settle anywhere
   across most of the track while --p only ever marks one edge of it. The
   fix — reading --mkpx, the marker's own landing pixel — was scoped to
   data-res="win" only: correct for the settled figure, and no longer the
   number the bubble sits on before that, which is what made it wrong at
   idle in the first place. (That win-scoped position rule is itself gone
   now — [data-res] hides the bubble outright, further down — and the
   needle this paragraph once named is long deleted; the bead took its
   job. History kept, stale reference removed: fix-round D2.) */
/* OWNER, THIS ROUND: "Look at the bubble it move and has a frame." — the
   bubble adopts v1's own recipe (ref v1.html:158-164): white fill, dark
   ink, a 2.5px ACCENT border (the frame), 12px radius, 6px 13px padding,
   800-weight letterspaced type, and a gentle ±2° rock about the tail tip
   (sprock, 3.4s) — that is the "it moves". The border colour is OUR token
   (--acc, the CoinPoker red), never v1's ember hex. v1's spring-follow is
   ALREADY ours structurally: this left:calc() reads the same --p the
   spring loop (game.js springFrame) writes sixty times a second, so the
   bubble trails the blob on exactly the spring v1's does — nothing to
   port. Copy stays WIN $x — the settled verdict is the chip's, and the
   owner's different win animation comes later, not here. */
.sp{position:absolute;z-index:4;pointer-events:none;
  /* CLAMPED TO THE FRAME'S ROOM (fix round C1): the raw inset calc is the
     anchor, but the spring OVERSHOOTS a rail end on an ordinary longshot
     (Under 2.00 swings --p below 0 for a few frames) and carried the
     bubble 17-37px past #frame's edge — 05-frame's overflow:hidden cut
     "$48.50" to "8.50". --spmin/--spmax are the chip-clamp pattern solved
     for THIS element: layout()/paint() (spClamp in game.js) write the
     bubble's own measured half-width against the same region room
     --chipmin/--chipmax use. The fallbacks are wide open so the first
     pre-layout paint behaves as before. */
  left:clamp(var(--spmin,-9999px),
    calc(var(--r)/2 + (100% - var(--r)) * var(--p,50) / 100),
    var(--spmax,9999px));
  bottom:calc(50% + var(--bs)/2 + 14px*var(--sc,1));
  transform:translateX(-50%);
  transform-origin:50% calc(100% + 10px);
  background:#fff;color:#14161C;white-space:nowrap;
  border:2.5px solid var(--acc);
  /* ROPE ROUND: the bubble's face is sized from the DROP it points at
     (max(10, bs*0.21)), never from its own text — it measured 2.42x the
     hero's width at 720x405. The width half of the rule is paint()'s: if
     WIN $x exceeds 1.8 drops, the figure prints alone. */
  font:800 12px/1 var(--ui);font-size:max(10px, calc(var(--bs) * .21));letter-spacing:.06em;
  padding:calc(6px*var(--sc,1)) calc(13px*var(--sc,1));border-radius:calc(12px*var(--sc,1));
  box-shadow:0 4px 10px rgba(0,0,0,.4);
  animation:sprock 3.4s ease-in-out infinite;
  opacity:1;transition:opacity .2s linear}
/* v1's tail: a rotated square wearing the SAME 2.5px border on its outer
   two edges, so the frame runs continuously around the pointer — the old
   borderless CSS triangle would have broken the frame at the tail. */
.sp::after{content:'';position:absolute;left:50%;top:calc(100% - 6px*var(--sc,1));
  width:calc(13px*var(--sc,1));height:calc(13px*var(--sc,1));background:#fff;
  border-right:2.5px solid var(--acc);border-bottom:2.5px solid var(--acc);
  border-bottom-right-radius:3px;
  transform:translateX(-50%) rotate(45deg)}
@keyframes sprock{0%,100%{transform:translateX(-50%) rotate(-2deg)}
  50%{transform:translateX(-50%) rotate(2deg)}}
/* THE DOCKED MINIMUM GETS A SMALLER BUBBLE, rendered before it was kept:
   v1's full-size frame (32px tall with the border) on the 24px track at
   380x300 reached up into the mode rail itself — the row it sat over in
   the post-decay shot. Face, padding, border and standoff all step down
   together; the lane solver in layout() measures the element, so the
   caption maths follows this rung with no second copy of these numbers. */
#frame[data-room="min"] .sp{padding:4px 9px;border-width:2px;
  bottom:calc(50% + var(--bs)/2 + 6px)}
#frame[data-room="min"] .sp::after{width:10px;height:10px;border-width:2px;
  top:calc(100% - 5px)}
/* v1's pop: the springy scale-in, then the rock resumes. game.js re-arms
   the class when the decay returns the board to idle, so the bubble's
   reappearance is an arrival, not a fade. */
.sp.pop{animation:sppop .55s cubic-bezier(.2,1.8,.4,1),
  sprock 3.4s ease-in-out .55s infinite}
@keyframes sppop{0%{transform:translateX(-50%) scale(0) rotate(-10deg)}
  100%{transform:translateX(-50%) scale(1) rotate(0deg)}}
/* TASK 7 loop 2: THE BUBBLE YIELDS TO ANY SETTLED RESULT. Loop 1 moved it
   under the track on a win and the render answered: at 900x520 it sat on the
   MULTIPLIER readout, at 900x430 on the 0 tick - every slot in the track's
   airspace belongs to something at some frame. The win amount lives on the
   chip now (#chipw, 40-track.css), the one element already clamped inside the
   board, so the settled bubble has nothing left to say: it prices the NEXT
   bet, which is its idle job, and it comes back the moment the verdict clears.
   The loss half of this is task 7's ruling unchanged - ten of eleven filmed
   builds draw nothing at all on a loss. */
/* ITEM 3 (casino-manager MAJOR): THE HIDE IS INSTANT. The base rule's .2s
   opacity fade meant that when data-res landed the bubble spent ~155ms
   cross-fading while the chip's own #chipw printed the SAME "WIN $x" — a
   double-print every settle (autoflick-08.png). v1 hides by near-instant
   scale, not a crossfade, for the same reason; the trace (two figures never
   both legible) is the requirement, and transition:none is the version that
   proves it — opacity is 0 on the first frame after the verdict lands.
   animation:none so the rock cannot hold a transform frame either. The soft
   .2s fade stays for the idle↔drag/rolling dim, which is the base rule's. */
/* wf-anim F3 fix, caught on a turbo burst frame: the settled hide also PARKS
   THE SCALE at 0. Without it, the next commit (data-res leaving, data-rolling
   arriving) put opacity back to 1 instantly while the .15s scale-out was
   still near 1 — a full-size bubble flashing for a frame at every autobet
   commit. Parked at 0, settled→rolling never has a visible frame; the
   manual idle→rolling scale-out (scale 1 → 0, opacity 1) is untouched, and
   the decay's sppop still enters from scale(0) as it always did. */
#frame[data-res] .sp{opacity:0;transform:translateX(-50%) scale(0);transition:none;animation:none}
/* MOBILE ROUND (audit S4 #9, a desktop bug too): THE BUBBLE ALSO YIELDS TO
   A HELD FIGURE. There is one state [data-res] does not cover where a
   settled figure and the live bubble are both up: a bet changed after a
   settle (paint()'s sameCfg block strips the verdict, the figure keeps its
   position, the bubble resumes pricing) — at 720x405, figure 94.09 and
   target 95, they overlapped 51.1 x 24.6px in the shipped build. paint()
   writes data-sphold while the two boxes intersect; the bubble goes quiet
   on its own .2s opacity clock (the base rule's) and returns the moment
   the target moves clear. Opacity only — the box stays where it is, so
   the writer's intersection test cannot flap. */
#frame[data-sphold] .sp{opacity:0;animation:none}
/* (the loss-only opacity rule that stood here is absorbed by the broader
   [data-res] rule above - one writer for "settled means quiet") */
/* wf-anim C6/F3: v1 WINS OVER THE DIM — the bubble HIDES for the whole roll
   (v1's .hide: scale to nothing about the tail tip, .15s, animation killed
   so the rock cannot hold a transform frame). The .6 dim it replaces still
   left an 87.4x16.5px figure/bubble overlap at 380x300 mid-roll; a hidden
   bubble collides with nothing, at every size. Restoration is the path
   already in place: data-res keeps it hidden through the settle (the rule
   above), and the decay's sppop pops it back — or, on a plain bet change,
   data-rolling simply leaves and the base rule returns it. */
#frame[data-rolling] .sp{transform:translateX(-50%) scale(0);transition:transform .15s;animation:none}

/* ==========================================================================
   PORTRAIT (mobile round, 2026-08-27) — the two figures become CONTROLS.
   The chance/multiplier pair is the phone's only exact input (the rail's
   finest touch step at 390px of frame is 0.095 points, and the default
   48.50 is not even in its reachable set), so the frameless figures grow
   to 42/48px tap cells: layout()'s portrait ORBFS/MRFS floors are the
   faces, the 11px input padding here is the cell, and the lane solve's
   padCell pays for it. Portrait phones are data-room="min" by width, so
   every rule here that reopens a min-room shed (the captions, the full
   bubble) must sit after that rung in this file and win by order. */
#frame[data-port] .orw{gap:1px}
#frame[data-port] .orw input{font-size:var(--orbfs,20px);padding:11px 10px;
  width:calc(var(--orbw,34px) + 20px)}
#frame[data-port] .orw .u{font-size:calc(var(--orbfs,20px)*.62)}
#frame[data-port] .orwCap{display:block;font-size:9.5px}
#frame[data-port] .mrm input{padding:11px 10px;width:calc(var(--mrw,96px) + 20px)}
#frame[data-port] .mrm .x{font-size:calc(var(--mrfs,26px)*.46)}
#frame[data-port] .mrmCap{display:block;font-size:9.5px;top:calc(100% + 1px)}
/* the band-edge pair stays a LABEL on its ball, not a third readout */
#frame[data-port] .hv{font-size:13px}
/* the +8 rank drop in the band modes — the JS half is the lane solve's
   (port && bandLane) term; rendered at 390x844 Between the two ranks
   closed to a 1.3px graze without it */
#frame[data-port] #tk:has(#gripB:not([hidden])) .orw,
#frame[data-port] #tk:has(#gripB:not([hidden])) .mrm.det{margin-top:8px}
#frame[data-port] .mrm.det input{font-size:var(--orbfs,20px);width:calc(var(--mrdw,60px) + 20px)}
#frame[data-port] .mrm.det .x{font-size:calc(var(--orbfs,20px)*.62)}
/* the bubble is the full-size one again (the min-room small bubble was
   solved for a 24px track; portrait's box is 94-140px), standing off the
   fatter 48px drop — layout()'s portrait spStand/spGap are the same 12s */
#frame[data-port] .sp{padding:6px 13px;border-width:2.5px;
  bottom:calc(50% + var(--bs)/2 + 12px);font-size:max(13px, calc(var(--bs)*.27))}
#frame[data-port] .sp::after{width:13px;height:13px;border-width:2.5px;top:calc(100% - 6px)}


/* ---- DENSITY: what the small frames do differently ----------------------
   THIS FILE WAS 60-ledger.css AND IT IS THE SAME FILE, RENAMED. Round 3, the
   owner: "remove the session data, wagered, rolls tweats etc..." — every
   #ledger rule and every [data-led] selector left with the node. What stayed
   is what was always the larger half of the file and never had anything to do
   with the ledger: the [data-tiny] / [data-narrow] rungs for .sf, #stats,
   .dial, #modes, #chipn, #chiplane, #tabs, .amt, .qb, #balzone,
   #bal, #capline and #cta. A file called 60-ledger.css holding no ledger is a
   lie, so it is called what it is.

   THE LEADING NUMBER DOES NOT MOVE, and that is the point of renaming rather
   than relocating: css/ is concatenated in filename order (build-game.mjs),
   so the numbers ARE the cascade — 60-density sorts in exactly the slot
   60-ledger did, between 50-readouts.css and 70-dock.css, and every rule
   below keeps the byte-identical cascade position it had. */
/* THE BOARD'S TOP LINE IS DELETED, NOT RE-GATED. The rule that stood here —
   #frame:not([data-room="full"]) #boardtop{display:none}, itself the third
   gate the record line wore ([data-led="pill"], then [data-tiny], then
   data-room) — is gone WITH the line: the owner's live-test mark over the
   mode rail ("random writting tab appearing") retired the strip at every
   room, so a rule hiding it at some of them has nothing left to hide. The
   sentence this file already carried is now the whole design: the record
   itself is one press away behind the first door at every size. See
   src/index.html at #play for the full ruling. */
/* ---- WF-UI: THE CONSOLE'S DENSITY LADDER (UI-CHOICE §Density story) -----
   Height and width are SEPARATE questions and stay separate: data-room
   steps the bar's HEIGHT (--barh 60/52) and the dock's floorpad; data-tiny
   and data-narrow step the WIDTHS and faces (--stakefs 22/19/15, --qbw
   44/40/32, --ctafs 17/15/13) — --qbw is keyed on width flags ONLY, never
   on data-room. 720x405 (tiny, not narrow) keeps the full grammar at the
   19px scale with the commit in the bar; 380x300 (narrow) sheds the field
   caption (the 15px mark stays as the field's identity — never ellipsize
   to a lone letter), sheds PRESS SPACE, and drops the commit to its own
   full-width row (VERDICT collision 5, below). The rungs sit in THIS file
   (before 70-dock in the cascade) and win by specificity — the ids are the
   rung's gate, not its position. */
#frame[data-room="short"] .hzBar,
#frame[data-room="min"] .hzBar{--barh:52px}
#frame[data-tiny] .hzBar{--stakefs:19px;--qbw:40px;--ctafs:15px}
#frame[data-tiny] #bal{font-size:13.5px}
/* VERDICT COLLISION 5, BINDING — at the docked minimum the commit LEAVES
   the bar for its own full-width row: all three competition finals showed
   MAX abutting ROLL with no gutter, the money mis-tap the producer ruled
   against. A 340x30 commit is a larger target than the shipped 104x92
   block. The refinement round priced the second row honestly — field 36 +
   commit 30 + 4px row-gap, repaid by the narrow dock's own rhythm
   (70-dock's floorpad 5/gap 4, the 9.5px tabs, #betMsg:empty leaving its
   row) — because iter1's 44px field over a 40px commit put Between's
   second readout row into rail 1. */
#frame[data-narrow] .hzBar{--stakefs:15px;--qbw:32px;--ctafs:13px;--barh:36px;
  grid-template-columns:100%;height:auto;row-gap:4px}
#frame[data-narrow] .hzField{height:var(--barh,36px);
  border-right:1px solid var(--hair);border-radius:12px}
#frame[data-narrow] .hzBar #cta{grid-column:1;grid-row:2;height:30px;border-radius:12px}
#frame[data-narrow] .hzBar #cta .s{font-size:9.5px}
#frame[data-narrow] .hzCap{display:none} /* the caption sheds, never MAX */
#frame[data-narrow] .hzMk{width:15px;height:15px}
#frame[data-narrow] .qb{height:26px;padding:0 5px;font-size:10px}
#frame[data-narrow] #tabs button{font-size:9.5px;padding-bottom:4px}
#frame[data-narrow] #bal{font-size:12.5px}
#frame[data-narrow] .hzSpace{display:none}
/* graft C2: rail 2 sheds WHOLE at the docked minimum's height — the one
   frame class short enough (≤330px) is also bled, so the viewport IS the
   frame and a media query cannot disagree with layout() about it.
   #dock's id, NOT bare .hzRail2: this file precedes 70-dock.css, whose
   base .hzRail2{display:flex} would win a specificity tie by order alone —
   the exact cascade-shadowing class the old tiny rungs died of. */
@media (max-height:330px){ #dock .hzRail2{display:none}
  /* casino-manager MAJOR (scale round): the AT-CAP truth outranks the shed.
     Item 5 hid the max-bet line at rest and surfaces it only ON the
     ceiling — but this height gate deleted the whole rail regardless of
     state, so at 380x300 (the frame the MAX ruling was written FOR) a
     capped player got no explanation at all. The rail returns for exactly
     the at-cap state; the idle hint and the running readout stay shed,
     which is what graft C2 actually priced. */
  #dock .hzRail2:has(#capline[data-at]){display:flex} }
/* EVERY .sf / #stats / .dial RUNG IS DELETED (collar round, 2026-08-27) —
   the readout row and the odds dial left the board for the collar and the
   end-stop etch (50-readouts.css carries the ruling), so a density ladder
   for cards that no longer exist would be exactly the stale-selector
   defect class §13 of the spec tallies. What replaced them needs almost
   nothing from this file: the satellite's face and the etch's face are
   layout()-solved from the drop's own diameter (--orbfs/--mrfs), which
   already steps with the frame, and the one genuine density decision —
   the WIN CHANCE caption shedding at the docked minimum — lives with its
   own rule in 50-readouts.css. The docked-minimum From/To story ends the
   same way it was heading: the cards are gone at EVERY room now, the
   balls drag, the hv values print the edges, and mult/chance still
   reprice the band. */
#frame[data-tiny] #modes{padding:2px;border-radius:999px}  /* goo r6 task 4a: the pill stays a pill at the dense rung */
/* task 9: the rail's own [data-tiny] sizing (was 30px, a pre-cascade leftover
   from before the rail's three-rung ladder existed) moved to 10-chrome.css,
   which is the rail's one file — this sheet was a second, later-cascading
   opinion on the same buttons and it was winning by source order alone. */
/* THE TICK RETURNS AT THE SMALL FRAMES (fix round M2b). The rule that stood
   here — #frame[data-tiny] #chipn{display:none} — reasoned "the number IS
   the marker, its tick a second pointer at the same position", and that
   premise broke the day the chip's translate was clamped: at the rail ends
   the figure drifts off the pixel it names by up to 7.7 track units at
   380/720 (the clamp biting), which is precisely where the small frames
   live and precisely the job the tick was kept for (verdict graft 11).
   40-track.css now counter-translates #chipn onto the TRUE --mkpx, so the
   tick is not a second pointer at the same position — it is a pointer at
   the RIGHT position whenever the clamp bites. Division of labour, stated
   honestly (producer audit): on a WIN the landing is inside the paying
   region, where the hung tick sits BEHIND the rope (later positioned
   sibling) in the rope's own gold — there #wigland's highlight is what
   marks the landing; the tick's visible work at tiny is the LOSS (a
   landing over empty track) and the decayed echo. At tiny it hangs
   OUT OF FLOW below the chip (the rule under this comment): charging its
   9+2px to the lane instead was measured to cost the 720x405 track box its
   42px (35, under check §23's 40px floor) and to demote 900x430's Under
   room a rung — hung absolute, it overlaps the track box's own empty upper
   band, where the settled bead it points at already sits, and the lane
   budget (tickalw 4) stays exactly what the flow holds.

   task 5 (kept): the size override that also sat here is long removed.
   It shrank the OLD hollow-ring .grip to an 11x22 tap target at this frame.
   The goo grip is a transparent 60x60 box standing in for the blob under it
   ("the box must match the blob's or the player grabs at a place the blob is
   not" — 40-track.css), so re-shrinking it here would put the drag target
   out of register with what the player sees. */
#frame[data-tiny] #chipn{position:absolute;top:calc(100% + 1px);left:50%;margin-left:-1px;height:8px}
/* #frame[data-tiny] #chiplane{height:calc(var(--num) + 4px)} IS DELETED —
   the lane's extra is layout()'s --lanex now (40-track.css), which already
   carries the 4-instead-of-14 tick allowance at tiny AND the settled WIN
   sub-line the constant never budgeted; a second fixed height here would
   re-open the win-collides-with-modes defect the variable exists to close. */
/* #frame[data-tiny] #dock{gap:5px;padding:7px} IS DELETED, not retired —
   same cascade-shadowing class as above: tiny is only ever true alongside
   lay="stack" (both of tiny's own conditions, w<470 and !flank&&h<520,
   require !flank), so this selector's match set is always a subset of
   `#frame[data-lay="stack"] #dock`, which 70-dock.css declares later
   (padding:9px at :16, gap:7px;padding:9px at :82) at equal specificity —
   this rule's gap:5px/padding:7px never once painted. */
/* #frame[data-tiny] #dockhead{margin-bottom:6px} IS DELETED WITH ITS NODE —
   the chrome never reparents into the console any more (owner: "settings
   should be always on the top right"), so #dockhead left index.html and a
   margin on a node that does not exist is not a rule. */
/* THE OLD CONSOLE'S TINY RUNGS (#tabs pill sizes, .amt/.qb trims, the
   balance/capline/cta face steps, and the MAX-restoration note that lived
   here) ARE DELETED WITH THE CONSOLE THEY SIZED — the wf-ui rewrite's
   ladder at the top of this file is the one opinion now, keyed on the same
   flags. MAX's restoration itself is permanent (owner ruling, restated in
   70-dock.css at the .qb rule): the key exists at every width and the
   caption is what sheds. */

/* ==========================================================================
   3. THE CONSOLE — three rungs on the ground. WF-UI (work/dice/gauntlet/
   UI-CHOICE.md): the A-horizon winner with grafts B1–B4/C1–C2, replacing
   the two-density console (the 520px "wide" stack / 460px "bar" grid) this
   file held before. WHAT THE REWRITE REPLACED, so the history is not lost
   with the rules:

   - THE 520/460 WIDTH LITERALS ARE DEAD, and "one measure" is why: the
     console now sits on #play's OWN width formula, copied verbatim from
     20-stage.css's #play rule — min(var(--playw), 100% - 2*var(--bpad)) —
     so the two objects share their left and right verticals by
     construction and cannot drift a pixel apart. The 460 was solved
     against a 380px frame's 96%; the 520 was candidate A's approved stack
     measure; both described consoles that no longer exist.
   - data-lay ("wide"/"bar") HAS NO READERS LEFT and layout() no longer
     writes it. The zone wrappers .dkMid/.dkFoot left index.html with the
     stack; .dkTop survives as rail 1's row.
   - THE OLD RUNGS ARE DELETED, NOT OVERRIDDEN — a shipped
     #frame[data-lay="wide"] #balzone .k outranks any class this file could
     write, which is exactly how the first cut of this console would have
     rotted.
   - NO !important ANYWHERE IN THIS FILE: 95-motion.css's reduced-motion
     overrides must stay able to win by cascade order.

   The rungs:
     rail 1   BALANCE $x [delta] ················ MANUAL AUTO
     bar      ⊛ BET AMOUNT ····· $ 1.00 ½ 2× MAX ┃ ROLL / $1.00
     rail 2   MAX BET $x [lever] ················ PRESS [SPACE] / Running…

   The depth budget, stated once (UI-CHOICE §3): the COMMIT and the MODE
   THUMB (80-auto.css) are the only money-material objects, the stake WELL
   is the only inset, everything else is flat. */
/* THE CONSOLE IS A CARD NOW — the owner's third ruling this round: "We can
   have centered with the UI organized in a container." The horizon it
   used to stand on is retired (20-stage.css), so the console gets the
   bounded ground the frame's foot used to lend it: BirdCrash's borderless
   panel grammar (its history chip, linear-gradient(180deg, rgba(11,12,16,
   .9), rgba(20,22,29,.72)), r12 — the house precedent for a gradient
   ground with no stroke), on the game's own measure, centred under the
   game, part of the centred unit. The catch-light chamfer (graft B1) rides
   the card's top edge now — the same inset stroke the horizon carried.
   The INTERNALS are the approved wf-ui console, untouched: the card is a
   ground under them, not a redesign. The game above stays on open sky —
   the frame remains the game's one container.
   --floorpad, the token that was the console's standoff from the horizon,
   is the card's padding now — same density rungs, same writer. */
#dock{min-width:0;min-height:0;position:relative;grid-area:dock;
  /* ONE MEASURE — 20-stage.css:102's formula, copied not paraphrased */
  width:min(var(--playw,760px), 100% - 2*var(--bpad,22px));
  justify-self:center;
  display:grid;grid-template-columns:100%;grid-auto-rows:auto;
  gap:calc(9px*var(--scc,1));align-content:start;
  padding:calc(var(--floorpad,20px)*var(--scc,1)) calc(var(--floorpad,20px)*var(--scc,1)*1.1)
          calc(var(--floorpad,20px)*var(--scc,1)*.9);
  background:linear-gradient(180deg, rgba(20,22,29,.78), rgba(11,12,16,.92));
  border:0;border-radius:calc(16px*var(--scc,1));
  box-shadow:inset 0 1px 0 rgba(255,255,255,.045), 0 18px 44px -18px rgba(0,0,0,.75)}
#frame[data-room="short"] #dock,
#frame[data-room="min"] #dock{--floorpad:14px}
/* the narrow dock repays collision 5's two-row bar (refinement round):
   floorpad 5, gap 4 — with the density rungs in 60-density.css this lands
   the dock at ~100px, the winner's own 380x300 budget */
/* the card keeps its box at the docked minimum but not its full radius —
   16 on a ~110px card reads pill-cornered; 10 is BirdCrash's own small-
   panel step. Padding rides the same --floorpad rung (5px). */
#frame[data-narrow] #dock{--floorpad:5px;gap:4px;border-radius:10px}

/* ---- one caption face for every rail text (UI-CHOICE §3) ----------------
   All rail captions — balance, max bet, press-space, the running readout —
   wear one face, so the three rungs read as one instrument, not three
   opinions about what a label is. --off is #7E8498, the spec's own hex. */
#balzone .k,#capline,.hzSpace,.hzRun{
  font:600 calc(9.5px*var(--scc,1))/1 var(--ui);letter-spacing:.18em;
  text-transform:uppercase;color:var(--off)}

/* ---- rail 1: BALANCE ... MANUAL AUTO ------------------------------------ */
.dkTop{display:flex;align-items:baseline;justify-content:space-between;
  gap:14px;min-height:16px}
#frame[data-narrow] .dkTop{min-height:0}
#balzone{display:flex;align-items:baseline;gap:8px;min-width:0}
#balrow{display:flex;align-items:baseline;gap:8px;min-width:0}
/* 16px: the stake (22px) stays the console's largest figure — the number
   being RISKED outranks the number being held */
#bal{font:700 calc(16px*var(--scc,1))/1.1 var(--fig);font-variant-numeric:tabular-nums;color:var(--ink);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
#dl{font:700 calc(11.5px*var(--scc,1))/1 var(--fig);font-variant-numeric:tabular-nums;opacity:0;
  transition:opacity .2s;white-space:nowrap}
#dl[data-s="up"]{color:var(--win);opacity:1}
#dl[data-s="dn"]{color:var(--loss);opacity:1}
/* R1 item 2 (producer VERDICT): the delta is SUPPRESSED while a roll is in
   flight — settle() is #dl's one writer, so mid-flight the only thing a
   prior round's chip could do here is sit, tenseless, beside a live
   balance. data-rolling leaves on the same synchronous frame settle()
   writes the new delta, so the settle frame brings it back with fresh
   text. (Carried through the wf-ui rewrite verbatim.) */
#frame[data-rolling] #dl{opacity:0}

/* THE TABS ARE WORDS, NOT A PILL — app-mode never wears the money material
   (UI-CHOICE §3): bare uppercase words, the active one white with a 2px
   red underline that springs in. The red segmented pill on the board is a
   BET parameter and keeps its fill; these switch the console's mode and
   stay flat. */
#tabs{display:flex;gap:calc(18px*var(--scc,1));align-self:baseline;
  background:none;border:0;padding:0}
#tabs button{position:relative;padding:0 0 5px;border-radius:0;
  font:700 calc(10.5px*var(--scc,1))/1 var(--ui);letter-spacing:.14em;text-transform:uppercase;
  color:#767C90;transition:color .16s}
#tabs button::after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;
  border-radius:2px;background:var(--cp-red);box-shadow:0 0 8px rgba(192,24,12,.55);
  transform:scaleX(0);transform-origin:left;
  transition:transform .22s cubic-bezier(.3,1.35,.4,1)}
@media (hover:hover){#tabs button:hover{color:#C8CDDA}}
#tabs button[data-on]{color:#fff}
#tabs button[data-on]::after{transform:scaleX(1)}

/* ---- the bar: the well and the commit, ONE welded object ----------------
   Two cells, both PINNED (graft B4 — B's iter1 lost its CTA to an implicit
   row through a stray grid-area). The field carries no right border and
   the commit squares its left corners: the seam is the weld. The bar's
   own numbers (--barh/--stakefs/--qbw/--ctafs) are declared here at the
   designed size; 60-density.css steps them per data-room (height) and
   data-tiny/data-narrow (width) — never conflated. */
.hzBar{display:grid;grid-template-columns:minmax(0,1fr) clamp(112px,36%,300px);
  height:var(--barh,60px);
  --barh:calc(60px*var(--scc,1));--stakefs:calc(22px*var(--scc,1));--qbw:calc(44px*var(--scc,1));--ctafs:calc(17px*var(--scc,1))}

/* THE FIELD — graft B2, the inset well: "a slot you put money in". The two
   blacks are tokens (--well-hi/--well-lo, 00-tokens.css); background-color
   backs the gradient so the surface never reads transparent to a probe or
   a slow first paint. */
.hzField{grid-column:1;grid-row:1;display:flex;align-items:center;gap:10px;
  min-width:0;height:100%;
  background-color:var(--well-hi);
  background-image:linear-gradient(180deg,var(--well-hi),var(--well-lo));
  border:1px solid var(--hair);border-right:0;
  border-radius:calc(14px*var(--scc,1)) 0 0 calc(14px*var(--scc,1));
  box-shadow:inset 0 2px 6px rgba(0,0,0,.55);
  padding:0 10px 0 14px}
.hzField:focus-within{border-color:#3A3F50}
/* the mark: the house glyph in the house red, the field's identity — and
   at data-narrow, where the caption sheds, its ONLY identity */
.hzMk{width:calc(18px*var(--scc,1));height:calc(18px*var(--scc,1));flex:none;color:var(--cp-red);
  filter:drop-shadow(0 0 6px rgba(192,24,12,.5))}
/* the slack lives in the CAPTION (flex:1), so the figure sits hard against
   the keys; ellipsis is a safety net the shed rung should always beat —
   never render "B" (A's iter1 lesson: shed whole, don't ellipsize) */
.hzCap{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;
  font:600 calc(9.5px*var(--scc,1))/1 var(--ui);letter-spacing:.18em;
  text-transform:uppercase;color:var(--off)}
.hzMoney{display:flex;align-items:baseline;gap:4px;flex:none;min-width:0}
.hzMoney .pfx{font:600 calc(var(--stakefs,22px)*.68)/1 var(--fig);color:#767C90;flex:none}
/* the input's width is the ghost span's (game.js sizeStake) — value-sized,
   so the well's dead space is all in the caption's cell */
#betInput{background:none;border:0;padding:0;min-width:0;text-align:right;
  font:700 var(--stakefs,22px)/1 var(--fig);
  font-variant-numeric:tabular-nums;color:var(--ink)}
#betInput:focus{outline:none;color:#fff}

/* quick keys: HAIRLINE, no fills — VERDICT Round 3 ruling ("three more
   filled keys next to a filled field is four slabs"); B's relief keys were
   rejected with B's own pre-authorised fallback */
.hzKeys{display:flex;gap:5px;flex:none;margin-left:8px}
.qb{flex:none;height:calc(28px*var(--scc,1));min-width:var(--qbw,44px);padding:0 8px;
  border-radius:9px;background:none;border:1px solid #262B37;
  font:600 calc(11px*var(--scc,1))/1 var(--ui);color:#98A0B4;
  transition:background .14s,color .14s}
@media (hover:hover){.qb:hover{background:rgba(255,255,255,.08);color:#fff}}
/* MAX AT THE DOCKED MINIMUM IS AN OWNER RULING — the key is present at
   every width; what sheds at data-narrow is the caption, never a key
   (60-density.css). */

/* THE COMMIT — v1's money material re-tokened (UI-CHOICE §3): the accent
   gradient, an ACCENT bloom (never black — a black shadow under the only
   red mass reads as a hole in the ground), 1px sink + Plinko's release
   spring. Material and type live on #cta ITSELF so 95-motion.css's
   reduced-motion override (#cta{transition:none}) still outranks the
   transition by cascade order; only the weld geometry needs the .hzBar
   scope. */
#cta{position:relative;height:100%;min-height:0;overflow:visible;
  /* solid accent UNDER the gradient — the slab never reads transparent to
     a slow first paint or a background-color probe (§15's material check) */
  background-color:var(--acc);
  background-image:linear-gradient(180deg,var(--acc),var(--acc2));
  box-shadow:0 8px 24px color-mix(in srgb, var(--acc) 30%, transparent);
  display:grid;justify-items:center;align-content:center;gap:2px;
  transition:transform .19s cubic-bezier(.34,1.56,.64,1), filter .15s}
.hzBar #cta{grid-column:2;grid-row:1;
  border-radius:0 calc(14px*var(--scc,1)) calc(14px*var(--scc,1)) 0}
#cta .t{font:800 var(--ctafs,17px)/1 var(--ui);letter-spacing:.12em;
  text-transform:uppercase;color:var(--icoin)}
/* #FFD9D5, NOT rgba(255,255,255,.72) — Mines measured the translucent white
   at 3.75:1 on this red, under the floor, on the line that states the
   price. (Carried from the pre-wf console verbatim.) */
#cta .s{font:700 calc(11px*var(--scc,1))/1 var(--fig);font-variant-numeric:tabular-nums;
  color:#FFD9D5;margin-top:0}
@media (hover:hover){#cta:hover{filter:brightness(1.06)}}
/* the acknowledged click: a 1px sink, and the spring above plays the
   release — §4's first 150ms is this plus the balance moving */
#cta[data-press],#cta:active{transform:translateY(1px)}
/* DISABLED, not merely styled (the CTA's own rule since round 1): the
   control is really disabled while rolling; the look is drained saturation
   over the SAME material, bloom off — a dead light, not a different lamp */
#cta:disabled{filter:saturate(.4) brightness(.6);cursor:not-allowed;box-shadow:none}
/* data-stop (a live STOP during an autobet run) wears the SAME money
   material — the approved auto-running render shows the red slab reading
   STOP / $1.00. The old hollow --e3 treatment died with the rewrite: a
   run's one live control should be the loudest object on the console, not
   the quietest. */

/* ---- the R1 MONEY LOCK (VERDICT R1 item 1 / UI-CHOICE graft C1) ---------
   Carried through the rewrite with its selector contract intact: the
   functional half is real `disabled` attributes written by paint(); this
   is the visible half, keyed on the same data-money attribute paint()
   writes, so lock and dim can never disagree about when money is live.
   .45 — one rung dimmer than the mode rail's .55: the rail is aim, this
   is money. #stakefld is the well itself now; the dim covers the mark,
   caption, figure and keys in one stroke. */
#stakefld,#tabs{transition:opacity .18s ease}
#app[data-money="live"] #stakefld,
#app[data-money="live"] #tabs{opacity:.45;pointer-events:none}

/* ---- rail 2: MAX BET [lever] ... PRESS SPACE / Running ------------------ */
.hzRail2{display:flex;align-items:baseline;justify-content:space-between;
  gap:14px;min-height:calc(12px*var(--scc,1))}
/* with the capline resting hidden, the hint (and the running readout that
   takes its slot) holds the rail's right edge on its own */
.hzSpace,.hzRun{margin-left:auto}
/* the cap line — §3 of the spec: the ceiling is PRINTED, the stake is
   CLAMPED, the payout is never truncated; at the ceiling the line adds
   Shuffle's exact lever. The lever keeps its sentence case inside the
   uppercase rail: it is a sentence, not a caption. */
/* ITEM 5 — owner: "Max bet can be removed from here." The LINE leaves the
   rail at rest; the CLAMP it printed is untouched (setStake still clamps,
   diag-dice-math's ceiling assertions still hold) and the truth still
   surfaces where it matters: paint()'s existing data-at write brings the
   line back — ceiling figure and Shuffle's lever — for exactly as long
   as the player is ON the ceiling. At rest, rail 2 is the SPACE hint
   alone, kept to the right edge it always held. */
#capline{display:none}
#capline[data-at]{display:flex;align-items:baseline;gap:12px;min-width:0}
#capline b{font:700 calc(11px*var(--scc,1))/1 var(--fig);font-variant-numeric:tabular-nums;
  color:#8A91A4;margin-left:4px}
#capline[data-at]{color:var(--acc2)}
#capline[data-at] b{color:var(--acc)}
#caplever{display:none;text-transform:none;letter-spacing:.02em;
  font-weight:500;color:#8A91A4;
  overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
#capline[data-at] #caplever{display:inline}
/* the SPACE hint left the commit's own face for this rail (the welded cell
   centres its two lines; a pinned kbd landed on them) — out of the red,
   where a hint belongs */
.hzSpace{flex:none;display:flex;align-items:baseline;gap:6px}
/* the kbd chip's padded box must NOT set rail 2's height: negative
   vertical margins collapse its outer box back to the text line, so the
   rail is the same height with the hint (idle), with the running readout
   (auto), and with neither (mid-roll) — the dock-height-constant gate
   measures exactly this */
.hzSpace kbd{font:600 calc(9px*var(--scc,1))/1 var(--ui);letter-spacing:.08em;color:#98A0B4;
  padding:2px 5px;margin:-3px 0;border:1px solid #2A2F3B;border-radius:4px}
/* .hzRun (the running readout) is 80-auto.css's — it swaps in over
   .hzSpace only while a run is live, and that file owns the run states */

/* THE STATUS LINE IS AN OVERLAY ON RAIL 2, NEVER A ROW (owner, 2026-09-02:
   "Not enough balance moves the UI up, which is horrible practice"). As a
   grid row that left when empty (the refinement round's gap saving) it
   moved the board 6-16px at every frame the moment it spoke — 24px of
   dock at 390x844. Rail 2 is the same 12px at every state (the kbd chip's
   negative margins exist to keep it so), so the message paints over it
   and the rail's own content — the cap line, the SPACE hint, the running
   readout — goes invisible for as long as the message has text: the same
   family of stake fact, one slot. line-height 1 so 10.5px sits inside the
   rail's 12px; nothing here can change a box. At max-height<=330 the rail
   itself is shed (60-density) and the message goes with it, where it was
   already a clipped 6px sliver — the dimmed commit is the signal there. */
.hzRail2{position:relative}
#betMsg{position:absolute;left:0;right:0;top:0;bottom:0;margin:0;display:none;
  font:600 10.5px/1 var(--ui);color:var(--cp-red-hi);white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}
#betMsg:not(:empty){display:flex;align-items:baseline}
.hzRail2:has(#betMsg:not(:empty)) > :not(#betMsg){visibility:hidden}

/* ==========================================================================
   TOUCH, WHEREVER THE POINTER IS COARSE (mobile round, 2026-08-27) — the
   landscape phone and the tablet, which data-port does not cover. Only
   what is FREE: 844x390 has ~10px of vertical slack and every pixel spent
   there comes straight off a 34px result figure, so nothing here may make
   a row taller. The quick keys grow INSIDE the 52px bar they already sit
   in. */
#frame[data-touch] .qb{height:44px;min-width:44px}
/* THERE IS NO KEYBOARD IN THE PLAYER'S HAND: "PRESS SPACE" is a claim
   about hardware, gated on the pointer that proves otherwise — the same
   gate the Settings Hotkeys row takes in game.js's openDoor('set'). */
#frame[data-touch] .hzSpace{display:none}

/* PORTRAIT — THE CONSOLE IS THE THUMB BAR. Everything below the waterline
   (20-stage.css) reaches the 44px floor; the faces ride --scp (type and
   air follow the frame's height, the touch floors never do — 44px is a
   property of the hand, not of the window). Portrait phones are
   data-narrow/data-tiny by width, so these rules sit after 60-density's
   rungs in the cascade (later file) and win by order at equal
   specificity. */
#frame[data-port] #dock{--floorpad:12px;gap:calc(10px*var(--scp,1));border-radius:18px}
/* RAIL 1 IS SOLVED, NOT GUESSED. At 390x844 the dock's inner measure is
   315.6px (playw 342 less 2x13.2 of floorpad). v2 put 164 of balance
   beside 153 of tabs with a 10px gap = 327, and #bal's own
   overflow:hidden/text-overflow:ellipsis ate it silently: the render read
   "BALANCE $1,000...." and on a settled win "BALANCE $1,... +$1.14". The
   budget below is 6 + 85 (balance) + 52 (delta) + 10 + 132 (tabs) = 285
   against 315.6. */
#frame[data-port] .dkTop{gap:10px;min-height:44px;align-items:center}
/* THE BALANCE CAPTION IS BACK, STACKED — the owner, on the live phone
   build (2026-08-28 mobile polish): "it doesn't say balance." The mobile
   round shed the word on overflow arithmetic (66 BALANCE + 6 + 80 + 8 +
   52 + 10 + 145 = 367 against 315.6 of dock measure, eaten silently by
   #bal's ellipsis), and that arithmetic was true of the SINGLE-LINE rail;
   the restoration changes the geometry instead of re-entering the sum.
   BET AMOUNT's own caption grammar, turned vertical: the caption sits
   ABOVE the figure, and the DELTA rides the caption line (the audit's own
   named escape — #dl out of the figure's row), so the figure line carries
   the balance alone:
       BALANCE  -$1.00          MANUAL  AUTO
       $99,999.99
   Worst case is now max(caption row, figure) beside 132 of tabs — the
   $99,999.99-class figure (~105px at 18px) fits the 360x640 floor's
   ~285px measure with ~40px spare, where the single-line sum lost 10.
   CSS only, one DOM: #balrow lifts its children into the zone's grid
   (display:contents), the caption and the delta share row 1, the figure
   spans row 2. Costs NO dock height — the stack (~33px) sits inside the
   44px .dkTop cell the tab targets already hold open. check-dice §27e
   asserts the caption PRESENT and #bal's zero overflow with the delta up
   at the $99,999.99-class worst case. */
#frame[data-port] #balzone{display:grid;grid-template-columns:auto 1fr;
  gap:3px 8px;align-items:baseline}
#frame[data-port] #balzone .k{display:block;grid-row:1;grid-column:1}
#frame[data-port] #balrow{display:contents}
#frame[data-port] #bal{font-size:18px;grid-row:2;grid-column:1/-1}
#frame[data-port] #dl{font-size:12px;grid-row:1;grid-column:2;justify-self:start}
#frame[data-port] #tabs{gap:2px;align-self:stretch}
/* the tab is a 44px CELL with a 12px word centred in it — padding cannot
   reach 44 without pushing the underline eight pixels off the word.
   12px/8px, not 12.5/10: at 360x800 with the delta up, 146.1 of tabs
   squeezed #bal by a measured 9px (balScroll 9). 132 of tabs clears it
   by 16. */
#frame[data-port] #tabs button{font-size:12px;padding:0 8px;letter-spacing:.10em;
  min-height:44px;display:grid;place-items:center}
#frame[data-port] #tabs button::after{bottom:9px}
/* the welded bar at thumb size: the phone frames are all data-narrow, so
   the commit is already its own full-width row (60-density's collision-5
   rung) — these steps size that two-row form; the 768-class tablet is
   not narrow and keeps the one-row weld, whose cells fill their 60px row
   (height:100%), so the two height rules below gate on data-narrow to
   leave the weld's geometry alone. */
/* --stakefs 22, DOWN FROM 26 (portrait round 2, 2026-09-02): with the keys
   welded into the field there is no caption to absorb slack, and 26px put
   "$1,000.00" (~140px) into ~112px of room — the owner's render shows it
   cut. 22 seats $1,000.00 at every phone width beside three 44px keys and
   the mark; wider figures step the face down further in sizeStake(). */
#frame[data-port] .hzBar{--stakefs:22px;--qbw:44px;--ctafs:19px;--barh:60px}
/* the 10px gap belongs to the TWO-ROW bar only (every portrait phone is
   data-narrow): on the tablet's one-row weld a gap is a slit between two
   square edges — rendered at 768x1024 and scoped the same day */
#frame[data-port][data-narrow] .hzBar{gap:10px}
#frame[data-port][data-narrow] .hzField{height:60px;border-radius:14px}
/* THE QUICK KEYS ARE PLINKO'S WELDED TAIL (portrait round, 2026-09-02 —
   the owner: "the quick buttons for bet amounts look very weird, check
   the other games"). Three 48px hairline squares floating inside the
   field were boxes inside a box, and no sibling does it: Plinko welds
   ½ | 2× into the field's tail as divider cells, Mines runs a slab row
   under the field. This is Plinko's, with MAX kept (the owner's own
   ruling: the key is present at every width): divider cells at the
   field's end, full height, no fills — the "four slabs" ruling holds —
   and the value right-aligned hard against them with the mark at the
   left, the way Plinko's .amt reads. Portrait only: landscape keeps the
   hairline keys VERDICT round 3 ratified. */
#frame[data-port] .hzField{padding-right:0;overflow:hidden}
#frame[data-port] .hzMoney{flex:1 1 auto;justify-content:flex-end;min-width:0;padding-right:8px}
#frame[data-port] .hzKeys{margin-left:0;gap:0;align-self:stretch;height:100%}
/* 44 wide, not 54 (round 2 — the owner: "the quick buttons should be
   smaller"): the touch floor exactly, which §27b's census holds, and the
   30px the three cells give back is the value's. 12px face. */
#frame[data-port] .qb{height:100%;min-width:44px;padding:0 5px;border:0;border-left:1px solid var(--hair);
  border-radius:0;font:600 12px/1 var(--ui);color:#98A0B4}
#frame[data-port] .qb:hover{background:rgba(255,255,255,.06);color:#fff}
#frame[data-port][data-narrow] .hzBar #cta{height:56px;border-radius:14px}
#frame[data-port] .hzBar #cta .s{font-size:12.5px}
#frame[data-port] #capline{font-size:10.5px}
/* the port half of the keyboard-copy deletion: data-port does not imply a
   coarse pointer (a tall desktop window can trip the gate with a mouse),
   but the portrait composition has no rail-2 slot budgeted for the badge
   either way */
#frame[data-port] .hzSpace{display:none}


/* ---- the autobet panel --------------------------------------------------
   §8. The number of bets uses INFINITY AS ITS EMPTY STATE rather than a
   separate toggle, and every one of these fields is read at a round boundary
   — Mines shipped five of these as drawings with no listener once, and a
   player-set loss limit that silently does not apply is the worst thing a
   panel like this can contain. */
/* TASK 8: sliders, not a field grid - see index.html's #autopanel note for
   the owner's reference and what was kept, adapted and left out. The sliders
   are the GAME's own, styled for the sheet: the track is a hairline with the
   filled side in the house red, the thumb a plain white disc - no gradients,
   no glow, restraint is the house style. Two columns while the sheet has the
   width, one when it does not (auto-fit, not a viewport query: the sheet's
   width is the container that matters). */
#autopanel{display:grid;gap:16px}
#autopanel[hidden]{display:none}
.apGrid{display:grid;gap:18px 26px;grid-template-columns:repeat(auto-fit,minmax(230px,1fr))}
.apCol{display:grid;gap:16px;align-content:start;min-width:0}
.ap{display:grid;gap:8px;min-width:0}
.apCap{display:flex;align-items:baseline;justify-content:space-between;gap:10px}
.apCap .cap,.apField .cap{font:600 9px/1 var(--ui);letter-spacing:.11em;text-transform:uppercase;
  color:#666C7E;white-space:nowrap}
.apCap b{font:700 13px/1 var(--fig);font-variant-numeric:tabular-nums;color:var(--ink)}
input[type=range].apR,#autopanel input[type=range]{
  -webkit-appearance:none;appearance:none;width:100%;height:22px;background:none;margin:0}
#autopanel input[type=range]::-webkit-slider-runnable-track{
  height:4px;border-radius:2px;
  background:linear-gradient(90deg,var(--cp-red) 0%,var(--cp-red) var(--fill,0%),#262A35 var(--fill,0%))}
#autopanel input[type=range]::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;width:16px;height:16px;border-radius:50%;
  background:#fff;border:0;margin-top:-6px;box-shadow:0 1px 4px rgba(0,0,0,.5)}
#autopanel input[type=range]:focus{outline:none}
#autopanel input[type=range]:focus-visible::-webkit-slider-thumb{
  box-shadow:0 0 0 3px rgba(192,24,12,.45)}
#autopanel input[type=range]::-moz-range-track{height:4px;border-radius:2px;background:#262A35}
#autopanel input[type=range]::-moz-range-progress{height:4px;border-radius:2px;background:var(--cp-red)}
#autopanel input[type=range]::-moz-range-thumb{width:16px;height:16px;border-radius:50%;
  background:#fff;border:0;box-shadow:0 1px 4px rgba(0,0,0,.5)}
.apCheck{display:flex;align-items:center;gap:10px;min-height:22px;cursor:pointer}
.apCheck span{font:600 12px/1.2 var(--ui);color:var(--ink)}
.apCheck input{width:16px;height:16px;accent-color:var(--cp-red);cursor:pointer}
.apField{display:grid;gap:6px}
.apField input{width:100%;min-width:0;height:32px;padding:0 8px;background:#080A0D;
  border:1px solid var(--hair);border-radius:8px;
  font:600 13px/1 var(--fig);font-variant-numeric:tabular-nums;color:var(--ink)}
.apField input:focus{outline:none;border-color:#3A3F50;color:#fff}
.apField input::placeholder{color:#454B5B;font-family:var(--ui);font-size:12px}
#autorun{font:600 10.5px/1.4 var(--fig);font-variant-numeric:tabular-nums;color:#6C7386}
#autorun:empty{display:none}

/* ---- WF-UI: RAIL 2's RUNNING READOUT ------------------------------------
   "Running · n rolls · +$x" as TEXT — no progress bar, ever (an infinite
   run has no denominator). While money is live the PRESS SPACE hint yields
   its slot (Space is dead as a commit during a run anyway — onCta turns it
   into STOP, which the readout sits beside explaining). paint() writes the
   text and the data-live flag; the figure is gold ONLY in profit
   (refinement note 3) and --loss — repointed to #8E93A3 by the ember law,
   which made this line's own hex THE token: the readable ledger grey is
   what --loss means now (text only; the board's loss is --acc-out), so
   the one figure that already wore it reads it by name. */
.hzRun{display:none}
#app[data-money="live"] .hzSpace{display:none}
#app[data-money="live"] .hzRun[data-live]{display:flex;flex:none;align-items:baseline}
.hzRun b{font:inherit;font-weight:700;font-variant-numeric:tabular-nums;color:var(--loss)}
.hzRun b[data-up="1"]{color:var(--win)}

/* ---- ADVANCED: the martingale disclosure --------------------------------
   Collapsed by default; the sliders stay the panel's first read. .seg is the
   retired panel's own two-way pill, byte-for-byte (style.css:751-754 before
   the split), because the click delegation in game.js was written against
   exactly this shape. The % field only earns its ink when its own mode is
   "Increase by" — :has() on the sibling seg, the same selector idiom the
   track already uses for #bandA — so a Reset row reads as one decided
   control, not a control plus a stray number. */
#apAdv{border-top:1px solid var(--hair);padding-top:12px}
#apAdv summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:7px;
  font:600 9px/1 var(--ui);letter-spacing:.11em;text-transform:uppercase;color:#666C7E;
  transition:color .15s}
#apAdv summary::-webkit-details-marker{display:none}
#apAdv summary::before{content:'';width:0;height:0;
  border-left:4px solid currentColor;border-top:3.5px solid transparent;
  border-bottom:3.5px solid transparent;transition:transform .18s}
#apAdv[open] summary::before{transform:rotate(90deg)}
@media (hover:hover){#apAdv summary:hover{color:#C8CDDA}}
.apAdvBody{display:grid;gap:12px;padding-top:14px}
.apAdvRow{display:grid;grid-template-columns:52px minmax(0,1fr) 74px;
  align-items:center;gap:10px}
.apAdvRow > .cap{font:600 9px/1 var(--ui);letter-spacing:.11em;text-transform:uppercase;
  color:#666C7E;white-space:nowrap}
.seg{display:grid;grid-auto-flow:column;gap:3px;padding:3px;background:#080A0D;
  border:1px solid var(--hair);border-radius:8px}
.seg button{height:26px;border-radius:6px;font:600 11px/1 var(--ui);color:#767C90}
.seg button[data-on]{background:#20242E;color:#fff}
.apPct{display:flex;align-items:center;gap:5px;height:32px;padding:0 8px;
  background:#080A0D;border:1px solid var(--hair);border-radius:8px;min-width:0}
.apPct input{width:100%;min-width:0;background:none;border:0;padding:0;text-align:right;
  font:600 13px/1 var(--fig);font-variant-numeric:tabular-nums;color:var(--ink)}
.apPct input:focus{outline:none;color:#fff}
.apPct span{font:600 11px/1 var(--ui);color:#5E6476;flex:none}
.apAdvRow:has(.seg [data-v="reset"][data-on]) .apPct{opacity:.35}

/* ---- the mode selector, ON THE BOARD ------------------------------------
   §6 of the research: multi-band betting is the structural differentiator and
   it is crypto-only — Roobet, Duelbits, wolf.bet and Windice all ship it and
   every regulated slider is strictly two-sided. The control sits above the
   track and not in the dock because the mode is what the track DRAWS: it
   changes the picture directly under it, and a control that repaints the
   object beside it belongs next to that object.
   ALL FOUR PRICE IDENTICALLY. Switching mode preserves the win chance
   exactly, so the multiplier does not move — Roobet's choice and the right
   one. That is asserted in scripts/diag-dice-math.mjs. */
/* FIVE OBJECTS ON FOUR AXES was the composition finding, and this is one of
   them: the rail was shrink-to-fit and centred at 316px inside a 760px group,
   agreeing with nothing above or below it. Stretched to the group it becomes a
   fourth object on the same two verticals, and four equal columns give the
   four modes the equal weight their identical price already claims. */
/* TASK (r2) 4: THE OWNER'S OWN WORDS — "Radio button on v1 is better, color
   red etc." v1's direction control is a two-segment pill with a sliding
   background and the ACTIVE segment filled in the house red gradient over
   dark ink; ours was four flat, low-contrast tabs. Adapted rather than
   copied straight: v1 only ever has two segments (Over/Under), this rail
   has four (Under/Over/Between/Outside) and stays four — the fill treatment
   and the sliding indicator are what transfer, not the segment count.
   #modes .segbg IS THE SAME MECHANISM v1's .segbg is (a sibling element
   sliding under the buttons), sized and positioned in CSS to match this
   rail's own 4-up grid rather than v1's 2-up one — see game.js's mode-rail
   IIFE (builds it) and paint() (drives --idx, the one number both the
   pill's position and this file's math read). */
/* GOO R6 TASK 4a: THE PILL IS v1's PILL. The owner pointed back at v1's
   selector ("the selector for under/above is also better"); measured against
   ours, the MECHANISM was already v1's (this .segbg, the .34s
   cubic-bezier(.3,1.35,.4,1) spring, the accent gradient, the label flip —
   all ported in r2 task 4), so what actually differed is the GEOMETRY and
   the MATERIAL: v1's track is a borderless pill (980px radii) in
   rgba(255,255,255,.06), ours was an 11px-radius bordered box. Adopted:
   999px radii on track/pill/buttons, v1's translucent track (the hairline
   border goes — v1's control is cut out of light, not drawn in line), and
   the slide moves to TRANSFORM, v1's own property, which is also this
   file's rule for anything that moves (the compositor, not layout). */
/* THE FILL IS THE ACCENT, THE INK IS WHITE — the owner corrected the last
   round's reading of their mark: "I meant the text to be white the button
   should be red." The white-pill detour lasted one round; what their mark
   was actually pointing at was the ACTIVE LABEL's #2B0805 ink, unreadable
   on the red fill. Flat fill (no gradient, no glow — the mechanism carries
   no shadow), white ink. Both colours are 00-tokens.css's --pill/--pill-ink
   — one writer, no hex here. Everything else about the control — the
   spring, the transform slide, the .segclip cap window, the four-segment
   geometry — is untouched. */
/* collar round: justify-self:stretch left with the rail's old home — as a
   #play row it stretched to the group; as the board's own row (20-stage)
   it CARRIES the group's measure itself and centres. Position is
   20-stage's business now; everything below is still the control's own. */
#modes{position:relative;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:4px;padding:4px;
  background:rgba(255,255,255,.06);border-radius:999px;
  max-width:100%}
/* THE MATH, re-based for the transform slide: the pill sits at the first
   column (left:4px, width 25% - 5px — the 4px padding + 4px gap sum both
   cancel to that constant) and travels by its OWN width plus one gap per
   index: translateX reads % of the element itself, so 100% + 4px IS the
   column step (25% of track - 1px), algebraically, at every track width. */
/* THE OVERSHOOT IS CLIPPED AT THE TRACK'S OWN CAPS. The .34s spring below
   necessarily travels PAST its target before settling; between the inner
   segments the excess lands in the neighbour's gap and reads as bounce, but
   at the END columns there is no neighbour — the pill's cap painted up to
   ~19px past the track's rounded right edge on a click into Outside
   (measured at 1280×760; ~8px at 380×300), and symmetrically past the left
   edge arriving at Under from the right. .segclip is a window with the
   track's exact geometry (inset:0, the same 999px radii): the spring curve
   is untouched, the bounce still reads on the inner segments, and at the
   ends the escaping cap compresses into the track's own cap instead of
   painting outside it. A wrapper rather than overflow:hidden on #modes
   itself so the buttons' focus outlines (2px outside the button, flush
   with the track edge) stay unclipped. Built by the mode-rail IIFE in
   game.js; segbg's coordinates are unchanged — inset:0 spans the same
   containing block the pill measured against before. */
#modes .segclip{position:absolute;inset:0;border-radius:999px;overflow:hidden;
  z-index:0;pointer-events:none}
/* WF-UI GRAFT B3: THE THUMB WEARS THE MONEY MATERIAL — the second of
   exactly two money-material objects (the commit is the other; UI-CHOICE
   §3's depth budget). A bet-parameter segment may wear money per v1's own
   grammar; the flat --pill fill this replaces was the r2 correction's
   floor, and B's gradient+catch-light rendered better than A's plainer
   one, so B's recipe ships: the red's own hi/lo around the pill colour,
   a top catch-light, an accent bloom. background-color stays --pill so
   the token pair (--pill/--pill-ink, 00-tokens.css) is still the one
   writer of the pill's hue. */
#modes .segbg{position:absolute;top:4px;bottom:4px;left:4px;
  width:calc(25% - 5px);border-radius:999px;z-index:0;
  transform:translateX(calc(var(--idx,0) * (100% + 4px)));
  background-color:var(--pill);
  background-image:linear-gradient(180deg, var(--cp-red-hi) -40%, var(--pill) 45%, var(--cp-red-lo) 130%);
  box-shadow:0 3px 12px color-mix(in srgb, var(--cp-red) 40%, transparent),
             inset 0 1px 0 rgba(255,255,255,.22),
             inset 0 -1px 0 rgba(0,0,0,.25);
  transition:transform .34s cubic-bezier(.3,1.35,.4,1)}
#modes button{position:relative;z-index:1;height:calc(34px*var(--scc,1));padding:0 8px;border-radius:999px;
  font:600 calc(12.5px*var(--scc,1))/1 var(--ui);color:#767C90;white-space:nowrap;transition:color .16s}
@media (hover:hover){#modes button:hover{color:#C8CDDA}}
/* WHITE INK ON THE RED FILL — the owner's correction, verbatim: "the text
   to be white the button should be red." Read through --pill-ink so the
   pair can only ever move together. White on --cp-red measures ~5.9:1 —
   verified in the render, including mid-slide. */
#modes button[data-on]{color:var(--pill-ink);font-weight:800}
#frame[data-tiny] #modes button{height:21px;padding:0 6px;font-size:10.5px}
/* the tiny rung pads 2px (60-density), so the pill's rest position and
   width re-solve for it: col = 25% - 4px, and the step is again its own
   width + the 4px gap = 25% exactly. */
#frame[data-tiny] #modes .segbg{top:2px;bottom:2px;left:2px;width:calc(25% - 4px)}
/* R1 MONEY LOCK: the rail's dim now covers the AUTO run too, not only the
   in-flight roll — setMode has guarded S.rolling||S.auto since it was
   written, so between autobet rolls the rail was inert but looked live:
   a click did nothing and told the player nothing. data-money="live" is
   paint()'s exact spelling of that same guard's condition. */
#frame[data-rolling] #modes,
#app[data-money="live"] #modes{opacity:.55;pointer-events:none}

/* PORTRAIT (mobile round): the mode rail is FOUR THUMB TARGETS — the tiny
   rung's 21px buttons (portrait phones are all data-tiny by width) step to
   the 44px floor; the pill's rest position and width re-solve for the 3px
   padding exactly as the tiny rung re-solved for its 2px. After the tiny
   rules, same specificity: order wins. */
#frame[data-port] #modes{padding:3px}
#frame[data-port] #modes button{height:44px;padding:0 6px;font-size:13.5px}
/* 25% - 4.5px, the file's own algebra re-run for a 3px pad: col =
   (W - 2*3 - 3*4)/4 = 25% - 4.5px, and the step (own width + 4px gap)
   is again the column pitch exactly — the mock's -6px drifted the pill
   1.5px per index and sat it 4.5px shy of Outside's cap. */
#frame[data-port] #modes .segbg{top:3px;bottom:3px;left:3px;width:calc(25% - 4.5px)}


/* ==========================================================================
   4. THE SHEET — the shared component, driven exactly as Mines drives it:
   <dialog> WITHOUT showModal(), [open] plus .is-on, and .cpscrim inside the
   game's own frame. A modal dialog would claim the iframe's top layer and
   trap focus in a frame the player can tab out of anyway.
   ========================================================================== */
/* the .led six-tile ledger that opened the Last rounds door is gone (owner,
   2026-09-02: "remove the session figures from history") — histHTML() in
   game.js prints the rolls and nothing summed over them */
.rowlist{display:grid;gap:5px}
/* THE HEADER IS A ROW OF THE SAME TABLE -- same grid, same inset, same radius
   as the rows it names, on the darkest ground in the panel so those rows read
   as sitting on it. The tones are the ladder Plinko and Mines use:
   band #10131A < ball rows #1A1F29 < round #242A38 < pinned #2E3546. */
.rlh{display:grid;grid-template-columns:auto minmax(0,1fr) auto auto;gap:12px;
  align-items:center;padding:9px 11px;background:#10131A;border:1px solid #10131A;
  border-radius:9px;font:600 11px/1 var(--ui);color:#B9BFD0;
  position:sticky;top:0;z-index:2}
/* the labels take their columns' own widths and edges, or they hang off them */
.rlh .n{min-width:34px}
.rlh .b{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rlh .r,.rlh .p{text-align:right}
.rlh .p{min-width:64px}

.rl{display:grid;grid-template-columns:auto minmax(0,1fr) auto auto;gap:12px;align-items:center;
  padding:8px 11px;background:#242A38;border:1px solid #333B4D;border-radius:9px;
  font:600 12px/1 var(--fig);font-variant-numeric:tabular-nums}
/* a run's rolls sit below the run they belong to, as they do in the others */
.rl.sub{background:#1A1F29;border-color:#262D3B}
.rl.grp.open{background:#2E3546;border-color:#3D4658}
/* fixed width, or every row jogs sideways as the nonce crosses 10 and 100 */
.rl .n{color:#5C6274;font-size:11px;min-width:34px}
.rl .b{color:var(--cps-ink-dim,#767C90);font-family:var(--ui);font-size:11px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rl .r{color:var(--ink)}
/* TASK 7 fix-round: was --acc, same defect as 40-track.css's #chipv and
   30-history.css's .hp — found auditing for other instances of it once the
   coordinator's ruling named the pattern. This row's own .p two lines down
   was already --win; the roll figure beside it read red on the same won
   row, which is the identical "two verdicts at once" bug relocated into
   the fairness sheet's round list. */
.rl[data-w="1"] .r{color:var(--win)}
.rl[data-w="0"] .r{color:var(--loss)}
.rl .p{color:#8A91A4;min-width:64px;text-align:right}
.rl[data-w="1"] .p{color:var(--win)}
/* wf-anim §2.6: the NEWEST pill arrives with v1's hin pop. histHTML puts
   the class on the first (newest) pill only, so a reopened door animates
   one entrance, never the whole record at once. */
.rl.hinew{animation:hin .3s cubic-bezier(.2,1.4,.4,1)}
@keyframes hin{from{transform:scale(.3);opacity:0}}

/* the verifier, behind a disclosure closed by default — COMPONENTS.md is
   explicit that ninety-nine players out of a hundred open this panel to read
   or replace a seed, and the hundredth is the one who wants the arithmetic */
.pfMore{margin-top:14px;border-top:1px solid var(--cps-hair,#242731);padding-top:12px}
.pfMore summary{cursor:pointer;font:600 12px/1 var(--ui);color:var(--cps-ink-dim,#767C90);
  list-style:none;display:flex;align-items:center;gap:7px}
.pfMore summary::-webkit-details-marker{display:none}
.pfMore summary::before{content:"\203A";display:inline-block;transition:transform .16s;
  color:var(--cp-red-hi);font-size:15px}
.pfMore[open] summary::before{transform:rotate(90deg)}
@media (hover:hover){.pfMore summary:hover{color:var(--cps-ink,#E7E9F0)}}
.pfBody{margin-top:12px;display:grid;gap:11px}
.pfBody code.blk{display:block;padding:10px 12px;background:var(--cps-well,#07080B);
  border:1px solid var(--cps-hair,#242731);border-radius:9px;
  font:500 11px/1.7 ui-monospace,Menlo,Consolas,monospace;color:#9AA1B4;
  white-space:pre-wrap;word-break:break-all}
.vf{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:9px}
.vf label{display:grid;gap:4px;min-width:0}
.vf .cap{font:600 9px/1 var(--ui);letter-spacing:.11em;text-transform:uppercase;color:#666C7E}
.vf input{width:100%;min-width:0;height:32px;padding:0 9px;background:var(--cps-well,#07080B);
  border:1px solid var(--cps-hair,#242731);border-radius:8px;
  font:500 11.5px/1 ui-monospace,Menlo,Consolas,monospace;color:var(--cps-ink,#E7E9F0)}
.vf input:focus{outline:none;border-color:var(--cps-red-hi,#E0392B)}
#vout{font:600 12.5px/1.6 var(--ui);color:var(--cps-ink-mid,#A7ACBD)}
/* task 4 judgement call: this bold figure carries no [data-w]/[data-s] — it
   is a verifier readout, not a win/lose signal, so it fits neither this
   task's ACCENT nor WIN bucket. Left on --gold it would still be gold; put
   here on --ink (matching .kv b's own near-white treatment two rules below)
   rather than force it into a semantic it may not have. Flagged in the task
   4 report for a ruling. */
#vout b{font-family:var(--fig);font-variant-numeric:tabular-nums;color:var(--ink)}
.kv{display:grid;grid-template-columns:auto minmax(0,1fr);gap:6px 14px;
  font:600 12px/1.7 var(--ui);color:var(--cps-ink-mid,#A7ACBD)}
.kv b{font-family:var(--fig);font-variant-numeric:tabular-nums;color:var(--cps-ink,#E7E9F0);
  font-weight:700;justify-self:start}


/* ==========================================================================
   5. REDUCED MOTION — the whole travel is skipped, not slowed. The frame a
   player who asked for this gets is the FINISHED one: the result on the
   track, coloured, controls live. Never a mid-roll pose and never the same
   motion at half speed.
   ========================================================================== */
@media (prefers-reduced-motion:reduce){
  #chip{transition:none}
  #cta,#chipv,#chipn,#dl{transition:none}
  /* wf-ui: the console's new movers arrive placed — the active tab's
     underline (a scaleX spring) and the money lock's dim are state
     changes like the others in this block. This is why 70-dock.css bans
     !important: these override by cascade order and would silently die
     under one. */
  #tabs button::after{transition:none}
  #stakefld,#tabs{transition:none}
  /* wf-anim F6: the bead is placed at the answer as a finished circle —
     no gather fade, no colour bleed. travel()'s own short-circuit already
     puts it there with the verdict; this makes the arrival instant. The
     starfield's 60s/120s drift is a decorative loop and "still and
     finished, never a slower version of the same motion" applies to it
     too — a sky that never stops moving is not a "still" picture. */
  #bead{transition:none}
  .st1,.st2{animation:none}
  .sp{transition:none}
  /* the bubble's rock and its pop-in are decorative loops/entrances like
     the starfield's drift — still and finished, never slower */
  .sp,.sp.pop{animation:none}
  /* task 9: the decay's fade is a transition like the others - the result
     still clears after the hold (the JS skips straight to the cleared
     frame), it just never shows a half-faded pose */
  #frame[data-resfade] #chip,#frame[data-resfade] #bead{transition:none}
  /* task (r2) 4: the sliding pill is a position change like the others in
     this block — still and finished at the new mode, never a slide. */
  #modes .segbg{transition:none}
  /* goo r6 task 2: the celebration is expanding light and thrown matter —
     exactly the category this setting suppresses. What remains is Plinko's
     own reduced-motion answer: still and finished, not absence — the line,
     blob, bead and figure all arrive gold together (the dot stagger goes
     with the motion), and the WON line is placed, not popped. */
  #frame[data-res="win"] .cel i{animation:none}
  /* fix round M1: the dot stagger is deleted from 40-track (the wave moved
     to the unfiltered .wig layer), so the transition-delay:0s override that
     stood here had nothing left to override. The wave was then SUPPRESSED
     here like the celebration; the win round supersedes that answer — the
     inverted wave has a real finished state and arrives PLACED instead
     (the block below) — and the landing mark still ARRIVES PLACED (its
     opacity transitions zeroed in every state, its settled .96 kept —
     .92 until the win round grew it).
     SPECIFICITY IS MATCHED, NOT ASSUMED (producer audit, fix round): the
     first cut wrote `.wig i[data-v]` and bare `#wigland` here and BOTH
     LOST — 40-track's own selectors carry :not(.off) / a second id-level
     compound, a media query adds nothing, and the wave played at full
     strength under the very setting that asks for none (measured: peak
     opacity 1.0). Every selector below ties its 40-track counterpart
     exactly and wins by cascade order, which is 70-dock's whole rule
     about this block ("override by cascade order — no !important"). */
  /* WIN ROUND: the wave is inverted (it crosses the DEAD zone on the .off
     twins now) and under reduced motion it arrives PLACED AT ITS PEAK, not
     suppressed — W5's spec: the wave's finished state is "the dead zone is
     lit", so the twins hold the peak's own .55 for the verdict, still. The
     residue arrives placed the same way (transition:none = the final level
     in one frame). SPECIFICITY IS MATCHED, NOT ASSUMED — this file has
     shipped two dead RM overrides already; every selector below ties its
     40-track counterpart exactly and wins by cascade order. */
  #frame[data-res="win"] .wig i[data-v].off{
    animation:none;opacity:.55;transform:translate(-50%,-50%) scale(1)}
  /* the twins' .18s yield fade is motion too — placed means the .55 is on
     the FIRST frame, not eased into (the landing mark's own rule, applied
     to the lamps; without this the placed opacity reads 0 mid-transition) */
  .wig i[data-v]{transition:none}
  .dots2 .tick{transition:none}
  #frame[data-res="win"] .dots2 .tick.dry{transition:none}
  #frame[data-res="win"] #wigland,
  #frame[data-resfade] #wigland,
  #wigland{transition:none}
  /* ember law: the bank and the ignition are colour MOTION — under reduced
     motion the whole line arrives already in the verdict tone, one frame,
     no bleed (the same rule the bead's own transition:none above states).
     The win round's FAST bank restates the win clock at id+attr
     specificity, so the bare-class overrides here stopped reaching it —
     the win-scoped twins below tie those selectors exactly. */
  .blob,.band,.dot{transition:none}
  #frame[data-res="win"] .band,#frame[data-res="win"] .dot,
  #frame[data-res="win"] .blob,#frame[data-res="win"] #bead{transition:none}
  #chip[data-r="win"] #chipw:not(:empty){animation:none}
  /* the win figure's .10s colour clock and its nlandwin spring are motion
     like the rest — matched against 40-track's own win-scoped rule */
  #chip[data-r="win"] #chipv{animation:none;transition:none}
  /* the tier-4 ring outranks the blanket .cel i suppression above
     ((1,3,0) vs (1,2,1)) and must be silenced at its own weight */
  #frame[data-res="win"][data-mag="4"] .celR4{animation:none}
  /* THE ROOM ARRIVES LIT AND STILL (W4): only the clocks are removed —
     every transform is a declarative end state, so with transition:none
     the room is simply at its answer in one frame. The tier-4 dim the
     same way. */
  #roomwarm,#roomlit,#roomsky,#roomdim,
  #frame[data-res="win"][data-mag="2"] #roomwarm,
  #frame[data-res="win"][data-mag="3"] #roomwarm,
  #frame[data-res="win"][data-mag="4"] #roomwarm,
  #frame[data-res="win"][data-mag="2"] #roomlit,
  #frame[data-res="win"][data-mag="3"] #roomlit,
  #frame[data-res="win"][data-mag="3"] #roomsky,
  #frame[data-res="win"][data-mag="4"] #roomdim,
  #frame[data-resfade] #roomwarm,
  #frame[data-resfade] #roomlit,
  #frame[data-resfade] #roomsky,
  #frame[data-resfade] #roomdim{transition:none}
  #roomsky i{animation:none}
  /* wf-anim F6: the whole settle suite is still and finished — the figure
     placed (no nland), the icons still (no wob), the newest pill still
     (no hin). The struck-dot kick is element.animate and gates on
     reducedMotion() in game.js's strikeDot(). */
  #chip[data-r="win"] #chipv,#chip[data-r="lose"] #chipv{animation:none}
  .ico.wob{animation:none}
  /* the S1 acknowledge pulse is a courtesy, not information — reduced
     motion drops it entirely (the fix round's own spec: "reduced-motion:
     none"), and the tap still safely does nothing */
  .ico.beck{animation:none}
  #frame[data-tiny] .ico.beck{animation:none}
  .rl.hinew{animation:none}
  /* collar round: the collar's dims and the figures' focus clocks are state
     changes like the others here, and the last-rolls pill's hin pop is the
     same entrance the door pill's rule above suppresses — placed, not
     popped. */
  .orb,.orb .otrk,.orw,.orw input,.mrm,.mrm input{transition:none}
  .hp[data-new]{animation:none}
}

/* THE DOOR NEEDS A HANDLE. The grouped row emitted its caret from the start
   and nothing painted it, so ten rolls collapsed into a row with no sign it
   opened at all. Plinko's rule, so a run discloses the same way in both. */
.rl .cv{
  display:inline-block;width:0;height:0;margin-right:5px;vertical-align:1px;
  border:4.5px solid transparent;border-left:6px solid currentColor;
  border-right-width:0;transition:transform .12s;
}
.rl .cv.o{transform:rotate(90deg);}
@media (prefers-reduced-motion:reduce){ .rl .cv{transition:none;} }
