/*
  Layout for the broadcast. Colour, type and spacing come from theme.css --
  nothing here invents a value the design system already has.

  One constraint decides every size below. This is authored at 1920x1080 and
  watched inside a pump.fun stream player about 500 pixels wide, so everything is
  judged at a quarter of the size it is written at. A 26px number becomes six
  pixels of grey mush; a 60px number stays a number. That is why the type here
  looks enormous on a desktop, why nothing is a dense table, and why five Racers
  are named rather than twenty-five.

  It is also rendered by a machine, so there are no hover states, no focus rings
  and no transitions longer than the eye needs.
*/

/* ── the tote board ─────────────────────────────────────────────────────────

   The problem this solves, measured rather than felt: at stream size roughly two
   fifths of the frame was near-black -- header, footer, panels, turf -- so the
   whole broadcast read as a screen that was switched *off*, no matter what was
   happening on it. The answer used to be a blue arcade cabinet. It is now the
   same answer the site reached: a racecourse tote board lit at night. Dark turf
   surfaces, one accent, and that accent used loudly enough that the frame never
   reads as unlit — the Purse, the leader, the wire, the money.

   Flat opaque colour, hard offset shadows, snap motion, signage type.

   NOT the other thing, and this list is the point of writing it down —
     no neon purple/cyan, no glows, no scanlines, no chromatic aberration,
     no pixel fonts, no gradients on type, no particle spam.
   Every one of those is what an encoder destroys first, and together they are
   the look this is trying not to be. Hard edges and flat colour survive;
   atmosphere does not.

   The ladder, the status colours and the one accent are lifted verbatim from
   site/watch.css. The broadcast is the same venue as the site, seen from the
   grandstand instead of the concourse — it does not get its own palette.    */

:root {
  --color-bg:          #0b0f0c;
  --color-surface:     #10160f;
  --color-surface-2:   #151c14;
  --color-surface-3:   #1c261b;
  --color-line:        #1e2a1f;
  --color-line-strong: #2b3a2c;
  --color-text:        #f1eee3;
  --color-dim:         #97a091;
  --color-dimmer:      #626b5e;
  --color-positive:    #30d158;
  --color-danger:      #ff453a;
  --color-warn:        #ff9f0a;
  --color-info:        #0a84ff;

  /* The one accent: the chequered flag. Live money, first place, the wire.
     Nothing else, ever. */
  --flag:              #ffd21e;
  --flag-dim:          #e8bd18;
  /* Ink laid on the flag. Dark enough to read at a quarter size. */
  --flag-ink:          #0b0f0c;

  /* Shadows are a solid colour offset, never a blur. A blur is the first thing
     to turn to mud at a quarter size and a low bitrate. */
  --shadow-ink:        #050705;
  --turf-ink:          #0d3a10;

  /* Signage is condensed and uppercase; every number and identifier is mono.
     Same two families the site uses, same division of labour. */
  --font-cond: "Barlow Condensed", "Inter", ui-sans-serif, sans-serif;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: #000;
  color: var(--color-text);
  font-family: var(--font-sans);
  overflow: hidden;
}

/* Authored at a fixed size and scaled to whatever it is shown in, so a 1080p
   stream and a phone preview are the same picture rather than two layouts. */
.stage {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 1920px;
  height: 1080px;
  transform-origin: center center;
  background: var(--color-bg);
  display: grid;
  grid-template-rows: 108px 1fr 74px;
  overflow: hidden;
}
/* Until main.js has fitted it, the stage is a 1920x1080 block parked at the
   centre of the frame with no scale on it -- in the spectator page, that is an
   unscaled header hanging off the corner of the broadcast panel for as long as
   the module graph takes to load. Not display:none: the canvas sizing reads
   clientWidth, which display:none zeroes. */
.stage:not(.fitted) { visibility: hidden; }

/* ── bars ─────────────────────────────────────────────────────────────────── */

.bar {
  display: flex;
  align-items: center;
  gap: 32px;
  /* The chequered strip overlays the top edge of the stage, so the bar's own
     content starts below it rather than under it. */
  padding: 16px 40px 0;
  border-bottom: 1px solid var(--color-line);
  background: var(--color-surface);
}
.bar-foot {
  border-bottom: none;
  border-top: 1px solid var(--color-line);
  font-size: 26px;
  color: var(--color-dim);
  justify-content: space-between;
}

.brand { display: flex; align-items: center; gap: 16px; }
.brand-name {
  font-family: var(--font-mono);
  font-size: 30px;
  font-weight: 700;
  letter-spacing: 0.1em;
}

