/* PLN BOX SHOW RIG broadcast overlay v4: the Stamped Ledger package.
   Pawn-ticket noir, from the design handoff in docs/design/. Every plate is a
   lot ticket: flat #0e1016, hard offset shadows with no blur, perforated
   dotted edges, rotated rubber stamps. The reveal certificate is paper, the
   only light surface in the show, so the payoff is the one moment the frame
   goes bright.
   Easing: --ease-out entrances/fills, --ease-exit exits, --ease-slam impacts,
   linear for flash/tell/fades. Stagger: 60ms package, 40ms rows, 30ms digits.
   One specular sweep is the only shine and it fires only when money moves. */

:root{
  --truth:#3fd8ff; --truth-ink:#062733;
  --broker:#ff2e55; --broker-ink:#2b040d;
  --money:#ffc63d; --money-ink:#17130a;
  --crowd:#b45bff; --crowd-alt:#8f39da;
  --r-dust:#8a919d; --r-common:#e8ecf2; --r-rare:#3f8cff;
  --r-epic:#b45bff; --r-grail:#ffc63d;
  --g-a:#43e39a; --g-b:#3fd8ff; --g-c:#ffc63d; --g-d:#ff8a3d; --g-f:#ff2e55;
  --ticket:#0e1016; --ticket-strip:#12141b;
  --hairline:#2a3347; --hairline-hi:#3d4a66;
  --ink:#e8ecf2; --ink-dim:#7d8797;
  --paper:#f0e9d8; --paper-ink:#17130a; --paper-dim:#6b5410;
  --t-1:21px; --t-2:25px; --t-3:30px; --t-4:36px;
  --t-5:46px; --t-6:60px; --t-7:88px; --t-8:150px;
  --safe:88px;
  --trk-label:.1em; --trk-display:.01em; --trk-mono:.05em;
  --ease-out:cubic-bezier(.16,1,.3,1);
  --ease-exit:cubic-bezier(.7,0,.84,0);
  --ease-slam:cubic-bezier(.34,1.56,.64,1);
  --d-tick:120ms; --d-fast:240ms; --d-in:420ms; --d-hero:700ms;
  --z-stage:0; --z-panel:10; --z-lower:20; --z-reveal:30;
  --z-flash:40; --z-fire:50; --z-fair:60;
  /* Hard offset shadows, never blurred: the ticket sits ON the video. */
  --sh-sm:4px 4px 0 rgba(0,0,0,.5);
  --sh-md:6px 6px 0 rgba(0,0,0,.5);
  --sh-lg:10px 10px 0 rgba(0,0,0,.5);
  --sh-hero:14px 14px 0 rgba(0,0,0,.5);
  --font-display:"Anton","Haettenschweiler","Franklin Gothic Medium","Archivo Narrow",sans-serif;
  --font-mono:"IBM Plex Mono","Consolas","Courier New",monospace;
  --font-support:"Archivo Narrow","Franklin Gothic Medium",sans-serif;
}
*{margin:0;padding:0;box-sizing:border-box}
html,body{width:100%;height:100%;overflow:hidden;background:transparent}
body{font-family:var(--font-support);color:var(--ink)}
body.studio-bg{background:radial-gradient(120% 90% at 50% 8%,#1a2133 0%,#0b0e14 55%,#05070b 100%)}

#viewport{position:absolute;inset:0;overflow:hidden}
#stage{position:absolute;left:50%;top:50%;width:1920px;height:1080px;transform-origin:center;overflow:hidden;
  transform:translate(-50%,-50%) scale(var(--s,1))}
#stage.quake{animation:quake .5s linear}
@keyframes quake{
  0%,100%{transform:translate(-50%,-50%) scale(var(--s,1)) translate(0,0)}
  15%{transform:translate(-50%,-50%) scale(var(--s,1)) translate(-9px,5px)}
  30%{transform:translate(-50%,-50%) scale(var(--s,1)) translate(8px,-6px)}
  45%{transform:translate(-50%,-50%) scale(var(--s,1)) translate(-6px,-4px)}
  60%{transform:translate(-50%,-50%) scale(var(--s,1)) translate(5px,6px)}
  75%{transform:translate(-50%,-50%) scale(var(--s,1)) translate(-3px,2px)}
  90%{transform:translate(-50%,-50%) scale(var(--s,1)) translate(2px,-2px)}}

/* ── ticket DNA ── */
.plate{position:relative;background:var(--ticket);border:2px solid var(--hairline-hi);box-shadow:var(--sh-md)}
/* The one permitted shine. Fires only when money moves. */
.sweepable{position:relative;overflow:hidden}
.sweepable .sweep{position:absolute;inset:-2px;pointer-events:none;opacity:0;z-index:3;
  background:linear-gradient(105deg,transparent 42%,rgba(255,255,255,.16) 50%,transparent 58%);
  transform:translateX(-110%)}
.sweepable.swept .sweep{animation:sweep var(--d-hero) var(--ease-out) forwards}
@keyframes sweep{0%{opacity:1;transform:translateX(-110%)}100%{opacity:1;transform:translateX(110%)}}
.label{font-family:var(--font-mono);font-weight:400;font-size:var(--t-1);letter-spacing:var(--trk-label);
  color:var(--ink-dim);text-transform:uppercase}
.num{font-variant-numeric:tabular-nums;letter-spacing:0}
/* Rubber stamp: Anton, bordered, rotated, always on its own little plate. */
.tstamp{display:inline-block;font-family:var(--font-display);font-weight:400;font-size:var(--t-1);
  letter-spacing:2px;text-transform:uppercase;padding:3px 12px 4px;border:2px solid currentColor;
  background:var(--ticket);transform:rotate(var(--rot,-3deg));white-space:nowrap}
/* Legacy hero-text hooks, remapped to the flat language. */
.engrave{color:var(--money)}
.engrave.steel{color:var(--ink)}

/* ── package in/out ── */
.pkg{transition:transform var(--d-in) var(--ease-out),opacity var(--d-in) var(--ease-out)}
.pkg.out{opacity:0;transition:transform var(--d-fast) var(--ease-exit),opacity var(--d-fast) var(--ease-exit)}
.pkg.from-l.out{transform:translateX(-36px)}
.pkg.from-r.out{transform:translateX(36px)}
.pkg.from-b.out{transform:translateY(36px)}
.pkg.from-t.out{transform:translateY(-24px)}
.hidden{display:none !important}

/* ── show bug: teal PLN mark, wordmark ticket, live stamp ── */
#bug{position:absolute;top:var(--safe);left:var(--safe);z-index:var(--z-panel);
  display:flex;align-items:center;gap:10px;filter:drop-shadow(6px 6px 0 rgba(0,0,0,.5))}
#bug .plnmark{height:56px;display:block}
#bug .show{display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--ticket);
  border:2px solid var(--hairline-hi)}
#bug .show .name{height:26px;display:block}
#bug .live{font-family:var(--font-display);font-size:var(--t-1);letter-spacing:2px;color:var(--broker);
  border:2px solid var(--broker);padding:1px 9px 2px;transform:rotate(-3deg);animation:livePulse 1.6s infinite}
#bug .live i{display:none}
@keyframes livePulse{0%,100%{opacity:1}50%{opacity:.35}}
@keyframes livedot{0%,100%{opacity:1}50%{opacity:.35}}

/* ── lower third: the lot ticket ── */
#lower{position:absolute;left:var(--safe);bottom:var(--safe);z-index:var(--z-lower);width:1029px;
  background:var(--ticket);border:2px solid var(--hairline-hi);border-left:4px dotted var(--hairline-hi);
  box-shadow:var(--sh-lg);
  clip-path:polygon(0 0,100% 0,100% calc(100% - 22px),calc(100% - 22px) 100%,0 100%)}
#lower .lt-head{display:flex;align-items:center;justify-content:space-between;gap:18px;
  padding:10px 24px;border-bottom:2px solid var(--hairline)}
#lower .lot{font-family:var(--font-mono);font-weight:600;font-size:var(--t-1);letter-spacing:2px;color:var(--money)}
#lower .lot b{color:inherit;font-weight:inherit}
#lower .rec{--rot:-2deg;color:var(--ink-dim);font-size:var(--t-1)}
#lower .lt-body{display:flex;align-items:stretch}
#lower .who{flex:1;display:flex;align-items:center;padding:10px 24px;min-width:0}
#lower .who .name{font-family:var(--font-display);font-size:58px;line-height:1.05;color:var(--ink);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
#lower .ledger{width:400px;border-left:2px solid var(--hairline);padding:12px 24px 14px;
  display:flex;flex-direction:column;gap:8px;justify-content:center}
#lower .lrow{position:relative;display:flex;align-items:baseline;gap:12px}
#lower .lrow .k{font-family:var(--font-mono);font-size:var(--t-1);color:var(--ink-dim);letter-spacing:1px;white-space:nowrap}
#lower .lrow .leader{flex:1;border-bottom:2px dotted var(--hairline);transform:translateY(-6px)}
#lower .lrow .v{font-family:var(--font-mono);font-weight:600;font-size:var(--t-3);line-height:1.1}
#lower .lrow .v.truth{color:var(--truth)}
#lower .lrow .v.money{color:var(--money)}
#lower .items{display:flex;gap:7px;align-items:center}
#lower .items .slot{width:18px;height:18px;border-radius:50%;border:2px solid var(--hairline-hi)}
#lower .items .slot.filled{background:var(--money);border-color:var(--money)}
/* Tick: the row underlines itself when its number changes. */
#lower .lrow .base{position:absolute;left:0;right:0;bottom:-4px;height:2px;background:var(--truth);
  opacity:0;transform:scaleX(0);transform-origin:left}
#lower .lrow.ticked .base{animation:baseline .5s var(--ease-out)}
@keyframes baseline{0%{opacity:1;transform:scaleX(0)}70%{opacity:1;transform:scaleX(1)}100%{opacity:0;transform:scaleX(1)}}
.delta{position:absolute;top:-26px;right:0;font-family:var(--font-display);font-size:var(--t-2);color:var(--truth);
  opacity:0;transform:translateY(8px) scale(.8)}
.delta.money{color:var(--money)}
.delta.show{animation:delta 2s var(--ease-slam) forwards}
@keyframes delta{0%{opacity:0;transform:translateY(8px) scale(.8)}18%{opacity:1;transform:translateY(0) scale(1)}
  80%{opacity:1}100%{opacity:0;transform:translateY(-8px) scale(.95)}}

/* ── odometer: per-digit roll, 30ms stagger (chrome.js odoSet) ── */
.odo{display:inline-flex;overflow:hidden;line-height:1.1}
.odo .col{display:inline-block;height:1.1em;overflow:hidden}
.odo .strip{display:inline-flex;flex-direction:column;transition:transform .34s var(--ease-out)}
.odo .strip span{height:1em;line-height:1em}
.odo .ch{display:inline-block}

/* ── ladder rail: ticket stubs ── */
#ladder{position:absolute;right:var(--safe);top:50%;transform:translateY(-50%);z-index:var(--z-panel);
  display:flex;flex-direction:column-reverse;gap:8px;width:250px}
#ladder.pkg.from-r.out{transform:translateY(-50%) translateX(36px)}
#ladder .rung{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 14px;position:relative;
  border-left:3px dotted var(--hairline-hi);box-shadow:var(--sh-sm)}
#ladder .rung .tier{font-family:var(--font-mono);font-size:var(--t-1);color:var(--ink-dim);white-space:nowrap}
#ladder .rung .price{font-family:var(--font-display);font-size:24px;color:var(--ink)}
#ladder .rung.upcoming{opacity:.78}
#ladder .rung.cleared{opacity:.6}
#ladder .rung.cleared .tier,#ladder .rung.cleared .price{text-decoration:line-through;
  text-decoration-color:rgba(255,46,85,.7);text-decoration-thickness:2px;color:var(--ink-dim)}
/* The CLEARED stamp hangs off the stub. chrome.js writes the text into .check. */
#ladder .rung .check{display:none}
#ladder .rung.cleared .check{display:block;position:absolute;left:50%;bottom:-15px;transform:translateX(-50%) rotate(-6deg);
  font-family:var(--font-display);font-size:15px;letter-spacing:3px;color:var(--broker);
  border:2px solid var(--broker);background:var(--ticket);padding:0 8px 1px;z-index:1}
/* Current rung breaks the column: gold, wider, with a vertical tab. */
#ladder .rung.current{background:var(--money);border-color:var(--money-ink);border-left-style:solid;
  margin:0 -12px;box-shadow:var(--sh-md);animation:rungGlow 2.2s ease-in-out infinite}
#ladder .rung.current .tier{color:var(--money-ink);font-weight:600}
#ladder .rung.current .price{color:var(--money-ink);font-size:32px}
@keyframes rungGlow{0%,100%{box-shadow:var(--sh-md),0 0 18px rgba(255,198,61,.45)}50%{box-shadow:var(--sh-md),0 0 34px rgba(255,198,61,.75)}}
#ladder .rung .curtab{display:none}
#ladder .rung.current .curtab{display:block;position:absolute;left:-38px;top:50%;transform:translateY(-50%);
  writing-mode:vertical-rl;font-family:var(--font-mono);font-weight:600;font-size:var(--t-1);letter-spacing:2px;
  color:var(--money);background:var(--ticket);border:2px solid var(--money);padding:8px 1px}
#ladder .rung .dtag{display:none}

