:root {
  --ink:        #dff2fb;
  --ink-dim:    #a2b5c2;
  --ink-hi:     #eaffff;

  /* Surfaces. Every full-screen backdrop is --scrim; there is one black and one blur
     because a player who opens the wallet, then a reality check, should not watch the
     water change depth of field between them. */
  --scrim:      rgba(1, 6, 12, .82);

  /* The rail's width, in one place, because two things need it: the rail itself and the
     room the dock reserves so the controls never sit under it. They were separate numbers -
     304px and a 340px reservation that was "304 plus a gutter" - and the moment the rail
     became responsive the reservation stopped tracking it. */
  --rail-w:     clamp(304px, 26vw, 380px);

  /* Edges. Four steps of one hue. Twenty-three of the fifty-one hand-written
     copies now name a step; the rest are mid-values that still have to be
     looked at one at a time rather than rounded in bulk. */
  --edge-hair:  rgba(140, 220, 255, .10);
  --glass-hi:   rgba(163, 189, 207, 0.16);
  --edge-ctrl:  rgba(140, 220, 255, .30);
  --edge-live:  rgba(140, 220, 255, .55);

  /* THE SURFACE, WHICH WAS TWO MATERIALS ONE GUTTER APART.
     #rail and #dock are adjacent siblings in index.html, and at 1024 — the narrowest
     width the rail exists at — the rail box runs x 702..1006 and the dock's plate
     x 18..684, so there are exactly 18px of water between them. The dock was an opaque
     gradient (.88 at its top, .92 at its foot), no blur, 18px radius, edge .16. The rail
     was a flat rgba(4, 16, 26, .62) behind blur(7px), 10px radius, edge .13. So 12% and
     8% of the scene came through one and 38% through the other, one sharp and one out of
     focus: two materials, a gutter apart, on adjacent siblings.

     CONSOLIDATED ON THE OPAQUE ONE, from the census rather than from taste. Of the 28
     boxes in this file that carry a fill, a 1px border and a radius, exactly 3 carry a
     blur — .hist and the rail's two. The other 25 are painted flat whatever their alpha:
     12 at .86 or over, and 13 translucent, down to .08 on .stepper button. BLUR is the
     rare property here, not opacity, which is the stronger version of the same argument:
     the rail is the three-box minority, so the rail is what has to argue, and it does: #rail overrides --surface-fill and --surface-blur
     for its own subtree, with the measurement, and tools/layout-check.mjs holds both.
     Everything else — edge, radius, the lit top hairline — is shared, so this is one
     recipe with one stated exception rather than two recipes.

     THE EDGE IS --glass-hi AND IS NOT COPIED HERE. .deck wrote rgba(140, 220, 255, .12)
     and the rail's two wrote .13: three near-misses of a token that already means "the
     edge of a surface". Composited over the dock's own fill over the abyss, .12 lands at
     Y 43.9 against --glass-hi's 51.3 — the deck's hairline 14% darker than the panel's
     10px below it, over a byte-identical background.

     14px AND NOT .panel's 18: .panel's own max-height:520 override already asked for 14
     and .deck was already 14, so 14 is the only radius the two boxes in the dock ever
     agreed on. That override said the same thing as this and has gone. */
  /* A PLATE, NOT A WINDOW. Aviator paints #1B1C1D; Stake paints a flat dark field.
     Glass over the sky (.62 + blur) is what made the rail and the bet card look like
     two different products, one gutter apart. The ocean still runs full-bleed behind
     the chrome; the chrome itself is opaque. */
  --surface-fill:   rgb(8, 22, 34);
  /* Smooth coated alloy. Texture belongs in the water; the cockpit stays quiet. */
  --surface-coat:   linear-gradient(180deg, rgba(64, 77, 87, .18), rgba(2, 9, 17, .06));
  --surface-blur:   none;
  /* THE SHEET MATERIAL, WHICH TWO BOXES NOW SHARE. A sheet sits OVER the running game
     rather than beside it, so unlike --surface-fill it is opaque: the cashier and the
     phone's room panel both cover live water, and a panel you can read the round through
     is a panel that reads as a rendering fault. It was written out once as a literal here
     and once as a fallback for a token that did not exist, which is two copies of one
     decision. */
  --sheet-bg:       rgba(7, 20, 31, .99);
  --surface-radius: 14px;
  /* The lit top hairline, which is the half of .panel's box-shadow that reads as
     material. The other half — the 0 22px 60px lift — is NOT in the recipe: it reaches
     22 + 60/2 = 52px below its own bottom edge and #dock's gap is 10px, so the same
     shadow on .deck would lay 42px of black across the top of the panel under it. */
  --surface-lip:    inset 0 1px 0 rgba(217, 234, 246, .055),
                    inset 0 -1px 0 rgba(0, 0, 0, .35);
  --instrument-fill: linear-gradient(180deg, #08121b, #0b1822);
  --instrument-edge: rgba(149, 174, 190, .22);
  --press-ease: cubic-bezier(.2, .8, .2, 1);

  --cyan:       #6fe6ff;
  --amber:      #ffc24a;
  --green:      #57f2a5;
  --red:        #ff6b52;
  --mono: "SF Mono", "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace;
  --ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Helvetica, Arial, sans-serif;
}

* { box-sizing: border-box; }

/*
 * `hidden` MEANS HIDDEN, AND IT DID NOT.
 * =====================================
 *
 * The user agent hides `[hidden]` with a `display: none` rule of its own, and ANY author
 * rule that gives an element a display value beats it — `.row { display: flex }` is a class
 * selector against an attribute selector at the same specificity, and later in the cascade
 * besides. So an element could carry the attribute, report `el.hidden === true`, and be on
 * the screen in full.
 *
 * WHAT WAS ON THE SCREEN. Six of the twenty-two elements that ship with the attribute:
 *
 *   #signout-row       "Sign out" — on a build with no identity provider, where
 *                      /auth/signout is served by nobody. (Not named by its environment
 *                      variable, because this file is PUBLISHED and the build refuses to
 *                      ship anything matching a credential's name — which is how that rule
 *                      is meant to work, and it caught this comment.)
 *                      The comment on the control says it is "shown only to
 *                      somebody who is actually signed in", and authwire-check asserts the
 *                      JavaScript half of that; the CSS took it back.
 *   #board-self        "Your rank #— Net —" for a reader who has not played, which is the
 *                      shaming the board is explicitly built to avoid.
 *   #board-mine-more   "Show older" with nothing older, twice.
 *   #board-rounds-more
 *   #wd-open           the open-withdrawals list, with no withdrawals.
 *   #l-back            the reinstatement button, to somebody who is not excluded.
 *
 * Every one of them is this project's own worst defect class — a live-looking control that
 * answers a press with nothing, or in the first case with a 404 that takes a player out of
 * the game.
 *
 * `!important` IS THE POINT, not a shortcut. It is the one declaration that cannot be
 * outrun by a class rule written later by somebody who has never read this paragraph, which
 * is exactly how all six arrived. Nothing in this interface shows an element while the
 * attribute is set — every one of the six is toggled through `el.hidden` — so there is no
 * legitimate rule for this to break.
 *
 * AND THE FILE ALREADY AGREED, one line below: `.hidden { display: none !important }` is
 * the same decision, made for the CLASS, presumably the first time something rendered
 * anyway. The attribute half was never given it, and four of the six had arrived since.
 */
[hidden] { display: none !important; }

/* Keyboard players need to see where they are. The stepper inputs and the seed field
   previously set outline:none with nothing in its place. */
:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
  border-radius: inherit;
}

/* iOS swallows the second of two fast taps as a double-tap-zoom. The design expects
   three presses of BLOW BALLAST inside a twenty-second dive. */
button, .stepper input, .icon { touch-action: manipulation; }
/*
 * AND THE SECOND TAP SELECTED THE LABEL. `touch-action: manipulation` stops the ZOOM; it
 * does nothing about the selection, and a double-press on DIVE, on Room, on ×25 — the
 * ordinary rhythm of this game, three presses inside a twenty-second dive — left the word
 * highlighted in the browser's own blue over the design's palette, and on iOS raised the
 * Copy / Look Up callout over the round. Dragging a thumb across the dock did the same to
 * every label it crossed.
 *
 * ON THE CONTROLS AND THE CHROME ONLY, never on the page. Everything a player might have to
 * copy is a value somebody may need to prove something with — a deposit address, a server
 * seed, a round hash, what somebody said in the room — and an app-wide `user-select: none`
 * takes all of those away to fix a highlight. So this lists the things that are PRESSED:
 * buttons, the tab rows, the labels that sit on the dock and the bar. Panel prose, the chat
 * log, the tables and every readonly field stay selectable because they are not named here.
 *
 * -webkit-touch-callout is the iOS long-press menu, which is the same gesture arriving as a
 * hold rather than a double tap.
 */
button, .icon, .deck-bar, #topbar, .brand, .wallet-btn,
.field > span, .histrow, #idle-panel .small, #deck-badge {
  -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
}
/* The stepper's own field is typed into, so it keeps selection; only the ± around it lose
   it, and they are buttons, covered above. */
.stepper input { -webkit-user-select: text; user-select: text; }
/* 44px, because that is the smallest target a thumb hits reliably and these two were 36.
   They sit beside the wallet on the one bar that is always on screen. */
.icon { min-width: 44px; min-height: 44px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

html, body {
  margin: 0; padding: 0; height: 100%;
  /*
   * DARK, AND SAID IN CSS AS WELL AS IN A META TAG.
   *
   * index.html carries `<meta name="color-scheme" content="dark">`, which is what governs
   * the page before this file has loaded. This is the same declaration in the place the
   * property actually lives, so it is inherited and computed rather than merely defaulted:
   * scrollbars, the caret, and the spinner arrows on the stake and auto-surface fields are
   * native widgets, and without it they are drawn for a white background inside a panel
   * that is nearly black.
   */
  color-scheme: dark;
  background: #01060d;
  color: var(--ink);
  font-family: var(--ui);
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior: none;
}

#stage {
  position: fixed; inset: 0;
  display: flex; flex-direction: column;
}
/* The full-size ocean has floating controls; experience.css owns their responsive layout. */
#play {
  position: absolute; inset: 0;
  min-height: 0; min-width: 0;
  display: block; padding: 0;
  z-index: 2;
}
#game {
  position: absolute; inset: 0; min-height: 0;
  border-radius: 0;
  overflow: hidden;
  isolation: isolate;
  background: #01060d;
  border: 0;
  box-shadow: none;
}
#scene {
  position: absolute; inset: 0;
  width: 100%; height: 100%; display: block; z-index: 1;
}

.hidden { display: none !important; }

/* ------------------------------------------------------------------ top bar */

#topbar {
  position: relative;
  flex: 0 0 auto;
  /* Three columns rather than space-between, so the cashier sits at the TRUE centre of
     the frame and does not drift as the brand or the session readout change width. */
  display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 10px;
  /* index.html opts into viewport-fit=cover, so without env() insets the chrome sits
     under the notch and the dock's lower edge lands inside the iOS home-indicator strip
     — where a swallowed SURFACE tap is a money bug, not a cosmetic one. */
  padding: calc(8px + env(safe-area-inset-top)) calc(18px + env(safe-area-inset-right))
           8px calc(18px + env(safe-area-inset-left));
  pointer-events: none;
  z-index: 5;
  /* A BAR, not sky. The ticker sits in #play immediately under this; without a fill the
     wallet, the icons and the multiplier pills all float on one field. Same plate as
     the dock and the rail, hairline under it so the strip is a layer of its own. */
  background: var(--surface-fill);
  background-image: var(--surface-coat);
  border-bottom: 1px solid var(--glass-hi);
  box-shadow: var(--surface-lip);
}
#topbar > * { pointer-events: auto; }

.brand { display: flex; align-items: center; gap: 8px; }
.brand .mark {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--cyan);
  box-shadow: 0 0 0 3px rgba(111, 230, 255, .07), 0 0 9px rgba(111, 230, 255, .45);
  align-self: center;
  position: relative;
  border: 0; padding: 0; cursor: pointer;
}
.brand .mark.round-ping::before {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: 17px; height: 17px; margin: -8.5px;
  border: 1px solid rgba(111, 230, 255, .65); border-radius: 50%;
  pointer-events: none; animation: sonar-ping .85s ease-out both;
}
@keyframes sonar-ping {
  from { opacity: .7; transform: scale(.6); }
  to { opacity: 0; transform: scale(1.8); }
}
@keyframes pulse { 0%,100% { opacity: .45; } 50% { opacity: 1; } }

.brand .name {
  font-family: var(--ui); font-weight: 800; font-size: 19px;
  letter-spacing: .18em; color: #edf3f5;
  text-shadow: 0 1px 0 #000;
}
/*
 * `.brand` USED TO BE ON THE LINE ABOVE THIS, WITHOUT A BRACE.
 *
 * Which made it the descendant selector `.brand .top-right` — and .top-right is a SIBLING
 * of .brand, not a descendant, so the rule had never once applied. Above 640px the icons
 * inherited `display: block` and sat mid-bar instead of flushed right. A missing brace is
 * not a syntax error in CSS; it is a different, silent, always-false rule.
 */
.top-right { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
.icons { display: flex; align-items: center; gap: 6px; }

.icon {
  width: 28px; height: 28px; border-radius: 6px;
  background: transparent; border: 0;
  color: var(--ink-dim); font-size: 14px; cursor: pointer;
  /* NAMED PROPERTIES, BECAUSE `transition: .16s` MEANS `transition-property: all`.
     The one-value form of the shorthand leaves the property list at its initial `all`, and
     `all` catches the press below and RAMPS it rather than applying it. Measured on #mute one
     frame after :active lands: with `all`, getComputedStyle(...).transform reads
     matrix(1, 0, 0, 1, 0, 0) with a 160 ms CSSTransition running on transform; .chips button,
     which names its two properties, reads matrix(1, 0, 0, 1, 0, 1) with none running. */
  transition: border-color .16s, color .16s;
}
.icon:hover { color: var(--ink-hi); background: rgba(140, 220, 255, .08); }
/* The press, in the frame it happened in. The same 1px .chips button, .big and .wtabs button
   already use - one press idiom in this file, not a fourth. */
.icon:active { transform: translateY(1px); }
.icon.off { color: #55707e; }

.menu { position: relative; }
.menu-pop {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 188px;
  display: flex; flex-direction: column; gap: 2px;
  padding: 6px;
  background: var(--surface-fill);
  background-image: var(--surface-coat);
  border: 1px solid var(--glass-hi);
  border-radius: 10px;
  box-shadow: var(--surface-lip), 0 12px 32px rgba(0, 0, 0, .35);
  z-index: 30;
}
.menu-pop.hidden { display: none; }
.menu-pop button {
  appearance: none; background: none; border: 0; border-radius: 8px;
  text-align: left; padding: 10px 12px; min-height: 40px;
  color: var(--ink); font: inherit; font-size: 13px; cursor: pointer;
}
.menu-pop button:hover { background: rgba(140, 220, 255, .08); }

/* ------------------------------------------------------------------ history */

/*
 * RECENT DIVES — a row in the dock, not a column in the corner.
 *
 * This was absolutely positioned at top-right and computed its own `top` from the pressure
 * dial's geometry in vh and vw, so the two instruments would not overlap. They overlapped
 * anyway at every viewport once enough rounds accumulated, which is what tools/layout-check
 * was written for. The dial is gone and this is in normal flow inside the panel, so there
 * is nothing to derive and nothing to collide with — and it cannot reach the session net
 * position and clock, which is the one band on the screen that must stay clear.
 */
#history {
  display: flex; flex-direction: row; gap: 5px;
  flex-wrap: nowrap; overflow: hidden; min-height: 0;
  margin-bottom: 10px;
}
/* A round the player sat out. Present, because a shared history that showed only your own
   rounds would misrepresent how often the table runs — and dim, because it is not yours.
   It carries its MULTIPLIER now rather than a dot: the number is the whole point of the
   strip, and half of them being punctuation was half a strip. */
/* .72, NOT .46. The dimming says "this one was not yours" and that is worth keeping — but
   on a SHARED table most rounds are not yours, so nearly the whole strip was seen through a
   54% veil, and the bands the strip was just given are the thing being veiled. An amber 40x
   you sat out is exactly the row a player wants to catch. The distinction does not depend on
   this number: `.hist.mine` carries a hairline under the pill, which is the quietest
   available way to say whose money it was and does not fight the colour. */
.hist.away { opacity: .72; }
/* Yours. One hairline under the pill, which is the quietest available way to say "this one
   was your money" without colouring it by how big it was. */
.hist.mine { box-shadow: inset 0 -2px 0 rgba(120, 220, 255, .30); }
.hist {
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  font-variant-numeric: tabular-nums;
  padding: 3px 8px; border-radius: 7px; text-align: right;
  background: rgb(6, 18, 28);
  background-image: linear-gradient(160deg, rgba(179, 222, 245, .10), transparent 60%);
  border: 1px solid rgba(140, 220, 255, .12);
  position: relative; overflow: hidden;
  box-shadow: inset 0 1px 0 rgba(221, 247, 255, .07), 0 2px 5px rgba(0, 0, 0, .16);
}
.hist:first-child { animation: slide .18s var(--press-ease); }
@keyframes slide { from { opacity: 0; transform: translateX(14px); } }
/* ------------------------------------------------------------- the three bands
 *
 * BANDED BY MAGNITUDE AT THE OWNER'S EXPLICIT DIRECTION, and the argument against is kept
 * in ui.js rather than deleted: colour on a history strip invites streak-reading, and every
 * dive is drawn independently. It is what Aviator and bustabit do and what a player arriving
 * from either expects to find.
 *
 * THIS GAME'S COLOURS, NOT AVIATOR'S. Its bands are blue, purple and magenta on black —
 * borrowing them would make the strip the only part of this interface that belongs to
 * another game. The scale here runs with the water: the dim ink the panel already uses,
 * then the cyan every live control is drawn in, then the amber the depth tape and the
 * pressure warnings use for the far end of the scale.
 *
 * A SCALE, NOT A LADDER OF PRIZES. Three steps, each one notch brighter than the last, and
 * no glow, no weight change, no fourth "epic" tier. A record that flashes is an advert.
 */
/*
 * THE FRACTION, DRAWN. The caption says "9/14 banked" and this says the same thing without
 * being read - which is the half a player takes in while deciding, with a round running and
 * their own money on the table. Two pixels tall and the resting colour of an edge: it is a
 * gauge, not a progress bar with somewhere to get to.
 */
.cap-bar {
  height: 2px; margin: 0 0 6px; border-radius: 1px;
  background: var(--edge-hair); overflow: hidden;
}
.cap-bar i { display: block; height: 100%; background: var(--green); transition: width .3s; }

.hist.flat { color: var(--ink-dim); }
.hist.lo   { color: var(--ink-dim); }
.hist.mid  { color: var(--cyan); border-color: rgba(111, 230, 255, .30); }
/* ONE ALPHA FOR BOTH LIT BANDS, and .34 was a second ladder arguing with the first. The
   note above says three steps, each one notch brighter — and the border alphas ran
   .12 -> .30 -> .34, which composited over the pill's fill is Y 35.9 -> 70.9 -> 75.8:
   a +97% step and then a +7% one. What actually separates mid from hi is the HUE, which
   is what that note asks for. At one alpha the amber lands at Y 68.4, three and a half
   percent under the cyan and indistinguishable from it in brightness on a 1px hairline —
   so nothing was bought by the extra .04, and it cost the file a value. */
.hist.hi   {
  color: var(--amber); border-color: rgba(255, 194, 74, .30);
  background-image: linear-gradient(145deg, rgba(202, 150, 49, .22), transparent 70%);
}
.hist.hi:first-child::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(105deg, transparent 20%, rgba(255, 231, 168, .25), transparent 75%);
  transform: translateX(130%); pointer-events: none;
  animation: foil-pass .8s .1s ease-out both;
}
@keyframes foil-pass {
  from { transform: translateX(-130%); }
  to { transform: translateX(130%); }
}
/* A win keeps the green it has always had — `.hist .win` below already says so, and a
   second copy here was a duplicate rather than an override. What the player TOOK outranks
   how big the number was; the band rides on the pill's border instead of fighting the text
   for it. */
/* A round the ledger refused: not a win, not a loss, not one you sat out. Deliberately the
   same weight as `away` — the money has not moved, and a loud colour here would read as an
   outcome. The title attribute carries the sentence. */
.hist.away[title] { opacity: .55; cursor: help; }
.held { color: var(--amber); letter-spacing: .04em; }
/*
 * `.hist.won`, NOT `.hist .win` — TWO MISTAKES IN ONE SELECTOR, AND THE COMMENT ABOVE CITED
 * IT AS THE THING THAT ALREADY WORKED.
 *
 * The pill is ONE element — renderHistory writes `<div class="hist flat mine won hi">` — so
 * a descendant combinator has nothing to reach, and the class is `won` rather than `win`
 * either way. Nothing has ever matched it. What that cost is exactly what the note four
 * rules up promises and then removes a "duplicate" in favour of: a round the player got out
 * of kept the BAND's colour — grey, cyan or amber by how big the hull was — instead of the
 * green that says they took money out of it. The hairline under the pill (`.hist.mine.won`)
 * was the only thing that ever said so.
 *
 * Below the band rules on purpose, and at equal specificity: what the player TOOK outranks
 * how big the number was.
 */
.hist.won { color: var(--green); }
/*
 * A ROUND YOU GOT OUT OF, marked rather than renumbered. The pill carries the HULL's
 * multiplier now — the strip is the table's history — so "this one was mine and it paid"
 * has to be said some other way than by printing a different number in it. `mine` is
 * already an inset rule along the bottom edge; a win turns that rule green. Two states, one
 * property, no extra glyph and no extra width, which matters because the row cannot wrap in
 * landscape.
 */
.hist.mine.won { box-shadow: inset 0 -2px 0 rgba(125, 255, 196, .55); }

/* Quick stakes. Deliberately quieter than the stepper above them: the stepper is the
   control, these are shortcuts into it, and four bright buttons under a field would read as
   four more things to decide. */
.chips { display: flex; gap: 5px; margin-top: 6px; }
.chips button {
  flex: 1 1 0; min-width: 0; min-height: 32px; cursor: pointer;
  font-family: var(--mono); font-size: 11px; letter-spacing: .04em;
  color: var(--ink-dim);
  background: rgba(6, 22, 34, .7);
  border: 1px solid rgba(140, 220, 255, .14); border-radius: 8px;
  transition: border-color .16s, color .16s;
}
.chips button:hover:not(:disabled) { border-color: var(--edge-live); color: var(--ink); }
.chips button:disabled { opacity: .45; cursor: default; }
.chips button:active { transform: translateY(1px); }
/* A standing order is armed. Cool green, the colour surfacing already uses, so the field
   says "this will take you out" in the same language the cash-out does. */
#auto.armed {
  color: var(--green);
  border-color: rgba(70, 220, 150, .45);
  background: linear-gradient(90deg, rgba(49, 166, 119, .08), transparent);
  border-radius: 6px;
  text-shadow: none;
}
.stepper:has(#auto.armed) {
  border-color: rgba(118, 187, 159, .38);
  box-shadow: inset 3px 0 0 rgba(107, 217, 166, .55), inset 0 2px 5px rgba(0, 0, 0, .30);
}
.stepper:has(#auto.armed) button[data-auto] {
  color: #b6dfca;
  background: linear-gradient(180deg, #223c35, #192e28);
  border-color: rgba(128, 191, 159, .22);
}
.stepper button[data-auto]:disabled { opacity: .45; cursor: default; }

/*
 * AUTO-BET, WHICH LOOKS LIKE WHAT IT IS: a preset, in the same row shape the stake presets
 * use, because it is the same kind of decision made once before a round. Warmer than the
 * stake chips when it is running — it is the only control in the panel that keeps acting
 * after you stop touching it, and that has to be visible from across the screen.
 */
#idle-panel .chips.stakes,
#idle-panel .chips.autobet { display: none; }
.chips.autobet button { letter-spacing: .1em; }
.chips.autobet button.on {
  color: var(--amber);
  border-color: rgba(255, 190, 90, .55);
  background: rgba(120, 80, 20, .28);
}

/* The one that IS the stake. Quiet enough not to compete with the DIVE button, loud enough
   that you do not have to read the number field to know what you are about to risk. */
.chips button.on {
  color: var(--ink);
  border-color: var(--edge-live);
  background: rgba(23, 200, 240, .15);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12);
}

