/* Pickle Arcade: Trellis, the fifth cabinet.
 *
 * arcade.css carries the site register and the --arc-* tokens on
 * body[data-app="minigame"]; arena.css carries the pieces every cabinet shares
 * (.arc-band, .arc-tube, .arc-players, .arc-pending, .arc-lamp, .arc-notes).
 * This sheet carries what Trellis alone asks for: the screen-and-rail table,
 * the canvas, the grid it scrolls, the line and its white head, the price pill,
 * the tiles, the amber bets, the money floating off them, and the rail's
 * console. Every class here is prefixed tre- and every class the markup uses
 * must exist here or in a sheet that loads before it, because
 * tests/design-language.test.mjs walks both.
 *
 * THE CABINET IS BUILT LIKE CRASH: A SCREEN AND A RAIL. The board used to be a
 * full-bleed canvas with the stake ladder, the purse, the feed tag and the wins
 * list floating over it, and a console of three panels a screen below. Two
 * things were wrong with that at once. The ladder sat over the bottom row of
 * squares for sale, so the squares under it could not be pressed; and none of
 * it was the arcade's grammar - every other cabinet is a lit band with a 2px
 * tube, a screen, and a plate beside or under it. Now nothing sits on the
 * screen but the board and the wins, and the rail carries every reading and
 * every control.
 *
 * NOT ONE HEX IN THIS FILE. Every colour resolves to a shared-ui token or to
 * --arc-trellis, the cabinet light arcade.css declares for the hall's ticket.
 *
 * COLOUR IS A GRAMMAR. Pink is the market: the grid, the line, the axis, the
 * price, the tiles. White is NOW: the last few seconds of the line and its
 * head. Amber is a stake, the estate's own --pickle-amber. A hollow square is a
 * bet that lost. Nothing else on the canvas is coloured.
 *
 * THE ESTATE'S RADIUS IS 2px, AND SO IS EVERY CORNER HERE. The squares were
 * drawn at rx 12 - pills on a board whose every other edge on the estate is
 * square - and the pill on the axis at 13. They are tiles now.
 *
 * NO INLINE style= ANYWHERE. Everything that varies per frame is an SVG
 * geometry attribute written by JS, and everything that varies per state is a
 * class. Font sizes on fitted figures are presentation ATTRIBUTES and this
 * sheet must not set them: a declaration here beats the attribute.
 *
 * MOTION IS A LAYER, NOT THE PICTURE. Every rule with a duration lives inside
 * @media (prefers-reduced-motion: no-preference) at the end of this file. The
 * scroll itself is not in that layer: a board whose columns never arrived would
 * not be the game, and it is a frame loop rather than a CSS animation.
 */

/* ===================================================================== tokens
 * On .tre-band so they cannot leak to another cabinet. Every one derived: the
 * ground from shared-ui, the market from --arc-trellis, the stake from
 * --pickle-amber. */
.tre-band {
  --tre-pink: var(--arc-trellis);
  --tre-pink-hi: var(--arc-trellis-hi);
  --tre-pink-dim: color-mix(in srgb, var(--arc-trellis) 38%, transparent);
  --tre-pink-faint: color-mix(in srgb, var(--arc-trellis) 14%, transparent);
  --tre-pink-ghost: color-mix(in srgb, var(--arc-trellis) 6%, transparent);
  --tre-amber: var(--pickle-amber);
  /* Ink is what is WRITTEN ON a staked square, read against amber. */
  --tre-ink: var(--pickle-bg-deep);
  --tre-now: var(--pickle-text);
  /* The two axes in pixels: the canvas draws in CSS pixels once measured,
     and AXIS_H_PX and AXIS_W_PX in minigame/lib/trellis-state.js are these
     numbers. */
  --tre-axis-h: 26px;
  --tre-axis-w: 74px;
}

/* ====================================================================== table
 * The screen and the rail, one grid with a definite height so the canvas has
 * a box to fill and the rail has one to scroll inside. Taller than Crash's
 * 62dvh because nine rows of tiles are the game; capped so the rail's ladder
 * is never below the fold on a laptop. */
