/* =============================================================================
   bust.fun — PLINKO styles  (games/plinko/plinko.css)  — OWNER A (+B for fx CSS)
   -----------------------------------------------------------------------------
   Game-SPECIFIC styles only. The shared chrome (.panel/.row/.seg/.preview/.stat/
   .muted/.field) is owned centrally in styles.css — not forked here. Synthwave
   tokens only (tokens.css); never a hard-coded palette. a11y: 44px touch targets,
   0 horizontal overflow @360px, reduced-motion no-ops all motion.
   ========================================================================== */

/* ---- rows / risk selectors: STACK the two gv-controls segmented fields full-width (Rows above Risk)
   rather than side-by-side. Side-by-side gave each field only ~half the narrow rail (~102px), too tight
   for its three 44px touch-target buttons (8|12|16 / Low|Med|High) — so the last button WRAPPED to a
   second line (the "vertical, tall-to-short, looks bad" report). Full-width gives each field the whole
   rail (~220-288px), so all three buttons sit on ONE horizontal row. The segmented control itself uses
   the shared .seg button.active chrome from styles.css — no bespoke chip styling forked here. ------- */
.plinko-controls { gap: var(--sp-3); flex-direction: column; align-items: stretch; }

/* ---- the board stage (canvas) -------------------------------------------- */
.plinko-stage {
  position: relative;
  /* CENTER the board in the display column: `margin-inline: auto` (with the max-width cap below) keeps the
     stage — and therefore the peg lattice, which layoutBoard already centers WITHIN the canvas — horizontally
     centered whether or not the chat dock is docked (the container-query column shrinks, the stage re-centers
     in it). Plinko OWNS its centering here so it never depends on a shared-file `margin-inline` rule; the
     top/bottom margin stays the original vertical rhythm. */
  margin: var(--sp-3) auto var(--sp-2);
  border-radius: var(--r-lg);
  background:
    radial-gradient(120% 80% at 50% 0%, var(--brand-soft), transparent 60%),
    linear-gradient(180deg, var(--bg-1), var(--surface-2));
  border: 1px solid var(--line-2);
  overflow: hidden;
  box-shadow: inset 0 0 40px rgba(0, 0, 0, .35);
}
.plinko-canvas {
  display: block;
  width: 100%;
  /* JS (layoutBoard + the fx sizeCanvas) sets the authoritative inline height per the viewport
     (the board now fills the tall display). This is only the PRE-RENDER placeholder so the box
     doesn't pop from a short 240px to the taller computed height on first paint — keep it close
     to the computed floor and let it grow with the viewport. Kept in lockstep with layoutBoard's
     `vh - 250` reserve (the drop-button row + preview strip were removed, freeing vertical space). */
  height: clamp(260px, calc(100vh - 250px), 760px);
  max-width: 100%;
}

/* =============================================================================
   HIDE THE ORPHANED EMPTY RESULT CARD (audit: a visibly empty bordered "broken card" in the left rail).
   The shared #resultCard is the shell's per-bet verdict card; between bets the shell parks it in the
   `.result-card-empty` state (shell.js clearResultCard) which is MEANT to be an invisible, in-flow height
   reserve (visibility:hidden) so the footer below it doesn't shift as the card cycles. But there is no CSS
   rule for `.result-card-empty` in the shared styles — so it renders as a fully-visible empty .result-card
   (its --surface-2 fill + --line-2 border) with a reserved 112px min-height: an empty bordered box.
   Plinko NEVER uses the shared #resultCard — its verdict lives in the in-board #plinkoLanded badge + the lit
   bucket (the shared card stays empty the whole session) — so on the plinko page that reserve box is pure
   orphaned chrome. Hide it ONLY in its EMPTY reserve state and ONLY on the plinko surface (body[data-game=
   "plinko"]), so if the shell ever did populate it (it doesn't for plinko — a populated card drops the
   `.result-card-empty` class) the real verdict would still show. Scoped, plinko-only, no shared-file edit. */