/* ----------------------------------------------------- a phone lying on its side
 *
 * 844x390 and the dock took 267 of the 390: the top bar has 72, so the water — the entire
 * subject of the game — was a fifty-two pixel sliver between them. Measured, not guessed.
 *
 * A landscape phone is a real way to play a game like this and it cannot be a layout where
 * you can see the controls and not the thing they control. So on a short viewport the dock
 * gives height back: the panel tightens, the big button stops being a two-line block, and
 * the quick chips go — they are a convenience and the stepper beside them does the same job
 * in the same row.
 *
 * Height, not width: the constraint is vertical and a wide phone in landscape is still a
 * short screen. 520 is above every phone laid sideways and below every laptop.
 */
@media (max-height: 520px) {
  /*
   * SAME TRADE AS THE PHONE WIDTH BLOCK: a landscape phone is short, not wide. The
   * stepper does the job of the denomination chips; auto-bet is armed from a lobby
   * on a screen that can show it.
   */
  #idle-panel .chips.stakes,
  #idle-panel .chips.autobet { display: none; }
  #ticker { margin: 0; }
  #history { margin-bottom: 0; }
  #play { gap: 6px; padding-bottom: calc(8px + env(safe-area-inset-bottom)); }
  /* And the bar at the top gives some back too — it was 72px of a 390px screen. */
  #topbar { padding-top: 8px; padding-bottom: 8px; }
  /*
   * THESE THREE, AND ONLY THESE THREE, ARE DECLARED ABOVE THIS BLOCK.
   *
   * Everything else this block used to say now lives at the END of the file, because a
   * selector declared LOWER than this block wins on source order at equal specificity and
   * the rule written here does nothing whatever. That is not a hypothesis: `.panel` sat in
   * this block asking for 9px of padding and computed 14px on a measured 667x375, and
   * `.big b` asked for 16px and computed 19px. Five dead declarations, in the one block
   * whose entire job is to make a landscape phone fit — which is why a landscape phone
   * did not fit.
   *
   * `.chips` (197), `#history` (131) and `#topbar` (67) are the survivors. Anything added
   * here has to be checked against its declaration line or it quietly joins the dead.
   */
}

/* ------------------------------------------------------------------ dock */

#dock {
  position: relative;
  flex: 0 0 auto;
  width: 100%;
  min-width: 0;
  /* Gutter lives on #play so the game card and this plate share one edge. Padding here
     is zero so the plate is as wide as the ocean above it — Aviator's bet card. */
  padding: 0;
  display: flex; flex-direction: column; align-items: stretch; gap: 6px;
  z-index: 5;
  /*
   * THE GAME BOX KEEPS THE WATER. The dock used to overlay the canvas and cap itself at
   * 73vh so 27vh of ocean stayed visible behind it. The ocean is its own card now, so
   * this plate is content-sized. 64vh is a clip so a landscape phone still has a hull;
   * All Bets is what actually grew (see .bets) and is capped well below this. Do not
   * raise this to "make the table taller" — that is the 42vh-of-dummy-rows defect.
   */
  max-height: 64vh;
  overflow: hidden;
}

/*
 * AND WHAT GETS CLIPPED IS NEVER THE CONTROLS.
 *
 * The cap above hides its overflow, and overflow is clipped at the BOTTOM — where DIVE is.
 * #deck-body's comment already anticipated this ("the dock would overflow its max-height and
 * clip the DIVE button instead of the drawer") and gave the drawer `min-height: 0` so it
 * would give way first. That guard only covers a drawer that is OPEN. Closed, #deck-body is
 * hidden and contributes no height, the deck and the panel both refuse to shrink below their
 * content, and the overflow lands on the panel exactly as feared: measured at 667x375, the
 * idle panel ran from 223 to 448 in a 375px viewport and DIVE's bottom edge was at 379. The
 * primary control of the game was off the screen on a phone held sideways.
 *
 * So the order of what gives is stated here rather than left to flex defaults: the drawer
 * first, then the deck's own box (its history strip is a nicety and clips without cost),
 * and the panel never. `overflow: hidden` on the deck is what makes its shrink real — a
 * flex child with visible overflow shrinks its box and paints its content straight over
 * whatever is below it, which here is the button this rule exists to protect.
 */
#dock > .panel { flex: 0 0 auto; box-shadow: var(--surface-lip); }
/*
 * AT RISK SAT BETWEEN THE TWO CARDS. It un-hid only while a hull was going down
 * (`sync` toggles `#position.hidden` off when `diving && !spectating`), so the
 * lobby looked tight and the live round grew a gutter: 6px + the line + 6px
 * between SURFACE and All Bets. The cash-out button already quotes the live
 * figure; the opening stake is on the All Bets row.
 */
#dock > #position { display: none !important; }
/*
 * NOT `overflow: hidden`, WHICH IS WHAT THE FIRST VERSION OF THIS RULE SAID.
 *
 * A flex item's automatic minimum size — the thing that stops a flex child being squashed
 * below its own content — is switched OFF when its computed overflow is anything but
 * visible. Written as `min-height: 0; overflow: hidden` to "make the shrink real", this rule
 * did the opposite of its purpose: the deck lost its floor and collapsed to 18px with a 44px
 * bar inside it, so the Table and Room buttons were sliced in half by the dock's clip. Left
 * visible, the deck's automatic minimum IS the bar's line, the drawer's explicit
 * `min-height: 0` lets the drawer give way instead, and no magic pixel figure is needed.
 */
#deck { flex: 0 1 auto; }

/*
 * THE DOCK'S CAP NO LONGER MOVES WHEN A DRAWER OPENS, AND THE RULE THAT MOVED IT IS GONE.
 *
 * What was here:
 *
 *     #dock:has(#deck[data-view="table"]),
 *     #dock:has(#deck[data-view="room"]) { max-height: 92vh; }
 *
 * and it was right for the layout it was written for. The drawer used to be IN the dock's
 * flow, up to 34vh of it, with a panel above that would not yield — so the overflow landed
 * on the bottom of the dock, which is DIVE. Measured then, with the room open: 390x844 put
 * DIVE's bottom edge at 889 in an 844 viewport, 320x568 put the whole button below the
 * fold, 900x700 overflowed by 89px. Lifting the cap to 92vh was the fix.
 *
 * The drawer left the flow. It is `position: fixed` now — a sheet over the water, anchored
 * to the top of the dock — so it contributes NOTHING to the dock's height, and the reason
 * for the exception went with it. What the exception still did was let the dock GROW by up
 * to 19vh the moment somebody pressed Room: measured at 320x568, the cap went 414.64px to
 * 522.56px on the press, so the dock rose into the water and the multiplier — the exact
 * defect the 73vh cap exists to prevent — and --dock-h grew with it, which pushed the sheet
 * up and made it smaller. Opening the room made the room smaller.
 *
 * One cap now, for every state: 73vh, 27vh of water, whatever is open.
 */

/*
 * AND THE TWO PANELS THAT ARE READING MATTER GIVE WAY BEFORE THE CONTROLS DO.
 *
 * The dock clips from the bottom and its .panel children are `flex: 0 0 auto`, so whatever
 * sits above them pushes DIVE off the end — measured at 320x568 with the
 * room open: the dock wanted 193px more than it had and #dive was below the fold.
 *
 * Prose panels are read once, they are not time-critical,
 * and neither is holding money — so they are the members that shrink, and they scroll
 * inside what they are given rather than pushing a control off the screen. `overflow` other
 * than visible switches off the automatic minimum size, which here is exactly what is
 * wanted: these two may go to nothing, and DIVE may not.
 */
/*
 * AND WHAT SCROLLS IS THE PROSE, NEVER THE WAY OUT.
 *
 * Scrolling the whole panel put Skip and Got it below its own fold on a 320px screen — the
 * dismiss control inside the scroll area of the thing it dismisses. The text is the part
 * that can be long, so the text is the part that scrolls; the button row is pinned to the
 * bottom of whatever height the panel is given.
 */

#dock > .panel,
#dock > .deck { width: 100%; min-width: 0; }

.panel {
  width: min(720px, 100%);
  display: flex; flex-direction: column; gap: 10px;
  background: var(--surface-fill);
  background-image: var(--surface-coat);
  border: 1px solid var(--glass-hi);
  border-radius: var(--surface-radius);
  /* Padding is NOT in the recipe. This box holds prose, a stake field and DIVE; .deck
     holds a 44px bar. Padding follows the content and the dock's height budget, which
     this file measures in several places; the material does not. */
  padding: 14px;
  /* THE LIFT IS THIS BOX'S ALONE, and it is the one property .deck deliberately does not
     copy — see --surface-lip: 52px of reach over a 10px gap would put 42px of it on this
     panel. The lip says the two are the same material; the lift says which one the round
     is played on. */
  box-shadow: 0 22px 60px rgba(0, 0, 0, .5), var(--surface-lip);
}

.row { display: flex; gap: 10px; }

/* ------------------------------------------------- the stake panel, while watching
 *
 * A press with the doors shut places the bet for the NEXT round, so the panel is on screen
 * for most of a round rather than for the six seconds of a lobby. At full size that is half
 * the height of a phone — config.js measures it — for the whole time there is something to
 * watch, which is the wrong trade: the lobby is a moment a player is READING a panel, and a
 * running round is a moment they are watching water.
 *
 * The same markup and the same bindings, with the two things that are conveniences dropped:
 * the quick chips, whose job the stepper does in the same row, and the recent-dives strip,
 * which is on the rail on a wide screen and is not what somebody watching a hull needs.
 * Measured at 375x812: 408px full, 300px compact — 50% of the viewport against 37%.
 */
/*
 * AUTO-BET PRESETS ONLY IN COMPACT. A run can only be ARMED from a lobby — starting
 * something that spends money on its own should be a deliberate act with the full panel
 * in front of you — and it can be STOPPED from anywhere, because the big button says
 * STOP throughout. On a phone the presets are gone entirely (see the 640px block);
 * this is the desktop compact case.
 */
#idle-panel.compact .chips.autobet { display: none; }
/*
 * THE DIVES STRIP STAYS, and dropping it was the wrong economy.
 *
 * It is twenty-one pixels, and this file already says what it is worth: "the only thing on
 * screen that says what kind of table this is" — which is why the rail keeps it up for the
 * whole round on a wide screen. On a phone it lives in this panel, and hiding it here hid
 * it during exactly the rounds a player wants to compare against. The stake row is what
 * pays for the space instead; a strip of numbers is not what makes this panel tall.
 *
 * Measured at 375x812 with both cards on: 294px without it, 325px with — 36% against 40%.
 */
#idle-panel.compact .big { padding: 10px; }
#idle-panel.compact .big b { font-size: 15px; }

/*
 * THE STAKE ROW STAYS DURING A ROUND, ON EVERY WIDTH.
 *
 * It used to `display: none` below 560px in compact, because two seat cards over the
 * water ate 76% of a phone. There is one seat now, and hiding the field is what stopped
 * a player entering a bet while the hull was already down: the button still queued, but
 * the amount it queued was whatever was last typed in the lobby. The caption still
 * quotes the price; the field is how you change it.
 */

/* ------------------------------------------------------------------ seat

 * ONE SEAT. There were two, laid out as a grid whose column count was the number of open
 * ones, with `data-open` written by ui.js from the engine's own state. The second seat is
 * removed — the card, the toggle, the slot-2 state, the keyboard's shift+space and the
 * server's willingness to accept it — so the grid has nothing left to count and is a plain
 * single column. `.seats` survives as the wrapper because the dive panel and the lobby both
 * use it to hold their one card, and a wrapper that exists in one place and not the other
 * is how the two panels start drifting apart.
 */