.tre-table {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 20rem;
  /* The whole screen under the nav, and no more: the board is the page.
     Measured, the nav stands 73px tall against its 72px token - a rule
     under it - and that one pixel was a scrollbar's worth of page. */
  height: max(28rem, calc(var(--arc-stage) - 1px));
  /* AND THE ROW IS THAT HEIGHT, NOT "AT LEAST" IT. Left implicit, the row was
     `auto`, which stretches to the table but never shrinks below its content -
     and the content is an SVG whose viewBox is its own measured size, so its
     aspect ratio at the screen's width set a minimum height of its own. On a
     wide, short window that minimum beat the table: measured at 1920 by 960
     the table was 887px and the screen 1004px, the page scrolled 117px and
     the bottom row of the board sat under the fold. The measure then fed that
     height back into the viewBox, so it never corrected itself. minmax(0, 1fr)
     pins the row to the table; the canvas takes what the row gives it. */
  grid-template-rows: minmax(0, 1fr);
}

/* The screen: the estate's own ground, a shade lighter at the top so the
   grid reads as lit from above rather than printed on black. Clipped, never
   hidden: hidden makes a scroll container. */
.tre-screen {
  position: relative;
  isolation: isolate;
  min-width: 0;
  height: 100%;
  overflow: clip;
  background: linear-gradient(180deg, var(--pickle-surface-2), var(--pickle-bg));
}

/* The rail: page ground beside the screen's glass, so the two read as a
   picture and the panel that explains it. */
.tre-rail {
  display: flex;
  flex-direction: column;
  min-width: 0;
  height: 100%;
  overflow-y: auto;
  background: var(--pickle-bg);
  border-left: 1px solid var(--pickle-line);
}

@media (max-width: 1023px) {
  .tre-table {
    grid-template-columns: minmax(0, 1fr);
    /* Stacked, the screen and the rail are two rows of their own heights:
       the screen's is fixed below and the rail's is its content. */
    grid-template-rows: none;
    height: auto;
  }

  /* Tall enough for nine rows to be pressable with a thumb, short enough
     that the ladder under it is on the first screen of an 812px phone. */
  .tre-screen { height: min(var(--arc-stage-phone), 46dvh); min-height: 19rem; }

  .tre-rail {
    height: auto;
    overflow: visible;
    border-left: 0;
    border-top: 1px solid var(--pickle-line);
  }
}

/* ===================================================================== canvas
 * `preserveAspectRatio="none"` on purpose: this is a chart, not a drawing. The
 * grid fills the screen at any window. */
.tre-canvas {
  display: block;
  width: 100%;
  height: 100%;
  /* NOTHING ON THIS CANVAS IS A TARGET EXCEPT A SQUARE. Declared once on the
     root and re-enabled on the one element that is a target. */
  pointer-events: none;
}

/* The frame loop writes a new transform on this group sixty times a second;
   without the hint the browser re-rasterises several hundred nodes on each. */
.tre-scroll { will-change: transform; }

/* ======================================================================= grid */
.tre-rule {
  stroke: var(--tre-pink-faint);
  stroke-width: 1;
}

/* The price rules a shade stronger than the time rules: the price axis is
   what a square is checked against. */
.tre-axis .tre-rule { stroke: var(--tre-pink-dim); }

/* Where the columns for sale begin. */
.tre-rule-open { stroke: var(--tre-pink-dim); }

.tre-dot { fill: var(--tre-pink-dim); }

/* No font-size: TrellisStage sets it and skips columns too narrow for one. */
.tre-clock {
  fill: var(--tre-pink-dim);
  font-family: var(--pickle-font-mono);
  text-anchor: middle;
}

.tre-price {
  fill: var(--tre-pink);
  font-family: var(--pickle-font-mono);
  font-size: 11px;
  text-anchor: start;
}

/* ======================================================================= tiles
 * An open square is a TILE: a faint surface at the estate's radius with the
 * multiplier on it, so a column reads as a column of things to press rather
 * than as a column of figures on graph paper. */
.tre-cell-face {
  fill: var(--tre-pink-ghost);
  stroke: none;
  pointer-events: none;
}

/* THE HIT TARGET IS THE WHOLE SQUARE AND IT TAKES THE POINTER WHATEVER IT IS
   PAINTED WITH. `pointer-events: all`, not `auto`: for an SVG shape `auto`
   means visiblePainted, and a rect whose fill is `none` is then hit only on
   its stroke. That is exactly what the hover rule used to do to this rect -
   set `fill: none` to draw an outline - so the moment a pointer entered a
   square, the square stopped being under it, the hover ended, the fill came
   back, and it flickered between the two states for as long as the pointer
   stayed. A press landed on whichever state was current: the "sometimes I
   have to click several times". The outline is drawn on the face now, and
   this rect is never painted. */