body[data-game="plinko"] #resultCard.result-card-empty {
  display: none;
}

/* landed multiplier badge (announced via role=status; appears AFTER the server result) */
.plinko-landed {
  position: absolute; top: var(--sp-3); left: 50%; transform: translateX(-50%);
  padding: var(--sp-1) var(--sp-3); border-radius: var(--r-pill);
  font-family: var(--font-display); font-weight: var(--fw-bold); font-size: var(--fs-lg);
  background: var(--surface-4); border: 1px solid var(--line-2); color: var(--fg);
  box-shadow: var(--shadow-2);
  animation: plk-badge-in var(--dur-3) var(--ease-spring) both;
}
.plinko-landed.is-win { color: var(--win); border-color: var(--win); box-shadow: var(--glow-win); }
/* THIN WIN (F4): a profit below the 2× celebrate threshold (a centre-board 1.0×–1.99× grind, which on a
   low-risk 16-row board is almost every drop). It IS a profit so it must NOT read as a loss — but the
   full green glow over-celebrates a +$0.01 grind and dilutes the genuine edge hits. So a thin win gets a
   calm, neutral-positive acknowledgement: the foreground tints toward win (a hint of green) with the
   default chrome border + NO win glow. Only ≥2× gets the full .is-win neon. */
.plinko-landed.is-thinwin { color: color-mix(in srgb, var(--win) 55%, var(--fg)); border-color: var(--line-2); }
/* the lose badge text sits on the elevated --surface-4 fill (.plinko-landed) — --muted (3.21:1) fails
   AA there; --muted-2 lifts it ≥4.5:1 (contrast audit, systemic --muted-on-elevated-surface fix). */
.plinko-landed.is-lose { color: var(--muted-2); }
/* The per-ball payout figure (counts up via fx.numberRamp on a profit). tabular-nums so the ramp
   doesn't jitter the badge width frame-to-frame. Inherits the badge color/weight. Tokens only. */
.plk-landed-pay { font-variant-numeric: tabular-nums; }
@keyframes plk-badge-in { 0% { opacity: 0; transform: translate(-50%, -8px) scale(.9); } 100% { opacity: 1; transform: translate(-50%, 0) scale(1); } }

/* in-flight counter (how many balls are currently falling) — bottom-left, quiet */
.plinko-flight {
  position: absolute; left: var(--sp-2); bottom: var(--sp-2);
  padding: 2px var(--sp-2); border-radius: var(--r-pill);
  font-family: var(--font-display); font-weight: var(--fw-semi); font-size: var(--fs-xs);
  color: var(--cyan); background: rgba(0, 0, 0, .35); border: 1px solid var(--line-2);
  pointer-events: none; letter-spacing: .02em;
}

/* =============================================================================
   BOARD EMPTY-STATE — the "Press Bet to drop" idle cue over the inert pre-bet board.
   The pre-bet board is a static peg field with no call to action; this overlay tells the player how to
   start. It sits ABSOLUTELY over the SAME box the board fills (the stage is position:relative; overflow:
   hidden), so showing it at rest / clearing it while balls rain adds NO layout box and never shifts the
   board — idle→drop→idle stays shift-free. It's a hint, not a control: pointer-events:none so nothing is
   intercepted, aria-hidden so the #plinkoLanded role=status badge owns the real announcement. Tokens only.
   Visible only when .on (toggled off by reflectEmpty() while any ball is in flight). Mirrors .dt-empty.
   ========================================================================== */