/* ── reveal sequence ── */
#vignette{position:absolute;inset:0;z-index:calc(var(--z-reveal) - 1);pointer-events:none;opacity:0;
  background:radial-gradient(circle at 50% 46%,transparent 34%,rgba(6,7,10,.62) 100%);
  transition:opacity var(--d-fast) var(--ease-out)}
#vignette.on{opacity:1}
#rarityWash{position:absolute;inset:0;z-index:calc(var(--z-reveal) - 2);pointer-events:none;opacity:0;
  background:radial-gradient(58% 52% at 50% 45%,var(--rarc,transparent) 0%,transparent 68%);
  transition:opacity .7s var(--ease-out)}
#rarityWash.on{opacity:var(--wash,0)}
#rarityWash.grail{animation:washBreathe 2.6s ease-in-out infinite}
@keyframes washBreathe{0%,100%{opacity:var(--wash,0)}50%{opacity:calc(var(--wash,0) * 1.45)}}
.tell{position:absolute;top:0;bottom:0;width:150px;z-index:calc(var(--z-reveal) - 1);pointer-events:none;opacity:0}
#tellL{left:0;background:linear-gradient(90deg,var(--tellc,transparent),transparent)}
#tellR{right:0;background:linear-gradient(-90deg,var(--tellc,transparent),transparent)}
.tell.on{animation:tellpulse .9s linear infinite}
.tell.grail-hold{animation:tellbreathe 1.1s var(--ease-out) infinite}
@keyframes tellpulse{0%,100%{opacity:.28}50%{opacity:.6}}
@keyframes tellbreathe{0%,100%{opacity:.45;transform:scaleX(1)}50%{opacity:.9;transform:scaleX(1.35)}}
#flash{position:absolute;inset:0;z-index:var(--z-flash);pointer-events:none;background:#fff;opacity:0}
#flash.fire{animation:flash 90ms linear}
@keyframes flash{0%{opacity:0}30%{opacity:1}100%{opacity:0}}
#reveal{position:absolute;inset:0;z-index:var(--z-reveal);display:grid;place-items:center;pointer-events:none}
#slate{position:absolute;top:150px;left:50%;transform:translateX(-50%) rotate(-2deg) scale(.8);opacity:0;
  font-family:var(--font-display);font-size:26px;letter-spacing:6px;color:var(--ink);text-transform:uppercase;
  border:2px solid var(--ink);background:var(--ticket);padding:5px 22px 6px;box-shadow:var(--sh-md)}
#slate.show{animation:slam .32s var(--ease-slam) forwards}
@keyframes slam{0%{opacity:0;transform:translateX(-50%) rotate(-2deg) scale(1.4)}
  100%{opacity:1;transform:translateX(-50%) rotate(-2deg) scale(1)}}
#crate{position:absolute;left:50%;top:50%;width:300px;height:300px;margin:-150px 0 0 -150px;opacity:0}
#crate.in{animation:cratein .38s var(--ease-slam) forwards}
@keyframes cratein{0%{opacity:0;transform:translateY(-140px) scale(1.2)}100%{opacity:1;transform:translateY(0) scale(1)}}
#crate.sh1{opacity:1;animation:shake1 .18s linear infinite}
#crate.sh2{opacity:1;animation:shake2 .13s linear infinite}
#crate.sh3{opacity:1;animation:shake3 .09s linear infinite}
#crate.hold{opacity:1;animation:none;transform:scale(1.04)}
@keyframes shake1{0%,100%{transform:translate(0,0) rotate(0)}25%{transform:translate(-4px,2px) rotate(-1deg)}75%{transform:translate(4px,-2px) rotate(1deg)}}
@keyframes shake2{0%,100%{transform:translate(0,0) rotate(0)}25%{transform:translate(-8px,4px) rotate(-2deg)}75%{transform:translate(8px,-4px) rotate(2deg)}}
@keyframes shake3{0%,100%{transform:translate(0,0) rotate(0) scale(1.02)}20%{transform:translate(-13px,7px) rotate(-3deg) scale(1.02)}60%{transform:translate(12px,-8px) rotate(3deg) scale(1.02)}}
#shockwave{position:absolute;left:50%;top:50%;width:340px;height:340px;margin:-170px 0 0 -170px;border-radius:50%;
  border:3px solid var(--rarc,#fff);opacity:0;transform:scale(.4)}
#shockwave.go{animation:shock .7s var(--ease-out) forwards}
@keyframes shock{0%{opacity:.9;transform:scale(.4);border-width:6px}100%{opacity:0;transform:scale(2.4);border-width:1px}}

/* ── the certificate: paper, the only light object in the show ── */
#card{width:600px;padding:10px;opacity:0;background:var(--paper);border:2px solid var(--paper-ink);
  position:relative;z-index:31;
  box-shadow:var(--sh-hero),0 0 90px color-mix(in srgb,var(--rarc,#fff) 45%,transparent);
  transform:rotate(-1.5deg) scale(1.16);
  transition:transform var(--d-hero) var(--ease-out)}
#card.in{animation:cardin .38s var(--ease-slam) forwards}
@keyframes cardin{0%{opacity:0;transform:rotate(-4deg) scale(1.16)}60%{opacity:1}
  100%{opacity:1;transform:rotate(-1.5deg) scale(1)}}
/* Docked, the certificate condenses to a strip: name, value, seal. Full-card
   scaling parked it under the fairness strip, and paper under the strip is
   glaring in a way the old dark slab never was. The strip sits beside the show
   bug, inside title safe, on air for the whole decision. */
#card.docked{transform:translate(-335px,-402px) rotate(0deg) scale(1);width:330px;padding:6px 10px;
  box-shadow:var(--sh-md)}
#card.docked .cert-inner{display:flex;align-items:center;gap:16px;border:none;padding:2px 6px}
#card.docked .cert-head,#card.docked .slab-window,#card.docked .vlabel{display:none}
/* THE BREAK: the owner sentence only airs on the full centre-stage
   certificate, the same way the picture window and the odds line already
   yield once it docks (the two rules directly above and below this one).
   The compact docked strip (330x53, see test/composite.test.ts's own
   dockedCert measurement) has room for a name and a value, not a second
   sentence, and the sentence has already had its whole HERO_HOLD to read by
   the time a card docks. cert-plate needs no rule of its own here: it lives
   inside .slab-window, already hidden whole by the rule above. */
#card.docked .cert-team,#card.docked .cert-owner{display:none}
/* The size is reveal.js's fitDockedName's: 26px when the name fits the strip,
   smaller down to 15px when it does not (util.js's dockedNameSize), so a long
   card name reads whole instead of ending in an ellipsis. The ellipsis stays
   for the name no size can carry. */
#card.docked .iname{font-size:var(--dock-name,26px);padding:0;text-align:left;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis;min-width:0;flex:1}
#card.docked .cert-foot{padding:0;gap:12px;align-items:center}
#card.docked .value{font-size:26px;padding:1px 10px 2px}
#card.docked .odds{display:none}
.cert-inner{border:3px double var(--paper-ink);padding:0}
#card .cert-head{display:flex;align-items:baseline;justify-content:space-between;gap:14px;
  padding:10px 18px;border-bottom:2px solid var(--paper-ink)}
#card .cert-head .cb{font-family:var(--font-mono);font-weight:600;font-size:var(--t-1);letter-spacing:2px;color:var(--paper-ink)}
#card .certno{font-family:var(--font-mono);font-size:var(--t-1);color:var(--paper-dim)}
#card .slab-window{position:relative;overflow:hidden;height:300px;margin:12px 14px 0;background:#14161c;
  display:grid;place-items:center}
#card .cat{width:196px;height:196px;color:var(--rarc,#e8ecf2);display:block;
  filter:drop-shadow(0 12px 20px rgba(0,0,0,.65))}
#card .prod{display:none;width:100%;height:100%;object-fit:contain;padding:14px}
#card.has-media .prod{display:block}
#card.has-media .cat{display:none}
#card .iname{font-family:var(--font-display);font-size:42px;letter-spacing:.5px;line-height:1.05;
  color:var(--paper-ink);text-align:center;padding:14px 18px 4px;text-wrap:balance}
/* THE BREAK: the team a card belongs to, then the sentence that says who
   holds it. Both empty and hidden (the shared .hidden rule) outside a
   break; see reveal.js's writeCertExtras and breakboard.js's ownerLine.
   Cyan is platform truth on this certificate already (the value pill below
   reads the same colour), and the owner sentence is exactly that: a fact
   the server computed, not a guess, so it reads in the same colour. */
#card .cert-team{font-family:var(--font-mono);font-weight:600;font-size:var(--t-1);letter-spacing:3px;
  color:var(--paper-dim);text-transform:uppercase;text-align:center;padding:0 18px}
#card .cert-owner{font-family:var(--font-display);font-size:27px;letter-spacing:.5px;line-height:1.1;
  color:var(--truth);text-align:center;padding:4px 18px 0;text-wrap:balance}
/* The short team name, stamped on the product picture like a lot ticket:
   OTTERS. Positioned inside .slab-window (already position:relative).

   var(--t-1), not 20px: this package has a 21px legibility floor and every
   other comment in this file cites it, and this plate shipped one pixel under
   it (review of Tasks 7, 8 and fix wave 2, finding M14). It is the graphic
   that says which buyer a card just paid, so it is not the one to shave. The
   plate is absolutely positioned inside the picture window and hidden whenever
   it has no team (reveal.js's writeCertExtras), so this changes the
   certificate's own box by nothing at all: re-measured live, the hero
   certificate's envelope is identical to the pixel either side of the
   change. */
#card .cert-plate{position:absolute;left:14px;bottom:14px;z-index:2;
  font-family:var(--font-display);font-size:var(--t-1);letter-spacing:2px;
  background:var(--truth);color:var(--truth-ink);padding:3px 12px 4px;
  border:2px solid var(--paper-ink);transform:rotate(-3deg)}
#card .cert-foot{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;padding:6px 18px 16px}
#card .vlabel{display:block;font-family:var(--font-support);font-weight:500;font-size:var(--t-1);
  letter-spacing:3px;color:var(--paper-dim);text-transform:uppercase;margin-bottom:4px}
#card .value{display:inline-block;font-family:var(--font-display);font-size:56px;line-height:1;
  background:var(--truth);color:var(--truth-ink);border:2px solid var(--paper-ink);padding:3px 16px 5px}
#card .odds{font-family:var(--font-mono);font-weight:600;font-size:var(--t-2);color:var(--paper-ink);
  opacity:0;transform:translateY(6px);padding-bottom:8px}