.conn {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-left: 10px;
  font-family: var(--font-mono);
  font-size: 20px;
  color: var(--color-dimmer);
}
.conn .dot { width: 13px; height: 13px; border-radius: 50%; background: var(--color-warn); }
.conn.live .dot { background: var(--color-positive); }
.conn.down .dot { background: var(--color-danger); }

.bar-stats { display: flex; align-items: flex-end; gap: 56px; margin-left: auto; }
.stat { display: flex; flex-direction: column; gap: 4px; align-items: flex-end; }
.stat-label {
  font-family: var(--font-mono);
  font-size: 19px;
  font-weight: 500;
  letter-spacing: 0.18em;
  color: var(--color-dimmer);
}
.stat-value {
  font-family: var(--font-mono);
  font-size: 42px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
/* The Purse is the reason anybody is watching, so it is the largest thing in the
   bar by a wide margin rather than one stat among four. The dollar figure sits
   beside it rather than under it: stacked, the three lines overflow the bar and
   the label gets sliced off at the top. */
.stat-hero .stat-value { color: var(--flag); font-size: 60px; }
.stat-line { display: flex; align-items: baseline; gap: 14px; }
.stat-sub {
  font-family: var(--font-mono);
  font-size: 24px;
  color: var(--color-dim);
  font-variant-numeric: tabular-nums;
}

/* ── main ─────────────────────────────────────────────────────────────────── */

/* The track is the broadcast. It gets the whole frame; everything else overlays
   it, because a side panel costs a quarter of the width and returns text nobody
   watching at stream size can read. */
.main { display: block; min-height: 0; }
.track-wrap { position: relative; height: 100%; background: var(--color-bg); }
#track { display: block; width: 100%; height: 100%; }

/* ── commentary ───────────────────────────────────────────────────────────── */

/* One line at a time. A feed of five is a feed nobody reads. */
.callout {
  position: absolute;
  top: 34px;
  left: 50%;
  transform: translateX(-50%);
  padding: 14px 34px;
  background: var(--color-surface-2);
  border-left: 5px solid var(--flag);
  font-family: var(--font-mono);
  font-size: 34px;
  font-weight: 600;
  letter-spacing: 0.04em;
  white-space: nowrap;
  opacity: 0;
  transition: opacity 200ms linear;
}
.callout.show { opacity: 1; }
.callout.hot { border-left-color: var(--flag); color: var(--flag); }

/* ── running order, as a lower third ──────────────────────────────────────── */

.lower {
  position: absolute;
  left: 40px;
  right: 40px;
  bottom: 30px;
  display: flex;
  align-items: center;
  gap: 14px;
  opacity: 0;
  transition: opacity 200ms linear;
}
.lower.show { opacity: 1; }
.lower-rows { display: flex; gap: 12px; flex: 1; min-width: 0; }

/* Sized to its contents rather than to an equal share. Equal shares truncate the
   wallet, and a wallet cut to "BkrF…" is not an identity — it is the same four
   characters half the Field starts with. */
.row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 0 1 auto;
  min-width: 0;
  padding: 13px 16px;
  background: var(--color-surface-2);
  border-bottom: 4px solid var(--color-line-strong);
  font-family: var(--font-mono);
  transition: opacity 160ms linear;
}
.row-pos {
  font-size: 30px;
  font-weight: 700;
  color: var(--color-dimmer);
  font-variant-numeric: tabular-nums;
  flex: none;
}
/* The silk is the same mark as the jockey on the track, which is the only thing
   tying a name in this strip to an animal out there. */