.tre-cell-hit {
  fill: transparent;
  stroke: none;
  pointer-events: all;
}

.tre-cell,
.tre-bet { cursor: pointer; }
.tre-cell:focus,
.tre-bet:focus { outline: none; }

/* The multiplier, in the estate's mono at display weight. No font-size: it is
   fitted to the square. The trailing x is a unit, smaller and dimmer. */
.tre-mult {
  fill: var(--tre-pink);
  font-family: var(--pickle-font-mono);
  font-weight: 600;
  letter-spacing: -0.02em;
  /* Start, not middle: the figure and its cross are centred together by
     TrellisStage, which needs a known left edge. */
  text-anchor: start;
  pointer-events: none;
}

/* The multiplier's cross, a drawn mark beside the figure: it is a unit, so it
   is dimmer than the figure it follows. */
.tre-mult-ico {
  fill: none;
  stroke: var(--tre-pink-dim);
  stroke-width: 1.7;
  stroke-linecap: round;
  pointer-events: none;
}

.tre-cell:hover .tre-mult-ico,
.tre-cell:focus-visible .tre-mult-ico { stroke: var(--tre-pink); }

/* The same cross and the player mark on a staked square, in the square's ink. */
.tre-bet-ico {
  fill: color-mix(in srgb, var(--tre-ink) 60%, transparent);
  stroke: color-mix(in srgb, var(--tre-ink) 60%, transparent);
  stroke-width: 1.7;
  stroke-linecap: round;
  pointer-events: none;
}

.tre-bet-lost .tre-bet-ico {
  fill: var(--tre-pink-dim);
  stroke: var(--tre-pink-dim);
}

/* A square the model prices past the game's ceiling: a dash, not a blank. */
.tre-mult-none {
  fill: var(--tre-pink-dim);
  font-weight: 400;
}

/* A shut column: hatched, dim, no cursor. It is on the board and not for
   sale, and has to look like neither an open square nor a fault. */
.tre-shut-line {
  stroke: var(--tre-pink-faint);
  stroke-width: 1;
}

