/* The dice combat screen (DD-13 P-4). Ported from the polygon prototype's FX
   layer: the motion is CSS, the art is silhouettes, and every class that keys an
   effect is put there by the SERVER for the last event batch - the client runs
   no game logic. */

/* THE INK LADDER (KB-101). Nothing in the tree ever DEFINED --ink-label /
   --ink-faint / --ink-body / --ink-emph / --ink-aside - only --ink-well is ever
   named, and that too only as a fallback - so all 54 var(--ink-*,#hex) on this
   sheet have always painted their hard-coded fallback. #5C564E is 2.60:1 on the
   strip's #101011, the value the legibility law forbids for a number or a
   control label, and fourteen of them wore it. #857E74 is 4.69:1 and still a
   clear step below --ink-label, so the ladder keeps its rungs. One definition
   moves all of them. Safe by construction: view.cpp hands a page exactly ONE
   extra_css, so combat.css and crawl.css are never loaded together. */
:root{
  --ink-emph:#F0EAE0;   /* 15.5:1 - the ending, a sentence's stress */
  --ink-body:#D3CCC0;   /* 11.8:1 - a decision number, my own blood */
  --ink-label:#8F877C;  /*  5.3:1 - a label beside a number */
  --ink-faint:#857E74;  /*  4.7:1 - the quietest thing still legible */
  --ink-aside:#B8B0A4;  /*  9.2:1 - text that stands on ART, not on a panel */
}

/* overflow-x:CLIP, not hidden: hidden coerces overflow-y to `auto`, making the
   wrap a scroll box that clips every tooltip rising above the lane-head (KB-084
   tips are position:absolute and escape only a `visible` ancestor). `clip` holds
   the horizontal page-scroll guard WITHOUT touching overflow-y, and the margin
   lets an edge chip's centred tooltip paint out past the side instead of being
   sheared. .qchips is NO LONGER a clipper - the wrap rule further down sets
   overflow-x:visible unconditionally and wins the cascade (KB-095 corrected).
   The clippers that remain are .lane (it must clip the room's art) and
   .console .cb-row (a scroll box); tips inside those flip below with .tipdn. */
.cb-wrap { max-width: 100%; margin: 0 auto; overflow-x: clip; overflow-clip-margin: 200px; }

/* THE POLYGON PROTOTYPE'S BOARD IS GONE (SUR-74). The swing queue, the diamond
   of five slots, .cb-unit and its figure, bar, tell, trait, intent and part
   cards were the pre-lane screen; the lane (KB-075 onward), the header strip and
   the plates replaced every one of them. They had no emitter left in any
   template or script - only the graveyard list in docs/contracts/fight-ui.json
   kept them from silently returning. The list shrinks with them. SUR-98 took the
   last three: the breadcrumb, the orange PROTOTYPE banner, and the tell's
   `matched` lighting that the :has() family had already replaced. */

/* ---- the hand ---- */
.cb-die { width: 46px; height: 46px; border: 1px solid #48484e; background: #1a1a1c;
  display: flex; align-items: center; justify-content: center; position: relative; }