.row-silk { width: 26px; height: 26px; flex: none; border-radius: 3px; }
.row-addr {
  font-size: 28px;
  font-weight: 600;
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.row-gap {
  margin-left: auto;
  padding-left: 10px;
  font-size: 28px;
  color: var(--color-dim);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  flex: none;
}
.row.lead { border-bottom-color: var(--flag); }
.row.lead .row-pos, .row.lead .row-addr, .row.lead .row-gap { color: var(--flag); }

.lower-more {
  flex: none;
  padding: 13px 22px;
  background: var(--color-surface-2);
  border-bottom: 4px solid var(--color-line-strong);
  font-family: var(--font-mono);
  font-size: 28px;
  color: var(--color-dimmer);
  white-space: nowrap;
}

/* ── countdown ────────────────────────────────────────────────────────────── */

.interstitial {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  background: var(--color-bg);
}
.interstitial.show { display: flex; }
.inter-inner { text-align: center; width: 1500px; }

.inter-kicker {
  font-family: var(--font-mono);
  font-size: 28px;
  font-weight: 600;
  letter-spacing: 0.36em;
  color: var(--color-dimmer);
  margin-bottom: 10px;
}
.inter-clock {
  font-family: var(--font-mono);
  font-size: 210px;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.03em;
}
.inter-sub { margin-top: 12px; font-size: 30px; color: var(--color-dim); }
.inter-sub:empty { margin-top: 0; }

.inter-bar {
  margin: 26px auto 0;
  width: 760px;
  height: 12px;
  background: var(--color-surface-3);
  border-radius: 6px;
  overflow: hidden;
  display: none;
}
.inter-bar.show { display: block; }
.inter-bar-fill { height: 100%; width: 0%; background: var(--flag); transition: width 0.4s ease; }

/* The pitch. Somebody scrolling pump.fun gives this about eight seconds, and if
   they leave without knowing that holding the coin enters them, the broadcast
   has failed regardless of how good the race looked. */
.pitch {
  margin-top: 24px;
  padding: 26px 34px;
  background: var(--color-surface-2);
  border-left: 6px solid var(--flag);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  font-size: 32px;
  color: var(--color-dim);
}
/* One line, and it has to survive being shrunk to a quarter of this size, so it
   says the whole offer rather than half of it. */
.pitch b {
  font-family: var(--font-mono);
  font-size: 44px;
  font-weight: 700;
  color: var(--color-text);
  letter-spacing: 0.01em;
  white-space: nowrap;
}

.proof {
  margin-top: 20px;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 20px;
}
.proof-item {
  padding: 20px 10px;
  background: var(--color-surface);
  border-top: 3px solid var(--color-line-strong);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.proof-value {
  font-family: var(--font-mono);
  font-size: 50px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.proof-item:first-child .proof-value { color: var(--flag); }
.proof-label {
  font-family: var(--font-mono);
  font-size: 23px;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--color-dim);
}

.inter-last {
  margin-top: 32px;
  font-family: var(--font-mono);
  font-size: 26px;
  color: var(--color-dimmer);
  min-height: 34px;
}
.inter-last b { color: var(--color-dim); font-weight: 600; }
.inter-last .ok { color: var(--color-positive); }

/* ── the winner ───────────────────────────────────────────────────────────── */

.result {
  position: absolute;
  inset: 0;
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: var(--color-surface);
}
.result.show { display: flex; }
.result-kicker {
  font-family: var(--font-mono);
  font-size: 30px;
  font-weight: 700;
  letter-spacing: 0.5em;
  color: var(--flag);
  margin-bottom: 24px;
}
.result-who { display: flex; align-items: center; gap: 24px; }
.result-silk { width: 54px; height: 54px; border-radius: 5px; }
.result-wallet {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: 76px;
  font-weight: 700;
  letter-spacing: -0.01em;
}
.result-purse {
  margin-top: 14px;
  font-family: var(--font-mono);
  font-size: 132px;
  font-weight: 800;
  line-height: 1.05;
  color: var(--flag);
  font-variant-numeric: tabular-nums;
}
.result-usd { font-family: var(--font-mono); font-size: 32px; color: var(--color-dim); }

/* The receipt is the point. A number on a screen is a claim; a confirmed
   signature is the only part of this a stranger can go and check. */
.result-receipt {
  margin-top: 30px;
  min-height: 66px;
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 14px 28px;
  font-family: var(--font-mono);
  font-size: 28px;
  color: var(--color-dim);
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  opacity: 0;
  transition: opacity 200ms linear;
}
.result-receipt.show { opacity: 1; }
.result-receipt.done { border-color: var(--color-positive); }
.result-receipt .tick { color: var(--color-positive); font-weight: 700; }
.result-receipt .sig { color: var(--color-dimmer); font-size: 24px; }
.result-receipt.bad { border-color: var(--color-danger); }
.result-receipt.bad .tick { color: var(--color-danger); }
.result-receipt.wait { border-color: var(--color-warn); }
.result-receipt.wait .tick { color: var(--color-warn); }

/* ── verifiability: copy chips and Solscan links ───────────────────────────
   Nothing in here may change the frame at rest. The chip is a button pinned back
   to exactly what the plain text rendered as, and the link's own colour rule is
   zero-specificity so every existing `.sig` / `.toast-sig` colour still wins. */

.copy-chip {
  position: relative;
  margin: 0;
  padding: 0;
  appearance: none;
  background: none;
  border: 0;
  border-radius: 0;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  line-height: inherit;
  text-align: inherit;
  text-transform: inherit;
  cursor: pointer;
}

a:where(.tx-link) {
  color: inherit;
  text-decoration: none;
}

a:where(.tx-link):hover {
  text-decoration: underline;
}

/* The toast stack is click-through so it never eats a click meant for the race;
   the one thing in it worth clicking has to opt back in. */
.toast-sig .tx-link { pointer-events: auto; }

/* Broadcast type: the confirmation has to be readable from a stream, and the
   winner's name sits in a clipped box, so the colour flip carries it there. */
.copy-chip[data-copied]::after {
  content: attr(data-copied);
  position: absolute;
  left: 0;
  top: -1.4em;
  font-family: var(--font-cond);
  font-size: 14px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-positive);
  white-space: nowrap;
  pointer-events: none;
}

/* The refusal state only: the chip is holding a full address for the moment the
   reader has to select it by hand, and forty-four characters will not sit on the
   one line a stub was sized for. It wraps and stays selectable rather than
   running out of its cell -- the layout moving matters less than every character
   being reachable, and it is back to a stub two and a half seconds later. */
.copy-chip[data-copied]:not([data-copied="Copied"]) {
  color: var(--color-warn);
  white-space: normal;
  word-break: break-all;
  /* Chrome will not let a drag select inside a button, so a selection lost to a
     stray click could not be made again. */
  user-select: text;
}

.copy-chip[data-copied]:not([data-copied="Copied"])::after { color: var(--color-warn); }

.copy-chip.is-copied { color: var(--color-positive); }

.copy-chip:focus-visible {
  outline: 1px solid var(--color-line-strong);
  outline-offset: 2px;
}

.result-places {
  margin-top: 30px;
  display: flex;
  gap: 44px;
  font-family: var(--font-mono);
  font-size: 28px;
  color: var(--color-dimmer);
}

/* ── the money popups ─────────────────────────────────────────────────────── */

.toasts {
  position: absolute;
  right: 40px;
  top: 34px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  align-items: flex-end;
  pointer-events: none;
}

.toast {
  min-width: 560px;
  background: var(--color-surface);
  border: 1px solid var(--color-line-strong);
  border-left: 6px solid var(--color-line-strong);
  padding: 18px 24px;
  animation: toast-in 260ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.toast.out { animation: toast-out 320ms ease forwards; }
.toast.paid { border-left-color: var(--flag); }
.toast.failed { border-left-color: var(--color-danger); }
.toast.unresolved { border-left-color: var(--color-warn); }

.toast-head {
  display: flex;
  justify-content: space-between;
  gap: 24px;
  font-family: var(--font-mono);
  font-size: 21px;
  font-weight: 600;
  letter-spacing: 0.16em;
  color: var(--color-dimmer);
}
.toast-body {
  margin-top: 8px;
  font-family: var(--font-mono);
  font-size: 46px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.toast.paid .toast-body { color: var(--flag); }
.toast.failed .toast-body { color: var(--color-danger); }
.toast-note { margin-top: 6px; font-family: var(--font-mono); font-size: 24px; color: var(--color-dim); }
/* A transaction id is a receipt anyone can check. It is the difference between a
   number on screen and a claim about money that nobody can verify. */
.toast-sig { margin-top: 8px; font-family: var(--font-mono); font-size: 20px; color: var(--color-dimmer); }

@keyframes toast-in { from { opacity: 0; transform: translateY(-14px); } to { opacity: 1; transform: none; } }
@keyframes toast-out { to { opacity: 0; transform: translateY(-10px); } }

/* ── footer ───────────────────────────────────────────────────────────────── */

.foot-left {
  font-family: var(--font-mono);
  font-size: 24px;
  font-weight: 600;
  letter-spacing: 0.2em;
}
.foot-mid { color: var(--color-warn); font-size: 26px; }
.foot-right { display: flex; gap: 46px; font-family: var(--font-mono); font-size: 24px; }
.foot-item b { color: var(--color-text); font-weight: 700; }

/* ── arcade: type and weight ────────────────────────────────────────────────
   Appended deliberately, so every layout decision above -- which sizes were
   measured against a compressed 480px frame and were right -- stays exactly as
   it was. This changes what things look like, not where they are.          */

/* Anything that does not name a family lands here, so the default is
   signage rather than a UI sans that belongs to neither family. */
body { font-family: var(--font-cond); }

.brand-name {
  font-family: var(--font-cond);
  font-size: 34px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-shadow: none;
}

.conn { font-family: var(--font-cond); font-weight: 500; letter-spacing: 0.12em; text-transform: uppercase; }

.stat-label {
  font-family: var(--font-cond);
  font-weight: 500;
  font-size: 21px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-dimmer);
}
.stat-value {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 40px;
  text-shadow: none;
}
/* The Purse leans. A number set on a slant is the oldest trick a racing game
   has for making a static figure look like it is moving, and it costs nothing. */
.stat-hero .stat-value {
  font-size: 62px;
  display: inline-block;
  font-weight: 700;
  text-shadow: none;
}
.stat-sub { font-family: var(--font-mono); font-weight: 400; }

/* ── arcade: the lower third ───────────────────────────────────────────────
   Opaque blocks that sit on the turf rather than tinting it. A translucent panel
   over grass is a smear once the bitrate drops. */

.row, .lower-more {
  background: var(--color-surface-2);
  border: 2px solid var(--color-line-strong);
  border-bottom-width: 2px;
  box-shadow: 5px 5px 0 var(--shadow-ink);
}
.row-pos {
  font-family: var(--font-cond);
  font-weight: 700;
  font-size: 33px;
  color: var(--color-dim);
}
.row-addr { font-family: var(--font-mono); font-weight: 700; font-size: 27px; }
.row-gap {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 27px;
  color: var(--color-text);
}
.lower-more {
  font-family: var(--font-cond);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-dim);
}

/* The leader's block is the one thing on screen that changes hands, so it is the
   one thing that changes colour when it does. */
.row.lead {
  background: var(--flag);
  border-color: var(--flag-dim);
}
.row.lead .row-pos, .row.lead .row-addr, .row.lead .row-gap { color: var(--flag-ink); }

/* ── arcade: commentary ────────────────────────────────────────────────────── */

.callout {
  background: var(--flag);
  color: var(--flag-ink);
  border-left: none;
  border: 3px solid var(--flag-dim);
  box-shadow: 6px 6px 0 var(--shadow-ink);
  font-family: var(--font-cond);
  font-weight: 800;
  font-size: 38px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  transform: translateX(-50%);
}
/* Punches in rather than fading. Fading is what a dialog does; this is a
   commentator raising their voice. */
.callout.show { animation: callout-punch 170ms cubic-bezier(0.2, 1.5, 0.4, 1); }
.callout.hot { background: var(--color-text); border-color: var(--flag); color: var(--flag-ink); }
@keyframes callout-punch {
  from { transform: translateX(-50%) scale(1.18); }
  to   { transform: translateX(-50%) scale(1); }
}

/* ── arcade: footer ────────────────────────────────────────────────────────── */

.foot-left {
  font-family: var(--font-cond);
  font-weight: 700;
  font-size: 26px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.foot-right { font-family: var(--font-cond); font-weight: 500; font-size: 26px; letter-spacing: 0.12em; text-transform: uppercase; }
.foot-item b { font-family: var(--font-mono); font-weight: 700; font-size: 24px; color: var(--flag); }
.bar-foot { color: var(--color-dim); }

/* ── arcade: motion ─────────────────────────────────────────────────────────

   The complaint that produced this section, and it was the right one: the HUD
   was furniture. Counted rather than felt — across a fifty second Race the whole
   interface owned five opacity fades, one width transition (on the *between*
   races screen), two toast slides and one callout punch. Nothing on the race
   screen moved. The Gate was a state change, the wire was a state change, and
   the money was a label that occasionally held a different number.

   So four things move now, and each is pinned to something that actually
   happened rather than to a timer: the rail runs with the Race, the Purse moves
   when the Engine says it moved, the HUD clears at the Gate and comes back, and
   the wire gets announced.

   The list the lighting pass wrote down still applies and matters more here,
   because motion is where it gets broken: no glows, no particles, no sparkles,
   no long eases. Snap in, hold, snap out. An encoder at crf 32 throws away slow
   movement and thin moving things first and keeps hard-edged blocks that jump.
   Everything below is 150-260ms, opaque, and moves in whole pixels.          */

/* Any gap the Gate opens up reads as the world rather than as a hole in it. */
.stage { background: var(--color-bg); }

/* ── the rail ─────────────────────────────────────────────────────────────── */

/* It floats, inset, with the same hard offset shadow the running order uses.
   Flush to the header it read as the header getting taller -- a loading bar
   welded to the chrome rather than an instrument you look at. */
.rail {
  position: absolute;
  top: 22px;
  left: 40px;
  right: 40px;
  height: 76px;
  z-index: 3;
  opacity: 0;
}
.rail.show { opacity: 1; }

.rail-bed {
  position: relative;
  height: 62px;
  background: var(--color-surface-3);
  border: 3px solid var(--color-line-strong);
  box-shadow: 6px 6px 0 var(--shadow-ink);
  overflow: hidden;
}

/* Ground covered, and inside it the band the Field actually occupies. Two blocks
   rather than a number: at stream size "how far through" and "how strung out"
   are both read from shape, and neither survives being written as text. */
.rail-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 0%;
  background: var(--flag-dim);
}
.rail-pack {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 0%;
  background: var(--flag);
}

/* The poles that are already out there on the turf, at the same five percent, so
   the rail measures the same trip the horses are running. */
.rail-poles { position: absolute; inset: 0; }
.rail-poles i {
  position: absolute;
  top: 0;
  width: 4px;
  height: 16px;
  background: rgba(255, 255, 255, 0.28);
}
.rail-poles i.quarter { height: 100%; width: 5px; background: rgba(255, 255, 255, 0.34); }

/* The leader, in the silks they are wearing on the track. Only the leader: the
   first four finish within a fraction of a percent of each other, so four chips
   would be one chip with three hidden underneath it. */
.rail-runners { position: absolute; inset: 0; }
.rail-chip {
  position: absolute;
  top: 5px;
  width: 40px;
  height: 42px;
  margin-left: -20px;
  border: 4px solid var(--color-text);
  box-shadow: 5px 0 0 var(--shadow-ink);
}

/* The winning post. A block, not a line — a line is the first thing an encoder
   throws away, and this is the one mark on the rail that has to stay legible. */
/* A post, and taller than the bed it stands in, because at a quarter size a mark
   flush with its container is not a mark. */
.rail-wire {
  position: absolute;
  top: -6px;
  right: -4px;
  width: 26px;
  height: 76px;
  background: repeating-conic-gradient(var(--flag-ink) 0% 25%, var(--flag) 0% 50%) 0 0 / 13px 13px;
  box-shadow: 6px 6px 0 var(--shadow-ink);
}

/* Inside the final furlong the rail stops being an instrument and becomes a
   warning. Same shape, one colour flip — the cheapest thing an encoder can carry
   and the loudest thing a phone can see. */
.rail.hot .rail-fill { background: var(--flag); }
.rail.hot .rail-pack { background: var(--color-text); }

/* ── the Gate: everything clears, and comes back ──────────────────────────── */

/* A Race used to begin by the numbers in the corner changing. Now the furniture
   gets out of the way, the turf is bare for a beat with the field standing on
   it, and the HUD returns a piece at a time — which is what a broadcast does
   when it cuts to the start, and it costs four transforms. */
.bar, .bar-foot, .rail, .lower {
  transition:
    transform 190ms cubic-bezier(0.2, 0.8, 0.3, 1),
    opacity 160ms linear;
}
.stage.gate > .bar      { transform: translateY(-100%); }
.stage.gate > .bar-foot { transform: translateY(100%); }
.stage.gate .rail       { transform: translateY(-100%); }
.stage.gate .lower      { transform: translateY(150%); }

/* Coming back, staggered, so it assembles rather than appears. */
.stage.gate-in > .bar      { transition-delay: 0ms; }
.stage.gate-in .rail       { transition-delay: 90ms; }
.stage.gate-in > .bar-foot { transition-delay: 160ms; }
.stage.gate-in .lower      { transition-delay: 230ms; }

/* ── the Purse, moving ────────────────────────────────────────────────────── */

/* It was a label that sometimes held a different number. Now it counts, and the
   count is a tween between two figures the Engine sent — never a rate, never an
   increment this page invented. Both ends of the animation are the Engine's. */
.stat-line { position: relative; }
.stat-hero.bumped .stat-value { animation: purse-bump 240ms cubic-bezier(0.2, 1.6, 0.4, 1); }
@keyframes purse-bump {
  from { transform: scale(1.16); }
  to   { transform: scale(1); }
}

/* Fees landing, said on the number they land in rather than only in a corner
   popup. Flies off the Purse and is gone in a second. */
.stat-delta {
  position: absolute;
  right: 0;
  top: -34px;
  font-family: var(--font-mono);
  font-size: 30px;
  font-weight: 700;
  color: var(--flag);
  text-shadow: 3px 3px 0 var(--shadow-ink);
  opacity: 0;
  pointer-events: none;
  white-space: nowrap;
}
.stat-delta.fly { animation: delta-fly 1100ms cubic-bezier(0.2, 0.7, 0.3, 1); }
@keyframes delta-fly {
  0%   { opacity: 0; transform: translateY(16px); }
  18%  { opacity: 1; transform: translateY(0); }
  70%  { opacity: 1; transform: translateY(-8px); }
  100% { opacity: 0; transform: translateY(-32px); }
}

/* The pool on the countdown screen is the same money one step earlier, so it gets
   the same treatment -- but its own keyframes. The Purse's bump carries the skew
   the Purse is set at, and borrowing it here tipped the pool over for a quarter
   second and then snapped it upright. */
.proof-item { position: relative; }
.proof-item.bumped .proof-value { animation: pool-bump 240ms cubic-bezier(0.2, 1.6, 0.4, 1); }
@keyframes pool-bump {
  from { transform: scale(1.16); }
  to   { transform: scale(1); }
}
/* Centred over the tile by the box rather than by a transform: the fly animation
   owns `transform`, and a centring one would be thrown away the moment it ran. */
#poolDelta { left: 0; right: 0; top: -30px; text-align: center; }

/* ── the wire ─────────────────────────────────────────────────────────────── */

/* Fires off the shared simulation the instant the leader crosses, which is ahead
   of the Engine's result by however long a message takes. That is exactly why it
   says the Race is won, names nobody, and mentions no money: the only thing it
   is allowed to know at this point is that somebody crossed. */
.winflash {
  position: absolute;
  /* High, and not centred. The Engine's result lands about half a second after
     the wire, so for the rest of the shout's life it shares the screen with the
     winner's panel -- and a celebration that covers the wallet and the amount
     for a second and a half is the one thing this beat must not do. This clears
     the rail above it and the WINNER kicker below it. */
  top: 9%;
  left: 0;
  right: 0;
  display: flex;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  z-index: 6;
}
.winflash span {
  padding: 16px 68px;
  background: var(--flag);
  border: 4px solid var(--flag-dim);
  box-shadow: 12px 12px 0 var(--shadow-ink);
  font-family: var(--font-cond);
  font-weight: 800;
  font-size: 118px;
  line-height: 1.1;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--flag-ink);
  white-space: nowrap;
}
.winflash.show { animation: winflash 1500ms cubic-bezier(0.2, 0.9, 0.3, 1); }
@keyframes winflash {
  0%   { opacity: 0; transform: scale(1.9); }
  9%   { opacity: 1; transform: scale(1); }
  74%  { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.14); }
}