#card .odds.show{transition:all var(--d-fast) var(--ease-out);opacity:1;transform:none}
/* Rarity seal, overlapping the right edge. */
#card .seal{position:absolute;top:72px;right:-52px;width:150px;height:150px;border-radius:50%;z-index:2;
  background:radial-gradient(circle at 35% 30%,color-mix(in srgb,var(--rarc,#ffc63d) 85%,#fff),var(--rarc,#ffc63d));
  border:4px double var(--paper-ink);box-shadow:var(--sh-md);transform:rotate(12deg);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;text-align:center}
#card .seal b{font-family:var(--font-display);font-size:34px;line-height:1;color:var(--paper-ink);letter-spacing:1px}
#card .seal span{font-family:var(--font-mono);font-weight:600;font-size:var(--t-1);color:var(--paper-ink)}
#card.docked .seal{top:50%;right:-34px;width:76px;height:76px;transform:translateY(-50%) rotate(12deg);border-width:3px}
#card.docked .seal b{font-size:19px}
#card.docked .seal span{display:none}
/* ── THE BREAK's own hero certificate ──
   The same #card every format ignites into, narrowed and moved into the
   centre column for this format only (body.break-on, set by chrome.js for
   the whole life of a break episode; every other format is untouched, proven
   by pixel-hashing the overlay in its idle, hero and docked states against
   the commit before this change).

   Why it had to move. Review of Task 6 + the frame fix wave, finding 3: the
   undocked certificate measured x602-1375, y160-919 and break-quad puts a
   face in all four corners with a score chip across the bottom of each
   (y446-486 and y822-862, the full width of each window). The certificate
   sat on all four of those chips by 119x40 each, and for buyers 2 and 4 that
   is the left edge where the total prints, so the graphic the caption band
   is pointing at was covered at the exact moment it counts. It also covered
   the host inset's own head zone whole.

   Why the column and not simply a smaller card in the middle: the host inset
   sits at the top of the centre column (x768-1152) and its head zone
   (x902.4-1017.6) straddles the frame's own centre line, so a card centred
   on x960 covers that head at any width. The column BELOW the inset and
   above the caption band is the one region of this frame with no head and no
   chip in it, which is exactly why the market strip (y420-607) and the
   allocation stamp (y444-530) already live there; neither of those is on
   screen during a hit (see their own rules above, and the "break hit" scene
   in test/composite.test.ts).

   Three changes, in order of how much they buy: the picture window shrinks,
   which is where the height goes; the card narrows to clear the chips on the
   x axis; and the entrance drops its scale(1.16) overshoot, because a box
   this close to its neighbours cannot afford to grow by 16% for 380ms on the
   way in. The overshoot becomes rotation only, which moves without growing.
   Type sizes come down with the card but stay well above this package's own
   21px floor: the name, the owner sentence and the value are the three
   things a viewer actually reads here.

   The offset is a translateY inside the transform, not a `top`, so the dock
   transition still animates one property from one place to another: with a
   `top` offset the card jumped 96px the instant it gained .docked and then
   slid, because `top` does not transition alongside a transform. */
body.break-on #card:not(.docked){width:430px;transform:translateY(76px) rotate(-1.5deg) scale(1)}
body.break-on #card:not(.docked).in{animation:cardinbreak .38s var(--ease-slam) forwards}
@keyframes cardinbreak{0%{opacity:0;transform:translateY(76px) rotate(-4deg) scale(1)}60%{opacity:1}
  100%{opacity:1;transform:translateY(76px) rotate(-1.5deg) scale(1)}}
body.break-on #card:not(.docked) .slab-window{height:122px;margin:10px 12px 0}
body.break-on #card:not(.docked) .cat{width:84px;height:84px}
body.break-on #card:not(.docked) .iname{font-size:30px;padding:8px 14px 2px}
body.break-on #card:not(.docked) .cert-owner{font-size:23px;padding:3px 14px 0}
body.break-on #card:not(.docked) .cert-foot{padding:4px 14px 12px}
body.break-on #card:not(.docked) .value{font-size:40px;padding:2px 12px 4px}
body.break-on #card:not(.docked) .cert-head{padding:8px 14px 6px}
body.break-on #card:not(.docked) .seal{top:36px;right:-14px;width:100px;height:100px}
body.break-on #card:not(.docked) .seal b{font-size:23px}
/* THE BREAK, docking: a bent path, so the strip never crosses the host's
   head. The dock everywhere else is #card's own one transition on transform,
   a straight line from the centre column to the strip beside the show bug,
   and in a break that line's top right corner crossed the host inset's head
   zone (x902-1017, y206-331) by 12.6x43.4 for 33ms on every dock
   (test/composite.test.ts's breakDockedCert comment, which measured it and
   priced the straight fixes: further left lands on the show bug, lower lands
   on the market strip, an L either way crosses more heads). A keyframe bends
   the path on the same one property and touches no other format: from the
   break's own undocked transform, left and a little up to a waypoint whose
   top edge (y363) is still under the zone's bottom (y331), then up along a
   line whose right edge is already left of the zone (x865 at the height the
   straight path crossed at x946).

   The second waypoint is this branch's. Two keyframes were not enough: the
   leg from the first waypoint straight to the dock ran down through buyer
   one's own head zone (x305-491, y221.76-423.6) with its bottom left corner,
   because it reached x491 while its top was still at y215, inside the zone.
   Measured on main at 1920x1080 over eighteen cells (motion full, reduced and
   off, one to three line names, both safe modes): the crossing is there in
   every one of the twelve full and reduced cells, 32.5 to 33.8ms, widest 10.8
   to 11.5px and tallest 36.0 to 36.7px (the widest and the tallest frames are
   not the same frame), and absent under motion=off only because that whole
   transit is 0.01ms. The straight fix does not exist: the card is 330 wide,
   so it cannot pass the zone below it either, and the only way across x491
   without touching the zone is above it. The waypoint at (x560, y260) is
   where the card rises to before it moves left, chosen against three
   constraints at once, not one: right edge 890, which clears the host inset
   zone's x902.40 by 12.4; top 260, which clears the fairness strip in both
   safe modes (y88-134 by default, y132-178 under ?safe=sponsor) at the
   waypoint itself; and a last leg that reaches x491 with its top at y157,
   which is 12.8 above the y168.76 that would put its bottom edge back in
   buyer one's zone.

   What that costs is the fairness strip, which the certificate passes UNDER
   (#fair is --z-fair, the card is 31), so no graphic is covered by it. The
   card's right end spends longer under the strip on its way in, and less time
   doing it: 8.2x8.0 over 49.9ms by default against 2.2x10.2 over 65.9ms
   before, and 17.5x38.3 over 83.1ms under ?safe=sponsor against 8.7x32.3 over
   115.3ms. Sampled on a live transit off the task queue, median 0ms between
   samples.

   The last keyframe is the docked transform itself, so the settled strip is
   exactly where OVERLAY.breakDockedCert says. .still is a reconnect restore
   (reveal.js's landReveal, statically), which comes back already docked and
   animates nothing. The path is pinned as geometry in
   test/composite.test.ts's "the break's dock path crosses no head zone at
   all", which rebuilds it from these very keyframes and goes red if a
   waypoint is moved or removed. */
@keyframes breakdock{0%{transform:translateY(76px) rotate(-1.5deg) scale(1)}
  50%{transform:translate(-260px,-150px) rotate(0deg) scale(1)}
  75%{transform:translate(-235px,-253px) rotate(0deg) scale(1)}
  100%{transform:translate(-335px,-402px) rotate(0deg) scale(1)}}
body.break-on #card.docked:not(.still){animation:breakdock var(--d-hero) var(--ease-out) forwards}
/* THE BREAK, docked: no seal. Review of fix wave 3, finding I6. The seal is
   absolutely positioned with right:-34px, so it hangs 34px outside the docked
   strip and, rotated 12 degrees, paints a 90px circle at x738.93-829.07,
   y92.93-183.07 (measured live over 204 settled docked frames). The fairness
   strip runs from x791, so the seal crosses it by 39x42 and is painted under
   it. The strip already drops the picture window, the odds, the team line and
   the owner sentence for want of room in 330x53 (see the rules above), and
   the seal is the rarity tell, which has had the whole of its certificate's
   hero hold at full size by the time the card docks. Dropping it here leaves
   the break's docked box exactly the strip, clear of the fairness strip on
   the x axis, and is the one change that resolves the overlap without moving
   a pixel of the strip itself.
   Break scoped on purpose: the same overlap is there in every other format
   that docks a certificate, and has been since the dock shipped. It is
   recorded, with the measurement, in test/composite.test.ts's own
   OVERLAY.dockedCert comment; those formats' pixels are not touched here. */
body.break-on #card.docked .seal{display:none}
/* Decision stamp slams across the certificate. */
#stamp{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%) rotate(-8deg) scale(2);opacity:0;
  font-family:var(--font-display);font-size:var(--t-6);letter-spacing:2px;padding:6px 30px 8px;white-space:nowrap;
  border:4px solid currentColor;background:rgba(240,233,216,.12);z-index:4}
#stamp.show{animation:stampin .32s var(--ease-slam) forwards}
@keyframes stampin{0%{opacity:0;transform:translate(-50%,-50%) rotate(-8deg) scale(2)}
  100%{opacity:1;transform:translate(-50%,-50%) rotate(-8deg) scale(1)}}
#stamp.ship{color:var(--truth)} #stamp.flip{color:var(--broker)} #stamp.ride{color:var(--money)}
#confetti{position:absolute;inset:0;overflow:hidden;pointer-events:none}
#confetti i{position:absolute;top:-20px;width:9px;height:14px;opacity:0;animation:confall 1.8s linear var(--cd,0s) forwards}
@keyframes confall{0%{opacity:1;transform:translate(0,0) rotate(0)}
  100%{opacity:.7;transform:translate(var(--cx,0),1150px) rotate(var(--cr,720deg))}}

/* ── left rail: one tenant at a time ── */
#slot{position:absolute;left:var(--safe);bottom:calc(var(--safe) + 172px);z-index:var(--z-panel);
  width:560px;display:flex;flex-direction:column;justify-content:flex-end;gap:16px}
.panel{padding:16px 20px 20px;background:var(--ticket);border:2px solid var(--hairline-hi);
  border-left:4px dotted var(--hairline-hi);box-shadow:var(--sh-lg)}
.panel .phead{display:flex;justify-content:space-between;align-items:center;gap:14px;margin-bottom:14px}
.panel .ptitle{font-family:var(--font-display);font-size:28px;letter-spacing:2px;color:var(--ink)}
.panel .ptag{font-family:var(--font-display);font-size:var(--t-1);letter-spacing:2px;padding:2px 10px 3px;
  border:2px solid currentColor;background:var(--ticket);transform:rotate(3deg);white-space:nowrap}
#broker{border-color:var(--broker);border-left-color:var(--broker)}
#broker .ptitle{color:var(--broker)}
#broker .ptag{color:var(--broker)}
#crowdp{border-color:var(--crowd);border-left-color:var(--crowd)}
#crowdp .ptitle{color:var(--crowd)}
#crowdp .ptag{color:var(--crowd)}
#crowdp .ptag.locked{color:var(--money)}
#crowdp .ptag.dark{color:var(--ink-dim);border-style:dashed}
/* Platform truth and the Broker's number, always together, as chips. */
.duel{display:grid;grid-template-columns:1fr 1fr}
.duel .side{padding:8px 14px 12px;text-align:center}
.duel .side:last-child{border-left:2px solid var(--hairline)}
.duel .side .label{display:block;margin-bottom:10px}
.duel .amount{display:inline-block;font-family:var(--font-display);font-size:54px;line-height:1;
  border:2px solid var(--money-ink);padding:2px 14px 4px}
.duel .truthside .amount{background:var(--truth);color:var(--truth-ink)}
.duel .brokerside .amount{background:var(--broker);color:var(--broker-ink)}
.duel .brokerside .amount.stamped{animation:stampnum .32s var(--ease-slam)}
@keyframes stampnum{0%{transform:scale(1.7);opacity:0}100%{transform:scale(1);opacity:1}}
.duel .amount.struck{text-decoration:line-through;text-decoration-thickness:4px;opacity:.4;
  transition:opacity .32s var(--ease-out)}
.brokerline{margin-top:12px;font-family:var(--font-support);font-style:italic;font-weight:700;
  font-size:24px;color:var(--ink);text-align:center}