.seats { display: grid; grid-template-columns: 1fr; gap: 10px; }
.seat { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
/*
 * AND `hidden` MUST WIN — now said once, at the top of this file, for every element rather
 * than for this one. The rule that used to live here read `.seat[hidden], .seats > [hidden]
 * { display: none }` and its own note drew the general conclusion without acting on it:
 * "any rule that gives an element a display value has to say what `hidden` means for it."
 * Six more elements had drifted into the same trap by the time anybody looked. See the
 * [hidden] rule beside the box-sizing reset.
 */


/* A stake locked into a launch is not editable, and it should look it rather than only
   refusing. Written from NetEngine.lockedFor, which answers for a QUEUED stake as well as a
   committed one: the server holds a queued intent and no keystroke can reach it. */
.seat.locked .row { opacity: .55; }

/*
 * min-width: 0 ON A FLEX CHILD THAT HOLDS AN INPUT.
 *
 * A flex item's default minimum is its CONTENT's minimum, and a number input carries an
 * intrinsic width of its own — so with two bet cards in a half-width grid column the two
 * fields refused to shrink, and the second card's AUTO-SURFACE field rendered outside the
 * panel entirely, over the water. Seen at 800px. `.stepper input` already carried this and
 * it is not enough: the chain is field → stepper → input, and one link without it pins the
 * whole row.
 */
.field { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.stepper { min-width: 0; }

/*
 * AND THE OTHER HALF OF THAT FIX, WHICH TOOK A YEAR TO NOTICE.
 *
 * min-width: 0 stopped the second card's field rendering outside the panel. It did it by
 * letting the fields shrink WITHOUT LIMIT, and what shrinks is the input: STAKE's stepper
 * carries three buttons (½, 2×, MAX) that do not shrink at all, so everything the row
 * gives up comes out of the number. Measured on a phone in landscape with two bets open,
 * 667x375: the stake input was SIXTEEN PIXELS wide. Not cramped — unreadable, and
 * untypeable. AUTO-SURFACE next to it had 85px because its stepper holds one button.
 *
 * So the two fields share a line while there is room for both and take their own lines
 * when there is not. 190px is the width below which the stake stepper's three buttons
 * leave the number less than about five characters, which is where a stake stops being
 * legible. The WRAPPING is not a breakpoint — it responds to the width of this row, which
 * depends on the viewport, the dock's padding and whether a second bet is open, and a
 * breakpoint would have to guess at all three.
 *
 * THE min-width GUARD IS, AND IT IS NOT OPTIONAL. Below 641px `.row` is already
 * flex-direction: column, and in a column a flex-BASIS is a HEIGHT: written unguarded, the
 * two fields became 190px tall each and the row overflowed 205px above the panel, so on a
 * portrait phone with two bets open the stake and auto-surface rows were simply not on the
 * screen. Seen at 375x812, which is the commonest phone there is. Above 641 the row is
 * horizontal and the basis means what it says; below it, the existing column rule already
 * gives each field the full width and there is nothing to fix.
 */
@media (min-width: 641px) {
  .seat .row { flex-wrap: wrap; }
  .seat .row > .field { flex: 1 1 190px; }
}
.field > span {
  font-size: 10px; letter-spacing: .12em; color: #adbfc9;
  font-weight: 600; text-shadow: 0 1px 0 rgba(0, 0, 0, .5);
}

.stepper {
  display: flex; align-items: stretch; gap: 4px;
  background: var(--instrument-fill);
  border: 1px solid var(--instrument-edge);
  border-radius: 9px; padding: 4px;
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, .4), 0 1px 0 rgba(204, 236, 255, .045);
  transition: border-color .16s, box-shadow .16s;
}
.stepper:focus-within { border-color: var(--edge-live); }
.stepper input {
  flex: 1; min-width: 0;
  background: none; border: none;
  color: #e6eef2; font-family: var(--mono); font-size: 18px; font-weight: 600;
  padding: 5px 8px;
}
.stepper input::-webkit-outer-spin-button,
.stepper input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.stepper button {
  background: linear-gradient(180deg, #273945, #1b2b36);
  border: 1px solid rgba(177, 198, 214, .14);
  color: #c6d5df;
  border-radius: 6px; padding: 0 10px; cursor: pointer;
  font-family: var(--ui); font-size: 11px; font-weight: 700;
  /* .16s AND NAMED. Two defects in one declaration: the one-value shorthand left
     transition-property at `all`, so the press below arrived as a 140 ms ramp (measured:
     identity at t0 with a CSSTransition on transform running), and .14s was a duration
     nobody wrote a reason for. Counted over the file: nineteen rules declare a transition,
     fifteen are purely .16s, and the four that differ all differ on purpose - `.big`'s .12s
     leg is the press itself, `#toast` is .2s, and `.cap-bar i` and `#cashout-flash.out` are
     .3s because they are progress rather than feedback. */
  transition: background .16s, color .16s;
  box-shadow: inset 0 1px 0 rgba(226, 239, 246, .08), 0 2px 1px rgba(0, 0, 0, .3);
}
.stepper button:hover:not(:disabled) { background: linear-gradient(180deg, #2c4b61, #1c3649); color: #fff; }
/* HALVE, DOUBLE AND MAX ACKNOWLEDGED NOTHING, 17 px ABOVE FOUR CHIPS THAT DO.
   Measured at 375x812, which is the width this argument is about: pointer:coarse is what
   raises both rows to the 44px floor and hover:none is what leaves a press as the only
   feedback there is. This row is three 44x44 buttons at y 463 and the quick-stake row is
   four 77.5x44 at y 524 - same height, same job, one row apart, and only one of them
   answered a finger. At 1280x720 with a fine pointer the rows are 29px and 32px tall and
   sit at y 484.5 and y 530.5; the gap is 17px in both. */
.stepper button:active:not(:disabled) {
  transform: translateY(1px);
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, .35), 0 1px 0 rgba(194, 231, 255, .07);
}

.big {
  width: 100%; border: none; cursor: pointer;
  border-radius: 14px; padding: 15px;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  font-family: var(--ui);
  position: relative; isolation: isolate; overflow: hidden;
  transition: transform .12s var(--press-ease), filter .16s, box-shadow .16s, background .16s;
}
.big > * { position: relative; z-index: 1; }
.big::before {
  content: ""; position: absolute; inset: 1px; border-radius: 12px;
  border: 1px solid rgba(226, 255, 241, .10);
  background: radial-gradient(ellipse at 20% 0%, rgba(248, 255, 244, .21), transparent 60%);
  pointer-events: none;
}
.big.primary:not(:disabled)::after {
  content: ""; position: absolute; inset: -35% -50%; pointer-events: none;
  background: radial-gradient(ellipse at 35% 0%, rgba(238, 255, 223, .16), transparent 48%);
  animation: caustic-crawl 9s linear infinite;
}
@keyframes caustic-crawl {
  from { transform: translateX(-14%); }
  to { transform: translateX(14%); }
}
.big b { font-size: 22px; letter-spacing: .11em; font-weight: 800; }
/*
 * THE ONE LINE ON THIS BUTTON THAT EVER CHANGES, AND .75 PUT IT UNDER THE FLOOR.
 *
 * #dive's <small> is where the game answers "what will this press do, and when". In a lobby
 * it is the countdown — "doors close in 6.0s · 1 in" — and with an empty balance it is
 * "no funds — open the Wallet to deposit", which is the only thing on a new player's
 * first screen that says where to put money. It is not a decorative subtitle.
 *
 * #002230 at .75 over the primary gradient, sampled at the caption's own row (45 px down a
 * 67 px button, measured at 1280x720), is 3.37:1. WCAG 1.4.3 asks 4.5:1 of 11 px text. At
 * full opacity the same ink on the same pixels is 4.91:1.
 *
 * Nothing is lost to the label above it: `.big b` is 19px/800 against this 11px/400, so the
 * hierarchy was already carried by size and weight and the opacity was spending contrast on
 * top of a difference that was already made. tools/a11y-check.mjs computes both figures
 * from this file.
 */
.big small { font-size: 12px; letter-spacing: .02em; }
/* THE REASON THE PRESS WILL OR WILL NOT FIRE. Not the cash-key — that is a second
   <small> — and not optional: ready-in / placing / next-round have to be readable
   at a glance or the button looks live when it is not. */
#dive-status {
  display: block;
  min-height: 1.25em;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .02em;
  line-height: 1.3;
  max-width: 22em;
}
.big:hover:not(:disabled) { filter: brightness(1.06); transform: translateY(-1px); }
.big:active:not(:disabled) { transform: translateY(1px); }
/* Disabled controls use their own cold enamel and bright ink. A subtree filter
   would dim the deposit instruction along with the plate. The lightest disabled
   background (#233440) and light ink (#bcd0dc) keep the instruction readable. */
.big:disabled { filter: none; cursor: not-allowed; transform: none; }

.big.primary {
  background: linear-gradient(180deg, #91e5ae, #3ac279);
  color: #042010;
  box-shadow: 0 3px 0 #1d6840, 0 5px 14px rgba(4, 12, 9, .25),
              inset 0 1px 0 rgba(242, 255, 235, .65), inset 0 -3px 0 rgba(0, 76, 37, .14);
}
.big.primary:disabled, .big.cash:disabled {
  background: linear-gradient(180deg, #233440, #192933);
  color: #bcd0dc;
  box-shadow: inset 0 1px 0 rgba(204, 231, 247, .10), inset 0 -2px 0 rgba(0, 0, 0, .2),
              inset 0 0 0 1px rgba(157, 183, 203, .16);
  animation: none;
}
.big:disabled::before { border-color: rgba(162, 196, 217, .09); background: none; }
.big.primary:active:not(:disabled) { box-shadow: 0 1px 0 #0a6532, inset 0 2px 5px rgba(0, 54, 23, .25); }
/*
 * THE CASH-OUT, WHICH IS THE ONLY CONTROL ON SCREEN WHILE A ROUND RUNS.
 *
 * --gain is set from the interface every frame: 0 at the stake, 1 when the position has
 * roughly quadrupled. Everything that escalates hangs off it, so the button gets visibly
 * hotter the longer somebody stays down. It is a READOUT of the position, not a prompt —
 * it grows because the money grew.
 */
.big.cash {
  --gain: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1px; line-height: 1.05;
  background: linear-gradient(180deg,
    hsl(calc(152 - var(--gain) * 26) 82% calc(60% + var(--gain) * 6%)),
    hsl(calc(158 - var(--gain) * 26) 78% calc(37% + var(--gain) * 5%)));
  color: #002314;
  box-shadow: 0 3px 0 #075942, 0 8px 24px rgba(70, 240, 160, calc(.20 + var(--gain) * .12)),
              inset 0 1px 0 rgba(255, 255, 255, .6), inset 0 -3px 0 rgba(0, 79, 51, .15);
  animation: breathe 2.4s ease-in-out infinite;
}
/* The amount leads. Tabular figures so the digits do not shuffle sideways as it climbs —
   a number that jitters while it grows reads as unstable rather than as increasing. */
.cash-amt {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  font-size: calc(27px + var(--gain) * 9px);
  letter-spacing: -.01em;
  text-shadow: 0 0 calc(var(--gain) * 18px) rgba(255, 255, 255, calc(var(--gain) * .65));
}
/*
 * SPECIFICITY, OR THE HIERARCHY IS INVERTED. `.big b` is (0,1,1) and beat `.cash-verb` at
 * (0,1,0); `.big small` beat `.cash-key` the same way. So on the one control a player
 * presses under time pressure, the VERB was the largest thing on the button and the AMOUNT
 * came second — the exact opposite of what these three rules were written to say.
 *
 * The key's authored 9.5px at .45 opacity measures about 2.2:1 over the green gradient, so
 * it is corrected here rather than merely switched on.
 */
.big .cash-verb { font-size: 11px; font-weight: 700; letter-spacing: .16em; opacity: .72; }
/* Quieter than the verb, which is quieter than the money. Three levels, so the eye reads
   the amount first and the key only when it is looking for one. */
.big .cash-key {
  display: block; font-family: var(--mono); font-size: 10px;
  letter-spacing: .14em; opacity: .72; margin-top: 1px;
}
/* The keyboard hint remains full-opacity on both the green and disabled navy
   plates. Type size carries its hierarchy without spending contrast. */
.big.primary .cash-key { opacity: 1; }
/* A short lift each time the figure changes, so growth is something you SEE happen rather
   than a number that is different when you next look at it. */
.cash-amt.tick { animation: cash-tick .18s ease-out; }
@keyframes cash-tick {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.055); }
  100% { transform: scale(1); }
}
@keyframes breathe {
  0%, 100% { filter: brightness(1); }
  50% { filter: brightness(1.035); }
}
/* Reduced motion keeps the SIZE and the colour — those are information — and drops the
   pulse and the tick, which are not. */
@media (prefers-reduced-motion: reduce) {
  .big.cash, .big.primary:not(:disabled)::after, .hist:first-child { animation: none; }
  .cash-amt.tick { animation: none; }
  .cash-amt { text-shadow: none; }
}

/* #ballast, #reinforce and #half had hover rules here. All three were solo-dive levers,
   the solo dive is gone, and the buttons went with it — the CSS did not. Dead style for
   dead elements is quieter than dead code but it is the same thing: the next person reading
   this file learns the interface has three controls it has not had for a long time. */

.position {
  font-family: var(--mono); font-size: 11px; color: var(--ink-dim);
  letter-spacing: .08em; text-align: center;
}
.position b { color: var(--ink); }

/* Shared rounds: the label a spectator gets where the SURFACE button would be. Same
   height, so the panel does not jump when a round the player is in ends and the next one
   starts without them. */
/*
 * A LIVE STATUS LINE, not a placeholder.
 *
 * This was 18px of padding inside a DASHED border, sitting in the dock's full 720px plate —
 * so a player watching a round they are not in was shown a big empty box with one small
 * sentence in it. A dashed rule means "nothing here yet" in every interface anybody has
 * used; this is the opposite, a round happening right now that they can watch.
 *
 * A solid pill sized to its own text, with a slow pulse on the dot so it reads as live.
 */
/*
 * `p.watching`, NOT `.watching` — AND THE ELEMENT NAME IS THE WHOLE FIX.
 *
 * src/main.js:216 does `document.body.classList.toggle('watching', on)` for a visitor with
 * no account, and this file spells that state correctly nineteen hundred lines below —
 * `body.watching #bet`, `body.watching #watch-prompt`. Unscoped, this pill's rule ALSO
 * matched that body: a bare class selector (0,1,0) beating `html, body` (0,0,1) seven
 * hundred lines earlier, so every spectator got the whole interface in the mono face at
 * 12px, dimmed, centred, with .1em of tracking — wherever a rule did not name its own
 * property. The ⓘ panel's prose, the watch prompt they are being shown, the board sheet
 * behind the button main.js relabels for them. It is the first paint they get, because
 * setWatching runs before the socket opens.
 *
 * The four elements that want this are all `<p role="status">` — see index.html — and body
 * is not a p. Anything else wanting a status pill should be a p too, or say so here.
 */
p.watching {
  font-family: var(--mono); font-size: 12px; letter-spacing: .1em;
  color: var(--ink-dim); text-align: center; margin: 0 auto;
  padding: 8px 16px 8px 13px; border-radius: 999px;
  background: rgba(6, 22, 34, .72);
  border: 1px solid rgba(140, 220, 255, .14);
  display: flex; align-items: center; gap: 9px; width: fit-content; max-width: 100%;
}
/*
 * THE WATCHING LABEL DOES NOT MOVE THE CONTROLS.
 * ==============================================
 * It toggles every cycle — "next round shortly" for the 4 s intermission, then gone when
 * the lobby opens — and it is the only status line in the dock that does. In flow that
 * costs 42 px: measured at 1280x800, the dock grew 272 -> 314 and the DIVE button moved
 * from y 653 to y 611 and back, every round, under whatever finger was reaching for it.
 * A control that jumps on a timer is worse than the silence this element was moved out of
 * #dive-panel to fix.
 *
 * Above the dock rather than inside it, so it floats over the water and the plate below it
 * never changes height. The other two dock-level status lines — #link-status and
 * #limit-status — stay in flow deliberately: both mean the game has stopped being playable,
 * and a layout that shifts when that happens is telling the truth.
 */
.dock-status {
  position: absolute; left: 0; right: 0;
  /* Set by ui.js from the dock's measured top — see paintWatchLabel. The fallback keeps it
     on screen if that has not run yet rather than parking it at the very bottom edge. */
  bottom: var(--watch-bottom, 300px);
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  pointer-events: none; z-index: 3;
}
.dock-status > * { pointer-events: auto; }

p.watching::before {
  content: ''; width: 6px; height: 6px; border-radius: 50%;
  background: var(--green); flex: 0 0 auto;
  animation: watchpulse 2.4s ease-in-out infinite;
}
@keyframes watchpulse { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }
/*
 * NOT A SHRUNKEN PLATE — NOTHING. `#watching` left this panel for a spoken
 * status outside the dock, so watch-only was an 8px padded empty box sitting
 * in `#dock`'s column between DIVE and All Bets. That is the gutter that
 * appeared while the hull was going down and a next-round bet was up.
 */
#dive-panel.watch-only { display: none; }

/*
 * THE RESULT IS A LINE, NOT A PLATE.
 *
 * Same shape the watching status had and the same fix: one sentence — "IMPLODED AT 1.19× ·
 * 442 m" — was framed by the dock's full 720px panel, so every round ended with a large
 * empty box under the water. The payout a winner cares about is already set large in the
 * centre of the screen, in the slot the figure climbed in, so this line is the footnote to
 * that and should carry itself like one.
 *
 * It keeps a plate, unlike the watching pill, because it IS a result and a result may
 * announce itself — just one sized to its own words.
 */
#outro-panel {
  align-items: center;
  width: fit-content; max-width: 100%;
  padding: 10px 6px;
}
/*
 * IT WRAPS. `white-space: nowrap` on a centred, fit-content panel inside a dock that hides
 * its overflow meant an over-wide sentence was clipped at BOTH ends — the opening word and
 * the money. The HELD state, which is the only statement on screen that a player's stake is
 * frozen, was destroyed completely. The panel is already `width: fit-content; max-width:
 * 100%`, so a short line still shrinks to its own words and nothing changes on a desktop.
 */
#outro-line {
  font-family: var(--mono); font-size: 14px; letter-spacing: .04em;
  padding: 4px 16px; text-align: center; overflow-wrap: anywhere;
  /* Two roughly equal lines rather than one long one and an orphaned unit: this
     wrapped as "IMPLODED AT 12.00x · 4,480" over a lone "m". */
  text-wrap: balance;
}
#outro-line .up { color: var(--green); }
#outro-line .down { color: var(--red); }

/* ------------------------------------------------------------------ modal */

#modal {
  position: fixed; inset: 0; z-index: 20;
  background: var(--scrim);
  backdrop-filter: blur(6px);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.sheet {
  position: relative;
  width: min(680px, 100%); max-height: 86vh; overflow-y: auto;
  background: linear-gradient(180deg, rgba(10, 28, 42, .97), rgba(4, 14, 24, .97));
  border: 1px solid rgba(140, 220, 255, .2);
  border-radius: 20px;
  padding: 28px 30px 24px;
  box-shadow: 0 40px 120px rgba(0, 0, 0, .7);
}
/*
 * THE ⓘ PANEL OPENS ON THE WORDMARK, so it is set in the wordmark's own metrics.
 *
 * Its content is the word HADAL - the product's NAME, not a description of the dialog -
 * which is why it is an h2 and not one of the five `h3.sheet-title` dialog titles
 * standardised below. That comment says "applies it to all five" and means it; this was
 * never in its scope, and was left at 16px / .3em matching nothing in the product.
 *
 * The weight is DECLARED rather than left to the UA default for h2, because `.brand .name`
 * declares 700 and a match that depends on a browser default is a coincidence, not a match.
 *
 * The colour goes through var(--ink-hi) and never the literal #eaffff: dom-check asserts
 * every palette token's hex appears exactly once in this file.
 *
 * The wordmark's text-shadow is deliberately NOT copied. Every text-shadow in this
 * stylesheet is chrome or celebration - the mark in the top bar, the live multiplier, the
 * cash-out - and nothing inside a .sheet glows. A 22px bloom over a panel of body prose
 * would make this the only lit text on a reading surface.
 */
.sheet h2 {
  margin: 0 0 4px;
  font-family: var(--mono); font-weight: 700;
  font-size: 15px; letter-spacing: .34em;
  color: var(--ink-hi);
}
.sheet h3 {
  margin: 22px 0 8px; font-size: 11px; letter-spacing: .22em;
  color: var(--cyan); text-transform: uppercase;
}
/*
 * A DIALOG TITLE IS NOT A SECTION EYEBROW, and one rule was serving both.
 *
 * `.sheet h3` above is right for the six eyebrows inside the ⓘ panel — small, tracked,
 * uppercase, cyan, the label over a block of prose. It is wrong for the five things that
 * NAME A DIALOG, and the measurements say so: at 11px they were smaller than the 11.5px
 * `.small` text directly beneath them. "Sign in to play" — the first thing a signed-out
 * visitor reads — was set smaller than its own explanatory sentence.
 *
 * Two of the five also carried the eyebrow's 22px top margin inside the sheet's own 28px of
 * padding, which is why #reality and #terms had a one-off margin patch below.
 *
 * The figures are not invented: .wallet-sheet h3 had already worked half of this out for
 * "Wallet" alone (17px, .1em) and let the colour and the casing fall through. This makes
 * that the standard and applies it to all five.
 */
.sheet h3.sheet-title {
  margin: 0 0 12px;
  font-family: var(--ui); font-size: 17px; letter-spacing: .1em;
  color: var(--ink); text-transform: none; font-weight: 700;
}

.sheet p { margin: 0 0 10px; font-size: 13.5px; line-height: 1.62; color: #c2dced; }
.sheet .lede { color: var(--ink); font-size: 14.5px; }
.sheet ul { margin: 0 0 10px; padding-left: 18px; }
.sheet li { font-size: 13.5px; line-height: 1.6; color: #c2dced; margin-bottom: 7px; }
.sheet code {
  font-family: var(--mono); font-size: 12px;
  background: rgba(110, 225, 255, .1); padding: 1px 5px; border-radius: 5px;
  color: #a8ecff;
}
.sheet .small { font-size: 11.5px; color: #7f9fb2; line-height: 1.55; }
.sheet .foot { margin-top: 18px; }
kbd {
  font-family: var(--mono); font-size: 10px;
  border: 1px solid var(--edge-ctrl); border-radius: 5px;
  padding: 1px 5px; color: var(--ink);
}

ul.facts { list-style: none; padding: 0; }
ul.facts li {
  display: flex; justify-content: space-between; gap: 12px;
  border-bottom: 1px dashed rgba(140, 220, 255, .14);
  padding: 6px 0; margin: 0;
}
ul.facts span { color: var(--ink-dim); font-size: 12.5px; }
ul.facts b { font-family: var(--mono); font-size: 12.5px; color: var(--ink-hi); }

.fair-h {
  margin: 18px 0 2px; font-size: 10px; letter-spacing: .18em;
  color: var(--ink-dim); text-transform: uppercase; font-weight: 600;
}
.verdict { font-family: var(--mono); letter-spacing: .04em; margin-left: 8px; }
.verdict.ok  { color: var(--green); }
.verdict.bad { color: var(--red); }
/* Neither green nor red: a client that joined mid-game has nothing to check the first
   reveal against, and colouring that as a failure is an accusation it cannot support. */
.verdict.wait { color: var(--ink-dim); }

.fair { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 10px 0 4px; }
.fair label.wide { grid-column: 1 / -1; }
.fair label {
  display: flex; flex-direction: column; gap: 4px;
  font-size: 9.5px; letter-spacing: .14em; color: var(--ink-dim); text-transform: uppercase;
}
.fair input {
  background: rgba(2, 12, 20, .7);
  border: 1px solid var(--glass-hi);
  border-radius: 9px; padding: 8px 10px;
  color: var(--ink); font-family: var(--mono); font-size: 11px;
  text-overflow: ellipsis;
}
/* The one field in this family that holds a sentence rather than a figure: same chrome, and
   it wraps. See the note beside #s-rtp in index.html. */
.fair .fair-val {
  background: rgba(2, 12, 20, .7);
  border: 1px solid var(--glass-hi);
  border-radius: 9px; padding: 8px 10px;
  color: var(--ink-dim); font-family: var(--mono); font-size: 11px;
  text-transform: none; letter-spacing: 0; line-height: 1.45;
}
.fair input:focus { border-color: var(--edge-live); }
.fair input[readonly] { color: var(--ink-dim); }
.fair input:disabled {
  color: var(--ink-dim); -webkit-text-fill-color: var(--ink-dim);
  opacity: .55; cursor: default;
}

.close {
  position: absolute; top: 14px; right: 16px;
  width: 30px; height: 30px; border-radius: 50%;
  background: var(--edge-hair); border: 1px solid rgba(140, 220, 255, .2);
  color: var(--ink); font-size: 17px; cursor: pointer; line-height: 1;
  transition: background .16s;
}
.close:hover { background: rgba(140, 220, 255, .24); }
/* The control every modal needs a finger to hit, and the coarse-pointer block below sizes it
   to 44px for exactly that reason. It should answer that finger. */
.close:active { transform: translateY(1px); }

/* ------------------------------------------------------------------ toast */

/*
 * BELOW THE READOUT BAND, NOT FOUR PIXELS UNDER THE BAR. This was `top: 78px`, which clears
 * the top bar as it is currently drawn — measured at 74px on both a phone and a desktop —
 * and sits inside the 128px band layout-check declares off limits for the session net and
 * the elapsed clock, which are RTS 2E/13C and may never be covered. Four pixels of clearance
 * is not a guarantee; it is the bar not having grown yet.
 *
 * The old band rule could not see this: it only fired on rules that named `right:`, and a
 * toast centred with `left: 50%` names none. Moving it below the band costs a transient
 * message a few pixels and makes the rule true rather than nearly true.
 */
#toast {
  position: fixed; left: 50%; top: 136px; transform: translateX(-50%);
  font-family: var(--mono); font-size: 12px; letter-spacing: .08em;
  /*
   * AN OPAQUE PLATE, NOT A TINT. This is the only visible channel for a refused bet, an
   * insufficient balance, an auto-bet stopping or a limit firing — and at 16% alpha over the
   * canvas it composited to between 1.19:1 and 2.42:1 against the lobby's sky, which is
   * where it fires most. .93 alpha over the worst sky stop measures 12.41:1.
   */
  background: rgba(20, 6, 4, .93);
  border: 1px solid rgba(255, 107, 82, .55);
  color: #ffc9bd;
  padding: 8px 16px; border-radius: 10px;
  opacity: 0; pointer-events: none; transition: opacity .2s; z-index: 30;
}
#toast.show { opacity: 1; }

/* ------------------------------------------------------------------ narrow */

/* ------------------------------------------------- screen-reader only
   Clipped rather than display:none or visibility:hidden — both of those remove the node
   from the accessibility tree, which would defeat the point. */
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  border: 0;
}
/* Hidden until it takes focus, then a real, visible control. A sighted keyboard user
   tabbing into an invisible button is a WCAG 2.4.7 failure, so it has to show itself. */
.sr-only-focusable {
  position: absolute; left: -9999px; top: 0; z-index: 40;
  background: rgba(6, 22, 34, .96); color: var(--ink);
  border: 1px solid var(--edge-live); border-radius: 8px;
  padding: 10px 14px; font: inherit; font-size: 13px; min-height: 44px;
}
.sr-only-focusable:focus { left: 12px; top: 12px; }

/* ------------------------------------------------- touch targets
   WCAG 2.5.5 asks for 44px where the pointer is a finger. Measured on a 375x812 phone
   before this block: the stake chips were 29x29 and the sound/info icons 36x36. In a
   product where MAX sits beside 2x and OFF beside the auto-surface field, a mis-tap
   changes what is staked — so this is a money control, not a comfort setting. Scoped to
   pointer:coarse so precise pointers keep the compact layout. */
@media (pointer: coarse) {
  /*
   * THE SEVEN MONEY CHIPS. The four quick stakes and the three auto-bet presets were 32px
   * on a coarse pointer and never got an override, while every control around them — the
   * steppers, DIVE, SURFACE, the icons — was raised to 44px in this block. They set the
   * stake and start an automatic run, so they are the chips it costs money to mis-tap.
   */
  .chips button { min-height: 44px; }
  /* The control that opens the verifier on a past round — a real target in a list of forty,
     so it gets the floor the rest of this block gives every other control. */
  button.board-seed { min-height: 44px; padding: 0 4px; }
  .icon { width: 44px; height: 44px; }
  .stepper button { min-width: 44px; min-height: 44px; }
  .stepper input { min-height: 44px; }
    #dive, #surface { min-height: 60px; }
  /* THE SECOND SEAT'S CONTROLS TOO. Written into the block that already exists rather than
     a second pointer:coarse block above it — layout-check reads the FIRST such block by
     name to prove these three selectors are in it, so an earlier duplicate makes the check
     pass against the wrong rules while the real ones go unread. It caught exactly that. */
  /*
   * AND THE ROOM'S OWN CONTROLS, which this block never covered. Measured on a 390px touch
   * viewport with the room open: the composer and its Send button were 22px tall — half the
   * floor every other control in this block is held to, on the one control a player types
   * into. It went unnoticed because chat was two presses deep inside the wallet until the
   * deck put it on the dock; the defect was always there, on a surface nobody reached.
   *
   * Written into THIS block rather than a new one, for the reason the note above gives:
   * layout-check reads the FIRST `pointer: coarse` block by name, so an earlier duplicate
   * makes the check pass against the wrong rules while the real ones go unread.
   */
  .chat-form input, .chat-form button { min-height: 44px; }
  /* The board's window and view switchers, and the deck's two doors, are all `.small-btn`
     in a `.row` — 32px by default, which is fine under a mouse and not under a thumb. */
  .board-periods .small-btn, .board-views .small-btn, .deck-bar .small-btn,
  .board-more { min-height: 44px; }
}

@media (max-width: 640px) {
  /*
   * THE DENOMINATIONS AND THE AUTO-BET PRESETS GO ON A PHONE.
   *
   * The stepper already halves, doubles and MAXes; auto-surface is its own field. Four
   * chips plus AUTO ×10/×25/×50 under those is what made the dock a wall of buttons on
   * a 375px screen. Markup stays — desktop still has them — so this is display, not a
   * second product.
   */
  #idle-panel .chips.stakes,
  #idle-panel .chips.autobet { display: none; }
  /* Measured on a 375px phone: the top bar laid out to 488px, putting SOUND and HOW IT
     WORKS entirely off-screen — elementFromPoint returned nothing at either centre — and
     clipping the session net/clock at 380px. Those are not decorations. The info button is
     the only route to the fairness panel, the stats and the balance reset, and RTS 2E/13C
     requires the net position and session clock to be ON SCREEN, not merely in the DOM.
     The wordmark is the one thing in that row nothing depends on, so it is what goes. */

  /* THE TAG IS NOT THE WORDMARK, and hiding it here was the same decision applied to two
     things that are not alike.
     The reason above — "the one thing in that row nothing depends on" — is true of the
     name and false of the tag. The tag is the only text on screen that says what the
     player is risking; in this build it reads "play money · no real stakes", and in a
     real-money build it is where the licence mark and the mainnet/testnet warning go.
     Hiding it below 640px made the statement of the stakes desktop-only, on the viewport
     most gambling is done from. Measured at 375px it was display:none with the whole
     .brand box collapsed to 9x9 — a dot in the corner where the product's name and its
     regulatory mark used to be.
     It gets its own full-width row rather than a column, so it cannot drive the width of
     column one and re-create the overflow the rules above exist to prevent. */

  /* ONE ROW on a phone, now that the session readout and the stakes tag have both left the
     top bar. It needed three: one row could not hold the mark, the cashier, the session
     net/clock and two icons at 375px, and adding the cashier had pushed SOUND and HOW IT
     WORKS off the right edge twice. With two of the five gone the mark, the cashier and the
     icons fit across one line.

     The `tag` and `session` areas are deliberately not left behind. A grid template that
     names an area no element claims still reserves the track, which is how the overflow
     these rules exist to prevent would come back — silently, and only on a phone.
     tools/layout-check.mjs asserts the template names neither. */
  #topbar {
    /*
     * SYMMETRIC, so the cashier is at the FRAME's centre and not merely inside the middle
     * column. This was `auto 1fr auto`: the two outer tracks sized to their own contents,
     * so the brand and the icon cluster were different widths and the middle track's centre
     * was wherever that difference put it. `justify-content: center` below then centred the
     * cashier within an off-centre column, which is why it drifted as the screen changed —
     * the base layout has said `1fr auto 1fr` for exactly this reason since it was written.
     *
     * minmax(0, 1fr) rather than 1fr: a flexible track refuses to shrink below its content
     * by default, so on a narrow phone the two outer tracks would push the whole bar wider
     * than the viewport instead of giving way.
     */
    /*
     * EQUAL OUTER TRACKS, SAME AS THE BASE. `max-content` on the icons (and `minmax(0, auto)`
     * on the cashier) made the right column as wide as the two buttons and the middle
     * column as wide as the balance, so the cashier's centre was wherever that difference
     * put it — it drifted left as the balance grew and was never the frame centre on a
     * phone. minmax(0, 1fr) on BOTH sides keeps the middle track at the geometric centre;
     * the cashier already ellipsises below 420px, which is what absorbs a fat balance
     * rather than shoving the bubble off-centre.
     */
    grid-template-columns: auto 1fr;
    grid-template-areas: "brand end";
    row-gap: 4px; column-gap: 6px;
    padding-left: 10px; padding-right: 10px;
  }
  .brand { grid-area: brand; }
  .top-right { grid-area: end; justify-self: end; }
  /*
   * FOUR 44px ICONS DO NOT FIT. Trophy and Me joined mute and info; at 375 the
   * cluster is ~188px, the symmetric 1fr columns copy that width onto the empty
   * mark side, and the bar overflows — wallet clipped, icons off the right edge.
   * Mute is the device volume on a phone. Info is the glowing mark, now a 44px
   * target. What remains is the original two-icon budget: the board and you.
   */

  .brand .mark {
    width: 28px; height: 28px;
    background: transparent; box-shadow: none; animation: none;
    display: grid; place-items: center;
  }
  .brand .mark::after {
    content: "";
    width: 9px; height: 9px; border-radius: 50%;
    background: var(--cyan);
    box-shadow: 0 0 14px var(--cyan);
    animation: none;
  }
    
  /* The cashier's own narrow-screen sizing lives at the BOTTOM of this file, after the
     base .purse-bal / .wallet-btn rules. It used to be here, where it did nothing: the
     base rules are defined further down and win on source order at equal specificity, so
     three lines of measured phone tuning had never once applied. The pointer:coarse block
     already carries this exact warning; these rules were written above it anyway. */
  .top-right { gap: 6px; }
  .wallet { padding: 5px 9px; }
  .row { flex-direction: column; }
        #dock { padding: 0; }
  .fair { grid-template-columns: 1fr; }
  #wallet-modal { padding: 10px; }
  .wallet-sheet { padding: 16px 14px; border-radius: 14px; }
  .you-card { padding: 10px; gap: 10px; margin-bottom: 10px; }
  .you-mark .board-avatar svg { width: 32px; height: 32px; }
  .you-name { font-size: 14px; }
}


/*
 * THE CONNECTION LINE. Same shape as the watching pill — it is the same kind of thing, a
 * sentence about the state of the table rather than a control — and amber, because this one
 * is the player's problem to know about rather than merely to read.
 */
.link-down {
  color: var(--amber);
  border-color: rgba(255, 190, 90, .45);
  background: rgba(60, 40, 10, .55);
}
/* A limit the player set, still in force. Deliberately NOT amber: this is not a fault and
   it is not the operator's doing — it is a control working, so it reads as calm rather than
   as an alarm. The same shape as the line above it, in the ink the rest of the panel uses. */
.limit-stop {
  color: var(--ink);
  border-color: rgba(140, 220, 255, .38);
  background: rgba(10, 32, 48, .72);
}

/*
 * AND THE HEADING THAT HOLDS IT WRAPS, because the picker is as wide as its widest option.
 *
 * A <select> sizes itself to its longest entry — here a round number, a multiplier and a
 * timestamp — and it sat inline after "Check it yourself —" in an h4. Measured at 375px it
 * came out 330px against 325px of sheet, and the whole ⓘ panel scrolled SIDEWAYS: 470px of
 * scroll width in a 325px box, a modal that pans on a phone because one control in it is
 * six pixels too wide. Flex-wrap puts the picker on its own line when the line is short,
 * and changes nothing on a desktop where all three fit.
 *
 * Scoped with :has() rather than a class, because .fair-h is the heading of every panel in
 * this sheet and only this one carries a control.
 */
.fair-h:has(select) {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px;
}
/* The verification archive picker. Every settled dive stays checkable, so this needs to
   look like a control rather than a label. */
#f-which {
  max-width: 100%;
  font-family: var(--mono); font-size: 12px;
  background: rgba(8, 26, 40, .9); color: var(--ink);
  border: 1px solid var(--edge-ctrl); border-radius: 6px;
  padding: 3px 6px; margin: 0 4px; min-height: 28px;
}
@media (pointer: coarse) { #f-which { min-height: 44px; } }


/* ------------------------------------------------------------- the harm register
   Inside the ⓘ panel, styled like the rest of it rather than as a feature. A limits
   control that shouts is a limits control people close; one that reads like the section
   above it is one they scroll into. */
.limits-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 8px 0; }
.small-btn {
  background: var(--edge-hair);
  border: 1px solid var(--edge-ctrl);
  color: var(--ink); border-radius: 8px; padding: 7px 13px; cursor: pointer;
  font-family: var(--mono); font-size: 11px; letter-spacing: .09em;
  min-height: 32px;
  transition: background .16s, border-color .16s, color .16s;
}
.small-btn:hover { background: rgba(140, 220, 255, .2); }
/* THE ROW THE WALLET TABS WERE MEASURED AGAINST. `.wtabs button`'s note says those tabs
   "answered the pointer with nothing, while the .small-btn row directly beneath them in the
   same sheet lights up" - and the fix it got was a hover AND a press, which left the row it
   was compared to with less than it. Fifteen of index.html's 51 pressable controls wear this
   class: both deck tabs, the six board switchers, Save limits, the limits sheet's Back,
   Exclude myself, Sign out, Continue playing and both consent buttons. The transition above names background/border-color/color
   and not transform, so this lands in the frame the finger did. */