/* ── the winner's panel ───────────────────────────────────────────────────── */

/* It used to fade in over a translucent black laid on a Race that was still
   being drawn underneath — ghost horses and a leftover wallet tag showing
   through it, which at a low bitrate is just dirt on the lens. Opaque, and it
   punches. */
.result { background: var(--color-surface); }
.result.show { animation: result-in 260ms cubic-bezier(0.2, 1.35, 0.4, 1); }
@keyframes result-in {
  from { opacity: 0; transform: translateY(26px); }
  to   { opacity: 1; transform: none; }
}
/* The money arrives after the name, hard, on its own beat. */
.result.show .result-purse { animation: purse-land 300ms 150ms cubic-bezier(0.2, 1.5, 0.4, 1) both; }
@keyframes purse-land {
  from { opacity: 0; transform: scale(1.5); }
  to   { opacity: 1; transform: scale(1); }
}
/* The receipt is the payoff, so it stamps rather than fades. It still only ever
   appears in whatever state the chain actually reported. */
.result-receipt.show { animation: stamp 220ms cubic-bezier(0.2, 1.5, 0.4, 1); }
@keyframes stamp {
  from { opacity: 0; transform: scale(1.3) rotate(-2deg); }
  to   { opacity: 1; transform: none; }
}

/* The countdown backdrop was the same translucent black over the same live
   canvas, on the screen that is up for most of every cycle. */