/* Crowd bars */
.bars{display:flex;flex-direction:column;gap:10px}
.bar{display:grid;grid-template-columns:74px 1fr 74px;align-items:center;gap:10px}
.bar .bname{font-family:var(--font-mono);font-weight:600;font-size:var(--t-1);color:var(--ink)}
.bar .track{height:26px;background:#0a0c12;border:2px solid var(--hairline);position:relative}
.bar .fill{position:absolute;inset:2px auto 2px 2px;width:0%;background:var(--crowd);
  transition:width var(--d-in) var(--ease-out)}
.bar.leading .fill{background:linear-gradient(90deg,var(--crowd),#d9a2ff)}
.bar .pct{font-family:var(--font-mono);font-weight:600;font-size:var(--t-2);color:var(--crowd);text-align:right}
.bar.rb .fill{background:var(--rc)}
.bar.rb .pct{color:var(--rc)}
.bar.rb .bname{color:var(--rc)}
.joinrow{display:flex;align-items:center;gap:16px;margin-top:16px;padding-top:14px;
  border-top:2px dotted var(--hairline)}
.joinrow .jqr{flex:0 0 auto;line-height:0;padding:5px;background:#fff;border:2px solid var(--paper-ink)}
.joinrow .jtext{display:flex;flex-direction:column;gap:5px;min-width:0}
.joinrow .jlabel{font-family:var(--font-display);font-size:24px;letter-spacing:1px;color:var(--ink);
  text-transform:uppercase;line-height:1.15}
.joinrow .jurl{font-family:var(--font-mono);font-size:var(--t-1);color:var(--ink-dim);word-break:break-all;line-height:1.35}
#verdict{margin-top:16px;text-align:center;font-family:var(--font-display);font-size:var(--t-4);letter-spacing:2px;
  padding:6px 8px;border:3px solid var(--crowd);color:var(--crowd);background:var(--ticket);
  opacity:0;transform:rotate(-4deg) scale(1.8)}
#verdict.show{animation:verdictin .32s var(--ease-slam) forwards}
@keyframes verdictin{100%{opacity:1;transform:rotate(-4deg) scale(1)}}
#verdict.defied{border-color:var(--broker);color:var(--broker)}
#verdict.with{border-color:var(--g-a);color:var(--g-a)}

/* ── THE BREAK's own market strip ──
   CALL THE HIT for this format, but never a #slot tenant: break-quad's own
   review found the old left-rail crowd panel sitting on top of two buyer
   faces (window 1 and window 3's own head zones) once that layout shipped,
   because #slot's own left-rail position is exactly where break-quad's left
   column of faces now sits. The strip moves to the centre column instead,
   below the host inset: x708-1212 is the gap between break-quad's own left
   and right windows (each 620 wide, starting at x88 and x1212), the same
   column the host inset (x768, w384) already sits centred in. left:708,
   width:500 (508 with the 4px hard shadow the box-shadow override below
   swaps in for .panel's own default var(--sh-lg), which would otherwise
   push the shadow's own 10px past x1212 and into the right window's own
   column) keeps the whole visible box, shadow included, inside that gap
   with the shadow landing exactly on the boundary. top:420 clears the host
   inset's own bottom edge (396) by 24px. See test/composite.test.ts's
   OVERLAY.breakMarket comment for the measured box this CSS produces. */
#breakMarket{position:absolute;left:708px;top:420px;width:500px;z-index:var(--z-panel);
  padding:14px 18px 16px;border-color:var(--crowd);border-left-color:var(--crowd);
  box-shadow:var(--sh-sm)}
/* The tier row keeps its own height whichever of its two states is on air
   (panels.js's buildOrUpdateMarketChips), so the strip's measured box does
   not move when the first call lands. 67px is the chip row's own settled
   height, measured live at --s:1 with all six chips populated; the empty row
   sets one line of 21px mono in the same box. Without this the strip would
   be 45px shorter with nobody having called and 45px taller the instant
   somebody did, which is a graphic jumping on air and a tracked envelope
   that is only true half the time. */
.bm-tiers{display:flex;gap:6px;min-height:67px}
/* NO CALLS YET: the market has no denominator, so it says so rather than
   printing six zeroes (see rip.js's marketCopy). Purple, because this is the
   crowd's own graphic, the same colour the strip's border already carries.
   21px, this package's own floor, in the mono face the strip's other support
   text uses. */
.bm-tiers.bm-empty{align-items:center;justify-content:center;
  background:var(--ticket-strip);border:1px solid var(--hairline);
  font-family:var(--font-mono);font-weight:600;font-size:var(--t-1);letter-spacing:3px;
  color:var(--crowd);text-transform:uppercase}
.bm-chip{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;gap:2px;
  padding:5px 2px 6px;background:var(--ticket-strip);border:1px solid var(--hairline)}
.bm-chip .bm-tier{font-family:var(--font-mono);font-weight:600;font-size:12px;letter-spacing:.5px;
  color:var(--ink-dim);text-transform:uppercase}
.bm-chip .bm-pct{font-family:var(--font-display);font-size:20px;line-height:1.1;color:var(--crowd)}
.bm-chip.rb .bm-pct{color:var(--rc)}
.bm-play{display:flex;align-items:center;gap:14px;margin-top:12px;padding-top:12px;
  border-top:2px dotted var(--hairline)}
.bm-qr{flex:0 0 auto;line-height:0;padding:4px;background:#fff;border:2px solid var(--paper-ink)}
.bm-playtext{display:flex;flex-direction:column;gap:3px;min-width:0}
.bm-playlabel{font-family:var(--font-display);font-size:20px;letter-spacing:1px;color:var(--ink);
  text-transform:uppercase;line-height:1.15}
.bm-playurl{font-family:var(--font-mono);font-size:13px;color:var(--ink-dim);word-break:break-all;line-height:1.3}
/* The strip stands down under a certificate: break.revealed (main.js) sets
   this before the pack cascade starts and clears it once the certificate
   has either docked beside the show bug or the pack cleared without ever
   showing one (a brick). JS-driven, never :has(), for the same reason
   cert-docked and settle-on already are (see overlay.css's own note on
   #vaultchip above): an OBS Chromium without :has() support would drop a
   `body:has(#card:not(.docked))` rule silently and the strip would sit
   under the certificate with no error to catch it. */
body.break-cert-on #breakMarket{display:none}
/* On-air review, finding 3: the allocation ceremony (breakboard.js's
   runAllocationCeremony) stamps for as long as spots.length * 650ms plus a
   final gap, entirely on client side timers, and nothing stopped a producer
   opening the case before the last stamp landed. Every remaining stamp then
   covered the strip below it once #breakMarket came up. JS-driven off
   body.break-ceremony-on (set for exactly the ceremony's own lifetime,
   cleared on both a natural finish and an early cancel), never :has(), for
   the same OBS Chromium compatibility reason break-cert-on above already
   is. */
body.break-ceremony-on #breakMarket{display:none}

/* THE BREAK's own caption band: one plain sentence per beat (breakstory.js's
   storyLine/renderStory), replacing the last, so a viewer on mute can follow
   the whole show without reading the ticker of jargon this format's other
   graphics still carry (a hash, a spot count, a stamp). Lives in the same
   centre column #breakMarket and the allocation stamp already hold
   (left:708, width:504, the gap between break-quad's own left and right
   windows), anchored to the bottom safe band instead: bottom:var(--safe)
   puts its own bottom edge on y992, the title-safe floor every plate in this
   package respects, clear of every quad window's own head zone on the y
   axis alone (the bottom pair's head zone ends at y799.6; see
   test/composite.test.ts's OVERLAY.breakStory comment for the measured
   box). Archivo Narrow (var(--font-support)), never the display face: this
   is read as a sentence, not a stamped headline, and 22px sits a real notch
   above the package's own 21px legibility floor (var(--t-1)). Empty and
   `display:none` until the first beat renders; `.on` (renderStory's own
   toggle) is what a producer or a stale episode never has to remember to
   clear by hand, the same "hidden until it has something to say" contract
   #breakMarket's own panel already keeps. */
/* Above the scrims, not under them (review of Tasks 7, 8 and fix wave 2,
   finding I8). Every full-frame dimmer in this package sits at --z-flash: the
   commitment card's own scrim, the winner shell's, the settlement receipt's,
   the white hit flash. At --z-panel this band spent 4.2 of the commitment
   card's 5.2 seconds under an 82% black wash, and the whole winner beat under
   a 60% one, which is exactly the beat a viewer on mute is reading it for.
   One above the scrim layer, still below the fairness strip and the fire
   layer (--z-fair is 60, --z-fire 50): the sentence is never the thing that
   is dimmed. */
#breakStory{position:absolute;left:708px;width:504px;bottom:var(--safe);z-index:calc(var(--z-flash) + 1);
  display:none;padding:12px 18px 14px;background:var(--ticket);border:2px solid var(--hairline-hi);
  border-left:4px dotted var(--hairline-hi);
  font-family:var(--font-support);font-weight:500;font-size:22px;line-height:1.3;color:var(--ink)}
#breakStory.on{display:block}

/* THE BREAK's pack counter, the number the caption band says on its per-pack
   sentences and nothing used to hold between them (the watchable pass's
   residual list). A slim strip in the centre column, a real gap under the
   hero certificate (whose box ends at y859 in test/composite.test.ts's
   heroCert) and a real gap above the caption band (y904): bottom:180px with
   its own height puts it at y866-900, measured live (OVERLAY.breakPackCounter
   in test/composite.test.ts: x840-1080, 7px under the certificate, 4px above
   the band). It sits with the band above the scrims for the same
   reason the band does. Never near a face: the centre column x708-1212 holds
   no face below the host inset's y396. */
#breakPackCounter{position:absolute;left:50%;transform:translateX(-50%);bottom:180px;
  z-index:calc(var(--z-flash) + 1);display:none;padding:4px 16px 5px;background:var(--ticket);
  border:2px solid var(--hairline-hi);font-family:var(--font-mono);font-weight:600;font-size:var(--t-1);
  letter-spacing:3px;line-height:1;color:var(--ink);white-space:nowrap}
#breakPackCounter.on{display:block}

/* ── odds desk grade card ── */
#grade{border-color:var(--broker);border-left-color:var(--broker)}
#grade .ptitle{color:var(--broker)}
#grade .ptag{border-color:var(--money-ink);color:var(--money-ink);background:var(--g-c)}
#grade.g-a .ptag{background:var(--g-a)} #grade.g-b .ptag{background:var(--g-b)}
#grade.g-c .ptag{background:var(--g-c)} #grade.g-d .ptag{background:var(--g-d)}
#grade.g-f .ptag{background:var(--g-f);color:#fff;border-color:#fff}
#grade .gbody{display:flex;gap:18px;align-items:stretch}
/* The grade insert: a solid block of the grade's colour, letter cut into it. */
#grade .gletter{width:150px;min-width:150px;display:grid;place-items:center;font-family:var(--font-display);
  font-size:120px;line-height:.85;color:var(--money-ink);background:var(--g-c);
  border:2px solid var(--money-ink);box-shadow:var(--sh-md)}
#grade.g-a .gletter{background:var(--g-a)}
#grade.g-b .gletter{background:var(--g-b)}
#grade.g-c .gletter{background:var(--g-c)}
#grade.g-d .gletter{background:var(--g-d)}
#grade.g-f .gletter{background:var(--g-f);color:#fff}
#grade .gtext{flex:1;display:flex;flex-direction:column;justify-content:center;gap:10px;min-width:0}
#grade .gline{font-family:var(--font-support);font-weight:500;font-size:24px;color:var(--ink);line-height:1.35}
#grade .gopts{display:flex}
#grade .gopt{flex:1;padding:5px 6px;text-align:center}
#grade .gopt + .gopt{border-left:2px solid var(--hairline)}
#grade .gopt .ok{font-family:var(--font-mono);font-size:15px;letter-spacing:1px;color:var(--ink-dim);font-weight:600}
#grade .gopt .ov{font-family:var(--font-mono);font-weight:600;font-size:var(--t-2);font-variant-numeric:tabular-nums;color:var(--ink)}
#grade .gopt.best .ov{color:var(--g-a)}
#grade .gopt.taken{box-shadow:inset 0 0 0 3px var(--money)}
#grade .gopt.taken .ok{color:var(--money)}

/* ── Broker camera window ── */
#brokerCam{width:100%;display:none}
#brokerCam.on{display:block}
#brokerCam .bc-bar{display:flex;align-items:center;gap:9px;background:var(--ticket);
  border:2px solid var(--broker);border-bottom:none;padding:6px 12px;
  font-family:var(--font-display);font-size:var(--t-1);letter-spacing:2px;color:var(--broker);text-transform:uppercase}
#brokerCam .bc-tally{width:9px;height:9px;border-radius:50%;background:var(--broker);animation:livedot 1.6s infinite}
#brokerCam .bc-live{margin-left:auto;color:var(--ink-dim)}
#brokerCam .bc-window{height:242px;border:2px solid var(--broker);border-left:4px dotted var(--broker);
  background:transparent;position:relative}

/* ── locking in: a held breath, not a result ── */
#lockin{position:absolute;left:50%;bottom:300px;transform:translateX(-50%) translateY(18px);z-index:var(--z-reveal);
  display:flex;align-items:center;gap:14px;padding:12px 28px;opacity:0;pointer-events:none;
  background:var(--ticket);border:2px dashed var(--hairline-hi)}
#lockin.show{animation:lockin .34s var(--ease-slam) forwards}
@keyframes lockin{100%{opacity:1;transform:translateX(-50%) translateY(0)}}
#lockin .who{font-family:var(--font-mono);font-weight:600;font-size:var(--t-1);letter-spacing:2px;color:var(--ink);
  text-transform:uppercase}
#lockin .says{font-family:var(--font-mono);font-size:var(--t-1);letter-spacing:2px;color:var(--ink-dim);text-transform:uppercase}
/* Hollow by design: an outline, never a filled stamp, because it is not final. */
#lockin .call{font-family:var(--font-display);font-size:34px;line-height:1;letter-spacing:2px;
  color:transparent;-webkit-text-stroke:2px var(--lockc,var(--truth));
  animation:lockpulse 1.1s ease-in-out infinite}
#lockin .call::after{content:" ···";-webkit-text-stroke:0;color:var(--lockc,var(--truth))}
@keyframes lockpulse{0%,100%{opacity:1}50%{opacity:.55}}
#lockin.ship{--lockc:var(--truth)} #lockin.flip{--lockc:var(--broker)} #lockin.ride{--lockc:var(--money)}

/* ── instant replay ── */
#replayBug{position:absolute;left:var(--safe);top:184px;z-index:var(--z-flash);display:none;
  align-items:center;gap:9px;background:var(--ticket);border:2px solid var(--broker);padding:5px 16px;
  font-family:var(--font-display);font-size:var(--t-1);letter-spacing:3px;color:var(--broker);
  box-shadow:var(--sh-sm)}
#replayBug.on{display:flex}
#replayBug .rb-dot{width:8px;height:8px;border-radius:50%;background:var(--broker);animation:livedot 1s infinite}
#stinger{position:absolute;inset:0;z-index:var(--z-fire);pointer-events:none;display:none}
#stinger.go{display:block}
#stinger .st-wipe{position:absolute;inset:0;display:grid;place-items:center;
  background:linear-gradient(105deg,transparent 0%,var(--ticket) 18%,#151922 50%,var(--ticket) 82%,transparent 100%);
  transform:translateX(-110%);animation:stinger .62s var(--ease-out) forwards}
#stinger .st-wipe span{font-family:var(--font-display);font-size:var(--t-7);letter-spacing:8px;color:var(--broker)}
@keyframes stinger{0%{transform:translateX(-110%)}100%{transform:translateX(110%)}}

/* ── act out / rejoin ── */
#actout{position:absolute;inset:0;z-index:var(--z-flash);display:none;place-items:center;background:rgba(4,6,10,.9)}
#actout.on{display:grid}
#actout .ao-card{width:900px;padding:44px 56px;text-align:center;box-shadow:var(--sh-hero);
  border-left:5px dotted var(--hairline-hi);animation:cardin2 .42s var(--ease-slam) both}
@keyframes cardin2{0%{opacity:0;transform:scale(1.16)}60%{opacity:1}100%{opacity:1;transform:scale(1)}}
#actout .ao-eyebrow{font-family:var(--font-mono);font-weight:600;font-size:var(--t-1);letter-spacing:3px;
  color:var(--truth);text-transform:uppercase}
#actout .ao-name{font-family:var(--font-display);font-size:96px;line-height:1;margin:12px 0 16px;color:var(--ink)}
#actout .ao-line{font-family:var(--font-mono);font-size:var(--t-2);color:var(--ink-dim)}

/* ── tale of the tape ── */
#tape{position:absolute;inset:0;z-index:var(--z-flash);display:none;place-items:center;background:rgba(4,6,10,.82)}
#tape.on{display:grid}
#tape .tape-card{width:1000px;padding:36px 44px;box-shadow:var(--sh-hero);
  border-left:5px dotted var(--hairline-hi);animation:cardin2 .42s var(--ease-slam) both}
#tape .tape-title{text-align:center;font-family:var(--font-display);font-size:var(--t-4);letter-spacing:4px;
  margin-bottom:24px;color:var(--ink)}
#tape .tape-grid{display:grid;grid-template-columns:1fr 240px 1fr;gap:12px 18px;align-items:center}
#tape .tr-l{text-align:right;font-family:var(--font-mono);font-weight:600;font-size:var(--t-3);
  font-variant-numeric:tabular-nums;color:var(--truth)}
#tape .tr-m{text-align:center;font-family:var(--font-mono);font-size:var(--t-1);letter-spacing:2px;
  color:var(--ink-dim);text-transform:uppercase}