.cb-pip { font-size: 2rem; line-height: 1; color: #e6dbc8; }
.cb-pip.sm { font-size: 1.15rem; }
/* A SPENT DIE IS PAST, NOT GONE. At .28 it was a smudge - you could not read
   which face you had already thrown, which is exactly what you want to know when
   deciding what to do with the rest of the hand. */
.cb-die.spent { opacity: .45; }
/* THE STATE MARKS, in the reference's own grammar (DD-15
   ricochet-and-forged-marks-are-swapped-and-cold). Ricochet takes the warm
   border and a warm mark, because the mod is the thing that makes a die worth
   more than its face. A held die is a gold outline the reference fills.
   The forged floor mark left with the die natures (KB-081). */
.cb-die.held { border-color: #e8c87a; background: #221A0E;
  box-shadow: 0 0 0 1px #e8c87a inset; }
.cb-die.ricochet { border-color: #7a6a52; }
.cb-die.ricochet::after { content: "↺"; position: absolute; top: -2px; right: 3px;
  font: 400 .62rem/1 var(--mono, monospace); color: #8f877c; }
/* A die is a label for its own checkbox now; the held look rides :has() in the
   ONE FORM section, so picking costs no request. */
label.cb-die { cursor: pointer; }

/* THE TOOLTIP (KB-084): one reusable, scriptless explainer. Anything wearing
   class="tip" with a data-tip speaks on hover and on keyboard focus - the
   fight screen stays a page, not a program. */
.tip{position:relative}
.tip::after{content:attr(data-tip);position:absolute;left:50%;bottom:calc(100% + 8px);
  transform:translateX(calc(-50% + var(--tip-dx, 0px)));width:max-content;max-width:min(38ch,86vw);padding:8px 11px;
  background:#14100d;border:1px solid #7a6a52;color:#e6dbc8;
  font:400 .72rem/1.5 var(--mono,monospace);letter-spacing:.02em;
  text-align:left;white-space:pre-line;opacity:0;pointer-events:none;
  transition:opacity .12s;z-index:40}
.tip::before{content:"";position:absolute;left:50%;bottom:calc(100% + 3px);
  transform:translateX(-50%);border:5px solid transparent;border-top-color:#7a6a52;
  opacity:0;pointer-events:none;transition:opacity .12s;z-index:40}
/* A TAP IS NOT A HOVER. :focus-visible never matches pointer focus, so on a touch
   screen the tip never opened at all. A tap on a tabindex="0" element gives it
   :focus - scoped by [tabindex] so a mouse click on a <button class="tip"> does
   not latch its tip open. */
.tip:hover::after,.tip:hover::before,
.tip:focus-visible::after,.tip:focus-visible::before,
.tip[tabindex]:focus::after,.tip[tabindex]:focus::before{opacity:1}
/* A TIP UNDER A CEILING. The lane clips (it has to: the room's art and the men
   are drawn to its frame), and every plate sits near its top edge, so a box
   rising off a plate is sheared. On a plate the tip hangs BELOW the thing it
   explains, where there is always lane to paint on. */
.tip.tipdn::after{bottom:auto;top:calc(100% + 8px)}
.tip.tipdn::before{bottom:auto;top:calc(100% + 3px);
  border-top-color:transparent;border-bottom-color:#7a6a52}
/* The plate is inert so the man beneath it stays pressable; what must be HOVERED
   takes the pointer back, the way the part chips already do. This is why the
   soak and sentenced tips shipped by KB-084 have never once fired on hover. */
.lane .plate .tip{pointer-events:auto}
/* THE TOP STRIP HANGS ITS TIPS BELOW (owner 2026-08-04). The strip sits at the
   very top of the fight - a box rising off a chip cleared the fight and drew over
   the primer and the nav. There is always lane to paint on below it. Horizontal
   spill (an edge chip like the queue total) is nudged back by tip-clamp.js via
   --tip-dx above; this only turns the vertical direction. */
.lane-head .tip::after{bottom:auto;top:calc(100% + 8px)}
.lane-head .tip::before{bottom:auto;top:calc(100% + 3px);
  border-top-color:transparent;border-bottom-color:#7a6a52}
/* THE SOAK BADGE IS BURIED (SUR-84 review): its emitter left with SUR-74, when
   the hide's word moved to the fx mark. The token rests in the graveyard. */
/* Under sentence (KB-086) - the Headsman's mark burns ember on the card. Scoped
   like its neighbours so it outranks both hosts' default gold (SUR-84 review:
   it lost the cascade in the lane AND the muster and burned gold for a year). */
.badge.sentb,.lane .plate .badge.sentb{color:#E25822;border-color:#8a3a1a}
/* THE WOUND (SUR-84) - the anatomist's mark, and the whole of a wound's reading
   with no script: the crown is the director's, this word is the server's. Blood,
   not the sentence's ember - the two ride the same row and must not read alike. */
.badge.woundb,.lane .plate .badge.woundb{color:#D05A4E;border-color:#7a3028}
/* ANGUISH (SUR-111, KB-151) - the Tormentor's spirit-break, and his IDENTITY colour
   (owner 2026-08-16): black-and-violet, never the wound's blood-red. A deep violet
   ink on the plate's near-black chip, a darker violet at the rim - it reads on both
   the lane plate and the muster dock, and can never be mistaken for the Inquisitor's
   bleed that keeps woundb above. Scoped like its neighbours so it outranks the plate
   badge's default gold in both hosts. */
.badge.anguishb,.lane .plate .badge.anguishb{color:#A87BE0;border-color:#4a2f70}
/* THE FAR RANK (SUR-41) - a cool steel tag: a 3+ die reaches this second rank
   for a lighter cut; the tip carries the rule. Distinct from the warm marks, and
   scoped under .lane .plate so it outranks the plate badge's default gold. */
.lane .plate .badge.reachb{color:#8fa6b8;border-color:#4a5c6a}
/* HELD BACK (SUR-60) - the rear support who queues no swing. Ash, not gold: the
   badge names an absence, so it must not read as a threat beside the trait it
   points at. Scoped like reachb so it outranks the plate badge's default gold. */
.lane .plate .badge.heldb{color:#9a938a;border-color:#4c4740}
/* THE REST, COUNTED (SUR-67) - the plate wears its heaviest word and this chip
   says how many more the man carries, with their names and their rules in its
   tip. Ash like the name beside it: it is a pointer at a reading, not a reading
   of its own. Scoped like reachb and heldb so it outranks the plate's gold. */
.lane .plate .badge.moreb{color:var(--ink-label,#8F877C)}
/* THE READING (KB-087): what the fight was and what it taught, on the plate. */
.cb-read{display:flex;flex-wrap:wrap;gap:14px;margin:8px 0 2px;
  font:400 .72rem/1.5 var(--mono,monospace);letter-spacing:.05em;color:#8f877c}
.cb-read .gain{color:#c8b998}
.cb-read .dry{color:#7a6a52;font-style:italic}

/* The Weight (KB-086) - the Headsman's stack, worn beside the streak. */
.wchip{display:flex;align-items:baseline;gap:6px;padding:4px 10px;
  border:1px solid #48484e;background:#1a1a1c;color:#c8b998;
  font:400 .72rem/1.4 var(--mono,monospace);letter-spacing:.06em}
.wchip b{color:#8f877c;font-weight:400;text-transform:uppercase;font-size:.6rem}
.wchip i{font-style:normal;color:#E25822}

/* ---- verbs and reactions ---- */
.cb-verbs, .cb-react { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0; }
/* THE VERBS, at the designer's own size. They were left-aligned cards carrying a
   full sentence each, which turned the control bar into a page of instructions
   and pushed the lane off the screen. The face says the verb and where it lands
   (or what is missing); the sentence lives in the title, where it costs nothing. */
.cb-vbtn { border: 1px solid #38383c; background: #151517; color: #cbc2b4;
  padding: 5px 10px; cursor: pointer; font: inherit; min-width: 92px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; }
.cb-vbtn .vn { display: block; font: 600 .74rem/1 var(--mono, monospace); color: #e6dbc8;
  letter-spacing: .02em; }
.cb-vbtn .vf { display: block; font: 400 .62rem/1.2 var(--mono, monospace); color: #8f877c;
  letter-spacing: .04em; }
.cb-vbtn.ready { border-color: #6a5c3a; }
.cb-vbtn.armed { border-color: #e8c87a; box-shadow: 0 0 0 1px #e8c87a inset; }
.cb-vbtn[disabled] { opacity: .4; cursor: default; }
/* A DIMMED ACTION MUST NOT ARM - but it must still explain itself. This was
   pointer-events:none for a day; that closed the dead end and also killed hover
   on the one label whose title= carries the action's arithmetic, so a spent hand
   could read no formula at all. The template drops the `for` instead: the label
   activates nothing and stays hoverable. Nothing to write here. */
.cb-rrow { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; width: 100%; }
.cb-rlbl { font: 400 .66rem var(--mono, monospace); letter-spacing: .08em;
  text-transform: uppercase; color: #8f877c; min-width: 118px; }
.cb-rbtn { border: 1px solid #48484e; background: #1a1a1c; color: #cbc2b4; padding: 5px 9px;
  cursor: pointer; font: 400 .72rem var(--mono, monospace); display: flex; gap: 5px; align-items: center; }

.cb-rbtn.vuln { border-color: #e8c87a; color: #e8c87a; }

.cb-callout { font: 400 .78rem var(--mono, monospace); color: #cbc2b4; margin: 8px 0; min-height: 20px; }
.cb-ticker { border-left: 2px solid #38383c; padding: 6px 10px; margin: 10px 0;
  font: 400 .76rem var(--mono, monospace); color: #b8ae9f; max-height: 150px; overflow-y: auto; }
.cb-ticker div + div { margin-top: 3px; }
.cb-ticker .last { color: #f3e4cf; }
.cb-endbar { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0; }
.cb-chipline { display: flex; gap: 10px; align-items: center; font: 400 .72rem var(--mono, monospace); color: #9a9186; }
.cb-streak.hot { color: #e8c87a; }
.cb-streak.blazing { color: #f0641a; }
.cb-mechs { border: 1px solid #29292c; background: #121214; padding: 10px 12px; margin: 8px 0; }
.cb-mechs p { margin: 0 0 7px; font-size: .82rem; color: #b8ae9f; }
.cb-lockline{margin:7px 0 0;padding:6px 10px;font:italic .84rem/1.4 var(--serif,serif);
  color:#C9A24A;border-left:2px solid #7a5a1a;background:rgba(122,90,26,.10)}
.cb-done { border: 1px solid #48484e; background: #151517; padding: 10px 12px; margin: 10px 0;
  font: 400 .8rem var(--mono, monospace); color: #e6dbc8; }

/* THE LEDGER (KB-125): the fight closes on a sheet, not a sentence - the loop
   lands. Server-gated by is_over (no :has, correct with JS off); the reveal and
   count-up are the director's later flourish. Bordeaux rule over parchment-dark. */
.cb-ledger { max-width: 440px; margin: 14px auto 6px; padding: 15px 18px 14px;
  border: 1px solid #48484e; border-top: 2px solid var(--blood-arterial, #6E1C23);
  background: #141416; box-shadow: 0 10px 30px rgba(0,0,0,.5); text-align: center; }
.cb-ledger-head { font: 600 .95rem/1.2 var(--mono, monospace); letter-spacing: .07em;
  color: var(--ink-emph, #F0EAE0); text-transform: uppercase; }
.cb-ledger-sub { margin: 4px 0 10px; font: 400 .72rem/1.4 var(--mono, monospace);
  letter-spacing: .04em; color: #8f877c; }
.cb-ledger .cb-read { margin: 0; justify-content: center; }
.gain-ic { width: .95em; height: .95em; display: inline-block; vertical-align: -2px;
  margin-right: 4px; fill: currentColor; }
.cb-ledger-level { margin: 10px 0 0; padding: 8px 11px; border: 1px solid #8a3a1a;
  background: #1c1512; font: 400 .78rem var(--mono, monospace); color: #E25822;
  letter-spacing: .05em; text-transform: uppercase; }
.cb-ledger-level b { color: #f0a35a; font-size: .95rem; margin-left: 4px; }
.cb-ledger .cb-endbar { margin: 14px 0 0; justify-content: center; }

/* THE LEDGER RISES OVER THE LANE (KB-125): the close is a modal - a dim scrim
   over the figures with the sheet fading up over it. The motion is pure CSS, so
   it plays on render even with JS off; visibility stays the server's is_over,
   never a selector, so the :has ceiling is untouched. Layered like the site's
   own modalback (z 50): above header/kbhelp, below the film grain. */
.cb-over { position: fixed; inset: 0; z-index: 60; display: flex;
  align-items: center; justify-content: center; padding: 20px; overflow-y: auto;
  background: rgba(10, 8, 7, .8); animation: cbOverIn .3s ease both; }
.cb-over .cb-ledger { margin: 0; width: min(440px, 100%);
  animation: cbLedgerRise .42s cubic-bezier(.2, .7, .3, 1) both; }
@keyframes cbOverIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes cbLedgerRise { from { opacity: 0; transform: translateY(16px) scale(.965); }
  to { opacity: 1; transform: none; } }
/* THE WIN GETS A BEAT (SUR-52). On a WON close the scrim and the sheet hold back so
   the killing blow finishes falling before the paperwork rises over it; a loss or a
   flight still closes at once. fill-mode `both` keeps the scrim invisible through the
   delay, so the lane plays clear - the fight is over, no input is lost. */
.cb-over[data-over="won"] { animation-delay: 1.2s; }
.cb-over[data-over="won"] .cb-ledger { animation-delay: 1.35s; }
/* The level crossed is the LAST thing to move: it blooms once after the count-up
   settles, so the eye lands on the biggest reward on the sheet. */
.cb-over[data-over="won"] .cb-ledger-level { animation: cbLevelBloom .4s ease both; animation-delay: 2.25s; }
@keyframes cbLevelBloom {
  0% { transform: scale(1); box-shadow: none; }
  40% { transform: scale(1.06); box-shadow: 0 0 14px rgba(226, 88, 34, .55); }
  100% { transform: scale(1); box-shadow: none; }
}
@media (prefers-reduced-motion: reduce) {
  .cb-over, .cb-over .cb-ledger { animation-duration: .01ms; }
  .cb-over[data-over="won"], .cb-over[data-over="won"] .cb-ledger { animation-delay: 0s; }
  .cb-over[data-over="won"] .cb-ledger-level { animation: none; }
}

/* THE PROTOTYPE'S FX LAYER WENT WITH ITS BOARD (SUR-74). Lunge, recoil, dodge
   and the stage shake all keyed .cb-unit / .cb-stage, and the herald banner was
   never lit (the slot is deliberately silent). The lane's own figure motion -
   figLungeL/R, figHit, figOffBoard - is what plays a blow today. */

@media (max-width: 720px) {
  .cb-rlbl { min-width: 0; width: 100%; }
}

/* ---- the loadout screen ---- */
.ld-slots { display: flex; flex-wrap: wrap; gap: 10px; }
.ld-slot { border: 1px solid #38383c; background: #141416; padding: 8px 10px;
  min-width: 132px; text-align: center; }
.ld-slot.filled { border-color: #6a5c3a; }
.ld-slotno { font: 400 .62rem var(--mono, monospace); letter-spacing: .12em;
  text-transform: uppercase; color: #7d766d; }
.ld-pip { font-size: 1.8rem; line-height: 1.1; color: #e6dbc8; }
.ld-pip.empty { color: #48484e; }
.ld-name { font: 600 .78rem var(--mono, monospace); color: #e6dbc8; }
.ld-bag { display: flex; flex-wrap: wrap; gap: 10px; }
.ld-die { border: 1px solid #38383c; background: #141416; padding: 8px 10px; min-width: 190px; }
.ld-die.seated { border-color: #6a5c3a; }
.ld-tag { font: 400 .66rem var(--mono, monospace); color: #e8c87a; margin-top: 4px; }
.ld-die form { margin-top: 6px; display: flex; gap: 5px; align-items: center; flex-wrap: wrap; }

/* ---- THE LANE (DD-14 D-3) --------------------------------------------------
   A room, a floor of slabs, and figures standing on them. Two laws shape every
   rule below.

   DEPTH IS BRIGHTNESS, NEVER SIZE. A man further down the lane is the same man;
   only the light reaching him changes. Shrinking him would say he is smaller.

   AND THE PLATES ARE NEVER DIMMED. His name, his tell and his bar are what you
   read in order to decide - they must be as legible at the back of the room as
   at the front, whatever the light is doing to the figure. */

/* THE ROOM (9B). The canvas is 1200 wide always; the HEIGHT and the SEAM are the
   room's whole character, and both arrive from the server as one ratio and one
   percentage - the closet at 1200x300 through the vault at 1200x560. The
   fallbacks are the chamber, which is the room most fights are in. */
/* The lane ratio and the .cb-wrap height budget below MUST use the same number
   (2.5): the budget sizes the width so this aspect's height fits the viewport, and
   a mismatch pushes the console off-screen. Lower = taller room. */
.lane{position:relative;overflow:hidden;aspect-ratio:2.5;container-type:inline-size;
      background:#0B0A09;border:1px solid var(--edge);margin:0 0 12px}
/* THE ROOM is a drawn schematic (placeholder art). It is stretched to the stage
   rather than fitted, which is correct here: the stage keeps the room's own
   1200 x H ratio, so nothing is actually distorted, and every stroke stays a
   hairline at any width. */
.lane .room{position:absolute;inset:0;width:100%;height:100%;display:block}
.lane .room .bays path{fill:none;stroke:rgba(211,207,199,.035);stroke-width:1;
                       vector-effect:non-scaling-stroke}
/* A DOORWAY IS DARK WITH A PALE EDGE. The pass before this lit them from inside,
   which turned two holes into two lamps and pulled the eye off the men. In the
   reference they are near-black, drawn only by a hairline of old stone. */
.lane .room .door path{fill:#070505;stroke:rgba(198,188,172,.26);stroke-width:1;
                       vector-effect:non-scaling-stroke}
.lane .room .door.far path{stroke:rgba(198,188,172,.18)}
.lane .room .seamline{fill:none;stroke:rgba(0,0,0,.85);stroke-width:1.5;
                      vector-effect:non-scaling-stroke}
.lane .room .boards path{fill:none;stroke:rgba(211,207,199,.028);stroke-width:1;
                         vector-effect:non-scaling-stroke}
.lane .herald{position:absolute;left:0;right:0;top:8px;text-align:center;z-index:6;
              font:italic .82rem/1.4 var(--serif,Georgia,serif);
              color:var(--ink-aside,#6B655C)}

/* A unit is placed by its FLOOR MARK - the point where the feet touch - so a
   taller man grows upward instead of sinking into the boards. */
.lane .unit{position:absolute;left:calc(var(--x) * 1%);bottom:calc(var(--y) * 1% + 7%);
            transform:translateX(-50%);display:flex;flex-direction:column;
            align-items:center;justify-content:flex-end}
/* The figure stands in a box the size of its stature, and the box carries a
   silhouette. A foe whose sprite has not been drawn yet still reads as a man of
   the right height in the right place - the layout cannot wait on the art. */
/* A STATURE IS A FRACTION OF THE ROOM, not a count of pixels. The old factor
   (.18px) drew a 205-tall man 37px high whatever the stage was doing, so the
   figures shrank away from the slabs they stand on and the room stopped reading
   as a room.
   Measured in cqw - the LANE's own width - because a percentage height needs a
   parent with a definite height and .unit is placed by its floor mark and has
   none. Against 100cqw the canvas is 1200 units wide, so every number below is
   simply the design's own pixel taken straight off the mockup. */
/* --rim is the SLOT the arming rules fill (SUR-71): a legal man's ember edge is
   passed in as a value, not written as a second filter, so the depth brightness
   above survives and every louder state (.marked, .attacking, the aim lift) keeps
   the override it already had. Empty by default - a no-op shadow, always valid. */
.lane .unit .figbox{position:relative;z-index:2;
                    height:calc(var(--h) / 1200 * 100cqw);
                    width:calc(var(--h) * .5 / 1200 * 100cqw);
                    background:none;border:0;
                    --rim:drop-shadow(0 0 0 rgba(0,0,0,0));
                    filter:brightness(var(--b, .92)) drop-shadow(0 6px 10px rgba(0,0,0,.7))
                           var(--rim)}
.lane .unit .figbox .fig{position:absolute;left:50%;bottom:0;transform:translateX(-50%);
                         height:100%;width:auto;max-width:none}
/* A MAN THROWS A POOL. Without one the figures float a hair above the boards,
   which is the single thing that reads most like a diagram. */
.lane .unit .figbox:before{content:"";position:absolute;left:50%;bottom:-4%;
                           transform:translateX(-50%);width:150%;height:22%;
                           background:radial-gradient(ellipse at center,
                                       rgba(0,0,0,.55), rgba(0,0,0,0) 68%);
                           pointer-events:none}
.lane .unit.you .figbox{filter:drop-shadow(0 6px 10px rgba(0,0,0,.7))}
/* HERO figure rides a wrapper so an aura / red flash placed INSIDE it shares every
   gesture's transform (lunge, hit, tithe, suffer) - cemented to the model, not the
   lane. inline-block shrinks the box to the art; the ground-shadow stays on the
   figbox (planted, doesn't lunge). */
.lane .unit.you .figbox .figwrap{display:inline-block;vertical-align:bottom;position:relative;transform-origin:center bottom}
/* the figure sits ABOVE the martyr ring behind it. The .hasfig here is load-bearing:
   .you.hasfig .figbox .fig{position:static} (below) is equal-specificity and later, so
   without it position:static would win and z-index:1 (which needs a positioned box) go
   inert - the ring would paint in front of the silhouette. left/bottom:auto neutralise
   the general .figbox .fig{left:50%;bottom:0} (written for the ABSOLUTE non-hasfig fig):
   the moment this fig is positioned those offsets wake up and shove it half-right. */
.lane .unit.you.hasfig .figbox .figwrap .fig{position:relative;z-index:1;left:auto;bottom:auto}
/* PAIN PULSE - a SINGLE red flash along the figure's CONTOUR that rises then dies
   down (drop-shadow rides the art's own alpha, so the light hugs the silhouette). The
   shared "impulse of pain": Tithe, Suffer, and mortification's ONSET each fire ONE.
   Mortification does NOT throb - the ring is its lasting mark; the flash only marks the
   moment it takes hold. 0%/100% is the figure's normal warm rim-light, 20% the red
   peak - a deliberate rise, a clean fade. */
@keyframes fxPainPulse{
  0%,100%{filter:drop-shadow(0 0 1.5px rgba(232,218,190,.72)) drop-shadow(0 0 4px rgba(232,218,190,.34))}
  20%{filter:drop-shadow(0 0 3px rgba(246,62,50,1)) drop-shadow(0 0 10px rgba(214,34,28,.85)) drop-shadow(0 0 20px rgba(162,16,16,.6))}}
/* one pulse - the director adds .fx-pain on Tithe, on a Suffered blow, and once when
   the martyr ring arrives. ~1s so it reads as a slow throb of pain. */
.lane .unit.you .figwrap .fig.fx-pain{animation:fxPainPulse 1s ease-out 1}
/* the martyr ring rides inside the wrapper (moves with the figure). Sized RELATIVE to
   the figure (not a fixed 150px that dwarfs a small figure and shrinks against a big
   one), and raised to sit HIGH on the body (~75% up: chest/shoulders, a halo). */
.lane .unit.you .figwrap .fx-aura{width:auto;height:66%;aspect-ratio:1;top:25%;left:50%;transform:translate(-50%,-50%)}
/* BLOOD SPRAY sits on the .unit (never the figbox, whose brightness/mirror would
   tint and flip it) and centres on the struck body - the catalogue's JS bottom
   anchor is replaced here by a figure-centred one. z above the figure, below the
   number. The crit burst (.big) is taller, so its own half-height offset. */
/* the directional fling (normal hit) sits a touch higher on the body; its own art
   already carries the up-right throw, so no offset is needed here. */
.lane .unit .fx-fling{top:24%;margin-top:-104px}
/* STATUS MARKS ride just above the figure's crown (the JS director appends the
   .fx-marks row to the unit); smaller here than the catalogue's demo size. */
.lane .unit .fx-marks{bottom:calc(100% + 4px)}
.lane .fx-mark{width:30px;height:30px}
.lane .fx-mark .count{font-size:.6rem}
/* A PICKED FIGURINE (0132) replaces the placeholder token with a real drawing.
   INTRINSIC HEIGHT (owner 2026-08-01): the figure is an <img> whose height is the
   ART'S own, not a number - so a boss of ANY height needs no --h to mismatch. A
   width is chosen and the height follows; the figbox shrinks to the drawing; the
   plate (below) rides above whatever that turns out to be. A room-relative
   max-height + object-fit:contain is the ceiling clamp: a tower boss scales down
   to fit the room instead of shearing its own name on the lane's top edge. The art
   already faces right (toward the foe), so no flip. */
.lane .unit.you.hasfig .figbox,
.lane .unit.foe.hasfig .figbox{height:auto;width:auto}
.lane .unit.you.hasfig .figbox .fig,
.lane .unit.foe.hasfig .figbox .fig{position:static;display:block;
                                    width:calc(140.625 / 1200 * 100cqw);height:auto;aspect-ratio:auto;
                                    max-height:calc(var(--rh, 420) * .82 / 1200 * 100cqw);
                                    max-width:none;object-fit:contain;object-position:center bottom;
                                    background:none;border:0;box-shadow:none;transform:none;
                                    transform-origin:center bottom;
                                    /* RIM-LIGHT (owner 2026-08-01): the warm pale edge-glow the foe used
                                       to wear - now the PLAYER's, lifting the figure off the dark lane.
                                       A hit/death animation overrides this filter while it plays. */
                                    filter:drop-shadow(0 0 1.5px rgba(232,218,190,.72))
                                           drop-shadow(0 0 4px rgba(232,218,190,.34))}
/* Idle breathing REMOVED (owner 2026-08-01): the figures stand still; only the
   event animations (hit / lunge / topple) move them. youIdle keyframes kept -
   still the base pose the event rules return to. */
@keyframes youIdle{0%,100%{transform:translateY(0) rotate(0)}
                   50%{transform:translateY(-1.5%) rotate(.3deg)}}
/* PLACEHOLDER FOE (2026-08-01): ONE black silhouette on a pedestal stands in for
   EVERY enemy until each foe's own art is drawn. Sized by the foe's stature --h,
   the box set 3:4 so the drawing fills it and the figure rises the full height;
   feet on the floor mark. The plate keeps its file ladder - foes stand in ranks -
   and already rides at --h, i.e. the silhouette's head. The art faces LEFT, toward
   the player, so no flip. Per-foe art later just swaps this URL for one the
   encounter carries.
   NO LONGER INLINE (2026-08-02): the silhouette rode as a 72KB base64 data URI,
   which is 37% of this sheet and a stylesheet BLOCKS the first paint while an
   image does not. It is also not dead code - it is the fallback every calling
   without figurine art still leans on (only three of them have drawings), so it
   could not simply be deleted. The same picture now comes off disk. */
.lane .unit.foe.alive .figbox{width:calc(var(--h) * .75 / 1200 * 100cqw)}
.lane .unit.foe.alive .figbox .fig{width:100%;height:100%;aspect-ratio:auto;max-width:none;
                                   background:url("/static/img/figurines/foe_still.png") center bottom / contain no-repeat;
                                   border:0;box-shadow:none;transform-origin:50% 94%;
                                   /* RIM-LIGHT, barely SCARLET (owner 2026-08-01): the foe wears a
                                      faint red edge-glow - friend/foe by colour now that the floor
                                      diamonds are gone (the player's rim is warm pale). A hit/death
                                      animation overrides this filter while it plays. */
                                   filter:drop-shadow(0 0 1.5px rgba(206,72,60,.62))
                                          drop-shadow(0 0 4px rgba(206,72,60,.3))}

/* THE FOE IS YOUR OWN SHADOW (owner 2026-08-02) - a placeholder until he is
   drawn: the player's own figurine, crushed to a flat black cut-out and turned
   to face him.
   IT MUST SIT HERE, AFTER the .foe.alive block. Both selectors carry the same
   weight, so the later one wins - written above, every line of this was quietly
   overruled and the man stayed in colour, wearing the stand-in silhouette's
   sizing. So this block also has to UNDO what .alive sets: its width, its
   background plate, and its rim.
   The mirror rides the BOX, never the figure: every event animation writes
   `transform` on the figure and would wipe a mirror put there. And because it is
   the same drawing at the same size, the two men stand on the SAME LINE - two
   different plates never agreed on where the feet were. */
.lane .unit.foe.hasfig .figbox{width:auto;height:auto;transform:scaleX(-1)}
.lane .unit.foe.hasfig .figbox .fig{width:calc(140.625 / 1200 * 100cqw);height:auto;
                                    background:none;
                                    filter:brightness(0)
                                           drop-shadow(0 0 2px rgba(176,74,66,calc(.95 * var(--rimdim, 1))))
                                           drop-shadow(0 0 8px rgba(176,74,66,calc(.55 * var(--rimdim, 1))))}
/* THE STAND-IN'S RANK IS IN ITS RIM (SUR-70). A drawn man carries his depth in
   his whole body: brightness(--b) on the figbox dims every pixel of him. The
   stand-in has no body to dim - his fill is brightness(0), and black times any
   --b is still black - so the only thing of him that can recede is the scarlet
   edge. --b does reach it (the box's filter dims the child's own shadows), but
   one thin glow reads far less of a step than a whole man does, so the edge
   takes a second, steeper ladder of its own on top.
   --rimdim is a MULTIPLIER, never an absolute: every rim alpha on a stand-in is
   written as base * var(--rimdim), so the depth ladder and SUR-54's menace-dim
   COMPOSE instead of overruling each other - a rank-3 man with no chip in the
   queue wears both. Three plain depth classes, no :has(), no new class.
   The steep step is between rank 1 and rank 2 - the pair the board cannot part,
   since its reach fill lights both the same ember - and the last step is gentle
   on purpose: --b has already taken rank 3 down to .55, and SUR-54 halves his
   rim again if no chip in the queue is his, so a third steep cut there would
   put him under the room rather than behind it.
   (Dial: these three numbers. Lower separates harder; the floor is the peak the
   rear man can still be picked out at - measured at 1440 as 1.7x the wall behind
   him with both dims on, against 2.2x before this ticket. Most of that drop is
   --b's own, not this ladder's: at rank 3 the rim peak barely answers --rimdim
   at all, so raise lane.json's third brightness before reaching for this one.) */
.lane .unit.foe.d1{--rimdim:1}
.lane .unit.foe.d2{--rimdim:.84}
.lane .unit.foe.d3{--rimdim:.8}
/* A DRAWN foe wears its OWN colour (owner 2026-08-13, SUR-38): its sprite is real
   art, not the black shadow, so brightness(0) is lifted and only a drop-shadow for
   depth stays. The swing and death keyframes carry no filter, so they inherit this
   and stay coloured; only the hurt beat bakes brightness(0), so it gets a colour-
   safe variant. An undrawn foe (the stand-in) keeps the shadow. */
/* A drawn foe is painted facing LEFT (toward you), so it is NOT mirrored like the
   shadow was (owner 2026-08-13); its lunge then travels left (figLungeL), toward
   you, instead of the mirrored figLungeR. */
/* BIGGER + SEATED LOWER (owner 2026-08-13): a drawn sprite carries its OWN
   pedestal and padding, so at the hero's width it reads small and rides high off
   the ground. Widen it a touch over the hero's own width (near his height, owner
   2026-08-13), and drop the whole figure
   (pedestal and all) a touch so the two men meet on the same line. The drop rides
   the BOX (transform below, which also un-mirrors: the art already faces left);
   it must NOT ride the .fig, whose swing and hurt keyframes write transform and
   would wipe it. The plate is a sibling of the box, so the name stays put. */
.lane .unit.foe.drawn.hasfig .figbox{transform:translateY(12px)}
.lane .unit.foe.drawn.hasfig .figbox .fig{width:calc(142 / 1200 * 100cqw)}
/* Per-foe size (owner 2026-08-14): the stitcher stands 10% taller than the base
   drawn figure. The k-<key> hook lets any one foe be tuned without touching the
   rest; feet stay on the floor mark (the figbox is bottom-anchored). */
.lane .unit.foe.drawn.hasfig.k-stitcher .figbox .fig{width:calc(142 * 1.1 / 1200 * 100cqw)}
.lane .unit.foe.drawn.hasfig.swinging .figbox .fig{animation:figLungeL .6s cubic-bezier(.34,.85,.4,1)}
.lane .unit.foe.drawn.hasfig .figbox .fig{filter:drop-shadow(0 6px 10px rgba(0,0,0,.7))}
/* SUR-54: a foe with no chip in the header queue cannot reach you this round, so he
   RECEDES and the men who WILL swing stand out. The stand-in's scarlet rim dims
   (still scarlet - friend/foe by colour holds); the drawn foe (no rim) fades a
   touch. A cornered lone survivor gets his full rim back on the next render. */
.lane .unit.foe.hasfig:not(.drawn):not(.menace):not(.hurt):not(.died) .figbox .fig{
  filter:brightness(0) drop-shadow(0 0 2px rgba(176,74,66,calc(.42 * var(--rimdim, 1))))
                       drop-shadow(0 0 8px rgba(176,74,66,calc(.22 * var(--rimdim, 1))))}
.lane .unit.foe.drawn.hasfig:not(.menace) .figbox{opacity:.78}
/* SUR-59: hovering or focusing a queue chip LIGHTS its own man - a soft scarlet
   halo on the figure, a quiet cousin of the react .attacking rim, so the eye finds
   the chip's man without reading every plate. .marked is JS-added on hover/focus,
   never in server HTML (registered in fight-ui.json). */
.lane .unit.foe.marked .figbox{
  filter:brightness(var(--b,.92)) drop-shadow(0 0 5px rgba(214,72,72,.7)) drop-shadow(0 0 14px rgba(214,72,72,.45))
         var(--rim)}
.lane .unit.foe.drawn.hurt .figbox .fig{animation:figHitFoeDrawn .5s linear}
@keyframes figHitFoeDrawn{
  0%{transform:translateX(0) rotate(0deg)}
  10%{transform:translateX(-13%) rotate(-3deg);
      filter:brightness(1.35) drop-shadow(0 0 5px rgba(255,220,205,.95)) drop-shadow(0 0 18px rgba(212,36,28,.92))}
  26%{transform:translateX(10%) rotate(2deg)}
  42%{transform:translateX(-7%) rotate(-1.4deg)}
  60%{transform:translateX(5%) rotate(.7deg)}
  80%{transform:translateX(-2.5%) rotate(-.3deg)}
  100%{transform:translateX(0) rotate(0deg)}}
/* His box is mirrored, so the RIGHTWARD lunge is the one that travels left. */
.lane .unit.foe.hasfig.swinging .figbox .fig{animation:figLungeR .6s cubic-bezier(.34,.85,.4,1)}
/* THE CORPSE BOX IS GONE (SUR-92): a felled figurine falls off the board and
   the next render omits him whole - nothing lies on the field to size. */

/* THE FIGURES STAND ON BAKED PEDESTALS now (owner 2026-08-01), so the CSS floor
   diamond under each man is redundant - hidden. An empty seat (.slab.cell) keeps
   its marker: a seat with no man has no pedestal to stand on. */
.lane .slab.ember, .lane .slab.blood{display:none}

/* ---- COMBAT ANIMATIONS (2026-08-01): play ONCE on the re-render, driven by the
   server's last-action FX classes (hurt / swinging on a unit, shake on the lane).
   The figures stand STILL between events (idle breathing removed, owner
   2026-08-01); an event class drives the one render it is set on. ---- */
/* HURT: a shudder with a RED flash - fires on ANY damage taken, hero or foe.
   Slower and HELD (owner 2026-08-01): the flash rises hard then eases off across
   the recoil, so a player actually SEES the hit instead of a 50ms blink. */
.lane .unit.you.hurt .figbox .figwrap{animation:figHit .5s linear}
/* THE CUT-OUT NEVER LIGHTS (owner 2026-08-03). A black silhouette must stay black
   even as it is struck: figHit's white brightness pulse REPLACES the fig's static
   brightness(0) and reveals the borrowed figurine beneath - the un-blacking the
   owner kept seeing. The foe gets its own hurt: same recoil, but the flash holds
   brightness(0) and flares the RIM. The filter is named ONLY at 10%, so it eases
   from the fig's own static rim and back; both ends carry brightness(0) + two
   drop-shadows, matched in count so the list interpolates cleanly. */
.lane .unit.foe.hurt .figbox .fig{animation:figHitFoe .5s linear}
/* THREE THINGS MAKE A SHUDDER READ SMOOTH, and this one had all three wrong.
   (1) LINEAR, not a bezier: an easing curve is applied BETWEEN EVERY PAIR of
   keyframes, so a hard curve across seven of them is seven separate decelerations
   - the stutter people felt. The damping already lives in the amplitudes; easing
   it twice is what broke it.
   (2) The flash is named ONCE, at the top. A property that appears in a single
   keyframe interpolates from the element's own value and back to it, so the red
   rises fast and bleeds away across the whole recoil. It used to be pinned at 9%
   and 24% and then cut dead with `filter:none` at 40% - a value that does not
   interpolate cleanly out of a filter list, and which ended the flash while the
   figure was still moving. That mismatch is why the motion looked finished early.
   (3) rotate() is present in EVERY frame, 0deg where it is idle. A transform list
   that changes shape between frames drops the browser from per-component
   interpolation to matrix interpolation, which slides differently. */
@keyframes figHit{
  0%{transform:translateX(0) rotate(0deg)}
  10%{transform:translateX(-13%) rotate(-3deg);
      filter:brightness(1.75) sepia(1) saturate(6) hue-rotate(-20deg)}
  26%{transform:translateX(10%) rotate(2deg)}
  42%{transform:translateX(-7%) rotate(-1.4deg)}
  60%{transform:translateX(5%) rotate(.7deg)}
  80%{transform:translateX(-2.5%) rotate(-.3deg)}
  100%{transform:translateX(0) rotate(0deg)}}
/* The foe's hurt: figHit's recoil, but the fill stays black. brightness is never
   named above 0, so the silhouette holds; only the rim flares (white-hot at 10%,
   then eased back to the fig's own dim static rim). */
@keyframes figHitFoe{
  0%{transform:translateX(0) rotate(0deg)}
  10%{transform:translateX(-13%) rotate(-3deg);
      filter:brightness(0) drop-shadow(0 0 5px rgba(255,220,205,.95)) drop-shadow(0 0 18px rgba(212,36,28,.92))}
  26%{transform:translateX(10%) rotate(2deg)}
  42%{transform:translateX(-7%) rotate(-1.4deg)}
  60%{transform:translateX(5%) rotate(.7deg)}
  80%{transform:translateX(-2.5%) rotate(-.3deg)}
  100%{transform:translateX(0) rotate(0deg)}}
/* ATTACK: a lunge toward the enemy and back - you lunge right, the foe left.
   A touch slower and smoother so the blow reads. */
.lane .unit.you.swinging .figbox .figwrap{animation:figLungeR .6s cubic-bezier(.34,.85,.4,1)}
.lane .unit.foe.swinging .figbox .fig{animation:figLungeL .6s cubic-bezier(.34,.85,.4,1)}
/* Same rule as the shudder: every frame carries the SAME transform list - the
   scale used to appear at 32% alone, which shape-shifted the list and dropped
   the browser onto matrix interpolation for the rest of the lunge. The curve
   stays a bezier here on purpose: this shape is drive-out, overshoot, drift
   back, and it wants the deceleration the shudder did not. */
/* --reach scales the lunge's travel (default 1 = a full swing; SUR-56 dials it to
   .5 for the thief's half-distance palm reach). The transform LIST stays identical
   across every frame - only the translateX magnitude moves - so the browser never
   drops onto matrix interpolation (the same law the shudder/lunge comment states). */
@keyframes figLungeR{0%{transform:translateX(0) rotate(0deg) scale(1)}
  32%{transform:translateX(calc(40% * var(--reach,1))) rotate(6deg) scale(1.04)}
  44%{transform:translateX(calc(46% * var(--reach,1))) rotate(7deg) scale(1.02)}
  100%{transform:translateX(0) rotate(0deg) scale(1)}}
@keyframes figLungeL{0%{transform:translateX(0) rotate(0deg) scale(1)}
  32%{transform:translateX(calc(-40% * var(--reach,1))) rotate(-6deg) scale(1.04)}
  44%{transform:translateX(calc(-46% * var(--reach,1))) rotate(-7deg) scale(1.02)}
  100%{transform:translateX(0) rotate(0deg) scale(1)}}
/* DEATH: the felled figurine is KNOCKED OFF THE BOARD (KB-078 as amended by
   KB-146 / SUR-92, owner 2026-08-15) - it topples at the feet like a tabletop
   piece, then slides over the table's near edge, dropping and fading, and ends
   GONE (forwards holds opacity 0). The next render omits the man entirely; the
   kill lives on in the log and the header's count. His floor pool and plate
   fade with him so nothing orphaned lingers under an empty patch of board. */
.lane .unit.foe.died .figbox .fig{animation:figOffBoard 1.1s cubic-bezier(.25,.6,.35,1) forwards}
@keyframes figOffBoard{0%{transform:rotate(0) translate(0,0);opacity:1}
  42%{transform:rotate(-82deg) translate(0,3%);opacity:1}
  58%{transform:rotate(-96deg) translate(-3%,22%);opacity:.95}
  100%{transform:rotate(-118deg) translate(-9%,150%);opacity:0}}
.lane .unit.foe.died .figbox::before{animation:figOffFade 1.1s ease-out forwards}
.lane .unit.foe.died .plate{animation:figOffFade .8s ease-out .35s forwards}
@keyframes figOffFade{to{opacity:0}}
/* THE MAN WALKS IN (KB-107). An opener is a POST and a whole new page, so there
   is no transition to ride across it: the carry is an ENTRY animation that
   BEGINS at the doorway the previous page left him standing in (--stepfrom, in
   lane widths, handed over by the server) and ends at 0 - which is where this
   page already puts him. The first painted frame therefore matches the last one
   of the page before, and the seam does not show.
   Two things this must not get wrong, both learned the hard way today:
   the unit's own translateX(-50%) is carried through EVERY frame (drop it and he
   jumps half his width on the first tick), and every frame holds the same three
   transform functions, so the browser interpolates per component instead of
   falling back to matrices. LINEAR on purpose: the ease lives in the spacing of
   the frames - he leans off, carries, and sets down - and a curve on top of that
   would be six decelerations instead of one walk. */
.lane .unit.you.stepin{animation:stepIn 1.15s linear both}
@keyframes stepIn{
    0%{transform:translateX(-50%) translateX(calc(var(--stepfrom,0) * 1cqw))   translateY(0)}
   18%{transform:translateX(-50%) translateX(calc(var(--stepfrom,0) * .80cqw)) translateY(-.9cqw)}
   40%{transform:translateX(-50%) translateX(calc(var(--stepfrom,0) * .52cqw)) translateY(-1.6cqw)}
   64%{transform:translateX(-50%) translateX(calc(var(--stepfrom,0) * .26cqw)) translateY(-1.6cqw)}
   84%{transform:translateX(-50%) translateX(calc(var(--stepfrom,0) * .08cqw)) translateY(-.7cqw)}
  100%{transform:translateX(-50%) translateX(0cqw)                             translateY(0)}}
/* ANY MAN WALKS TO A NEW MARK (KB-147, the law above generalized). A unit is
   placed by left/bottom from --x/--y and fight-swap.js hands every render a
   brand-new node, so a CSS transition on those two can never carry a move
   across an action - it would have nothing to transition FROM. The carry is
   therefore the same shape as the hero's: a one-shot ENTRY animation that
   begins on the mark the man LEFT (--movefrom-x/--movefrom-y, the server's own
   old-minus-new in the same lane percent --x/--y are in) and ends at 0, which
   is where this render already put him.
   The two rules the walk above paid for hold here word for word: translateX(-50%)
   is carried in EVERY frame, and every frame holds the SAME three transform
   functions in the same order, so the browser interpolates per component instead
   of dropping onto matrices. The frame spacing is the hero's gait exactly - lean
   off, carry, set down - only the clock is halved: this is a beat INSIDE an
   action (he closes, then he swings), not the entrance to the fight.
   Y NEEDS NO cqh. The lane is container-type:inline-size, so only cqw exists;
   --y is a percentage of the LANE'S HEIGHT and the lane is aspect-ratio 2.5, so
   one --y unit is exactly .4cqw. The sign flips because --y counts up from the
   floor while translateY counts down. The vertical lift is folded into the same
   translateY rather than added as a fourth function - the shape must not move. */
.lane .unit.moved{animation:moveIn .6s linear both}
@keyframes moveIn{
    0%{transform:translateX(-50%) translateX(calc(var(--movefrom-x,0) * 1cqw))   translateY(calc(var(--movefrom-y,0) * -.4cqw))}
   18%{transform:translateX(-50%) translateX(calc(var(--movefrom-x,0) * .80cqw)) translateY(calc(var(--movefrom-y,0) * -.32cqw - .9cqw))}
   40%{transform:translateX(-50%) translateX(calc(var(--movefrom-x,0) * .52cqw)) translateY(calc(var(--movefrom-y,0) * -.208cqw - 1.6cqw))}
   64%{transform:translateX(-50%) translateX(calc(var(--movefrom-x,0) * .26cqw)) translateY(calc(var(--movefrom-y,0) * -.104cqw - 1.6cqw))}
   84%{transform:translateX(-50%) translateX(calc(var(--movefrom-x,0) * .08cqw)) translateY(calc(var(--movefrom-y,0) * -.032cqw - .7cqw))}
  100%{transform:translateX(-50%) translateX(0cqw)                               translateY(0cqw)}}
/* THE WAR BAND WAKES (SUR-74). The walk-in revealed four or five men as a still
   tableau: the hero stepped in and nothing else in the room was alive. Each foe
   now settles the last hair DOWN into his stance, one after another off --bobd
   (the server's per-man stagger, which used to feed a retired idle bob), and
   then stands still. IDLE BREATHING STAYS DEAD (owner 2026-08-01): `woke` is a
   one-shot the server writes on the entrance render alone, so a refresh or any
   later action's swap cannot replay it. It rides the .fig, so the reveal gate
   holds it with every other figure motion and the reduced-motion block below
   already kills it. Both frames carry the same one transform function, the law
   the walk above paid for. */
.lane .unit.foe.woke .figbox .fig{animation:foeWoke .42s cubic-bezier(.22,.72,.3,1) var(--bobd,0s) both}
@keyframes foeWoke{0%{transform:translateY(-.34cqw)}100%{transform:translateY(0cqw)}}
/* THE LANE JOLTS on any blood - a beat longer so it's felt. */
.lane.shake{animation:laneShake .4s ease-out}
@keyframes laneShake{0%,100%{transform:translate(0,0)}20%{transform:translate(-.7%,.35%)}
  45%{transform:translate(.6%,-.35%)}70%{transform:translate(-.4%,.2%)}88%{transform:translate(.2%,-.1%)}}
/* THE HEAVY BEAT - the Headsman's weathered counter and the Cutthroat's reckoning
   jolt the room harder, and the room leans in: a 1.015 push at the hardest jolt,
   HELD through the middle so it reads as a lean rather than more noise, released
   at the end (SUR-78 stage S, second sitting - 1.012-decaying-in-sync read as
   nothing). Every frame carries the same transform list. */
.lane.shake-heavy{animation:laneShakeHeavy .52s cubic-bezier(.2,.9,.3,1)}
@keyframes laneShakeHeavy{0%,100%{transform:translate(0,0) scale(1)}12%{transform:translate(-1.8%,.9%) scale(1.015)}
  30%{transform:translate(1.5%,-.8%) scale(1.015)}50%{transform:translate(-1.1%,.6%) scale(1.014)}70%{transform:translate(.7%,-.4%) scale(1.01)}88%{transform:translate(-.3%,.15%) scale(1.004)}}
@media (prefers-reduced-motion:reduce){.lane .unit .figbox .fig,.lane .unit.you .figbox .figwrap{animation:none !important}
  .lane.shake,.lane.shake-heavy{animation:none}
  .lane .unit.you.stepin{animation:none}
  .lane .unit.moved{animation:none}}

/* ---- REVEAL GATE (KB-099) ------------------------------------------------
   Hold the combatants hidden and their event motion FROZEN until the art has
   decoded, so a cold load never flashes a black box and never spends the
   hit/death animation on an un-drawn figure. `js-gate` is added by a one-shot
   inline script the instant it runs; `ready` when the figures decode. With NO
   script neither class appears, so the lane is visible and animates exactly as
   before - the no-JS fight screen stays law. The foe silhouette is inlined in
   this sheet (it decodes WITH the CSS), so in practice only the player <img> is
   ever awaited. The pause rules match the animation rules' specificity (they
   too carry .figbox) and sit later in the file, so the longhand play-state wins. */
.lane.js-gate:not(.ready) .unit .figbox .fig{opacity:0}
.lane.js-gate:not(.ready) .unit .figbox .fig,
.lane.js-gate:not(.ready) .unit .figbox .figwrap,
.lane.js-gate:not(.ready).shake{animation-play-state:paused}
.lane.js-gate.ready .unit .figbox .fig{opacity:1;transition:opacity 90ms ease-out}
@media (prefers-reduced-motion:reduce){.lane.js-gate.ready .unit .figbox .fig{transition:none}}
/* HOLD THE WHOLE ARENA, not just the figures (owner 2026-08-05): while the gate is up
   EVERY lane child is hidden, so nothing paints in piecemeal behind the spinner; the
   full arena fades in together the instant .ready lands. The ::after spinner is a
   pseudo-element, so it survives this blanket hide. */
.lane.js-gate:not(.ready) > *{opacity:0}
/* on reveal DON'T force opacity:1 (that would light up children the fight keeps hidden
   - the aim arc, any hover-gated overlay); just transition, and each child falls back
   to its OWN opacity (a unit to 1, the .arc to its default 0). */
.lane.js-gate.ready > *{transition:opacity 240ms ease-out}
@media (prefers-reduced-motion:reduce){.lane.js-gate.ready > *{transition:none}}
/* A VISIBLE WAIT (owner 2026-08-04). While the gate holds the figures (until they
   decode, capped by the gate's own ~1.5s timer), spin a ring so the pause reads as
   loading rather than a blank flash. It rides the lane's own ::after and vanishes
   the instant .ready lands; on a warm load the gate holds a short 300ms beat
   (owner 2026-08-15), so the ring barely shows. No script -> no gate -> no
   spinner, and the figures paint immediately. */
.lane.js-gate:not(.ready)::after{content:"";position:absolute;left:50%;top:50%;
  width:32px;height:32px;margin:-16px 0 0 -16px;border-radius:50%;
  border:3px solid rgba(230,219,200,.15);border-top-color:rgba(230,219,200,.72);
  animation:laneSpin .72s linear infinite;z-index:5;pointer-events:none}
@keyframes laneSpin{to{transform:rotate(360deg)}}

/* The slabs: ember is yours, blood is theirs, and theirs fade with depth. */
/* The slab carries its OWN perspective: a rotation inherited through a
   positioned ancestor flattens to a squashed box, and a squashed box is not a
   floor. Each one is its own little diamond. */
.lane .slab{position:absolute;bottom:-6px;
            width:calc(var(--s, .9) * 150 / 1200 * 100cqw);
            height:calc(var(--s, .9) * 74 / 1200 * 100cqw);
            z-index:1;border:1px solid transparent;
            transform:perspective(190px) rotateX(58deg)}
/* THE SLABS carry more weight in the reference than they did here: a foe's is a
   FILLED diamond of blood, not a wireframe, and yours is an ember outline that
   is never filled - the floor you own is drawn, the floor they hold is claimed. */
/* THE FLOOR IS A BOARD OF SIX LANES (KB-146, owner 2026-08-15). The dividers
   between the lanes and the reach fill on them are SERVER-PLACED at the same
   lane arithmetic the men stand by (dice_fight.cpp emits both), so the board
   and the figures cannot drift. Both lean by one shared skew from the floor
   line - the bottom edge stays exactly on the lane boundary, the top drifts
   right, the sketch's own stroke. Each layer is a settings switch; either can
   be absent. Under the men, takes no press, no JS.
   (Dials: --gridlean, the line alphas, the fill gradients.) */
.lane{--gridlean:-16deg}
.lane .gridline{position:absolute;z-index:0;pointer-events:none;
  left:calc(var(--x) * 1%);bottom:7%;width:0;height:calc(var(--yt) * 1%);
  border-left:1px solid rgba(255,150,66,.34);
  box-shadow:-1px 0 6px rgba(232,90,16,.22), 1px 0 6px rgba(232,90,16,.22);
  transform:skewX(var(--gridlean));transform-origin:bottom left;
  -webkit-mask-image:linear-gradient(to top, #000 0, #000 55%, transparent 95%);
  mask-image:linear-gradient(to top, #000 0, #000 55%, transparent 95%)}
.lane .gridband{position:absolute;z-index:0;pointer-events:none;
  left:calc(var(--x) * 1%);bottom:7%;
  width:calc(var(--w) * 1%);height:calc(var(--yt) * 1%);
  transform:skewX(var(--gridlean));transform-origin:bottom left}
.lane .gridband.lit{background:linear-gradient(to top,
  rgba(232,90,16,.15), rgba(232,90,16,.05) 55%, rgba(232,90,16,0) 92%)}
.lane .gridband.cold{background:linear-gradient(to top,
  rgba(74,92,106,.12), rgba(74,92,106,.04) 55%, rgba(74,92,106,0) 92%)}
.lane .slab.ember{border-color:rgba(232,90,16,.62);background:none;border-style:dashed}
.lane .slab.blood{border-color:rgba(140,44,50,.62);background:rgba(110,28,35,.20)}
.lane .d2 .slab.blood{border-color:rgba(140,44,50,.48);background:rgba(110,28,35,.15)}
.lane .d3 .slab.blood{border-color:rgba(140,44,50,.36);background:rgba(110,28,35,.11)}

/* THE CROWN PLATE: the tell, the name, the bar - one block per foe, sitting
   above the head at any stature and never dimmed. */
/* THE PLATE IS ANCHORED LEFT of the man, not centred on him, and the pieces in
   it are left-aligned. A file steps back and up, so left-anchored plates climb
   away as a ladder and each one's text starts clear of the last; centred plates
   met in the middle and welded three names into one (the hall, six men). */
/* The plate rides above the head - but never out through the ceiling. A tall man
   in a short room (the front file of a chamber on a narrow window) pushed his own
   name past the top of the lane, which clips. It sinks onto his shoulders instead:
   the design's own plates overlap their figures anyway. */
/* NAME / HP / TELL ride BELOW the pedestal now (owner 2026-08-04): the space over
   the head belongs to the status marks. Absolute, centred just under each figure. */
.lane .plate{position:absolute;z-index:5;
             top:calc(100% + 6px);bottom:auto;
             left:50%;transform:translateX(-50%);
             display:flex;flex-direction:column;align-items:center;gap:2px;
             white-space:nowrap;pointer-events:none}
/* YOUR plate rides in NORMAL FLOW above the figure, not pinned to a hardcoded --h
   (owner 2026-08-01): order:-1 lifts it above the figbox in the unit's flex column,
   so it sits over the head at ANY figure height - the token's, a figurine's, or a
   boss's - with no number to mismatch. Centred, since you stand alone; the FILE
   rule above (left-anchor + --h ladder) stays the foes', to keep a rank's names
   from welding. */
.lane .unit.you .plate{position:absolute;left:50%;transform:translateX(-50%);
                       top:calc(100% + 6px);bottom:auto;order:0;margin:0;
                       align-items:center;text-align:center}
/* The tell rides beside the name and the badges beside the bar: what used to be
   six stacked rows is three, and the plate stops being taller than the man. */
.lane .plate .pline{display:flex;align-items:center;gap:5px}
.lane .plate .tell{flex:none;width:20px;height:20px;border:1px solid #E8C87A;background:#241B0C;
                   color:#E8C87A;border-radius:5px;display:flex;align-items:center;
                   justify-content:center;font-size:.82rem;line-height:1}
/* ONE BADGE, TWO FACES (SUR-84). The tell keeps its own box untouched - its
   lighting, its glow and its lit-on-a-checked-die rules all target .tell.tN and
   nothing else - and the seam is a second, quieter chip pressed under it. Gold
   over blood: the tell is the crit everyone gets, the seam is the anatomist's
   read, and a die can only ever be one of them. It costs the plate one row's
   height and the field no width, which is the trade NvN can pay. */
.lane .plate .tfaces{flex:none;display:flex;flex-direction:column;align-items:center;gap:1px}
/* THE FLOOR IS THE ONE THING THAT CANNOT MOVE (KB-076). The plate hangs below the
   feet, the lane clips, and the small rooms had 12px of headroom or less before this
   badge - a closet already shaved its own last row. The badge is 17px taller than the
   chip, so the plate is lifted by 17 (6 - 17 = -11) and its BOTTOM edge stays exactly
   where it was in every room. What that spends is the gap over the boards, and the
   badge rides the plate's left end, well outside the man's own silhouette. */
.lane .unit.seamed .plate{top:calc(100% - 11px)}
.lane .plate .seam{flex:none;width:20px;height:16px;border:1px solid rgba(176,74,66,.9);
                   background:#1A0D0D;border-radius:4px;display:flex;align-items:center;
                   justify-content:center;font-size:.72rem;line-height:1}
.lane .plate .seam .pips{padding:2px}
.lane .plate .seam .pips i{width:3px;height:3px;background:#E0938A}
.lane .plate .seam .unknown{font:600 .62rem/1 var(--mono,monospace);color:#8F877C}
/* A MAN'S NAME IS NOT SMALL PRINT (KB-101's own floor, honoured late). It sat at
   10.6px - under the floor the legibility law set - and the plate is a
   shrink-to-fit flex column with no fixed height, so it simply grows. */
.lane .plate .nm{font:.66rem/1.2 var(--mono,monospace);color:var(--ink-label,#8F877C)}
.lane .plate .nm.gold{color:#C9A24A}
.lane .plate .bar{position:relative;flex:none;width:52px;height:5px;background:rgba(0,0,0,.6);
                  border:1px solid rgba(0,0,0,.8)}
.lane .plate .bar i{display:block;height:100%;
                    background:linear-gradient(90deg,#6E1C23,#4A1015)}
.lane .unit.you .plate .bar i{background:linear-gradient(90deg,#8a2630,#6E1C23)}
/* The bone notch: healing cannot pass the Wick's share. */
.lane .plate .notch{position:absolute;top:-2px;width:1px;height:9px;
                    background:rgba(211,207,199,.7)}
.lane .plate .num{font:.72rem/1.2 var(--mono,monospace);color:var(--ink-label,#8F877C)}
.lane .unit.you .plate .num{font-size:.82rem;color:var(--ink-body,#D3CCC0)}
/* A trait says so in words as well as in gold: hover never carries a reading on
   its own, so the badge is written out and the name repeats it in its title. */
.lane .plate .badge{font-size:.62rem;letter-spacing:.06em;text-transform:uppercase;
                    color:#C9A24A;opacity:1}

/* ---- the header strip ------------------------------------------------------
   The streak sits LEFT of the queue: it is the main mechanic and belongs above
   the player, not among the men who are about to hit him. */
.lane-head{display:flex;align-items:stretch;flex-wrap:wrap;
           border:1px solid var(--edge);border-bottom:0;background:#101011}
.lane-head .streak{display:flex;align-items:center;gap:8px;padding:8px 12px;
                   border-right:1px solid var(--edge)}
.lane-head .streak b{font:.62rem/1 var(--mono,monospace);letter-spacing:.16em;
                     text-transform:uppercase;color:var(--ink-label,#8F877C)}
.lane-head .streak span{font-size:1.15rem;color:#E85A10;
                        border:1px solid rgba(232,90,16,.5);padding:1px 7px}
.lane-head .streak.hot span{animation:streakpulse 1.6s ease-in-out infinite}
@keyframes streakpulse{50%{box-shadow:0 0 14px rgba(232,90,16,.45)}}
.lane-head .qlbl{display:flex;align-items:center;padding:8px 12px;max-width:104px;
                 border-right:1px solid var(--edge);
                 font:.62rem/1.3 var(--mono,monospace);letter-spacing:.16em;
                 text-transform:uppercase;color:var(--ink-label,#8F877C)}
.lane-head .qchips{display:flex;flex:1;min-width:0;overflow-x:auto}
.lane-head .qchip{display:flex;align-items:center;gap:8px;padding:8px 12px;
                  border-left:3px solid #6E1C23;white-space:nowrap}
.lane-head .qchip b{font:.72rem/1 var(--serif,Georgia,serif);font-weight:400;
                    color:var(--ink-body,#D3CCC0)}
.lane-head .qchip i{font-style:normal;color:#C46A6A}
.lane-head .qchip small{font-size:.62rem;letter-spacing:.1em;text-transform:uppercase;
                        color:var(--ink-faint,#857E74)}
/* A fallen man's swings left the queue with him. */
.lane-head .qchip.dead b{text-decoration:line-through;color:var(--ink-faint,#5C564E)}
.lane-head .qsum{display:flex;align-items:center;gap:6px;padding:8px 12px;
                 margin-left:auto;border-left:1px solid var(--edge)}
.lane-head .qsum b{font-size:1.05rem;color:#C46A6A}
.lane-head .qsum span{font:.62rem/1 var(--mono,monospace);color:var(--ink-label,#8F877C)}
/* THE TWO STREAMS ARE MARKED, NOT ONLY COLOURED (WCAG 1.4.1). Measured: ember
   #E85A10 (L .2451) and the ambient label grey #8F877C (L .2463) sit at 1.00:1
   against each other - in greyscale the blow you DEAL is the same mark as the
   word beside it, and blood and ember both collapse toward one another under
   deuteranopia. What comes AT you now carries a blade glyph; what you deal keeps
   its minus. The mark rides the CHILD, never the tip host - .tip owns both
   ::before and ::after. */
.lane-head .qchip i::before,
.lane-head .qsum b::before{content:"\2694\FE0E";margin-right:.25em;font-size:.72em}
.lane-head .qchip i,
.lane-head .qsum b{font-weight:700}
/* THE BILL IS LETHAL (SUR-48): a braced end-turn that still ends you wears the
   break-off's own fatal colours, so the warning stands on the sum without a hover.
   Higher specificity than the narrow-width reset, so it holds on a phone too. */
.lane-head .qsum.fatal{border:1px solid #8E2A2A;background:#1C1210}
.lane-head .qsum.fatal b{color:#B04A42}
.lane-head .qsum.fatal span{color:#B87070}
.lane .plate .hitnum,
.lane .plate .part .pd{font-weight:400}

/* THE PHASE WORD SHRINKS, IT DOES NOT GO (SUR-72). It was the first thing the
   strip sacrificed, and it is the only thing on the strip that says whether
   these swings are yours to answer or theirs to land. It sheds the 104px column
   and the tracking instead, and heads the chips as a prefix. */
@media (max-width:700px){
  .lane .plate .nm{font-size:.62rem}
  .lane .plate .bar{width:54px}
  .lane-head .qlbl{max-width:none;letter-spacing:normal;padding:4px 8px;
                   border-right:0}
}
@media (prefers-reduced-motion:reduce){
  .lane-head .streak.hot span{animation:none}
}

/* ---- three states of ONE page (DD-14 D-3, screens 11A/11B/11C) -------------
   The opener, the stop-frame and the corpse law are not other screens: they are
   this screen, reading differently. Nothing here navigates; every one of them is
   the same lane with different things true about it. */

/* 11A - THE OPENER. You are still in the doorway. Your slab waits with a dashed
   ring and a dashed step leading to it, the tells are question marks, and what
   you know of the room is your presence and nothing else. The read stands in
   the HEADER's right cell - where the queue will stand once you walk in; the
   order of battle is earned, not handed over from the doorway. */
.lane-head .doorread{display:flex;align-items:center;gap:6px;padding:8px 12px;
                     margin-left:auto;border-left:1px solid var(--edge);
                     font:.66rem/1.6 var(--mono,monospace);color:var(--ink-label,#8F877C)}
.lane-head .doorread b{color:#C9A24A}
.lane-head .doorread i{font-style:normal;color:var(--ink-body,#D3CCC0)}
.lane .slab.waiting{border-style:dashed;background:none}
.lane .slab.waiting::after{content:"";position:absolute;inset:8px;
                           border:1px dashed rgba(232,96,24,.35)}
/* A SLAB WITHOUT A MAN ON IT. In the opener your ground is drawn and you are
   not on it yet - the mark is the floor's, so it carries no figure and no
   label. What stood here was a dashed box with rotated text inside it, which
   the design never draws and which read as a hole in the room. */
.lane .unit.slabonly{pointer-events:none}
.lane .unit.slabonly .slab.ember{border-style:dashed;opacity:.75}
/* The dashed step from the door to the waiting floor is GONE (owner 2026-08-02):
   the man walks it himself now, and a dotted line telling you where he is about
   to stand is a caption on a thing you are about to watch. */

/* 11B - THE STOP-FRAME. The room dims through a radial time-stop, leaving only
   the pocket where the blow lands still clear. He crosses to a strike slab beside
   you; his own slab is left dashed and empty behind him. */
/* THE POCKET IS NAMED ONCE (SUR-78 stage M, KB-149). The veil's clear eye and the
   camera's push share one point, so moving the veil carries the lean with it. */
.lane{--pocket-x:34%;--pocket-y:62%}
.lane .stopveil{position:absolute;inset:0;z-index:3;pointer-events:none;
                background:radial-gradient(ellipse 42% 60% at var(--pocket-x) var(--pocket-y),
                           rgba(0,0,0,0) 0%, rgba(0,0,0,.55) 62%, rgba(0,0,0,.78) 100%)}
/* SUR-55: a CHOICELESS spent-hand stop-frame is one Brace, not a pointer marathon.
   A transparent full-lane button over the veil takes the swing on a tap anywhere;
   the dim and the lit striker below are untouched. Rendered only when the server
   knows there is no die to spend and no free answer - a frame with a choice keeps
   its buttons. With JS off it is a plain submit; the console's Take still stands. */
.lane .takeall{position:absolute;inset:0;z-index:5;border:0;background:transparent;
  cursor:pointer;display:flex;align-items:flex-end;justify-content:center;padding:0 0 11%}
.lane .takeall .takeall-hint{font:600 .66rem/1 var(--mono,monospace);letter-spacing:.14em;
  text-transform:uppercase;color:rgba(230,219,200,.6);pointer-events:none;
  border:1px solid rgba(230,219,200,.2);border-radius:3px;padding:5px 10px;background:rgba(10,8,7,.4)}
.lane .takeall:hover .takeall-hint,.lane .takeall:focus-visible .takeall-hint{
  color:rgba(230,219,200,.92);border-color:rgba(230,219,200,.42)}
/* THE CAMERA LEANS INTO THE STOP-FRAME (SUR-78 stage M, KB-149). The ROOM pushes
   toward the pocket under the server's react state; the men, the veil and the
   lane's own box hold still - a frame that grows is a layout artefact.
   STATIC, never animated: the stop-frame outlives several answers, each one a
   fight-swap re-render, and an animation restarts on a swapped-in node.
   `scale`, not `transform`: it composes, so the plates keep their drift and the
   shake keyframes still play over the lean. */
.lane.stopped > .room{scale:1.025;transform-origin:var(--pocket-x) var(--pocket-y)}
/* The band ends at the seam, so the lane's pocket lies deeper in its own box. */
.lane.stopped > .scape{scale:1.025;transform-origin:var(--pocket-x)
                       calc(var(--pocket-y) * 100 / (100 - var(--seam,31)))}
@media (prefers-reduced-motion:reduce){
  .lane.stopped > .room,.lane.stopped > .scape{scale:none}
}
.lane.stopped .unit{z-index:2}
.lane.stopped .unit.you,.lane.stopped .unit.attacking{z-index:4}
/* He HOLDS HIS GROUND (owner 2026-08-03). The striker used to step toward you,
   but ANY offset makes the foe jump as the turn hands to the answer and back -
   the two phases must place him identically. His slab still marks him ember
   below; nothing on the figure moves. */
.lane.stopped .unit.attacking{transform:translateX(-50%)}
.lane.stopped .unit.attacking .slab{border-style:dashed;
                                    border-color:rgba(232,96,24,.5);
                                    background:rgba(232,96,24,.05)}
/* THE SHADOW STAYS A SHADOW (owner 2026-08-02). This frame used to LIGHT the
   swinger with brightness(1) - written when a foe wore his own art. Now the foe
   is the player's own figurine blacked out, so lighting it revealed a second
   colour player facing the first. Who is swinging is already told by the veil
   dimming everyone else, his NOW chip in the strip, his name in the answer lead,
   and his red rim, so the cut-out is left un-lit. The heavier box drop-shadow is
   kept as a quiet lift; the borrowed-figurine's brightness(0) is not disturbed. */
.lane.stopped .unit.attacking .fig,
.lane.stopped .unit.attacking .figbox{filter:brightness(1) drop-shadow(0 8px 14px #000)}
/* His chip in the strip flares, and says so in words as well as in colour. */
.lane-head .qchip.cur{border-left-color:#E85A10;background:rgba(232,90,16,.07)}
.lane-head .qchip.cur::after{content:"NOW";font:.62rem/1 var(--mono,monospace);
                             letter-spacing:.14em;color:#E85A10}
.lane-head .qchip.done{opacity:.45}

/* 11C - THE CORPSE LAW IS AMENDED (SUR-92): a felled figurine falls off the
   board and vanishes; no bone slab marks where he stood. */

/* ---- THE CONSOLE (DD-14 D-3) -----------------------------------------------
   One bounded block under the lane: the hand, the verbs, the two tabs, the log.
   Everything in it works with JavaScript off - the tabs are radio + :checked and
   the log is a <details>, because JS may enhance and may never carry. */

.console{border:1px solid var(--edge);border-top:0;background:#101011;
         padding:10px 12px 12px;position:relative}
.console .cb-callout{margin:0 0 8px;font:12px/1.5 var(--mono,monospace);
                     color:var(--ink-label,#8F877C)}

/* THE ANSWERS replace the verbs in the stop-frame; they are not another row of
   them. Take it is always yours, and the other two spend a die you kept back. */
.console .answers{border:1px solid rgba(232,90,16,.35);padding:8px 10px;margin:0 0 8px}
.console .answers .alead{display:flex;align-items:baseline;gap:8px;margin-bottom:6px}
.console .answers .alead b{font:.82rem/1.2 var(--serif,Georgia,serif);font-weight:400;
                           color:var(--ink-emph,#F0EAE0)}
.console .answers .alead span{font-size:.62rem;letter-spacing:.14em;text-transform:uppercase;
                              color:var(--ink-label,#8F877C)}
.console .answers .saved{margin-top:6px;display:flex;align-items:baseline;gap:6px}
.console .answers .saved b{color:#E85A10}
.console .answers .saved span{font:.62rem/1 var(--mono,monospace);
                              color:var(--ink-label,#8F877C)}
/* THE ANSWER'S NUMBER IS A DECISION NUMBER (KB-101). <small> is a UA ratio, not
   a size: .8333 of whatever it inherits. Inside the button it landed under the
   floor, while the bare "no dice" note beside it inherited the body and came out
   LARGER than the figure you spend a die on. Both are named here. This is also
   the room the BRACE and TITHE work needs: "Brace 3 (was 6)" and a live
   percentage must read at .72rem without wrapping. */
.console .answers .cb-rbtn small{font:400 .72rem/1.3 var(--mono,monospace);
                                 color:var(--ink-body,#D3CCC0)}
.console .answers .cb-rrow > small{font:400 .66rem/1.4 var(--mono,monospace);
                                   color:var(--ink-label,#8F877C)}
/* SUR-55 P2: the bulk-brace shortcut stands a touch brighter than the single Brace
   beside it - it is the whole answer to a choiceless tail. */
.console .answers .cb-rbtn.takerest{border-color:rgba(232,90,16,.45)}
.console .answers .cb-rbtn.takerest:hover{border-color:rgba(232,90,16,.75)}
/* THE VULN DIE was gold and nothing else - one hue, on a border and on the text.
   The dash is the signal now; the gold goes back to being decoration. */
.cb-rbtn.vuln{border-style:dashed}
/* AN ANSWER THAT CAN PAY NOTHING IS NOT AN ANSWER (KB-103). A free answer whose
   payoff the cap refuses is greyed rather than left pressable - since the brace
   landed, taking a swing whole for nothing is strictly worse than standing. */
.cb-rbtn.off,.cb-rbtn[disabled]{opacity:.4;cursor:default}
/* THE ANSWERS SIT IN A ROW, not a stack (owner 2026-08-01): "take it" and the
   calling's reaction stand side by side. The swing lead and the saved-tally keep
   their own full-width lines; a wide die-based reaction (dodge/counter/riposte)
   simply wraps to the next line. */
.console .answers{display:flex;flex-wrap:wrap;gap:6px 8px;align-items:baseline}
.console .answers .alead,
.console .answers .saved{flex-basis:100%}
.console .answers .cb-rrow{width:auto}

/* THE TABS. The radios themselves never show; the labels are the control, and
   :checked does the swapping. No request, no script, no state to lose. */
.console .tabr{position:absolute;opacity:0;width:0;height:0;pointer-events:none}
.console .tabs{display:flex;gap:0}
.console .tabs label{padding:2px 7px;cursor:pointer;border:1px solid transparent;
                     font:400 .62rem/1.3 var(--mono,monospace);
                     letter-spacing:.1em;text-transform:uppercase;
                     color:var(--ink-faint,#5C564E)}
.console #tab-belt:checked ~ .tabs label[for=tab-belt],
.console #tab-crawl:checked ~ .tabs label[for=tab-crawl]{
  color:var(--ink-body,#D3CCC0);border-color:var(--edge);background:#141416}
.console .tabpane{display:none;align-items:center;gap:8px;padding:0;min-height:0}
.console #tab-belt:checked ~ .tabpane.belt{display:flex}
.console #tab-crawl:checked ~ .tabpane.crawl{display:flex}
.console .tabpane .thing{display:flex;align-items:center;gap:6px}
.console .tabpane .thing img{width:28px;height:28px;object-fit:contain}
/* THE BELT ITEM IS THE BUTTON (owner 2026-08-12): point at the draught you brought
   and drink it. The "Drink +N" cue stays hidden until hover/focus, so the item
   reads as the thing it is until you mean to use it. No-JS: a plain submit button. */
.console .tabpane button.thing.use{cursor:pointer;border:1px solid #6a5c3a;background:#151517;
  color:#cbc2b4;border-radius:2px;padding:3px 8px;font:inherit;line-height:1}
.console .tabpane .thing.use .drink{display:none;font:600 .62rem/1.2 var(--mono,monospace);color:#e8c87a}
.console .tabpane .thing.use:hover,.console .tabpane .thing.use:focus-visible{
  border-color:#e8c87a;box-shadow:0 0 0 1px #e8c87a inset;outline:none}
.console .tabpane .thing.use:hover .drink,.console .tabpane .thing.use:focus-visible .drink{display:inline}
.console .tabpane .thing.spent{opacity:.5}
.console .tabpane .say{font:12px/1.5 var(--mono,monospace);
                       color:var(--ink-label,#8F877C)}
.console .tabpane .say b{color:var(--ink-body,#D3CCC0)}
/* The crawl tab folds the verbs away while it is open: you are looking at the
   place, not choosing a swing. */
/* Looking at the place, not choosing a swing. */
.console:has(#tab-crawl:checked) .cb-row .grp:not(.last){opacity:.4;pointer-events:none}

/* THE LOG hides the console while it is open: you are either fighting or reading
   what happened, never half of each. */
.console .cblog{margin:8px 0 0;border-top:1px solid var(--edge);padding-top:8px}
.console .cblog summary{cursor:pointer;font:10px/1.4 var(--mono,monospace);
                        letter-spacing:.14em;text-transform:uppercase;
                        color:var(--ink-faint,#5C564E)}
.console .cblog .lines{margin-top:8px;max-height:220px;overflow-y:auto;
                       font:12px/1.7 var(--mono,monospace);
                       color:var(--ink-label,#8F877C)}
/* The classes here are the ones the template actually emits - .cb-row is the
   whole live strip (hand or the roll, verbs, tabs); .cb-openers is the door;
   .answers is the stop-frame. A fold that names classes nobody renders folds
   nothing (DD-15 log-open-does-not-fold-the-console). */
/* IT STEPS ASIDE, IT DOES NOT VANISH. display:none cannot be animated, and a
   strip that blinks out while the record grows smoothly reads worse than no
   animation at all - the console's height would jump the moment you touched it.
   Collapsed instead, so both halves move on the same clock. */
.console:has(.cblog[open]) .cb-row,
.console:has(.cblog[open]) .cb-openers,
.console:has(.cblog[open]) .answers{block-size:0;opacity:0;overflow:hidden;
                                    pointer-events:none;border-top-color:transparent}

/* The door out. Ember-deep, because leaving is yours and it is not a failure -
   and blood-lit when the parting swings would finish you, which the button
   says in words as well. */
.cb-endbar .breakoff{border-color:rgba(232,90,16,.55)}
.cb-endbar .breakoff.fatal{border-color:rgba(110,28,35,.9);color:#B04A42}


/* ---- THE MAN IS THE BUTTON (D-3) -------------------------------------------
   With a verb armed every man you may take becomes a submit; with none armed he
   is a picture. The form is a bare wrapper - it must not disturb the figure's
   placement, which is the whole grammar of the lane. */
/* No wrapper form any more: the man himself is a submit of the one form #f. */
/* The button IS the figure box, so it inherits the same stature arithmetic. It
   had width:100% of a shrink-to-fit parent, which resolves to nothing: the man
   was drawn but there was no target under him to hover or press. */
.lane .unit .figbox.hit{padding:0;cursor:pointer;display:block;
                        background:none;border:0}
.lane .unit .figbox.hit:hover .fig,
.lane .unit .figbox.hit.aimed .fig,
.lane .unit .figbox.hit:focus-visible .fig{filter:brightness(1.25)
                        drop-shadow(0 0 12px rgba(232,90,16,.5))}
.lane .unit .figbox.hit:focus-visible{outline:1px solid #E85A10;outline-offset:3px}
/* THE SHADOW STAYS BLACK WHEN AIMED AT (owner 2026-08-03). The aim-highlight
   above brightens the figure - right for a foe with his own art, but on the
   blacked-out stand-in it un-crushes the fill and reveals the colour player. The
   cut-out keeps brightness(0); the aim reads as a HOTTER ember rim instead. */
.lane .unit.foe.hasfig .figbox.hit:hover .fig,
.lane .unit.foe.hasfig .figbox.hit.aimed .fig,
.lane .unit.foe.hasfig .figbox.hit:focus-visible .fig{filter:brightness(0)
                        drop-shadow(0 0 3px rgba(232,90,16,.95))
                        drop-shadow(0 0 12px rgba(232,90,16,.6))}
/* THE AIMED RUNG (SUR-63). A coarse pointer has no hover, so the read the mouse
   gets for free has to be TAKEN: the first tap on an armed man aims him, the
   second strikes. .aimed is that middle state - added by fight-swap.js, never
   rendered by the server (booked in fight-ui.json beside .marked). It wears the
   aim lift above and adds a solid ember edge and a halo on the whole figure, so
   the ladder reads rest < legal rim (SUR-71) < aimed < the blow itself. The
   halo is the .marked rule's shape in ember rather than scarlet - a mark points
   a man out, an aim says the die is about to be spent on him. */
.lane .unit .figbox.hit.aimed{outline:2px solid #E85A10;outline-offset:3px}
.lane .unit.foe.aimed .figbox{
  filter:brightness(var(--b,.92)) drop-shadow(0 0 4px rgba(232,90,16,.85)) drop-shadow(0 0 15px rgba(232,90,16,.5))
         var(--rim)}
/* What the blow would take, in ember, on his own plate. */
.lane .plate .hitnum{font:.72rem/1.2 var(--mono,monospace);color:#E85A10}
/* THE TELL DOUBLES THE BLOW, and the preview says so in words, not only in a
   bigger number. The mark is a plain child, so it survives greyscale and needs
   no hover - the largest multiplier in the game had never been named on screen. */
.lane .plate .hitnum .crit,
.lane .plate .part .pd .crit{font-style:normal;color:#E8C87A;font-size:.62rem;
                             letter-spacing:.04em;margin-left:3px}

/* THE PARTS live INSIDE the crown plate, in ONE ROW. Stacked, with a bar and a
   damage figure each, they grew the plate to 153 x 99 canvas units - and no room
   can be widened enough to hold six of those without pushing men off the floor.
   At rest a part says only its name and its face WINDOW, which is the thing you
   need before you spend a die; what the blow would take appears when you are
   actually pointing at it. */
/* PART CHIPS BELONG TO THE VAULT. The designer draws none of them on a chamber
   lane - the vault exists precisely because "part plates need the air" - and
   carrying them on every plate is what pushed a crown to 76px tall and made the
   men move apart to accommodate their own labels. They are shown where the design
   shows them, and elsewhere only for the man being pointed at.
   pointer-events:auto because the plate above them is inert - without it these
   buttons have never been clickable at all. */
.lane .plate .parts{display:none;flex-wrap:wrap;gap:3px;margin-top:3px;max-width:150px;
                    pointer-events:auto}
.lane.rm-vault .plate .parts,
.lane.rm-vault_attended .plate .parts,
.lane .unit:hover .plate .parts,
.lane .unit.aimed .plate .parts,
.lane .unit:focus-within .plate .parts{display:flex}
/* A FINGER HAS NO HOVER (SUR-66). Every reveal above is a thing a POINTER does,
   so on a touch screen the row outside a vault was never on the page at all: a
   player had no way to learn that a man even has parts, let alone read the window
   his die is measured against. Where the pointer cannot hover, every man wears his
   parts the way the vault's men do - the same exception, read off the hardware
   instead of the room. The two-tap law (SUR-63) still governs the press. */
@media (hover:none){
  .lane .plate .parts{display:flex}
}
.lane .plate .part{display:flex;align-items:baseline;gap:3px;
                   background:rgba(10,9,8,.9);border:1px solid rgba(110,28,35,.55);
                   padding:0 3px;cursor:pointer;color:var(--ink-label,#8F877C);
                   font:.62rem/1.5 var(--mono,monospace);white-space:nowrap}
.lane .plate .part:hover,
.lane .plate .part.aimed,
.lane .plate .part:focus-visible{border-color:#E85A10;color:var(--ink-body,#D3CCC0)}
/* A part is a commit button too, so it takes the same two taps (SUR-63) and
   wears the held die's own inset edge to say which one is aimed. */
.lane .plate .part.aimed{box-shadow:0 0 0 1px #E85A10 inset}
.lane .plate .part.weak{border-style:dashed;opacity:.7}
.lane .plate .part.gone{text-decoration:line-through;opacity:.4;cursor:default;
                        border-color:var(--edge)}
.lane .plate .part .pn{overflow:hidden;text-overflow:ellipsis;max-width:52px}
/* THE WINDOW IS A DECISION NUMBER (KB-101), and it read at 11.3px - under the
   13px floor, on the one figure that says whether a die lands full or half.
   The chip's own name may stay small; the number a die is measured against may
   not. The line-height is pinned so the taller figure still sits in the chip's
   own 17px line box - the inherited 1.5 would have grown every chip by a fifth
   and the parts block with them.
   AND IT EXPLAINS ITSELF BEFORE THE DIE IS HELD (SUR-66): the chip is inert until
   the blow is legal, and pointer-events:none on the button blocks the hover on its
   children too, so the sentence behind the number could be reached by nothing at
   rest - no hand, no finger. The number takes the pointer back for itself. The
   press it would otherwise let through an inert button (a span inside a button
   still activates that button) is swallowed in fight-swap.js, so the arming sheet
   still decides every press. */
.lane .plate .part .pw{color:#C9A24A;font-size:.72rem;line-height:1.3;
                       pointer-events:auto}
/* The figure and the bar are detail: they cost the whole room when worn at rest. */
.lane .plate .part .pd,
.lane .plate .part .pb{display:none}
/* Which .pd figure shows is the selection's to say - see the ONE FORM section:
   a part carries one per reachable pick, and CSS reveals the picked one. The cut
   is the other decision number on the chip and takes the same floor. */
.lane .plate .part .pd{color:#E85A10;font-size:.72rem;line-height:1.3}

/* ---- THE RICOCHET ARC (9A) -------------------------------------------------
   One layer per man the knife could take, drawn in the room's own 1200xH space
   so it scales with the stage. Hidden until that man is hovered or focused: this
   is a preview, and a preview that carries state is a lie waiting to happen.
   SOLID is the throw you chose; DASHED is what the mod does with it. */
.lane .arc{position:absolute;inset:0;width:100%;height:100%;z-index:7;
           pointer-events:none;opacity:0;transition:opacity .12s ease}
/* CHALK, not fire (owner 2026-08-05): bone on the dark room, a scout's line scratched
   flat to where the blade will bite. SOLID-ish flight = the throw you chose (a faint
   chalk texture, but continuous); dotted = what the ricochet does with it. */
.lane .arc .flight{fill:none;stroke:#cfc7b4;stroke-width:1.7;stroke-linecap:round;
                   stroke-dasharray:11 3.5;opacity:.78;vector-effect:non-scaling-stroke}
.lane .arc .bounce{fill:none;stroke:#cfc7b4;stroke-width:1.5;stroke-dasharray:2.5 5;
                   stroke-linecap:round;opacity:.55;vector-effect:non-scaling-stroke}
.lane .arc .burst path{fill:none;stroke:#e0d9c6;stroke-width:1.7;stroke-linecap:round;
                       vector-effect:non-scaling-stroke}
.lane .arc .burst.small path{stroke-width:1.4;opacity:.7}

/* Which arc shows is decided in the ONE FORM section below: the man hovered
   picks the .aN layer and the checked ricochet dice pick the .bK variant. */

/* ---- THE PRIMER (DD-13 P-10.3) ---------------------------------------------
   The prototype's slides, ported into the fold that already sat at the top of
   the fight. Radio + :checked and nothing else: it is read in the MIDDLE of a
   fight, so it must not carry a request - a page load here would clear the dice
   you had picked. */
.cb-mechs .psr{position:absolute;opacity:0;width:0;height:0;pointer-events:none}
/* The fold itself is :has() on a checkbox of the one form (DD-15): open it,
   fight on, and every redirect hands it back open. The label wears the
   disclosure mark the old <summary> carried. */
.cb-mechs .mxs{cursor:pointer;display:block}
.cb-mechs .mxs::before{content:"\25B8  ";color:var(--ink-faint,#5C564E)}
.cb-mechs:has(#mx:checked) .mxs::before{content:"\25BE  "}
.cb-mechs .mxbody{display:none}
.cb-mechs:has(#mx:checked) .mxbody{display:block}
.cb-mechs .mxs:hover{color:var(--ink-emph,#F0EAE0)}
.cb-mechs .ptabs{display:flex;flex-wrap:wrap;gap:0;margin:8px 0 0;
              border-bottom:1px solid var(--edge)}
.cb-mechs .ptabs label{padding:5px 10px;cursor:pointer;border:1px solid transparent;border-bottom:0;
                    font:.62rem/1.4 var(--mono,monospace);letter-spacing:.12em;
                    text-transform:uppercase;color:var(--ink-faint,#5C564E)}
.cb-mechs .ptabs label:hover{color:var(--ink-label,#8F877C)}
.cb-mechs #ps1:checked ~ .ptabs label[for=ps1],
.cb-mechs #ps2:checked ~ .ptabs label[for=ps2],
.cb-mechs #ps3:checked ~ .ptabs label[for=ps3],
.cb-mechs #ps4:checked ~ .ptabs label[for=ps4],
.cb-mechs #ps5:checked ~ .ptabs label[for=ps5],
.cb-mechs #ps6:checked ~ .ptabs label[for=ps6]{
  color:var(--ink-body,#D3CCC0);border-color:var(--edge);background:#141416}
.cb-mechs .ppane{display:none;padding:10px 2px 2px}
.cb-mechs .ppane p{margin:0;font:.72rem/1.65 var(--serif,Georgia,serif);
                color:var(--ink-label,#8F877C);max-width:74ch}
.cb-mechs #ps1:checked ~ .ppane.s1,.cb-mechs #ps2:checked ~ .ppane.s2,
.cb-mechs #ps3:checked ~ .ppane.s3,.cb-mechs #ps4:checked ~ .ppane.s4,
.cb-mechs #ps5:checked ~ .ppane.s5,.cb-mechs #ps6:checked ~ .ppane.s6{display:block}

/* The hand, shown: four still frames of one round. */
.cb-mechs .pdemo{display:none;align-items:center;gap:8px;margin-top:10px;min-height:44px}
.cb-mechs #pd0:checked ~ .pdemo.d0,.cb-mechs #pd1:checked ~ .pdemo.d1,
.cb-mechs #pd2:checked ~ .pdemo.d2,.cb-mechs #pd3:checked ~ .pdemo.d3{display:flex}
.cb-mechs .pdemo .cb-pip{position:relative;display:flex;align-items:center;justify-content:center;
                      width:34px;height:34px;border:1px solid var(--edge);background:#0E0E10;
                      font-size:24px;line-height:1;color:var(--ink-body,#D3CCC0)}
.cb-mechs .pdemo .cb-pip.held{border-color:#E85A10;color:#F0EAE0;
                           box-shadow:0 0 14px rgba(232,90,16,.35)}
.cb-mechs .pdemo .cb-pip u{position:absolute;left:50%;transform:translateX(-50%);bottom:-13px;
                        text-decoration:none;font:8px/1 var(--mono,monospace);
                        letter-spacing:.1em;text-transform:uppercase;color:#E85A10}
.cb-mechs .pdemo > i{font:.66rem/1.5 var(--mono,monospace);color:var(--ink-faint,#5C564E);
                font-style:normal;margin-left:6px}
.cb-mechs .pdctl{display:flex;gap:4px;margin-top:12px}
.cb-mechs .pdctl label{cursor:pointer;padding:2px 8px;border:1px solid var(--edge);
                    font:.62rem/1.4 var(--mono,monospace);color:var(--ink-faint,#5C564E)}
.cb-mechs #pd0:checked ~ .pdctl label[for=pd0],.cb-mechs #pd1:checked ~ .pdctl label[for=pd1],
.cb-mechs #pd2:checked ~ .pdctl label[for=pd2],.cb-mechs #pd3:checked ~ .pdctl label[for=pd3]{
  border-color:#E85A10;color:#E85A10}

/* PUT THE PRIMER AWAY (KB-116): a "don't show again" tick on the last slide. Its
   OWN post (it persists), never the fold's request-free form. No :has() - the
   ceiling holds. */
.cb-mechs .mxoff{margin-top:14px;padding-top:11px;border-top:1px solid var(--edge)}
.cb-mechs .mxoff label{display:inline-flex;gap:7px;align-items:center;cursor:pointer;
  font:.7rem/1.4 var(--mono,monospace);letter-spacing:.05em;color:var(--ink-label,#8F877C)}
.cb-mechs .mxoff label:hover{color:var(--ink-emph,#F0EAE0)}
.cb-mechs .mxoff-note{display:block;margin-top:6px;
  font:.72rem/1.6 var(--serif,Georgia,serif);color:var(--ink-faint,#5C564E);max-width:74ch}
.cb-mechs .mxoff .btn{margin-top:10px}


/* ---- THE CONSOLE STRIP -----------------------------------------------------
   ONE BAR, TWO ROWS, sized off the designer's own console. It had grown to seven
   times its height: the groups wrapped onto separate lines, the dice were 46px
   with a 2rem pip, and every button carried the padding of a page control. A
   control bar this tall pushes the room it belongs to off the screen.

   Nothing here is decorative. The three groups are the three things a turn is
   made of - the hand you were dealt, what you may do with it, and what you
   brought - and the hairlines between them are what makes that legible at a
   glance rather than after reading. */

.console{padding:8px 10px 10px}

/* The head: what happened, and the door to the log. */
.console .cb-head{display:flex;align-items:baseline;gap:14px;margin:0 0 8px}
.console .cb-callout{margin:0;flex:1;min-width:0;
                     font:400 .74rem/1.5 var(--mono,monospace);
                     color:var(--ink-label,#8F877C)}
.console .cb-callout b{color:var(--ink-emph,#F0EAE0);font-weight:400}
.console .cblog{margin:0;border:0;padding:0;flex:none}
.console .cblog summary{cursor:pointer;list-style:none;
                        font:400 .54rem/1.3 var(--mono,monospace);letter-spacing:.16em;
                        text-transform:uppercase;color:var(--ink-faint,#5C564E)}
.console .cblog summary::-webkit-details-marker{display:none}
.console .cblog summary:hover{color:var(--ink-label,#8F877C)}
.console .cblog .lines{margin:8px 0 0;max-height:200px;overflow-y:auto;
                       font:400 .7rem/1.7 var(--mono,monospace);
                       color:var(--ink-label,#8F877C)}

/* The row itself: three groups, never wrapping, divided by a hairline. */
.console .cb-row{display:flex;align-items:stretch;flex-wrap:nowrap;gap:0;
                 border:1px solid var(--edge);background:#141416;overflow-x:auto}
.console .cb-row .grp{display:flex;flex-direction:column;gap:6px;
                      padding:7px 12px;border-right:1px solid var(--edge);
                      flex:none;min-width:0}
.console .cb-row .grp.verbs{flex:1 1 auto}
.console .cb-row .grp.last{border-right:0}
.console .cb-row .glbl{font:400 .62rem/1 var(--mono,monospace);letter-spacing:.12em;
                       text-transform:uppercase;color:var(--ink-faint,#5C564E);
                       white-space:nowrap}
.console .cb-row .glbl b{color:var(--ink-label,#8F877C);font-weight:400;font-size:.72rem}
.console .cb-row .gbody{display:flex;align-items:center;gap:6px;flex-wrap:nowrap}

/* The hand. A die is a die, not a tile. */
.console .cb-row .cb-die{width:34px;height:34px;flex:none;border-radius:3px}
.console .cb-row .cb-pip{font-size:1.4rem}
.console .cb-row .gstack{display:flex;flex-direction:column;gap:3px;margin-left:2px}
.console .cb-row .gnudge{display:flex;align-items:center;gap:2px}
.console .cb-row .gnudge .gn{order:3;margin-left:3px;font-style:normal;
                             font:400 .72rem/1 var(--mono,monospace);
                             color:var(--ink-faint,#5C564E)}
.console .cb-row .cb-rbtn{padding:2px 6px;font:400 .62rem/1.25 var(--mono,monospace);
                          border-radius:2px}

/* The verbs. Small, centred, two words. */
.console .cb-row .cb-vbtn{padding:4px 9px;min-width:0;border-radius:2px;gap:2px}
.console .cb-row .cb-vbtn .vn{font:600 .68rem/1.15 var(--mono,monospace)}
.console .cb-row .cb-vbtn .vf{font:400 .62rem/1.2 var(--mono,monospace)}
.console .cb-row .cb-vbtn.breakoff{border-color:rgba(140,44,50,.7)}
.console .cb-row .cb-vbtn.breakoff.fatal{border-color:#8E2A2A;color:#E0B8B8}

/* END TURN is the only wide thing in the bar: it is what you press last. */
.console .cb-end{border:1px solid #6a5c3a;background:#1A1A1C;color:#E6DBC8;
                 padding:0 14px;margin-left:4px;cursor:pointer;align-self:stretch;
                 white-space:nowrap;border-radius:2px;
                 font:600 .66rem/1 var(--mono,monospace);letter-spacing:.12em;
                 text-transform:uppercase}
.console .cb-end:hover{border-color:#E8C87A;color:#F0EAE0}

/* What you carry, at the right-hand end. */
.console .cb-row .grp.last .gbody{min-height:34px}
.console .cb-row .tabpane .thing img{width:26px;height:26px}
.console .cb-row .tabpane .say{font:400 .62rem/1.4 var(--mono,monospace);
                               max-width:26ch;overflow:hidden;text-overflow:ellipsis;
                               white-space:nowrap}

/* A CUE NEVER WRAPS. "no pair in hand" broke onto three lines inside a 65px
   button, which grew it to 78px and dragged the whole bar to seven times the
   designer's height - the row is only ever as tall as its tallest word. */
.console .cb-row .cb-vbtn{white-space:nowrap}
.console .cb-row .cb-vbtn .vn,
.console .cb-row .cb-vbtn .vf{white-space:nowrap}
/* A VERB BUTTON NEVER WIDENS THE STRIP (owner 2026-08-14). The name sets the
   width; a long state-cue (gut's spent line) is capped and ellipsized rather
   than dragging the whole row wide. The full line still lives in the button's
   title, so nothing is lost on hover. */
.console .cb-row .cb-vbtn{max-width:16ch}
.console .cb-row .cb-vbtn .vn,
.console .cb-row .cb-vbtn .vf{max-width:100%;overflow:hidden;text-overflow:ellipsis}
/* Every button here is wrapped in its own form, so the form is the flex child
   and the button inside it never saw the row's height. */
.console .cb-row .gbody form{display:flex;align-items:stretch}
.console .cb-end{height:auto;align-self:auto;display:flex;align-items:center}
.console .cb-row .gstack{justify-content:center}

/* ONE HEIGHT FOR EVERY CONTROL IN THE ROW. Left to their content the disabled
   verbs collapsed to 19px (they carry no form to hold them open) while END TURN
   stayed 13px inside one. A control bar reads as a bar because its controls are
   the same size, not because they happen to line up. */
.console .cb-row .cb-vbtn,
.console .cb-row .cb-end{height:35px;flex:none;box-sizing:border-box}
.console .cb-row .cb-end{justify-content:center}
/* The reroll and the nudge stand as tall as the dice beside them, together. */
.console .cb-row .cb-rbtn{padding:1px 6px;font-size:.58rem;line-height:1.3;
                          height:15px;box-sizing:border-box;
                          display:flex;align-items:center;justify-content:center}
.console .cb-row .gstack{gap:4px}

/* The strip is a bar, not a paragraph: what a group cannot say in a few
   characters it says in a tooltip. */
.console .cb-row .tabpane .say{max-width:16ch}

/* The head line: the man on the left, where and when on the right. */
.cb-head-line{display:flex;align-items:baseline;justify-content:space-between;
              gap:16px;margin:0 0 10px}
.cb-head-line h1{margin:0}
.cb-where{font:400 .62rem/1 var(--mono,monospace);letter-spacing:.18em;
          text-transform:uppercase;color:var(--ink-faint,#5C564E);white-space:nowrap}

/* ---- THE DOOR: the openers ---------------------------------------------------
   Cards, not buttons. Each carries its terms, and they share the row equally so
   the choice reads as one question with several answers rather than a pile of
   controls of different sizes. Two of the four only exist inside a place: you
   cannot slip PAST a man in a street, and there is no room there to rob. */
.cb-openers{display:flex;gap:8px;margin:10px 0 0;align-items:stretch}
.cb-openers form{flex:1 1 0;min-width:0;display:flex}
.cb-openers .op{flex:1 1 auto;min-width:0;cursor:pointer;text-align:left;
                display:flex;flex-direction:column;gap:5px;
                padding:9px 12px;border:1px solid var(--edge);background:#141416;
                color:var(--ink-body,#D3CCC0)}
.cb-openers .op:hover{border-color:#6a5c3a}
.cb-openers .opn{font:600 .82rem/1.1 var(--serif,Georgia,serif);letter-spacing:.01em;
                 color:var(--ink-emph,#F0EAE0)}
.cb-openers .opt{font:400 .62rem/1.5 var(--mono,monospace);letter-spacing:.06em;
                 text-transform:uppercase;color:var(--ink-faint,#5C564E);
                 white-space:normal}
/* The one that is always yours is the filled one. */
.cb-openers .op.free{background:#E6DCCB;border-color:#E6DCCB}
.cb-openers .op.free .opn{color:#100D0B}
.cb-openers .op.free .opt{color:#48484E}
.cb-openers .op.free:hover{background:#F0E8DA;border-color:#F0E8DA}
/* The opener cards are direct flex children now (no wrapper forms). */
.cb-openers > .op{flex:1 1 0}
/* SUR-57: the door's closing strip - the law the whole screen rests on, set faint
   under the card row so it reads as an epigraph, not another control. */
.oplaw{margin:9px 0 0;text-align:center;font:400 .62rem/1.4 var(--mono,monospace);
       letter-spacing:.08em;text-transform:uppercase;color:var(--ink-faint,#5C564E)}

/* ---- ONE FORM, NO ROUND TRIPS (DD-15 FLOW) ---------------------------------
   The dice are checkboxes and the verb is a radio inside the single fight form.
   Everything below arms the men, reveals the previews and lights the tells off
   :has() - a pick and an arm cost NOTHING: no POST, no redirect, no re-render,
   no fold lost. The server renders every reading these rules could need and
   the resolver still re-judges the whole selection when the form posts. */

/* The state block: real inputs, held off-screen but focusable, so the keyboard
   can pick dice and arm a verb without a pointer. */
.hand-state input{position:absolute;opacity:0;width:0;height:0;pointer-events:none}

/* The held die and the armed verb, read straight off :checked. */
.cb-wrap:has(#pk0:checked) .cb-die.s0,
.cb-wrap:has(#pk1:checked) .cb-die.s1,
.cb-wrap:has(#pk2:checked) .cb-die.s2,
.cb-wrap:has(#pk3:checked) .cb-die.s3,
.cb-wrap:has(#pk4:checked) .cb-die.s4,
.cb-wrap:has(#pk5:checked) .cb-die.s5{border-color:#e8c87a;box-shadow:0 0 0 1px #e8c87a inset}
.cb-wrap:has(#vb-strike:checked) label[for=vb-strike],
.cb-wrap:has(#vb-twin:checked) label[for=vb-twin],
.cb-wrap:has(#vb-knife:checked) label[for=vb-knife],
.cb-wrap:has(#vb-gut:checked) label[for=vb-gut]{
  border-color:#e8c87a;box-shadow:0 0 0 1px #e8c87a inset}
label.cb-vbtn{cursor:pointer}
.cb-vbtn.off{opacity:.4}

/* The keyboard's place marker, since the inputs themselves are off-screen. */
.cb-wrap:has(#pk0:focus-visible) .cb-die.s0,
.cb-wrap:has(#pk1:focus-visible) .cb-die.s1,
.cb-wrap:has(#pk2:focus-visible) .cb-die.s2,
.cb-wrap:has(#pk3:focus-visible) .cb-die.s3,
.cb-wrap:has(#pk4:focus-visible) .cb-die.s4,
.cb-wrap:has(#pk5:focus-visible) .cb-die.s5,
.cb-wrap:has(#vb-strike:focus-visible) label[for=vb-strike],
.cb-wrap:has(#vb-twin:focus-visible) label[for=vb-twin],
.cb-wrap:has(#vb-knife:focus-visible) label[for=vb-knife],
.cb-wrap:has(#vb-gut:focus-visible) label[for=vb-gut]{
  outline:1px solid #E85A10;outline-offset:2px}

/* THE MAN IS THE BUTTON - but only while the whole action is legal. Unarmed, he
   is a picture. */
.lane .figbox.hit{pointer-events:none}
.lane .plate button.part,
.muster button.part{pointer-events:none}
/* ONE RULE PER VERB, AND IT CARRIES THE WHOLE LAW (SUR-94/SUR-95).
   The arming used to say two things only - a verb is armed, a die is held - and
   a second family further down tried to shut the man again whenever the rest of
   the selection was wrong. That family never landed: an arming rule carries an
   id (#vb-strike) inside its :has(), so every later gate lost the specificity
   fight, and a mouse could press a man with two dice on Strike and eat a
   wrong_dice_count the sheet was written to prevent. MEASURED with
   getComputedStyle, not read off the page - the bug survived reading.
   So legality is written ONCE, in the rule that arms: a man takes a press only
   where can_use() and execute() would both say yes, and there is nothing left
   for a later rule to out-rank. One source of truth per state.

   The law mirrored here is the verb table (verbs.cpp) and execute()'s reach
   gate, read from the code, not remembered:
     strike   exactly one die   - the frontmost rank, or the 2nd with a 3+ die
     flay     exactly one die   - the same gate, exactly (SUR-82: it is a near
              cutter that spends one die, so it joins strike's LIST rather than
              opening a line of its own - the law is written once)
     twin     a matching pair   - the frontmost rank, or the 2nd with a 3+ pair
     knife    one low die, or several that are every one a ricochet - any rank
     condemn  exactly one die   - any rank (KB-086: the sentence is read, not
              swung, so it reaches the whole room like the thrown knife)
   KB-144's 3+ toll holds whatever the weapon is: a reach-2 weapon takes the
   falloff off the blow, never the gate off the die - execute() asks for face 3
   at the 2nd rank with no word about the hero's reach.

   Two server-emitted words carry what CSS cannot compute: `deep` on a die input
   of face 3 or better, and `far2` on the man in the 2nd alive rank. `lo` could
   not serve: it is face <= 3 for the knife's own law, and this gate turns at 3
   from the other side. The twin's pair is counted the way the previews count it,
   with the ~ combinator - no checked die may be followed by a checked die of a
   different face.

   AND THE ARMING IS VISIBLE (SUR-71). These same selectors fill --rim, so the
   whole legal SET reads at a glance instead of one man at a time under the
   pointer, and the rim can never disagree with the press - it is the same rule.
   Deliberately faint: about a third of the aim rim below, which still lifts
   brighter on top of it. The figure and the plate's parts arm and light together
   through one :is(), which is what the old pair of lines per verb said twice.

   AND THE DOCK OBEYS THE SAME SENTENCE (SUR-96, KB-148). On a narrow screen the
   reading leaves the lane for the muster, and a part chip down there is the same
   commit button - so it must be inert in exactly the states the plate's chip is
   inert in. Two selector LISTS carry that, not a second copy of the law: the HOST
   is :is(.foe, .mcard) - the card wears the man's own alive/reachable/far2 words,
   emitted once by the server - and the TARGET is `.parts button.part`, which is
   the chip's own row and reads the same in the plate and in the card. Both are
   the same specificity as what they replace ((0,2,0) host, (0,2,1) target), so
   SUR-94/95's one-source-of-truth ranking is untouched, and :is() costs the
   ceiling nothing - it is not :has(). */
.cb-wrap:has(#vb-knife:checked):has(.dcr:checked):not(:has(.dcr.hi:checked)):not(:has(.dcr:checked ~ .dcr:checked):has(.dcr:not(.ric):checked)) :is(.foe, .mcard).alive :is(.figbox.hit, .parts button.part),
.cb-wrap:has(#vb-condemn:checked):has(.dcr:checked):not(:has(.dcr:checked ~ .dcr:checked)) :is(.foe, .mcard).alive :is(.figbox.hit, .parts button.part),
.cb-wrap:has(:is(#vb-strike, #vb-flay):checked):has(.dcr:checked):not(:has(.dcr:checked ~ .dcr:checked)) :is(.foe, .mcard).alive.reachable:not(.far2) :is(.figbox.hit, .parts button.part),
.cb-wrap:has(:is(#vb-strike, #vb-flay):checked):has(.dcr.deep:checked):not(:has(.dcr:checked ~ .dcr:checked)) :is(.foe, .mcard).alive.far2 :is(.figbox.hit, .parts button.part),
.cb-wrap:has(#vb-twin:checked):has(.dcr:checked ~ .dcr:checked):not(:has(.dcr:checked ~ .dcr:checked ~ .dcr:checked)):not(:has(.dcr.f1:checked ~ .dcr:not(.f1):checked)):not(:has(.dcr.f2:checked ~ .dcr:not(.f2):checked)):not(:has(.dcr.f3:checked ~ .dcr:not(.f3):checked)):not(:has(.dcr.f4:checked ~ .dcr:not(.f4):checked)):not(:has(.dcr.f5:checked ~ .dcr:not(.f5):checked)):not(:has(.dcr.f6:checked ~ .dcr:not(.f6):checked)) :is(.foe, .mcard).alive.reachable:not(.far2) :is(.figbox.hit, .parts button.part),
.cb-wrap:has(#vb-twin:checked):has(.dcr:checked ~ .dcr:checked):not(:has(.dcr:checked ~ .dcr:checked ~ .dcr:checked)):not(:has(.dcr:not(.deep):checked)):not(:has(.dcr.f3:checked ~ .dcr:not(.f3):checked)):not(:has(.dcr.f4:checked ~ .dcr:not(.f4):checked)):not(:has(.dcr.f5:checked ~ .dcr:not(.f5):checked)):not(:has(.dcr.f6:checked ~ .dcr:not(.f6):checked)) :is(.foe, .mcard).alive.far2 :is(.figbox.hit, .parts button.part){
  pointer-events:auto;cursor:pointer;
  --rim:drop-shadow(0 0 2px rgba(232,90,16,.36)) drop-shadow(0 0 7px rgba(232,90,16,.2))}

/* THE RECKONING ARMS LIKE ANY CUT (SUR-101, KB-114 section 7). It is die-less, so
   it is the one blow above with NO dice gate at all: a held die neither arms it
   nor blocks it, and the resolver reads no selection when it lands. Its law is the
   frontmost living rank and nothing else - `alive.reachable:not(.far2)` is exactly
   target_ordinal == 1, the one rank execute() takes a die-less blow at.
   THE MAN, NOT HIS PARTS. Gut ignores the part windows entirely, so arming a chip
   would promise a window that does not apply; the target is the FIGURE alone. That
   also settles the host: only the lane draws men, so there is nothing on a muster
   card for this rule to arm and the card is not named. The dock keeps the reading;
   the board keeps the blow. */
.cb-wrap:has(#vb-gut:checked) .foe.alive.reachable:not(.far2) .figbox.hit{
  pointer-events:auto;cursor:pointer;
  --rim:drop-shadow(0 0 2px rgba(232,90,16,.36)) drop-shadow(0 0 7px rgba(232,90,16,.2))}

/* WHAT THE BLOW WOULD TAKE. Every reachable selection's figure is rendered and
   hidden; the checked controls reveal exactly one. `revert` hands each element
   its own display back - the plate figure is a block, a part's is inline.

   THE DIE AND THE VERB ARE ONE READ (SUR-82). The hand's checkboxes and the verb
   radios are siblings on purpose, dice first (see dice-fight.html): so a checked
   die AND a checked verb is one relation, `#pkN:checked ~ #vb-X:checked`, and not
   two questions asked separately. Same answer, same specificity ((2,2,0) either
   way), one :has() instead of two - which is what paid for the Flay's own six
   lines without the ceiling moving. The one-die verbs share the ladder; a verb
   may NOT be folded in with :is(), because arming strike would then reveal the
   flay number over the same man, and two figures over one head is a lie. */
.pv{display:none}
.cb-wrap:has(#pk0:checked ~ #vb-strike:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-strike.sel-s0,
.cb-wrap:has(#pk0:checked ~ #vb-flay:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-flay.sel-s0,
.cb-wrap:has(#pk0:checked ~ #vb-knife:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-knife.sel-s0,
.cb-wrap:has(#pk1:checked ~ #vb-strike:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-strike.sel-s1,
.cb-wrap:has(#pk1:checked ~ #vb-flay:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-flay.sel-s1,
.cb-wrap:has(#pk1:checked ~ #vb-knife:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-knife.sel-s1,
.cb-wrap:has(#pk2:checked ~ #vb-strike:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-strike.sel-s2,
.cb-wrap:has(#pk2:checked ~ #vb-flay:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-flay.sel-s2,
.cb-wrap:has(#pk2:checked ~ #vb-knife:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-knife.sel-s2,
.cb-wrap:has(#pk3:checked ~ #vb-strike:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-strike.sel-s3,
.cb-wrap:has(#pk3:checked ~ #vb-flay:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-flay.sel-s3,
.cb-wrap:has(#pk3:checked ~ #vb-knife:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-knife.sel-s3,
.cb-wrap:has(#pk4:checked ~ #vb-strike:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-strike.sel-s4,
.cb-wrap:has(#pk4:checked ~ #vb-flay:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-flay.sel-s4,
.cb-wrap:has(#pk4:checked ~ #vb-knife:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-knife.sel-s4,
.cb-wrap:has(#pk5:checked ~ #vb-strike:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-strike.sel-s5,
.cb-wrap:has(#pk5:checked ~ #vb-flay:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-flay.sel-s5,
.cb-wrap:has(#pk5:checked ~ #vb-knife:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-knife.sel-s5{display:revert}
/* v5 kit cuts join the one-die ladder (owner-signed §11 #2, TASK-45): whet, feint, condemn — each
   its own selector, never :is() (the law above). */
.cb-wrap:has(#pk0:checked ~ #vb-whet:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-whet.sel-s0,
.cb-wrap:has(#pk0:checked ~ #vb-feint:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-feint.sel-s0,
.cb-wrap:has(#pk0:checked ~ #vb-condemn:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-condemn.sel-s0,
.cb-wrap:has(#pk1:checked ~ #vb-whet:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-whet.sel-s1,
.cb-wrap:has(#pk1:checked ~ #vb-feint:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-feint.sel-s1,
.cb-wrap:has(#pk1:checked ~ #vb-condemn:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-condemn.sel-s1,
.cb-wrap:has(#pk2:checked ~ #vb-whet:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-whet.sel-s2,
.cb-wrap:has(#pk2:checked ~ #vb-feint:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-feint.sel-s2,
.cb-wrap:has(#pk2:checked ~ #vb-condemn:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-condemn.sel-s2,
.cb-wrap:has(#pk3:checked ~ #vb-whet:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-whet.sel-s3,
.cb-wrap:has(#pk3:checked ~ #vb-feint:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-feint.sel-s3,
.cb-wrap:has(#pk3:checked ~ #vb-condemn:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-condemn.sel-s3,
.cb-wrap:has(#pk4:checked ~ #vb-whet:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-whet.sel-s4,
.cb-wrap:has(#pk4:checked ~ #vb-feint:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-feint.sel-s4,
.cb-wrap:has(#pk4:checked ~ #vb-condemn:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-condemn.sel-s4,
.cb-wrap:has(#pk5:checked ~ #vb-whet:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-whet.sel-s5,
.cb-wrap:has(#pk5:checked ~ #vb-feint:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-feint.sel-s5,
.cb-wrap:has(#pk5:checked ~ #vb-condemn:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-condemn.sel-s5{display:revert}
/* heave + payback close the one-die ladder (owner-signed 2026-09-05, TASK-45 — the FOURTH
   sanctioned edit of the frozen sheet): damage cuts like strike, each its OWN selector, never :is(). */
.cb-wrap:has(#pk0:checked ~ #vb-heave:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-heave.sel-s0,
.cb-wrap:has(#pk0:checked ~ #vb-payback:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-payback.sel-s0,
.cb-wrap:has(#pk1:checked ~ #vb-heave:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-heave.sel-s1,
.cb-wrap:has(#pk1:checked ~ #vb-payback:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-payback.sel-s1,
.cb-wrap:has(#pk2:checked ~ #vb-heave:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-heave.sel-s2,
.cb-wrap:has(#pk2:checked ~ #vb-payback:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-payback.sel-s2,
.cb-wrap:has(#pk3:checked ~ #vb-heave:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-heave.sel-s3,
.cb-wrap:has(#pk3:checked ~ #vb-payback:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-payback.sel-s3,
.cb-wrap:has(#pk4:checked ~ #vb-heave:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-heave.sel-s4,
.cb-wrap:has(#pk4:checked ~ #vb-payback:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-payback.sel-s4,
.cb-wrap:has(#pk5:checked ~ #vb-heave:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-heave.sel-s5,
.cb-wrap:has(#pk5:checked ~ #vb-payback:checked):not(:has(.dcr:checked ~ .dcr:checked)) .pv-payback.sel-s5{display:revert}
/* Twin: a checked pair of one face and nothing else checked. */
.cb-wrap:has(#vb-twin:checked):has(.dcr.f1:checked ~ .dcr.f1:checked):not(:has(.dcr:checked ~ .dcr:checked ~ .dcr:checked)) .pv-twin.sel-f1,
.cb-wrap:has(#vb-twin:checked):has(.dcr.f2:checked ~ .dcr.f2:checked):not(:has(.dcr:checked ~ .dcr:checked ~ .dcr:checked)) .pv-twin.sel-f2,
.cb-wrap:has(#vb-twin:checked):has(.dcr.f3:checked ~ .dcr.f3:checked):not(:has(.dcr:checked ~ .dcr:checked ~ .dcr:checked)) .pv-twin.sel-f3,
.cb-wrap:has(#vb-twin:checked):has(.dcr.f4:checked ~ .dcr.f4:checked):not(:has(.dcr:checked ~ .dcr:checked ~ .dcr:checked)) .pv-twin.sel-f4,
.cb-wrap:has(#vb-twin:checked):has(.dcr.f5:checked ~ .dcr.f5:checked):not(:has(.dcr:checked ~ .dcr:checked ~ .dcr:checked)) .pv-twin.sel-f5,
.cb-wrap:has(#vb-twin:checked):has(.dcr.f6:checked ~ .dcr.f6:checked):not(:has(.dcr:checked ~ .dcr:checked ~ .dcr:checked)) .pv-twin.sel-f6{display:revert}
/* Gut: no die, so no sel- variant and no dice gate - one figure a legal man, shown
   the moment the verb is armed. The server renders it only where the blow would
   land, so an empty bank shows nothing rather than a "-0". */
.cb-wrap:has(#vb-gut:checked) .pv-gut{display:revert}

/* THE TELL LIGHTS the moment a die of its face is checked. */
.cb-wrap:has(.dcr.f1:checked) .tell.t1,
.cb-wrap:has(.dcr.f2:checked) .tell.t2,
.cb-wrap:has(.dcr.f3:checked) .tell.t3,
.cb-wrap:has(.dcr.f4:checked) .tell.t4,
.cb-wrap:has(.dcr.f5:checked) .tell.t5,
.cb-wrap:has(.dcr.f6:checked) .tell.t6{border-color:#F0EAE0;background:#3A2C10;
  color:#F0EAE0;box-shadow:0 0 22px rgba(232,200,122,.78)}

/* THE ARC follows the checked ricochet dice: the hovered man picks the .aN
   layer, the count of checked low ricochet dice picks the .bK variant. A high
   die in the selection kills the preview - the knife would refuse it.
   ONE LINE A MAN, NOT TWO (SUR-101). Every aN x bK variant used to be written
   twice - once for :hover, once for :focus-visible - and the pair differed by
   that one word alone. Folded into `.hit:is(:hover, :focus-visible)`, which is
   the same set with the same specificity (:is() takes its most specific
   argument, and these two are equal), the matrix halves: 48 lines to 24, 216
   :has() to 108. That is what paid for the finisher joining the bar. A sheet
   that already requires :has() support requires :is() support. */
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):not(:has(.dcr.ric.lo:checked)) .lane:has(.fx0 .hit:is(:hover, :focus-visible)) .a0.b0,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):not(:has(.dcr.ric.lo:checked)) .lane:has(.fx1 .hit:is(:hover, :focus-visible)) .a1.b0,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):not(:has(.dcr.ric.lo:checked)) .lane:has(.fx2 .hit:is(:hover, :focus-visible)) .a2.b0,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):not(:has(.dcr.ric.lo:checked)) .lane:has(.fx3 .hit:is(:hover, :focus-visible)) .a3.b0,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):not(:has(.dcr.ric.lo:checked)) .lane:has(.fx4 .hit:is(:hover, :focus-visible)) .a4.b0,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):not(:has(.dcr.ric.lo:checked)) .lane:has(.fx5 .hit:is(:hover, :focus-visible)) .a5.b0{opacity:1}
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):has(.dcr.ric.lo:checked):not(:has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked)) .lane:has(.fx0 .hit:is(:hover, :focus-visible)) .a0.b1,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):has(.dcr.ric.lo:checked):not(:has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked)) .lane:has(.fx1 .hit:is(:hover, :focus-visible)) .a1.b1,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):has(.dcr.ric.lo:checked):not(:has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked)) .lane:has(.fx2 .hit:is(:hover, :focus-visible)) .a2.b1,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):has(.dcr.ric.lo:checked):not(:has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked)) .lane:has(.fx3 .hit:is(:hover, :focus-visible)) .a3.b1,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):has(.dcr.ric.lo:checked):not(:has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked)) .lane:has(.fx4 .hit:is(:hover, :focus-visible)) .a4.b1,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):has(.dcr.ric.lo:checked):not(:has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked)) .lane:has(.fx5 .hit:is(:hover, :focus-visible)) .a5.b1{opacity:1}
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked):not(:has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked ~ .dcr.ric.lo:checked)) .lane:has(.fx0 .hit:is(:hover, :focus-visible)) .a0.b2,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked):not(:has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked ~ .dcr.ric.lo:checked)) .lane:has(.fx1 .hit:is(:hover, :focus-visible)) .a1.b2,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked):not(:has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked ~ .dcr.ric.lo:checked)) .lane:has(.fx2 .hit:is(:hover, :focus-visible)) .a2.b2,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked):not(:has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked ~ .dcr.ric.lo:checked)) .lane:has(.fx3 .hit:is(:hover, :focus-visible)) .a3.b2,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked):not(:has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked ~ .dcr.ric.lo:checked)) .lane:has(.fx4 .hit:is(:hover, :focus-visible)) .a4.b2,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked):not(:has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked ~ .dcr.ric.lo:checked)) .lane:has(.fx5 .hit:is(:hover, :focus-visible)) .a5.b2{opacity:1}
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked ~ .dcr.ric.lo:checked) .lane:has(.fx0 .hit:is(:hover, :focus-visible)) .a0.b3,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked ~ .dcr.ric.lo:checked) .lane:has(.fx1 .hit:is(:hover, :focus-visible)) .a1.b3,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked ~ .dcr.ric.lo:checked) .lane:has(.fx2 .hit:is(:hover, :focus-visible)) .a2.b3,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked ~ .dcr.ric.lo:checked) .lane:has(.fx3 .hit:is(:hover, :focus-visible)) .a3.b3,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked ~ .dcr.ric.lo:checked) .lane:has(.fx4 .hit:is(:hover, :focus-visible)) .a4.b3,
.cb-wrap:has(#vb-knife:checked):not(:has(.dcr.hi:checked)):has(.dcr.ric.lo:checked ~ .dcr.ric.lo:checked ~ .dcr.ric.lo:checked) .lane:has(.fx5 .hit:is(:hover, :focus-visible)) .a5.b3{opacity:1}

/* The resolver's no, in blood - never a silent re-render. It shakes its head once
   (SUR-47): a refusal renders a fresh .co-err span, so this entrance plays exactly
   once, and only on a refusal (no other state carries the span). */
.cb-callout .co-err{color:#C46A6A;display:inline-block}
@media (prefers-reduced-motion:no-preference){
  .cb-callout .co-err{animation:coErrNo .26s ease-out both}
}
@keyframes coErrNo{
  0%{transform:translateX(0);filter:brightness(1)}
  16%{transform:translateX(-3px);filter:brightness(1.4)}
  42%{transform:translateX(3px)}
  66%{transform:translateX(-2px)}
  86%{transform:translateX(1px)}
  100%{transform:translateX(0);filter:brightness(1)}
}

/* THE CALLOUT reads the selection: pick a die → choose an action → choose a
   target. THE DIE COMES FIRST and the callout now says so (SUR-61): a landed
   blow sends its verb home still armed, so "a verb and no die" is the ordinary
   state between two strikes, and the old middle rule read it as ready to aim -
   it asked for a target while the arming gate held every man dark. Same three
   rules, same five :has(): the die is the gate on both of the later two. */
.cb-callout .co{display:none}
.cb-wrap:not(:has(.dcr:checked)) .co-pick{display:inline}
.cb-wrap:has(.dcr:checked):not(:has(.vbr:checked)) .co-act{display:inline}
.cb-wrap:has(.dcr:checked):has(.vbr:checked) .co-target{display:inline}

/* THE NUDGE PAIR exists for the die you are holding - each die's ± is rendered
   and only the checked die's pair shows. No selection, no nudge row. */
.console .cb-row .gnudge .cb-rbtn{display:none}
.cb-wrap:not(:has(.dcr:checked)) .gnudge{display:none}
.cb-wrap:has(#pk0:checked) .gnudge .ns0,
.cb-wrap:has(#pk1:checked) .gnudge .ns1,
.cb-wrap:has(#pk2:checked) .gnudge .ns2,
.cb-wrap:has(#pk3:checked) .gnudge .ns3,
.cb-wrap:has(#pk4:checked) .gnudge .ns4,
.cb-wrap:has(#pk5:checked) .gnudge .ns5{display:flex}
/* ONE DIE AT A TIME (owner 2026-08-14): a nudge tunes a SINGLE die, so the pair
   shows only while exactly one die is held - hold two or more and the row would
   pile up arrow after arrow, one pair per die, saying nothing the eye can use.
   Pick the one die you mean to tune, and its + and - appear alone. */
.cb-wrap:has(.dcr:checked ~ .dcr:checked) .gnudge{display:none}

/* THE LOG'S FOUR VOICES (DD-15 package 3). It was one grey for everything: a
   round header, a blow of yours and a blow of theirs read identically, so the
   fold was a wall of text rather than a record you can scan. */
.console .cblog summary .lgl{color:var(--ink-faint,#5C564E)}
.console .cblog .lines .ln{padding:1px 0}
.console .cblog .lines .ln.round{color:var(--ink-faint,#5C564E);letter-spacing:.14em;
                                 text-transform:uppercase;font-size:.62rem;margin-top:6px}
.console .cblog .lines .ln.you{color:#D8925E}
.console .cblog .lines .ln.them{color:#A86A6A}
.console .cblog .lines .ln.end{color:var(--ink-emph,#F0EAE0)}
.console .cblog .lines .ln.note{color:var(--ink-faint,#5C564E)}

/* ---- A DIE IS DRAWN, NOT TYPESET (DD-15 package 4) --------------------------
   Every die on this screen was a Unicode character (U+2680-2685) set in the
   page's serif stack - a font with no such glyph, so each face fell back to
   whatever symbol font the machine happened to own, at whatever size and
   baseline it happened to choose, inside a box that was itself drawn as a die.
   A die inside a die, and no two players seeing the same one.

   Nine cells, and the face lights the ones it needs. One grammar for the hand,
   the tell over a man's head, the reaction buttons and the primer's own frames,
   so the manual and the fight can never drift apart. */
.pips{display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(3,1fr);
      width:100%;height:100%;padding:8px;box-sizing:border-box;gap:0;place-items:center}
.pips i{width:7px;height:7px;border-radius:50%;background:#E6DBC8;opacity:0;display:block}
.pips.sm{padding:3px}
.pips.sm i{width:4px;height:4px}

/* The six faces. Cells run left to right, top to bottom. */
.pips.f1 i:nth-child(5),
.pips.f2 i:nth-child(1),.pips.f2 i:nth-child(9),
.pips.f3 i:nth-child(1),.pips.f3 i:nth-child(5),.pips.f3 i:nth-child(9),
.pips.f4 i:nth-child(1),.pips.f4 i:nth-child(3),.pips.f4 i:nth-child(7),.pips.f4 i:nth-child(9),
.pips.f5 i:nth-child(1),.pips.f5 i:nth-child(3),.pips.f5 i:nth-child(5),
.pips.f5 i:nth-child(7),.pips.f5 i:nth-child(9),
.pips.f6 i:nth-child(1),.pips.f6 i:nth-child(3),.pips.f6 i:nth-child(4),
.pips.f6 i:nth-child(6),.pips.f6 i:nth-child(7),.pips.f6 i:nth-child(9){opacity:1}

/* The hand, at the reference's size. */
.console .cb-row .cb-die{width:46px;height:46px;border-radius:7px;padding:0}
.console .cb-row .cb-die .pips i{background:#E6DBC8}
.console .cb-row .cb-die.held .pips i{background:#F0EAE0}
.console .cb-row .cb-die.spent .pips i{background:#8F877C}

/* The tell over a man's head: a die, and the one thing the screen asks you to
   read. It never drew the eye at all. */
.lane .plate .tell .pips{padding:4px}
.lane .plate .tell .pips i{width:4px;height:4px;background:#F0EAE0}
.lane .plate .tell .unknown{font:600 .72rem/1 var(--mono,monospace);color:#8F877C}
@media (prefers-reduced-motion: no-preference) {
  .lane .plate .tell{animation:tellglow 2.8s ease-in-out infinite alternate}
  @keyframes tellglow {
    from{box-shadow:0 0 0 rgba(232,200,122,0)}
    to{box-shadow:0 0 16px rgba(232,200,122,.55)}
  }
}

/* The primer's frames are the same object at the same size. padding:0 is
   load-bearing: the site chrome owns a `.die` STAT CARD (suresne.css:142,
   padding 11px 12px) that bleeds in here and starved the pips' 100% grid
   down to an 8px window - the clustered-dots bug. */
.cb-mechs .pdemo .die{position:relative;display:block;width:34px;height:34px;padding:0;
                      border:1px solid var(--edge);border-radius:5px;background:#0E0E10}
.cb-mechs .pdemo .die .pips{padding:5px}
.cb-mechs .pdemo .die .pips i{width:5px;height:5px}
.cb-mechs .pdemo .die.held{border-color:#E85A10;box-shadow:0 0 14px rgba(232,90,16,.35)}
.cb-mechs .pdemo .die.held .pips i{background:#F0EAE0}
.cb-mechs .pdemo .die u{position:absolute;left:50%;transform:translateX(-50%);bottom:-13px;
                        text-decoration:none;font:.62rem/1 var(--mono,monospace);
                        letter-spacing:.1em;text-transform:uppercase;color:#E85A10}

/* The reaction buttons carry a small one. */
.cb-rbtn .pips{width:18px;height:18px;display:inline-grid;vertical-align:middle}

/* A DIE IN A THIEF'S HAND. It rendered as a faceless, dimmed box that the hand's
   own label counted as a die you had spent - the one moment a mechanic reaches
   in and takes something was the one moment the screen said nothing. */
.console .cb-row .cb-die.palmed{opacity:.5;border-style:dashed;border-color:#6E1C23;
                                background:#111112}
.console .cb-row .cb-die.palmed .pips i{opacity:0}
.console .cb-row .cb-die.palmed::after{content:"✋";position:absolute;inset:0;
                                       display:flex;align-items:center;justify-content:center;
                                       font-size:.9rem;opacity:.75}
.console .cb-row .glbl b.taken{color:#C46A6A}

/* ---- the strip keeps every control reachable at any width (KB-060) ---------
   Measured at the design width: the three groups want 1056px and the row offers
   1057. One pixel of slack, so every narrower window pushed the verbs behind an
   invisible scroll and painted them over the belt. The row grows in height now
   instead of hiding what you can press.

   A group's controls wrap inside it at every width. At 1280 there is room, so
   nothing wraps and the bar is unchanged; it is there for the fight that carries
   a sixth verb or a longer name, which would otherwise overflow at the design
   width too. */
.console .cb-row{flex-wrap:wrap}
.console .cb-row .gbody{flex-wrap:wrap;row-gap:6px}

/* Below the width the bar actually needs, the three groups stack full-width with
   their own labels and a bottom hairline. Anything cleverer squeezes one group
   to feed another: giving the belt a grow factor took 195px off the verbs and
   wrapped them at 1280. */
@media (max-width:1279px){
  .console .cb-row .grp,
  .console .cb-row .grp.hand,
  .console .cb-row .grp.verbs{flex:1 0 100%;border-right:0;
                              border-bottom:1px solid var(--edge)}
  .console .cb-row .grp.last{border-bottom:0}
}

/* The queue of men wraps rather than scrolling out of sight - a chip nobody can
   see is a swing nobody can read. */
.lane-head .qchips{flex-wrap:wrap;overflow-x:visible}
/* THE STRIP FOLDS INSTEAD OF STACKING (SUR-72). The chips box was flex:1 off a
   zero basis while the sum claimed a whole row, so on a phone the box was dealt
   NO width at all: every chip broke to a line of its own behind the sum, and a
   lethal sum - which paints an opaque ground - buried all six. The box steps out
   of the way (display:contents) and its chips join the header's own wrap flow,
   so the phase word heads the first row and the sum follows the last chip. The
   basis is a share of the strip, not a pixel count: three to a row at every
   width this block owns, which holds a six-man wave in two rows. No min-width
   here - a chip's automatic minimum is its own min-content, and that is what
   keeps a long name inside its box instead of over its neighbour. */
@media (max-width:620px){
  .lane-head .qchips{display:contents}
  .lane-head .qchip{flex:1 1 22%;padding:4px 8px;white-space:normal}
  .lane-head .qchip b,
  .lane-head .qchip small{font-size:.62rem}
  .lane-head .qsum{padding:4px 8px}
  .wchip{padding:4px 8px}
}

/* The callout and the log fold share one line only while both fit. The fold was
   flex:none with a 56ch newest-line, so at 375px it crushed "pick a die - or
   keep it for your guard" to 29px and stacked it one word per line. */
.console .cblog{flex:0 1 auto;min-width:0}
@media (max-width:820px){
  .console .cb-head{flex-wrap:wrap;gap:6px}
  .console .cb-callout,
  .console .cblog{flex:1 1 100%}
}

/* THE DOOR at a narrow width. Four cards sharing one row equally is the whole
   point of the opener, but at 375px equal shares are 78px each and the terms
   under each name become a column of single words. Two up, then one. */
@media (max-width:720px){
  .cb-openers{flex-wrap:wrap}
  .cb-openers > .op,
  .cb-openers form{flex:1 1 calc(50% - 4px)}
}
@media (max-width:430px){
  .cb-openers > .op,
  .cb-openers form{flex:1 1 100%}
}

/* The break itself now belongs to the whole folded strip (SUR-72): a chip sized
   by its share of the row must be allowed to break, or its min-content holds the
   old full-name width and the share is overruled. What is left here is the last
   tightening a 320px strip can still take. */
@media (max-width:400px){
  .lane-head .qchip{gap:6px}
}

/* ---- THE MUSTER (SUR-96, KB-148) -------------------------------------------
   The plate is drawn in PIXELS and the lane is drawn in RATIOS, so the narrower
   the screen the less room a man has under his own feet. Measured on the live
   page: the plate's own reading - tell, name, bar, blood, badges - stands 45 to
   56px tall, and the room under the deepest man's feet is 17.8% of the lane's
   height. In the hall, the six-man room, that reading leaves the lane below
   772px of viewport; in a chamber below 980px; in a closet it clips at every
   width the page can be. The parts row never fits at all on a touch screen,
   where SUR-66 keeps it always open. Nothing can climb out either: the lane
   clips by law (KB-076) and it is a container, so even a fixed descendant is
   trapped (the same wall KB-145 hit with the tooltips).
   So under the breakpoint the reading LEAVES THE ROOM. The lane keeps the men
   and their light; the muster carries the words, one card a man, in the foes'
   own order. 820px is the sheet's own nearest breakpoint at or above the
   measured failure, and the fight screen already admits it is narrow there -
   the callout and the log fold onto separate lines at the same width. */
.muster{display:none}
.muster .mrow{display:flex;align-items:center;gap:6px;flex-wrap:wrap;min-width:0}
.muster .mcard{border:1px solid var(--edge);border-left:3px solid #6E1C23;
               background:#101011;padding:6px 8px}
/* The man the finger aims at, or the man a queue chip points out, opens whole. */
.muster .mcard.aimed{border-left-color:#E85A10;
                     box-shadow:0 0 0 1px rgba(232,90,16,.45) inset}
.muster .mcard.marked{border-left-color:#B04A42}
.muster .tell{flex:none;width:22px;height:22px;border:1px solid #E8C87A;background:#241B0C;
              color:#E8C87A;border-radius:5px;display:flex;align-items:center;
              justify-content:center;font-size:.82rem;line-height:1}
.muster .tell .pips{padding:4px}
.muster .tell .pips i{width:4px;height:4px;background:#F0EAE0}
.muster .tell .unknown{font:600 .72rem/1 var(--mono,monospace);color:#8F877C}
/* The card carries the same two-face badge, at the card's own size (SUR-84): a
   phone that could not read the seam would be reading a different fight. */
.muster .tfaces{flex:none;display:flex;flex-direction:column;align-items:center;gap:1px}
.muster .seam{flex:none;width:22px;height:18px;border:1px solid rgba(176,74,66,.9);
              background:#1A0D0D;border-radius:4px;display:flex;align-items:center;
              justify-content:center;font-size:.72rem;line-height:1}
.muster .seam .pips{padding:2px}
.muster .seam .pips i{width:4px;height:4px;background:#E0938A}
.muster .seam .unknown{font:600 .62rem/1 var(--mono,monospace);color:#8F877C}
/* THE NAME TAKES WHAT IS LEFT and ends in an ellipsis rather than pushing the
   blood off the line: six men have to be comparable at a glance, so every card
   puts its bar and its number in the same place. */
.muster .nm{flex:1 1 auto;min-width:4ch;overflow:hidden;text-overflow:ellipsis;
            white-space:nowrap;font:.72rem/1.3 var(--mono,monospace);
            color:var(--ink-body,#D3CCC0)}
.muster .nm.gold{color:#C9A24A}
.muster .bar{position:relative;flex:none;width:58px;height:6px;background:rgba(0,0,0,.6);
             border:1px solid rgba(0,0,0,.8)}
.muster .bar i{display:block;height:100%;
               background:linear-gradient(90deg,#6E1C23,#4A1015)}
.muster .num{flex:none;font:.72rem/1.3 var(--mono,monospace);
             color:var(--ink-label,#8F877C)}
.muster .badge{flex:none;font-size:.62rem;letter-spacing:.06em;text-transform:uppercase;
               color:#C9A24A}
.muster .badge.sentb{color:#E25822}
.muster .badge.reachb{color:#8fa6b8}
.muster .badge.woundb{color:#D05A4E}
.muster .badge.anguishb{color:#A87BE0}
.muster .badge.heldb{color:#9a938a}
.muster .badge.moreb{color:var(--ink-label,#8F877C)}
/* WITH NO SCRIPT EVERY CARD STANDS OPEN: a longer page, degraded, never wrong.
   With script the dock folds to one line a man and the aimed or marked man opens.
   THE GATE IS ON THE ROOT, NOT ON THE LANE. A sibling gate off `.lane.js-gate`
   holds for exactly one render: fight-swap.js replaces the whole .cb-wrap, and
   script in inserted markup never runs, so the lane comes back bare - measured,
   the class list after one action is `lane rm-hall st-street`, both js-gate and
   ready gone. The root is the one node the swap never touches, which is why the
   parallax numbers already live there. Same word, no new one; no :has(). */
.muster .mexp{display:flex;align-items:center;flex-wrap:wrap;gap:5px;margin-top:5px}
:root.js-gate .muster .mexp{display:none}
:root.js-gate .muster .mcard.aimed .mexp,
:root.js-gate .muster .mcard.marked .mexp{display:flex}
.muster .hitnum{font:.72rem/1.3 var(--mono,monospace);color:#E85A10}
.muster .hitnum .crit,
.muster .part .pd .crit{font-style:normal;color:#E8C87A;font-size:.62rem;
                        letter-spacing:.04em;margin-left:3px}
.muster .parts{display:flex;flex-wrap:wrap;gap:5px}
/* A CHIP DOWN HERE IS PRESSED WITH A THUMB, so it is taller than the plate's -
   the lane's 17px row is a pointer's target, not a finger's. */
.muster .part{display:flex;align-items:center;gap:5px;min-height:28px;
              background:rgba(10,9,8,.9);border:1px solid rgba(110,28,35,.55);
              padding:2px 8px;cursor:pointer;color:var(--ink-label,#8F877C);
              font:.62rem/1.5 var(--mono,monospace);white-space:nowrap}
.muster .part:hover,
.muster .part.aimed,
.muster .part:focus-visible{border-color:#E85A10;color:var(--ink-body,#D3CCC0)}
.muster .part.aimed{box-shadow:0 0 0 1px #E85A10 inset}
.muster .part.gone{text-decoration:line-through;opacity:.4;cursor:default;
                   border-color:var(--edge)}
.muster .part .pn{overflow:hidden;text-overflow:ellipsis;max-width:14ch}
/* The window takes the pointer back for itself for the same reason the plate's
   does (SUR-66): the chip is inert until the blow is legal, and an inert button
   hands no hover to its children, so the sentence behind the number could be
   read by nobody at rest. */
.muster .part .pw{color:#C9A24A;font-size:.72rem;line-height:1.3;pointer-events:auto}
.muster .part .pd{display:none;color:#E85A10;font-size:.72rem;line-height:1.3}

@media (max-width:820px){
  .muster{display:flex;flex-direction:column;gap:5px;margin:0 0 12px}
  /* The lane's own overflow law is untouched - the plate simply is not there to
     be cut. The HERO keeps his: he is one three-row block on a lane of his own. */
  .lane .unit.foe .plate{display:none}
}

/* ---- THE STREET (KB-061) ----------------------------------------------------
   The same skeleton as the undercroft: the seam is where it always was, and the
   two doors still stand on it, because every floor mark and the throw's arc are
   measured from that line. Only what the room is MADE OF changes. A city reads
   as a city from silhouette and one warm light; detail at this size is noise. */
.lane .room .fronts path{fill:#050609;stroke:none}
/* Party walls: without an edge between them six fronts read as one dark mass. */
.lane .room .party path{fill:none;stroke:rgba(198,188,172,.07);stroke-width:1;
                        vector-effect:non-scaling-stroke}
.lane .room .roofline path{fill:none;stroke:rgba(198,188,172,.11);stroke-width:1;
                           vector-effect:non-scaling-stroke}
/* Most of the city is asleep. A shuttered pane is cut from its own front, a shade
   lighter; the two that are awake are the only warm thing above the seam. */
.lane .room .panes path{fill:rgba(198,188,172,.05);stroke:none}
.lane .room .panes.lit path{fill:rgba(232,166,74,.20)}
.lane .room .lamps path{fill:none;stroke:rgba(198,188,172,.18);stroke-width:2;
                        vector-effect:non-scaling-stroke}
/* The course the cobbles are laid in - one line, far off, so the street reads as
   laid ACROSS your path rather than running along it. */
.lane .room .courses path{fill:none;stroke:rgba(211,207,199,.03);stroke-width:1;
                          vector-effect:non-scaling-stroke}
/* Out of doors the joint is a kerb, not a wall's shadow: shallower, and cooler. */
.lane.st-street .room .seamline{stroke:rgba(0,0,0,.7)}

/* ---- THE DIE COMES FIRST (KB-062) ------------------------------------------
   You could arm Strike with an empty hand, walk up to a man, press him, and be
   told by the RESOLVER what the screen had known all along: the blow needs a
   die. A refusal is the right answer to a bad post; it is the wrong way to
   teach an order the interface could simply hold.

   EVERY die-spending blow is shut, not a hand-listed three (owner 2026-08-01:
   a verb that needs a die MUST wear this gate). The bar's verbs are the <label>
   .cb-vbtn - Strike, Twin, the knife, Condemn, and whatever calling comes next -
   so ONE class gate holds them all and a new active can never ship ungated.
   Steady, the belt, Break off and END TURN cost no die and are <button>s, not
   labels, so they stay live: a man with an empty hand still needs a way out.
   Gated on the checkbox itself, so it costs no request: the same :has() the
   picks and arms already ride.

   AND A BLOW THAT COSTS NO DIE IS NOT GATED (SUR-101). The Cutthroat's finisher
   is armed and aimed like any cut but spends the BANK, not the hand - so this
   rule, written to stop a die-spending verb shipping ungated, would have dimmed
   it, made it inert, and told the player to pick a die it will never read. The
   server marks such a verb `free` off the verb table's own selection rule
   (sel_min < 0), the same word the free opener wears, so the exemption is a
   reading of the law rather than a name on a list here. */
.cb-vbtn .vf.need{display:none}
.cb-wrap:not(:has(.hand-state input[name="dice"]:checked)) label.cb-vbtn:not(.free){
  opacity:.4;pointer-events:none;border-color:var(--edge)}
.cb-wrap:not(:has(.hand-state input[name="dice"]:checked)) label.cb-vbtn:not(.free) .vf{display:none}
.cb-wrap:not(:has(.hand-state input[name="dice"]:checked)) label.cb-vbtn:not(.free) .vf.need{display:block}

/* The man is handled where he is armed, not shut afterwards - see the arming
   rules above. A gate placed here loses the specificity fight and does nothing. */

/* ---- THE FIGHT GETS THE WHOLE SCREEN (KB-064) -------------------------------
   Every other page is capped at 1120px because a page is READ, and a long line
   is hard to read. A fight is not read - it is a room you are standing in, and
   at 1920 it was a 288px band with a third of the width sitting empty.

   Measured at 1920x1080 before this: 223px above the lane (brandbar, header,
   page padding, the primer fold, the queue strip) and 126px below it to the
   bottom of the console. The reserve below allows for those, the footer and a
   little air.

   The height cap is what keeps the console on screen. The lane's ratio is the
   ROOM's own, never stretched, so when a window is too short to hold the hall at
   full width the whole fight narrows together rather than the controls being
   pushed off the bottom - the lane and the console always share an edge. */
.wrap.fight-hall{max-width:min(1800px,100%);padding-top:6px}
/* THE RESERVE IS WHAT MUST SHARE THE SCREEN WITH THE ROOM - and that is the
   console, not the whole page. The first cut counted everything: 223px above the
   lane (brandbar, header, page padding, the primer, the queue strip) and 126
   below. But the header does not have to be visible AT THE SAME TIME as the
   controls; it scrolls away, and nobody misses it. Only the room and the console
   have to be in view together.
   Measured: the console is ~126 tall, plus the footer and a little air. With 420
   a five-man hall on an 884-tall window was squeezed to 1030px wide; with 190 the
   same window gives it 1540 and the console still sits above the fold.
   The floor stays: below ~190px of window the subtraction goes negative and the
   whole fight would collapse to nothing. */
.cb-wrap{margin-inline:auto;
         max-width:min(100%, max(320px, calc((100vh - 165px) * 2.5)))}

/* ---- THE CONSOLE IS AN INSTRUMENT, NOT A BANNER (KB-065) --------------------
   Measured against the designer's own strip: it wants 1013px and the reference
   is 1019 wide - the reference IS the console at its content width. Given the
   whole hall it stretched to 1739 and the verbs group, the only one allowed to
   grow, swallowed all 726 spare pixels; the belt ended up an arm's length from
   the last verb with nothing in between.

   max-content is the rule the design was already obeying: as wide as the groups
   need and no wider, centred under the hall. max-width keeps it inside the
   narrow-window regime, where the groups stack full width instead. */
.console .cb-row{width:max-content;max-width:100%;margin-inline:auto}

/* The strip is not its own panel. It sat on #141416 while the block holding the
   callout sits on #101011, so the console read as a lighter card pasted onto the
   screen. Transparent rather than the same value written twice: one background,
   and the two can never drift apart again. */
.console .cb-row{background:transparent}

/* ---- THE VERB ANSWERS THE SELECTION, NOT ONLY THE HAND (KB-066) -------------
   The buttons read what is IN the hand - "no pair in hand" - but never what you
   are actually holding, so Strike stayed lit with two dice picked and the
   resolver refused a blow the screen had just offered. can_use() is the law:
   Strike takes exactly one die, Twin Cut exactly two of a kind, Throw Knife
   nothing over three.

   Counted with the ~ combinator on the hand's own checkboxes, which is what the
   damage previews already do - the machinery was here, the buttons simply were
   not using it. Costs no request; the resolver still re-judges everything. */
.cb-vbtn .vf.only1,
.cb-vbtn .vf.pair2,
.cb-vbtn .vf.lowonly{display:none}

/* Strike, and the Flay beside it: one die, no more. Both take exactly one, so
   both read the same refusal off the same count - a LIST, not a second law. */
.cb-wrap:has(.dcr:checked ~ .dcr:checked) label:is([for=vb-strike], [for=vb-flay]){
  opacity:.4;pointer-events:none;border-color:var(--edge)}
.cb-wrap:has(.dcr:checked ~ .dcr:checked) label:is([for=vb-strike], [for=vb-flay]) .vf{display:none}
.cb-wrap:has(.dcr:checked ~ .dcr:checked) label:is([for=vb-strike], [for=vb-flay]) .vf.only1{display:block}

/* Throw Knife: nothing over three goes across the room. */
.cb-wrap:has(.dcr.hi:checked) label[for=vb-knife]{
  opacity:.4;pointer-events:none;border-color:var(--edge)}
.cb-wrap:has(.dcr.hi:checked) label[for=vb-knife] .vf{display:none}
.cb-wrap:has(.dcr.hi:checked) label[for=vb-knife] .vf.lowonly{display:block}

/* Twin Cut: exactly two, and the same face. Shut for a third die, and shut
   unless two of one face are actually held - six faces, one clause each. */
.cb-wrap:has(.dcr:checked ~ .dcr:checked ~ .dcr:checked) label[for=vb-twin],
.cb-wrap:has(.dcr:checked):not(:has(.f1:checked ~ .f1:checked)):not(:has(.f2:checked ~ .f2:checked)):not(:has(.f3:checked ~ .f3:checked)):not(:has(.f4:checked ~ .f4:checked)):not(:has(.f5:checked ~ .f5:checked)):not(:has(.f6:checked ~ .f6:checked)) label[for=vb-twin]{
  opacity:.4;pointer-events:none;border-color:var(--edge)}
.cb-wrap:has(.dcr:checked ~ .dcr:checked ~ .dcr:checked) label[for=vb-twin] .vf,
.cb-wrap:has(.dcr:checked):not(:has(.f1:checked ~ .f1:checked)):not(:has(.f2:checked ~ .f2:checked)):not(:has(.f3:checked ~ .f3:checked)):not(:has(.f4:checked ~ .f4:checked)):not(:has(.f5:checked ~ .f5:checked)):not(:has(.f6:checked ~ .f6:checked)) label[for=vb-twin] .vf{display:none}
.cb-wrap:has(.dcr:checked ~ .dcr:checked ~ .dcr:checked) label[for=vb-twin] .vf.pair2,
.cb-wrap:has(.dcr:checked):not(:has(.f1:checked ~ .f1:checked)):not(:has(.f2:checked ~ .f2:checked)):not(:has(.f3:checked ~ .f3:checked)):not(:has(.f4:checked ~ .f4:checked)):not(:has(.f5:checked ~ .f5:checked)):not(:has(.f6:checked ~ .f6:checked)) label[for=vb-twin] .vf.pair2{display:block}

/* AND THE MAN STAYS A PICTURE - which is now the ARMING rule's own work, not a
   second family's (SUR-95). These lines used to shut a man again for a selection
   the verb could not spend, and they were written "with two :has() so it
   outweighs the arming rule": it did not. The arming carries an id inside its
   :has() and outweighed them right back, so with two dice on Strike every man
   was pressable and the refusal came from the resolver. The condition lives up
   at the arming now, where it cannot lose a specificity fight, and these rules
   are gone with their 18 :has() - a family that paid rent and did nothing.
   The button dimming above STAYS: it is the reading, not the guarantee. */

/* A second knife only leaves the hand if it can bounce. can_use(): more than one
   die on the throw and every one of them must be a ricochet. The man's own gate
   for this rides the knife's arming line above. */
.cb-vbtn .vf.riconly{display:none}
.cb-wrap:has(.dcr:checked ~ .dcr:checked):has(.dcr:not(.ric):checked) label[for=vb-knife]{
  opacity:.4;pointer-events:none;border-color:var(--edge)}
.cb-wrap:has(.dcr:checked ~ .dcr:checked):has(.dcr:not(.ric):checked) label[for=vb-knife] .vf{display:none}
.cb-wrap:has(.dcr:checked ~ .dcr:checked):has(.dcr:not(.ric):checked) label[for=vb-knife] .vf.only1{display:block}

/* ---- EVERY FIGURE IS A SQUARE (KB-067) --------------------------------------
   The drawn men were one borrowed knight standing in for the whole bestiary: the
   plated, the songstress and the choirmaster were the same picture, which reads
   as finished art that happens to be wrong rather than as a placeholder. A block
   is honest - it claims nothing it has not earned, and it makes the things that
   ARE designed (the tell, the plate, the slab, the arc) the only things drawn.

   The class stays .fig, so depth-brightness, the hover lift, the stop-frame and
   the shadow pool all keep working untouched. The square's side is the figbox's
   own width, so a man keeps his footprint on the slab and every mark measured
   from the room is where it was. */
.lane .unit .figbox .fig{position:absolute;left:50%;bottom:0;
                         transform:translateX(-50%);
                         width:100%;height:auto;aspect-ratio:1;
                         background:#12100E;
                         border:1px solid rgba(198,188,172,.22);
                         box-sizing:border-box}
/* Yours reads warm; theirs read cold. The one distinction a block can carry
   without inventing a character it does not have. */
.lane .unit.you .figbox .fig{background:#1A120C;border-color:rgba(232,90,16,.55)}

/* The belt's thing, the same way. */
.console .cb-row .tabpane .thing .sq{display:inline-block;width:26px;height:26px;
                                     background:#12100E;
                                     border:1px solid rgba(198,188,172,.22);
                                     box-sizing:border-box;vertical-align:middle}

/* ---- THE CALLOUT IS THE FOLD'S HANDLE (KB-070) ------------------------------
   The designer's console has ONE line across the top: a marker, the sentence
   telling you what to do now, and the log's name at the far right. That line IS
   the summary - open it and the record unrolls between it and the strip, which
   steps aside while you read.

   Before this the log was a small fold sitting to the RIGHT of the callout, and
   the strip below was a bordered card inside a bordered panel - two boxes where
   the design has one body. */
.console .cblog{display:block;margin:0;border:0;padding:0;flex:none;max-width:none}
.console summary.cb-head{display:flex;align-items:baseline;gap:14px;margin:0;
                         padding:9px 12px;list-style:none;cursor:pointer}
.console summary.cb-head::-webkit-details-marker{display:none}
.console summary.cb-head::before{content:"\25B8";flex:none;
                                 color:var(--ink-faint,#5C564E);font-size:.7rem}
.console .cblog[open] summary.cb-head::before{content:"\25BE"}
.console summary.cb-head:hover::before{color:var(--ink-label,#8F877C)}
.console summary.cb-head .cb-callout{flex:1 1 auto;min-width:0}
/* The log's name keeps to the far right and never competes with the sentence. */
.console summary.cb-head .lgl{flex:none;margin-left:auto;
                              font:400 .58rem/1 var(--mono,monospace);
                              letter-spacing:.16em;text-transform:uppercase;
                              color:var(--ink-faint,#5C564E)}

/* THE SHUT HANDLE REPORTS WHAT JUST HAPPENED (SUR-68). The right half of the
   summary spent itself on a static label; the record's newest line rides in
   front of it now, in the fold's own voice, so a glance away costs nothing.
   It lives on LEFTOVER width alone - a zero basis, so it never enters the
   sentence's own measurement and can never push the callout into a second
   line; what room is left it fills, up to 40 characters, and ends in an
   ellipsis. On a narrow desk it gives that room back rather than take it. */
.console summary.cb-head .lglast{flex:6 1 0%;min-width:0;max-width:40ch;
                                 overflow:hidden;text-overflow:ellipsis;
                                 white-space:nowrap;
                                 text-transform:none;letter-spacing:.01em;
                                 font:400 .62rem/1.5 var(--mono,monospace);
                                 color:var(--ink-label,#8F877C)}
.console summary.cb-head .lglast.you{color:#D8925E}
.console summary.cb-head .lglast.them{color:#A86A6A}
.console summary.cb-head .lglast.end{color:var(--ink-emph,#F0EAE0)}
.console summary.cb-head .lglast.round,
.console summary.cb-head .lglast.note{color:var(--ink-faint,#5C564E)}
/* The joint between the report and the label, only when there is a report. */
.console summary.cb-head .lglast + .lgl::before{content:"\00B7";margin-right:7px;
                                                letter-spacing:0;
                                                color:var(--ink-faint,#5C564E)}

/* The record itself, and the note that says where the hand went. */
.console .cblog .lines{margin:0;padding:2px 12px 0;max-height:none;overflow:visible}
.console .lognote{padding:10px 12px 12px;font-style:italic;
                  font:italic 400 .68rem/1.5 var(--serif,Georgia,serif);
                  color:var(--ink-faint,#5C564E)}
.console .cblog:not([open]) .lognote{display:none}

/* THE STRIP IS ONE BODY WITH THE CONSOLE. No border of its own and no width of
   its own: it is the console's lower half, not a card parked inside it. */
.console .cb-row{width:auto;max-width:none;margin:0;border:0;
                 border-top:1px solid var(--edge)}

/* ---- THE FOLD OPENS ON A CLOCK (KB-070) -------------------------------------
   <details> has no animation of its own: the record used to appear whole, in one
   frame. ::details-content is the only handle the browser gives on the part that
   opens, and interpolate-size is what makes a height of `auto` a number the
   transition can walk to.

   Scoped to .console rather than :root on purpose - interpolate-size changes how
   EVERY `auto` length behaves under it, and the fight screen is the only place
   asking for it.

   Where it is not supported the fold simply opens the way it always did, which
   is where the screen was before this block existed. */
@media (prefers-reduced-motion: no-preference) {
  .console{interpolate-size:allow-keywords}

  .console .cblog::details-content{block-size:0;overflow:hidden;
                                   transition:block-size .26s ease,
                                              content-visibility .26s allow-discrete}
  .console .cblog[open]::details-content{block-size:auto}

  /* The strip closes on the same clock, so the console has one motion, not two. */
  .console .cb-row,
  .console .cb-openers,
  .console .answers{transition:block-size .26s ease, opacity .18s ease,
                               border-color .26s ease}

  /* The marker turns rather than swapping character. */
  .console summary.cb-head::before{content:"\25B8";display:inline-block;
                                   transition:transform .26s ease, color .2s ease}
  .console .cblog[open] summary.cb-head::before{content:"\25B8";
                                                transform:rotate(90deg)}
}

/* ---- THE CONSOLE WEARS THE DESIGN'S COLOURS (KB-070) ------------------------
   The room and the console are one instrument, so the 12px of page between them
   goes: the strip already shares the console's body, and the console should
   share the room's. */
.lane{margin-bottom:0}

/* THE SENTENCE IS A SENTENCE. Moving the callout inside <summary> made it
   inherit the fold's small-caps and letter-spacing, so the line that speaks to
   you in plain words was shouting them. It is lower case, warm, and only a
   little wider than its letters. */
.console summary.cb-head .cb-callout{text-transform:none;letter-spacing:.01em;
                                     font:400 .78rem/1.5 var(--mono,monospace);
                                     color:#E6DCC8}
.console summary.cb-head .cb-callout b,
.console summary.cb-head .cb-callout .co-emph{color:#E8C87A;font-weight:700}
.console summary.cb-head .co-err{color:#C46A6A;text-transform:none}
/* The ember rule down the left edge - the design's mark that this line is the
   fight speaking, not a label. */
.console summary.cb-head{border-left:2px solid var(--ember-mid,#8a4a12)}
.console .cblog[open] summary.cb-head{border-left-color:#E85A10}

/* The log's name: warm and quiet, never competing with the sentence. */
.console summary.cb-head .lgl{color:#A8894F}
.console summary.cb-head:hover .lgl{color:#8a6f45}

/* ---- THE RECORD'S FOUR VOICES, ON THE DESIGN'S LADDER -----------------------
   The design separates them by LIGHT rather than by hue: a round header sits
   back, what you did comes forward in warm cream, what was done to you sits
   between, and the ending is the brightest thing in the fold. The hue is kept
   only far enough to tell your blow from theirs at a glance. */
.console .cblog .lines{font:400 .74rem/1.9 var(--mono,monospace)}
.console .cblog .lines .ln{padding:1px 0}
.console .cblog .lines .ln.round{color:var(--ink-faint,#857E74);letter-spacing:.14em}
.console .cblog .lines .ln.you{color:#E0D6C6}
.console .cblog .lines .ln.them{color:#9C8878}
.console .cblog .lines .ln.end{color:#F0EAE0;font-weight:700}
.console .cblog .lines .ln.note{color:#5C564E}
/* The last thing that happened is the one you opened the fold for. */
.console .cblog .lines .ln:last-child{color:#F0EAE0;font-weight:700}

/* ---- END TURN, BREAK OFF, AND THE BELT (KB-070) -----------------------------
   END TURN is the one FILLED thing on the bar. Everything else is an outline
   waiting to be chosen; ending the turn is the choice you always have, so the
   design gives it parchment and lets the dark type sit on it. Slightly set off
   from the verbs, because it is not one of them. */
/* Read straight off the designer's inspector, value for value: serif rather than
   the console's mono, 0.58rem, Medium, uppercase, NO tracking, #0A0A0B on
   #F0EAE0, square, no border.

   THE LEADING IS UNITLESS, NOT THE 9.28px THE INSPECTOR PRINTS. Both are the
   same thing - 9.28 is 0.58rem against a 16px root - but this client runs its
   root at +14% (suresne.css: html,body{font-size:18.24px}, owner 2026-07-20), so
   an absolute length here would quietly opt this one button out of that rule and
   stop it growing with everything around it. The tooltip in the inspector says
   as much: unitless multiplies the size.
   Height and padding are left alone - they are what keeps it level with the
   verbs beside it, and the inspector did not speak to them. */
.console .cb-row .cb-end{
  font:500 .62rem/1 var(--serif,'EB Garamond',Georgia,'Times New Roman',serif);
  letter-spacing:normal;text-transform:uppercase;text-align:center;
  color:#0A0A0B;background:#F0EAE0;border:1px solid #6a5c3a;border-radius:0;
  opacity:1;overflow:visible;margin-left:10px;width:auto;min-width:95px;
  padding:0 19px}
.console .cb-row .cb-end:hover{background:#FFFFFF;color:#0A0A0B}

/* THE WAY OUT IS LAST, AND IT IS RED. It sat among the blows, where a button
   that ends the fight has no business being - past END TURN it reads as what it
   is: the thing after everything else. */
/* THE PAIR AT THE END OF THE BAR. Break off wears END TURN's type exactly - the
   same serif, size, weight, case and absence of tracking - and the same 95x35, so
   the two things that finish a turn are the same object twice. Only the colours
   part company: one is the way on, the other is the way out.
   The rims are opposite on purpose: the light button takes a dark rim, the dark
   one takes a light rim, so each has an edge against what is behind it. */
.console .cb-row .cb-vbtn.breakoff{
  margin-left:10px;width:95px;
  font:500 .62rem/1 var(--serif,'EB Garamond',Georgia,'Times New Roman',serif);
  letter-spacing:normal;text-transform:uppercase;text-align:center;
  border-radius:0;gap:1px;
  background:#16100F;border:1px solid rgba(140,44,50,.7);color:#E0D6C6}
.console .cb-row .cb-vbtn.breakoff .vn{font:inherit;text-transform:inherit}
.console .cb-row .cb-vbtn.breakoff .vf{font:inherit;text-transform:inherit;
                                       color:#9A6060}
.console .cb-row .cb-vbtn.breakoff:hover{background:#1E1413;
                                         border-color:rgba(176,74,66,.9)}
/* When the parting blows would finish you the rim lights and the name warms -
   still restrained. A filled red button shouted the warning before it was true. */
.console .cb-row .cb-vbtn.breakoff.fatal{background:#1C1210;border-color:#8E2A2A;
                                         color:#E0B8B8}
.console .cb-row .cb-vbtn.breakoff.fatal .vf{color:#B87070}

/* THE BELT WAS LOSING ITSELF. It held 99px against the verbs' several hundred,
   so the one place that says what you are carrying read as an afterthought at
   the end of the bar. Wide enough for its two tabs and the things under them. */
.console .cb-row .grp.last{min-width:190px}
.console .cb-row .grp.last .tabs{gap:6px}
.console .cb-row .grp.last .tabpane{gap:8px}
.console .cb-row .grp.last .tabpane .sq{width:34px;height:34px}

/* ---- THE REROLL AND THE NUDGE ARE DRAWN (KB-070) ----------------------------
   They were typeset: U+21BA for the reroll and the fullwidth ＋ / － for the
   nudge - glyphs chosen for their WIDTH rather than their shape, at whatever
   weight the font felt like, in a button 15px tall. The dice stopped being
   characters a while ago; these are the last two on the screen that were.
   Hairline paths at the room's own weight, and a hit area a thumb can find. */
.console .cb-row .cb-rbtn .ic{width:11px;height:11px;flex:none;display:block;
                              fill:none;stroke:currentColor;stroke-width:1.5;
                              stroke-linecap:round;stroke-linejoin:round;
                              vector-effect:non-scaling-stroke}
/* One button carries both chevrons and shows the one it is. */
.console .cb-row .cb-rbtn.up .ic:last-child,
.console .cb-row .cb-rbtn.down .ic:first-child{display:none}

.console .cb-row .cb-rbtn{height:20px;gap:4px;padding:0 8px;
                          display:flex;align-items:center;justify-content:center}
.console .cb-row .cb-rbtn b{font:400 .72rem/1 var(--mono,monospace);font-weight:400}
/* The nudge pair sits as one control, not two strays. */
.console .cb-row .gnudge{gap:3px}
.console .cb-row .gnudge .cb-rbtn{padding:0 6px}
.console .cb-row .gstack{gap:6px}

/* ---- THE SCAPE: THE BRIDGE IS PAINTED (KB-073) ------------------------------
   Two plates over the schematic, one per depth: the city far off, and the
   arcade wall between it and the men. The men do not move; the plates drift with
   the pointer at different rates, so the lane turns around the fight instead of
   sliding under it.

   Both plates are placed off the SEAM, the same line every floor mark is
   measured from - the art cannot drift away from the geometry it stands on. */
.lane .scape{position:absolute;left:0;right:0;top:0;
             height:calc(100% - var(--seam,31) * 1%);
             overflow:hidden;pointer-events:none}
.lane .scape i{position:absolute;top:-5%;bottom:-5%;left:-6%;right:-6%;display:block;
               background-repeat:no-repeat}
/* One drift law for every kind of layer: painted plates (i) and schematic
   furniture (svg). A layer without a --d of its own sits still. */
.lane .scape i,
.lane .scape svg{transform:translate3d(calc(var(--px,0) * var(--d,0) * 1px),
                                       calc(var(--py,0) * var(--d,0) * .34px),0)}
/* Both plates are CROPPED to the band and anchored to the seam, never stretched
   to it: the band runs from 1200x170 in a closet to 1200x320 in a hall, and an
   arch squashed to fit a low room stops being an arch. A low room simply shows
   less of the same wall. */
.lane .scape .sky{--d:16}
.lane .scape .wall{--d:4}
.st-bridge .scape i.sky{background-image:url(/static/img/combat/bg/bridge_city.png);
                        background-size:cover;background-position:50% 100%}
.st-bridge .scape i.wall{background-image:url(/static/img/combat/bg/bridge_arcade.png);
                         background-size:cover;background-position:50% 100%}
/* Night, and the game's own palette: the plates are generated pale so the
   silhouettes stay readable, and the room takes them down to its own light -
   warm stone against a cold city, which is the only reason the openings read
   as openings at this size. */
.st-bridge .scape i.sky{filter:saturate(.45) contrast(1.2) brightness(.58) sepia(.3)}
.st-bridge .scape i.wall{filter:saturate(.35) contrast(1.18) brightness(.4) sepia(.55)}
/* THE STREET wears a PAINTED skyline too (KB-100): the shuffled far city, one
   plate, cover-cropped and anchored to the seam, drifting on --d:16. NO colour
   filter (owner 2026-08-02) - the art carries its own light and detail; a filter
   only flattened it to black-on-black. The .ways vignette still frames the street,
   so this scape needs no :after. */
.st-street .scape i.sky{background-size:cover;background-position:50% 100%}
/* THE FAR PLANE IS ABSTRACT AGAIN (owner 2026-08-02). The painted city is parked:
   the art was not ready, and a half-read city standing behind the men is worse
   than no city at all. What holds the band now is DRAWN, not photographed - a
   cold depth rising off the seam and a single hairline where the ground ends.
   Nothing in it pretends to be a building; it reads as distance, which is all
   the far plane owes the fight. It sits on the BAND, so if a plate is ever put
   back in the pool it simply paints over this. */
.st-street .scape{background:
    linear-gradient(180deg,
      #050504 0%, #0A0908 30%, #15110D 58%, #241B15 82%, #35271C 100%)}
.st-street .scape::after{content:"";position:absolute;left:0;right:0;bottom:0;
                         height:1px;pointer-events:none;
                         background:rgba(211,207,199,.14)}
/* A ceiling, a joint and the room's own vignette - for the PAINTED settings
   only: the plates cover the schematic's vignette, so the band carries its own.
   The vector settings keep their vignette in the ways overlay instead. */
.st-bridge .scape:after{content:"";position:absolute;inset:0;pointer-events:none;
                   background:
                     radial-gradient(ellipse 70% 120% at 50% 62%,
                       rgba(6,5,4,0) 40%,rgba(6,5,4,.66) 100%),
                     linear-gradient(180deg,
                       rgba(6,5,4,.92) 0%,rgba(6,5,4,.28) 14%,rgba(6,5,4,0) 34%,
                       rgba(6,5,4,0) 58%,rgba(6,5,4,.55) 88%,rgba(6,5,4,.85) 100%)}

/* ---- THE ENTRY DOLLY (SUR-78 stage L, KB-149) -------------------------------
   The cold-load reveal lands a FLAT stack: each layer starts converged toward the
   near plane and settles back to its own depth beside the 240ms fade. One block,
   and every layer reads its own --d - a layer without one does not move.
   `translate`/`scale`, NOT `transform`: the drift law above owns `transform` on
   these very nodes, and the individual properties compose with it, so the parallax
   stays live and the missing end frame lands on it rather than fighting it.
   ONE-SHOT BY CONSTRUCTION: both gate words are written by the reveal script, and
   markup fight-swap inserts never runs its scripts, so a swapped lane wears
   neither and no answer can replay the settle. The pause below is the gate's own
   law, extended to the layers it never named. */
@keyframes scapeSettle{
  0%{translate:0 calc(var(--d,0) * .12%);scale:calc(1 + var(--d,0) * .002)}
}
.lane.js-gate .scape i,
.lane.js-gate .scape svg{animation:scapeSettle .42s ease-out}
.lane.js-gate:not(.ready) .scape i,
.lane.js-gate:not(.ready) .scape svg{animation-play-state:paused}

/* The pointer is a nicety; a room that lurches is not. */
@media (prefers-reduced-motion:reduce){
  .lane .scape i,.lane .scape svg{transform:none}
  .lane.js-gate .scape i,.lane.js-gate .scape svg{animation:none}
}

/* ---- THE ROOM'S SEATING (KB-072) --------------------------------------------
   A slab used to appear only under a man, so a formation was five marks and the
   eye had to join them up. Every seat the room has is drawn now - faint, and
   fainter the deeper it lies, the same way a man's own light falls off. An empty
   seat is one the horn could fill; drawing it makes the shape of the fight, and
   the room's remaining capacity, a thing you can see. */
.lane .unit.cellonly{pointer-events:none;z-index:0}
/* THE EMPTY SEATS ARE GONE (owner 2026-08-12): the faint diamonds that drew a
   formation's unfilled places read as clutter, not a shape. Only the men and the
   ground they stand on stay. */
.lane .unit.cellonly{display:none}
.lane .unit.cellonly .slab.cell{border-style:dashed;
                                border-color:rgba(211,207,199,.14);
                                background:rgba(211,207,199,.012)}
.lane .unit.cellonly .slab.cell.d2{border-color:rgba(211,207,199,.10)}
.lane .unit.cellonly .slab.cell.d3{border-color:rgba(211,207,199,.07)}



/* KEYBOARD PLAY (KB-117). All of this is JS-injected by fight-keys.js and styled
   through <kbd> + data-* attributes, so the fight-ui contract needs no new class
   and there is no :has() - the ceiling is untouched. Nothing shows without JS. */
.cb-wrap kbd[data-kbb]{display:inline-block;margin-right:5px;padding:0 4px;
  border:1px solid var(--edge);border-radius:2px;background:#0b0b0d;
  font:700 .62rem/1.5 var(--mono,monospace);letter-spacing:.02em;
  color:var(--ink-emph,#F0EAE0);vertical-align:middle;pointer-events:none}
.cb-wrap kbd[data-kbb][data-under]{position:absolute;top:100%;left:50%;
  transform:translateX(-50%);margin:2px 0 0;padding:0;border:0;background:transparent;
  line-height:1.2;color:var(--ink-label,#8F877C);z-index:6}
/* Reserve room under the dice for their number, so the row never overflows the
   overflow:auto cb-row into a scrollbar. Only when the keyboard layer is live. */
.cb-wrap[data-kb] .console .cb-row .grp.hand{padding-bottom:15px}
/* Target cursor (KB-117): two vertical bordeaux bars flanking the marked man's
   tell + name, injected by fight-keys.js beside that plate row. */
[data-kbmark]{position:absolute;left:-9px;right:-9px;top:-2px;bottom:-2px;pointer-events:none;z-index:6}
[data-kbmark]::before,[data-kbmark]::after{content:"";position:absolute;top:0;bottom:0;width:3px;
  border-radius:2px;background:var(--blood-text,#A04545);box-shadow:0 0 5px rgba(160,69,69,.6);
  animation:kbmark-pulse 1.7s ease-in-out infinite}
[data-kbmark]::before{left:0}
[data-kbmark]::after{right:0}
@keyframes kbmark-pulse{0%,100%{opacity:.6}50%{opacity:1}}
[data-kbhelp]{position:fixed;right:14px;bottom:14px;z-index:50;max-width:320px;
  background:#121214;border:1px solid #29292c;padding:12px 14px;
  font:.72rem/1.5 var(--serif,Georgia,serif);color:var(--ink-body,#D3CCC0);
  box-shadow:0 8px 30px rgba(0,0,0,.6)}
[data-kbhelp] h3{margin:0 0 8px;font:700 .7rem/1 var(--mono,monospace);
  letter-spacing:.12em;text-transform:uppercase;color:var(--ink-label,#8F877C)}
[data-kbhelp] dl{display:grid;grid-template-columns:auto 1fr;gap:3px 12px;margin:0}
[data-kbhelp] dt{font-family:var(--mono,monospace);color:var(--ink-emph,#F0EAE0);white-space:nowrap}
[data-kbhelp] dd{margin:0;color:var(--ink-label,#8F877C)}
[data-kbhelp] p{margin:8px 0 0;font-size:.62rem;color:var(--ink-faint,#5C564E)}