.interstitial { background: var(--color-bg); }

/* The callout and the popups move down to clear the rail. */
.callout { top: 120px; }
.toasts  { top: 120px; }

/* Stacking, now that four things want the same middle of the screen: the
   countdown sits over the track, the winner's panel over that, the shout over
   the panel, and the money popups over everything, because a payout that failed
   must never end up behind a celebration. */
.interstitial { z-index: 4; }
.result       { z-index: 5; }
.toasts       { z-index: 7; }

/* ── the countdown, run as a race ─────────────────────────────────────────── */

/* The screen that is up for most of every cycle had one moving part: a clock
   whose digits changed. So the countdown gets the rail too — sitting in the
   layout rather than floating over the track, running to the Gate instead of to
   the wire, and going hot in the last ten seconds the same way the race rail
   goes hot in the final furlong. */
.gate-rail {
  position: relative;
  top: auto;
  left: auto;
  right: auto;
  width: 1180px;
  height: 76px;
  /* Sits below the money popups rather than under them. A claim lands at T-10s,
     top right, and it is opaque -- at 30px the rail ran straight behind it and
     the winning post came out the other side. */
  margin: 104px auto 0;
  z-index: auto;
}
.gate-rail .rail-fill { transition: width 1s linear; }
.gate-rail.hot .rail-fill { background: var(--flag); }