#tape .tr-r{font-family:var(--font-mono);font-weight:600;font-size:var(--t-3);
  font-variant-numeric:tabular-nums;color:var(--broker)}
#tape .tape-head{font-family:var(--font-display);font-size:var(--t-5);letter-spacing:1px}

/* ── crowd lever ── */
#lever{position:absolute;right:var(--safe);bottom:calc(var(--safe) + 152px);z-index:var(--z-lower);width:560px;
  padding:12px 22px 16px;border-color:var(--crowd);border-left:4px dotted var(--crowd);box-shadow:var(--sh-md)}
#lever .lhead{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:8px}
#lever .lhead .label{font-family:var(--font-display);font-size:22px;letter-spacing:2px}
#lever .lhead .pct{font-family:var(--font-mono);font-weight:600;font-size:var(--t-2);color:var(--crowd)}
#lever .track{height:18px;background:#0a0c12;border:2px solid var(--hairline);position:relative;overflow:hidden}
#lever .fill{position:absolute;inset:2px auto 2px 2px;width:0%;transition:width .5s linear;
  background:repeating-linear-gradient(135deg,var(--crowd) 0 12px,var(--crowd-alt) 12px 24px)}
#lever.armed{background:var(--crowd)}
#lever.armed .lhead .label{color:var(--money-ink)}
#lever.armed .lhead .pct{color:var(--money-ink)}
#lever.armed .track{border-color:var(--money-ink);background:rgba(0,0,0,.35)}
#lever.armed .fill{animation:armpulse 1s linear infinite}
@keyframes armpulse{0%,100%{filter:brightness(1)}50%{filter:brightness(1.5)}}
#fire{position:absolute;inset:0;z-index:var(--z-fire);pointer-events:none;opacity:0;
  background:radial-gradient(50% 50% at 50% 50%,rgba(180,91,255,.85) 0%,rgba(180,91,255,.28) 40%,transparent 72%)}
#fire.go{animation:firego .9s var(--ease-out)}
@keyframes firego{0%{opacity:0;transform:scale(.3)}25%{opacity:1}100%{opacity:0;transform:scale(2.1)}}

/* ── fairness strip: never removed, in any state ── */
#fair{position:absolute;right:var(--safe);top:var(--safe);z-index:var(--z-fair);display:flex;align-items:stretch;
  background:var(--ticket);border:2px solid var(--hairline-hi);box-shadow:var(--sh-md)}
#fair .chip{display:flex;align-items:center;padding:7px 12px;font-family:var(--font-mono);font-weight:600;
  font-size:var(--t-1);letter-spacing:1px;white-space:nowrap}
#fair .chip.proof{background:var(--money);color:var(--money-ink);border-right:2px solid var(--money-ink)}
#fair .chip.verify{background:var(--truth);color:var(--truth-ink);border-left:2px solid var(--truth-ink)}
#fair .commit{display:flex;align-items:center;gap:0;padding:7px 14px;font-family:var(--font-mono);
  font-size:var(--t-1);letter-spacing:.5px;color:var(--ink-dim);white-space:nowrap}
#fair .commit b{color:var(--ink);font-weight:400;padding:0 6px}
#fair .barcode{width:56px;margin:6px 8px 6px 0;
  background:repeating-linear-gradient(90deg,var(--ink) 0 2px,var(--ticket) 2px 5px,var(--ink) 5px 6px,var(--ticket) 6px 10px)}

/* The custody head airs in full (64 hex characters), not truncated like the
   seed hash, because a viewer has to transcribe it exactly to check it on
   /verify: there is no round trip that can hand them an untamperable copy
   the way the revealed seed can be fetched over the API. Wrap it rather
   than letting it run off the plate. */
#fair.custody .commit{white-space:normal;max-width:420px}
#fair.custody .commit b#fSeed{white-space:normal;word-break:break-all;letter-spacing:0;line-height:1.3}

/* ── QR plate: the certificate's door off the screen (Task 12) ──
   Position is set inline from QR_PLATE in ov/qrplate.js, the tested
   geometry; this rule is visual chrome only, the same split #lower uses
   between its CSS (looks) and its geometry constant (measured contract). */
.qr-plate{position:absolute;z-index:var(--z-panel);background:var(--ticket);
  border:2px solid var(--hairline-hi);box-shadow:var(--sh-md);
  display:flex;flex-direction:column;align-items:center;overflow:hidden}
/* width/height are also set inline per instance, from ov/qrplate.js's
   qrCodeBox: that is the tested, floor-checked size (final review finding
   4), and the inline style on the element wins over this rule. This rule
   stays as the pre-render/no-JS fallback shape only. */
.qr-plate svg{display:block;width:100%;height:auto}
/* 12px, not the show's usual --t-1 type scale: qrCodeBox reserves exactly
   this many px for the label band when it sizes the code, so this height
   and QR_PLATE_LABEL_HEIGHT in qrplate.js must move together. Shrunk twice
   now, first from 28px to 16px, then from 16px to this: 16px left the
   sponsor mode code sitting exactly on the 150px sofa-scan floor with no
   margin at all, so a caption a pixel taller would have pushed it under
   with nothing to catch that. See qrplate.js's QR_CODE_MARGIN for why this
   number is not free to shrink again without also raising the code's own
   floor-plus-margin check in test/composite.test.ts. */
.qr-label{width:100%;height:12px;flex:0 0 12px;display:flex;align-items:center;justify-content:center;
  font-family:var(--font-mono);font-weight:600;font-size:10px;letter-spacing:var(--trk-mono);
  color:var(--money);background:var(--ticket-strip);border-top:2px solid var(--hairline)}

/* ── vault chip ── */
#vaultchip{position:absolute;right:var(--safe);top:170px;z-index:var(--z-fair);display:flex;gap:12px;align-items:center;
  background:var(--ticket);border:2px solid var(--money);padding:5px 14px;box-shadow:var(--sh-sm)}
#vaultchip span{font-family:var(--font-mono);font-weight:600;font-size:var(--t-1);letter-spacing:2px;color:var(--money)}
#vaultchip b{font-family:var(--font-display);font-size:var(--t-3);color:var(--money);font-variant-numeric:tabular-nums;line-height:1}
#vaultchip.bump b{animation:stampnum .32s var(--ease-slam)}
/* The vault chip also yields while the reveal certificate is docked (Task
   12): #card.docked sits beside the show bug for the whole decision, and
   the top-right corner is where the QR plate lives for that same beat (see
   qrplate.js). The chip's own corner is real furniture the rest of the
   show, tracked as OVERLAY.vaultchip in test/composite.test.ts; it only
   stands down here and for settlement, above. Driven from a body class,
   not :has(), since the docked state is already set explicitly from
   ov/reveal.js and this rule is safety critical: an unsupported selector
   would silently be dropped by the parser with no error, leaving the chip
   stacked on the QR plate on air. */
body.cert-docked #vaultchip{display:none}
/* THE BREAK has no vault at all. Every buyer keeps the teams they bought,
   nothing sweeps to the season pot and nothing is ever added to it, which is
   why chrome.js's showBreakWinner has no vault bump where showRipWinner and
   showWinner both do, and why breakWinnerCopy refuses to claim a sweep in
   words. The chip aired anyway for the whole of a break whenever the season
   pot happened to be carrying a balance from another format's episode, and
   then vanished at the winner beat (body.break-winner-on, below). A figure
   that has nothing to do with this show, on screen for most of it and gone
   exactly when a viewer might look at it, is worse than either.

   So it yields for the format, on the class chrome.js sets for a break's
   whole life. Break scoped: nothing here changes any other format's pixels,
   where this chip is real furniture (test/composite.test.ts's
   OVERLAY.vaultchip). From the body class and never :has(), the same reason
   the two rules either side of this one give. */
body.break-on #vaultchip{display:none}

/* ── decision panel ── */
#decision{position:absolute;right:var(--safe);bottom:var(--safe);z-index:var(--z-lower);width:660px;
  padding:12px 20px 0;display:flex;flex-direction:column;justify-content:center;box-shadow:var(--sh-lg);
  clip-path:polygon(0 0,100% 0,100% calc(100% - 22px),calc(100% - 22px) 100%,0 100%)}
#decision .drow.top{display:flex;align-items:baseline;justify-content:space-between;gap:18px}
#decision .next{font-family:var(--font-mono);font-weight:600;font-size:var(--t-1);color:var(--money);white-space:nowrap}
#decision .question{font-family:var(--font-display);font-size:34px;letter-spacing:.5px;color:var(--ink);
  margin:4px 0 10px;line-height:1.1}
#decision .choices{display:flex;border-top:2px solid var(--hairline);margin:0 -20px}
#decision .choices span{flex:1;text-align:center;padding:8px 4px 10px;font-family:var(--font-mono);font-weight:600;
  font-size:var(--t-1);letter-spacing:.5px;white-space:nowrap}
#decision .choices span + span{border-left:2px solid var(--hairline)}
#decision .choices span:nth-child(1){color:var(--truth)}
#decision .choices span:nth-child(2){color:var(--broker)}
#decision .choices span:nth-child(3){color:var(--money)}

/* ── show open title ── */
#title{position:absolute;inset:0;z-index:var(--z-flash);display:grid;place-items:center;pointer-events:none;opacity:0}
#title.show{opacity:1}
#title .tcard{text-align:center;transform:scale(1.3);opacity:0;background:var(--ticket);
  border:2px solid var(--hairline-hi);border-left:5px dotted var(--hairline-hi);
  box-shadow:var(--sh-hero);padding:40px 80px 52px}
#title.show .tcard{animation:slamc .42s var(--ease-slam) forwards}
@keyframes slamc{0%{opacity:0;transform:scale(1.3)}100%{opacity:1;transform:scale(1)}}
#title.bye .tcard{animation:titleout .38s var(--ease-exit) forwards}
@keyframes titleout{100%{opacity:0;transform:scale(.85)}}
#title .tmark{height:64px;display:block;margin:0 auto 14px}
#title .t1{font-family:var(--font-mono);font-weight:600;font-size:var(--t-1);letter-spacing:4px;color:var(--ink-dim)}
#title .t2{line-height:1;margin-top:18px}
#title .t2 img{height:140px;display:block}

/* ── player intro ── */
#intro{position:absolute;inset:0;z-index:var(--z-flash);display:none;place-items:center;pointer-events:none}
#intro.on{display:grid}
#intro .icard{width:840px;padding:38px 52px;text-align:center;box-shadow:var(--sh-hero);
  border-left:5px dotted var(--hairline-hi);animation:cardin2 .42s var(--ease-slam) both}
#intro.bye .icard{animation:titleout .32s var(--ease-exit) forwards}
#intro .ieyebrow{font-family:var(--font-mono);font-weight:600;font-size:var(--t-1);letter-spacing:3px;
  color:var(--truth);text-transform:uppercase}
#intro .iname{font-family:var(--font-display);font-size:100px;line-height:1;margin:8px 0 24px}
#intro .istats{display:flex;border-top:2px solid var(--hairline)}
#intro .istat{flex:1;padding:14px 10px}
#intro .istat + .istat{border-left:2px solid var(--hairline)}
#intro .istat .iv{font-family:var(--font-mono);font-weight:600;font-size:var(--t-3);margin-top:6px;font-variant-numeric:tabular-nums}
#intro .istat.rec .iv{color:var(--ink)}
#intro .istat.streak .iv{color:var(--money)}
#intro .istat.best .iv{color:var(--truth)}

/* ── technical hold: deliberately calm. No rotation, no hard shadow. ── */
#hold{position:absolute;inset:0;z-index:95;display:none;place-items:center;background:rgba(5,7,11,.88)}
#hold.on{display:grid}
#hold .holdcard{padding:34px 56px;text-align:center;border-color:var(--hairline-hi);box-shadow:none}
#hold .holdtitle{font-family:var(--font-display);font-size:36px;letter-spacing:3px;color:var(--ink)}
#hold .holdsub{margin-top:12px;font-family:var(--font-mono);font-size:var(--t-1);letter-spacing:2px;color:var(--ink-dim)}

/* ── settlement: the receipt ── */
/* The scrim is light on purpose: the quad hugs the corners around this card,
   and four faces reacting are the applause. A .72 scrim was burying them. */
#settle{position:absolute;inset:0;z-index:var(--z-flash);display:none;place-items:center;background:rgba(4,6,10,.45)}
#settle.on{display:grid}
#settle .scard{width:780px;padding:0;box-shadow:16px 16px 0 rgba(0,0,0,.5);
  border-left:5px dotted var(--hairline-hi);animation:cardin2 .38s var(--ease-slam) forwards}
#settle .shead{display:flex;align-items:center;justify-content:space-between;gap:16px;
  padding:18px 32px;border-bottom:2px solid var(--hairline)}
#settle .stitle{font-family:var(--font-display);font-size:36px;letter-spacing:3px;color:var(--ink)}
#settle .shead .tstamp{--rot:4deg;color:var(--broker);font-size:26px;border-width:3px}
#settle .sname{font-family:var(--font-display);font-size:56px;line-height:1;color:var(--ink);padding:20px 32px 6px}
#settle .srows{display:flex;flex-direction:column;gap:10px;padding:14px 32px 6px}
#settle .srow{display:flex;align-items:baseline;gap:12px}
#settle .srow .label{white-space:nowrap}
#settle .srow .leader{flex:1;border-bottom:2px dotted var(--hairline);transform:translateY(-6px)}
#settle .srow .sv{font-family:var(--font-mono);font-weight:600;font-size:var(--t-3);color:var(--ink)}
#settle .srow .sv.money{color:var(--money)}
#settle .srow .sv.truth{color:var(--truth)}
#settle .stotal{display:flex;align-items:center;justify-content:space-between;gap:20px;
  margin-top:16px;padding:16px 32px;background:var(--ticket-strip);border-top:2px solid var(--hairline)}