.small-btn:active { transform: translateY(1px); }
.small-btn.danger {
  background: rgba(255, 107, 82, .1);
  border-color: rgba(255, 120, 110, .38); color: #ffb3ad;
}
.small-btn.danger:hover { border-color: rgba(255, 120, 110, .7); color: #fff; }
.small-btn[disabled] { opacity: .45; cursor: default; }
@media (pointer: coarse) { .small-btn { min-height: 44px; } }
#l-exclude-for {
  font-family: var(--mono); font-size: 12px;
  background: rgba(8, 26, 40, .9); color: var(--ink);
  border: 1px solid var(--edge-ctrl); border-radius: 6px;
  padding: 5px 8px; min-height: 32px;
}
@media (pointer: coarse) { #l-exclude-for { min-height: 44px; } }
/* A pending change is news; a landed one is not. */
#l-pending:not(:empty) { color: #ffd7a8; }
#l-excluded:not(:empty) { color: #ffb3ad; }
#l-note:not(:empty) { color: #9fe6c0; }

/* The reality check. Same furniture as #no-server / #signed-out — a centred sheet over
   everything — because it is the same kind of thing: play cannot continue past it. */
/* The terms sheet is the same furniture for the same reason, and the selectors are shared
   rather than copied: two rules that must stay identical and are written twice are two rules
   that eventually differ, and the one that differs is the one nobody is looking at. */
#reality, #terms {
  position: fixed; inset: 0; z-index: 40;
  display: flex; align-items: center; justify-content: center;
  background: var(--scrim); backdrop-filter: blur(6px);
  padding: 24px;
}
/*
 * AND NEITHER OF THEM BLOCKS CASHING OUT, WHICH IS WHAT THE REALITY CHECK SAYS OF ITSELF.
 *
 * index.html states of that dialog in as many words that "It does NOT block cashing out —
 * nothing here can". Both are full-frame scrims and #dock is z-index 5, so while either was
 * up SURFACE was behind it and untappable: the promise was true of the code that reads the
 * button and false of the pixels. The scrim stops taking presses, the sheet keeps them, and
 * the sheet moves off the bottom of the frame so it is not over the button.
 *
 * BOTH, AND THE FIRST VERSION OF THIS FIX ONLY DID #reality — three lines above the note
 * saying these selectors are shared "rather than copied: two rules that must stay identical
 * and are written twice are two rules that eventually differ, and the one that differs is
 * the one nobody is looking at." Written, then done anyway, in the same file.
 *
 * THE TERMS SHEET NEEDS IT AS MUCH. It rises when the operator publishes a new version,
 * which can happen while a player has a stake on the hull, and gates.js is explicit that
 * terms gate WAGERS and not the way out: "NOT ON THE WITHDRAWAL PATH ... and must not grow
 * one." A surface is not a wager. The server would have taken it; the scrim was what
 * refused it.
 */
#reality, #terms { pointer-events: none; align-items: flex-start; padding-top: 10vh; }
#reality .sheet, #terms .sheet { pointer-events: auto; }
/* EXCEPT THE ONE NUMBER THAT MUST DIFFER, and it is the whole defect this line fixes.
   Sharing the rule above gave the terms sheet z-index 40, over #wallet-modal at 25 and
   #modal at 20 — so a funded player could not open the cashier, a self-excluded player could
   not reach the money ws.js admits them to collect, and #terms-ok sat on the withdrawal
   address field's pixels, recording a permanent legal acceptance from a misdirected click.
   18 puts it under both.

   AND THE REALITY CHECK HAS THE SAME BUG, which is how it got the same fix. index.html says
   of it, in as many words, "It does NOT block cashing out — nothing here can, and the gate is
   deliberately absent from withdrawalGates." At z-index 40 over a cashier at 25 it blocked
   cashing out, and had since it was written. The server is right in both cases; the
   stylesheet was the thing making the claim false. Acknowledging is still what clears the
   wager gate — that is server-enforced and unchanged — and a player who puts either sheet
   down is refused the next bet and shown it again. */
#terms, #reality { z-index: 18; }
#reality.hidden, #terms.hidden { display: none; }
#reality .sheet, #terms .sheet { max-width: 30rem; }
#terms a { color: var(--cyan); }

/* ------------------------------------------------------------------ the cashier */

/*
 * ONE CONTROL, NOT TWO SITTING NEAR EACH OTHER.
 *
 * The balance and the way into the wallet were separate pills with a gap between them, and
 * they are not separate facts: the number is what you can stake and the button is where you
 * change it. Joined into a single unit with a divider, so the header reads as one cashier
 * rather than as two widgets that happen to be adjacent.
 */
.top-centre {
  display: flex; align-items: center;
  justify-self: end;
  pointer-events: auto; position: relative;
  background: none;
  border: 0; border-radius: 0;
  gap: 8px;
  /* NO `overflow: hidden` - IT WAS EATING THE FOCUS RING ON THE CASHIER'S FRONT DOOR.
     It was here to round #wallet-btn's square right corners to the group's 12px. But overflow
     clips to the PADDING box, and the button's 81.98x40 border box shares that box's top,
     right and bottom edges exactly - measured y 16..56 for both, and the right edges equal at
     every width, because the button is the last flex item in a stretch container with no
     padding of its own.
     :focus-visible wants outline-width 2 + outline-offset 2 = 4px of clearance and had 0, so
     4px of each of those three runs was clipped and only the left run survived: 80 of the
     ring's 536 painted px2, a 2x40 bar sitting on the divider hairline, where it reads as the
     divider getting brighter. WCAG 2.4.7, on the control that opens deposits and withdrawals.
     NOT FIXED WITH A NEGATIVE outline-offset. Pulling the ring inside the button to dodge the
     clip lands #6fe6ff on the button's own gradient, where it measures 1.89:1 against #3aa0ff
     and 2.79:1 against #1f7fe0 - both under WCAG 1.4.11's 3:1. Outside, it measures 12.84:1
     over this element's composited fill and 14.17:1 over the sky sampled behind it. The clip
     is what has to go; the offset was right.
     The rounding moves to the half that actually needs it - see .top-centre .wallet-btn. */
  transition: border-color .16s;
}

/* The balance reads as one control with the asset it is denominated in, because those two
   facts are useless apart: "1,240.00" of WHAT is not a balance. */
.purse { position: relative; }

/* The border and the corners belong to the group above now; this is its left half. */
.purse-bal {
  display: flex; align-items: center; gap: 6px;
  background: transparent; border: 0; border-radius: 0;
  padding: 4px 0; min-height: 32px;
  font: inherit; color: var(--ink); cursor: pointer;
}
.purse-bal .val {
  font-family: var(--mono); font-weight: 700; font-size: 15px;
  font-variant-numeric: tabular-nums; color: var(--green);
}
/* The divider between the two halves, and it is a border on the button so it cannot end up
   doubled if either side gains one later. */
/* The divider between the two halves, and it is a border on the button so it cannot end up
   doubled if either side gains one later. The right corners are rounded HERE now rather than
   by a clip on the group: 12px of outer radius minus the group's own 1px border is 11px of
   inner radius. The left half needs none - it has no fill, so the group's own background is
   what draws that corner. */
#wallet-btn .purse-bal { pointer-events: none; }
/* The header purse is a READOUT now, not a control: no hover lift, no pointer. */
#play-purse { cursor: default; }
/* The group carries the hover, because the group is the control. */
.wallet-btn:hover { color: var(--ink-hi); }
/*
 * The asset chooser, in the wallet sheet where the deposits it governs live.
 *
 * AND IT DRAWS ITS OWN CHROME, because .purse-bal has two wearers and only one of them is
 * inside .top-centre. The rule above gives the background, the border and the corners up on
 * the grounds that the group owns them - true in the header, where there IS a group. In the
 * sheet there is no group, so the strip landed on a button with no fill, no edge and no
 * hover of any kind: a control rendering as a line of text beside a coin, on the one surface
 * where a player moves real money. Scoped to .asset-row, so the header half of the class is
 * untouched and the comment above stays true of it.
 *
 * THE TWO WEARERS ARE NOT THE SAME KIND OF THING. #play-purse is a <div> readout and
 * layout-check.mjs asserts it must stay one and must not contain a button; this is a
 * <button> that opens a listbox. One class, a readout and a control, machine-enforced as
 * different.
 *
 * FIELD CHROME, NOT HEADER CHROME. The figures are .wtabs button's, unchanged - the tab row
 * this trigger sits directly above - because the sheet hovers on BACKGROUND everywhere
 * (.wtabs, .small-btn, .asset-menu button) while .top-centre hovers on border-color. Copying
 * the header would make the trigger the odd one out among every control beside it.
 *
 * IT DECLARES NOTHING BUT CHROME. At (0,2,0) it outranks the 640px and 420px cashier blocks
 * at the foot of this file whatever the source order, so padding, gap and min-height are
 * deliberately absent: declaring them would strand that phone tuning for this button for
 * good. It also outranks :focus-visible's `border-radius: inherit`, which is why the focus
 * ring stops squaring off around a rounded button and starts tracing it.
 */
.asset-row .purse-bal {
  background: rgba(8, 26, 40, .8);
  border: 1px solid rgba(140, 220, 255, .18); border-radius: 10px;
  transition: background .16s;
}
.asset-row .purse-bal:hover { background: rgba(140, 220, 255, .08); }
.asset-row .purse-bal:active { transform: translateY(1px); }
/* The mainnet emphasis, on the cashier's note now that the topbar badge is gone. Amber,
   because "real funds" is not a neutral fact and should not read like the rest of a sheet. */
#wallet-net.live { border-color: rgba(255, 176, 82, .55); color: #ffcf9b;
                   background: rgba(255, 176, 82, .12); }
/*
 * AND #signed-out, WHICH HAD NONE OF THIS.
 *
 * The note on the reality check says these three share their furniture, and #signed-out was
 * never given any: no position, no inset, no centring, no backdrop. So the panel that tells
 * a player their money is behind a door laid itself out in normal document flow — a box in
 * the top-left corner, over the stake field and the Bet button, with the table playing on
 * behind it. It is the first thing a signed-out player sees and the only one of the three
 * that a real deployment shows every day.
 *
 * z-index BELOW #no-server on purpose: losing the socket outranks needing to sign in, and
 * when both are true the one to read is the one about the connection.
 */
#no-server, #signed-out {
  position: fixed; inset: 0; display: grid; place-items: center;
  background: var(--scrim); backdrop-filter: blur(6px); padding: 24px;
}
#no-server { z-index: 60; }
#signed-out { z-index: 55; }
#no-server .sheet, #signed-out .sheet { max-width: 30rem; }
.asset-row { display: flex; align-items: center; gap: 10px; margin: 2px 0 10px; }
.asset-lbl { font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
             color: var(--ink-dim); }
.purse-bal .val { font-family: var(--mono); font-size: 15px; font-weight: 700; letter-spacing: .02em; }
.purse-bal .coin {
  display: grid; place-items: center; width: 20px; height: 20px; border-radius: 50%;
  font-size: 12px; font-weight: 700; color: #04121c; background: var(--coin, #2775ca);
}
.purse-bal .chev { font-size: 10px; color: var(--ink-dim); }

.asset-menu {
  position: absolute; top: calc(100% + 6px); left: 0; min-width: 220px;
  margin: 0; padding: 6px; list-style: none; z-index: 30;
  background: rgba(6, 20, 32, .98);
  border: 1px solid var(--glass-hi); border-radius: 12px;
  box-shadow: 0 18px 40px rgba(0, 0, 0, .5);
}
.asset-menu li { margin: 0; }
.asset-menu button {
  display: flex; align-items: center; gap: 10px; width: 100%;
  background: none; border: 0; border-radius: 8px; padding: 9px 10px;
  font: inherit; color: var(--ink); text-align: left; cursor: pointer; min-height: 44px;
  transition: background .16s;
}
.asset-menu button:hover, .asset-menu button:focus-visible { background: rgba(20, 60, 88, .8); }
.asset-menu button:active { transform: translateY(1px); }
.asset-menu .coin {
  display: grid; place-items: center; width: 22px; height: 22px; border-radius: 50%;
  font-size: 12px; font-weight: 700; color: #04121c; flex: none;
}
.asset-menu .sym { font-weight: 700; letter-spacing: .06em; }
.asset-menu .amt { margin-left: auto; font-family: var(--mono); font-size: 13px; color: var(--ink-dim); }
/* An asset that cannot yet be settled says so rather than pretending. */
.asset-menu .soon { font-size: 9.5px; letter-spacing: .1em; color: var(--amber); }

.wallet-btn {
  display: flex; align-items: center;
  background: linear-gradient(160deg, rgba(42, 60, 73, .8), rgba(16, 30, 40, .9));
  border: 1px solid rgba(174, 195, 209, .2); border-radius: 10px; padding: 0 12px; min-height: 32px;
  font: inherit; cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(216, 242, 255, .06), inset 0 -2px 4px rgba(0, 0, 0, .24);
  transition: border-color .16s, background .16s, box-shadow .16s;
}
.wallet-btn:hover { border-color: rgba(145, 206, 235, .48); box-shadow: inset 0 1px 0 rgba(216, 242, 255, .18), 0 0 12px rgba(111, 230, 255, .06); }
/* The transition above names `filter`, so this is not smeared with it. */
.wallet-btn:active { transform: translateY(1px); }

/* ------------------------------------------------------------------ wallet panel */

#wallet-modal { position: fixed; inset: 0; z-index: 25; display: grid; place-items: center;
  background: var(--scrim); backdrop-filter: blur(6px); padding: 24px; }
#wallet-modal.hidden { display: none; }
.wallet-sheet {
  position: relative; width: min(520px, 100%); max-height: 88vh; overflow: auto;
  background: var(--sheet-bg); border: 1px solid var(--glass-hi);
  border-radius: 16px; padding: 22px;
}
/* The cashier's labels are labels. Every other field label in the product is uppercase -
   the dock's, the fairness panel's, the asset picker's, the statement's - and these were
   the one set that was not, so the surface a player deposits real money through read its
   own field names as body text. The size is left at 11px deliberately: this is a money
   form, and the rest of the family is smaller than it should be here rather than this
   being larger than it should be. Tracking goes to .14em because uppercase needs it. */
.wallet-sheet label { display: block; margin: 12px 0 6px; font-size: 11px;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-dim); }
.wallet-sheet input {
  width: 100%; background: rgba(4, 14, 22, .9); color: var(--ink);
  border: 1px solid rgba(140, 220, 255, .22); border-radius: 8px;
  padding: 10px 12px; font-family: var(--mono); font-size: 13px; min-height: 44px;
}
/*
 * AND ON A TOUCH SCREEN THEY ARE 16px, WHICH IS AN iOS RULE RATHER THAN A TASTE.
 *
 * Safari zooms the layout when a field under 16px takes focus and does not zoom back out,
 * so a player who taps the deposit address or a limit box is left on a magnified page with
 * no way back. The viewport meta is deliberately left alone: maximum-scale would stop the
 * zoom by disabling pinch-zoom for everybody, which is a worse answer to the same problem.
 *
 * DECLARED HERE RATHER THAN IN THE COARSE BLOCK ABOVE, because `.wallet-sheet input` is
 * declared at this line and `.fair input` above it — equal specificity, later wins, and the
 * rule written up there computed 13px in a live measurement. This file has now recorded
 * that trap four times.
 */
@media (pointer: coarse) {
  .fair input, .wallet-sheet input { min-height: 44px; font-size: 16px; }
  /* The wrapping readout takes the same size as the boxes it sits under — not for the iOS
     reason, which cannot apply to something that never takes focus, but because a panel
     where one value is two-thirds the size of the rest reads as an error. */
  .fair .fair-val { font-size: 16px; }
}

/* Says what network this is, every time the wallet is opened. A player must never have to
   guess whether the funds on screen are real. */
