
/* ---- 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)}
#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)}}