#settle .stotal .wl{font-family:var(--font-display);font-size:30px;letter-spacing:2px;color:var(--ink)}
#settle .stake{position:relative;overflow:hidden;display:inline-block;font-family:var(--font-display);font-size:84px;
  line-height:1;background:var(--money);color:var(--money-ink);border:2px solid var(--money-ink);
  padding:2px 22px 6px;box-shadow:8px 8px 0 rgba(0,0,0,.5)}
#settle.on .stake::after{content:"";position:absolute;inset:0;
  background:linear-gradient(105deg,transparent 42%,rgba(255,255,255,.35) 50%,transparent 58%);
  transform:translateX(-110%);animation:sweep var(--d-hero) var(--ease-out) 1s forwards}
#settle .sitems{padding:14px 32px;font-family:var(--font-mono);font-size:var(--t-1);color:var(--ink-dim);line-height:1.5}
#settle .sverify{display:flex;align-items:center;gap:14px;padding:12px 32px 18px;
  border-top:2px dotted var(--hairline);font-family:var(--font-mono);font-size:var(--t-1);color:var(--ink-dim)}
#settle .sverify b{color:var(--ink);font-weight:400}
#settle .sverify .barcode{width:88px;height:24px;margin-left:auto;
  background:repeating-linear-gradient(90deg,var(--ink) 0 2px,var(--ticket) 2px 5px,var(--ink) 5px 6px,var(--ticket) 6px 10px)}

/* ── box battles ──
   Sides are told apart by position and two neutral hues. Gold stays money and
   cyan stays platform truth: the Vault chip airs in this same frame, and both
   players' item values are platform-verified, so neither colour can double as
   a player identity. (Deliberate deviation from the handoff, recorded in the
   playbook.) */
#battle{position:absolute;inset:0;display:none}
#battle.on{display:block}
/* Battle claims the frame. The lot ticket is redundant here: the player
   columns carry both names and both totals, and the tug bar owns the band the
   ticket would sit in. The left rail moves to the open centre, because the
   columns own both edges. */
body.battle-on #lower{display:none}
/* THE BREAK has no ship-or-flip lower third: no rung, and no playable/banked
   ledger for a single contestant, because the score lives on each buyer's own
   window (stage.css's .scorechip). The old left rail board is retired for
   this format and main.js never calls renderBoard again, so the sentence that
   used to stand here, naming #breakBoard as this format's scoreboard, was
   describing a graphic that no longer airs.
   This rule is load bearing, not tidiness. The lower third's box is x88-1117,
   y842-992, and BOTH graphics this format puts on the title-safe floor sit in
   it: the pack counter (x840-1080, y866-900) is inside it whole, and the
   caption band (x708-1212, y904-992) overlaps it 409x88. Neither was ever
   measured against a lower third that airs, because in this format it does
   not. Pinned by test/composite.test.ts's "the lower third stands down for
   the whole of a break", which is what fails if this line is deleted; every
   break scene's furniture list is silent about #lower on the strength of it.
   From a class and never :has(), the same reason the two rules either side
   give. */
body.break-on #lower{display:none}
/* #breakBoard is not a #slot tenant, but it sits on the exact same left rail
   #slot occupies (both left:88px, both starting well above the safe floor),
   which "not a #slot tenant" above does not actually protect against: on-air
   review, finding 5 found CALL THE HIT (panels.js's showCrowd, opened per
   pack pull for this format too, see ingestBreakPackPull) rendering
   completely underneath the board, invisible, because the board is appended
   to #stage later and wins the tie at equal z-index (--z-panel). body.slot-on
   is set in JavaScript (panels.js's showCrowd/brokerOffer/hideSlot) the
   moment #slot actually has a tenant, so the two graphics take turns instead
   of stacking, the same explicit-class yield grammar body.battle-on #lower
   and body.settle-on #ladder already use here: never a :has() selector,
   because an OBS Chromium without :has() support drops such a rule silently
   and the graphics stack on air with no error to catch it. */
body.slot-on #breakBoard{display:none}
body.settle-on #lower{display:none}
body.settle-on #ladder{display:none}
body.settle-on #lever{display:none}
/* The left rail yields too. A decision grade that lingered into settlement
   sat on the bottom left quad window, on a recorded demo, over a face. The
   settlement scene is four faces and a receipt, nothing else. */
body.settle-on #slot{display:none}
body.settle-on #decision{display:none}
/* The vault chip yields for settlement too (Task 12): "four faces and a
   receipt, nothing else" was already the rule for slot and decision above,
   and the vault chip is the same kind of persistent chrome, not part of the
   receipt. See qrplate.js for the QR plate's own top-right placement, which
   is what this corner is cleared for during settlement. */
body.settle-on #vaultchip{display:none}
/* Task 13 review, Finding 5: the rip battle winner shell is full frame but,
   unlike settlement, does not itself hide #battle, which only dims under
   the winner scrim (see #winner above). The QR plate now airs at this beat
   too (chrome.js's showRipWinner), the funnel's one on-air surface for a
   format that never reaches settlement, and #battle's own scoreboard
   ticket sits right under the plate's usual top-right corner. There is
   nothing left to say once the winner is named, so the whole rig stands
   down rather than fighting the plate for the same real estate, the same
   "nothing else" standard settlement already holds itself to above. Driven
   from this explicit body class (set in showRipWinner, cleared with the
   shell), never :has(), for the same reason cert-docked and settle-on are:
   an OBS Chromium without :has() support drops the rule silently and the
   graphics stack on air with no error to catch it. */
body.rip-winner-on #battle{display:none}
body.rip-winner-on #vaultchip{display:none}
/* Task 14, THE BREAK's own winner beat (chrome.js's showBreakWinner). A
   break never reaches ship-or-flip's own settlement (episode.settlement is
   never set for this format, so showSettlement's own `if (!s...) return`
   bails and body.settle-on never fires), and the board itself already
   stands down at settlement on its own (main.js's renderAll, guarded on
   ep.status !== "settled"). What is left standing is the vault chip, the
   same persistent chrome settlement and the rip winner beat both stand down
   for the same reason: the QR plate now airs at this beat too, in the same
   top-right corner. Driven from this explicit body class (set in
   showBreakWinner, cleared with the shell), never :has(), for the same OBS
   Chromium reason the rip-winner-on rule above gives. */
body.break-winner-on #vaultchip{display:none}
/* The winner shell's centred text block defaults to a 1000px cap
   (#winner>div above), proven safe against battle-two-shot's corner head
   zones (x257-389 and x1531-1663) by the rip winner beat. A break settles
   into "quad" (episodes.ts has no special case for this format, so
   cast.ts's desiredLayout falls through to the same `episode.status ===
   "settled"` branch every format shares), and quad's own corner windows sit
   much closer to centre: headBox() puts their head zones at x305-491 and
   x1429-1615 (see test/composite.test.ts's OVERLAY.breakWinnerShell
   comment for the arithmetic). The 1000px cap collides with both corners
   there by a real 31x34px; this narrower cap is what clears them, with
   margin to spare. */
body.break-winner-on #winner>div,
body.break-winner-on #winner .wtext,
body.break-winner-on #winner .wsub{max-width:800px}
/* On-air review, finding 4: the 800px cap above was proven safe against
   quad's corner head zones, but never checked against its own copy. wText
   inherits #winner .wtext's 110px font size and stays nowrap with an
   ellipsis (overlay.css above), so "MARCUS WEBB WINS" (a real buyer name
   from the demo roster) measured a real scrollWidth of 864px against the
   800px clientWidth and aired as "MARCUS WEBB WI...". Any name over about
   nine characters clipped, which is not a realistic name length floor for a
   buyer-entered field. Scoped to this beat only (rip and battle winners keep
   their own 1000px cap and 110px size, already proven against their own
   copy): 68px, measured live, holds every two-word name up to about twenty
   characters, "MORGAN BLACKWOOD WINS" included (724px), with real margin to
   spare; "CHRISTOPHER ABERNATHY WINS" (21 characters) is the first of the
   tested names to still clip (828px), and clips the same honest way every
   other headline in this package does past its own ceiling: an ellipsis,
   not a silent cut. */
body.break-winner-on #winner .wtext{font-size:68px}
body.battle-on #slot{left:50%;transform:translateX(-50%);bottom:248px}
:root{--team-a:#9db4d0;--team-b:#d0b48a}
.bhalf{position:absolute;top:280px;width:470px;padding:0;background:var(--ticket);
  border:2px solid var(--hairline-hi);box-shadow:var(--sh-md)}
.bhalf.p1{left:var(--safe);border-left:4px dotted var(--team-a)}
.bhalf.p2{right:var(--safe);border-right:4px dotted var(--team-b);text-align:right}
.bhalf .bname{display:block;font-family:var(--font-display);font-size:38px;letter-spacing:1px;
  text-transform:uppercase;color:var(--ink);padding:12px 18px 0;border:none;background:none}
.bhalf .btotal{display:inline-block;font-family:var(--font-display);font-size:40px;line-height:1;
  font-variant-numeric:tabular-nums;margin:8px 18px 12px;padding:2px 14px 4px;border:2px solid var(--money-ink)}
.bhalf.p1 .btotal{background:var(--team-a);color:var(--money-ink)}
.bhalf.p2 .btotal{background:var(--team-b);color:var(--money-ink)}
.bhalf .btotal.bump{animation:stampnum .32s var(--ease-slam)}
.brounds{display:flex;gap:6px;margin:0 18px 12px}
.bhalf.p2 .brounds{justify-content:flex-end}
.brounds .rchip{min-width:40px;height:28px;border:2px solid var(--hairline);background:#0a0c12;
  display:grid;place-items:center;padding:0 5px;
  font-family:var(--font-mono);font-weight:600;font-size:15px;letter-spacing:1px;color:var(--ink-dim)}
.brounds .rchip.won{border-color:var(--g-a);color:var(--g-a)}
.brounds .rchip.lost{opacity:.4}
.brounds .rchip.draw{border-color:var(--ink-dim);color:var(--ink-dim)}
.brounds .rchip.live{border-color:var(--money);color:var(--money);animation:armpulse 1s linear infinite}
.brounds .rchip.ot{border-style:dashed}
.bhalf .bpulls{display:flex;flex-direction:column;border-top:2px solid var(--hairline);padding:6px 18px 12px}
.bpull{display:flex;gap:12px;align-items:baseline;padding:7px 0;border-bottom:2px dotted var(--hairline);
  font-family:var(--font-mono);font-size:var(--t-1);color:var(--ink);
  animation:cardin2 .32s var(--ease-slam) both;border-left:none;background:none;clip-path:none}
.bpull:last-child{border-bottom:none}
.bhalf.p2 .bpull{flex-direction:row-reverse}
.bpull span:first-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.bpull .bval{color:var(--truth);font-weight:600;font-variant-numeric:tabular-nums;margin-left:auto}
.bhalf.p2 .bpull .bval{margin-left:0;margin-right:auto}
/* The tug of war: the format's signature, full width above the safe floor. */
#tug{position:absolute;left:var(--safe);right:var(--safe);bottom:180px;height:48px;transform:none;
  background:#0a0c12;border:2px solid var(--hairline-hi);overflow:hidden;box-shadow:8px 8px 0 rgba(0,0,0,.5)}
#tug .t1{position:absolute;top:0;bottom:0;left:0;width:50%;height:auto;background:var(--team-a);
  border-right:4px solid var(--ink);transition:width .8s var(--ease-out)}
#tug .t2{position:absolute;top:0;bottom:0;right:0;width:50%;height:auto;background:var(--team-b);
  transition:width .8s var(--ease-out)}
#tug .tlabel{position:absolute;top:50%;transform:translateY(-50%);font-family:var(--font-mono);font-weight:600;
  font-size:var(--t-2);color:var(--money-ink);white-space:nowrap}
#tug .t1 .tlabel{left:14px}
#tug .t2 .tlabel{right:14px}
#tug.flip{box-shadow:8px 8px 0 rgba(0,0,0,.5),0 0 0 2px var(--money),0 0 18px rgba(255,198,61,.5)}
#tug.tied .t1{border-right-color:var(--broker);animation:armpulse 1.1s linear infinite}
#bvs{position:absolute;left:50%;top:300px;transform:translateX(-50%) rotate(-4deg);
  font-family:var(--font-display);font-size:54px;color:var(--ink);letter-spacing:2px;
  background:var(--ticket);border:3px solid var(--ink);padding:2px 20px 6px;box-shadow:var(--sh-md)}
#bround{position:absolute;left:50%;top:150px;transform:translateX(-50%);
  font-family:var(--font-mono);font-weight:600;font-size:var(--t-2);letter-spacing:3px;color:var(--ink);
  text-transform:uppercase;background:var(--ticket);border:2px solid var(--hairline-hi);padding:5px 18px;
  box-shadow:var(--sh-sm)}
#bsit{position:absolute;left:50%;top:214px;transform:translateX(-50%) rotate(-2deg);display:none;
  font-family:var(--font-display);font-size:26px;letter-spacing:2px;padding:4px 22px 6px;
  border:2px solid currentColor;background:var(--ticket);white-space:nowrap;color:var(--ink-dim)}