.net-note {
  background: rgba(232, 178, 58, .12); border: 1px solid rgba(232, 178, 58, .4);
  color: var(--amber); border-radius: 10px; padding: 8px 12px;
  font-size: 12px; letter-spacing: .04em; margin-bottom: 14px;
}
.net-note.live { background: rgba(255, 96, 88, .12); border-color: rgba(255, 96, 88, .5); color: #ff9a94; }

/*
 * THEY WRAP RATHER THAN RUN OFF THE EDGE.
 *
 * Four tabs fitted a 375px phone and five did not: adding Table pushed Chat half off the
 * right-hand side, where it was still focusable and still in the tab order — a control that
 * exists, answers to the keyboard, and cannot be seen or tapped. A row of tabs that
 * silently loses its last member as the list grows is a layout that punishes the next
 * addition, so it wraps: `flex-wrap` with a floor on the button width, which on a narrow
 * screen gives two rows and on a wide one still gives the single row it always had.
 *
 * `min-width` rather than a media query, because the thing that decides is how many tabs
 * there are against how much room, and neither of those is a viewport.
 *
 * AND THE WRAPPED ONE IS NOT A BANNER. Wrapping with `flex: 1 1 84px` gave a 375px phone
 * three tabs of 89px and then Board alone across all 281 — a full-width filled control
 * directly under a row of tabs, which reads as the sheet's primary action rather than as
 * the fourth of four. A grid wraps the same way and keeps every tab one column wide: 110px
 * fits four across the 476px the sheet has on a desktop and two across the 281px it has on
 * a phone, so the phone gets a square 2x2 and nothing is singled out.
 */
.wtabs {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: 6px; margin-bottom: 6px;
}
.wtabs button {
  min-width: 0;
  background: rgba(8, 26, 40, .8); color: var(--ink-dim);
  border: 1px solid rgba(140, 220, 255, .18); border-radius: 10px;
  padding: 10px; min-height: 44px; font: inherit; font-size: 13px; cursor: pointer;
}
/*
 * THEY ANSWERED THE POINTER WITH NOTHING, while the .small-btn row directly beneath them in
 * the same sheet lights up — so Deposit / Withdraw / History / Board read as labels rather
 * than controls, next to things that read as controls.
 */
.wtabs button { transition: background .16s, border-color .16s, color .16s; }
.wtabs button:hover { background: rgba(140, 220, 255, .08); color: var(--ink); }
.wtabs button:active { transform: translateY(1px); }
/*
 * AND "CURRENT" IS ONE COLOUR IN ONE PRODUCT. This was a navy fill while every other
 * selected-state in the interface is the cyan wash — the deck tabs, the board views, the
 * sign-in page's own tabs. Three ways of saying the same thing is two too many.
 */
.wtabs button[aria-selected="true"] { background: var(--glass-hi); color: var(--ink);
  border-color: var(--edge-live); }

/* A neutral action. `.reset` is deliberately alarming — it is for topping up and erasing —
   and copying an address or queueing a withdrawal is neither of those things. */
.btn-quiet {
  margin-top: 12px; min-height: 44px;
  background: rgba(20, 60, 88, .8); color: var(--ink);
  border: 1px solid var(--edge-ctrl); border-radius: 10px;
  padding: 10px 16px; cursor: pointer;
  font-family: var(--mono); font-size: 11px; letter-spacing: .1em;
  transition: border-color .16s;
}
/*
 * NOT WHILE DISABLED. #wd-submit and #stmt-more disable themselves in flight, and brightening
 * under the cursor is worse than looking unchanged: it actively says "live" about a control
 * that is refusing.
 */
.btn-quiet:hover:not(:disabled) { border-color: rgba(140, 220, 255, .65); }
/* Carrying the hover's :not(:disabled) for the same reason the note above gives: #wd-submit
   and #stmt-more disable themselves in flight, and a control that appears to take a press it
   is refusing is worse than one that looks unchanged. */
.btn-quiet:active:not(:disabled) { transform: translateY(1px); }
.btn-quiet:disabled { opacity: .45; cursor: default; }
/* The signed-out panel's Sign in is an ANCHOR, not a button: it navigates, and a button
   that navigates loses the middle-click, the long-press and the "open in a new tab" a
   player may well want on a login page. So it needs the parts a button gets for free. */
a.btn-quiet { display: inline-flex; align-items: center; text-decoration: none; }
/*
 * AND THE PRIMARY HAD NO HOVER AT ALL, which is the sort of thing nobody reports and
 * everybody feels. `border: 0` here wins on source order over the border-color hover above,
 * so the only response those buttons had was repainting a border with no width.
 *
 * It is the three that matter most: "Sign in to play" — the one conversion control a watcher
 * ever sees — "Request withdrawal", and the signed-out panel's "Sign in", which sits in the
 * same row as a plain .btn-quiet "Reload" that DOES respond. Side by side, one of them
 * looked broken.
 *
 * The swap is the same gradient the consent sheet and the sign-in page already use.
 */
.btn-quiet.primary { background: linear-gradient(180deg, #3aa0ff, #1f7fe0); color: #04121c;
  border: 0; font-weight: 700; transition: background .16s; }
.btn-quiet.primary:hover { background: linear-gradient(180deg, #4aaaff, #2a89ea); }

/* Must come AFTER the cashier rules above: an equal-specificity selector earlier in the
   file loses on source order, which is why the first attempt left these at 40px. */
@media (pointer: coarse) {
  .purse-bal, .wallet-btn { min-height: 44px; }
  .icon { width: 44px; height: 44px; }
  /* The close control is the one every modal needs a finger to hit. */
  .wallet-sheet .close, #modal .close { width: 44px; height: 44px; }
}

/*
 * The 375px sizing that was stranded earlier in the file, moved down here for source order.
 *
 * IT MUST STAY ABOVE THE 360px BLOCK BELOW. Media queries add no specificity, so at 320px
 * BOTH match and the later one wins — and I appended this one after the 360px block, which
 * silently killed every declaration in it and left the 21px of clipping exactly where it
 * was. The same defect this file's own comment twenty lines up warns about, recreated
 * between two of the blocks that comment was written for. layout-check compares media
 * rules against BASE rules and never against each other, so it saw nothing; it does now.
 */
@media (max-width: 640px) {
  .purse-bal { padding: 4px 0; gap: 6px; }
  .purse-bal .val { font-size: 13.5px; }
  .wallet-btn { padding: 0 10px; font-size: 12px; }
}

/*
 * 320px, WHICH THE 640px BLOCK ABOVE DOES NOT ACTUALLY REACH.
 *
 * That block's comment says it fixed SOUND and HOW IT WORKS being off-screen, and it did —
 * at 375. Measured at 320x568 the info button was still clipped by 21px and so was the
 * session readout, which is the RTS 2E/13C display, not a decoration. layout-check has
 * 320x568 in its own list of viewports and only ever used it for the history strip.
 *
 * The row needs about 340px: mark 9 + cashier 204 + icons 94 + gaps and padding. The
 * 44px touch targets are the one thing that must NOT give — that is the accessibility
 * floor and shrinking a button to make a layout fit is trading one failure for another —
 * so the cashier gives instead.
 */
/*
 * THE CASHIER'S TIGHT SIZING, FROM 420 RATHER THAN 360.
 *
 * 375, 390 and 414 are the three commonest phone widths and all three sat above this block,
 * so they kept the full-size figure and padding on the bar that has least room for it. The
 * measurement that moved it is in the grid-template note above.
 */
@media (max-width: 420px) {
  .purse-bal { padding: 8px 6px; gap: 4px; min-width: 0; }
  .purse-bal .val { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .purse-bal .val { font-size: 12.5px; }
  .purse-bal .chev { margin-left: 1px; }
  .wallet-btn { padding: 0 8px; font-size: 11.5px; }
  #topbar { column-gap: 4px; }
  .icons { gap: 4px; }
}

/* The provenance line in the fairness panel. Green states a checkable ordering; red states
   that nothing here proves one — which is a finding, not a missing feature, so it is
   coloured like one rather than left off the panel. */
/* AND THE HASH IN IT BREAKS. This sentence carries a 64-character chain head inline, which
   has no break opportunity in it anywhere: at 375px the word ran to 428px inside a 263px
   column and made the whole ⓘ panel pan sideways — a modal that scrolls horizontally on a
   phone, from one unbreakable word. `anywhere` rather than `break-word` because the two
   differ exactly here: break-word will not shrink the line box that MEASURES the paragraph,
   so the min-content width stays 428px and the overflow survives. */
.provenance { border-left: 2px solid var(--glass-hi); padding-left: 10px;
              overflow-wrap: anywhere; }
.provenance.ok  { border-left-color: var(--green); }
.provenance.bad { border-left-color: var(--amber); color: var(--amber); }

/* ------------------------------------------------------------------ the table */

/* Who else is in this round. Left side: the right belongs to the pressure dial and the
   history strip, and the depth tape on the far left is a ~34px instrument.

   Deliberately NOT a leaderboard. No ranking by winnings, no "biggest win", no celebration
   of anybody's result — those are the patterns the rest of this interface refuses, and a
   list of other people's wins is the most efficient version of all of them. It shows who
   is in, what they staked, and where they got out. That is what makes a shared hull
   legible, and it is already in the settlement everybody can recompute. */
/*
 * THE ROSTER MOVES TO THE RIGHT, under the session readout.
 *
 * It sat at left:46px, which put it directly on top of the depth tape — the tape's rail is
 * at max(26px, 2.8vw) and its zone names ran from rail+12 to about rail+80, so "1 STILL
 * DOWN OF 1" was printed across SUNLIGHT and TWILIGHT at every viewport. Two layers, one
 * column, neither aware of the other: the same failure the recent-dives strip had against
 * the pressure dial, on the opposite side of the screen.
 *
 * RE-MEASURED AFTER THE TAPE WAS CALMED. The seam-column zone names are gone; the only name
 * left is a caption docked under the caret. The geometry a DOM layer has to stay off is now
 * a mark gutter reaching rail+17 and ONE text column at rail+22, whose widest string is
 * "11,000 m" — eight characters, 56.0 px at 390x844, right edge x=104.
 *
 * The right-hand side is free now that the dial and the strip have both left it. The top
 * offset clears the top bar rather than guessing: #topbar is padding + a 44px control row,
 * which lands its lower edge near 120px, and TOPBAR_BAND in tools/layout-check.mjs is the
 * same number asserted from the other side.
 */
#table {
  position: absolute;
  right: calc(18px + env(safe-area-inset-right));
  /*
   * BELOW THE MULTIPLIER, not beside it. At 132px it cleared the top bar and then ran
   * straight into the headline number, which is centred at 19vh and is up to 104px tall —
   * so "1 STILL DOWN OF 1" was set against the right-hand digits of "3.96×". 28vh clears
   * the number's lower edge at every viewport the tape is tested at, and scales with the
   * height instead of being a pixel that happens to work on one screen.
   */
  top: calc(28vh + env(safe-area-inset-top));
  align-items: flex-end;
  text-align: right;
  width: min(210px, 34vw);
  display: flex; flex-direction: column; gap: 3px;
  z-index: 4; pointer-events: none;
  max-height: min(42vh, calc(100vh - 340px));
  overflow: hidden;
}
/* ------------------------------------------------------------------------- the rail
 *
 * WHY IT EXISTS. At 1440x900 this game was a column down the middle of the screen with two
 * thirds of the frame empty, while the three things that make a crash game social — who is
 * in the round, what they staked, and what everybody is saying — were either a 210px
 * whisper in the corner or behind a tab inside the wallet modal, which covers the game you
 * opened it from. A table you cannot see and a room you cannot be in while playing.
 *
 * It is an OVERLAY, like the dock, not a column the scene has to make space for. The water
 * keeps the whole frame; the rail sits on top of the part of it that never had anything in
 * it. That also means no reflow of the canvas and no new geometry for the HUD to avoid.
 *
 * Gated in JS rather than by a media query, because the switch REPARENTS nodes — CSS
 * cannot move the chat log out of the wallet pane, and authoring it in both places is how
 * you end up with two logs that disagree.
 */
#rail {
  position: absolute;
  /*
   * BELOW THE TOP BAR'S BAND, which is 128px and is not a guess: #topbar is 14px of padding
   * above a 44px control row and layout-check asserts the same number from the other side.
   * The session net position and the elapsed clock live up there — RTS 2E and 13C — and are
   * the two figures on this screen that may never be covered. 96px put the rail on them,
   * which the suite caught before the pixels did.
   */
  /* 72 + 18: the bar's real lower edge plus the gutter every other edge in this layout
     uses - #topbar's padding, #dock's padding-left, the rail's own right and bottom. It was
     132, which cleared a band that had been measured at 128 and is 72. */
  top: calc(90px + env(safe-area-inset-top));
  right: calc(18px + env(safe-area-inset-right));
  bottom: calc(18px + env(safe-area-inset-bottom));
  /*
   * WIDER WHERE THERE IS ROOM, AND NEVER NARROWER THAN IT WAS.
   *
   * 304px was a fixed width for a panel that carries five columns, and the name column
   * resolved to 78px - so a twelve-character handle measured 94px and ellipsised, in the
   * one panel a player watches for a whole round. The rail only exists above 1024px wide,
   * and at 1024 there is genuinely no room to spare, which is why this floors at the old
   * number rather than replacing it: 26vw reaches 304 at 1169px and is clamped below that,
   * so nothing changes on a small laptop and a 1440px screen gets a rail it can read.
   */
  width: var(--rail-w);
  z-index: 5;
  display: flex; flex-direction: column; gap: 0;
  pointer-events: none;
  /* ONE BOARD. Feed and chat are sections of this plate, not two glass cards with a gap.
     Same fill / edge / radius as .panel. */
  background: var(--surface-fill);
  background-image: var(--surface-coat);
  border: 1px solid var(--glass-hi);
  border-radius: var(--surface-radius);
  box-shadow: var(--surface-lip);
  padding: 10px 10px 12px;
  overflow: hidden;
}
#rail > * { pointer-events: auto; }
/* TWO ROWS YOU CAN SCROLL, same instrument as the phone strip. A 304px column fits about
   five pills across; wrapping forty-eight of them was a wall that ate the rail. Newest
   still start at the left; the rest is a swipe. */
#rail-hist { flex: 0 0 auto; min-width: 0; }
#rail-hist #history {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(2, auto);
  grid-auto-columns: max-content;
  column-gap: 5px;
  row-gap: 4px;
  overflow-x: auto;
  overflow-y: hidden;
  margin-bottom: 0;
  overscroll-behavior-x: contain;
  /* The thumb sat on the pills. Swipe still works; the fade says there is more. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
#rail-hist #history::-webkit-scrollbar { display: none; width: 0; height: 0; }
/* Half the rail, not 38%: the feed is the reason the rail exists and the room can hold more
   people than the room can show. It scrolls, and the scrollbar is left alone rather than
   hidden — a list that scrolls without saying so is a list that looks like it ends. */
/*
 * SIZED TO WHO IS ACTUALLY IN THE ROUND, up to half the rail.
 *
 * `flex: 1 1 auto` made the feed take every pixel the room did not, so one player sat in
 * 200px of glass — the same defect the chat had below it, and the same state a table is in
 * on the day it opens. `0 1 auto` grows with the roster and stops at the ceiling the note
 * above describes; the chat's `margin-top: auto` puts the slack between them rather than
 * under everything.
 */
/*
 * AND IT IS A COLUMN, WHICH IT DID NOT HAVE TO SAY UNTIL IT HELD TWO THINGS.
 *
 * `display: flex` with no direction is a ROW. That was invisible while #rail-table held one
 * child; the moment the feed's disclaimer joined it — so that "Nothing here is a ranking"
 * would be on the screen a desktop player actually sees — the sentence laid out BESIDE the
 * roster, taking a share of a 304px column and pushing the feed into what was left, with
 * its own text clipped by the `overflow: hidden` above. #deck-table states its direction
 * one screen down and every other slot in the rail states its own; this was the omission.
 *
 * It was verified in a browser and the verification missed it, because the table was empty:
 * a row layout with one visible child and one empty one looks exactly like a column.
 */
#rail-table {
  flex: 0 1 auto; min-height: 0; max-height: 52%; overflow: hidden;
  display: flex; flex-direction: column;
}
/*
 * `.small` IS SCOPED TO `.sheet`, AND THE RAIL IS NOT IN ONE EITHER.
 *
 * The same trap the deck hit and records two hundred lines down: the class reads as a font
 * size and is not one, so the disclaimer rendered at the document's 16px in full --ink over
 * the water — a 35-word paragraph set larger than the roster it introduces. Same figures as
 * the sheet and the deck, so all three read as one product.
 */
#rail .small { font-size: 11.5px; color: #7f9fb2; line-height: 1.55; }
#rail #rail-table p { margin: 0 0 6px; }
/*
 * A ROOM NOBODY IS TALKING IN DOES NOT GET HALF THE RAIL.
 *
 * `flex: 1 1 auto` on both is right once there is something in each. On a quiet table it
 * gave the chat 240px of the column to hold one sentence — measured at 1440x900 with a bet
 * down: an empty box the height of the bet feed, beside the feed, on the widest screen the
 * game has. Which is exactly the state a table is in on the day it opens.
 *
 * `.quiet` is written by paintChat from the same emptiness the sentence inside it comes
 * from, so the two cannot disagree, and the moment somebody speaks the rail is back to the
 * layout the comment above describes.
 */
/* AND IT SITS AT THE FOOT OF THE COLUMN. Shrunk but left in place, the space it gave up
   opened UNDER it — a gap at the bottom of the rail, which reads as the layout having
   failed rather than as a quiet room. The feed above has a max-height it will not exceed,
   so somebody has to take the slack, and the right one is the empty box. */
#rail-chat.quiet { flex: 0 0 auto; margin-top: auto; }
/* `height: 15rem` is on the base .chat-log for the wallet sheet, where it is a fixed panel;
   in the rail it is a flex child and the quiet case has to unset it, not merely stop it
   growing. Measured: the class applied, the flex applied, and the log stayed 240px. */
#rail-chat.quiet .chat-log { flex: 0 0 auto; height: auto; min-height: 0; }
#rail-chat {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; gap: 6px;
  padding: 10px 2px 0;
  margin-top: 8px;
  border-top: 1px solid var(--glass-hi);
}
/*
 * `#chat-note`, NOT `#rail-chat #chat-note` - THE SAME BUG THIS FILE ALREADY RECORDS
 * TWENTY LINES DOWN for the composer and the Send button. The room has two homes and
 * ui.js MOVES the same three nodes between them: #rail-chat on a wide screen, #deck-chat
 * in the dock's drawer on a phone. Scoped to the rail, this caption reached exactly one of
 * them, so "The room." rendered at the document's 16px in the UI face - not mono, not
 * uppercase, not tracked, not dimmed - on every phone, directly above a log that was all
 * three. An id is unique; the ancestor bought nothing but the defect.
 */
#chat-note {
  font-family: var(--mono); font-size: 9px; letter-spacing: .18em;
  color: var(--ink-dim); text-transform: uppercase; margin: 0 0 2px;
}
#rail-chat .chat-log {
  flex: 1 1 auto; min-height: 0;
  background: transparent; border: 0; padding: 0;
}
/*
 * THE COMPOSER, LIKE THE BUTTON BESIDE IT, IS STYLED WHEREVER THE ROOM IS.
 *
 * Scoped to #rail-chat this reached only the desktop rail, so on a phone — where the deck's
 * drawer is the room's only home — the field fell back to the user agent's slab beside a
 * Send button that had just been given a fill. Half a styled form reads worse than none.
 */
.chat-form input {
  background: rgba(2, 10, 18, .7); color: var(--ink);
  border: 1px solid rgba(140, 220, 255, .22); border-radius: 8px;
  padding: 8px 10px; font-family: var(--mono); font-size: 12px;
}
.chat-form input::placeholder { color: var(--ink-dim); opacity: .8; }
.chat-form input:focus-visible { outline: 2px solid var(--cyan); outline-offset: 1px; }
/* The Send control takes the interface's own quiet-button treatment rather than the user
   agent's default, which rendered a bright grey slab against near-black water — the
   loudest thing in the rail belonging to the least important action in it. */
/*
 * `.chat-form`, NOT `#rail-chat .chat-form`. The rail is the desktop home of the room; on a
 * narrow screen the room lives in the deck's drawer, and there this rule did not reach — so
 * Send was the browser's grey slab and the composer an unstyled field, on every phone. One
 * rule for one control, in the place both homes read.
 */
.chat-form button {
  min-height: 0; padding: 8px 12px; cursor: pointer;
  background: rgba(20, 60, 88, .8); color: var(--ink);
  border: 1px solid var(--edge-ctrl); border-radius: 8px;
  font-family: var(--mono); font-size: 11px; letter-spacing: .08em;
  transition: border-color .16s;
}
.chat-form button:hover { border-color: rgba(140, 220, 255, .65); }
.chat-form button:active { transform: translateY(1px); }

/*
 * THE DOCK GETS OUT OF THE WAY.
 *
 * The dock's panel is min(720px, 100%) centred across the full width, and the rail is 304px
 * pinned to the right edge — so from 1,180 up to about 1,364 the two overlap and the DIVE
 * button runs underneath the chat log. Rather than raise the threshold until the collision
 * stops (which would cost every screen between those two numbers a rail it has the room
 * for), the dock centres in what is left.
 *
 * On the dock and not on the stage: the scene, the HUD and the readout all stay centred on
 * the whole frame, because the water genuinely does run the whole width behind the glass.
 * Only the controls have to be reachable, and only the controls move.
 */
/* THE RAIL'S OWN WIDTH PLUS THE GUTTER EITHER SIDE OF IT, not a number that happened to
   clear it once. 340 was 304 + 36 and did not move when the rail did, so the channel
   between the control plate and the rail swelled from 18px at 1024 to 129px at 1440 - the
   plate's left edge landing at x=199, aligned with nothing, and drifting as the window
   resized. */
body.rail-on #dock {
  padding-right: calc(var(--rail-w) + 36px + env(safe-area-inset-right));
}
/*
 * SAME CHANNEL AS THE DOCK. The wallet bubble is `1fr auto 1fr` in #topbar, which
 * centred it on the whole window — including the rail — while the hull, the 1.06× and
 * the bet plate sit in the water to the left of the rail. One padding, one centre.
 * The ♪ and i move to the play column's right edge, just before the rail, rather than
 * sitting on top of the chat.
 */
body.rail-on #topbar {
  padding-right: calc(var(--rail-w) + 36px + env(safe-area-inset-right));
}
body.rail-on #ticker {
  right: calc(var(--rail-w) + 18px);
}
/*
 * ONLY THE CANVAS. #topbar, #dock and #rail all live inside #stage. Insetting #stage
 * itself shoved the whole UI left and left a black void on the right — measured on a
 * desktop after deploy. #scene is the ocean; shrinking it to the rail's left edge
 * centres the hull on the same column as the bet plate without moving the chrome.
 */
body.rail-on #scene {
  right: calc(var(--rail-w) + 18px + env(safe-area-inset-right));
}

/*
 * DESKTOP PLAY COLUMN — ONE INSTRUMENT, NOT A STACK OF FORMS.
 * ============================================================
 * At 1440 the game was a fat card of stacked fields under a two-row pill grid in the
 * rail: a dashboard dumped on the ocean. A crash table of this kind is a ticker, a
 * stake, and a bet button on the play column, and a single glass column for who is in
 * and what they are saying.
 *
 * The ticker is a sibling of the panels, not inside #idle-panel, so it stays up for the
 * whole round — the same reason the strip was moved out of that panel on a phone.
 */
#board-btn { font-size: 16px; }
#me-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
#me-btn svg { display: block; }

#ticker {
  position: absolute;
  top: 10px; left: 12px; right: 12px;
  z-index: 3;
  width: auto;
  flex: none;
  min-height: 0;
  pointer-events: none;
  display: flex;
  align-items: center;
}
#ticker #history {
  pointer-events: auto;
  display: flex;
  flex-wrap: nowrap;
  overflow-x: auto;
  overflow-y: hidden;
  gap: 6px;
  margin: 0;
  padding: 0 0 2px;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-x;
  scrollbar-width: none;
  -ms-overflow-style: none;
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 18px), transparent);
          mask-image: linear-gradient(to right, #000 calc(100% - 18px), transparent);
}
#ticker #history::-webkit-scrollbar { display: none; width: 0; height: 0; }
#ticker .hist {
  font-size: 12px; font-weight: 700;
  padding: 3px 8px;
  border-width: 1px;
  border-radius: 7px;
  flex: 0 0 auto;
}

/*
 * ALL BETS — under the stake, never over the water.
 * Aviator's third box. The feed is the same #table node; these rules undo the overlay
 * placement (absolute, 210px, no rows) so it reads as a list in the dock.
 *
 * SHORT, ON PURPOSE. flex: 1 and max-height: 42vh let sixteen dummy seats grow this
 * plate to almost half the window, which is the defect the first split layout did
 * not have: two compact cards under a hull that filled the column. 22vh is five or
 * six rows — the same density Aviator shows — and the rest scrolls. Do not grow it
 * to "look filled"; the dummy roster already fills the scroller.
 */
.bets {
  width: 100%;
  flex: 0 1 auto;
  min-height: 0;
  max-height: 18vh;
  display: flex;
  flex-direction: column;
  background: var(--surface-fill);
  background-image: var(--surface-coat);
  border: 1px solid var(--glass-hi);
  border-radius: var(--surface-radius);
  box-shadow: var(--surface-lip);
  overflow: hidden;
}
/*
 * AFTER the 22vh default, or source order undoes it. The first 18vh rule sat
 * above this block and lost: 390x844 still measured 22vh / 186px, and the
 * ocean was 41% of the column.
 *
 * 640, not 499: a 520px desktop window is the one sitting next to Aviator,
 * and `@media (max-width: 640px) { .row { column } }` was stacking stake
 * then auto then DIVE into a 231px plate. Fields share a row; DIVE is full
 * width underneath. Never a side column — that is the strip that made DIVE
 * an 80px stamp.
 */
@media (max-width: 640px) {
  .bets { max-height: 16vh; }
  #idle-panel { padding: 8px 10px; gap: 6px; }
  #idle-panel .seat { gap: 6px; }
  #idle-panel .seat > .row {
    flex-direction: row; flex-wrap: nowrap; gap: 8px;
  }
  #idle-panel .seat > .row > .field { flex: 1 1 0; min-width: 0; }
  #idle-panel .field { gap: 4px; }
  #idle-panel .big { padding: 10px 8px; }
  #idle-panel .big b { font-size: 19px; letter-spacing: .10em; }
  #idle-panel .stepper button[data-bet="half"] { display: none; }
}
/* The stake has two shortcuts and auto has one. Give the stake its extra room
   on the smallest phones without changing the two-field row or touch targets. */
@media (max-width: 360px) {
  /* Keep one crew ticket visible below the compact tab and column headers. */
  .bets { max-height: 20vh; }
  #idle-panel .seat > .row > .field:first-child { flex: 1.15 1 0; }
  #idle-panel .seat > .row > .field:last-child { flex: .85 1 0; }
  #idle-panel .field > span { letter-spacing: .04em; }
  #idle-panel .stepper input { padding-inline: 4px; }
  #idle-panel .stepper button { padding-inline: 7px; }
}
/*
 * AVIATOR'S BET: fields stacked on the left, DIVE a tall plate on the right.
 * AFTER the 640px block, or that block's flex-direction:row on the fields
 * wins at 520px and DIVE goes back underneath.
 */
@media (min-width: 500px) and (min-height: 640px) {
  #idle-panel .seat {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(168px, 42%);
    grid-template-areas: "fields action";
    gap: 8px 10px;
    align-items: stretch;
  }
  #idle-panel .seat > .row {
    grid-area: fields; min-width: 0;
    flex-direction: column;
    gap: 8px;
  }
  #idle-panel .seat > .row > .field { flex: 0 1 auto; min-width: 0; min-height: 0; }
  #idle-panel .seat > #dive {
    grid-area: action;
    align-self: stretch;
    min-width: 0;
    justify-content: center;
    padding: 12px 10px;
  }
  #idle-panel .seat > #dive b { font-size: 22px; letter-spacing: .12em; }
  #idle-panel .seat > #dive .cash-key { display: none; }
}
.bets-head {
  display: flex; align-items: center; justify-content: flex-start; gap: 6px;
  padding: 0 12px;
  flex: 0 0 auto;
  width: 100%;
  max-width: none;
  margin-inline: 0;
  background: rgba(30, 43, 54, .34);
  border-bottom: 1px solid rgba(179, 199, 212, .10);
}
.bets-tab {
  appearance: none; background: none; border: 0; cursor: pointer;
  font-family: var(--ui); font-size: 11px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--ink-dim);
  padding: 9px 12px;
  border-radius: 0;
  border-bottom: 0;
  transition: color .16s, background .16s, box-shadow .16s;
}
.bets-tab[aria-selected="true"] {
  color: #e8f1f5;
  background: rgba(117, 154, 174, .055);
  border-bottom: 0;
  box-shadow: inset 0 -2px 0 #91b9c7;
}
.bets-tab:hover { color: var(--ink-hi); }
.bets-tab:active { transform: translateY(1px); }
#bets #table {
  position: static; right: auto; top: auto;
  width: auto; max-height: none;
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; align-items: stretch;
  text-align: left; pointer-events: auto;
  overflow-y: auto;
  background: transparent; border: 0; border-radius: 0;
  padding: 2px 8px 4px; box-shadow: none;
}
#bets #table.hidden { display: flex; }
#bets #table .cap {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  text-align: left; margin-bottom: 0;
  font-family: var(--ui); font-size: 12px; letter-spacing: .01em; color: #c5d3dc;
  padding: 5px 8px;
}
#bets #table .cap span:last-child {
  text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; font-weight: 600;
}
#bets #table .cap small {
  display: inline-block; font-family: var(--ui); font-size: 10px; letter-spacing: .06em; font-weight: 500;
  margin-left: 6px;
  color: var(--ink-dim); text-transform: uppercase;
}
#bets .cap-bar { height: 3px; margin: 1px 0 4px; }
#bets #table .who {
  display: grid;
  grid-template-columns: 28px minmax(0, 1.3fr) 64px 44px 64px;
  align-items: center; gap: 6px;
  padding: 5px 8px;
  margin: 0;
  border-radius: 4px;
  background: rgba(27, 42, 54, .18);
  border: 1px solid transparent;
  border-bottom-color: rgba(170, 191, 205, .06);
  box-shadow: none;
}
#bets #table .who.hdr {
  background: none; border-color: transparent;
  padding: 0 8px 2px; margin: 0;
  font-size: 10px; opacity: 1;
}
#bets #table .who .win { display: block; }
#bets #table .who.hdr { display: grid; }
#bets #table .who.demo { opacity: .88; }
#bets #table .table-empty{
  margin: 8px 2px; color: var(--ink-dim); font-size: 12px;
}
/*
 * AFTER the All Bets grid, or 70px money columns win and 390px names
 * ellipsis to "tidal_m". Win is recoverable from bet × multiplier; the
 * name is not. Compact chrome so 20vh holds three avatar rows.
 */