.plk-empty {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-3);
  text-align: center;
  pointer-events: none;        /* a hint, never intercepts the board below */
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-2) var(--ease-out), visibility var(--dur-2);
  z-index: 3;                  /* above the canvas + flight counter, below nothing interactive */
}
.plk-empty.on { opacity: 1; visibility: visible; }
.plk-empty-glyph {
  font-size: clamp(28px, 8vw, 40px);
  line-height: 1;
  color: var(--cyan);
  filter: drop-shadow(0 0 12px var(--cyan-glow, var(--brand-soft)));
  opacity: .9;
  animation: plk-empty-bob 1600ms var(--ease-in-out, ease-in-out) infinite;
}
.plk-empty-title {
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: var(--fw-bold, 700);
  color: var(--fg);
  max-width: 22ch;
  line-height: 1.3;
}
.plk-empty-sub {
  font-size: var(--fs-xs);
  color: var(--muted);
  max-width: 30ch;
  line-height: 1.4;
}
/* the ↓ glyph gently bobs to read as "drop happens here" (motion-gated below) */
@keyframes plk-empty-bob {
  0%, 100% { transform: translateY(-3px); }
  50%      { transform: translateY(3px); }
}

/* The shared shell Bet button drops single balls (each click = one independent, server-authoritative ball;
   rapid clicks rain). The AUTO-RAIN toggle now lives in its own labelled "Mode" .seg field (built beside
   the Rows/Risk fields) so it sits INSIDE the bet cluster, not orphaned on its own wrapped row — it holds
   the click for you, flips to a pulsing "Stop" while engaged, and self-halts on out-of-balance / over-cap
   / leaving the board. Its field never grows, so Rows/Risk take the slack and Auto stays a tidy fixed body. */