#bsit.on{display:block}
#bsit.matchpoint{color:var(--broker)}
#bsit.clinched{color:var(--money)}
#bsit.tied{color:var(--broker);animation:armpulse 1.1s linear infinite}
#bsit.overtime{color:var(--broker)}
#bleadflag{position:absolute;left:50%;bottom:158px;transform:translate(-50%,0) rotate(-3deg) scale(2);opacity:0;
  font-family:var(--font-display);font-size:44px;letter-spacing:2px;color:var(--money-ink);
  border:3px solid var(--money-ink);padding:4px 26px 8px;background:var(--broker);
  box-shadow:10px 10px 0 rgba(0,0,0,.5);pointer-events:none;z-index:var(--z-lower)}
#bleadflag.go{animation:leadflag 1.9s var(--ease-slam) forwards}
@keyframes leadflag{
  0%{opacity:0;transform:translate(-50%,0) rotate(-3deg) scale(2)}
  14%{opacity:1;transform:translate(-50%,0) rotate(-3deg) scale(1)}
  75%{opacity:1}
  100%{opacity:0;transform:translate(-50%,0) rotate(-3deg) scale(1.06)}}
#winner{position:absolute;inset:0;z-index:var(--z-flash);display:none;place-items:center;background:rgba(4,6,10,.6)}
#winner.on{display:grid}
/* Task 13 review, Finding 5: the winner shell is full frame but does not
   hide battle-two-shot's face windows (see body.rip-winner-on below, which
   only stands the battle scoreboard down, not the underlying two-shot).
   Uncapped, a long enough matchup name pushes this centred block wide
   enough to sit under either corner's head zone (head zone edges measure at
   x389 and x1531 against this layout); 1000px centred lands at x460-1460,
   a real 71px clear of each, not a guessed number. Every line here clips to
   an ellipsis rather than wrapping, because a taller block risks the same
   head zones from below instead of the side. */
#winner>div{max-width:1000px}
/* THE BREAK's own winner beat keeps its cast on screen (a settled break stays
   in break-quad, see cast.ts), and break-quad carries a score chip across the
   bottom of every window: the top pair sits at y446-486 and this block,
   centred on a 1080 frame, measured y480.35-599.65, reaching 8px into both.
   Nudged down so the block clears them with a real margin; the room is there,
   because the bottom pair of chips is 200px below the block's own floor. */
body.break-winner-on #winner>div{transform:translateY(28px)}
#winner .wtext{font-family:var(--font-display);font-size:110px;letter-spacing:2px;text-align:center;line-height:1.1;
  color:var(--money);animation:slamc .5s var(--ease-slam) both;
  max-width:1000px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
#winner .wsub{font-family:var(--font-mono);font-size:var(--t-2);letter-spacing:1px;color:var(--ink);margin-top:12px;text-align:center;
  max-width:1000px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* ── safe-area variants (?safe=captions,platform,sponsor) ── */
body.safe-captions #lower{bottom:190px}
body.safe-captions #decision{bottom:190px}
body.safe-captions #lever{bottom:calc(190px + 152px)}
body.safe-captions #slot{bottom:calc(190px + 172px)}
body.safe-captions #tug{bottom:282px}
body.safe-captions #bleadflag{bottom:260px}
body.safe-captions #opHud{display:none}
body.safe-sponsor #fair{top:132px}
body.safe-sponsor #vaultchip{top:214px}
#sponsorBug{position:absolute;right:var(--safe);top:44px;z-index:var(--z-fair);text-align:right;
  background:var(--ticket);border:2px solid var(--hairline-hi);border-top:3px solid var(--truth);padding:8px 18px;
  box-shadow:var(--sh-sm)}
#sponsorBug .sp-label{display:block;font-family:var(--font-mono);font-size:15px;font-weight:600;
  letter-spacing:2px;color:var(--ink-dim);text-transform:uppercase}
#sponsorBug .sp-name{display:block;font-family:var(--font-display);font-size:var(--t-2);color:var(--ink);letter-spacing:1px}

/* Ad break: retreat to bug + fairness strip. The commitment stays on air. */
body.ad-break #lower,body.ad-break #slot,body.ad-break #ladder,body.ad-break #lever,
body.ad-break #decision,body.ad-break #reveal,body.ad-break #battle,body.ad-break #brokerCam,
body.ad-break #replayBug,body.ad-break #vaultchip{display:none!important}

/* ── output modes ── */
body.clean-feed #bug,body.clean-feed #slot,body.clean-feed #lower,body.clean-feed #lever,
body.clean-feed #fair,body.clean-feed #decision,body.clean-feed #ladder,body.clean-feed #reveal,
body.clean-feed #battle,body.clean-feed #vaultchip,body.clean-feed #settle,body.clean-feed #winner{display:none!important}

#safeGuide{position:absolute;inset:54px 96px;z-index:90;border:1px dashed rgba(63,216,255,.5);pointer-events:none;display:none}
#safeGuide::after{content:"ACTION SAFE";position:absolute;right:8px;top:6px;font:600 12px var(--font-mono);
  letter-spacing:.08em;color:rgba(63,216,255,.75)}
#safeGuide.on{display:block}
#camGuide{position:absolute;inset:0;z-index:90;pointer-events:none;display:none}
#camGuide.on{display:block}
#camGuide div{position:absolute;border:1px dashed rgba(255,198,61,.45)}
#camGuide div::after{position:absolute;left:6px;top:5px;font:600 11px var(--font-mono);letter-spacing:.08em;
  color:rgba(255,198,61,.7)}
#camGuide .cam-centre{left:640px;top:150px;width:640px;bottom:190px}
#camGuide .cam-centre::after{content:"CENTRE CAMERA · CONTESTANT"}
#camGuide .cam-product{left:1320px;top:600px;width:330px;height:300px}
#camGuide .cam-product::after{content:"PRODUCT CAMERA"}

/* ── preview-only operator HUD ── */
#opHud{position:fixed;left:0;right:0;bottom:0;z-index:999;background:rgba(6,9,14,.94);border-top:1px solid var(--hairline);
  padding:5px 14px;font-family:var(--font-mono);font-size:11px;letter-spacing:.04em;color:#9fb2c8;line-height:1.6;
  display:flex;gap:24px;flex-wrap:wrap;align-items:baseline}
#opHud .hud-row{display:flex;gap:14px;flex-wrap:wrap}
#opHud b{color:var(--ink);font-weight:700}
#opHud b.live{color:var(--g-a)}
#opHud b.reconnecting,#opHud b.stale{color:var(--broker)}
#opHud b.connecting{color:var(--money)}
#opHud .hud-keys{display:flex;gap:12px;flex-wrap:wrap;color:#5d6675;margin-left:auto}
#opHud .hud-keys b{color:var(--truth)}

/* ── rip battles: cards, not boxes ──
   Cascade tiles and the tier chip reuse the show's existing rarity ramp
   (RARITY_COLORS, keyed by --rc per element) rather than a second palette.
   The certificate itself is the shared #card the reveal sequence already
   owns above: a priced hit under a match that is not value free reuses
   reveal.js's landReveal wholesale, so its look needs no new rules here.
   Every other hit (comp null, or any hit at all under a value free match)
   lands through rip.js's own staticCertificate instead, which writes the
   value straight into this same #cValue element, so its look needs only
   the one variant below: dim ticket paper for a value this certificate
   genuinely does not know, never for a points figure it does know. */
#card .value.unpriced{background:var(--ticket);color:var(--ink-dim);border-color:var(--paper-ink)}

/* The chase card's full frame flag: the one new treatment in the package.
   Everything else in a rip battle reuses the existing reveal ramp as is. */
body.rip-chase #vignette{opacity:1}
body.rip-chase #card{box-shadow:var(--sh-hero),0 0 140px color-mix(in srgb,var(--rarc,#ffc63d) 60%,transparent)}

/* The cascade strip: base cards fly past fast, centre stage, in the same
   spot the crate and certificate already occupy, so a hit's ignition simply
   takes over the space the strip was just holding.

   Measured, not guessed. A fixed width keeps this box stable regardless of
   how many base card tiles are actually on screen (rip.js caps the tile count
   to fit it), so the box below is a real ceiling: x580-1340, y780-840. In a
   rip battle that airs over battle-two-shot (see test/composite.test.ts's
   "battle" scene), whose windows sit at x103-543 and x1377-1817, so the strip
   never reaches a face at all, and it sits above the tug bar (starts y852) by
   12px.

   THE BREAK airs this same strip: a break's own pack reveal reuses rip.js's
   cascade wholesale (main.js's "break.revealed" case), which the comment here
   used to deny outright by saying rip battles only ever air on
   battle-two-shot. Under break-quad there IS something at the bottom of the
   frame: a score chip across the bottom of all four windows, y822-862.
   Measured live over a whole twelve pack case, this strip reached 18px into
   the bottom pair of them over the last 126px of its own width, and the tiles
   actually drawn inside it by 35x15 each (review of Tasks 7, 8 and fix wave
   2, finding I9). So the break lifts it 40px, and only the break: the strip
   is byte-identical in every other format. It has no head to clear on either
   side either way (x580-1340 never reaches break-quad's own head columns at
   x305-491 and x1429-1615), so the chips were the whole question, and 22px
   is the answer. If either box ever moves, move OVERLAY.ripCascade or
   OVERLAY.breakCascade in composite.test.ts with it. */
#ripCascade{position:absolute;left:50%;bottom:240px;transform:translateX(-50%);z-index:var(--z-reveal);
  width:760px;height:60px;overflow:hidden;display:flex;justify-content:center;align-items:center;
  gap:10px;pointer-events:none}
body.break-on #ripCascade{bottom:280px}
#ripCascade.hidden{display:none}
.rip-basecard{display:flex;align-items:center;gap:8px;padding:6px 12px;background:var(--ticket);
  border:2px solid var(--hairline-hi);box-shadow:var(--sh-sm);opacity:0;
  animation:ripcard .32s var(--ease-slam) forwards}
@keyframes ripcard{0%{opacity:0;transform:translateY(10px) scale(.9)}100%{opacity:1;transform:translateY(0) scale(1)}}
.rip-basecard-name{font-family:var(--font-mono);font-size:var(--t-1);color:var(--ink-dim);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:90px}
.rip-tierchip{display:inline-block;font-family:var(--font-mono);font-weight:600;font-size:15px;
  letter-spacing:1px;padding:2px 8px 3px;border:2px solid var(--rc,var(--hairline-hi));color:var(--rc,var(--ink));
  background:var(--ticket);white-space:nowrap}

/* Brick and split stamps reuse .tstamp, the same rotated rubber stamp every
   other rec/settled/cleared stamp in the package uses. A brick airs as loud
   as a hit, same size and weight as the split stamp below, but red is
   spoken for: this package uses it for the Broker, and rip battles have no
   Broker mechanic at all, so a brick does not get to borrow that meaning.
   Dust, the bottom of the same rarity ramp brickStamp's own tier already
   maps to (a brick is thematically the base tier, nothing hit), keeps the
   stamp its own real result without stealing a colour that means something
   else in this show. */
.tstamp.rip-brick{--rot:-4deg;color:var(--r-dust);font-size:34px;padding:4px 20px 6px}
.tstamp.rip-split{--rot:3deg;color:var(--ink-dim);font-size:30px}

/* Per pack lot ticket, replacing box-battles' per-card bpull rows inside the
   same .bhalf .bpulls column. */
.rip-packticket{display:flex;align-items:baseline;justify-content:space-between;gap:12px;
  padding:8px 14px;margin:6px 0;box-shadow:var(--sh-sm);border-left:3px dotted var(--hairline-hi)}