@media (max-width: 499px) {
  .bets-head { padding: 0 6px; justify-content: space-between; gap: 2px; }
  .bets-tab { padding: 7px 6px; font-size: 10px; letter-spacing: .04em; }
  #bets #table { padding: 2px 8px 4px; }
  #bets #table .cap { font-size: 11px; margin-bottom: 0; padding: 4px 8px; }
  #bets #table .cap small { font-size: 9px; margin-left: 4px; }
  #bets .cap-bar { margin: 1px 0 4px; }
  #bets #table .who {
    grid-template-columns: 28px minmax(0, 1fr) 52px 40px;
    gap: 6px;
    padding: 4px 8px;
  }
  #bets #table .who.hdr { padding: 0 8px 2px; }
  #bets #table .who .win { display: none; }
}

/* The rail's history slot is unused on desktop; hide the empty box. */
#rail-hist:not(:has(#history)) { display: none; }

/*
 * THE PLAY COLUMN IS THE CHANNEL LEFT OF THE RAIL, not a 720px card floating in it.
 * Ticker, lobby plate and cash-out plate share this width and this centre.
 * The seat inside the plate is unchanged: stake and auto stacked, DIVE underneath.
 */
body.desktop #idle-panel,
body.desktop #dive-panel {
  width: 100%;
  padding: 10px 12px;
  gap: 8px;
}

/* DIVE stays under the fields. A side column at 500px made it a stamp. */
#dive-panel #surface { position: relative; z-index: 2; }
@media (max-width: 480px) {
  #idle-panel .seat { overflow: hidden; }
  #idle-panel .seat > #dive { padding-left: 8px; padding-right: 8px; }
  #idle-panel .seat > #dive b { letter-spacing: .12em; font-size: 16px; }
  /* ½ + 2× + MAX plus the number will not fit beside DIVE at this width. */
  #idle-panel .stepper button[data-bet="half"] { display: none; }
}
/* The table, once it is in the rail: a block in a column rather than a thing pinned to a
   corner. Every property here undoes one the absolute placement needed.

   INCLUDING pointer-events, WHICH IT DID NOT. The base `#table` block sets
   `pointer-events: none` because on a phone it is an overlay pinned over the water and must
   not eat presses meant for the scene; `#deck-table #table` puts it back for the dock's
   drawer, and this block — the OTHER home the same node is reparented into — never did.
   pointer-events inherits, and `#rail > *` reaches #rail-table rather than the #table
   inside it, so the feed's own `none` won: in the rail it could not be scrolled, its rows
   could not be hovered, and the name tooltip written expressly for this panel could not be
   raised. `max-height: 52%` with `overflow-y: auto` means everybody past the cap was
   unreachable on the widest screen the game has. */
/*
 * THE FEED GETS THE SAME GLASS THE ROOM HAS.
 *
 * It had no background at all — bare text laid on the water. That is fine over the abyss
 * and unreadable over the SURFACE, which is where every round begins: measured at 1440x900
 * in the first second of a dive, "1 IN · 1 DOWN · 25.00 STAKED" and the PLAYER/BET/WIN
 * header were pale grey on a pale sky. The one panel that says the game is being played by
 * other people, invisible for the part of the round everybody is looking at.
 *
 * MORE GLASS, NOT LESS. The rail's own note says it must read as glass over the ocean
 * rather than a panel bolted to the side of it — a backdrop blur is more that, not less,
 * and it is what lets the fill stay light enough to see the water through.
 */
#rail #table {
  /* See the note above: the base block's `none` is for the phone's pinned overlay, and this
     is a block in a column that scrolls. */
  pointer-events: auto;
  position: static; right: auto; top: auto;
  width: auto; max-height: none; flex: 1 1 auto;
  align-items: stretch; text-align: left;
  overflow-y: auto;
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 0;
  box-shadow: none;
}
#rail #table.hidden { display: none; }

#table .cap {
  font-family: var(--mono); font-size: 10px; letter-spacing: .10em;
  color: var(--ink); text-transform: uppercase; margin-bottom: 3px;
}
/* Standing on its own on a phone, it is a readable pill rather than a caption over nothing
   — and it wraps to two cramped lines at 127px, so the panel widens to fit one. */
/*
 * WITHOUT THE RAIL THIS IS A PILL, AND IT HAS TO BE SIZED LIKE ONE.
 *
 * The panel's own rule is `width: min(210px, 34vw)` with `overflow: hidden` — a hundred and
 * twenty-seven pixels on a 375-wide phone — holding a caption that does not wrap. So the
 * text ran out of the box to the left and the box cut it off mid-word: a clipped grey bar
 * reading "MITTED · 1,876.00 STAKED · DEMO" lying across the water. Reported, accurately,
 * as an ugly bar across the screen.
 *
 * The first attempt at this wrote `#table:not(#rail-table > #table)`, which is a combinator
 * inside :not() and never matched anything — so the 127px box and its clip both survived
 * and I did not notice, because the desktop layout it was written beside looked right.
 * `body:not(.rail-on)` is the class this file already toggles, and it is unambiguous.
 */
/*
 * NOTHING AT ALL BELOW THE RAIL.
 *
 * This was a pill in the corner carrying the count, and before that a caption clipped
 * mid-word into a grey bar lying across the water. Both were the same mistake in different
 * sizes: a panel designed for a 304px column, wedged into a phone.
 *
 * A phone has one screen and the game is on it. The room is not worth a floating box over
 * the submarine when the dock already has a line for exactly this — "watching round N ·
 * X down" — which is text the layout already accounts for and nothing has to be squeezed
 * around. The count moved there; this is gone.
 */
/*
 * …EXCEPT IN THE SHEET, WHERE THERE IS ROOM.
 *
 * The rule below is about the FLOATING panel: pinned over the water at min(210px, 34vw) it
 * is 127 pixels on a phone, and everything the comment above says about that is true. It
 * was written when the only two places the feed could be were the rail and the corner.
 *
 * There is a third now. Inside the wallet sheet a 375px phone gives the table about 300 —
 * the width the comment itself names as the threshold — so the rows come back, with the
 * same grid the rail uses.
 */
#deck-table #table {
  position: static; width: auto; max-height: 46vh; overflow-y: auto;
  align-items: stretch; text-align: left; pointer-events: auto;
}
#deck-table #table .who { display: grid; grid-template-columns: 8px minmax(0, 1fr) 52px 46px 56px; }
#deck-table #table .who .win { display: block; }
#deck-table #table .who.hdr { display: grid; }
#deck-table #table.hidden { display: none; }
/* And the caption is a heading here rather than a pill over water. */
#deck-table #table .cap { text-align: left; }

body:not(.desktop) #deck-table #table { display: flex; }

/*
 * THE PAGE WITH THE SCRIPT SWITCHED OFF.
 *
 * Styled rather than left bare, because the browsers that block script are the ones whose
 * owner made a decision, and a wall of Times New Roman on white in the middle of a dark
 * page reads as breakage. Same ink, same panel, same measure as every other sheet here.
 */
.noscript {
  /* A COLUMN, NOT A GRID. `display: grid; place-items: center` made each of the three
     children its own row and stretched the rows over the viewport, so the heading and the
     two paragraphs sat a hundred pixels apart on a phone. They are one block of prose. */
  position: fixed; inset: 0; z-index: 100;
  display: flex; flex-direction: column; justify-content: center; align-items: center;
  /* NO BACKGROUND AND NO FALLBACK LITERALS. `html, body` already paints the page, and in
     the state this rule exists for nothing is drawn over it — the canvas is never created,
     because creating it is the script that is switched off. A colour here would be a second
     copy of a value that lives in exactly one place, which is what dom-check refuses. */
  padding: 24px; color: var(--ink);
  font: 15px/1.6 var(--ui);
}
.noscript > * { max-width: 30rem; }
.noscript h1 { font-size: 20px; letter-spacing: .02em; margin: 0 0 12px; }
.noscript p { margin: 0 0 10px; color: var(--ink-dim); }

/* ------------------------------------------------------- the phone's deck
 *
 * The table and the room, in the dock's flow, on a screen too narrow for the rail. They used
 * to be reparented into the WALLET — two presses and a full-viewport sheet over the round,
 * measured at 598px of an 812px screen — which is worse than every reference product
 * measured. bustabit keeps one in-flow panel below the game and never covers it; that is the
 * shape this follows.
 *
 * NOTHING HERE IS POSITIONED. The deck is a flex child of #dock, so it grows the dock upward
 * and the renderer's ResizeObserver lifts the submarine to match. An absolutely positioned
 * drawer would need its own geometry and could reach the top bar; this cannot.
 */
/* A SURFACE, LIKE THE PANELS IT SITS WITH. Without a background the feed rows float over the
   water with the kelp showing between them, which reads as a rendering fault rather than as
   a list. Same treatment as `.panel`, so the dock is one family. */
.deck {
  width: min(720px, 100%);
  /*
   * ONE LINE: THE STRIP AND THE DOORS TOGETHER.
   *
   * Stacked, this was a wrapped block of history chips over a row of two buttons — three
   * rows of it on a 390px screen, about 150px of chrome above the only control that does
   * anything. The strip is a glance, not a table: it belongs beside the doors on one line,
   * clipped at the right edge, because the newest results are the ones anybody reads and a
   * fourteen-chip block is a wall.
   *
   * `wrap` with a 100% basis on the drawer is what keeps the drawer on its own line below.
   */
  display: flex; flex-flow: row wrap; align-items: center; gap: 6px;
  /* "Same treatment as .panel" above is true now and was not: this carried a copy of the
     panel's gradient, a .12 edge against its .16, and 14px against its 18 — two boxes
     10px apart in one column, one material apart. */
  background: var(--surface-fill);
  background-image: var(--surface-coat);
  border: 1px solid var(--glass-hi);
  border-radius: var(--surface-radius);
  padding: 10px 12px;
  box-shadow: var(--surface-lip);
}
/* 44px, which is the figure the coarse-pointer block already uses and layout-check already
   requires of every control in it. `flex: 0 0 auto` so the door survives any dock height —
   a two-seat lobby, a keyboard — because a drawer that collapses to a sliver with
   no affordance left to reopen it is the defect this replaces wearing a different hat. */
/* SIDE BY SIDE AT EVERY WIDTH. `.row` turns into a column below 640px — right for a stake
   field beside an auto-surface field, wrong for two doors: stacked they took two rows of the
   drawer to say "Table" and "Room". The same override .board-periods needed, for the same
   reason. */
.deck-bar { flex: 0 0 auto; flex-direction: row; gap: 6px; align-items: center; }
.deck-bar .small-btn { flex: 0 1 auto; min-height: 44px; padding: 0 14px; }
.deck-bar .small-btn[aria-pressed="true"] {
  background: var(--glass-hi);
  border-color: var(--edge-live);
  color: var(--ink);
}
/* The unread count. A number and nothing else: no colour change, no sound, no growth — a
   badge that pulls attention out of a running round is what this panel is arranged to
   avoid. aria-live="off" in the markup says the same to a screen reader, and it is not
   spoken because a11y-check caps say() at eleven and the client is at eleven. */
#deck-badge { margin-left: auto; color: var(--ink-dim); }
/*
 * `.small` IS SCOPED TO `.sheet`, WHICH THE DECK IS NOT IN. It reads as a font-size class
 * and is not one: the only rule that sets a size is `.sheet .small`, so the feed's
 * disclaimer rendered at the document's 16px and took 95px — a third of the drawer — for one
 * sentence. Same figures as the sheet, so the two read as one product.
 */
.deck .small { font-size: 11.5px; color: #7f9fb2; line-height: 1.55; }
.deck #deck-table p { margin: 0 0 6px; }
/* Capped so the drawer can never push the dock's top edge above the multiplier's floor:
   READOUT_BOTTOM_Y is 0.265, so 26.5vh is where the band ends, and 34vh of drawer plus the
   dock's own controls stays under it at every phone size tested. */
/* THE MEMBER THAT GIVES. `min-height: 0` is what lets it shrink below its content inside the
   dock's cap — without it a flex child refuses, and the dock would overflow its max-height
   and clip the DIVE button instead of the drawer. */
#deck-body { flex: 0 1 100%; max-height: 34vh; overflow: hidden; display: flex; min-height: 0; }
#deck-body > div { flex: 1 1 auto; min-height: 0; display: flex; }
#deck-table { flex-direction: column; }
#deck-chat { flex-direction: column; gap: 6px; }
#deck-chat .chat-log { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
/*
 * FADED AT THE RIGHT EDGE RATHER THAN CUT. The strip is one line and runs off the end by
 * design — the newest results are on the left and are the ones anybody reads — but a chip
 * sliced through its own glyphs reads as a rendering fault. The mask says "there is more"
 * in the way a scrollable row is supposed to.
 */
#deck-hist {
  flex: 1 1 140px; min-width: 0; overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 18px), transparent);
          mask-image: linear-gradient(to right, #000 calc(100% - 18px), transparent);
}
#deck-hist #history {
  flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; margin-bottom: 0;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-x;
  /* Measured on macOS: the overlay thumb covered a whole row of multipliers.
     overflow-x stays auto, so a finger or a trackpad still pans. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
#deck-hist #history::-webkit-scrollbar { display: none; width: 0; height: 0; }

/*
 * TWO ROWS YOU CAN SWIPE, ON A PORTRAIT PHONE.
 * ===========================================
 * Wrapping fourteen pills left the second row a third full — ~10 fit on the first line
 * and the leftover four sat in a hole. A column-flow grid of two rows fills both lines
 * evenly and grows sideways, so the rest of the table's history is a swipe away rather
 * than clipped. Newest still start at the left.
 *
 * Landscape keeps one scrolling row: height is the scarce thing there.
 */
/* One scrolling row, the way Aviator keeps it — wrapping to two lines ate the game. */
/* Only one of the two is ever mounted visible: both are tall and a phone has one screen. */
#deck[data-view="table"] #deck-chat,
#deck[data-view="room"] #deck-table { display: none; }
/* A TABLE, not a list. Four columns of the same kind of thing per row, read DOWN — which
   is what makes a bet feed scannable and is why every crash game on the market lays this
   panel out the same way. The grid is what does it: without fixed tracks the columns wander
   by a few pixels per row as the digits change, and the eye loses the column. */
/*
 * TWO SHAPES, BECAUSE THERE ARE TWO PANELS.
 *
 * In the rail there is 304px and the full table fits: dot, name, bet, multiplier, win, with
 * a header over it. Pinned to the corner on a narrower screen there is 210px, and four
 * numeric columns plus their gaps take 186 of it — which left about twenty pixels for the
 * NAME, so every row read as an anonymous dot beside some money. Caught on screen; the
 * columns lined up perfectly and identified nobody.
 *
 * So the corner panel drops the WIN column and the header. The name is what identifies a
 * row and it gets the space; the win is recoverable from the bet and the multiplier, which
 * are both still there.
 */
#table .who {
  display: grid;
  grid-template-columns: 8px minmax(0, 1fr) 62px 46px;
  align-items: center; gap: 6px;
  font-family: var(--ui); font-size: 13px;
  padding: 3px 8px; border-radius: 6px;
  background: none;
  border: 1px solid transparent;
  position: relative; isolation: isolate; overflow: hidden; flex: 0 0 auto;
}
/* The header is the same grid so the columns line up with the rows under it, and quiet
   enough that it never competes with a number. */
/*
 * ON A PHONE THERE ARE NO ROWS AT ALL, and that is the honest answer rather than a
 * compromise.
 *
 * The corner panel is min(210px, 34vw) — a hundred and twenty-seven pixels on a 375 wide
 * screen. Four numeric columns and their gaps take a hundred and twenty-four of them, so
 * the NAME got three: measured on a phone, the feed was a column of anonymous numbers
 * lying across the submarine, obscuring the one thing the player came to watch.
 *
 * Shrinking the type would keep it unreadable and shrinking the columns would lose the
 * money. A bet feed needs about three hundred pixels to be a bet feed; below that it is
 * decoration in the way. So the caption carries it alone — how many are in and how many
 * are still down is the fact that matters, it is one line, and it does not cover anything.
 * The rows return the moment there is a rail to put them in.
 */
#table .who { display: none; }
#table .who .win { display: none; }
#table .who.hdr { display: none; }
/* In the rail, the full table. */
#rail #table .who { display: grid; grid-template-columns: 8px minmax(0, 1fr) 52px 46px 56px; }
#rail #table .who .win { display: block; }
#rail #table .who.hdr { display: grid; }

#table .who.hdr {
  background: none; border-color: transparent;
  font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-dim); opacity: .8; padding-bottom: 0;
}
#table .who.hdr .name, #table .who.hdr .stake,
#table .who.hdr .at, #table .who.hdr .win { color: inherit; font-size: 10px; font-weight: 500; }
#table .who .dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: hsl(var(--h, 200) 70% 58%);
  box-shadow: 0 0 6px hsl(var(--h, 200) 80% 55% / .5);
}
.who-av {
  width: 24px; height: 24px; min-width: 24px; min-height: 24px;
  border-radius: 50%;
  background: linear-gradient(145deg, hsl(var(--h, 200) 46% 42%), hsl(var(--h, 200) 50% 22%));
  color: #f4fffb;
  font-size: 9px; font-weight: 800; letter-spacing: .04em;
  display: grid; place-items: center;
  text-transform: uppercase; line-height: 1;
  flex: 0 0 auto;
  overflow: hidden;
  box-shadow: inset 0 0 0 1px hsl(var(--h, 200) 65% 78% / .4), inset 0 1px 2px rgba(237, 250, 255, .25), 0 1px 3px rgba(0, 0, 0, .35);
}
#bets #table .who.hdr .who-av{ visibility: hidden; }
/*
 * AND THE MONEY COLUMNS TRUNCATE TOO, not only the name.
 *
 * These are fixed grid tracks and both figures are right-aligned. Right-aligned text that
 * exceeds its box overflows to the LEFT, so a large payout printed backwards across the
 * multiplier badge beside it. `.name` got this treatment and the two columns carrying the
 * numbers did not.
 */
#table .who .stake, #table .who .win {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#table .who .name { min-width: 0; overflow: hidden; text-overflow: ellipsis;
                    white-space: nowrap; color: #c1d1dc; font-weight: 500; }
#table .who .stake { color: var(--ink-dim); font-family: var(--mono); font-size: 11px; text-align: right;
                     font-variant-numeric: tabular-nums; }
/*
 * THE MULTIPLIER IS A BADGE, not a number in a column.
 *
 * It was plain text the same weight as everything beside it, so a row of five numbers had
 * no shape and the eye had to read all of them to find the one that mattered. Every crash
 * game on the market gives this figure a filled pill, and they are right for a reason that
 * is not fashion: it is the only value in the row that is a STATE as well as a number —
 * a seat either has one or is still down — and a filled shape says "this happened" at a
 * glance where a colour on text does not.
 *
 * ONE ACCENT, NOT A SPECTRUM. Banding it by magnitude is what the history strip refuses in
 * as many words, because reading a column of sizes as a pattern is the gambler's fallacy
 * with a palette. Green means banked. That is the whole vocabulary.
 */
#table .who .at {
  justify-self: end; min-width: 42px; text-align: center;
  padding: 1px 5px; border-radius: 999px;
  font-family: var(--mono); font-size: 10px; font-variant-numeric: tabular-nums;
  color: var(--ink-dim); background: none;
  border: 1px solid transparent;
}
#table .who .win { text-align: right; color: var(--ink-dim); font-family: var(--mono); font-size: 11px;
                   font-variant-numeric: tabular-nums; }
/* Open: worth this much if they got out now, and not theirs yet. Quieter than a banked one
   by enough that a glance down the column separates the two without reading either. */
#table .who .win.open { color: rgba(190, 225, 245, .55); }
/* OUT: they banked it. The row goes green — the one state change in this panel a player
   watches for, and the reason a bet feed is worth having on screen at all. */
#table .who.out, #bets #table .who.out {
  background: linear-gradient(90deg, rgba(38, 90, 67, .36), rgba(22, 49, 39, .12));
  border-color: transparent;
  border-bottom-color: rgba(156, 212, 185, .07);
  box-shadow: inset 2px 0 0 rgba(107, 211, 160, .55);
}
#table .who.out .at {
  color: #0b2018; background: var(--green);
  border-color: rgba(120, 255, 190, .5); font-weight: 700;
}
/* The win is the loudest thing on a banked row, because it is what the row is FOR. */
#table .who.out .win { color: var(--green); font-weight: 700; }
#table .who.out .name { color: rgba(190, 255, 225, .85); }
/* Arriving. A row that appears without moving reads as a repaint; one that slides in reads
   as somebody sitting down. 140ms, and reduced motion switches it off globally.

   NOT `#rail #table .who` - the table is one of the five nodes ui.js moves between the rail
   and the deck's drawer, so scoped to the rail this reached the desktop only and every seat
   on a phone arrived as the repaint the sentence above calls wrong. The five rules that
   give the table its shape are mirrored for both homes at #deck-table; this one was not,
   and an animation needs no ancestor to say which copy it means. */
#table .who.crew-arrive { animation: seat .16s var(--press-ease); }
@keyframes seat { from { opacity: 0; transform: translateY(-3px); } }
#table .who.me, #bets #table .who.me {
  border-color: rgba(110, 230, 255, .45);
  box-shadow: inset 2px 0 0 rgba(110, 230, 255, .65);
}
#table .who.me .name { color: var(--ink); }
/* Still down. One colour, no gradient by how deep they are: shading a live position by
   size is a ranking with the numbers taken off. */
#table .who.down .at { color: var(--ink-dim); }

/* A short optical acknowledgement, driven only by a public seat transition.
   It cannot move a column or sit over the live payout text. */