/* The last ten seconds. The clock stops being a readout and starts being a
   countdown — one punch per second, which is the whole difference between a
   number that is decreasing and a number that is running out. */
.inter-clock.urgent { color: var(--flag); }
.inter-clock.beat { animation: clock-beat 260ms cubic-bezier(0.2, 1.5, 0.4, 1); }
@keyframes clock-beat {
  from { transform: scale(1.11); }
  to   { transform: scale(1); }
}

/* ── arcade: the money popups ─────────────────────────────────────────────── */

/* These were the one thing left wearing the old skin: a translucent near-black
   card with a thin coloured left border, sliding out of a corner. Which is both
   the most default object in interface design and, on a lit blue screen, plainly
   a piece of somebody else's dashboard.

   They are also the most important thing on the page, because they are the only
   moment the money is visible. So they get the loudest object language the
   broadcast has — opaque block, thick ink border, hard offset shadow — and they
   arrive by snapping in rather than easing. */
.toast {
  min-width: 600px;
  padding: 20px 28px 22px;
  background: var(--color-surface-2);
  border: 3px solid var(--color-line-strong);
  border-left-width: 3px;
  box-shadow: 9px 9px 0 var(--shadow-ink);
  animation: toast-punch 220ms cubic-bezier(0.2, 1.35, 0.4, 1);
}
@keyframes toast-punch {
  from { opacity: 0; transform: translateX(46px) scale(0.96); }
  to   { opacity: 1; transform: none; }
}
.toast.out { animation: toast-leave 240ms cubic-bezier(0.4, 0, 1, 1) forwards; }
@keyframes toast-leave {
  to { opacity: 0; transform: translateX(46px); }
}