.tre-cell-shut { cursor: default; }
.tre-cell-shut .tre-cell-face { fill: url(#tre-shut); }
.tre-cell-shut .tre-mult { fill: var(--tre-pink-dim); }
.tre-cell-shut .tre-mult-ico { stroke: var(--tre-pink-faint); }

/* Brightest nearest the first column for sale, fading with distance right. */
.tre-away-0 { opacity: 1; }
.tre-away-1 { opacity: 0.84; }
.tre-away-2 { opacity: 0.7; }
.tre-away-3 { opacity: 0.56; }

/* Pointed at or carried by the keyboard: the face lights and takes an
   outline. A hollow outline and a lit face, never the amber fill: the one
   thing this board must never do is make "considering" look like "staked". */
.tre-cell:hover .tre-cell-face,
.tre-cell:focus-visible .tre-cell-face {
  fill: var(--tre-pink-faint);
  stroke: var(--tre-pink);
  stroke-width: 1.5;
}

.tre-cell:hover .tre-mult,
.tre-cell:focus-visible .tre-mult { fill: var(--tre-pink-hi); }

.tre-bet:hover .tre-bet-box,
.tre-bet:focus-visible .tre-bet-box { stroke: var(--tre-now); stroke-width: 1.5; }

/* The finger is down and has not lifted: pressed, visibly, before anything
   has been sent. */
.tre-armed .tre-cell-face {
  fill: var(--tre-pink-dim);
  stroke: var(--tre-pink-hi);
  stroke-width: 2;
}

.tre-armed .tre-bet-box { stroke: var(--tre-now); stroke-width: 2.5; }

/* A press waiting in the queue: a pink outline, pulsing under motion. */
.tre-pick {
  fill: none;
  stroke: var(--tre-pink-hi);
  stroke-width: 2;
  pointer-events: none;
}

/* How many presses are still queued on one square. Amber: money about to move. */
.tre-waiting {
  fill: var(--tre-amber);
  font-family: var(--pickle-font-mono);
  font-weight: 700;
  text-anchor: end;
  pointer-events: none;
}

/* ======================================================================= bets */
.tre-bet-box {
  fill: var(--tre-amber);
  stroke: none;
}

/* The player's own bet, marked by an outline rather than by a different fill:
   a second amber would be a second meaning for the colour that means staked. */
.tre-bet-mine {
  fill: none;
  stroke: var(--tre-now);
  stroke-width: 2;
  pointer-events: none;
}

/* No font-size on any of the three: fitted in TrellisStage. */
.tre-bet-locked {
  fill: color-mix(in srgb, var(--tre-ink) 60%, transparent);
  font-family: var(--pickle-font-mono);
  text-anchor: start;
  pointer-events: none;
}

.tre-bet-who {
  fill: color-mix(in srgb, var(--tre-ink) 60%, transparent);
  font-family: var(--pickle-font-mono);
  text-anchor: end;
  pointer-events: none;
}

.tre-bet-stake {
  fill: var(--tre-ink);
  font-family: var(--pickle-font-mono);
  font-weight: 700;
  text-anchor: start;
  pointer-events: none;
}

/* THE LINE HAS TOUCHED THIS SQUARE. It is a win from this instant, and the
   square says so before the chain has sealed the column: the amber goes
   bright, a white edge comes on, and under motion it flashes. */
.tre-bet-hit .tre-bet-box {
  fill: var(--tre-amber);
  stroke: var(--tre-now);
  stroke-width: 2;
}

/* A bet that lost: the fill goes, the count stays. Nothing is red. */
.tre-bet-lost .tre-bet-box {
  fill: none;
  stroke: var(--tre-pink-faint);
  stroke-width: 1;
}

.tre-bet-lost .tre-bet-stake,
.tre-bet-lost .tre-bet-locked { fill: var(--tre-pink-faint); }
.tre-bet-lost .tre-bet-who { fill: var(--tre-pink-dim); }

/* ======================================================= the squares behind
 * A settled square keeps its place until the grid carries it off, quieter
 * than a live one: nothing can be done about it any more. */
.tre-past-box {
  fill: none;
  stroke: var(--tre-pink-dim);
  stroke-width: 1;
}

.tre-past-amount {
  fill: var(--tre-pink-dim);
  font-family: var(--pickle-font-mono);
  text-anchor: middle;
}

/* A square that PAID, or that the line has touched and will pay at the seal:
   bright amber with a white edge, the grammar's "a stake that paid", and the
   figure in ink on it. It was amber at 22%, which read as a stake still
   standing rather than as money won. */
.tre-past.won .tre-past-box {
  fill: var(--tre-amber);
  stroke: var(--tre-now);
  stroke-width: 1.5;
}

.tre-past.won .tre-past-amount {
  fill: var(--tre-ink);
  font-weight: 700;
}
.tre-past.lost .tre-past-box { stroke: var(--tre-pink-ghost); }
.tre-past.lost .tre-past-amount { fill: var(--tre-pink-faint); }

.tre-past-mine {
  fill: none;
  stroke: var(--tre-now);
  stroke-width: 1.5;
  pointer-events: none;
}

/* ======================================================================= line
 * No vector-effect: this canvas is stretched on both axes, which is the
 * condition that trips Chrome's screen-space dash bug, and nothing here needs
 * a constant screen width badly enough to risk it. */
.tre-line {
  stroke: var(--tre-pink);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}

/* The last few seconds, in white, over the pink. */
.tre-line-tail {
  stroke: var(--tre-now);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.tre-head { fill: var(--tre-now); }

/* ======================================================================= pill */
.tre-pill-box { fill: var(--tre-pink); }

.tre-pill-text {
  fill: var(--tre-ink);
  font-family: var(--pickle-font-mono);
  font-size: 14px;
  font-weight: 600;
  text-anchor: middle;
}

/* ======================================================================= wins
 * The amount rises off the square it was won on, amber with a dark outline so
 * it stays legible over anything. paint-order puts the outline UNDER the fill. */
.tre-win-dot {
  fill: none;
  stroke: var(--tre-amber);
  stroke-width: 2;
}

.tre-win-amount {
  fill: var(--tre-amber);
  stroke: var(--tre-ink);
  stroke-width: 4;
  paint-order: stroke;
  font-family: var(--pickle-font-mono);
  font-size: 26px;
  font-weight: 700;
  text-anchor: start;
}

/* The sparks a hit throws: amber, and gone in under a second. Still under
   reduced motion, where they read as a small star on the square. */
.tre-spark {
  stroke: var(--tre-amber);
  stroke-width: 2;
  stroke-linecap: round;
}

.tre-burst { pointer-events: none; }

/* The list of recent wins, bottom left over the screen, above the time axis.
   It takes no pointer: the squares under it are for sale. */
.tre-feed {
  position: absolute;
  left: var(--pickle-space-4);
  bottom: calc(var(--tre-axis-h) + var(--pickle-space-3));
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 3px;
  pointer-events: none;
}

.tre-feed-row {
  display: flex;
  gap: var(--pickle-space-3);
  align-items: baseline;
  font-family: var(--pickle-font-mono);
  font-size: var(--pickle-text-xs);
}

.tre-feed-who { color: var(--tre-pink); }

.tre-feed-amount {
  color: var(--tre-amber);
  font-weight: 700;
}

.tre-feed-row:nth-child(2) { opacity: 0.72; }
.tre-feed-row:nth-child(3) { opacity: 0.5; }
.tre-feed-row:nth-child(4) { opacity: 0.32; }
.tre-feed-row:nth-child(5) { opacity: 0.18; }

/* The screen's one tag: the socket is gone. Amber is this estate's warn. */
.tre-hud-off {
  position: absolute;
  top: var(--pickle-space-4);
  left: var(--pickle-space-4);
  margin: 0;
  padding: var(--pickle-space-1) var(--pickle-space-3);
  color: var(--pickle-amber);
  font-family: var(--pickle-font-mono);
  font-size: var(--pickle-text-2xs);
  background: var(--arc-plate-hard);
  border: 1px solid var(--pickle-amber-line);
  border-radius: var(--pickle-radius);
  pointer-events: none;
}

/* The board is draining: it keeps its bands while squares stand on them, and
   the far columns wait. The line sits top right, over those waiting columns
   and clear of the wins feed and the disconnected tag, and takes no pointer so
   the squares under it still press. Amber, because it is a warning about the
   board and not a fault in it. */
.tre-resizing {
  position: absolute;
  top: var(--pickle-space-4);
  right: calc(var(--tre-axis-w) + var(--pickle-space-4));
  max-width: min(38ch, 55%);
  margin: 0;
  padding: var(--pickle-space-1) var(--pickle-space-3);
  color: var(--pickle-amber);
  font-family: var(--pickle-font-mono);
  font-size: var(--pickle-text-2xs);
  line-height: 1.45;
  text-align: right;
  background: var(--arc-plate-hard);
  border: 1px solid var(--pickle-amber-line);
  border-radius: var(--pickle-radius);
  pointer-events: none;
}

/* ===================================================================== paused
 * The board is GONE, not greyed: a grid left beside "the price is stale" is a
 * grid somebody will press. */
.tre-paused {
  position: absolute;
  inset: 0;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: var(--pickle-space-3);
  padding: var(--pickle-gutter);
  text-align: center;
  background: var(--pickle-bg);
}

.tre-paused-head {
  margin: 0;
  color: var(--tre-pink);
  font-family: var(--pickle-font-display);
  font-size: var(--pickle-text-xl);
  font-weight: 600;
  letter-spacing: var(--pickle-tracking-tight);
}

.tre-paused-why {
  margin: 0;
  max-width: 44ch;
  color: var(--pickle-muted);
  font-family: var(--pickle-font-mono);
  font-size: var(--pickle-text-sm);
}

/* ======================================================================= rail
 * The plate that says what is true and holds the only control this game has
 * besides the squares. Crash's .arc-cabin, restated for this rail's width. */
.tre-cabin {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--pickle-space-3);
  padding: var(--pickle-space-4) var(--pickle-space-4) var(--pickle-space-5);
  border-bottom: 1px solid var(--pickle-line);
  font-family: var(--pickle-font-mono);
  font-size: var(--pickle-text-xs);
  color: var(--pickle-muted);
  flex: 0 0 auto;
}

/* Actions are display face on this estate, not mono. */
.tre-cabin .ex-btn { font-family: var(--pickle-font-display); }

/* The reading: the pair as a caps label, the price as the display figure in
   the cabinet's own light. */
.tre-quote {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--pickle-space-2) var(--pickle-space-3);
}

.tre-quote-pair {
  color: var(--pickle-subtle);
  font-size: var(--pickle-text-2xs);
  letter-spacing: var(--pickle-tracking-caps);
  text-transform: uppercase;
}

.tre-quote-price {
  font-size: clamp(1.6rem, 2.1vw, 2rem);
  color: var(--tre-pink);
}

/* No board, or no answer yet: a figure nobody has been given is not a zero. */
.tre-quote-price.tre-idle { color: var(--pickle-subtle); }

/* A field label, in components.css's .ex-meta voice. */
.tre-label {
  margin: var(--pickle-space-2) 0 calc(var(--pickle-space-2) * -1);
  color: var(--pickle-subtle);
  font-size: .68rem;
  letter-spacing: .16em;
  text-transform: uppercase;
}

/* The ladder: four rungs of equal width, each the estate's 44px tap. */
.tre-stakes {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--pickle-space-2);
}