.plinko-controls > .seg:has(.plinko-auto) { flex: 0 0 auto; }
.plinko-auto {
  width: 100%;                 /* fill its (non-growing) Mode field so the body matches the segment height */
  min-height: 44px;            /* HARD: 44px touch target; aligns with the segmented control bodies */
  padding: 0 var(--sp-4);
  border-radius: var(--r-md);
  border: 1px solid var(--line-2);
  background: linear-gradient(180deg, var(--surface-3), var(--surface-2));
  color: var(--fg);
  font-family: var(--font-display); font-weight: var(--fw-bold); letter-spacing: .03em;
  cursor: pointer;
  transition: border-color var(--dur-2), box-shadow var(--dur-2), color var(--dur-2);
}
.plinko-auto:hover { border-color: var(--brand); box-shadow: var(--glow-brand); }
.plinko-auto:focus-visible { outline: none; box-shadow: var(--ring); border-color: var(--brand); }
.plinko-auto.on { border-color: var(--lose); color: var(--lose); box-shadow: 0 0 14px var(--lose-glow); }
.plinko-auto.on::before {
  content: ""; display: inline-block; width: 7px; height: 7px; margin-right: 7px;
  border-radius: 50%; background: var(--lose); vertical-align: middle;
  animation: plk-auto-pulse 900ms ease-in-out infinite;
}
@keyframes plk-auto-pulse { 0%, 100% { opacity: .4; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .plinko-auto.on::before { animation: none; opacity: 1; } }

/* ---- bucket multiplier row (under the board) -----------------------------
   ALIGNMENT (the headline fix): the labels MUST sit dead-centre under their bucket
   columns + landing slots on the canvas. The canvas grid is inset by `pad` px on each
   side and split into (rows+1) EQUAL columns of width (w − 2·pad)/(rows+1) with NO gap.
   So this row mirrors that EXACTLY: JS sets padding-left/right to `pad` px (per board),
   the row has NO column gap, and each cell is flex:1 1 0 — making each label cell the
   same width as a canvas bucket column and centred under it. A `gap` here would shift
   every label off its column, so it is deliberately ZERO. The tiny inter-cell hairline
   is drawn with an inset border instead, so it never steals column width. */
.plinko-buckets {
  display: flex; gap: 0;
  margin: 0 0 var(--sp-3);
  /* padding-left/right are set INLINE by buildBoard() to the board's `pad` (px). */
}
/* DESKTOP UNDER-FILL FIX (F5): the shared shell caps the stage at a flat 520px
   (styles/12-gameview.css `.gameview-display .plinko-stage { max-width:520px }`), which leaves ~296px
   of dead horizontal gutter in an 816px desktop column — a narrow strip in a wide dark panel. This file
   loads AFTER styles.css, so an equal-specificity rule here wins the cascade: let the stage GROW toward
   the column on wide viewports while keeping the ≤520px behaviour where the column is narrow (mobile/
   tablet stay unchanged — min() picks 520 there). The board art (pegs/buckets) scales with the width via
   layoutBoard, so a wider stage = larger, more legible pegs without any JS change. The height cap
   (cssW*1.55 in layoutBoard) keeps it a pleasant portrait, not a tower. */
.gameview-display .plinko-stage { max-width: min(680px, 100%); }
@media (min-width: 1100px) { .gameview-display .plinko-stage { max-width: min(720px, 100%); } }

/* The label row is a SIBLING of the stage that would otherwise span the FULL panel width — so it MUST
   track the stage's box at every width or the labels drift off the canvas columns (measured 92px @834,
   33px @1280 against the old flat 520 cap). Mirror the EXACT SAME cap as the stage above (in lockstep)
   so the label row coincides with the canvas stage box; then the JS pad-inset + equal no-gap columns land
   each label dead-centre under its bucket column. At narrow widths both resolve to 100% → identical box. */
.gameview-display .plinko-buckets,
.plinko-buckets { width: 100%; max-width: min(680px, 100%); margin-inline: auto; }
@media (min-width: 1100px) {
  .gameview-display .plinko-buckets,
  .plinko-buckets { max-width: min(720px, 100%); }
}
.plk-bucket {
  flex: 1 1 0; min-width: 0;
  margin: 0 1px;                    /* a 1px visual gutter that does NOT change column centre */
  /* --heat 0..1 drives the colour ramp from cool centre to hot edges */
  --bk-h: calc(280deg - var(--heat, 0) * 230deg);   /* violet → cyan → gold-ish hue sweep */
  padding: var(--sp-2) 1px;
  border: 0; border-radius: var(--r-sm);
  background:
    linear-gradient(180deg,
      hsla(var(--bk-h), 80%, calc(46% + var(--heat, 0) * 14%), calc(.22 + var(--heat, 0) * .55)),
      hsla(var(--bk-h), 70%, 30%, calc(.18 + var(--heat, 0) * .4)));
  color: var(--fg);
  font-family: var(--font-display);
  font-weight: var(--fw-semi);
  /* FIT-TO-CELL font: scale by the actual per-column width (--plk-colw, set by buildBoard) so a label
     like "0.5×" always fits a 16-row (17-column) board at 360px — NEVER ellipsis-clipped to hide a
     value (the non-misleading bar). ~0.46×colw keeps 4 glyphs inside the cell, capped at the chrome's
     --fs-xs so wide boards don't balloon. The 2.4vw term is a fallback before --plk-colw is set (first
     paint). LEGIBILITY FLOOR (F3): the old 7px floor rendered the 16-row labels at ~7.8px on a 390px
     phone — below comfortable/AA-legible body size for the payout table the player reads pre-bet (D21).
     Raise the floor to 8.5px (a max() wrapped around the clamp so it NEVER drops below 8.5px regardless
     of column width); the 16-row "0.5×"/"16×" glyphs still fit at 8.5px in a ~21px cell (overflow:hidden
     remains a safety net but is not hit). At desktop the cells stay ~11px (the --fs-xs cap), unchanged. */
  font-size: max(8.5px, clamp(8.5px, min(2.4vw, calc(var(--plk-colw, 22px) * 0.46)), var(--fs-xs)));
  letter-spacing: -0.01em;
  line-height: 1.1;
  text-align: center;
  white-space: nowrap; overflow: hidden;
  cursor: default;
  transition: transform var(--dur-2) var(--ease-spring), box-shadow var(--dur-2), filter var(--dur-2);
}
.plk-bucket.lit { transform: translateY(2px); filter: brightness(1.5); }
.plk-bucket.lit-win {
  box-shadow: 0 0 0 1px var(--win), var(--glow-win);
  animation: plk-bucket-flash var(--dur-4) var(--ease-out) both;
}
.plk-bucket.lit-lose {
  box-shadow: 0 0 0 1px var(--line-3);
  animation: plk-bucket-dim var(--dur-3) var(--ease-out) both;
}
@keyframes plk-bucket-flash {
  0% { filter: brightness(2.4); transform: translateY(-3px) scale(1.06); }
  100% { filter: brightness(1.5); transform: translateY(2px) scale(1); }
}
@keyframes plk-bucket-dim {
  0% { filter: brightness(1.6); }
  100% { filter: brightness(1); }
}

/* ---- responsive: keep 0 overflow @360px --------------------------------- */
@media (max-width: 420px) {
  /* narrow phones: a shorter placeholder floor (JS still sets the authoritative inline height). Kept in
     lockstep with layoutBoard's `vh - 250` reserve (drop row + preview strip removed). */
  .plinko-canvas { height: clamp(220px, calc(100vh - 250px), 520px); }
  /* drop the 1px gutter + horizontal padding on the tightest screens so 16-row labels keep max text
     room; margins are symmetric so removing them does not move any label off its column. The font is
     already fit-to-cell via --plk-colw above, so this only reclaims gutter pixels. */
  .plk-bucket { padding: var(--sp-1) 0; margin: 0; letter-spacing: -0.02em; }
}

/* =============================================================================
   RUNNING BATCH WIN TOTAL — one pill over the stage that ticks up as batch balls land
   ("+$4.20" → "+$4.85"), so the per-ball floats can stay small/capped without losing the win story.
   Display-only theatrics: the figure is a BigInt sum of the SERVER per-ball payouts formatted by the
   shared money helper (plinko.js tickBatchWin) — never money math. Appears with the batch's first
   winning ball, fades ~1s after the LAST ball lands (the aggregate #plinkoLanded verdict takes over).
   Top-right so it never collides with the top-centre landed badge or the bottom-left flight counter.
   aria-hidden in JS — the role=status badge owns the announcement. Tokens only.
   ========================================================================== */
.plinko-batch-total {
  position: absolute; top: var(--sp-3); right: var(--sp-2);
  padding: 2px var(--sp-2); border-radius: var(--r-pill);
  font-family: var(--font-display); font-weight: var(--fw-bold); font-size: var(--fs-md, 14px);
  font-variant-numeric: tabular-nums;  /* per-land updates never jitter the pill width */
  color: var(--win); background: rgba(0, 0, 0, .35); border: 1px solid var(--line-2);
  pointer-events: none; letter-spacing: .02em;
  z-index: 2;                          /* above the canvas, below the idle-hint overlay (z:3) */
  transition: opacity var(--dur-3) var(--ease-out);
}
.plinko-batch-total.out { opacity: 0; }
/* a subtle settle-pop each time a winning ball ticks the total (restarted via reflow in JS) */
.plinko-batch-total.tick { animation: plk-total-tick var(--dur-2) var(--ease-out); }
@keyframes plk-total-tick { 0% { transform: scale(1.12); } 100% { transform: scale(1); } }

/* ---- reduced motion: no-op every animation (drop is snapped in JS) ------- */
@media (prefers-reduced-motion: reduce) {
  .plinko-landed,
  .plk-bucket.lit-win,
  .plk-bucket.lit-lose { animation: none !important; }
  .plk-bucket { transition: none !important; }
  /* the idle hint still SHOWS/HIDES (the CTA is legible) but without the fade or the bobbing glyph */
  .plk-empty { transition: none !important; }
  .plk-empty-glyph { animation: none !important; }
  /* the batch running total still RENDERS (the figure is information) but without fade/pop theatrics */
  .plinko-batch-total { transition: none !important; }
  .plinko-batch-total.tick { animation: none !important; }
}

/* Multi-drop total-cost line ("Bet drops N balls · $X total") — display-only companion to the Balls
   selector; empty (collapsed) at N=1 so the single-ball layout is untouched. */
.plinko-total-hint { font-size: var(--fs-sm, 0.8125rem); min-height: 0; }
.plinko-total-hint:empty { display: none; }