/* Status is carried by the whole block, not by a six-pixel stripe down one edge.
   A stripe is the first thing lost to a downscale; a filled block is the last. */
.toast.paid       { background: var(--flag);          border-color: var(--flag-dim); }
.toast.claimed    { background: var(--flag);          border-color: var(--flag-dim); }
.toast.failed     { background: var(--color-danger);  border-color: #a52a22; }
.toast.unresolved { background: var(--color-warn);    border-color: #c47600; }

.toast-head {
  font-family: var(--font-cond);
  font-weight: 600;
  font-size: 25px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-dim);
}
.toast-body {
  margin-top: 6px;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 50px;
  line-height: 1.05;
  text-shadow: none;
}
.toast-note { font-family: var(--font-mono); font-weight: 400; font-size: 25px; }

/* On a filled block the text is the dark ink, and the shadow that made it pop on
   blue would only muddy it. */
.toast.paid .toast-head,
.toast.claimed .toast-head,
.toast.unresolved .toast-head { color: #6b4b00; }
.toast.failed .toast-head { color: #ffd9d6; }
.toast.paid .toast-body,
.toast.claimed .toast-body,
.toast.unresolved .toast-body { color: var(--flag-ink); text-shadow: none; }
.toast.failed .toast-body { color: #fff; text-shadow: none; }
.toast.paid .toast-note,
.toast.claimed .toast-note,
.toast.unresolved .toast-note { color: #6b4b00; }
.toast.sending .toast-head { color: var(--color-dim); }
.toast.sending .toast-body,
.toast.sending .toast-note { color: var(--color-text); }
.toast.failed .toast-note { color: #ffd9d6; }

/* The signature stays quiet and stays monospaced. It is not a headline — it is
   the thing somebody can go and check, and it has to be transcribable. */
.toast-sig { font-family: var(--font-mono); }
.toast.paid .toast-sig,
.toast.claimed .toast-sig,
.toast.unresolved .toast-sig { color: #7a5800; }
.toast.failed .toast-sig { color: #f0b5b0; }

/* Still sending is not yet money, so it does not get to look like money. */
.toast.sending { background: var(--color-surface-2); border-color: var(--color-line-strong); }

/* The countdown rail lines up with the pitch under it rather than floating at
   its own width. */
.gate-rail { width: 1500px; }

/* Screenshot only: hold every animation exactly where it is. The canvas stops
   when the draw loop stops; CSS does not, and the beats worth photographing are
   all shorter than the time it takes to take the photograph. */
body.frozen *, body.frozen *::before, body.frozen *::after {
  animation-play-state: paused !important;
}