.tre-stake {
  flex-direction: column;
  gap: 1px;
  min-height: var(--pickle-tap);
  padding: var(--pickle-space-2) var(--pickle-space-1);
  line-height: 1.2;
}

.tre-stake-eth {
  font-size: var(--pickle-text-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* The rung's ether, which must never spill into the next rung: clipped with an
   ellipsis rather than allowed to overlap, though at six decimals it fits any
   rail this sheet lays out. */
.tre-stake-usd {
  max-width: 100%;
  overflow: hidden;
  color: var(--pickle-subtle);
  font-family: var(--pickle-font-mono);
  font-size: var(--pickle-text-2xs);
  font-variant-numeric: tabular-nums;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The chosen rung is named by ink AND by a filled edge, and it is amber:
   amber is a stake on this board, and this is the stake. */
.tre-stake.on,
.tre-stake.on:hover {
  color: var(--tre-amber);
  border-color: var(--tre-amber);
  background: var(--pickle-amber-soft);
}

.tre-stake.on .tre-stake-usd { color: color-mix(in srgb, var(--tre-amber) 70%, transparent); }

/* What is left to stake, under the ladder that spends it. */
.tre-purse {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--pickle-space-1) var(--pickle-space-3);
}

.tre-purse-what {
  color: var(--pickle-subtle);
  font-size: var(--pickle-text-2xs);
  letter-spacing: var(--pickle-tracking-caps);
  text-transform: uppercase;
}

.tre-purse-sum {
  color: var(--tre-amber);
  font-size: var(--pickle-text-lg);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.tre-purse-eth { color: var(--pickle-muted); }

.tre-purse a {
  color: var(--pickle-accent-text);
  text-decoration: underline;
  text-underline-offset: .2em;
}

/* One sentence, in the reading face, on how a press works. */
.tre-hint {
  margin: 0;
  color: var(--pickle-subtle);
  font-family: var(--pickle-font);
  font-size: var(--pickle-text-xs);
  line-height: 1.55;
}

/* The two lists under the plate. */
.tre-room,
.tre-mine {
  padding: var(--pickle-space-4);
  border-bottom: 1px solid var(--pickle-line);
  font-family: var(--pickle-font-mono);
  font-size: var(--pickle-text-xs);
  color: var(--pickle-muted);
}

.tre-h {
  margin: 0 0 var(--pickle-space-3);
  color: var(--pickle-text);
  font-family: var(--pickle-font-display);
  font-size: var(--pickle-text-sm);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.tre-empty {
  margin: 0;
  color: var(--pickle-subtle);
}

.tre-stand,
.tre-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--pickle-space-1);
}

.tre-stand-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: var(--pickle-space-3);
  align-items: baseline;
  padding: var(--pickle-space-1) 0;
  border-bottom: 1px solid var(--pickle-line);
}

/* Your own row is named by ink and by the word "you", never by colour alone. */
.tre-stand-row.me .tre-stand-who { color: var(--tre-now); }

.tre-stand-who { color: var(--pickle-muted); }

.tre-stand-stake {
  color: var(--pickle-text);
  font-variant-numeric: tabular-nums;
}

.tre-stand-mult {
  color: var(--tre-amber);
  font-variant-numeric: tabular-nums;
}

.tre-row {
  display: grid;
  grid-template-columns: 3.5rem minmax(0, 1fr) auto;
  gap: var(--pickle-space-3);
  align-items: start;
  padding: var(--pickle-space-2) 0;
  border-bottom: 1px solid var(--pickle-line);
}

.tre-row-mult { color: var(--tre-amber); }

.tre-row-band,
.tre-row-state {
  display: grid;
  gap: 2px;
  color: var(--pickle-text);
}

.tre-row-state { text-align: right; }

.tre-row-when,
.tre-row-stake {
  color: var(--pickle-subtle);
  font-size: var(--pickle-text-2xs);
}

.tre-row-won .tre-row-state { color: var(--pickle-accent-text); }
.tre-row-hit .tre-row-state { color: var(--tre-amber); }
.tre-row-lost .tre-row-state { color: var(--pickle-muted); }
.tre-row-refunded .tre-row-state { color: var(--arc-unknown); }
.tre-row-open .tre-row-state { color: var(--arc-pending); }

/* ===================================================================== motion
 * Everything with a duration, and nothing above this line has one. Under
 * reduce the same board, the same tiles and the same figures arrive at their
 * end state; the tube's own strike is arena.css's `.arc-band .arc-tube`. */
@media (prefers-reduced-motion: no-preference) {
  .tre-screen {
    animation: arc-screen-on 260ms var(--pickle-ease) 480ms both;
    transform-origin: 50% 50%;
  }

  .tre-cabin { animation: ex-in-sm 480ms var(--pickle-ease) 700ms both; }
  .tre-room { animation: ex-in-sm 480ms var(--pickle-ease) 780ms both; }
  .tre-mine { animation: ex-in-sm 480ms var(--pickle-ease) 860ms both; }

  .tre-cell-face { transition: fill 120ms ease-out, stroke 120ms ease-out; }
  .tre-mult { transition: fill 120ms ease-out; }

  /* A press waiting: the outline breathes, so a square with something in
     flight on it is never mistaken for one nobody touched. */
  .tre-pick { animation: tre-breathe 900ms ease-in-out infinite; }

  @keyframes tre-breathe {
    50% { stroke-opacity: 0.35; }
  }

  /* A bet landing on a square: the amber arrives rather than appears, from
     the tile's own centre. */
  .tre-bet-box {
    transform-box: fill-box;
    transform-origin: center;
    animation: tre-land 260ms var(--pickle-ease) both;
  }

  @keyframes tre-land {
    from {
      opacity: 0.3;
      transform: scale(0.9);
    }
  }

  /* The touched square flashes white and settles bright amber. `.hit` is on
     the square from the touch to the seal, so the flash plays once, when the
     class arrives, and the settlement that follows changes nothing visible. */
  .tre-bet-hit .tre-bet-box,
  .tre-past.hit .tre-past-box { animation: tre-flash 700ms ease-out 2 both; }

  @keyframes tre-flash {
    0% { fill: var(--tre-now); }
    100% { fill: var(--tre-amber); }
  }

  /* Each spark runs out along its own x, which its group has already turned
     to point away from the hit. */
  .tre-spark { animation: tre-spark 720ms cubic-bezier(0.1, 0.8, 0.3, 1) forwards; }

  @keyframes tre-spark {
    0% {
      transform: translateX(0);
      opacity: 1;
      stroke-width: 3;
    }
    100% {
      transform: translateX(26px);
      opacity: 0;
      stroke-width: 0.5;
    }
  }

  .tre-win { animation: tre-rise 2600ms cubic-bezier(0.2, 0.7, 0.3, 1) forwards; }

  @keyframes tre-rise {
    0% {
      opacity: 0;
      transform: translate(0, 0) scale(0.8);
    }
    12% {
      opacity: 1;
      transform: translate(6px, -10px) scale(1);
    }
    100% {
      opacity: 0;
      transform: translate(40px, -120px) scale(0.55);
    }
  }

  /* The ring is the hit; the rising figure is the amount. Separate, because
     they read at different distances and need different durations. */
  .tre-win-dot {
    transform-box: fill-box;
    transform-origin: center;
    animation: tre-ring 620ms cubic-bezier(0.1, 0.8, 0.3, 1) forwards;
  }

  @keyframes tre-ring {
    0% {
      opacity: 0.9;
      stroke-width: 3;
      transform: scale(0.3);
    }
    100% {
      opacity: 0;
      stroke-width: 1;
      transform: scale(2.4);
    }
  }
}