.bhalf.p2 .rip-packticket{flex-direction:row-reverse;border-left:none;border-right:3px dotted var(--hairline-hi)}
.rip-pt-name{font-family:var(--font-mono);font-size:var(--t-1);color:var(--ink);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rip-pt-total{font-family:var(--font-mono);font-weight:600;font-size:var(--t-1);color:var(--truth)}

/* The show open's ruleset explainer, appended into #intro's own plate. */
.icard.rip-intro .istats{display:none}
.rip-rulesetcard{margin-top:18px;padding-top:16px;border-top:2px solid var(--hairline);text-align:left}
.rip-rc-head{display:flex;align-items:center;justify-content:space-between;gap:14px}
.rip-rc-title{font-family:var(--font-display);font-size:var(--t-4);letter-spacing:1px;color:var(--money)}
.tstamp.rip-vf{--rot:3deg;color:var(--truth);font-size:var(--t-1)}
.rip-rc-line{margin-top:8px;font-family:var(--font-mono);font-size:var(--t-1);letter-spacing:1px;color:var(--ink-dim)}

/* The champion belt: name and defence count. Airs inline in the winner card
   at the rip battle's own winner beat (see showRipWinner in chrome.js). A
   persistent corner chip was the first draft; it does not survive
   measurement against every layout's head zones the way qrplate.js's own
   plate had to (that corner is already fought over by the fairness strip,
   the vault chip and the QR plate), so the belt stays scoped to the one
   moment it airs.

   That moment is NOT actually free of video underneath it: battle-two-shot
   stays up under the winner scrim (see the OVERLAY.ripWinnerShell comment
   in test/composite.test.ts), so the belt gets the same width ceiling as
   #winner's own text above it, for the same reason: a long enough champion
   name would otherwise reach a corner's head zone. */
.rip-championbar{display:flex;align-items:center;gap:12px;justify-content:center;margin-top:18px;max-width:1000px;overflow:hidden}
.rip-championbar .label{color:var(--money);white-space:nowrap}
.rip-champ-name{font-family:var(--font-display);font-size:var(--t-4);color:var(--ink);letter-spacing:.5px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:520px}
.tstamp.rip-defend{--rot:-3deg;color:var(--money);font-size:var(--t-1);white-space:nowrap}
.tstamp.rip-defend.new{color:var(--truth)}

/* Ripper-keeps / consigned: the sweep is notional, so the settlement card
   says so in its own stamp rather than the dollar vault chip (which stays
   hidden for this whole card, see body.rip-winner-on #vaultchip below). Same
   rotated-stamp family as rip-defend above, sized to sit under the champion
   bar without pushing the shell past its own measured width. */
.tstamp.rip-vaultpoints{--rot:2deg;color:var(--truth);font-size:var(--t-1);white-space:nowrap;margin-top:10px}

/* ── THE BREAK: the board, the commitment card, the allocation ceremony ──
   One case shared by many buyers, each holding teams (spots); a card lands
   with whoever holds its team. Cyan throughout, not gold or purple: the
   board is the ledger of what the case actually paid out, the same platform
   truth the commitment and every comp already carry (this format has no
   Broker and no crowd mechanic, so red and purple are both correctly unused
   here).

   BREAK_BOARD in breakboard.js is this box's geometry, in pixels, and is the
   source of truth test/composite.test.ts's OVERLAY.breakBoard measures
   against: if this box ever moves, move that constant with it. Not a #slot
   tenant (it does not rotate out for a Broker or crowd panel the way this
   format has neither of), so it gets its own fixed position rather than
   #slot's bottom-anchored column.

   180/790 (on-air review, finding 1): the board used to be 620 tall and
   still overflowed at four buyers (a 183px row put the fourth entirely past
   clientHeight, on a passive OBS graphic nothing can scroll into view; the
   hidden row that day was the leader and eventual winner). Grown here to
   the most this rail can hold inside the frame: top 180 clears the fairness
   strip above (ends y153) by a real 27px, the same margin stage-geometry.js's
   pip() uses against the same strip and for the same reason; bottom 970
   clears the title-safe floor (992) by a real 22px. overflow-y:auto stays
   as a defensive floor, not the fix: the fix is the denser .brk-row below,
   verified live to hold ten rows in this height with zero scrollHeight
   overflow. */
#breakBoard{position:absolute;left:88px;top:180px;width:520px;height:790px;z-index:var(--z-panel);
  border-color:var(--truth);border-left-color:var(--truth);overflow-y:auto}
#breakBoard.hidden{display:none}
#breakBoard .ptitle{color:var(--truth)}
#breakBoard .ptag{color:var(--truth);border-color:var(--truth)}
.brk-rows{display:flex;flex-direction:column;gap:4px}
.brk-row{padding:4px 12px;background:var(--ticket-strip);border:2px solid var(--hairline);
  border-left:3px dotted var(--hairline-hi)}
/* The leader's row is called out the same way a leading ledger row is
   elsewhere in the package: a gold inset ring, not a colour swap, since
   cyan on this board already means something else (platform truth). */
.brk-row.leader{border-color:var(--money);box-shadow:inset 0 0 0 2px var(--money)}
/* One line for identity and money, one line for holdings: two lines per row,
   not four (on-air review, finding 1). The name shrank from var(--t-4) so a
   realistic field of buyers reads without forcing every row taller than the
   panel can hold; it still ellipsizes rather than wrapping, so one very long
   name can never blow out a row's height by itself. */
.brk-rhead{display:flex;align-items:baseline;gap:8px}
.brk-name{flex:1;min-width:0;font-family:var(--font-display);font-size:22px;color:var(--ink);
  letter-spacing:.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tstamp.brk-leader{--rot:-3deg;color:var(--money);font-size:13px;padding:1px 6px 2px;white-space:nowrap;flex:0 0 auto}
.brk-total{flex:0 0 auto;font-family:var(--font-mono);font-weight:600;font-size:19px;color:var(--truth)}
/* Holdings and card count share the second line, truncated to one line
   rather than wrapping: a buyer holding many spots used to wrap this line
   across two or three, which was the other half of why a row could reach
   183px tall. A viewer who needs the exact list can still read it off the
   allocation ceremony that just aired it, one spot at a time; the board's
   job is the standing, not a re-listing every spot lives on screen. */
.brk-spots{margin-top:3px;font-family:var(--font-mono);font-size:13px;letter-spacing:.5px;color:var(--ink-dim);
  text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.brk-cardcount{color:var(--ink-dim)}

/* The commitment card: both lists and their hash, before anything is drawn.
   Used to sit dead centre, full frame, like the show open and the reveal
   certificate: a dim scrim over the whole stage with the card grid-centred
   inside it. On-air review, finding 2: that centring is exactly what put it
   on top of host-single's own head zone (x735-1185, y219.28-708.8) for the
   card's whole 4.2s hold, every episode, because host-single's one face
   sits centred on the same 1920x1080 canvas the scrim covers: any box
   centred on that canvas straddles y540, which sits inside the head band
   regardless of the box's own width or height. Narrowing it the way
   breakWinnerShell's 800px cap clears quad's off-centre corner windows
   cannot work here for the same reason: there is no width that avoids a
   band whose centre matches the canvas's own centre. The fix is not
   narrower, it is lower: the scrim still covers the full frame (nothing
   behind it is meant to read while it holds), but the card itself now docks
   to the bottom safe band instead of the canvas centre, capped short enough
   to clear the head zone with real margin, verified live (see
   test/composite.test.ts's OVERLAY.breakCommitCard comment for the
   measurement). The buyer and spot lists scroll inside their own box if a
   large field does not fit; the hash above them, the one thing a viewer
   actually needs to check against /verify, never does. */
#breakCommit{position:absolute;inset:0;z-index:var(--z-flash);display:none;
  background:rgba(4,6,10,.82)}
#breakCommit.on{display:flex;align-items:flex-end;justify-content:center;padding-bottom:220px}
.brk-commitcard{width:460px;max-height:470px;display:flex;flex-direction:column;
  padding:14px 22px;box-shadow:var(--sh-hero);border-left:5px dotted var(--truth);
  animation:brkcommitin .42s var(--ease-slam) both;overflow:hidden}
/* The shared cardin2 entrance overshoots at scale(1.16), which this card can
   no longer afford: in the centre column it has a score chip 19px above it
   and another 23px below, and 16% of 480x270 is 38x43 of growth. It enters on
   rotation instead, which moves without growing, exactly the way the hero
   certificate's own break entrance does. Every other card in the package
   still uses cardin2 and is untouched. */
@keyframes brkcommitin{0%{opacity:0;transform:rotate(-1.4deg)}60%{opacity:1}100%{opacity:1;transform:none}}
.brk-commit-head{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:8px;
  flex:0 0 auto}
.brk-commit-title{font-family:var(--font-display);font-size:22px;letter-spacing:1px;color:var(--ink)}
.tstamp.brk-commit-tag{--rot:3deg;color:var(--truth);font-size:15px}
.brk-commit-counts{display:flex;gap:10px;margin-bottom:8px;flex:0 0 auto}
.tstamp.brk-commit-count{--rot:-2deg;color:var(--money);font-size:15px}
.brk-commit-lists{display:grid;grid-template-columns:1fr 1fr;gap:20px;padding:8px 0;
  border-top:2px solid var(--hairline);border-bottom:2px solid var(--hairline);
  flex:1 1 auto;min-height:0;overflow-y:auto}
.brk-commit-col .label{margin-bottom:4px}
.brk-commit-names{font-family:var(--font-mono);font-size:14px;color:var(--ink);line-height:1.5}
.brk-commit-rule{margin-top:8px;font-family:var(--font-mono);font-weight:600;font-size:13px;
  letter-spacing:1px;color:var(--truth);text-transform:uppercase;flex:0 0 auto}
.brk-commit-hash{margin-top:4px;font-family:var(--font-mono);font-size:14px;color:var(--ink-dim);
  word-break:break-all;flex:0 0 auto}
/* The drand round and its due time: the sentence a host can read aloud and a
   viewer can act on with a phone, so unlike the hash above it (which a
   viewer copies, never reads out) it sits at var(--t-1), this package's own
   21px legibility floor, in var(--font-support), the body face the rest of
   the package's readable copy uses, rather than the hash's own small
   var(--font-mono). Same var(--ink-dim) as the hash: both are the card's
   supporting proof line, not its headline. */
.brk-commit-round{margin-top:4px;font-family:var(--font-support);font-weight:600;
  font-size:var(--t-1);color:var(--ink-dim);flex:0 0 auto}

/* The allocation ceremony: one rubber stamp, landing on top of whatever else
   is on screen (the commitment card has already cleared by the time this
   runs), one spot at a time. Rhymes with #lockin's own centre bottom slot
   but is a real .tstamp (solid, rotated, shadowed) rather than the hollow
   held-breath look, because a spot landing is already a result, not a
   declaration waiting on a producer.

   Used to be centre stage, full width (bottom:230px, max-width:1200px,
   dead centred): that band is break-quad's own lower windows' territory now
   (window 3 and window 4's own head zones sit right under it, the two
   collisions composite.test.ts's break-quad review found). During the
   ceremony the case is not open yet, so the market strip above is not up
   and the centre column between the host inset (ends y396) and the caption
   band is genuinely free: this lands in that column instead. max-width
   shrank from 1200 to 460 to match the column's own width (see
   #breakMarket's comment above for where 708-1212 comes from), and the font
   from 34px to 24px, still a real 3px above the 21px floor this package
   never goes under.

   Review of Tasks 7, 8 and fix wave 2, findings M11 and M12, changed three
   more things about it, all measured:

   It may WRAP. A .tstamp is white-space:nowrap by default, and this one had
   text-overflow:ellipsis on top, so the five longest team names in each of
   the repo's two real sets were cut mid word ("VANTAGE POINT KESTRELS GOES
   TO MARCUS" wanted 489px in 456px). A ceremony whose whole job is to say
   which team landed on whom cannot ellipsis the team, so the line is
   released to two or three centred lines instead. Nothing shrinks: the font
   stays at 24px.

   The entrance overshoot is scale(1.25), not scale(1.6). This box has
   neighbours: at 1.6 it grew to x676-1244 on the way in, 30px into the score
   chip's column on each side at exactly chip height, the same argument the
   hero certificate's own entrance lost its overshoot to.

   And it sits at top:530, not top:460, which drops the whole band below the
   top pair of score chips (they end y486) with a real 19px of clearance
   rather than straddling them.

   See test/composite.test.ts's OVERLAY.breakAllocStamp comment for the
   measured envelope this CSS produces, entrance frames included. */
.brk-allocstamp{position:absolute;left:50%;top:530px;transform:translate(-50%,0) rotate(-3deg) scale(1.25);
  opacity:0;z-index:var(--z-reveal);color:var(--truth);font-size:24px;padding:6px 18px 8px;
  max-width:460px;white-space:normal;overflow-wrap:anywhere;text-align:center;line-height:1.15;
  pointer-events:none}
.brk-allocstamp.show{animation:brkstamp .9s var(--ease-slam) forwards}
@keyframes brkstamp{
  0%{opacity:0;transform:translate(-50%,0) rotate(-3deg) scale(1.25)}
  22%{opacity:1;transform:translate(-50%,0) rotate(-3deg) scale(1)}
  78%{opacity:1;transform:translate(-50%,0) rotate(-3deg) scale(1)}
  100%{opacity:0;transform:translate(-50%,0) rotate(-3deg) scale(1)}
}

/* ── reduced / off motion ── */
@media (prefers-reduced-motion:reduce){
  :root:not([data-motion="full"]) #crate.sh1,
  :root:not([data-motion="full"]) #crate.sh2,
  :root:not([data-motion="full"]) #crate.sh3{animation:none;opacity:1}
}
[data-motion="reduced"] #crate.sh1,[data-motion="reduced"] #crate.sh2,[data-motion="reduced"] #crate.sh3,
[data-motion="off"] #crate.sh1,[data-motion="off"] #crate.sh2,[data-motion="off"] #crate.sh3{animation:none;opacity:1}
[data-motion="reduced"] #flash.fire{animation:flashsoft .3s linear}
[data-motion="reduced"] #stage.quake,[data-motion="off"] #stage.quake{animation:none}
[data-motion="reduced"] #confetti i,[data-motion="off"] #confetti i{display:none}
[data-motion="reduced"] .tell.on,[data-motion="reduced"] .tell.grail-hold{animation:none;opacity:.45}
[data-motion="reduced"] #card.in,[data-motion="reduced"] #stamp.show,[data-motion="reduced"] #verdict.show,
[data-motion="reduced"] #title.show .tcard,[data-motion="reduced"] #settle .scard,[data-motion="reduced"] #winner .wtext,
[data-motion="reduced"] .brk-commitcard,[data-motion="reduced"] .brk-allocstamp.show,
[data-motion="reduced"] #bleadflag.go{animation-duration:.01ms;animation-timing-function:linear}
[data-motion="reduced"] .sweepable.swept .sweep,[data-motion="reduced"] #settle.on .stake::after{animation:none;opacity:0}
[data-motion="reduced"] #lockin .call,[data-motion="reduced"] #bug .live{animation:none}
[data-motion="reduced"] .rip-basecard,[data-motion="off"] .rip-basecard{animation-duration:.01ms}
[data-motion="off"] *,[data-motion="off"] *::before,[data-motion="off"] *::after{
  animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}
[data-motion="off"] #flash.fire{animation:none;opacity:0}
@keyframes flashsoft{0%{opacity:0}50%{opacity:.35}100%{opacity:0}}