#table .who.crew-cashout::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: linear-gradient(105deg, transparent 20%, rgba(255, 219, 142, .25), transparent 70%);
  animation: foil-pass .55s ease-out both;
}
#table .who.crew-cashout .who-av::after {
  content: ""; position: absolute; left: 21px; top: 50%;
  width: 4px; height: 4px; border: 1px solid rgba(233, 254, 255, .8); border-radius: 50%;
  pointer-events: none; animation: crew-bubble .6s ease-out both;
}
@keyframes crew-bubble { from { transform: translateY(5px); opacity: .8; } to { transform: translateY(-19px); opacity: 0; } }
#table .who.busted { opacity: .62; }
#table .who.crew-bust { animation: crew-settle .35s ease-out both; }
@keyframes crew-settle { from { opacity: 1; } to { opacity: .62; } }
#table .who.crew-bust::after {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: radial-gradient(circle at 13% 80%, rgba(161, 186, 193, .35) 0 1px, transparent 2px),
              radial-gradient(circle at 42% 65%, rgba(161, 186, 193, .25) 0 1px, transparent 2px),
              radial-gradient(circle at 73% 84%, rgba(161, 186, 193, .2) 0 1px, transparent 2px);
  animation: crew-silt .35s ease-out both;
}
@keyframes crew-silt { from { transform: translateY(4px); opacity: .6; } to { transform: translateY(-7px); opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .brand .mark.round-ping::before, .hist.hi:first-child::after,
  #table .who.crew-cashout::before, #table .who.crew-cashout .who-av::after,
  #table .who.crew-bust::after { display: none; }
  #table .who.crew-arrive, #table .who.crew-bust { animation: none; }
}

/* ------------------------------------------------------------------ account history
   A TABLE, because it is one: four columns of the same kind of thing per row, read down.
   Divs with ARIA roles would say the same to a screen reader and nothing to the browser's
   own find, selection or copy — and a statement is something people copy out of.

   SCROLLS IN ITS OWN BOX. The sheet is a fixed-height modal on a phone; a hundred rows
   inside it either push the buttons off the bottom or make the page itself scroll behind a
   dialog, and the second is how a modal loses its close button. */
.stmt-wrap { max-height: 46vh; overflow-y: auto; overflow-x: auto; margin: 10px 0;
  border: 1px solid rgba(140, 220, 255, .18); border-radius: 10px; }
.stmt { width: 100%; border-collapse: collapse; font-size: 12px; }
.stmt th, .stmt td { padding: 7px 10px; text-align: left; white-space: nowrap;
  border-bottom: 1px solid var(--edge-hair); }
.stmt thead th { position: sticky; top: 0; background: rgba(8, 26, 40, .97);
  color: var(--ink-dim); font-weight: 600; letter-spacing: .05em; font-size: 11px; }
.stmt td.num, .stmt th.num { text-align: right; font-family: var(--mono);
  font-variant-numeric: tabular-nums; }
/* Direction is carried by the sign as well as the colour: red and green alone is the one
   distinction a colour-blind reader cannot make, and this is money. WCAG 1.4.1. */
.stmt td.up { color: #7fe0a0; }
.stmt td.down { color: #ff9a94; }
.stmt tbody tr:last-child td { border-bottom: 0; }
.stmt .what { white-space: normal; min-width: 12ch; }
.stmt .tx { color: var(--ink-dim); font-family: var(--mono); font-size: 10px; }
/* THE TWO COLUMNS THAT MATTER MOST ARE THE TWO THAT FELL OFF THE PHONE.
   Measured at 375px: the table scrolled inside its box correctly and the page did not, but
   what was visible was "When" and "What" — a player had to scroll sideways to find out how
   much. The timestamp is the widest thing here and the least urgent, so it wraps onto two
   lines and the money stays on screen. */
@media (max-width: 560px) {
  .stmt th, .stmt td { padding: 6px 6px; font-size: 11px; }
  .stmt td:first-child, .stmt th:first-child { white-space: normal; min-width: 8ch; max-width: 11ch; }
  .stmt .what { min-width: 9ch; }
}
/*
 * AND THE SAME FIX AGAIN, ONE SIZE DOWN, BECAUSE THE FIRST ONE STOPPED AT 360.
 *
 * The block above was measured at 375 and it fits there exactly: 291px of table in 291px of
 * box, and 276 in 276 at 360. At 320 — an iPhone SE, and a great many cheap Android phones
 * — the same table is 271px in a 236px box, and the 35px that falls off the end is the
 * BALANCE column. The statement showed "95.0" for 95.07, cut mid-number, until the reader
 * thought to drag it sideways. A truncated money figure is not a missing display, it is a
 * wrong one, which is the whole reason the block above exists; it just did not go far
 * enough down.
 *
 * PADDING AND COLUMN FLOORS, AND NOTHING IS HIDDEN. This is a read-only table, so nothing
 * in it is a touch target with a size to keep, and 2px of horizontal padding across four
 * columns is most of the 35px on its own. The ledger row id under each description was the
 * obvious thing to drop next and it turned out not to need dropping: with `.what` allowed
 * to wrap, `#269` takes its own line inside the cell rather than widening it. Keeping it
 * matters — the code that writes it calls it "what support will ask for and the only
 * identifier that means the same thing on both sides of the conversation", and a player on
 * a small phone is not a player with less right to quote a reference.
 *
 * MEASURED AFTER, in a browser: 236px in a 236px box at 320, zero overflow, every balance
 * whole and every reference still on screen — and 276/276 at 360 and 291/291 at 375,
 * untouched.
 */
@media (max-width: 360px) {
  .stmt th, .stmt td { padding: 5px 2px; }
  .stmt td:first-child, .stmt th:first-child { min-width: 6ch; max-width: 8ch; }
  .stmt .what { min-width: 7ch; }
}

/* ---------------------------------------------------------------- the room
   Deliberately quiet. A chat panel on a gambling site is the one surface that
   competes with the game for attention, and the game is the thing the player
   came for — so it is legible and it is not loud. */
.chat-log {
  height: 15rem; overflow-y: auto; padding: .5rem .6rem; margin: .5rem 0;
  background: rgba(4, 14, 22, .55); border: 1px solid rgba(120, 170, 200, .16);
  border-radius: 8px; font-size: .82rem; line-height: 1.45;
  /* Long words and pasted URLs must not push the panel wider than the column. */
  overflow-wrap: anywhere;
}
/*
 * THE NAME GIVES WAY, NOT THE MESSAGE.
 *
 * The name column was `auto`, which takes its full min-content width whatever that costs —
 * so in the 304px rail a long handle squeezed the message into a three-word column running
 * down the side. What somebody said is the content; who said it is the label. The body
 * keeps a 6ch floor and the name truncates into it.
 */
.chat-line {
  display: grid; grid-template-columns: auto minmax(0, auto) minmax(6ch, 1fr);
  gap: .45rem; padding: .1rem 0;
}
.chat-who { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-when { color: rgba(150, 185, 205, .45); font-variant-numeric: tabular-nums; }
.chat-who  { color: #8fd8ff; font-weight: 600; }
.chat-body { color: #d7e6f2; }
/* Your own lines, marked without shouting: a tint, not a colour swap. */
.chat-line.mine .chat-who { color: #9dffcb; }
/* A line a moderator took down. Italic and dimmed rather than hidden — the row keeps its
   place so the conversation around it still makes sense, and the reader can see that
   something was removed rather than wondering what they missed. */
.chat-line.removed .chat-body { color: rgba(150, 185, 205, .5); font-style: italic; }
/* One deposit per line. They were joined with " · " while every entry was two words; a
   held one now carries the refusing gate's whole sentence, and three of those on one line
   is prose with separators lost inside it. */
.dep-line { display: block; }
.dep-line + .dep-line { margin-top: 4px; }
.dep-now {
  margin: 12px 0 8px; padding: 12px;
  background: var(--edge-hair);
  border: 1px solid var(--glass-hi);
  border-radius: var(--surface-radius);
}
.dep-now-k { margin: 0 0 4px; font-size: 10.5px; letter-spacing: .1em;
             text-transform: uppercase; color: var(--ink-dim); }
.dep-now-amt { margin: 0 0 6px; font-family: var(--mono); font-size: 18px;
               font-weight: 700; color: var(--ink-hi); }
.dep-now-bar {
  height: 4px; margin: 8px 0 10px; border-radius: 2px;
  background: var(--edge-hair); overflow: hidden;
}
.dep-now-bar i { display: block; height: 100%; width: 0; background: var(--green);
                 transition: width .3s; }
.dep-tx { color: var(--cyan); font-size: 12px; }

/* A SENTENCE THAT IS TELLING SOMEBODY NOT TO SEND MONEY. Amber rather than red: it is a
   configuration this deployment has not finished, not an error the player caused, and the
   red in this palette is reserved for the hull. The left rule is what makes it read as a
   warning at a glance on a pane that is otherwise all quiet grey text. */
.small.warn {
  color: var(--amber);
  border-left: 2px solid rgba(255, 194, 74, .55);
  padding-left: 8px;
  margin-top: 6px;
}

/* ------------------------------------------------------------------- the board
   Its tables reuse the statement's, which already handles a narrow column and a wide one.
   What is new is the chart and the window buttons. */
/* THREE SHORT BUTTONS, SIDE BY SIDE, at every width. `.row` turns into a column below
   640px — right for a stake field beside an auto-surface field, wrong for these: stacked,
   they took three rows and a third of the panel to say "Today". They fit across at 320. */
.board-periods { margin: 4px 0 10px; gap: 6px; flex-direction: row; }
/* Equal thirds and tighter padding, so the three fit across a 320px sheet rather than
   wrapping "All time" onto a line of its own. */
.board-periods .small-btn { flex: 1 1 0; padding: 0 8px; text-align: center; }
.board-periods .small-btn[aria-pressed="true"] {
  border-color: var(--edge-live); color: var(--ink);
}
/* WHICH LIST, under WHICH WINDOW. Same geometry as the period row so the two read as a
   pair of controls rather than as two unrelated things that happen to be buttons — but
   filled rather than outlined when pressed, because these switch what the panel IS and the
   row above only narrows it. Without that difference two identical-looking rows sit on top
   of each other with two different pressed states and neither reads as the current one. */
.board-views { margin: 2px 0 12px; gap: 6px; flex-direction: row; }
.board-views .small-btn { flex: 1 1 0; padding: 0 8px; text-align: center; }
.board-views .small-btn[aria-pressed="true"] {
  background: var(--glass-hi);
  border-color: var(--edge-live);
  color: var(--ink);
}
/* The first heading inside a view sits right under the switcher, so it does not need the
   gap it would take from a paragraph above it. */
.board-view > .fair-h:first-child { margin-top: 2px; }
/* The seed inside a bet row's detail line, rather than in a column of its own — a fifth
   column is what pushed "nobody was in it" into an ellipsis on the round list. Dimmer than
   the sentence it follows, so it reads as a reference and not as part of it.

   AND THE DETAIL LINE WRAPS ON THAT ROW ONLY. `.board-over` is nowrap-with-ellipsis, which
   is right for a leaderboard's "16 rounds · 320.00 staked" — but with a hash on the end of
   it the ellipsis ate the hash, and at 375px the seed came out as four characters on one
   row and eight on the next. Four hex characters are not an eyeball match against the ⓘ
   panel, and ragged widths down a list read as breakage. Wrapping gives the seed a second
   line when it needs one and costs nothing when it does not; `nowrap` on the seed itself
   keeps it whole rather than breaking mid-hash. */
.board-seed.inline { color: var(--ink-dim); opacity: .75; white-space: nowrap; }
/* The seed on a round row is a control now — it opens the verifier on that round — so it has
   to look like one without becoming a button in a list of forty. Underlined on hover and
   focus only; a permanently decorated column would read as forty links. */
/*
 * NO `font: inherit`. The shorthand is (0,1,1) and beat `.board-seed`'s own (0,1,0)
 * font-family and font-size, so the control that opens the verifier on a past round
 * rendered at the inherited size in the system font — resetting exactly what the rule below
 * it sets, and re-creating the clipping that rule exists to avoid.
 */
button.board-seed {
  background: none; border: 0; padding: 0; color: var(--ink-dim);
  cursor: pointer; text-align: right;
  transition: color .16s;
}
button.board-seed:hover, button.board-seed:focus-visible {
  color: var(--cyan); text-decoration: underline;
}
/* Given a 44px floor by the coarse-pointer block above, so it is a finger target in a list of
   forty and needs to say it took the press. */
button.board-seed:active { transform: translateY(1px); }
.board-row.bet .board-over { white-space: normal; overflow: visible; text-overflow: clip; }

/* "Show older" under a capped list. Full width so it reads as the end of the list rather
   than as a control floating beside it, and it disappears when the server says there is no
   more — a button that pages past the end is a control with nothing behind it. */
.board-more { display: block; width: 100%; margin: 2px 0 6px; }

/* The line explaining a settlement the player was not here for. Same furniture as the
   watching line it sits under — this is the dock's status voice — with the accent ink,
   because it is about the reader's own money rather than about the table. Flat for both
   outcomes: no green on a win, no red on a loss. */
.while-away { color: var(--ink); }

/* A withdrawal in flight. Three cells and no money column — the amount IS the headline
   here, unlike a bet row where the net is what the eye goes to. Same row furniture as the
   board so the two lists read as one family. */
.board-row.wd { grid-template-columns: auto auto minmax(0, 1fr);
                grid-template-areas: "rank mult over"; align-items: baseline; }
.board-row.wd .board-mult { grid-area: mult; color: var(--ink); }
.board-row.wd .board-over { grid-area: over; white-space: normal; }
.board-list.wds { margin-bottom: 10px; }

/* THE READER'S OWN ROW. A tint AND a chip: a tint alone is the whole of the signal for
   anybody who cannot see it, and this list is otherwise identical rows of strangers. The
   left edge is what carries at a glance down a column of twenty. */
.board-row.you { background: var(--edge-hair); box-shadow: inset 2px 0 0 rgba(140, 220, 255, .8); }
.board-you {
  margin-left: 6px; padding: 0 5px; border-radius: 4px; font-size: .68em;
  font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  background: rgba(140, 220, 255, .22); color: var(--ink);
  vertical-align: middle;
}
/* The standing line reads as a statement about the reader rather than as another caption,
   so it keeps the accent the chip uses instead of the dim the notes take. */
#board-standing { color: var(--ink); }

/* A fixed height so the panel does not jump as the bars change between windows. `color`
   rather than a fill on each element, because the axis and its labels are drawn with
   currentColor and there is one place to change them. */
.board-chart { height: 108px; margin: 6px 0 4px; color: var(--ink-dim); }
.board-chart svg { display: block; }
/* An empty chart still reserved 108px — the void under "Your profit" on a player
   who has not sat down yet. :empty is true while paintCurve has written nothing. */
.board-chart:empty { display: none; height: 0; margin: 0; }

/*
 * YOU, AS A CARD, NOT AS THE LEADERBOARD'S "you" CHIP.
 * `.board-you` is the small tag on a leader row. The identity strip used that class
 * and inherited the chip's fill, tracking and uppercase — which is why the name
 * read as a disabled field labelled PLAYING AS.
 */
.you-card {
  display: flex; align-items: center; gap: 12px;
  padding: 12px;
  margin: 0 0 14px;
  background: var(--edge-hair);
  border: 1px solid var(--glass-hi);
  border-radius: var(--surface-radius);
}
.you-mark { flex: 0 0 auto; }
.you-mark .board-avatar svg { width: 40px; height: 40px; }
.you-id { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; }
.you-name {
  font-family: var(--mono); font-size: 16px; font-weight: 700; color: var(--ink-hi);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.you-k { font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
         color: var(--ink-dim); }
.you-stats { display: flex; gap: 18px; flex: 0 0 auto; }
.you-stats > div { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.you-stat-k { font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
              color: var(--ink-dim); }
.you-stat-v { font-family: var(--mono); font-size: 15px; font-weight: 700;
              color: var(--ink-hi); font-variant-numeric: tabular-nums; }
.you-stat-v.down { color: var(--ink-dim); }
#wallet-modal:not(.you-open) .you-stats { display: none; }

/*
 * ME OPENS A PROFILE, NOT THE CASHIER ON THE BOARD TAB.
 * Asset picker, wallet tabs, the table-wide summary and the Leaders/Rounds
 * switcher are the public record's furniture. Trophy still shows all of it.
 */
#wallet-modal.you-open .wtabs,
#wallet-modal.you-open .asset-row,
#wallet-modal.you-open #wallet-net,
#wallet-modal.you-open #board-summary,
#wallet-modal.you-open .board-views { display: none; }
#wallet-modal.you-open .wallet-sheet { width: min(440px, 100%); }
#board-back {
  display: none; margin: 0 0 10px; width: auto; padding: 0 12px;
}
#wallet-modal.you-open #board-back { display: inline-flex; }
#board-leader-rows .board-row { cursor: pointer; }
#board-leader-rows .board-row:hover,
#board-leader-rows .board-row:focus-visible {
  border-color: var(--edge-live);
}
#board-view-mine.is-empty > .fair-h,
#board-view-mine.is-empty #board-curve-note { display: none; }
#board-view-mine.is-empty #board-mine-note {
  margin: 18px 0 4px; text-align: center; font-size: 13px;
}

@media (max-width: 420px) {
  .you-card { flex-wrap: wrap; }
  .you-stats { width: 100%; justify-content: flex-end; padding-top: 4px; }
}

/* ------------------------------------------------------------- the board's rows
 *
 * A LIST, NOT A TABLE. Five columns of numbers is a spreadsheet; one line per person —
 * who, how much, over how much play — is what a player reads. Grid rather than flex so the
 * rank, the mark, the name, the detail and the money hold their columns down the list
 * however long a name is.
 */
.board-list { list-style: none; margin: 4px 0 6px; padding: 0;
              display: flex; flex-direction: column; gap: 4px; }
.board-row {
  position: relative; isolation: isolate;
  display: grid; align-items: center; gap: 8px;
  /* 34px of avatar and 26 of rank, from the reference board this was measured against:
     the identity is the thing being ranked and it was the smallest thing in the row. */
  grid-template-columns: 26px 34px minmax(0, 1fr) auto;
  grid-template-areas: "rank mark who net" "rank mark over net";
  padding: 9px 10px; border-radius: 9px;
  background: rgba(8, 24, 38, .5);
  border: 1px solid var(--edge-hair);
}
/* The bar behind the row, scaled to the leader: a rank column says who is first, this says
   by how much. Behind everything and never over the text — z-index on an isolated row. */
.board-bar {
  position: absolute; inset: 0 auto 0 0; z-index: -1;
  border-radius: 9px;
  background: linear-gradient(90deg, rgba(87, 242, 165, .16), rgba(87, 242, 165, .03));
}
.board-rank { grid-area: rank; font-family: var(--mono); font-size: 11px;
              color: var(--ink-dim); font-variant-numeric: tabular-nums; text-align: center;
              align-self: center; }
/*
 * THE TOP THREE WEAR A MEDAL, AT THE OWNER'S DIRECTION, and the argument against is kept
 * rather than deleted because it was a real one: "a number in a warmer ink, no medal and no
 * glow — this is a record of who is ahead, and a podium is a prize ceremony." The reference
 * board this was measured against puts a gold, silver and bronze badge on the first three
 * and a plain numeral on everyone else, and a player arriving from one expects to find it.
 * It is a disc rather than a ribbon: the row is 34px tall and a ribbon at that size is a
 * smudge.
 */
.board-row.rank-1 .board-rank,
.board-row.rank-2 .board-rank,
.board-row.rank-3 .board-rank {
  width: 20px; height: 20px; margin: 0 auto; border-radius: 999px;
  display: grid; place-items: center;
  font-size: 10px; font-weight: 700; color: #0b1a12;
}
.board-row.rank-1 .board-rank { background: linear-gradient(160deg, #ffd98a, #d99a2b); }
.board-row.rank-2 .board-rank { background: linear-gradient(160deg, #e6eef5, #9fb2c2); }
.board-row.rank-3 .board-rank { background: linear-gradient(160deg, #efb98a, #b3703c); }
.board-avatar { grid-area: mark; color: var(--mark, var(--cyan)); display: flex;
                align-items: center; opacity: .95; }
/* Round, not rounded-square. Every board of this kind uses a circle for a person and the
   identicon reads the same either way. */
.board-avatar svg { display: block; border-radius: 999px;
                    background: rgba(140, 220, 255, .07); padding: 3px; }
.board-who { grid-area: who; font-family: var(--mono); font-size: 12px; color: var(--ink);
             overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.board-over { grid-area: over; font-size: 10.5px; color: var(--ink-dim);
              overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/*
 * EXCEPT ON THE LEADERBOARD, WHERE THE ELLIPSIS ATE A NUMBER. The rule above assumed this
 * line reads "16 rounds · 320.00 staked" and fits. At 375px it does not: measured, it came
 * out "14 rounds · 2,530…." — which is not a clipped sentence, it is a clipped FIGURE that
 * still looks like a whole one. Somebody reading that row is told this player staked two
 * and a half thousand and something, with the something hidden and no sign the line was cut.
 *
 * So it wraps here, for the same reason the seed on a bet row does: a second line when it
 * needs one, nothing when it does not — the desktop sheet has 474px and never wraps at all.
 */
#board-leader-rows .board-over {
  white-space: normal; overflow: visible; text-overflow: clip;
}
/* The figure is what the row is about, so it is the biggest thing in it. 15px against the
   name's 12: the reference board runs roughly the same ratio and it is what makes the
   column scannable without reading a single name. */
.board-net { grid-area: net; font-family: var(--mono); font-size: 15px; font-weight: 700;
             color: var(--green); font-variant-numeric: tabular-nums; text-align: right;
             align-self: center; padding-left: 10px; }
.board-net.down { color: var(--ink-dim); }

/*
 * THE READER'S OWN ROW, ABOVE THE LIST AND UNDER A DASHED RULE.
 * =============================================================
 * Two labelled figures on one line — rank left, net right — and a dashed border beneath,
 * which is the reference board's own separator and reads as "you are not part of the
 * ranking below" rather than as another row of it. Deliberately NOT a .board-row: it has no
 * bar, no avatar and no medal, because it is a statement about the reader and not an entry
 * in the list.
 */
.board-self {
  display: grid; grid-template-columns: auto 1fr auto auto;
  align-items: baseline; gap: 6px 10px;
  padding: 6px 10px 12px; margin-bottom: 10px;
  border-bottom: 1px dashed rgba(140, 220, 255, .18);
}
.board-self-k { font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
                color: var(--ink-dim); }
.board-self-v { font-family: var(--mono); font-size: 15px; font-weight: 700;
                color: var(--green); font-variant-numeric: tabular-nums; }
.board-self-v.down { color: var(--ink-dim); }
.board-self-k.right, .board-self-v.right { text-align: right; }

/* A player's own seats and the table's rounds share the row, with the multiplier where the
   avatar sits on the leaderboard — the same column, so the three lists scan as one thing. */
.board-list.rounds .board-row {
  grid-template-columns: 46px 62px minmax(0, 1fr) auto;
  grid-template-areas: "rank mult over net";
}
/*
 * A BET TAKES TWO LINES, like a leaderboard row, because its detail is the longest string
 * on the board: "20.00 staked · hull 6.24× · auto" against a bold money column beside it
 * clipped to "20.00 staked · hull …" at 390px — the sentence losing to the figure. The
 * money keeps the first line with the round and the multiplier; the sentence gets a line of
 * its own and the full width of the row.
 */
.board-list.bets .board-row {
  grid-template-columns: 46px 62px minmax(0, 1fr);
  grid-template-areas: "rank mult net" ".    over over";
}
.board-mult { grid-area: mult; font-family: var(--mono); font-size: 12px; font-weight: 700;
              font-variant-numeric: tabular-nums; }
/* The strip's own three bands, so a multiplier means the same thing wherever it is shown. */
.board-mult.lo  { color: var(--ink-dim); }
.board-mult.mid { color: var(--cyan); }
.board-mult.hi  { color: var(--amber); }
/* The seed may shrink; the sentence beside it may not. `auto` on this column made it
   demand its full width and the detail clipped to "nobody was i…" at 390px. */
.board-seed { grid-area: net; font-family: var(--mono); font-size: 10px;
              color: rgba(150, 185, 205, .55);
              overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.board-list.rounds .board-row { grid-template-columns: 46px 62px minmax(0, 1fr) minmax(0, auto); }
/* A losing seat is not shouted at. The row is the same weight as a winning one and only the
   money differs — a red row for every loss is a scoreboard telling somebody off. */
.board-row.bet.lost { background: rgba(8, 24, 38, .34); }
/* A REFUSED seat is neither. The stake was held and given straight back, so the row is
   quieter than a loss rather than louder: dimmed throughout, and the only one of the three
   whose multiplier cell carries a word instead of a figure. */
.board-row.bet.refused { background: rgba(8, 24, 38, .18); opacity: .74; }
.board-row.bet.refused .board-mult { letter-spacing: .04em; }

/* The room before anybody has spoken. Centred and quiet: it is a normal state, not an
   error, and it should not compete with the first real line that arrives. */
.chat-empty {
  margin: auto 0; padding: 0 4px; text-align: center;
  font-size: 12px; color: var(--ink-dim); opacity: .55;
}

/* The same sentence in the same voice, one panel over. See paintTable: the rail hides an
   empty table because a floating panel with nothing in it is clutter; the sheet, where the
   table is the only content, says so instead. */
.table-empty {
  margin: auto 0; padding: 10px 4px; text-align: center;
  font-size: 12px; color: var(--ink-dim); opacity: .55;
}

.chat-form { display: flex; gap: .4rem; }
.chat-form input { flex: 1 1 auto; min-width: 0; }
@media (prefers-reduced-motion: reduce) { .chat-log { scroll-behavior: auto; } }


/* ------------------------------------------------ the cash-out, announced over the round

   A player who cashes out mid-round keeps watching: the hull is the table's and it carries
   on descending. That is the right scene and it left the WIN with nothing to announce it —
   "i can barely tell when i cashout now or how much i won", which is the cost of taking the
   outro away without replacing what it did.

   Over the scene, not instead of it. It sits above the canvas, below the controls, ignores
   the pointer entirely, and stays until the lobby opens or the hull dies. */
#cashout-flash {
  position: absolute; left: 50%; top: 34%;
  transform: translate(-50%, -50%);
  z-index: 6; pointer-events: none; text-align: center;
  animation: co-in .42s cubic-bezier(.16, 1.1, .3, 1) both;
}
#cashout-flash.out { animation: co-out .55s ease-in both; }
.co-amt {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  font-weight: 800; font-size: clamp(38px, 9vw, 76px); line-height: 1;
  color: #b9ffdd;
  text-shadow: 0 0 26px rgba(70, 240, 160, .75), 0 0 70px rgba(70, 240, 160, .4),
               0 2px 0 rgba(0, 40, 24, .5);
}
.co-at {
  margin-top: 6px; font-family: var(--mono); font-size: 14px; letter-spacing: .16em;
  color: rgba(185, 255, 221, .8);
  text-shadow: 0 0 16px rgba(70, 240, 160, .5);
}
@keyframes co-in {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.7); }
  100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
@keyframes co-out {
  0%   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -62%) scale(1.06); }
}
/* The figure is the information; the flourish is not. Reduced motion still gets the
   number, at full size, held and then removed without movement. */
@media (prefers-reduced-motion: reduce) {
  #cashout-flash, #cashout-flash.out { animation: none; }
  #cashout-flash.out { opacity: 0; transition: opacity .3s linear; }
}

/* ------------------------------------------- a phone on its side, continued
 *
 * THE REST OF THE max-height: 520px BLOCK, DOWN HERE FOR A REASON.
 *
 * `#dock` and `.big` are declared further up this file than that block, so a rule for
 * them inside it has equal specificity and LOSES ON SOURCE ORDER. Written there first and
 * measured: the media query matched, `matchMedia('(max-height: 520px)').matches` was true,
 * and the computed gap was still 10px. The file already carries this warning once, above
 * the cashier's 44px touch targets. Twice now.
 *
 * WHY MORE WAS NEEDED. That block's own measurement is 267 of 390 and its argument is that
 * you cannot have a layout where the controls are visible and the thing they control is
 * not. Re-measured on the same 844x390 with a round actually RUNNING: 308. The lobby panel
 * and the watch-only strip are up together — deliberately, because betting on the next
 * round while watching this one is the shape of the game — and nothing had measured the
 * two of them stacked.
 *
 * The dock's own gap and bottom padding are the largest saving and cost nothing visible.
 * DIVE's second line is the one real loss, and it is the smallest: it alternates between
 * the countdown and the stake, and the stake is in the field two rows up while the
 * countdown is on the surface behind the panel.
 */
@media (max-height: 520px) {
  #dock { gap: 4px; }
  .big:not(:disabled) small { display: none; }
  #history { margin-bottom: 2px; }

  /*
   * AND THE FIELDS STAY ON ONE LINE HERE, WHICH IS THE OPPOSITE OF THE RULE ABOVE.
   *
   * Wrapping is the right answer to a narrow row and the wrong answer to a short screen:
   * with two bets open at 667x375 it put the dock at 410px in a 375px viewport — the panel
   * ran off the top of the screen and took the balance and the wallet with it. So on a
   * short viewport the row stays horizontal and the SPACE comes from somewhere else.
   *
   * From ½ and 2×, which is the same trade this block already makes for the quick chips
   * two rules up: they are a convenience, the number beside them is not, and a stake you
   * cannot read is not a control. MAX stays — it is the one that answers a question
   * ("everything") rather than adjusting an answer you can type. Measured with both open:
   * the dock goes 348 -> 329 at 667x375 and the stake field 16px -> 85px, and at 844x390
   * it is 226 of 390 with 109px fields, which is the first landscape layout where the hull
   * and the panel are both entirely visible.
   */
  /*
   * `flex-direction: row` IS SAID HERE, and its absence made the two lines below no-ops on
   * the two shortest viewports this block runs on.
   *
   * `@media (max-width: 640px)` sets a bare `.row { flex-direction: column }`, and a small
   * landscape phone — 568x320, 640x360 — is inside BOTH queries. In a column `flex-wrap:
   * nowrap` has nothing to wrap and `flex: 1 1 0` is a HEIGHT basis, so the stake and the
   * auto-surface field stacked and the seat came out about twice the height this block was
   * measured to produce — inside `#dock { max-height: 73vh; overflow: hidden }`, which at
   * 320px tall is 233px. The whole point of the block is fitting the panel above the
   * multiplier's floor.
   *
   * The file already knows the trap and guards the other half of this pair against it:
   * `.seat .row > .field { flex: 1 1 190px }` is wrapped in `@media (min-width: 641px)`
   * with the note "Below 641px `.row` is already flex-direction: column, and in a column a
   * flex-BASIS is a HEIGHT." Same trap, same pair of declarations, other end.
   */
  .seat .row { flex-direction: row; flex-wrap: nowrap; }
  .seat .row > .field { flex: 1 1 0; }
  .stepper button[data-bet="half"], .stepper button[data-bet="double"] { display: none; }

  /*
   * THE FIVE THAT WERE WRITTEN IN THE OTHER BLOCK AND NEVER APPLIED.
   *
   * `.panel` (297), `.big` (512), `.big b` (519), `.big small` (520) and `.field > span`
   * (484) are all declared below the max-height block at 253, so every one of them lost on
   * source order and the landscape panel was being laid out at its full-height figures. The
   * measurement that found it: padding computed 14px where the block asked for 9, and the
   * DIVE label 19px where it asked for 16. Together they are 20px of a screen with 276px to
   * spend, which is the difference between the button fitting and not.
   */
  /* The radius that was here said 14 and --surface-radius says 14: a restatement, in the
     one block this file records as a graveyard of rules that lost on source order. */
  .panel { gap: 6px; padding: 9px; }
  .big { padding: 9px; border-radius: 11px; }
  .big b { font-size: 16px; }
  /* 10px, not 8. These are the words that say which box is the wager and which is the
     auto-cash-out, and the row they were shrinking to save is 2px tall. */
  .field > span { font-size: 10px; letter-spacing: .14em; }

  /*
   * AND THE DECK GOES ON ONE LINE, WHICH IS WHERE THE REST OF THE HEIGHT COMES FROM.
   *
   * Stacked, the deck is 118px: a 46px history strip over a 44px bar with the padding
   * between them. On a 375px-tall screen the arithmetic does not work at any font size —
   * 276px of usable height, a panel that will not go below ~205, and 118 on top of it.
   * Something had to stop being a row.
   *
   * The strip and the bar share one instead. They are both short and the screen is 667px
   * wide in this orientation, which is the one dimension a landscape phone has to spare, so
   * the history stays ON SCREEN rather than being hidden — the deck goes to ~52px and keeps
   * everything. The drawer is forced onto its own line with `flex-basis: 100%` and capped
   * tighter than the 34vh it gets in portrait, because 34vh of a short viewport is most of
   * the water.
   *
   * `#deck-hist` gets `nowrap` here: wrapped chips are what made the strip 46px in the
   * first place, and a single line that scrolls off at the right edge is the correct
   * reading of a history — the newest results are the ones anybody looks at.
   */
  #deck { padding: 8px 10px; }
  #deck-body { max-height: 26vh; }

  /*
   * THE SEAT TURNS SIDEWAYS, WHICH IS WHERE THE HEIGHT ACTUALLY COMES FROM.
   *
   * Measured on a running round at 667x375: the dock wanted 341px and had 276 above the
   * multiplier's floor. A 65px deficit is not a padding problem, and every previous attempt
   * to close it — smaller type, tighter gaps, hidden chips — was shaving single figures off
   * a two-figure gap. What costs the height is the SHAPE: stake and auto-surface on one
   * line, DIVE on the next, stacked the way a portrait screen wants them.
   *
   * A landscape phone has width and no height, so the seat spends the one it has. The two
   * fields keep their row and DIVE moves beside it: the seat goes 139px to 69px, which is
   * the whole deficit and then some, and nothing is hidden to get it. `stretch` gives DIVE
   * the full height of the field row, which on this screen makes it the largest target in
   * the layout rather than the smallest.
   */
  .seat { flex-direction: row; align-items: stretch; gap: 8px; }
  .seat > .row { flex: 1 1 auto; min-width: 0; }
  .seat > .big { flex: 0 0 38%; }

  /* A landscape phone cannot spare a third plate. All Bets lives in the deck. */
  .bets { display: none; }

}

/*
 * WATCHING. The dock keeps its place and its height — the submarine's position is measured
 * off #dock, so hiding controls by collapsing the box would move the game — and the seat's
 * own controls go away entirely rather than being greyed. A disabled DIVE still answers the
 * Enter key, which is bound to whatever is on screen.
 *
 * The bet feed, the room, the history and the round all stay: that is the whole point of
 * being able to watch, and none of them belongs to a seat.
 */
/*
 * NOT #idle-panel WHOLESALE — #history lives inside it, and hiding the parent deleted the
 * round-history strip, which is one of the four things a spectator came to see. The seat's
 * own controls are named individually instead.
 */
body.watching #bet, body.watching #auto,
body.watching #idle-panel .row,
body.watching #idle-panel label,
body.watching #idle-panel .seats,
body.watching #dive,
body.watching #dive-panel,
body.watching #outro-panel,
body.watching .chips.autobet,
body.watching #chat-form { display: none; }
/*
 * AND THE PLATE ITSELF, ONCE THE STRIP HAS LEFT IT. The controls above are hidden
 * one by one so #history could stay — then mountRail moved that node into #deck-hist
 * on a phone and #ticker on a desktop. What remains is a padded .panel of nothing,
 * under Sign in to play. Hide the box when the strip is not in it.
 */
body.watching #idle-panel:not(:has(#history)) { display: none; }
/*
 * BUT NOT THE BUTTON, WHICH IS THE ONLY DOOR TO THE BOARD.
 *
 * `body.watching #wallet-btn { display: none }` was here, and it was right about the
 * cashier and wrong about everything else in that sheet. #pane-board lives inside
 * #wallet-modal, #wallet-btn is the only listener that opens it, and the server was taught
 * on purpose — board.request is one of six names in WATCHER_MAY, onBoard special-cases the
 * three player-keyed reads so a synthetic id cannot make its Promise.all reject, and
 * authwire-check asserts "the leaderboard answers a watcher instead of erroring". A frame
 * built for a spectator that no client could send.
 *
 * So the button stays and the MONEY tabs go: a watcher gets the public record and none of
 * the four controls that need an account. ui.js relabels the button and opens it on the
 * board, because a door marked Wallet that leads to a leaderboard is its own defect.
 */
body.watching #tab-deposit,
body.watching #tab-withdraw,
body.watching #tab-history,
body.watching #wallet-net,
body.watching .asset-row { display: none; }

/*
 * DISPLAY IS DRIVEN BY THE BODY CLASS, NOT BY THE `hidden` ATTRIBUTE.
 *
 * `hidden` is only a `display: none` in the user-agent stylesheet, so ANY explicit display
 * here beats it — the first version set `display: grid` unconditionally and the prompt sat
 * on screen for signed-in players, above a perfectly working DIVE button, telling them they
 * were watching. Caught by looking at it, not by the attribute, which read `hidden: true`
 * the whole time.
 */
#watch-prompt { display: none; }
body.watching #watch-prompt {
  display: flex; flex-direction: row; align-items: center;
  gap: 10px; padding: 8px 12px;
}
#watch-prompt p {
  margin: 0; color: var(--ink-dim); flex: 1 1 auto; min-width: 0;
  font-size: 12px; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#watch-prompt .btn-quiet {
  flex: 0 0 auto; justify-content: center; min-height: 44px;
  width: auto; padding: 0 16px; white-space: nowrap;
}
/* The room is readable while watching, so it says why it cannot be written to. */
body.watching #rail-chat::after,
body.watching #deck-chat::after {
  content: 'Sign in to say something.';
  display: block; padding: 8px 10px; font-size: 12px; color: var(--ink-dim);
}

/* The profit curve. Same box as the distribution chart beside it, so the two read as one
   family, and a fixed height because an SVG with preserveAspectRatio="none" has none of its
   own — without this it collapses to nothing. */
#board-curve { height: 96px; }
#board-curve .curve { width: 100%; height: 100%; display: block; }

/*
 * ────────────────────────────────────────────────── CALM ON A PHONE
 *
 * The Table and Room tabs and the drawer they open are gone below the rail's threshold, at
 * the owner's direction: a phone is one screen, and a crash table that spends it on chrome
 * is a cluttered one. What a player came for is the hull, the number and the history, and
 * those are what is left.
 *
 * THE HISTORY STAYS. It lives in the same deck as the tabs and is the one thing every crash
 * game shows, so this hides the bar and the drawer rather than the section — hiding #deck
 * outright would take the strip with it, and the submarine's position is measured off #dock,
 * so collapsing the box would also move the game.
 *
 * The room and the bet feed are not lost: they are in the rail on any screen wide enough to
 * hold one beside the water, which is where they read properly anyway.
 */
@media (max-width: 1023px) {
  /* History lives in #ticker above the game now, so this section is an empty shell. */
  #deck { display: none; }
  #deck-hist { flex: 1 1 100%; min-width: 0; }

  /*
   * THE DOORS ARE GONE ON A PHONE. Table and Room sat on the same line as the
   * history strip and ate the right third of it — measured at 375x812, 5 of 14
   * results were on screen. The strip is what this row is for; the feed and the
   * room stay in the rail on a wide screen.
   *
   * display: none, not visibility or a zero width: the buttons stay in the DOM
   * for the wide-layout reparent and for bindDeck, but they must not be
   * announced or tabbable on a screen that cannot press them.
   */
  .deck-bar { display: none; }
  /* Kept so layout-check still finds a finger-sized rule if the doors come back.
     Hidden, they are not a target. */
  @media (pointer: coarse) {
    .deck-bar .small-btn { min-height: 44px; padding: 8px 16px; }
  }

  /* Closed is the default, and closed is out of the way entirely. */
  #deck-body { display: none; }

  /*
   * AND IT STOPS AT THE TOP OF THE DOCK. This is the whole design, not a refinement of it.
   *
   * A sheet pinned to the bottom of the screen covers the dock, and the dock is where DIVE
   * is. Built that way and measured on a 375x812 screen, a player who opened the room during
   * a live round was looking at 364px of chat with their cash-out button underneath it: the
   * hull breaks, the stake is gone, and the control that would have saved it was on screen
   * the whole time behind a panel they opened to say hello. That is the most expensive
   * defect a layout can have, and no amount of "they can dismiss it" answers it — the two
   * seconds it takes to realise the sheet is in the way ARE the cash-out.
   *
   * So the sheet floats ABOVE the dock and the backdrop stops there too. Every control that
   * costs or returns money stays lit and stays pressable with the room open. --dock-h is
   * published by ui.js off a ResizeObserver, because the dock is content-sized and swaps the
   * idle panel for the dive panel mid-round; the 200px fallback is close to the measured
   * idle height, so a browser that never runs the script still clears most of it.
   */
  #deck[data-view="table"] #deck-body,
  #deck[data-view="room"] #deck-body {
    display: flex;
    position: fixed;
    left: 0; right: 0;
    /*
     * WHICHEVER IS HIGHER, THE DOCK OR THE KEYBOARD. This box holds the only text field on
     * the phone's game screen, and a field anchored to the bottom of the layout is a field
     * under the keyboard on both mobile browsers. --kb-h is the covered band, published by
     * ui.js off visualViewport and 0 whenever nothing is up, so with no keyboard this reads
     * exactly as it did: the top of the dock.
     */
    bottom: max(var(--dock-h, 200px), var(--kb-h, 0px));
    z-index: 40;
    /* 90px is the top bar's band, the same figure #rail clears: the balance, the session net
       position and the elapsed clock stay uncovered, which for the last two is RTS 2E and
       13C rather than a preference. */
    max-height: min(62vh, calc(100vh - max(var(--dock-h, 200px), var(--kb-h, 0px)) - 90px));
    padding: 10px 12px 12px;
    background: var(--sheet-bg);
    border-top: 1px solid var(--glass-hi);
    border-radius: 14px 14px 0 0;
    box-shadow: 0 -18px 40px rgba(0, 0, 0, .55);
  }
  /*
   * The backdrop is the dismiss target for a tap on the water, and it dims the ROUND rather
   * than the controls. It ends at the dock, so nothing that costs or returns money is behind
   * it, and it starts below the top bar for a harder reason than taste: the session net
   * position and the elapsed clock are up there, they are RTS 2E and 13C, and they may never
   * be covered. 90px is #rail's figure and layout-check asserts the same band from the other
   * side — the bar's lower edge at 72 plus the 18px gutter every edge in this layout uses.
   */
  #deck[data-view="table"]::after,
  #deck[data-view="room"]::after {
    content: ""; position: fixed; z-index: 39;
    top: calc(90px + env(safe-area-inset-top));
    left: 0; right: 0; bottom: max(var(--dock-h, 200px), var(--kb-h, 0px));
    /* Faded in over its first 30px rather than a flat fill, because a flat one drew a hard
       horizontal seam across open water at exactly 90px — the eye reads that as a rendering
       fault in the scene, not as a dimmed backdrop. The bottom edge needs no such treatment:
       it lands on the dock's own top edge, which is a real border. */
    background: linear-gradient(to bottom, rgba(2, 8, 14, 0) 0, rgba(2, 8, 14, .55) 30px);
  }
}

/*
 * AND IT DOES NOT ANIMATE FOR SOMEBODY WHO ASKED IT NOT TO. The sheet is the one thing on
 * this page that appears over the game, so it is exactly the motion a player with
 * prefers-reduced-motion set is asking to be spared.
 */
@media (max-width: 1023px) and (prefers-reduced-motion: no-preference) {
  #deck[data-view="table"] #deck-body,
  #deck[data-view="room"] #deck-body { animation: deck-rise .18s ease-out; }
}

/*
 * ───────────────────────── WHAT GIVES WAY WHEN THERE IS NOT ENOUGH ROOM
 *
 * On a 320x568 phone, during the four seconds the doors are open, the dock is 414px of
 * stake panel and 568 - 414 - 90 leaves the sheet SIXTY-FOUR PIXELS. Something has to be
 * clipped, and which thing it is was decided by document order: the form is last, so
 * `overflow: hidden` took the box you type into. Measured before this: the chat field
 * rendered entirely below the sheet's own bottom edge.
 *
 * So the order is stated instead. The log gives way first — it already scrolls, so a
 * shorter one loses nothing — then the standing disclaimer, and the field last of all. The
 * log's shrink factor is far above the disclaimer's precisely because flexbox distributes
 * shrinkage proportionally rather than in a sequence: an equal factor clipped the
 * disclaimer through the middle of its own line while the log still had space to give.
 *
 * AND AT 320x568 IT IS STILL NOT ENOUGH, which is worth saying rather than hiding. Sixty-
 * four pixels less the sheet's own padding is forty-one, and the field alone is forty-four:
 * there is no arrangement of these three that fits. What the order buys is that the eleven
 * pixels come off the BOTTOM of a field that is otherwise whole and on screen, instead of
 * the field being below the sheet entirely. Four seconds later the doors shut, the stake
 * panel goes, and the sheet is 230px.
 *
 * NO FLOOR AND NO TOP ANCHOR, and both were tried. A 150px minimum with the sheet anchored
 * from the top makes it grow DOWNWARD over the dock, and the dock's first member is the
 * deck bar — so in landscape the sheet covered Table and Room, which are the way OUT of it.
 * elementFromPoint on the Room button returned the chat input. A panel a player cannot
 * close is worse than a panel that is briefly small, and the readouts above the sheet are
 * RTS 2E and 13C and cannot be covered either. The squeeze is the honest consequence of
 * three constraints that all outrank it.
 */
#deck-chat .chat-form { flex: 0 0 auto; }
#deck-chat #chat-note { flex: 0 1 auto; min-height: 0; overflow: hidden; }
/* 12, not 4. Shrinkage is distributed by base x factor, so at 4 the log still left the
   disclaimer 4px short of its own single line — a clipped 9px cap-height row, which reads
   as broken rather than as tight. At 12 the log has to be down to nothing before the
   disclaimer gives anything at all, which is the order this block is here to state. */
#deck-chat .chat-log { flex: 1 12 auto; }
@keyframes deck-rise { from { transform: translateY(14px); opacity: .6; } }

/*
 * ────────────────────────────────────── THE TWO BUTTONS ON THE CONSENT SHEET
 *
 * `.limits-row` wraps, which is right for the six controls in the limits panel and wrong
 * here: the accept button's label is a SENTENCE — deliberately, because "Accept" is not what
 * somebody is affirming — and a wrapping flex row broke it across two lines with the second
 * centred under the first, beside a narrower button on a line of its own. It read as a
 * layout accident on the one sheet that has to look considered.
 *
 * So: one row that does not wrap, the affirmation taking the space it needs, and the way out
 * beside it at its natural width. The hierarchy is stated rather than implied — the primary
 * fill is the difference between "which of these two do I press" and a pair of grey boxes.
 */
/*
 * AND `flex-direction: row`, WITHOUT WHICH THE OTHER THREE DECLARATIONS DO NOTHING ON THE
 * PHONE THIS BLOCK WAS WRITTEN FOR.
 *
 * The element is `<div class="row limits-row">`, and `@media (max-width: 640px)` sets a bare
 * `.row { flex-direction: column }`. Neither `.limits-row` nor this rule declared an axis,
 * so on a phone it was a COLUMN: `flex-wrap: nowrap` has nothing to wrap, `flex: 0 0 auto`
 * on the way out fixes a HEIGHT rather than a width, and `align-items: stretch` makes both
 * buttons full width. "I have read and accept them" and "Not now" came out as two identical
 * stacked bars — not the affirmation beside a narrower way out this block specifies, and the
 * note above it names the phone as the measurement it was written from.
 *
 * The same trap as `.seat .row` in the max-height block, which is now guarded the same way.
 */
#terms .limits-row { flex-direction: row; flex-wrap: nowrap; gap: 10px; align-items: stretch; }
#terms-ok { flex: 1 1 auto; }
#terms-later { flex: 0 0 auto; }

/* Centred, and centred properly: a button is a flex box so a label that wraps stays centred
   on both lines instead of ragging left under the first. */
.small-btn { display: inline-flex; align-items: center; justify-content: center;
             text-align: center; line-height: 1.35; }

/*
 * The affirmative action on a consent sheet should look like the affirmative action — and
 * read like a sentence, which is what it is. `.small-btn` is spaced monospace, which is
 * right for THE SHORT LABELS it was built for and wrong for an affirmation: at eleven
 * pixels with .09em of tracking, "I have read and accept them" does not fit a phone's sheet
 * and broke with "them" alone on the second line.
 *
 * The label is not shortened to make it fit. "Accept" is not what somebody is affirming, and
 * the sentence is the point of the control.
 */
#terms-ok {
  background: linear-gradient(180deg, #3aa0ff, #1f7fe0);
  border-color: transparent; color: #04121c; font-weight: 700;
  font-family: inherit; font-size: 14px; letter-spacing: .01em; padding: 9px 14px;
  transition: background .16s;
}
#terms-ok:hover { background: linear-gradient(180deg, #4aaaff, #2a89ea); }

/* Both comfortably past the 44px touch target on a phone, where this sheet is most often
   read and where a 32px button beside a wall of prose is a miss waiting to happen. */
#terms-later { font-family: inherit; font-size: 14px; letter-spacing: .01em; }

/* Both comfortably past the 44px touch target on a phone, where this sheet is most often
   read and where a 32px button beside a wall of prose is a miss waiting to happen. */
@media (pointer: coarse) { #terms-ok, #terms-later { min-height: 44px; } }
