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