.not-found-page {
  box-sizing: border-box;
  display: grid;
  min-height: 100vh;
  margin: 0;
  padding: 32px 20px;
  place-items: center;
  background: radial-gradient(ellipse at 50% 35%, #24211c 0, #141414 50%, #101010 100%);
  color: #f2f2f2;
  font-family: Arial, sans-serif;
  text-align: center;
}
body:has(> .not-found-page) { margin: 0; background: #101010; }
.not-found-card { width: 460px; max-width: 100%; }
.not-found-brand { display: inline-flex; align-items: center; gap: 10px; text-decoration: none; }
.not-found-mark { width: 58px; height: auto; }
.not-found-word { width: 154px; height: auto; }
.not-found-code {
  margin-top: 40px;
  color: #292722;
  font-size: clamp(100px, 22vw, 172px);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -8px;
  text-shadow: 0 1px 0 #4c4030;
}
.not-found-accent { width: 44px; height: 3px; margin: 24px auto; border-radius: 3px; background: #ff9e45; }
.not-found-card h1 { margin: 0 0 14px; font-size: 32px; font-weight: 700; letter-spacing: -1px; }
.not-found-card p { margin: 0; color: #a5a29e; font-size: 15px; line-height: 1.7; }
.not-found-home {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 22px;
  margin-top: 32px;
  padding: 13px 22px;
  border: 1px solid #78502a;
  border-radius: 8px;
  background: #292016;
  color: #ffb36b;
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  transition: background-color 120ms ease, border-color 120ms ease;
}
.not-found-home:hover { background: #382a1b; border-color: #ff9e45; }
.not-found-home:focus-visible, .not-found-brand:focus-visible { outline: 2px solid #ff9e45; outline-offset: 5px; }
/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
@layer properties {
  @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
    *, :before, :after, ::backdrop {
      --tw-rotate-x: initial;
      --tw-rotate-y: initial;
      --tw-rotate-z: initial;
      --tw-skew-x: initial;
      --tw-skew-y: initial;
      --tw-space-y-reverse: 0;
      --tw-border-style: solid;
      --tw-shadow: 0 0 #0000;
      --tw-shadow-color: initial;
      --tw-shadow-alpha: 100%;
      --tw-inset-shadow: 0 0 #0000;
      --tw-inset-shadow-color: initial;
      --tw-inset-shadow-alpha: 100%;
      --tw-ring-color: initial;
      --tw-ring-shadow: 0 0 #0000;
      --tw-inset-ring-color: initial;
      --tw-inset-ring-shadow: 0 0 #0000;
      --tw-ring-inset: initial;
      --tw-ring-offset-width: 0px;
      --tw-ring-offset-color: #fff;
      --tw-ring-offset-shadow: 0 0 #0000;
      --tw-outline-style: solid;
      --tw-blur: initial;
      --tw-brightness: initial;
      --tw-contrast: initial;
      --tw-grayscale: initial;
      --tw-hue-rotate: initial;
      --tw-invert: initial;
      --tw-opacity: initial;
      --tw-saturate: initial;
      --tw-sepia: initial;
      --tw-drop-shadow: initial;
      --tw-drop-shadow-color: initial;
      --tw-drop-shadow-alpha: 100%;
      --tw-drop-shadow-size: initial;
    }
  }
}

@layer theme {
  :root, :host {
    --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
      "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
    --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
      "Courier New", monospace;
    --spacing: .25rem;
    --default-transition-duration: .15s;
    --default-transition-timing-function: cubic-bezier(.4, 0, .2, 1);
    --default-font-family: var(--font-sans);
    --default-mono-font-family: var(--font-mono);
  }
}

@layer base {
  *, :after, :before, ::backdrop {
    box-sizing: border-box;
    border: 0 solid;
    margin: 0;
    padding: 0;
  }

  ::file-selector-button {
    box-sizing: border-box;
    border: 0 solid;
    margin: 0;
    padding: 0;
  }

  html, :host {
    -webkit-text-size-adjust: 100%;
    tab-size: 4;
    line-height: 1.5;
    font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");
    font-feature-settings: var(--default-font-feature-settings, normal);
    font-variation-settings: var(--default-font-variation-settings, normal);
    -webkit-tap-highlight-color: transparent;
  }

  hr {
    height: 0;
    color: inherit;
    border-top-width: 1px;
  }

  abbr:where([title]) {
    -webkit-text-decoration: underline dotted;
    text-decoration: underline dotted;
  }

  h1, h2, h3, h4, h5, h6 {
    font-size: inherit;
    font-weight: inherit;
  }

  a {
    color: inherit;
    -webkit-text-decoration: inherit;
    -webkit-text-decoration: inherit;
    -webkit-text-decoration: inherit;
    text-decoration: inherit;
  }

  b, strong {
    font-weight: bolder;
  }

  code, kbd, samp, pre {
    font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);
    font-feature-settings: var(--default-mono-font-feature-settings, normal);
    font-variation-settings: var(--default-mono-font-variation-settings, normal);
    font-size: 1em;
  }

  small {
    font-size: 80%;
  }

  sub, sup {
    vertical-align: baseline;
    font-size: 75%;
    line-height: 0;
    position: relative;
  }

  sub {
    bottom: -.25em;
  }

  sup {
    top: -.5em;
  }

  table {
    text-indent: 0;
    border-color: inherit;
    border-collapse: collapse;
  }

  :-moz-focusring {
    outline: auto;
  }

  progress {
    vertical-align: baseline;
  }

  summary {
    display: list-item;
  }

  ol, ul, menu {
    list-style: none;
  }

  img, svg, video, canvas, audio, iframe, embed, object {
    vertical-align: middle;
    display: block;
  }

  img, video {
    max-width: 100%;
    height: auto;
  }

  button, input, select, optgroup, textarea {
    font: inherit;
    font-feature-settings: inherit;
    font-variation-settings: inherit;
    letter-spacing: inherit;
    color: inherit;
    opacity: 1;
    background-color: #0000;
    border-radius: 0;
  }

  ::file-selector-button {
    font: inherit;
    font-feature-settings: inherit;
    font-variation-settings: inherit;
    letter-spacing: inherit;
    color: inherit;
    opacity: 1;
    background-color: #0000;
    border-radius: 0;
  }

  :where(select:is([multiple], [size])) optgroup {
    font-weight: bolder;
  }

  :where(select:is([multiple], [size])) optgroup option {
    padding-inline-start: 20px;
  }

  ::file-selector-button {
    margin-inline-end: 4px;
  }

  ::placeholder {
    opacity: 1;
  }

  @supports (not ((-webkit-appearance: -apple-pay-button))) or (contain-intrinsic-size: 1px) {
    ::placeholder {
      color: currentColor;
    }

    @supports (color: color-mix(in lab, red, red)) {
      ::placeholder {
        color: color-mix(in oklab, currentcolor 50%, transparent);
      }
    }
  }

  textarea {
    resize: vertical;
  }

  ::-webkit-search-decoration {
    -webkit-appearance: none;
  }

  ::-webkit-date-and-time-value {
    min-height: 1lh;
    text-align: inherit;
  }

  ::-webkit-datetime-edit {
    display: inline-flex;
  }

  ::-webkit-datetime-edit-fields-wrapper {
    padding: 0;
  }

  ::-webkit-datetime-edit {
    padding-block: 0;
  }

  ::-webkit-datetime-edit-year-field {
    padding-block: 0;
  }

  ::-webkit-datetime-edit-month-field {
    padding-block: 0;
  }

  ::-webkit-datetime-edit-day-field {
    padding-block: 0;
  }

  ::-webkit-datetime-edit-hour-field {
    padding-block: 0;
  }

  ::-webkit-datetime-edit-minute-field {
    padding-block: 0;
  }

  ::-webkit-datetime-edit-second-field {
    padding-block: 0;
  }

  ::-webkit-datetime-edit-millisecond-field {
    padding-block: 0;
  }

  ::-webkit-datetime-edit-meridiem-field {
    padding-block: 0;
  }

  ::-webkit-calendar-picker-indicator {
    line-height: 1;
  }

  :-moz-ui-invalid {
    box-shadow: none;
  }

  button, input:where([type="button"], [type="reset"], [type="submit"]) {
    appearance: button;
  }

  ::file-selector-button {
    appearance: button;
  }

  ::-webkit-inner-spin-button {
    height: auto;
  }

  ::-webkit-outer-spin-button {
    height: auto;
  }

  [hidden]:where(:not([hidden="until-found"])) {
    display: none !important;
  }
}

@layer components;

@layer utilities {
  .invisible {
    visibility: hidden;
  }

  .visible {
    visibility: visible;
  }

  .absolute {
    position: absolute;
  }

  .fixed {
    position: fixed;
  }

  .relative {
    position: relative;
  }

  .static {
    position: static;
  }

  .block {
    display: block;
  }

  .contents {
    display: contents;
  }

  .grid {
    display: grid;
  }

  .hidden {
    display: none;
  }

  .inline {
    display: inline;
  }

  .table {
    display: table;
  }

  .shrink {
    flex-shrink: 1;
  }

  .grow {
    flex-grow: 1;
  }

  .transform {
    transform: var(--tw-rotate-x, ) var(--tw-rotate-y, ) var(--tw-rotate-z, ) var(--tw-skew-x, ) var(--tw-skew-y, );
  }

  .resize {
    resize: both;
  }

  :where(.space-y-2 > :not(:last-child)) {
    --tw-space-y-reverse: 0;
    margin-block-start: calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));
    margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)));
  }

  .rounded {
    border-radius: .25rem;
  }

  .border {
    border-style: var(--tw-border-style);
    border-width: 1px;
  }

  .shadow {
    --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, #0000001a), 0 1px 2px -1px var(--tw-shadow-color, #0000001a);
    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
  }

  .ring {
    --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
  }

  .outline {
    outline-style: var(--tw-outline-style);
    outline-width: 1px;
  }

  .filter {
    filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
  }

  .transition {
    transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
    transition-duration: var(--tw-duration, var(--default-transition-duration));
  }

  .outline-none {
    --tw-outline-style: none;
    outline-style: none;
  }
}

:root {
  color-scheme: dark;
  --radius: .5rem;
  --background: #242424;
  --foreground: #ffffffdb;
  --card: #292929;
  --card-foreground: #ffffffdb;
  --popover: #2f2f2f;
  --popover-foreground: #ffffffdb;
  --primary: #ff9e45;
  --primary-foreground: #191919;
  --secondary: #333;
  --secondary-foreground: #ffffffc7;
  --muted: #303030;
  --muted-foreground: #ffffff8f;
  --accent: #3a3a3a;
  --accent-foreground: #ffffffe6;
  --destructive: #ff5d5d;
  --border: #ffffff14;
  --input: #ffffff1f;
  --ring: #ff9e45;
}

* {
  box-sizing: border-box;
}

html, body, #root {
  min-height: 100%;
}

body {
  background: var(--background);
  min-width: 1120px;
  color: var(--foreground);
  margin: 0;
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif;
  font-size: 14px;
}

button, input {
  font: inherit;
}

button {
  color: inherit;
}

.no-scrollbar {
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.no-scrollbar::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;
}

.cell-fill i {
  min-width: 0;
  height: 100%;
  display: block;
}

.matrix-loader-panel {
  animation: 1.4s ease-in-out infinite matrix-loader-lift;
}

@keyframes matrix-loader-lift {
  0%, 100% {
    transform: translateY(0);
  }

  50% {
    transform: translateY(-2px);
  }
}

@media (max-width: 1280px) {
  body {
    min-width: 980px;
  }
}

@property --tw-rotate-x {
  syntax: "*";
  inherits: false
}

@property --tw-rotate-y {
  syntax: "*";
  inherits: false
}

@property --tw-rotate-z {
  syntax: "*";
  inherits: false
}

@property --tw-skew-x {
  syntax: "*";
  inherits: false
}

@property --tw-skew-y {
  syntax: "*";
  inherits: false
}

@property --tw-space-y-reverse {
  syntax: "*";
  inherits: false;
  initial-value: 0;
}

@property --tw-border-style {
  syntax: "*";
  inherits: false;
  initial-value: solid;
}

@property --tw-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}

@property --tw-shadow-color {
  syntax: "*";
  inherits: false
}

@property --tw-shadow-alpha {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 100%;
}

@property --tw-inset-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}

@property --tw-inset-shadow-color {
  syntax: "*";
  inherits: false
}

@property --tw-inset-shadow-alpha {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 100%;
}

@property --tw-ring-color {
  syntax: "*";
  inherits: false
}

@property --tw-ring-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}

@property --tw-inset-ring-color {
  syntax: "*";
  inherits: false
}

@property --tw-inset-ring-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}

@property --tw-ring-inset {
  syntax: "*";
  inherits: false
}

@property --tw-ring-offset-width {
  syntax: "<length>";
  inherits: false;
  initial-value: 0;
}

@property --tw-ring-offset-color {
  syntax: "*";
  inherits: false;
  initial-value: #fff;
}

@property --tw-ring-offset-shadow {
  syntax: "*";
  inherits: false;
  initial-value: 0 0 #0000;
}

@property --tw-outline-style {
  syntax: "*";
  inherits: false;
  initial-value: solid;
}

@property --tw-blur {
  syntax: "*";
  inherits: false
}

@property --tw-brightness {
  syntax: "*";
  inherits: false
}

@property --tw-contrast {
  syntax: "*";
  inherits: false
}

@property --tw-grayscale {
  syntax: "*";
  inherits: false
}

@property --tw-hue-rotate {
  syntax: "*";
  inherits: false
}

@property --tw-invert {
  syntax: "*";
  inherits: false
}

@property --tw-opacity {
  syntax: "*";
  inherits: false
}

@property --tw-saturate {
  syntax: "*";
  inherits: false
}

@property --tw-sepia {
  syntax: "*";
  inherits: false
}

@property --tw-drop-shadow {
  syntax: "*";
  inherits: false
}

@property --tw-drop-shadow-color {
  syntax: "*";
  inherits: false
}

@property --tw-drop-shadow-alpha {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 100%;
}

@property --tw-drop-shadow-size {
  syntax: "*";
  inherits: false
}
/* ============================================================================
   Theme palette — the only values that stay variables.

   Sizes, spacing and font sizes are written as plain px INSIDE each rule (see
   docs/frontend_rules.md): a number you can read and edit where you see it
   beats chasing an alias through this file. Colours are different — the same
   grey is reused in fifty places and has to change everywhere at once.
   ============================================================================ */

:root {
  /* surfaces */
  --surface-page: #242424;
  --surface-panel: #292929;
  --surface-rail: #333333;
  --surface-sunken: #1f1f1f;
  --surface-raised: #3a3a3a;
  --surface-hover: #353535;
  --surface-active: #3d3d3d;

  /* text */
  --text-strong: rgba(255, 255, 255, 0.9);
  --text-normal: rgba(255, 255, 255, 0.86);
  --text-soft: rgba(255, 255, 255, 0.72);
  --text-dim: rgba(255, 255, 255, 0.62);
  --text-muted: rgba(255, 255, 255, 0.56);
  --text-on-accent: #242424;

  /* lines */
  --line: rgba(255, 255, 255, 0.08);
  --line-strong: #4a4a4a;

  /* Single source of action colours for the matrix, Hands and Actions. */
  --action-fold: #3f7fbd;
  --action-fold-hover: #4a8ecd;
  --action-passive: #58b967;
  --action-passive-hover: #63c973;
  --action-aggressive: #f7343f;
  --action-raise: #eb303a;
  --action-aggressive-hover: #ff4450;
  --action-allin: #861b21;
  --action-allin-hover: #9a2129;

  /* grades (trainer feedback; shared with engine.ts GRADE_COLORS).
     There is no separate colour for a blunder: it is the same red as a wrong
     move (owner request 2026-08-17), so the burgundy --grade-worst is gone and
     nothing is left pointing at it. */
  --grade-good: #58b967;
  --grade-warn: #e6a935;
  --grade-bad: #f7343f;

  /* accent */
  --accent-brand: #ff9e45;
  --accent-brand-hover: #ffad5f;
  /* A deeper orange, owner value 2026-08-17. Only the trainer's Next hand
     button wears it, and it hovers UP to --accent-brand — the two are one pair,
     which is why this lives beside it rather than in the action colours. */
  --accent-brand-deep: #fc8e29;
  /* Ink for text sitting ON the orange, and the answer is white (owner report
     2026-08-17, twice: once for the study modal's confirm button and once for
     Next hand). The near-black --text-on-accent belongs on the GREEN buttons;
     on orange it reads as a black label stuck to the button, and a dark warm
     brown was no better — at label size it still just looks black.
     White costs contrast (2.2:1 against #ff9e45) and is used anyway: these are
     short, heavy, wide-tracked words on a big target, and the owner has now
     rejected the dark alternative twice. */
  --text-on-brand: #ffffff;
}

/* Action fills share these classes; inline styles only set dynamic dimensions. */
.action-fill.is-fold { background: var(--action-fold); }
.action-fill.is-call { background: var(--action-passive); }
.action-fill.is-bet { background: var(--action-aggressive); }
.action-fill.is-raise { background: var(--action-raise); }
.action-fill.is-allin { background: var(--action-allin); }
/* Bet / raise size shades (actionShade in lib/poker.ts): step N mixes N*10%
   of the all-in colour into the plain bet colour; 8 (80%) is the darkest. */
.action-fill.is-bet.is-shade-1 { background: color-mix(in srgb, var(--action-allin) 10%, var(--action-aggressive)); }
.action-fill.is-bet.is-shade-2 { background: color-mix(in srgb, var(--action-allin) 20%, var(--action-aggressive)); }
.action-fill.is-bet.is-shade-3 { background: color-mix(in srgb, var(--action-allin) 30%, var(--action-aggressive)); }
.action-fill.is-bet.is-shade-4 { background: color-mix(in srgb, var(--action-allin) 40%, var(--action-aggressive)); }
.action-fill.is-bet.is-shade-5 { background: color-mix(in srgb, var(--action-allin) 50%, var(--action-aggressive)); }
.action-fill.is-bet.is-shade-6 { background: color-mix(in srgb, var(--action-allin) 60%, var(--action-aggressive)); }
.action-fill.is-bet.is-shade-7 { background: color-mix(in srgb, var(--action-allin) 70%, var(--action-aggressive)); }
.action-fill.is-bet.is-shade-8 { background: color-mix(in srgb, var(--action-allin) 80%, var(--action-aggressive)); }
.action-fill.is-raise.is-shade-1 { background: color-mix(in srgb, var(--action-allin) 10%, var(--action-raise)); }
.action-fill.is-raise.is-shade-2 { background: color-mix(in srgb, var(--action-allin) 20%, var(--action-raise)); }
.action-fill.is-raise.is-shade-3 { background: color-mix(in srgb, var(--action-allin) 30%, var(--action-raise)); }
.action-fill.is-raise.is-shade-4 { background: color-mix(in srgb, var(--action-allin) 40%, var(--action-raise)); }
.action-fill.is-raise.is-shade-5 { background: color-mix(in srgb, var(--action-allin) 50%, var(--action-raise)); }
.action-fill.is-raise.is-shade-6 { background: color-mix(in srgb, var(--action-allin) 60%, var(--action-raise)); }
.action-fill.is-raise.is-shade-7 { background: color-mix(in srgb, var(--action-allin) 70%, var(--action-raise)); }
.action-fill.is-raise.is-shade-8 { background: color-mix(in srgb, var(--action-allin) 80%, var(--action-raise)); }
/* ============================================================================
   Playing cards. Size is set by ONE pair of variables per usage, and the rank
   and the suit watermark scale with it — so a card never has to be resized in
   three places.

   To resize a card family, set --card-width / --card-height on its class (see
   .seat-card and .board-card in practice.css) — rank and suit follow.
   ============================================================================ */

.playing-card {
  --card-width: 60px;
  --card-height: 78px;
  --card-rank-font: calc(var(--card-height) * var(--rank-size, 0.6));
  /* 0.55 of the height, and the number is a fit, not a taste (owner report
     2026-08-17). A suit glyph is about 0.89em WIDE, so at the old 1.05 it drew
     56px of ink across a 34px card — wider than every single family we have,
     which is why it read as a shapeless dark smear instead of a suit. The
     narrowest card is the study strip at W/H = 0.567, so 0.55 x height is the
     largest glyph that still fits inside it whole, with a margin. Raise this
     and the widest suits get clipped again. */
  --card-suit-font: calc(var(--card-height) * var(--suit-size, 1.2));

  /* Per-suit tuning knobs. Neutral here; the four blocks further down are what
     you actually edit. See "TUNING THE SUITS" below. */
  --suit-scale: 1;
  --suit-x: 0;
  --suit-y: 0;

  position: relative;
  display: block;
  /* A card is never squeezed: in a flex row (the board, a seat's hand, the
     strip) a shrinkable item gets squashed the moment the row runs out of
     width, and a card that is narrower than it is tall stops looking like a
     card at all. */
  flex-shrink: 0;
  width: var(--card-width);
  height: var(--card-height);
  overflow: hidden;
  border: var(--card-border-width, 1px) solid;
  border-radius: var(--card-radius, 6px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 3px 0 rgba(0, 0, 0, 0.38);
}

.playing-card.suit-s {
  --suit-glyph: var(--suit-s-glyph, "\2660");
  --suit-mask: var(--suit-s-mask, url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cpath%20d%3D%22M50%208C50%208%2088%2034%2088%2057C88%2070%2079%2079%2068%2079C61%2079%2055%2075%2052%2069C53%2081%2057%2089%2064%2095L36%2095C43%2089%2047%2081%2048%2069C45%2075%2039%2079%2032%2079C21%2079%2012%2070%2012%2057C12%2034%2050%208%2050%208Z%22%20fill%3D%22black%22%2F%3E%3C%2Fsvg%3E"));
  --suit-color: var(--suit-s-color, #555555);
  border-color: var(--card-border-color, var(--suit-s-border, #7a7a7a));
  background: var(--suit-s-face, linear-gradient(145deg, #737373 0%, #555555 48%, #3f3f3f 100%));
}

.playing-card.suit-h {
  --suit-glyph: var(--suit-h-glyph, "\2665");
  --suit-mask: var(--suit-h-mask, url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cpath%20d%3D%22M50%2092C18%2068%206%2050%206%2033C6%2018%2017%208%2030%208C39%208%2046%2013%2050%2021C54%2013%2061%208%2070%208C83%208%2094%2018%2094%2033C94%2050%2082%2068%2050%2092Z%22%20fill%3D%22black%22%2F%3E%3C%2Fsvg%3E"));
  --suit-color: var(--suit-h-color, #e60800);
  border-color: var(--card-border-color, var(--suit-h-border, #eb342e));
  background: var(--suit-h-face, linear-gradient(145deg, #ed4842 0%, #e60800 48%, #a10600 100%));
}

.playing-card.suit-d {
  --suit-glyph: var(--suit-d-glyph, "\2666");
  --suit-mask: var(--suit-d-mask, url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cpath%20d%3D%22M50%204L94%2050L50%2096L6%2050Z%22%20fill%3D%22black%22%2F%3E%3C%2Fsvg%3E"));
  --suit-color: var(--suit-d-color, #2f45d7);
  border-color: var(--card-border-color, var(--suit-d-border, #4d66ff));
  background: var(--suit-d-face, linear-gradient(145deg, #5368ff 0%, #2f45d7 48%, #1e2e9b 100%));
}

.playing-card.suit-c {
  --suit-glyph: var(--suit-c-glyph, "\2663");
  --suit-mask: var(--suit-c-mask, url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20100%20100%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2229%22%20r%3D%2220%22%20fill%3D%22black%22%2F%3E%3Ccircle%20cx%3D%2227%22%20cy%3D%2259%22%20r%3D%2220%22%20fill%3D%22black%22%2F%3E%3Ccircle%20cx%3D%2273%22%20cy%3D%2259%22%20r%3D%2220%22%20fill%3D%22black%22%2F%3E%3Cpath%20d%3D%22M44%2060h12c0%2018%203%2027%209%2033H35c6-6%209-15%209-33z%22%20fill%3D%22black%22%2F%3E%3C%2Fsvg%3E"));
  --suit-color: var(--suit-c-color, #0a922c);
  border-color: var(--card-border-color, var(--suit-c-border, #10b83a));
  background: var(--suit-c-face, linear-gradient(145deg, #16be45 0%, #0a922c 48%, #06651f 100%));
}

/* ============================================================================
   TUNING THE SUITS — edit only the four blocks below.

   Every suit is drawn at a different weight by the font: a club reads bulky, a
   diamond narrow, a heart and a spade somewhere between. So each one gets its
   own size and its own nudge instead of sharing a single number (owner request
   2026-08-17).

     --suit-scale   size, as a multiplier of the family's --card-suit-font.
                    1 = untouched, 1.1 = a tenth bigger, 0.9 = a tenth smaller.
     --suit-x       nudge sideways. POSITIVE moves RIGHT, negative LEFT.
     --suit-y       nudge vertically. POSITIVE moves UP, negative DOWN.

   Both nudges are fractions of the CARD HEIGHT, not pixels, so a tune made on
   one card holds on all six sizes: 0.05 is "a twentieth of the card", which is
   3px on the strip card and 3.9px on a picked one. 0 is dead centre
   horizontally and the resting height vertically.

   The overall size of every suit at once is --card-suit-font at the top of this
   file; these four only say how the suits differ from each other.

   The numbers here were tuned by eye in the appearance panel and pasted back
   (owner, 2026-08-17). Anything the panel writes at :root overrides them.
   ============================================================================ */
.playing-card.suit-s {          /* ♠ spade */
  --suit-scale: var(--suit-s-scale, 1);
  --suit-x: var(--suit-s-x, 0.27);
  --suit-y: var(--suit-s-y, -0.255);
}

.playing-card.suit-h {          /* ♥ heart */
  --suit-scale: var(--suit-h-scale, 1.02);
  --suit-x: var(--suit-h-x, 0.235);
  --suit-y: var(--suit-h-y, -0.295);
}

.playing-card.suit-d {          /* ♦ diamond — narrow, so it reads smaller */
  --suit-scale: var(--suit-d-scale, 1.04);
  --suit-x: var(--suit-d-x, 0.18);
  --suit-y: var(--suit-d-y, -0.3);
}

.playing-card.suit-c {          /* ♣ club — bulky, so it reads bigger */
  --suit-scale: var(--suit-c-scale, 1.05);
  --suit-x: var(--suit-c-x, 0.195);
  --suit-y: var(--suit-c-y, -0.3);
}

/* The suit as a watermark, drawn WHOLE. Its shape is the only thing naming the
   suit on these cards, so nothing about it may be cropped: it sits inside the
   lower half of the card with the rank riding over it.

   The old version was clipped twice over — off the bottom, and in the study
   variant off the right edge as well — but the real culprit was the size: the
   glyph was wider than the card, so there was no placement that could have
   saved it (owner report 2026-08-17). */
.playing-card-suit {
  pointer-events: none;
  position: absolute;
  bottom: calc(var(--card-height) * (0.04 + var(--suit-y)));
  left: 50%;
  transform: translateX(-50%) translateX(calc(var(--card-height) * var(--suit-x)));
  font-size: calc(var(--card-suit-font) * var(--suit-scale));
  line-height: 1;
  color: var(--card-suit-ink, #000);
  opacity: var(--suit-opacity, 0.28);
  mix-blend-mode: var(--card-suit-blend, multiply);
}

/* The suit is DRAWN by default (owner choice 2026-08-17): the four shapes above
   are painted as a mask with the ink as the background, so they tint and scale
   by the same font-size a character would have. The margin puts the shape's
   base where a glyph's baseline sat, so switching sets does not move it. */
.playing-card-suit::before {
  content: "";
  display: block;
  width: 1em;
  height: 0.78em;
  margin-bottom: 0.21em;
  background: currentColor;
  -webkit-mask: var(--suit-mask) center / contain no-repeat;
  mask: var(--suit-mask) center / contain no-repeat;
}

/* A FONT set instead of a drawn one — the appearance panel puts `suits-text`
   on the document root and writes the four characters. */
:root.suits-text .playing-card-suit::before {
  content: var(--suit-glyph, "\2660");
  display: inline;
  width: auto;
  height: auto;
  margin-bottom: 0;
  background: none;
  -webkit-mask: none;
  mask: none;
}

/* Study variant: the same watermark, a touch stronger. It no longer MOVES the
   glyph — leaning it into the corner cost more than the look was worth. */
.playing-card.is-corner .playing-card-suit {
  opacity: calc(var(--suit-opacity, 0.28) * 1.25);
}

/* ── size families ────────────────────────────────────────────────────── */
.playing-card--strip {          /* flop cards inside a node strip card */
  --card-width: 34px;
  --card-height: 56px;
}

.playing-card--chance {         /* the turn/river card a chance node shows */
  --card-width: 48px;
  --card-height: 64px;
}

.playing-card--picker {         /* board picker grid: same size as the strip */
  --card-width: 34px;
  /* A real number, NOT 100%. The rank and the watermark are sized with
     calc(var(--card-height) * k), and a percentage in a font-size is read
     against the inherited FONT SIZE, not against the box — so this family had
     to carry hand-written font sizes, and those did not track anything. The
     result was a 74px card showing a 42px rank next to a TALLER 78px picked
     card showing a 35px one (owner report 2026-08-17). With a px height the
     family derives like every other one.

     Keep in step with the height of .deck-card in study-modal.css. */
  --card-height: 60px;
}

.playing-card--picked {         /* the chosen card above the picker grid */
  /* Smaller than it was, still clearly bigger than the 34x60 of the deck below
     — the board being built has to outrank the list it is picked from (owner
     request 2026-08-17). */
  --card-width: 48px;
  --card-height: 66px;
}

.playing-card-rank {
  position: absolute;
  z-index: 10;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--card-rank-family, inherit);
  font-size: var(--card-rank-font);
  font-weight: var(--card-rank-weight, 600);
  line-height: 1;
  color: var(--card-rank-color, #fff);
  text-shadow: var(--card-rank-shadow, 0 2px 2px rgba(0, 0, 0, 0.6));
}

/* ── blank theme ──────────────────────────────────────────────────────────
   The other way round: a plain face, and the SUIT's colour carries the rank
   and the watermark instead of the background. Switched on by the appearance
   panel, which puts `cards-blank` on the document root. */
:root.cards-blank .playing-card {
  border-color: rgba(0, 0, 0, 0.22);
  background: var(--card-blank-face, #f3f5f8);
}

:root.cards-blank .playing-card-rank {
  color: var(--suit-color);
  text-shadow: none;
}

:root.cards-blank .playing-card-suit {
  color: var(--suit-color);
  opacity: var(--suit-opacity, 0.9);
  mix-blend-mode: normal;
}

/* The BACK of a card. Same box as a face, so every size family works on it
   unchanged. The villain's hand sits on the table from the deal and only turns
   over at showdown (owner request 2026-08-17); before that it is this. */
.card-back {
  position: relative;
  display: block;
  flex-shrink: 0;
  width: var(--card-width);
  height: var(--card-height);
  overflow: hidden;
  border: var(--card-border-width, 1px) solid rgba(255, 255, 255, 0.09);
  border-radius: var(--card-radius, 6px);
  background: repeating-linear-gradient(135deg, #242424 0, #242424 8px, #303030 8px, #303030 16px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.05),
    0 3px 0 rgba(0, 0, 0, 0.38);
}

.card-back::after {
  content: "";
  position: absolute;
  inset: 4px;
  border: 1px solid rgba(255, 255, 255, 0.05);
  border-radius: 4px;
}

/* ── small inline chip used in tables and lists ───────────────────────── */
.card-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 34px;
  border-radius: 5px;
  background: #f2f5f8;
  color: #14171b;
  font-weight: 850;
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.25);
  /* Never inherit a parent's text-shadow: a dark shadow under dark glyphs on
     the light chip reads as blur (Hands cards, 2026-08-16). */
  text-shadow: none;
}

.card-chip.heart { color: #c23845; }
.card-chip.diamond { color: #2879d0; }
.card-chip.club { color: #1e8a58; }

/* A row of small chips (the board as text-sized cards). */
.board-chips {
  display: flex;
  align-items: center;
  gap: 6px;
}
/* ============================================================================
   Practice trainer — the table, the seats, the action bar and the sidebar.
   Sizes are plain px right here; colours come from tokens.css.
   ============================================================================ */

/* ── page frame ───────────────────────────────────────────────────────────
   Exactly one screen tall: the sidebar and the felt scroll inside themselves,
   so the action bar can never be pushed below the fold. */
.practice-page {
  display: flex;
  height: 100vh;
  overflow: hidden;
  background: var(--surface-page);
  color: var(--text-normal);
}

/* ONE surface from the strip down to the bottom of the felt. The strip used to
   sit on the page colour with a line under it, which made it a header bolted
   on top of the table; it is part of the table (owner request 2026-08-20).
   Carried here rather than on each child so there is no seam to keep in step.
   A raw colour because this is the felt's surround and has no token: the
   surfaces in tokens.css are the panels that sit ON it. */
.practice-main {
  /* What the strip's edge arrows fade into: the surface below, not the page
     colour they default to. */
  --strip-fade: #1a1a1a;
  --strip-fade-soft: rgba(26, 26, 26, 0.7);
  display: flex;
  flex: 1;
  min-width: 0;
  flex-direction: column;
  overflow: hidden;
  background: #1a1a1a;
}

.practice-body {
  display: flex;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

/* ── the verdict mark ─────────────────────────────────────────────────────
   One drawing for every place the trainer shows a verdict: the sidebar, the
   strip, the review panel's move tiles and the verdict on a finished hand (see
   GradeIcon). The component draws the shape and its colour; the size and the
   weight of the stroke are here, so each place can match the line of text it
   sits on without a second set of artwork.

   `stroke-width` is inherited by the paths, so one line sets the weight of the
   whole drawing. */
.grade-icon {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  stroke-width: 2.2;
}

/* ── action strip (the hand so far) ───────────────────────────────────── */
.strip {
  display: flex;
  padding: 8px 16px;
}

/* The arrows hang off THIS, not off the track: they have to stay put while what
   is under them scrolls. */
.strip-viewport {
  position: relative;
  min-width: 0;
  flex: 1;
}

/* No scrollbar — the strip is driven from its edges, see StripScroll. */
.strip-track {
  display: flex;
  gap: 6px;
  overflow-x: auto;
}

.strip-step {
  /* Whose decision this tile holds, as raw r,g,b so one value can draw the ring
     both solid and as the soft glow around it. The other seat by default; the
     seat the user is sitting in overrides it just below. */
  --seat-rgb: 230, 169, 53;
  position: relative;
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  min-width: 104px;
  border: 1px solid var(--line);
  border-radius: 6px;
  /* A raw value: the tiles sit on the felt's own surround, and none of the
     rail's surfaces is the tone they were asked for (owner request
     2026-08-21). */
  background: #242424;
  padding: 6px 8px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 3px 0 rgba(0, 0, 0, 0.38);
  /* Rides in from the left as it is appended (owner request 2026-08-19). It
     runs on MOUNT, and React keys every tile by its place and its text, so a
     tile already in the strip is never remounted and never replays this — only
     what is genuinely new moves. A rewind, which cuts the strip and re-keys
     what follows, is the one other time it plays, and there it is right: those
     tiles are new again. `both` holds the last frame, so a tile that finishes
     while the tab is in the background still ends up where it belongs. */
  animation: strip-step-in 260ms cubic-bezier(0.22, 0.8, 0.24, 1) both;
}

/* Only `opacity` and `transform`, so the whole entrance is the compositor's
   work. The tile is already in its final place in the flow — the transform is
   the only thing that moves — so nothing after it in the strip is pushed
   about while it arrives. */
@keyframes strip-step-in {
  from {
    opacity: 0;
    transform: translateX(-16px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

/* A card being turned over needs room for the card and nothing else: the 104px
   a decision needs for its seat, its stack and a word left flop, turn and river
   sitting in a third of their own tile (owner request 2026-08-19). */
.strip-step.is-chance {
  min-width: 25px;
}

.strip-step.is-hero {
  --seat-rgb: 88, 185, 103;
}

/* The ring that says a tile can be opened, and whose move is in it: green for
   the seat the user is sitting in, amber for the other one (owner request
   2026-08-19). It used to be the GRADE's colour, on the tile's own border. The
   verdict has not gone anywhere — it is the tick or the warning beside the
   action, written in the grade's own colour — but a frame around a decision now
   answers "whose", which is the thing a strip of two players is read for.

   A pseudo-element rather than border-color, so that appearing can be an
   ANIMATION instead of a repaint: the ring fades up while it grows the last 4%
   into place, and both of those are `opacity` and `transform`, which the
   compositor runs by itself. Changing the tile's real border would also have
   nudged every word inside it by a pixel each time the mouse went past. */
.strip-step.is-openable::after {
  content: "";
  pointer-events: none;
  position: absolute;
  inset: 0;
  border: 2px solid rgb(var(--seat-rgb));
  border-radius: 6px;
  opacity: 0;
  box-shadow: 0 0 12px rgba(var(--seat-rgb), 0.3);
  transition:
    opacity 170ms ease,
    transform 170ms cubic-bezier(0.22, 0.8, 0.24, 1);
  transform: scale(0.96);
}

.strip-step.is-openable:hover::after,
.strip-step.is-openable:focus-visible::after,
.strip-step.is-open::after {
  opacity: 1;
  transform: scale(1);
}

/* The head lights up with the ring, so the tile reads as one thing coming on
   rather than as a frame drawn around text that ignores it (owner request
   2026-08-19). */
.strip-step.is-openable:hover .strip-step-seat,
.strip-step.is-openable:hover .strip-step-stack,
.strip-step.is-openable:focus-visible .strip-step-seat,
.strip-step.is-openable:focus-visible .strip-step-stack,
.strip-step.is-open .strip-step-seat,
.strip-step.is-open .strip-step-stack {
  color: rgb(var(--seat-rgb));
}

/* Every decision in the strip opens the node it was made at — see .move-review
   and MoveReview.tsx. Chance rows do not: there is nothing behind a card being
   turned over. */
.strip-step.is-openable {
  cursor: pointer;
  transition: background-color 120ms ease;
}

.strip-step.is-openable:hover {
  background: var(--surface-hover);
}

/* The step being looked at: the same ring hover draws, held on. */
.strip-step.is-open {
  background: var(--surface-hover);
}

/* The step that is waiting for the user: nothing has happened in the hand yet
   and it is on them (owner request 2026-08-17). It breathes rather than blinks
   — the border swells out and settles, and a light runs across the words — so
   it reads as "still waiting" instead of as an alert. Amber is the same colour
   the seat on the clock pulses in, given here as raw r,g,b so one set of
   keyframes can fade it all the way out. */
.strip-step.is-waiting {
  --wait: 230, 169, 53;
  /* Both: the entrance still has to run, and the two lists never touch the same
     property — this one is border-colour and box-shadow, the entrance is
     opacity and transform. */
  animation:
    strip-step-in 260ms cubic-bezier(0.22, 0.8, 0.24, 1) both,
    strip-wait 1.7s ease-in-out infinite;
}

@keyframes strip-wait {
  0%, 100% {
    border-color: var(--line);
    box-shadow: 0 0 0 0 rgba(var(--wait), 0);
  }
  50% {
    border-color: var(--grade-warn);
    box-shadow:
      0 0 0 3px rgba(var(--wait), 0.12),
      0 0 14px rgba(var(--wait), 0.22);
  }
}

/* The words are painted by a gradient that slides across them, which is what
   makes the card feel alive without anything moving. */
/* Not an action: a sentence asking for one. */
.strip-step.is-waiting .strip-step-text {
  text-transform: none;
  background: linear-gradient(
    100deg,
    var(--text-muted) 34%,
    var(--text-normal) 50%,
    var(--text-muted) 66%
  );
  background-size: 260% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: strip-wait-sheen 1.7s linear infinite;
}

@keyframes strip-wait-sheen {
  0% { background-position: 150% 0; }
  100% { background-position: -50% 0; }
}

.strip-step-head {
  display: flex;
  background: var(--surface-raised);
  align-items: center;
  font-size: 13px;
  color: var(--text-muted);
}

.strip-step-seat {
  /* SB and BB are already capitals; this is for the streets, which are stored
     lower case and are set FLOP / TURN / RIVER here (owner request
     2026-08-19). */
  text-transform: uppercase;
  font-weight: 700;
  transition: color 170ms ease;
  color: var(--text-soft);
}

/* A seat is a badge; a street is not (owner request 2026-08-19). The modifier
   is what tells them apart — .strip-step-seat carries the street name on a
   chance row, and a lone "turn" set in a chip would read as a player. */
.strip-step-seat.is-seat {
  border-radius: 4px;
  background: var(--surface-raised);
  padding: 1px 4px;
  color: var(--text-strong);
}

/* What the seat still had in front of it at the moment it acted, not the pot
   (owner request 2026-08-19): the pot is on the felt and in the verdict, while
   how deep a seat was is the thing that makes a past decision readable. */
.strip-step-stack {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  transition: color 170ms ease;
}

/* Fixed at the height of a card, not at the height of its own text: a step
   holding a flop is taller than a step holding "raise 5.6", and letting the row
   size itself made the whole strip — and the page under it — grow the moment a
   street arrived (owner report 2026-08-16). Every step is card-height from the
   start, so nothing moves. Must match --card-height on .strip-step-card. */
/* Small: it shares a 14px line with the word it grades — and heavier than
   elsewhere, because at that size a thinner stroke greys out next to 700-weight
   type beside it (owner request 2026-08-20). */
.strip-step-action .grade-icon {
  width: 14px;
  height: 14px;
  stroke-width: 3;
}

.strip-step-action {
  display: flex;
  height: 32px;
  align-items: center;
  gap: 4px;
  /* The action alone sits in from the tile's own padding (owner request
     2026-08-19, trimmed to 3px on 2026-08-20). NOT the head above it: the seat
     and the stack are already where they should be, and they are a separate
     row, so shifting this one leaves them exactly as they were. */
  padding-left: 3px;
  font-size: 14px;
  font-weight: 700;
  color: var(--text-normal);
}

/* A chance row holds a card, not an icon and a word. The tile is as wide as
   the street written over it, which is wider than one card, so a card left to
   start where the text does sat with all the slack on its right (owner report
   2026-08-19). Centred, it is under the middle of the word whether the street
   put down one card or three. */
.strip-step.is-chance .strip-step-action {
  justify-content: center;
  padding-left: 0;
}

.strip-step-text {
  /* Every action is one word and a number — see actionText — so this only ever
     raises the word (owner request 2026-08-19). */
  text-transform: capitalize;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A street's cards, drawn instead of spelled out (owner request 2026-08-16).
   Small enough that a three-card flop fits the step it belongs to. */
.strip-step-card {
  --card-width: 24px;
  --card-height: 28px;
  /* .playing-card reads its corner off --card-radius, defaulting to 6px, which
     is a big round on a card this small (see cards.css). */
  --card-radius: 2px;
  margin-top: 6px;
}

/* ── table ────────────────────────────────────────────────────────────── */
.table-area {
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  padding: 16px;
}

.table-stack {
  /* Nothing on the felt is text to be read out of the page: a drag across the
     table used to leave the board, the pot, a stack and a seat name all
     highlighted, which is the look of something broken rather than of something
     selected (owner request 2026-08-20). The action bar below is deliberately
     NOT covered — the verdict and the numbers on it are worth copying. */
  -webkit-user-select: none;
  user-select: none;
  display: flex;
  width: 100%;
  max-width: 980px;
  flex-direction: column;
  align-items: center;
}

/* The felt itself. Seats sit OUTSIDE it and overlap the edge by the negative
   margin below, the way a real client draws them. */
.felt {
  position: relative;
  /* A stacking context ONLY so the table below can sit at z-index: -1 and still
     be seen: without one a negative layer escapes to the nearest stacking
     context and is painted behind .practice-body's own background, i.e. into
     nothing. The single thing this re-homes is .felt-preparing-veil (z-index
     40) — a transparent overlay centred on the felt, nowhere near the seats
     that overlap its edge — so nothing changes on screen. */
  isolation: isolate;
  display: flex;
  width: 100%;
  flex-direction: column;
  align-items: center;
  /* TRANSPARENT, not deleted. box-sizing is border-box for everything
     (src/index.css), so taking the 6px border away would hand those 12px back
     to the content box and move every card, pot and chip on the felt by 6px.
     The border stays as geometry and stops being paint (owner request
     2026-08-19). */
  border: 6px solid transparent;
  border-radius: 150px;
  padding: 60px 40px;
  margin: -34px 0;
}

/* The table, laid UNDER everything the felt holds (owner request 2026-08-19).
   Drawn 840px wide; 384px is that width through the picture's OWN ratio
   (1760x804), so the table is never stretched — a poker table is a stadium
   shape and squashing one thickens the top and bottom rail while leaving the
   sides alone, which reads as a mistake immediately.

   A LAYER rather than a background, because a background is clipped to its
   border box and the felt's own box is shorter than the table is tall — as a
   background the table lost its top and bottom to that clip. Out of flow it is
   clipped by nothing, and the felt keeps its exact size, so no card, pot or
   chip moves and the seats that overlap the felt's edge go on sitting where
   they sat — now on the table's rail, the way a real client draws them.

   z-index: -1 puts it under every child of the felt at once, so there is no
   per-element ordering to keep in step; the seats are outside the felt and are
   painted above it anyway. */
.felt::before {
  content: "";
  pointer-events: none;
  position: absolute;
  z-index: -1;
  /* 55%, not 50%: the felt's own middle is not the table's. The hero seat sits
     lower than the villain seat rides high, so a table centred on the box read
     as sitting up out of it (owner request 2026-08-19). */
  top: 55%;
  left: 50%;
  width: 840px;
  height: 384px;
  background: url("/assets/poker-table-J1DhjKE4.webp") no-repeat center / contain;
  transform: translate(-50%, -50%);
}

/* ── laying out a spot handed over from the study ─────────────────────────
   The line has to be replayed against the server before the table means
   anything, so the felt says what it is doing instead of showing a half-built
   hand (owner request 2026-08-17). */
/* Centred by the LAYOUT, not by a transform. It was `top/left: 50%` plus a
   translate that lived inside the entrance keyframes — so with the system's
   Reduce Motion on, where that animation is switched off, the panel lost its
   centring and sat down-and-right of the middle (owner report 2026-08-17). A
   grid that fills the felt cannot be undone by anything. */
.felt-preparing-veil {
  pointer-events: none;
  position: absolute;
  z-index: 40;
  inset: 0;
  display: grid;
  place-items: center;
}

.felt-preparing {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  border: 1px solid var(--line-strong);
  border-radius: 12px;
  background: rgba(20, 20, 20, 0.92);
  padding: 16px 26px;
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.5);
  animation: felt-preparing-in 220ms ease-out both;
}

@keyframes felt-preparing-in {
  from {
    opacity: 0;
    transform: translateY(4px) scale(0.96);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

/* A ring that keeps opening outwards, with the spinner sitting inside it — the
   same idea as the seat on the clock, so the two read as one language. */
.felt-preparing-mark {
  position: relative;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border: 2px solid var(--accent-brand);
  border-radius: 999px;
}

.felt-preparing-mark::after {
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: 999px;
  animation: felt-preparing-ring 1.6s ease-out infinite;
}

@keyframes felt-preparing-ring {
  0% { box-shadow: 0 0 0 0 rgba(255, 158, 69, 0.55); }
  100% { box-shadow: 0 0 0 14px rgba(255, 158, 69, 0); }
}

.felt-preparing-spinner {
  /* An SVG rotates about its user-space origin unless told otherwise, which
     sends it round the corner instead of spinning in place. */
  transform-box: fill-box;
  transform-origin: center;
  color: var(--accent-brand);
  animation: felt-preparing-spin 900ms linear infinite;
}

@keyframes felt-preparing-spin {
  to { transform: rotate(360deg); }
}

.felt-preparing-text {
  font-size: 13px;
  font-weight: 900;
  letter-spacing: 0.04em;
  color: var(--text-normal);
}

.felt-preparing-sub {
  font-size: 11px;
  color: var(--text-muted);
}


/* The pot, announced at the top of the felt. Reads the SAME number as the pill
   under the pile — see potChips in PracticeTable. */
.pot {
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--surface-sunken);
  padding: 3px 12px;
  font-size: 12px;
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  color: var(--text-normal);
}

/* Nothing has been collected yet. `visibility`, never `display`: the line has
   to go on holding its own height, or the whole felt rises the moment the
   first street goes in. */
.pot.is-empty {
  visibility: hidden;
}

/* The slot is one card tall whether or not there is a card in it, so a flop
   arriving does not grow the felt and jump the whole table. Must match
   --card-height on .board-card. */
.board {
  display: flex;
  align-items: center;
  gap: 3px;
  min-height: 58px;
  margin-top: 12px;
}

/* The same size as a player's hole cards (owner request 2026-08-17): one deck
   on the table, not two. Keep the two numbers equal to .seat-card's, and the
   min-height on .board above equal to this height.

   A board card is dealt in from the left (owner-supplied animation
   2026-08-17). Two numbers own the timing: --dur is one card's slide, and the
   90ms below is the beat between cards of the same batch. The component only
   says WHICH card this is (`--i`, its place in the batch), so a lone turn card
   does not sit through the flop's stagger.

   `backwards` is what keeps a waiting card off screen: without it the card
   would be drawn in place first and jump left when its turn came. */
.board-card {
  --card-width: 44px;
  --card-height: 58px;
  --dur: 420ms;
  animation: deal-slide var(--dur) cubic-bezier(0.22, 0.7, 0.2, 1) backwards;
  animation-delay: calc(var(--i, 0) * 90ms);
}

@keyframes deal-slide {
  0% {
    transform: translateX(-300px);
    opacity: 0;
  }
  100% {
    transform: none;
    opacity: 1;
  }
}


/* ── the middle ───────────────────────────────────────────────────────────
   Chips that have been swept in sit UNDER the board with what they are worth
   written under them (owner request 2026-08-16), so a street ending reads as
   "everything went to the middle" and the number is where the eye already is.
   The slot keeps its height whether or not there is anything in it, for the
   same reason the message slot below does: a felt that grows mid-hand jumps
   the whole table. Bottom-aligned, so the amount stays put and only the pile
   grows upward — 68px is the tallest pile plus the pill under it. */
.pot-chips {
  display: flex;
  height: 68px;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
  margin-top: 8px;
}

/* Same pill as .pot at the top of the felt, minus the word: this one is read
   off the pile it sits under, so it needs no label (owner request
   2026-08-16). */
/* Comes up under the pile a beat after it lands, so the eye reads the chips
   first and the number second. */
.pot-chips-amount {
  animation: pot-amount-in 300ms ease-out 400ms both;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--surface-sunken);
  padding: 3px 12px;
  font-size: 12px;
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  color: var(--text-normal);
}

@keyframes pot-amount-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Width, never height: the four drawings share a chip diameter and differ only
   in how high the pile is, so at one width they stand on a common baseline and
   a bigger pot is a taller stack. Constrained by height they would invert —
   the single chip is the widest of the four.

   Chips are never animated (owner request 2026-08-16): money on a table is
   either there or it is not, and fading it in and out read as a glitch. */
.pot-chips-image {
  /* Same width as a seat's pile: chips are the same size wherever they sit on
     the table, so the middle must not be drawn in a bigger denomination. */
  width: 26px;
  height: auto;
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.5));
  /* The middle takes the street's chips. It lands rather than appears: up past
     full size and back down, which is what a pile dropped onto felt looks like
     (owner request 2026-08-19). No direction to it on purpose — the chips come
     from BOTH seats at once, so a slide from either one would be a lie about
     half of them. Keyed by the amount in PracticeTable, so every street that
     goes in gets its own landing. */
  animation: pot-chips-land 360ms cubic-bezier(0.22, 0.8, 0.24, 1) 260ms both;
}

@keyframes pot-chips-land {
  0% {
    opacity: 0;
    transform: scale(0.55);
  }
  62% {
    opacity: 1;
    transform: scale(1.12);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}

/* The verdict line lives in a slot of fixed height: without it the felt grew
   by a row the moment a message appeared and the whole table jumped
   (owner report 2026-08-16). */
.table-message-slot {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 24px;
  margin-top: 8px;
}

.table-message {
  border-radius: 6px;
  background: var(--surface-rail);
  padding: 4px 12px;
  font-size: 14px;
  font-weight: 700;
  color: var(--text-normal);
}

/* ── seats ────────────────────────────────────────────────────────────── */
.seat {
  position: relative;
  display: flex;
  align-items: center;
}

.seat-avatar {
  position: relative;
  /* 14 rather than 10 so that the turn glow, which has to come out OVER the
     hand (owner request 2026-08-19), can sit at 13 — above .seat-cards at 12 —
     and still be covered by the avatar itself, which is what keeps the light
     from washing the face out. Nothing on screen moves: the cards are absolute
     at `left: 100%` and never touch the avatar, so their order against it was
     never visible in the first place. */
  z-index: 14;
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  border: 3px solid var(--accent-brand);
  border-radius: 999px;
  background: var(--surface-panel);
  text-align: center;
}

/* EVERY glow this avatar can wear is painted here, on an empty ring lying
   exactly on its outer edge — never on the avatar itself.

   A box-shadow is painted together with the element it belongs to, so pulsing
   one on .seat-avatar re-rasterised the whole circle — fill, border and both
   lines of text — on every frame of an animation that never stops, and left the
   main thread with that work to do at the exact moment the seat name was
   sliding across it. A pseudo-element has nothing in it, so there is nothing to
   repaint but the ring, and the avatar is left animating `transform` alone,
   which the compositor can run without the main thread at all.

   -3px because an absolutely positioned box measures from the PADDING box:
   `inset: 0` would start the ring 3px inside the avatar's own border, so this
   number has to move whenever the border width does — the ring is meant to lie
   exactly ON the outer edge, not beside it. */
.seat-avatar::after {
  content: "";
  pointer-events: none;
  position: absolute;
  inset: -3px;
  border-radius: 999px;
}

.seat.is-hero .seat-avatar {
  border-color: var(--grade-good);
}

/* Whoever is on the clock GLOWS. Two layers of light, BEHIND the avatar.

   Behind is the whole point (owner report 2026-08-19). This used to hang off
   .seat-avatar, and a child cannot get under its parent's own background — a
   positioned pseudo-element is painted after it, whatever its z-index — so the
   light had to be a ring with a hole in the middle to keep from washing the
   face out. At the top of the breath the hole grew past the avatar's edge and
   opened a dark collar between the gold border and the glow. Hung off .seat
   instead, at a z-index under the avatar's own, the light is simply covered by
   the avatar (an opaque #292929 disc), so it can be solid all the way through
   and there is no hole left to escape.

   9 rather than nothing: .seat does not make a stacking context, so a layer at
   z-index auto is painted in document order — and the top seat comes before the
   felt, which would put the villain's glow under the table (the same trap
   .seat-cards documents below). Any positive number clears the felt; under 10
   keeps it under the avatar.

   TWO layers, one period, different peaks: the core swells at 50% and the wide
   bloom follows at 62%, so the light reads as spreading outward rather than as
   two things pulsing. They share 1.8s, so they never drift apart and never
   beat against each other.

   Neither ever reaches zero — the floor is 0.5, so a seat on the clock is lit
   at every moment of the cycle and the glow breathes instead of blinking out
   (owner request 2026-08-19, raised from 0.15 to 0.25 and then to 0.5 on
   2026-08-20). The bottom of the breath is what the eye reads as "off", so it
   has to stay clearly lit: at half strength the seat never stops being the lit
   one and the pulse is a swell on top of a light that is always on.

   --pulse is the colour as raw r,g,b so one pair of keyframe lists serves both
   seats: amber for the villain, green for the hero. It is set on the SEAT so
   that the layers, which are the seat's own pseudo-elements, inherit it. */
.seat.is-to-act {
  --pulse: 230, 169, 53;
}

.seat.is-hero.is-to-act {
  --pulse: 88, 185, 103;
}

.seat.is-to-act .seat-avatar {
  border-color: var(--grade-warn);
}

.seat.is-hero.is-to-act .seat-avatar {
  border-color: var(--grade-good);
}

/* Both discs are centred on the avatar: it is the seat's first in-flow child
   and 64px wide, so its middle is 32px in — the same anchor .seat-action uses.
   The negative margins are half the box, which centres it on that point and
   leaves `transform` free for the animation alone. */
.seat.is-to-act::before,
.seat.is-to-act::after {
  content: "";
  pointer-events: none;
  position: absolute;
  z-index: 13;
  top: 50%;
  left: 32px;
  width: 100px;
  height: 100px;
  margin: -50px 0 0 -50px;
  border-radius: 999px;
}

/* The core: bright where the avatar hides it, still half strength as it clears
   the rim, so the light comes out from under the disc with no seam. */
.seat.is-to-act::before {
  background: radial-gradient(
    closest-side,
    rgba(var(--pulse), 0.8),
    rgba(var(--pulse), 0.55) 58%,
    rgba(var(--pulse), 0) 90%
  );
  animation: seat-glow 1.8s ease-in-out infinite;
}

/* The bloom: wider, softer, and a beat late. */
.seat.is-to-act::after {
  background: radial-gradient(
    closest-side,
    rgba(var(--pulse), 0.45),
    rgba(var(--pulse), 0.3) 54%,
    rgba(var(--pulse), 0) 100%
  );
  animation: seat-bloom 1.8s ease-in-out infinite;
}

/* Only `opacity` and `transform`, on two empty discs: the compositor runs both
   on its own and the main thread never sees a frame of either. Identical first
   and last keyframes, so the loop has no seam to show. */
@keyframes seat-glow {
  0%, 100% {
    opacity: 0.5;
    transform: scale(0.94);
  }
  50% {
    opacity: 1;
    transform: scale(1.05);
  }
}

@keyframes seat-bloom {
  0%, 100% {
    opacity: 0.45;
    transform: scale(0.9);
  }
  62% {
    opacity: 0.8;
    transform: scale(1.12);
  }
}

/* The two lines inside an avatar, in their own wrapper so the seat name and
   the stack can be nudged towards each other (the translateY below) without
   moving the circle. */
.seat-avatar-text {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.seat-name {
  transform: translateY(2px);
  font-size: 16px;
  font-weight: 900;
  line-height: 1;
  color: var(--text-normal);
}

.seat-stack {
  transform: translateY(-2px);
  font-size: 13px;
  font-weight: 900;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

/* ── action flash ─────────────────────────────────────────────────────── */
/* What a seat just did is announced ON that seat, as a SWAP rather than a
   disappearance (owner request 2026-08-16): the seat name slides out to the
   left, the action rides in from the right in its place, holds, and then the
   two change back — the action leaves the way it came and the name slides in
   again behind it. The name is never simply switched off.

   Both halves move on the SAME axis in opposite directions, which is what makes
   it read as one thing replacing another rather than as two separate fades.
   Enter, hold and exit are ONE keyframe list per element: the chip is removed
   only after 1100ms, when it is already invisible, so nothing has to be
   unmounted mid-flight. Only the ring colour stays a variable — one keyframe
   list serves all four action kinds. */
.seat.is-flash-fold { --flash-rgb: 63, 127, 189; }
.seat.is-flash-call { --flash-rgb: 88, 185, 103; }
.seat.is-flash-bet,
.seat.is-flash-raise { --flash-rgb: 247, 52, 63; }
.seat.is-flash-allin { --flash-rgb: 134, 27, 33; }

/* The avatar takes the hit: one pop outwards in the colour of the action.
   Two animations on two elements rather than one list on the avatar: the pop is
   a `transform` and nothing else, so it stays on the compositor and carries the
   seat name and the stack with it smoothly, while the coloured ring is a
   box-shadow on the empty pseudo-element where a repaint costs nothing. Mixing
   the two in one keyframe list meant the circle and its text were re-painted 66
   times per announcement for the sake of a glow.
   Declared after .seat.is-to-act so it wins if a seat somehow carries both. */
.seat.is-flashing .seat-avatar {
  animation: seat-avatar-pop 1100ms cubic-bezier(0.22, 0.8, 0.24, 1) both;
}

.seat.is-flashing .seat-avatar::after {
  animation: seat-avatar-flash 1100ms cubic-bezier(0.22, 0.8, 0.24, 1) both;
}

@keyframes seat-avatar-pop {
  0% { transform: scale(1); }
  12% { transform: scale(1.07); }
  34%, 100% { transform: scale(1); }
}

@keyframes seat-avatar-flash {
  0% { box-shadow: 0 0 0 0 rgba(var(--flash-rgb), 0.6); }
  12% {
    box-shadow:
      0 0 0 7px rgba(var(--flash-rgb), 0.3),
      0 0 26px rgba(var(--flash-rgb), 0.55);
  }
  70% {
    box-shadow:
      0 0 0 3px rgba(var(--flash-rgb), 0.18),
      0 0 18px rgba(var(--flash-rgb), 0.3);
  }
  100% { box-shadow: 0 0 0 0 rgba(var(--flash-rgb), 0); }
}

/* The seat name rides OUT to the left and back in from the left. It keeps full
   opacity for the first stretch of the trip so the eye sees it leave; it only
   fades once it is off the avatar, which has no clipping of its own.
   The translateY(2px) is .seat-name's own resting nudge and has to be repeated
   in every frame, or the name would jump a couple of pixels as it goes.

   TWO animations, one for where the name IS and one for whether it can be seen,
   because they do not want the same shape. An easing curve is applied to each
   SEGMENT of a keyframe list, not to the list as a whole, so a frame added
   purely to time the fade also cuts the movement in two and gives the name a
   full stop in the middle of it. That is what made the return jerk (owner
   report 2026-08-17): the old list turned round at 78%, flew 31px to a
   dead stop at -7px on the 91% frame — cubic-bezier(.22,.8,.24,1) ends at zero
   speed — and then tore off again for the last 7px. Two hops, read as a stutter
   right where the eye was following it home.

   Split apart, the trip out is one accelerating segment and the trip back is
   ONE decelerating segment all the way to the resting position, with no
   keyframe in between to stop at. The fade keeps its own frames and cannot
   interrupt either.

   Both properties are ones the compositor can animate by itself, so neither
   depends on how busy the main thread is at the time. */
.seat-name.is-yield {
  animation:
    seat-name-slide 1100ms both,
    seat-name-fade 1100ms linear both;
}

@keyframes seat-name-slide {
  0% {
    transform: translateY(2px) translateX(0);
    animation-timing-function: cubic-bezier(0.5, 0, 0.86, 0.36);
  }
  20% {
    transform: translateY(2px) translateX(-38px);
    animation-timing-function: linear;
  }
  76% {
    transform: translateY(2px) translateX(-38px);
    animation-timing-function: cubic-bezier(0.18, 0.78, 0.28, 1);
  }
  100% { transform: translateY(2px) translateX(0); }
}

@keyframes seat-name-fade {
  0%, 9% { opacity: 1; }
  20%, 76% { opacity: 0; }
  93%, 100% { opacity: 1; }
}

/* The stack line dims under the chip instead of showing through it. */
.seat.is-flashing .seat-stack {
  animation: seat-stack-dim 1100ms cubic-bezier(0.22, 0.8, 0.24, 1) both;
}

@keyframes seat-stack-dim {
  0% { opacity: 1; }
  14%, 70% { opacity: 0.22; }
  100% { opacity: 1; }
}

/* The chip itself. It is wider than the avatar on purpose — "raise 5.6" reads
   as a call-out over the seat, the way a real client shows it, and it is the
   topmost thing a seat can show: 30 clears the cards, the chips and the dealer
   button, all of which are its siblings (see the note in PracticeTable).

   Centred on the AVATAR, not on the seat: the avatar is the seat's first
   in-flow child and 64px wide, so its middle is 32px in, while the seat's own
   middle is dragged right by the hand of cards beside it. */
.seat-action {
  position: absolute;
  top: 50%;
  left: 32px;
  z-index: 30;
  overflow: hidden;
  transform: translate(-50%, -50%);
  border-radius: 999px;
  padding: 6px 13px;
  font-size: 12px;
  font-weight: 900;
  line-height: 1;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  color: #ffffff;
  box-shadow:
    0 6px 18px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(255, 255, 255, 0.22);
  animation: seat-action 1100ms cubic-bezier(0.22, 0.8, 0.24, 1) both;
}

.seat-action.is-fold { background: var(--action-fold); }
.seat-action.is-passive { background: var(--action-passive); color: var(--text-on-accent); }
.seat-action.is-aggressive { background: var(--action-aggressive); }
.seat-action.is-allin { background: var(--action-allin); }

/* Comes in from the RIGHT as the name leaves to the left, and goes back out
   the same way it arrived. The scale bump on landing is what gives it weight;
   the blur only ever applies while it is off to the side. */
@keyframes seat-action {
  0% {
    opacity: 0;
    transform: translate(-50%, -50%) translateX(46px) scale(0.78);
    filter: blur(6px);
  }
  11% {
    opacity: 1;
    filter: blur(0);
  }
  17% {
    transform: translate(-50%, -50%) translateX(0) scale(1.12);
  }
  25% { transform: translate(-50%, -50%) translateX(0) scale(0.97); }
  32% { transform: translate(-50%, -50%) translateX(0) scale(1); }
  74% {
    opacity: 1;
    transform: translate(-50%, -50%) translateX(0) scale(1);
    filter: blur(0);
  }
  100% {
    opacity: 0;
    transform: translate(-50%, -50%) translateX(46px) scale(0.82);
    filter: blur(5px);
  }
}

/* One light sweep across the chip right after it lands, left to right, clipped
   by the chip's own overflow.

   It is a narrow band that MOVES rather than an oversized gradient whose
   background-position is animated. The two look the same; the difference is
   that a background-position has to be re-painted every frame, and the chip is
   itself flying across the avatar at the time — a repaint inside it forces the
   whole chip to be re-rastered mid-flight instead of just being moved by the
   compositor, and that is paid for in dropped frames on the animation the eye
   is actually following. A transform costs nothing. */
.seat-action::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 55%;
  background: linear-gradient(
    105deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.5) 50%,
    rgba(255, 255, 255, 0) 100%
  );
  animation: seat-action-sheen 1100ms ease-out both;
}

@keyframes seat-action-sheen {
  0%, 16% { transform: translateX(-110%); }
  56%, 100% { transform: translateX(300%); }
}


/* ── the number the other seat drew ───────────────────────────────────────
   The seat that is not the user's does not choose its move, it DRAWS one from
   the solution's own mix. This is the number that came up, shown for a moment
   over the avatar so the draw can be checked against the strategy behind it
   (owner request 2026-08-21).

   To the LEFT of the avatar (owner request 2026-08-21). Above it the badge ran
   into the strip along the top of the page; out to the side it clears
   everything — the dealer button shares that column but sits at the avatar's
   top or bottom edge, while this is on its centre line.

   One list of keyframes, and the holds inside it are safe: a segment whose two
   ends are identical is a hold whatever curve is applied to it, so the
   per-segment easing trap the seat name documents cannot bite here. */
.seat-roll {
  position: absolute;
  /* Over everything a seat carries, including its own cards and the word
     riding across it. */
  z-index: 40;
  top: 50%;
  right: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  margin-right: 10px;
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  background: var(--surface-panel);
  padding: 4px 9px;
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
  white-space: nowrap;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.5);
  animation: seat-roll-show 1000ms ease-out both;
}

/* The number that came up. */
.seat-roll b {
  font-size: 12px;
  font-weight: 900;
  color: var(--text-strong);
}

/* The band it fell in. Its WIDTH is the chosen action's own frequency, which is
   what makes the roll checkable against the panel without knowing the order the
   tree lays the actions out in — see MixDraw. */
.seat-roll i {
  font-size: 10px;
  font-style: normal;
  font-weight: 700;
  color: var(--text-muted);
}

/* Centred on the avatar and coming out of it: the badge starts a little to the
   right, under the disc, and slides clear. `translateY(-50%)` is the resting
   centring and has to be re-stated in every frame — a transform in an animation
   replaces the property outright rather than adding to it. */
@keyframes seat-roll-show {
  0% {
    opacity: 0;
    transform: translateY(-50%) translateX(10px) scale(0.9);
  }
  16%, 78% {
    opacity: 1;
    transform: translateY(-50%) translateX(0) scale(1);
  }
  100% {
    opacity: 0;
    transform: translateY(-50%) translateX(-4px) scale(0.97);
  }
}

.seat-dealer {
  position: absolute;
  z-index: 20;
  left: -22px;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 999px;
  background: #f1f1f1;
  font-size: 11px;
  font-weight: 900;
  color: var(--text-on-accent);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.45);
}

/* Top seat: button sits at its BOTTOM-left corner (towards the felt). Both
   buttons keep being pulled further onto the table as the table grows: -4px sat
   on the old edge, -12px was still short of the new rail, and -20px is where
   they read as lying ON the table rather than beside a seat (owner requests
   2026-08-19 and 2026-08-20). Down for the seat above, up for the seat below —
   both towards the middle. The two numbers are NOT the same: the rail of the
   table image runs further under the villain than it does over the hero, so the
   top button goes 8px deeper than the bottom one (owner request 2026-08-20). */
.seat.is-top .seat-dealer {
  bottom: -28px;
}

/* Bottom seat: button sits at its TOP-left corner (towards the felt). */
.seat.is-bottom .seat-dealer {
  top: -20px;
}

/* Above the felt. The top seat is painted BEFORE the felt is, so without a
   z-index of its own the villain's hand ends up behind the green (owner report
   2026-08-17) — the hero's looked fine only because that seat comes after the
   felt in the markup. ANY positive number does that job: the felt is positioned
   with z-index auto, so it is painted under every positive layer here.
   12 keeps it over the avatar's own 10 — the avatar's ring glows outwards and
   would otherwise wash across the near card — and under .seat-action's 30,
   which is the one thing on a seat that must never be covered. */
.seat-cards {
  position: absolute;
  z-index: 12;
  top: 50%;
  left: 100%;
  transform: translateY(-50%);
  margin-left: 4px;
  display: flex;
}

.seat-card {
  /* Through the variables, NOT width/height: the rank and the watermark are
     calc()'d from --card-height, so setting the box directly left them sized
     for the 78px family default while the card was drawn at 58 — which is why
     the appearance panel had no effect out here (owner report 2026-08-17). */
  --card-width: 44px;
  --card-height: 58px;
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.5);
}

.seat-card + .seat-card {
  margin-left: -12px;
}

/* ── the muck ──────────────────────────────────────────────────────────────
   A folded hand slides a little way OUT, to the right, and is gone (owner
   request 2026-08-20). It used to be thrown into the middle of the table; that
   read as a second thing happening on the felt at the exact moment the chips
   were going the other way, so the hand now simply leaves the frame the seat
   owns and lets the money have the middle to itself.

   PracticeTable puts this class on as soon as the fold is ANNOUNCED — the money
   model turns it on inside the same paced step — so the cards leave on the word
   rather than a beat after it, and the pot is collected on that same step.

   Two animations rather than one keyframe list, for the reason .seat-name-slide
   documents further up: a CSS easing curve is applied to every SEGMENT of a
   list, so the fade's own hold would break the slide's velocity exactly where
   it sits. Split, the travel owns one curve and the fade owns another.

   `translateY(-50%)` has to be re-stated in the keyframes: it is the resting
   rule's own centring, and a transform in an animation replaces the property
   outright rather than adding to it. It measures the element's own height, not
   the scale's, so it goes on centring the box at every size.

   `both` is what keeps the hand gone. It stays folded until the next one is
   dealt, and the fill holds the last frame — opacity 0 — for as long as it
   does. */
.seat-cards.is-mucked {
  pointer-events: none;
  animation:
    muck-away 340ms cubic-bezier(0.35, 0, 0.6, 1) both,
    muck-fade 340ms ease-in both;
}

/* Out and slightly over, shrinking as it goes: a hand pushed away rather than
   a card sliding on a rail. 64px is not far on purpose — it has to be clear
   that the cards LEFT, not that they travelled anywhere. */
@keyframes muck-away {
  from {
    transform: translateY(-50%) translate(0, 0) scale(1) rotate(0deg);
  }
  to {
    transform: translateY(-50%) translate(64px, -6px) scale(0.82) rotate(8deg);
  }
}

/* Held at full strength for the first third, so the hand is READ leaving
   rather than dissolving on the spot. */
@keyframes muck-fade {
  0% {
    opacity: 1;
  }
  35% {
    opacity: 0.92;
  }
  100% {
    opacity: 0;
  }
}

/* ── chips in front of a seat ─────────────────────────────────────────────
   What a player has out on the CURRENT street: a pile between the seat and
   the felt with the amount beside it. It appears the moment the action is
   announced and is gone the moment the street is swept — no flight, no fade
   (owner request 2026-08-16). Chips that slide and blink read as a glitch;
   money is either on the table or it is not.

   OFF-CENTRE, and that is load-bearing. The felt keeps only 60px of padding
   (owner values 2026-08-17), so a pile hanging off a seat reaches 83px in
   while the pot line starts at 63px: dead centre, everything above a single
   chip would sit on top of it. 100px sideways clears the widest pot pill with
   room to spare, and bets flanking the middle is how a real client draws them
   anyway. Villain to the right, hero to the left, so the two never share a
   column either. */
.seat-bet {
  position: absolute;
  z-index: 15;
  display: flex;
  align-items: flex-end;
  gap: 6px;
  transform: translateX(-50%);
}

/* Chips ride OUT of the seat towards the middle as they are put in: the top
   seat's travel DOWN, the bottom seat's travel UP (owner request 2026-08-19).
   Each ends a few pixels past its resting place and settles back, the way a
   stack pushed across felt does — a pile that simply stops dead reads as a
   sprite being switched on.

   Only `opacity` and `transform`, so the whole thing is the compositor's work
   and the pile's drop-shadow is never re-rasterised. The `translateX(-50%)`
   that centres the pile is part of every frame, or it would be dropped for the
   length of the animation and the chips would jump sideways.

   It plays on MOUNT, and PracticeTable keys .seat-bet by the amount, so it
   plays again for every raise on the same street rather than only for the
   first chips out. Between streets the bet is 0, the element goes, and the
   next street's first chips ride out afresh. */
.seat.is-top .seat-bet {
  top: 100%;
  left: calc(50% + 100px);
  margin-top: 8px;
  animation: seat-bet-down 320ms cubic-bezier(0.22, 0.8, 0.24, 1) both;
}

.seat.is-bottom .seat-bet {
  bottom: 100%;
  left: calc(50% - 100px);
  margin-bottom: 8px;
  animation: seat-bet-up 320ms cubic-bezier(0.22, 0.8, 0.24, 1) both;
}

@keyframes seat-bet-down {
  0% {
    opacity: 0;
    transform: translateX(-50%) translateY(-22px) scale(0.8);
  }
  70% {
    opacity: 1;
    transform: translateX(-50%) translateY(3px) scale(1.04);
  }
  100% {
    opacity: 1;
    transform: translateX(-50%) translateY(0) scale(1);
  }
}

@keyframes seat-bet-up {
  0% {
    opacity: 0;
    transform: translateX(-50%) translateY(22px) scale(0.8);
  }
  70% {
    opacity: 1;
    transform: translateX(-50%) translateY(-3px) scale(1.04);
  }
  100% {
    opacity: 1;
    transform: translateX(-50%) translateY(0) scale(1);
  }
}

/* The street is over and the chips go in: each pile crosses the felt to the
   middle, where the pot's own pile is about to land (owner request
   2026-08-19). The money model zeroed these the instant the street ended — the
   piles are still on screen only because useTableReplay holds them for the
   length of this flight; see Sweep there.

   TWO animations, not one list. Travel is a single segment so the curve is
   applied once and the chips accelerate away and settle without a kink; the
   fade is its own list because it has to hold full strength for most of the
   trip and only go at the end. Put in one list, the easing would be applied to
   each SEGMENT and the 70% frame would break the travel's velocity in half —
   the same trap the seat name fell into (see .seat-name-slide).

   The offsets are the real distance from each pile to the middle in the
   current layout: 100px across, and down or up to the pot pile, which is not
   in the felt's own middle. They do not have to be exact — the chips are
   already fading as they arrive, so a few pixels either way is invisible. */
.seat.is-top .seat-bet.is-sweeping,
.seat.is-bottom .seat-bet.is-sweeping {
  pointer-events: none;
}

.seat.is-top .seat-bet.is-sweeping {
  animation:
    seat-bet-sweep-down 460ms cubic-bezier(0.5, 0, 0.3, 1) both,
    seat-bet-gone 460ms ease-in both;
}

.seat.is-bottom .seat-bet.is-sweeping {
  animation:
    seat-bet-sweep-up 460ms cubic-bezier(0.5, 0, 0.3, 1) both,
    seat-bet-gone 460ms ease-in both;
}

@keyframes seat-bet-sweep-down {
  from {
    transform: translateX(-50%) translate(0, 0) scale(1);
  }
  to {
    transform: translateX(-50%) translate(-100px, 134px) scale(0.7);
  }
}

@keyframes seat-bet-sweep-up {
  from {
    transform: translateX(-50%) translate(0, 0) scale(1);
  }
  to {
    transform: translateX(-50%) translate(100px, -82px) scale(0.7);
  }
}

@keyframes seat-bet-gone {
  0%, 70% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}

.seat-bet-chips {
  width: 26px;
  height: auto;
  filter: drop-shadow(0 3px 5px rgba(0, 0, 0, 0.55));
}

.seat-bet-amount {
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.62);
  padding: 2px 7px;
  font-size: 12px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1.3;
  color: #f2f2f2;
}

/* ── action buttons ───────────────────────────────────────────────────── */
.action-bar {
  display: flex;
  width: 100%;
  max-width: 720px;
  /* Tall enough for the verdict panel that replaces the buttons at the end of
     a hand, so the bottom of the page does not grow the moment one finishes.
     Must match the height on .hand-verdict. */
  min-height: 72px;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.action-button {
  /* A row of its own, centred. A button leaves the browser to place its
     contents, and that placement stops being centring the moment the contents
     are more than one run of text — the refresh mark on Repeat hand sat in the
     top-left corner of it (owner report 2026-08-21). Stated here, every button
     carries its mark, its word and its key on one centred line. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  flex: 1;
  height: 50px;
  border: none;
  border-radius: 6px;
  font-size: 16px;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  cursor: pointer;
  transition: background-color 120ms ease;
}

/* The move is already sent and is being graded. Dim rather than gone: the
   buttons must not move out from under a finger that is still on its way down
   (owner report 2026-08-17). */
.action-button:disabled {
  cursor: default;
  opacity: 0.45;
}

.action-button.is-fold { background: var(--action-fold); }
.action-button.is-fold:hover { background: var(--action-fold-hover); }
.action-button.is-passive { background: var(--action-passive); }
.action-button.is-passive:hover { background: var(--action-passive-hover); }
.action-button.is-aggressive { background: var(--action-aggressive); }
.action-button.is-aggressive:hover { background: var(--action-aggressive-hover); }
.action-button.is-allin { background: var(--action-allin); }
.action-button.is-allin:hover { background: var(--action-allin-hover); }

/* "Next hand" and "Continue" stand in the same bar as fold/call/raise, because
   that is the one place the hand is played from (owner request 2026-08-16).
   They do NOT stretch across it: a single button 720px wide reads as a banner,
   not as something to click. Inside the verdict row the button shares the space
   with the score, so it gives up the fixed width there. */
.action-button.is-next,
.action-button.is-continue {
  display: flex;
  flex: 0 0 280px;
  align-items: center;
  justify-content: center;
  gap: 8px;
  color: var(--text-on-accent);
}

/* The orange one is written in white, not in the near-black the green buttons
   use — see --text-on-brand. */
.action-button.is-next {
  background: var(--accent-brand-deep);
  color: var(--text-on-brand);
}

/* Hovers up to the brand orange itself rather than to --accent-brand-hover:
   from this deeper resting colour that IS the step lighter. */
.action-button.is-next:hover { background: var(--accent-brand); }
.action-button.is-continue { background: var(--grade-good); }
.action-button.is-continue:hover { background: var(--action-passive-hover); }

/* ── the verdict on a finished hand ───────────────────────────────────────
   Takes over the action bar when there is nothing left to decide: the score,
   the streets it was earned on, and the way out — one row, one place to look
   (owner request 2026-08-16), redrawn from the owner's mockup 2026-08-17.

   --verdict is the hand's own colour, set on this row by the component from the
   worst grade in it, and it runs through the ring, the headline and the rail.
   Every street keeps its OWN colour on its dot, so a clean flop stays green
   inside a hand named after a preflop blunder.

   The height is fixed and .action-bar carries the same number as its
   min-height: this panel replaces the action buttons the instant a hand ends,
   and a bar that resized at that moment would jump the table above it. */
/* ── the verdict on a finished hand ───────────────────────────────────────
   A card in three bands rather than a single row (owner request 2026-08-21,
   from a mockup): what the hand was worth, how far it got, and the two things
   worth doing next.

   --verdict is the hand's own colour and is set inline; the rgb twin beside it
   is the same colour as raw channels, which is the only way a custom property
   can be taken at an opacity. Both are declared here as well, so a hand that
   was never scored still has something to resolve against — see .is-idle,
   which puts the neutral edge back. */
.hand-verdict {
  display: flex;
  width: 100%;
  /* Narrower than the row of action buttons it replaces: the card carries three
     short readings and two buttons, and at the bar's full width they drifted
     apart (owner request 2026-08-21). */
  max-width: 600px;
  flex-direction: column;
  margin-top: 10px;
  border-radius: 12px;
  padding: 6px 14px 11px 14px;
}

/* ── band one: what the hand was worth ─────────────────────────────────── */
.hand-verdict-top {
  display: flex;
  align-items: center;
  gap: 14px;
}

/* The share of this hand's decisions that cost nothing — the same measure the
   session score uses — drawn as how far round the circle it got. */
.hand-score {
  position: relative;
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: 42px;
  height: 42px;
}

.hand-score-ring {
  position: absolute;
  inset: 0;
  width: 38px;
  height: 38px;
  /* A dash starts at three o'clock; the score is read from twelve. */
  transform: rotate(-90deg);
}

.hand-score-track {
  fill: none;
  stroke: rgba(255, 255, 255, 0.09);
  stroke-width: 4;
}

.hand-score-arc {
  fill: none;
  stroke: var(--verdict);
  stroke-width: 4;
  stroke-linecap: round;
}

/* Deliberately NOT the verdict colour: the ring already carries that, and a
   number in the same amber as the ring around it stops being legible. */
.hand-score-value {
  position: relative;
  font-size: 12px;
  font-weight: 900;
  /* Back onto the middle of the ring: the ring is smaller than the box it sits
     in and is pinned to its top-left corner, so the number follows it. */
  top: -2px;
  left: -2px;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--text-strong);
}

/* One thing the hand is being reported on: a quiet heading and the number or
   phrase under it. Three of them share the row, spread left, middle and right,
   so each has a place of its own to be read from. */
.hand-fact {
  display: flex;
  min-width: 0;
  flex: 1;
  flex-direction: column;
  gap: 0;
}

.hand-fact.is-mid { text-align: center; }
.hand-fact.is-end { text-align: right; }

.hand-fact-label {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.hand-fact-value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 14px;
  font-weight: 800;
  color: var(--text-strong);
}

.hand-fact-value.is-verdict {
  color: var(--verdict);
}

.hand-outcome-amount {
  font-variant-numeric: tabular-nums;
}

.hand-outcome-amount.is-win { color: var(--grade-good); }
.hand-outcome-amount.is-loss { color: var(--grade-bad); }
.hand-outcome-amount.is-flat { color: var(--text-soft); }

.hand-verdict-idle {
  flex: 1;
  font-size: 15px;
  font-weight: 700;
  color: var(--text-soft);
}

/* ── band two: how far the hand got ────────────────────────────────────── */
.hand-streets {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  margin-top: 4px;
  padding: 9px 0;
}

/* A button, not a label: a street the hero was asked something on is the way
   back into the hand at that point (owner request 2026-08-21). One he was never
   asked on is drawn exactly the same and simply does not answer. */
.hand-street {
  --verdict: transparent;
  --verdict-rgb: 255, 255, 255;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease;
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 8px;
  padding: 6px 8px;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.3);
}

/* The hero was asked something here, and this is what came of it. */
.hand-street.is-graded {
  border-color: rgba(var(--verdict-rgb), 0.42);
  background: rgba(var(--verdict-rgb), 0.1);
  color: var(--verdict);
}

/* The hand travelled through, but nothing was asked of the hero. */
.hand-street.is-reached {
  border-color: rgba(255, 255, 255, 0.13);
  color: var(--text-soft);
}

.hand-street:not(:disabled):hover {
  border-color: rgba(var(--verdict-rgb), 0.6);
  background: rgba(var(--verdict-rgb), 0.16);
}

.hand-street:disabled {
  cursor: default;
}

.hand-street-mark {
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: 16px;
  height: 16px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.07);
  color: rgba(255, 255, 255, 0.35);
}

.hand-street.is-reached .hand-street-mark {
  background: rgba(255, 255, 255, 0.12);
  color: var(--text-soft);
}

/* Filled, with the tick punched out of it: the one street the eye should find
   first is the one that went wrong. */
.hand-street.is-graded .hand-street-mark {
  background: var(--verdict);
  color: #1b1b1b;
}

/* ── band three: what to do next ───────────────────────────────────────── */
.hand-verdict-actions {
  display: flex;
  gap: 10px;
}

.hand-verdict-actions .action-button {
  height: 40px;
  border-radius: 8px;
  font-size: 13px;
}

/* The quieter of the two: an outline, because playing the hand again is a
   second thought and moving on is the default. */
.action-button.is-repeat {
  flex: 1;
  border: 1px solid var(--line-strong);
  background: transparent;
  color: var(--text-strong);
}

.action-button.is-repeat:hover {
  background: var(--surface-hover);
}

.hand-verdict .action-button.is-next {
  flex: 1.4;
  /* The glow is the button's own orange, so it follows --accent-brand-deep. */
  box-shadow: 0 6px 16px rgba(252, 142, 41, 0.16);
}

/* The key that does the same thing, written on the button that does it. Set a
   little further off than the play mark is from the words: it is a note about
   the button, not part of its name. */
.action-key {
  margin-left: 5px;
  border: 1px solid rgba(255, 255, 255, 0.38);
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.2);
  padding: 3px 7px;
  font-family: inherit;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.02em;
  text-transform: none;
  /* Cut back from the label's white so the key reads as a note ON the button
     rather than as a second word of it. */
  color: rgba(255, 255, 255, 0.88);
}

/* A hand that never reached the hero — the SB folded on the button — has
   nothing to score, and an empty row would look like something failed. */
.hand-verdict-idle {
  flex: 1;
  font-size: 13px;
  color: var(--text-muted);
}

/* ── move review: a past decision, opened from the strip ──────────────────
   NOT a panel. The pieces stand free in the action bar exactly the way the
   game's own buttons do (owner request 2026-08-17): the actions that were on
   offer, the range bar under them, and the two ways out under that. Boxed into
   one card, a moment that has already been played read as a dialog rather than
   as the table. */
.action-bar.is-review {
  /* As wide as the felt above it (.table-stack), not as wide as the row of
     action buttons it replaces: this block carries four rows and the arrows
     that flank them, and at 720px the tiles were narrower than the moves they
     stand for (owner request 2026-08-17, trimmed to 880 on 2026-08-21). */
  max-width: 880px;
  align-items: center;
  gap: 12px;
  /* The system sans, not the app's Inter (owner request 2026-08-17): on macOS
     that is SF Pro, and -apple-system has to come FIRST or an installed Inter
     wins the stack in src/index.css. Set on the block so the tiles, the two
     buttons and the line between them are all in one face. */
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", system-ui, sans-serif;
}

/* The finished hand takes the SAME bar as the review panel: same width, same
   gap, arrows pushed to the same two edges. The pair of them is one place on
   the page, and an arrow that moved when the hand ended read as a different
   control (owner request 2026-08-21).

   `space-between` rather than the bar's own centring, because the card between
   them is capped at 600px and centring would gather all three in the middle
   instead of leaving the arrows where the panel's are. */
.action-bar.is-verdict {
  max-width: 880px;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

/* The two edges never move. Pinned a fixed way down from the top of the bar
   rather than centred in it: the bar is as tall as whatever it is holding, and
   the review panel and the verdict card are not the same height, so centred
   arrows shifted the moment one gave way to the other (owner report
   2026-08-21). One number, the same in both states, and they stay put whatever
   is between them. */
.action-bar.is-review .review-arrow,
.action-bar.is-verdict .review-arrow {
  align-self: flex-start;
  margin-top: 48px;
}

/* And the panel itself starts where the verdict card starts — .hand-verdict
   carries the same 10px — so the block between the arrows does not jump
   either. */
.action-bar.is-review .review-stack {
  margin-top: 10px;
}

/* Holds the pieces between the two arrows. NO background and NO border of its
   own — it is a layout column, not a card, so everything in it still stands
   free the way the game's own buttons do. */
.review-stack {
  display: flex;
  min-width: 0;
  flex: 1;
  flex-direction: column;
  gap: 10px;
}

/* One step back and one step forward through the hand's decisions, without
   going up to the carousel to click the next one (owner request 2026-08-17).
   Half the height of the panel beside them, so they read as a pair of edges
   rather than as two more buttons. */
.review-arrow {
  display: grid;
  width: 40px;
  height: 82px;
  flex-shrink: 0;
  place-items: center;
  border: none;
  border-radius: 8px;
  background: transparent;
  cursor: pointer;
  color: var(--text-soft);
  transition: background-color 120ms ease, color 120ms ease;
}

.review-arrow:hover {
  background: var(--surface-hover);
  color: var(--text-strong);
}

/* At the first or the last decision. Dimmed by COLOUR rather than by opacity
   (owner request 2026-08-17): a ghosted arrow read as a rendering fault, while
   a grey one reads as a road that ends. Still drawn, so the panel between them
   does not change width as you step through the hand. */
.review-arrow:disabled {
  cursor: default;
  color: var(--line-strong);
}

.review-arrow:disabled:hover {
  background: transparent;
}

.move-options {
  display: flex;
  gap: 8px;
}

/* One tile per action the seat had at that node, built like the buttons the
   game is played with — same height, same corners, same weight — because that
   is what they were. The percentage sits on the RIGHT inside the tile (owner
   request 2026-08-17), so a row reads name-then-number the way a strategy line
   does. */
.move-option {
  display: flex;
  height: 50px;
  min-width: 0;
  flex: 1;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  /* Outlined, never filled (owner request 2026-08-17): a filled tile reads as
     a button to press, and these are moves that were on offer at a moment
     already gone. The same 1px edge Change move wears, so the whole panel is
     one language. The colour of the action is carried by the text instead —
     see the inline `color` in MoveReview. */
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  background: #202020;
  padding: 0 14px;
  font-size: 14px;
  font-weight: 900;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  /* The words and the numbers are WHITE (owner request 2026-08-17); the tile's
     own `color` is the grade, and it is spent on the frame and the mark rather
     than on the text, which has to stay readable at any verdict. */
  color: var(--text-strong);
}

/* The verdict mark on a move tile — see GradeMark, which draws it with the
   shared GradeIcon. */
.move-option-mark {
  width: 17px;
  height: 17px;
}

/* The move actually made, framed in ITS OWN colour — red for a bet or a raise,
   green for a check or a call, blue for a fold, maroon for a jam — so the frame
   says WHAT was done as well as that something was (owner request 2026-08-17).
   currentColor is the action colour set on the tile.
   The padding gives back exactly the 3px the thicker border takes, the same
   trick .strip-step uses, so the tile does not change size when it is the one
   that was chosen. */
.move-option.is-taken {
  border-width: 3px;
  border-color: currentColor;
  /* Gives back exactly the 2px the thicker border takes, the same trick
     .strip-step uses, so the tile does not change size when it is the one that
     was chosen. */
  padding: 0 12px;
}

/* WHITE, explicitly. The tile's own `color` is the grade — it has to be, so
   that the frame and the mark can take it off currentColor — and everything
   inside a tile inherits it unless it says otherwise, which came out as red
   words on a red-framed tile (owner report 2026-08-17). */
.move-option-name {
  overflow: hidden;
  flex: 1;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-strong);
}

/* The percentage, and under it what the action is worth to the hand that was
   holding it. */
.move-option-figures {
  display: flex;
  flex-shrink: 0;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
}

.move-option-share {
  font-size: 14px;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--text-strong);
}

.move-option-ev {
  font-size: 10px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

/* The bar written out: one action per word, in the bar's own colours, centred
   under the tiles and above the bar it names. */
.move-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 14px;
  font-size: 13px;
  font-weight: 900;
  letter-spacing: 0.03em;
  /* Sentence case, not caps (owner request 2026-08-17): the tiles above are
     shouting already, and this line is a caption for the bar under it. The
     capital comes from the component, so "All-in" keeps its lower-case half. */
  text-transform: none;
}

/* Says whose numbers these are. The tiles above carry the hand's own mix and
   this line carries the whole range's, and without a word between them a 44%
   under a 0% reads as a contradiction (owner report 2026-08-17). */
.move-legend-title {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.move-legend-item i {
  font-style: normal;
  font-variant-numeric: tabular-nums;
  opacity: 0.75;
}

.move-review-foot {
  display: flex;
  gap: 8px;
}

.review-button {
  display: flex;
  height: 42px;
  flex: 1;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  background: var(--surface-rail);
  font-size: 14px;
  font-weight: 900;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  cursor: pointer;
  color: var(--text-normal);
  transition: background-color 120ms ease;
}

.review-button:hover {
  background: var(--surface-hover);
}

.review-button.is-primary {
  border-color: transparent;
  background: var(--accent-brand-deep);
  color: var(--text-on-brand);
}

.review-button.is-primary:hover {
  background: var(--accent-brand);
}

.review-button:disabled {
  cursor: default;
  opacity: 0.45;
}

/* ── hand history, to the right of the felt ───────────────────────────── */
/* ── the info panel ───────────────────────────────────────────────────────
   The solution behind the node the table is standing on, in the right-hand
   rail above the feed (owner request 2026-08-21).

   The matrix is the STUDY's — one component for one thing — so only its size is
   set here: 716px of grid has to become the width of a rail without the cells
   losing their shape or their labels. Scoped to this panel, so nothing about
   the study's own matrix moves. */
.practice-info {
  width: 100%;
  flex-shrink: 0;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-panel);
}

.info-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 10px;
}

.info-tabs {
  margin-top: 0;
}

/* Square, and as wide as it is given. The study's fixed 716x636 would keep its
   HEIGHT in a 278px column and hand every cell a 1:2 shape. */
.practice-info .matrix,
.practice-info .matrix-placeholder {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  border-radius: 5px;
}

.practice-info .matrix-cell {
  min-height: 0;
  padding: 0;
}

.practice-info .matrix-cell-label {
  font-size: 7px;
  font-weight: 800;
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.9);
}

.practice-info .matrix-cell-label.is-compact {
  top: 1px;
  left: 2px;
  font-size: 7px;
}

/* No room for a second number in a cell this size; the panel's own list below
   carries the figures. */
.practice-info .matrix-cell-metric {
  display: none;
}

.info-freqs {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.info-freqs-title {
  margin-bottom: 2px;
  font-size: 12px;
  font-weight: 800;
  color: var(--text-muted);
}

/* Name on the left, share on the right, both in the action's own colour — the
   same colour the bar under them is drawn in. */
.info-freq {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  font-size: 13px;
  font-weight: 800;
}

.info-freq-name {
  /* One word and a number — see actionText — so this only ever raises the
     word, the same way the strip does it. */
  text-transform: capitalize;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.info-freq-value {
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}

/* ── the history panel ────────────────────────────────────────────────────
   The session's record, in the right-hand rail. Rows go edge to edge and are
   separated by a line rather than by space: this is a list of many similar
   things, and gaps between them would cost the column half its rows. */
/* A COLUMN that is allowed to run out of room, with the rows as the part that
   gives: the head and the session line stay put and the list scrolls under
   them.

   It used to be one block in a scrolling rail, and it never scrolled at all
   (owner report 2026-08-20). `overflow: hidden` — which is here to keep the
   rows inside the rounded corner — also lets a flex item be squeezed below its
   own content, so with enough hands the panel was simply crushed to the height
   of the rail and the rest of the list clipped away, with no scrollbar
   anywhere. `max-height` rather than `flex: 1`, so a panel that has been
   collapsed stays the height of its own head instead of standing empty down
   the whole rail. */
.history {
  display: flex;
  width: 100%;
  /* Takes what the panel above it leaves, and gives the rest back by scrolling
     its own list — see .history-list. `0 1 auto` and not `1 1 auto`, so a
     collapsed feed stays the height of its own head instead of standing empty
     down the rail. */
  min-height: 0;
  flex: 0 1 auto;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-panel);
}

.history-head {
  display: flex;
  width: 100%;
  flex-shrink: 0;
  align-items: center;
  gap: 8px;
  border: none;
  border-bottom: 1px solid var(--line);
  background: transparent;
  padding: 11px 12px;
  font-size: 14px;
  font-weight: 700;
  color: var(--text-strong);
  cursor: pointer;
}

.history-head-icon {
  color: var(--text-muted);
}

.history-title {
  flex: 1;
  text-align: left;
}

/* The session the rows below belong to, headed by what it is worth so far. */
.history-session {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: 8px;
  border-bottom: 1px solid var(--line);
  background: var(--surface-sunken);
  padding: 8px 12px;
}

.history-session-score {
  font-size: 13px;
  font-weight: 900;
  font-variant-numeric: tabular-nums;
}

.history-session-score.is-good { color: var(--grade-good); }
.history-session-score.is-warn { color: var(--grade-warn); }
.history-session-score.is-bad { color: var(--grade-bad); }
.history-session-score.is-idle { color: var(--text-muted); }

.history-session-name {
  font-size: 12px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-soft);
}

.history-list {
  min-height: 0;
  overflow-y: auto;
  margin: 0;
  padding: 0;
  list-style: none;
}

.history-row + .history-row {
  border-top: 1px solid var(--line);
}

/* A finished hand UNROLLS at the top of the list (owner request 2026-08-20).
   It plays on MOUNT, and the rows are keyed by the hand's own id — see
   HandResult — so only the new one moves; keyed by position every row below it
   would be a new element and the whole column would come down at once.

   Growing the row's own height rather than sliding a finished row into place:
   a row that arrives at full height has to displace the ones under it in the
   same frame, which is the blink. Unrolled, the rows below are carried down by
   the layout itself, at exactly the rate the new row appears (owner report
   2026-08-20).

   `grid-template-rows` from 0fr to 1fr is what makes an unknown height
   animatable at all — a row is one line or twenty depending on whether it is
   open — and it only works on a CHILD, which is why there is a wrapper. The
   child needs `min-height: 0` or it refuses to be squeezed below its content,
   and `overflow: hidden` or the content spills out of the closed row. */
.history-row {
  display: grid;
  grid-template-rows: 1fr;
  animation: history-row-in 320ms cubic-bezier(0.22, 0.8, 0.24, 1) both;
}

.history-row-inner {
  min-height: 0;
  overflow: hidden;
  animation: history-row-fade 320ms ease-out both;
}

@keyframes history-row-in {
  from {
    grid-template-rows: 0fr;
  }
  to {
    grid-template-rows: 1fr;
  }
}

/* The height carries the movement, so this only has to bring the row out of
   nothing — a transform here would be clipped by the wrapper it lives in. */
@keyframes history-row-fade {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.history-row-head {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 7px 10px;
}

/* A hand the hero was never asked anything in has no verdict, and the row still
   has to line up with the ones that do. */
.history-nomark {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
}

.history-cards {
  display: flex;
  min-width: 0;
  flex: 1;
  align-items: center;
  gap: 2px;
}

/* Set off from the hand, so two cards and five read as two things. */
.history-board {
  display: flex;
  gap: 2px;
  margin-left: 7px;
}

.history-card {
  --card-width: 19px;
  --card-height: 25px;
  --card-radius: 2px;
}

.history-tools {
  display: flex;
  flex-shrink: 0;
  gap: 1px;
}

.history-tool {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border: none;
  border-radius: 5px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: color 120ms ease, background-color 120ms ease;
}

.history-tool:hover {
  background: var(--surface-hover);
  color: var(--accent-brand);
}

.history-tool:disabled {
  cursor: default;
  opacity: 0.35;
}

/* Kept: the star fills in rather than only changing colour, so a marked hand
   can be picked out of a column of them at a glance. */
.history-tool.is-on {
  color: var(--accent-brand);
}

.history-tool.is-on svg {
  fill: currentColor;
}

.history-tool-chevron {
  transition: transform 180ms cubic-bezier(0.22, 0.8, 0.24, 1);
}

.history-row.is-open .history-tool-chevron {
  transform: rotate(180deg);
}

.history-body {
  padding: 0 8px 8px;
  font-size: 12px;
  color: var(--text-soft);
}

/* ── a hand, read back ────────────────────────────────────────────────────
   Street by street, in blocks: what the street put down and what the pot stood
   at, then who did what — with the hero's own moves priced (owner request
   2026-08-20). Everything here comes off the copy the trainer kept when the
   hand ended; see HandResult.steps. */
.hand-read {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.hand-read-card {
  border-radius: 8px;
  background: var(--surface-sunken);
  padding: 9px 10px;
}

.hand-read-title {
  font-size: 14px;
  font-weight: 800;
  color: var(--text-strong);
}

/* The street names come off the tree in lower case; they are headings here. */
.hand-read-street .hand-read-title {
  text-transform: uppercase;
}

.hand-read-sub {
  margin-top: 1px;
  font-size: 12px;
  color: var(--text-muted);
}

/* Who sat where and on what, in the seats' own colours — the same green and
   amber the felt lights them with. */
.hand-read-seats {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 8px;
  font-size: 13px;
  font-weight: 700;
}

.hand-read-seat {
  display: grid;
  grid-template-columns: 46px 1fr;
  gap: 8px;
}

.hand-read-seat.is-hero { color: var(--grade-good); }
.hand-read-seat.is-villain { color: var(--grade-warn); }

.hand-read-street {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
}

.hand-read-cards {
  display: flex;
  flex-shrink: 0;
  gap: 3px;
}

.hand-read-face {
  --card-width: 23px;
  --card-height: 34px;
  --card-radius: 2px;
}

.hand-read-rows {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin-top: 8px;
}

/* Seat, what was done, how often the solution does it, what it cost. The two
   numbers sit at the right edge in their own columns, so a column of moves can
   be read down rather than across. */
.hand-read-row {
  display: grid;
  grid-template-columns: 46px 1fr auto auto;
  align-items: baseline;
  gap: 8px;
  font-size: 13px;
  font-weight: 700;
}

.hand-read-seat-name {
  color: var(--text-muted);
}

.hand-read-action {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-strong);
}

.hand-read-freq,
.hand-read-ev {
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--text-soft);
}

.hand-read-ev {
  min-width: 56px;
}

/* The unit, small and beside the number rather than under it. */
.hand-read-unit {
  font-size: 10px;
  font-style: normal;
  font-weight: 700;
  color: var(--text-muted);
}

.hand-read-show {
  display: grid;
  grid-template-columns: 46px 1fr;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 700;
}

.history-outcome {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 5px;
}

.history-note {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-dim);
}

.history-amount {
  flex-shrink: 0;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.history-amount.is-win { color: var(--grade-good); }
.history-amount.is-loss { color: var(--grade-bad); }
.history-amount.is-flat { color: #b8b8b8; }

.history-empty {
  padding: 14px 12px;
  font-size: 12px;
  color: var(--text-muted);
}


/* ── restored, not dealt ──────────────────────────────────────────────────
   Everything that was already on the table when the page opened. Coming back
   from the study restores a hand the trainer saved rather than starting one, so
   the board, the strip and the chips must simply BE there: dealing them again
   on every tab change read as the hand being played from the top, sound and all
   (owner report 2026-08-20).

   `animation-duration: 0ms`, never `animation: none`. Two reasons. Every one of
   these animations carries `both` or `backwards`, so a zero-length run lands on
   its end frame in the first painted frame — exactly "already there". And the
   mark can be taken off again without the animation springing to life: only a
   change of `animation-name` creates a new animation, while a change of
   duration merely re-measures one that has long since finished. With
   `animation: none` the same removal would start every entrance on the felt at
   once.

   Which elements carry the mark is decided in JS, by identity rather than by a
   flag that flips: see `restored` in PracticeTable, useRestoredTiles in
   PracticePage and DealOrder.instant in useDealDelays. */
.strip-step.is-instant {
  animation-duration: 0ms;
}

/* The delay too: a board card waits its turn in the batch, and `backwards`
   holds it off screen for as long as it waits. */
.board-card.is-instant {
  --dur: 0ms;
  animation-delay: 0ms;
}

.pot-chips-image.is-instant,
.pot-chips-amount.is-instant {
  animation-duration: 0ms;
  animation-delay: 0ms;
}

.seat.is-top .seat-bet.is-instant,
.seat.is-bottom .seat-bet.is-instant {
  animation-duration: 0ms;
}

/* A hand folded before the user arrived is simply not there. Without this the
   villain threw his cards away again on every trip back from the study (owner
   report 2026-08-20) — the muck plays on the class arriving, and on a restored
   table it arrives at mount. */
.seat-cards.is-mucked.is-instant {
  animation-duration: 0ms;
}

/* Hands the feed was restored with were played before the user arrived. */
.history-row.is-instant,
.history-row.is-instant .history-row-inner {
  animation-duration: 0ms;
}


/* ── animations off ───────────────────────────────────────────────────────
   The whole page, held still (owner request 2026-08-20). Chips flying and
   cards dealing are the point of the felt for most people and noise for some,
   so it is a preference — see prefs.ts.

   Durations of zero rather than `animation: none`, for the reason the restored
   table documents further up: changing a duration never re-creates an
   animation, so turning motion back on cannot fire every entrance on the page
   at once. Everything with a fill lands on the frame it would have ended on,
   which is exactly "already there".

   This is the DRAWING, not the pace: the hand still plays out step by step,
   because that is the hand being dealt rather than an effect on it. */
.practice-page.is-still *,
.practice-page.is-still *::before,
.practice-page.is-still *::after {
  animation-duration: 0ms !important;
  animation-delay: 0ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0ms !important;
  transition-delay: 0ms !important;
}
/* ============================================================================
   Practice trainer — left rail: brand, nav, table info, pause mode, stats.
   ============================================================================ */

/* Two of these: the settings on the left, the session's own feed on the right,
   the felt between them (owner request 2026-08-20). Same width and same
   surface, so the page reads as a table with a rail on either side rather than
   as a sidebar and an afterthought. */
.rail {
  /* Above the middle of the page, so a hover panel that reaches past the card
     it belongs to is not painted over by the felt (owner report 2026-08-20).
     .practice-main comes after this in the markup and would otherwise win on
     document order alone. */
  position: relative;
  z-index: 2;
  display: flex;
  height: 100vh;
  width: 310px;
  flex-shrink: 0;
  flex-direction: column;
  gap: 12px;
  overflow-y: auto;
  border-right: 1px solid var(--line);
  padding: 16px;
}

/* The feed manages its own overflow — the head stays and the rows scroll — so
   the rail itself does not scroll behind it. */
.rail.is-right {
  border-right: none;
  border-left: 1px solid var(--line);
  overflow: hidden;
}

/* The wings and the word are separate images (owner request 2026-08-17), so
   these three numbers are the whole layout: how far apart they sit, and how
   big each one is. Both are sized by HEIGHT so they share a baseline no matter
   what the word's aspect ratio is. */
.rail-brand {
  display: flex;
  align-items: center;
  /* Centred over the nav below it, not left-aligned to the rail's padding
     (owner request 2026-08-17). */
  justify-content: center;
  gap: 6px;
  padding: 0 2px 4px;
}

.rail-brand-w {
  height: 34px;
  width: auto;
}

.rail-brand-word {
  height: 34px;
  width: auto;
}




.rail-nav {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
  border-radius: 6px;
  background: var(--surface-panel);
  padding: 4px;
}

.rail-nav-button {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border: none;
  border-radius: 5px;
  background: transparent;
  padding: 8px;
  font-size: 14px;
  font-weight: 700;
  color: var(--text-muted);
  cursor: pointer;
  transition: background-color 120ms ease;
}

/* A button alone on its row takes both columns, which centres it under the
   pair above instead of leaving it half-width on the left (owner request
   2026-09-21). The label is already centred inside the button. */
.rail-nav-button.is-wide {
  grid-column: 1 / -1;
}

.rail-nav-button:hover {
  background: var(--surface-hover);
}

/* The brand colour, not just a lighter grey: on a rail of quiet buttons the
   chosen one has to be obvious at a glance, and the icon carries it too —
   lucide draws with `currentColor`, so it follows this line for free (owner
   request 2026-08-20). Every selected control in the rail is marked the same
   way; see .pause-option.is-active. */
.rail-nav-button.is-active {
  background: var(--surface-active);
  color: var(--accent-brand);
}

/* ── cards inside the rail ────────────────────────────────────────────── */
.rail-card {
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-panel);
  padding: 12px;
}

.rail-card-title {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  font-weight: 900;
  color: var(--text-normal);
}

.rail-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.rail-reset {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: background-color 120ms ease;
}

.rail-reset:hover {
  background: var(--surface-raised);
}

.rail-list {
  margin: 4px 0 0;
  padding: 0;
  list-style: none;
}

.rail-list li {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-soft);
}

/* ── table controls ───────────────────────────────────────────────────────
   A row of plain icon buttons under what the table IS: they act on it rather
   than describe it, so they are quiet until the pointer finds them. */
/* The captions below are positioned against THIS, so they start at the row's
   left edge whichever button is under the pointer. Anchored to the buttons
   they ran off the end of the rail — which scrolls, and therefore clips — and
   the last one lost its tail (owner report 2026-08-20). */
.table-controls {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
}

.table-control-wrap {
  position: relative;
  display: flex;
}

.table-control {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border: none;
  border-radius: 6px;
  /* A raw value: the rail's own surfaces are darker than this and none of them
     is the tone these buttons were asked for (owner request 2026-08-20). */
  background: #373737;
  color: var(--text-strong);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}

.table-control:hover {
  background: var(--surface-active);
  color: var(--accent-brand);
}

.table-control.is-off {
  color: var(--text-soft);
}

/* What the control does, under the pointer. Rises into place rather than
   appearing: a caption that snaps on reads as a glitch on a row where the
   pointer crosses four of them on its way anywhere.

   `white-space: nowrap` and the left anchor for the same reason .table-volume
   has them — the rail scrolls, so it clips on both axes, and a caption centred
   on a button this close to its edge would lose its left end. */
.table-hint {
  position: absolute;
  z-index: 5;
  top: 100%;
  /* Under the icon it belongs to, centred on it. The two ENDS of the row are
     the exception: the rail scrolls and therefore clips, so a caption centred
     on the first or last button would lose the half that hangs past the card
     (owner report 2026-08-20). They hang from their own edge instead, which is
     the only placement that cannot overflow. */
  left: 50%;
  visibility: hidden;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--surface-panel);
  padding: 5px 9px;
  margin-top: 6px;
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
  color: var(--text-normal);
  opacity: 0;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.45);
  transform: translate(-50%, -4px) scale(0.96);
  transform-origin: 50% 0;
  transition:
    opacity 150ms ease,
    transform 190ms cubic-bezier(0.22, 0.8, 0.24, 1),
    visibility 190ms;
}

.table-control-wrap:hover .table-hint,
.table-control-wrap:focus-within .table-hint {
  visibility: visible;
  opacity: 1;
  transform: translate(-50%, 0) scale(1);
}

.table-controls > .table-control-wrap:first-child .table-hint {
  left: 0;
  transform: translate(0, -4px) scale(0.96);
  transform-origin: 0 0;
}

.table-controls > .table-control-wrap:last-child .table-hint {
  left: auto;
  right: 0;
  transform: translate(0, -4px) scale(0.96);
  transform-origin: 100% 0;
}

.table-controls > .table-control-wrap:first-child:hover .table-hint,
.table-controls > .table-control-wrap:first-child:focus-within .table-hint,
.table-controls > .table-control-wrap:last-child:hover .table-hint,
.table-controls > .table-control-wrap:last-child:focus-within .table-hint {
  transform: translate(0, 0) scale(1);
}

.table-sound {
  position: relative;
  display: flex;
}

/* Over the button, not beside it, and anchored by its LEFT edge rather than
   centred: the rail scrolls, which makes it a clipping box on both axes, and a
   panel centred on a button this close to the rail's edge would lose its left
   end to that clip. */
.table-volume {
  position: absolute;
  z-index: 5;
  bottom: 100%;
  left: -6px;
  display: flex;
  visibility: hidden;
  align-items: center;
  opacity: 0;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-panel);
  padding: 9px 11px;
  margin-bottom: 6px;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.45);
  transform: translateY(4px);
  transition: opacity 140ms ease, transform 140ms ease, visibility 140ms;
}

.table-sound:hover .table-volume,
.table-sound:focus-within .table-volume {
  visibility: visible;
  opacity: 1;
  transform: translateY(0);
}

.table-volume input {
  width: 104px;
  height: 4px;
  -webkit-appearance: none;
  appearance: none;
  border-radius: 999px;
  background: var(--surface-active);
  cursor: pointer;
}

.table-volume input::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 13px;
  height: 13px;
  border-radius: 999px;
  background: var(--accent-brand);
}

.table-volume input::-moz-range-thumb {
  width: 13px;
  height: 13px;
  border: none;
  border-radius: 999px;
  background: var(--accent-brand);
}

/* ── pause mode switch ────────────────────────────────────────────────── */
.table-setting {
  margin-top: 14px;
}

.table-setting-title {
  /* Just off the card's edge, roughly over the start of the switch below
     (owner request 2026-08-20). */
  padding-left: 2px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-normal);
}

.pause-switch {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 4px;
  border-radius: 6px;
  background: #373737;
  margin-top: 3px;
}

.pause-switch.is-four {
  grid-template-columns: repeat(4, 1fr);
}

.pause-option {
  border: none;
  border-radius: 5px;
  background: transparent;
  padding: 6px 4px;
  font-size: 12px;
  font-weight: 700;
  color: var(--text-muted);
  cursor: pointer;
  transition: background-color 120ms ease;
}

.pause-option:hover:not(:disabled) {
  background: var(--surface-hover);
}

/* Not yet wired to anything, so it does not behave as though it were: no
   pointer, no hover. The chosen one still reads as chosen. */
.pause-option:disabled {
  cursor: default;
}

.pause-option.is-active {
  background: var(--surface-active);
  color: var(--accent-brand);
}

/* ── stats ────────────────────────────────────────────────────────────── */
.metric-row {
  display: grid;
  gap: 8px;
  margin-top: 8px;
  text-align: center;
}

.metric-row.is-three { grid-template-columns: repeat(3, 1fr); }
.metric-row.is-two { grid-template-columns: repeat(2, 1fr); }

.metric {
  border-radius: 6px;
  background: var(--surface-panel);
  padding: 6px 8px;
}

.metric-value {
  font-size: 16px;
  font-weight: 900;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--text-strong);
}

.metric-label {
  margin-top: 4px;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
}

/* The two bars above the five rows. Same column widths as .grade-row, which is
   what puts a bar's left edge exactly under the first letter of the verdict
   names below it. */
.grade-bars {
  margin-top: 12px;
  display: flex;
  flex-direction: column;
  gap: 0;
}

.grade-track {
  overflow: hidden;
  height: 12px;
  flex: 1;
  border-radius: 4px;
  background: var(--surface-active);
}

/* Animated on `width` rather than on a transform. A transform is the cheaper
   of the two and is what the felt uses everywhere — but scaling a rounded box
   horizontally pulls its corners into ellipses, and these ends are round. Two
   bars in a sidebar cost nothing to lay out. */
.grade-fill {
  display: block;
  height: 100%;
  border-radius: 4px;
  transition: width 200ms cubic-bezier(0.22, 0.8, 0.24, 1);
}

.grade-list {
  margin-top: 12px;
  display: flex;
  flex-direction: column;
  gap: 0;
}

/* Not space-between: that pushed the name to the far right, where five words
   of different lengths each started at a different place. The mark and its
   count hold a column of their own, so every name begins on the same line down
   the row and the block reads as one left-aligned column (owner request
   2026-08-20). */
.grade-row {
  display: flex;
  align-items: center;
  gap: 21px;
  font-size: 15px;
}

/* The mark and the count, together on the left. Fixed width, so a count that
   grows to two or three digits over a long session cannot push its own row's
   name out of the column. 46px is the icon, the gap and room for three
   digits. */
.grade-mark {
  display: flex;
  width: 54px;
  flex-shrink: 0;
  align-items: center;
  gap: 14px;
}

/* Drawn in its own verdict colour — the component sets that; see GradeIcon.
   Everything else about how it looks is here: bigger and heavier than a
   sidebar icon usually is, because these five marks are the block (owner
   request 2026-08-20). `stroke-width` inherits down to the paths, so one line
   sets the weight of all of them. */
/* Scoped to the rail, and by ancestor rather than by load order: the base rule
   lives in practice.css and both files are loaded together, so a bare
   `.grade-icon` here would win only by being second in the bundle. */
.rail .grade-icon {
  width: 18px;
  height: 18px;
  stroke-width: 2.3;
}

.grade-count {
  min-width: 16px;
  font-size: 15px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text-normal);
}

.grade-name {
  color: var(--text-strong);
}

.stats-divider {
  border-top: 1px solid var(--line);
  padding-top: 8px;
}

.stats-note {
  margin: 8px 0 0;
  font-size: 11px;
  line-height: 1.45;
  color: var(--text-muted);
}

/* ── last move feedback ───────────────────────────────────────────────── */
.last-move-grade {
  font-size: 13px;
  font-weight: 900;
}

.last-move-detail {
  margin-top: 4px;
  font-size: 12px;
  color: var(--text-soft);
}

/* Identical fixed brand/navigation and independently scrolling content on both pages. */
.rail-brand, .rail-nav { flex-shrink: 0; }
.rail-content-scroll { display: flex; flex: 1; min-height: 0; flex-direction: column; gap: 12px; overflow-y: auto; }
.rail-content-scroll > * { flex-shrink: 0; }
/* ============================================================================
   Shared primitives: card, badge, button, input, tabs, tooltip.
   These are the building blocks every page reuses.
   ============================================================================ */

.ui-card {
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-panel);
  color: var(--text-normal);
  box-shadow: 0 18px 60px rgba(0, 0, 0, 0.24);
}

.ui-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px;
}

.ui-card-title {
  display: flex;
  align-items: center;
  /* Tightened with the header bar (owner edit 2026-09-21): this is the shared
     card title, so the sidebar's Dataset and Flop headings follow it too. */
  gap: 5px;
  font-size: 14px;
  font-weight: 700;
  color: rgba(255, 255, 255, 0.84);
}

.ui-card-content {
  padding: 0 12px 12px;
}

.ui-badge {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-rail);
  padding: 2px 8px;
  font-size: 11px;
  font-weight: 700;
  color: var(--text-soft);
}

/* ── buttons ──────────────────────────────────────────────────────────── */
.ui-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border: none;
  border-radius: 6px;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}

.ui-button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.ui-button--md { height: 36px; padding: 0 12px; }
.ui-button--sm { height: 30px; padding: 0 8px; font-size: 13px; }
.ui-button--icon { width: 36px; height: 36px; padding: 0; }

.ui-button--subtle {
  background: var(--surface-rail);
  color: var(--text-normal);
}

.ui-button--subtle:hover:not(:disabled) {
  background: var(--surface-active);
}

.ui-button--ghost {
  background: transparent;
  color: var(--text-muted);
}

.ui-button--ghost:hover:not(:disabled) {
  background: var(--surface-panel);
  color: var(--text-normal);
}

.ui-button--primary {
  background: var(--accent-brand);
  color: var(--text-on-accent);
}

.ui-button--primary:hover:not(:disabled) {
  background: var(--accent-brand-hover);
}

/* ── input ────────────────────────────────────────────────────────────── */
.ui-input {
  width: 100%;
  height: 36px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface-panel);
  padding: 0 12px;
  font-size: 14px;
  color: var(--text-normal);
  outline: none;
}

.ui-input:focus {
  border-color: var(--accent-brand);
}

.ui-input::placeholder {
  color: var(--text-muted);
}

/* ── tabs ─────────────────────────────────────────────────────────────── */
.ui-tabs-list {
  display: grid;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-panel);
  padding: 4px;
}

/* the details card puts the tabs on a bar of their own */
.details-tabs-list.ui-tabs-list {
  border: 0;
  background: transparent;
  padding: 0;
}

.ui-tab {
  display: inline-flex;
  height: 36px;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: none;
  border-radius: 6px;
  background: transparent;
  padding: 0 12px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}

.ui-tab:hover {
  color: var(--text-normal);
}

.ui-tab[data-state="active"] {
  background: #3b3b3b;
  color: var(--accent-brand);
}

/* Tabs on the panel's toolbar bar: the same pill as `.toolbar-button` — 32px
   in a 36px bar, fully rounded — rather than a folder tab stuck to a border
   that no longer exists (owner request 2026-09-21). */
.details-tab {
  height: 32px;
  border-radius: 6px;
  font-size: 14px;
}

.ui-tooltip {
  z-index: 50;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: #2f2f2f;
  padding: 4px 8px;
  font-size: 12px;
  color: var(--text-normal);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

/* ── not-found route ──────────────────────────────────────────────────── */
.page-fallback {
  display: grid;
  min-height: 100vh;
  place-items: center;
  background: var(--surface-page);
  color: var(--text-normal);
}

.page-fallback-card {
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-panel);
  padding: 16px;
  text-align: center;
}

.page-fallback-title {
  font-size: 18px;
  font-weight: 800;
}

.page-fallback-button {
  margin-top: 12px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface-rail);
  padding: 8px 12px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-normal);
  cursor: pointer;
  transition: background-color 120ms ease;
}

.page-fallback-button:hover {
  background: var(--surface-raised);
}

/* small inline row used inside card titles */
.title-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
/* ============================================================================
   Study page — layout rail, toolbar, solution card and the spot/chance
   pickers.
   ============================================================================ */

/* ── page frame ───────────────────────────────────────────────────────── */
.study-page {
  display: grid;
  grid-template-columns: 310px minmax(0, 1fr);
  min-height: 100vh;
  background: var(--surface-page);
  color: var(--text-normal);
}

.study-main {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 8px;
  padding: 10px 14px 16px;
}

.study-columns {
  display: grid;
  grid-template-columns: minmax(0, 67fr) minmax(0, 45fr);
  /* Stretch, so the right rail ends exactly where the matrix ends. */
  align-items: stretch;
  min-width: 0;
  gap: 12px;
}

/* the matrix and its toolbar */
/* No padding and no gap under the toolbar: the matrix runs to the panel's own
   border, which is where the height freed on a laptop should go (owner edit
   2026-09-21, sent from DevTools). The Ranges view sets its own padding: 0
   and keeps working either way. */
.matrix-panel {
  container-type: inline-size;
  width: 100%;
  min-width: 0;
  max-width: 100%;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-panel);
}

.matrix-error {
  display: grid;
  min-height: 540px;
  place-items: center;
  border: 1px dashed var(--line);
  border-radius: 8px;
  font-size: 14px;
  font-weight: 600;
  color: #ff5d5d;
}

/* The rail is as tall as the matrix beside it and never taller (owner request
   2026-09-13). `height: 0` is what makes that true: an auto grid row is sized
   by the tallest item's CONTENT, and the Hands/Summary lists are thousands of
   pixels of content — they would set the row height and push the page into a
   scrollbar. With a specified height the rail contributes nothing to that
   measurement, the row is the matrix panel alone, and `min-height: 100%`
   stretches the rail back onto it. Everything inside then divides that height
   with flex, which is why none of the panels compute from `100vh` any more:
   those numbers could not know how tall the matrix actually is. */
.study-right {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 12px;
  height: 0;
  /* The 360px is a floor, not a design size: if a browser ever resolved the
     percentage against nothing the rail would be zero pixels tall and simply
     vanish, and a slightly-too-tall rail is a far better failure than a blank
     one. On any normal window the matrix is taller and the percentage wins. */
  min-height: max(360px, 100%);
}

/* ── left rail ────────────────────────────────────────────────────────── */
.study-rail {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  min-height: 100vh;
  width: 310px;
  flex-direction: column;
  gap: 12px;
  border-right: 1px solid var(--line);
  background: var(--surface-rail);
  padding: 16px;
}



.study-section-title {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  font-weight: 900;
  color: var(--text-normal);
}

/* ── dataset / spot list ──────────────────────────────────────────────── */
.spot-button {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-panel);
  padding: 12px;
  font-size: 14px;
  text-align: left;
  color: var(--text-soft);
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease;
}

.spot-button:hover {
  background: var(--surface-raised);
}

.spot-button.is-active {
  border-color: var(--accent-brand);
  background: var(--surface-raised);
  color: var(--text-strong);
}

.spot-button.is-locked {
  opacity: 0.5;
  cursor: not-allowed;
}

.spot-name {
  font-weight: 900;
}

.spot-meta {
  font-size: 12px;
  color: var(--text-muted);
}

/* ── flop search ──────────────────────────────────────────────────────── */
.flop-search {
  display: flex;
  align-items: center;
  gap: 8px;
}

.flop-search-input {
  flex: 1;
  height: 38px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface-panel);
  padding: 0 12px;
  font-size: 14px;
  color: var(--text-normal);
  outline: none;
}

.flop-search-input:focus {
  border-color: var(--accent-brand);
}

.icon-button {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface-panel);
  color: var(--text-soft);
  cursor: pointer;
  transition: background-color 120ms ease;
}

.icon-button:hover {
  background: var(--surface-raised);
}

.flop-list {
  display: flex;
  min-height: 0;
  flex: 1;
  flex-direction: column;
  gap: 8px;
  overflow-y: auto;
}

.flop-row {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-panel);
  padding: 8px 12px;
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease;
}

.flop-row:hover {
  background: var(--surface-raised);
}

.flop-row.is-active {
  border-color: var(--accent-brand);
  background: var(--surface-raised);
}

.flop-row-meta {
  font-size: 12px;
  color: var(--text-muted);
}

/* ── solution card (sidebar + strip) ──────────────────────────────────── */
.config-card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-panel);
  padding: 12px;
}

.config-title {
  font-size: 15px;
  font-weight: 900;
  line-height: 1.2;
  color: rgba(255, 255, 255, 0.88);
}

.config-bullets {
  margin-top: 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.config-bullet {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.25;
  color: var(--text-soft);
}

.config-tag {
  margin-top: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 11px;
  color: var(--text-muted);
}

.config-change {
  margin-top: auto;
  padding-top: 12px;
}

.config-change-button {
  display: flex;
  height: 36px;
  width: 100%;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border: none;
  border-radius: 6px;
  background: var(--surface-rail);
  font-size: 13px;
  font-weight: 700;
  color: var(--text-normal);
  cursor: pointer;
  transition: background-color 120ms ease;
}

.config-change-button:hover {
  background: var(--surface-active);
}

/* ── toolbar above the matrix ─────────────────────────────────────────── */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  min-height: 36px;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  border-radius: 6px;
  background: #202020;
  padding: 0 6px;
}

.toolbar-group {
  display: flex;
  flex-wrap: wrap;
  min-width: 0;
  align-items: center;
  gap: 4px;
}

.toolbar-group.is-tools {
  flex-shrink: 0;
}

/* ── strategy display picker ──────────────────────────────────────────────
   Our own menu, not a <select>: the popup of a native one belongs to the OS
   and takes no styling at all (owner request 2026-08-17). */
.display-menu {
  position: relative;
  display: inline-flex;
}

.display-menu-trigger {
  display: flex;
  height: 32px;
  align-items: center;
  gap: 8px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: var(--surface-panel);
  padding: 0 10px 0 12px;
  font-size: 14px;
  font-weight: 800;
  color: rgba(255, 255, 255, 0.88);
  outline: none;
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease;
}

.display-menu-trigger:hover {
  background: #404040;
}

.display-menu-trigger.is-open {
  border-color: rgba(255, 158, 69, 0.7);
  background: #404040;
}

/* The caret turns over while the list is down — the one cue that says the
   button is the thing holding the menu open. */
.display-menu-caret {
  transition: transform 140ms ease;
}

.display-menu-trigger.is-open .display-menu-caret {
  transform: rotate(180deg);
}

.display-menu-list {
  position: absolute;
  z-index: 60;
  top: 38px;
  left: 0;
  display: flex;
  min-width: 196px;
  flex-direction: column;
  gap: 2px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--surface-panel);
  padding: 6px;
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.5);
  animation: display-menu-open 120ms ease-out;
}

@keyframes display-menu-open {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.display-menu-item {
  display: flex;
  height: 30px;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  border: none;
  border-radius: 6px;
  background: transparent;
  padding: 0 8px 0 10px;
  text-align: left;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-soft);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}

.display-menu-item:hover {
  background: #404040;
  color: var(--text-normal);
}

.display-menu-item.is-selected {
  background: var(--surface-rail);
  color: var(--accent-brand);
}

.toolbar-caret {
  pointer-events: none;
  position: absolute;
  right: 8px;
  top: 50%;
  width: 0;
  height: 0;
  transform: translateY(-50%);
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid var(--text-dim);
}

.toolbar-button {
  position: relative;
  height: 32px;
  border: none;
  border-radius: 6px;
  background: transparent;
  padding: 0 12px;
  font-size: 14px;
  font-weight: 700;
  color: var(--text-muted);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}

.toolbar-button:hover {
  background: var(--surface-panel);
  color: rgba(255, 255, 255, 0.82);
}

.toolbar-button.is-toggle {
  /* Keep the range mode control hidden until it is needed again. */
  display: none;
  align-items: center;
  gap: 6px;
}

.toolbar-button.is-toggle.is-active {
  background: #343434;
  color: var(--text-strong);
}

.toolbar-button.has-caret {
  padding-right: 28px;
}

.toolbar-button .toolbar-caret {
  border-top-color: currentColor;
}

.toolbar-link {
  display: flex;
  height: 28px;
  align-items: center;
  gap: 4px;
  border: none;
  border-radius: 6px;
  background: transparent;
  padding: 0 8px;
  font-size: 12px;
  font-weight: 700;
  color: var(--text-muted);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}

.toolbar-link:hover {
  background: var(--surface-panel);
  color: rgba(255, 255, 255, 0.82);
}

.toolbar-link.is-copied {
  background: #2f4d33;
  color: #8fe39a;
}

.toolbar-icon {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}

.toolbar-icon:hover {
  background: var(--surface-panel);
  color: rgba(255, 255, 255, 0.82);
}

.toolbar-icon.is-copied,
.toolbar-icon.is-copied:hover {
  background: #2f4d33;
  color: #8fe39a;
}

.toolbar-icon:disabled {
  opacity: 0.4;
  cursor: default;
}

.toolbar-icon:disabled:hover {
  background: transparent;
  color: var(--text-muted);
}

/* ── spot / chance pickers ────────────────────────────────────────────── */
.picker-panel {
  border: 1px dashed var(--line);
  border-radius: 8px;
  background: var(--surface-panel);
  padding: 16px;
}

.picker-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.picker-title {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  font-weight: 800;
  color: var(--text-normal);
}

.picker-subtitle {
  margin-top: 2px;
  font-size: 12px;
  color: var(--text-muted);
}

.picker-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.flop-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(188px, 1fr));
  gap: 8px;
  max-height: 540px;
  overflow: auto;
  padding-right: 4px;
}

.flop-tile {
  display: flex;
  min-height: 56px;
  align-items: center;
  justify-content: space-between;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface-panel);
  padding: 8px 12px;
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease;
}

.flop-tile:hover {
  background: var(--surface-raised);
}

.flop-tile.is-active {
  border-color: var(--accent-brand);
  background: var(--surface-raised);
}

.flop-tile .card-chip {
  width: 28px;
  height: 32px;
}

.flop-tile-meta {
  font-size: 12px;
  color: var(--text-muted);
}

/* ── chance card picker ───────────────────────────────────────────────── */
.chance-rows {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.chance-row {
  display: grid;
  grid-template-columns: 22px repeat(4, 34px);
  align-items: center;
  gap: 8px;
}

.chance-rank {
  font-size: 12px;
  font-weight: 900;
  color: var(--text-muted);
}

.chance-card-button {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border: none;
  border-radius: 6px;
  background: transparent;
  padding: 0;
  cursor: pointer;
  transition: background-color 120ms ease;
}

.chance-card-button:hover:not(:disabled) {
  background: var(--surface-raised);
}

.chance-card-button:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

.chance-card-button.is-picked {
  background: var(--surface-raised);
  box-shadow: inset 0 0 0 1px var(--accent-brand);
}

.chance-card-button .card-chip {
  width: 28px;
  height: 32px;
  font-size: 13px;
}

/* dataset header row inside the sidebar card */
.dataset-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 12px;
  color: var(--text-muted);
}

.dataset-value {
  color: var(--text-soft);
}

/* the sidebar's flop list is capped so the solution card stays visible */
.study-rail .flop-list {
  scrollbar-width: none;
  max-height: 306px;
  margin-top: 8px;
}

/* ============================================================================
   Range matrix — the 13x13 hand grid.
   ============================================================================ */

.matrix {
  position: relative;
  display: grid;
  grid-template-columns: repeat(13, minmax(0, 1fr));
  grid-template-rows: repeat(13, minmax(0, 1fr));
  width: 100%;
  height: auto;
  aspect-ratio: 800 / 620;
  max-width: 100%;
  overflow: hidden;
  border: 1px solid var(--line);
  /* Square: the grid now sits against the panel's own rounded border, and a
     second radius inside it read as a seam (owner edit 2026-09-21). */
  background: #202020;
}

.matrix-placeholder {
  display: grid;
  width: 100%;
  height: auto;
  aspect-ratio: 800 / 620;
  max-width: 100%;
  place-items: center;
  border: 1px dashed var(--line);
  border-radius: 8px;
  font-size: 14px;
  color: var(--text-muted);
}

.matrix-placeholder.is-error {
  font-weight: 600;
  color: #ff5d5d;
}

/* ── one hand cell: left/bottom borders ───────────────────────────────── */
.matrix-cell {
  position: relative;
  min-height: 0;
  overflow: hidden;
  border: none;
  border-left: 1px solid #333;
  border-bottom: 1px solid #333;
  background: var(--surface-panel);
  padding: 4px;
  text-align: left;
  outline: none;
  cursor: default;
  transition: filter 300ms ease, transform 300ms ease;
}

/* A class with no combos here — none at all, or none left after the Actions
   filter — is a dead square: no click, no focus ring, no hover lift (owner
   report 2026-08-17). The `disabled` attribute does the work; this only makes
   sure nothing paints a highlight on it anyway. */
.matrix-cell:disabled {
  outline: none;
  filter: none;
  cursor: default;
}

.matrix-cell.has-reach {
  cursor: pointer;
}

.matrix-cell.has-reach:hover {
  z-index: 10;
  filter: brightness(0.78);
}

/* A class with no combos here reads as "off", not as a decorated square: it is
   the same flat dark tone as a hand hidden by the Simplified view. It used to
   carry a diagonal hatch, which drew the eye to exactly the cells that carry no
   information (owner request 2026-09-05). */
.matrix-cell.is-no-reach,
.matrix-cell.is-low-reach {
  background: #1f1f1f;
}

.matrix-cell.is-low-reach .matrix-cell-label {
  color: rgba(255, 255, 255, 0.26);
  text-shadow: none;
}

.matrix-cell.is-low-reach.is-low-reach-visible .matrix-cell-label {
  color: var(--text-strong);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.95);
}

.matrix-cell.is-active {
  z-index: 10;
  filter: brightness(0.78);
}

.matrix-cell.is-selected {
  z-index: 20;
  border-color: transparent;
}

.matrix-cell-label {
  position: absolute;
  z-index: 10;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 15px;
  font-weight: 900;
  line-height: 1;
  color: var(--text-strong);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.95);
}

.matrix-cell-label.is-compact {
  inset: auto auto auto 0;
  top: 4px;
  left: 4px;
  display: block;
  font-size: 15px;
}

/* Scale Study hand labels with their matrix, including compact display modes. */
.matrix-panel .matrix-cell-label {
  font-size: clamp(12px, 2cqw, 22px);
}

.matrix-cell-label.is-dim {
  color: rgba(255, 255, 255, 0.32);
}

.matrix-cell-metric {
  position: absolute;
  z-index: 10;
  right: 4px;
  bottom: 4px;
  max-width: calc(100% - 8px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: right;
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
  color: var(--text-soft);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9);
}

/* EV, equity, EQR and range share this slot in every matrix display mode. */
.matrix-panel .matrix-cell-metric {
  font-size: clamp(10px, 1.6cqw, 18px);
}

.matrix-panel .matrix-placeholder,
.matrix-panel .matrix-error,
.matrix-panel .matrix-loader-title {
  font-size: clamp(12px, 1.8cqw, 20px);
}

.matrix-panel .matrix-loader-sub {
  font-size: clamp(11px, 1.6cqw, 18px);
}

.matrix-cell-selection {
  pointer-events: none;
  position: absolute;
  inset: 0;
  z-index: 30;
  border: 3px solid var(--accent-brand);
}

/* fill that rises from the bottom of a cell */
.cell-fill {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

.cell-fill-band {
  display: flex;
  width: 100%;
  min-height: 1px;
  flex-shrink: 0;
}

/* ── skeleton + loader ────────────────────────────────────────────────── */
.matrix-skeleton-cell {
  position: relative;
  min-height: 0;
  overflow: hidden;
  border-bottom: 1px solid var(--line);
  border-right: 1px solid var(--line);
  background: #272727;
}

.matrix-skeleton-label {
  position: absolute;
  left: 4px;
  top: 4px;
  width: 28px;
  height: 12px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.08);
}

.matrix-skeleton-metric {
  position: absolute;
  right: 4px;
  bottom: 4px;
  width: 32px;
  height: 10px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.06);
}

.matrix-loading-veil {
  position: absolute;
  inset: 0;
  z-index: 30;
  display: grid;
  place-items: center;
  background: rgba(20, 20, 20, 0.34);
  backdrop-filter: blur(1px);
}

.matrix-loader-panel {
  display: flex;
  align-items: center;
  gap: 12px;
  border: 1px solid rgba(255, 158, 69, 0.28);
  border-radius: 8px;
  background: rgba(37, 37, 37, 0.95);
  padding: 12px 16px;
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.38);
}

.matrix-loader-mark {
  position: relative;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 999px;
  background: var(--surface-rail);
}

.matrix-loader-ring {
  position: absolute;
  inset: 0;
  border: 1px solid rgba(255, 158, 69, 0.25);
  border-radius: 999px;
}

.matrix-loader-text {
  display: flex;
  flex-direction: column;
}

.matrix-loader-title {
  font-size: 14px;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.88);
}

.matrix-loader-sub {
  font-size: 12px;
  color: var(--text-muted);
}

.matrix-loader-spinner {
  color: var(--accent-brand);
  animation: matrix-spin 1s linear infinite;
}

@keyframes matrix-spin {
  to { transform: rotate(360deg); }
}
/* A saved preflop profile can be studied before any postflop DB exists. */
.config-notice {
  margin: 10px 0 0;
  padding-top: 10px;
  border-top: 1px solid var(--line);
  color: var(--text-muted);
  font-size: 12px;
  line-height: 1.5;
}

.matrix-range-fill {
  width: 100%;
  background: #f0a334;
}

.solution-variant-control { position: relative; }
.solution-variant-trigger { display: flex; align-items: center; justify-content: center; width: auto; min-width: 64px; gap: 5px; padding-inline: 6px; }
.solution-variant-value { font-size: 11px; white-space: nowrap; }
.solution-variant-menu {
  position: absolute; right: 0; top: 34px; z-index: 90;
  width: 224px; padding: 6px 0; border-radius: 6px;
  background: var(--surface-panel); box-shadow: 0 5px 20px rgba(0, 0, 0, 0.4);
}
.solution-variant-option {
  display: flex; flex-direction: column; align-items: flex-start;
  width: 100%; gap: 4px; padding: 10px 12px; border: none;
  background: transparent; color: var(--text-normal); cursor: pointer; text-align: left;
}
.solution-variant-option small { font-size: 11px; }
.solution-variant-option:hover { background: #404040; }
.solution-variant-option.is-selected {
  background: linear-gradient(90deg, rgba(255, 158, 69, 0.34), rgba(255, 158, 69, 0.14));
  color: var(--text-strong); box-shadow: inset 2px 0 0 var(--accent-brand);
}

/* Transfer five percentage points from the matrix to details on laptop windows. */
@media (max-width: 1800px) {
  .study-columns { grid-template-columns: minmax(0, 61.4fr) minmax(0, 50.6fr); }
}
.config-size { display: flex; justify-content: space-between; gap: 8px; margin-top: 10px; color: var(--text-muted); font-size: 12px; }
.config-size strong { color: var(--text-soft); font-weight: 600; }

/* Summary hover is visual only: dim other classes without changing selection. */
.matrix.is-summary-preview .matrix-cell:not(.is-summary-focus) { filter: brightness(0.3); }
.matrix.is-summary-preview .matrix-cell.is-summary-focus { filter: brightness(1); }

/* Owner comparison: select the immutable poker configuration. */
.study-config-switcher select {
  min-width: 270px;
  max-width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--surface-raised);
  border-radius: 6px;
  background: var(--surface-panel);
  color: var(--text-normal);
  font-size: 14px;
}

.study-rail .flop-list::-webkit-scrollbar {
  display: none;
}

/* ============================================================================
   Hand drawer — the panel that slides out of a locked matrix cell with that
   class's own action mix (owner request 2026-09-13). Not a popup: it is a
   child of `.matrix`, anchored to the square that was clicked, and it opens
   INTO the grid — right/down from the top-left half, left/up from the
   bottom-right half — so it never has to reach past an edge the matrix clips.
   ============================================================================ */
.matrix-hand-drawer {
  position: absolute;
  /* Above every cell (selected cells stack at 20) and below the loading veil
     at 30, so a node still resolving greys the panel out with the grid. */
  z-index: 25;
  width: 224px;
  max-width: 48%;
  padding: 8px 10px 9px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: rgba(28, 28, 28, 0.96);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.55);
  color: var(--text-normal);
  /* Where it comes from and whether it is visible are two separate roles, so
     they are two separate animations (frontend_rules 5b.1) and each movement
     is one uninterrupted segment (5b.2). Only transform and opacity move. */
  animation:
    hand-drawer-open 220ms cubic-bezier(0.18, 0.78, 0.28, 1) both,
    hand-drawer-fade 180ms linear both;
}

.matrix-hand-drawer.is-left {
  animation:
    hand-drawer-open-left 220ms cubic-bezier(0.18, 0.78, 0.28, 1) both,
    hand-drawer-fade 180ms linear both;
}

@keyframes hand-drawer-open {
  from { transform: translateX(-18px); }
  to { transform: translateX(0); }
}

@keyframes hand-drawer-open-left {
  from { transform: translateX(18px); }
  to { transform: translateX(0); }
}

@keyframes hand-drawer-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

.hand-drawer-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 7px;
}

.hand-drawer-label {
  font-size: 15px;
  font-weight: 900;
  line-height: 1;
  color: var(--text-strong);
}

.hand-drawer-range {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 11px;
  color: var(--text-muted);
}

.hand-drawer-close {
  display: grid;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  place-items: center;
  border: none;
  border-radius: 5px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.hand-drawer-close:hover {
  background: var(--surface-raised);
  color: var(--text-strong);
}

.hand-drawer-mix {
  display: flex;
  height: 6px;
  margin-bottom: 8px;
  overflow: hidden;
  border-radius: 3px;
  background: #2a2a2a;
}

.hand-drawer-mix i {
  display: block;
  height: 100%;
}

.hand-drawer-rows {
  display: flex;
  flex-direction: column;
}

/* The negative margin matches the drawer's own padding, so the hover band runs
   edge to edge while the text stays on the title's line (frontend_rules 5a). */
.hand-drawer-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-inline: -10px;
  padding: 3px 10px;
}

.hand-drawer-row:hover {
  background: #404040;
}

.hand-drawer-row.is-unused {
  opacity: 0.45;
}

.hand-drawer-name {
  display: flex;
  min-width: 0;
  align-items: center;
  gap: 7px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
  color: var(--text-soft);
}

.hand-drawer-swatch {
  width: 8px;
  height: 8px;
  flex-shrink: 0;
  border-radius: 2px;
}

.hand-drawer-values {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}

.hand-drawer-values strong {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-strong);
}

/* Per-action EV in bb, only printed when the node actually carries one. */
.hand-drawer-values em {
  font-size: 11px;
  font-style: normal;
  color: var(--text-muted);
}

/* ============================================================================
   One-viewport Study (owner report 2026-09-13: the page could be nudged a few
   pixels even when everything was visible).

   The cause is arithmetic, not a stray element: the left rail is exactly
   100vh, `.study-page` is at least 100vh, and the main column is the node
   strip plus the matrix panel — whose height comes from its WIDTH through
   `aspect-ratio: 800/620`. Nothing in that chain knows about the window, so
   at most window sizes the column lands a handful of pixels over 100vh and
   the page scrolls by exactly the overhang, which is bottom padding and
   therefore invisible.

   So the matrix gives those pixels back instead: the column is told its real
   height, and the matrix shrinks into it. Width stays full, the cells lose
   the fraction of a percent that did not fit, and the page has nothing left
   to scroll. Where it already fits, nothing changes at all.

   Not applied to the Ranges comparison — two matrices, a stats column and the
   equity chart are genuinely taller than one screen and must stay scrollable
   — nor to windows too short to hold the layout, which keep the scrollbar
   they need.
   ============================================================================ */
@media (min-height: 700px) {
  .study-page:not(.is-ranges) {
    height: 100vh;
    overflow: hidden;
  }

  /* Safety only: the rail is built to fit 100vh (its flop list scrolls on its
     own), but it must never become unreachable if it one day does not. */
  .study-page:not(.is-ranges) .study-rail {
    overflow-y: auto;
  }

  .study-page:not(.is-ranges) .study-main {
    min-height: 0;
  }

  /* Everything the node strip did not take, and one row of exactly that size:
     an `auto` row would grow back to the matrix's natural height. */
  .study-page:not(.is-ranges) .study-columns {
    flex: 1;
    min-height: 0;
    grid-template-rows: minmax(0, 1fr);
  }

  .study-page:not(.is-ranges) .matrix-panel {
    display: flex;
    min-height: 0;
    flex-direction: column;
  }

  /* `min-height: 0` is what allows the shrink: a flex item's automatic minimum
     size would otherwise hold the matrix at its full aspect-ratio height and
     push the overhang straight back out of the panel.

     `flex: 1` is the other direction, and the one a laptop needs (owner report
     2026-09-21): the panel's row is the whole leftover height, while the
     matrix only ever asked for the height its WIDTH implies through the
     aspect ratio. On a narrower screen that is less, and the difference sat
     under the last row of the grid as dead space inside the panel. Growing
     into it costs nothing — the thirteen rows are 1fr each and simply divide
     whatever height the box ends up with — and on a wide monitor, where the
     aspect height already fills the row, there is nothing to grow into and
     the matrix keeps exactly the size it had. */
  .study-page:not(.is-ranges) .matrix,
  .study-page:not(.is-ranges) .matrix-placeholder,
  .study-page:not(.is-ranges) .matrix-error {
    flex: 1;
    min-height: 0;
  }
}

/* ── drawer: seat note and the class EV line ──────────────────────────── */
.hand-drawer-note {
  margin-bottom: 7px;
  font-size: 11px;
  color: var(--text-muted);
}

.hand-drawer-row.is-total {
  margin-top: 4px;
  border-top: 1px solid var(--line);
  padding-top: 5px;
}

.hand-drawer-row.is-total .hand-drawer-name {
  color: var(--text-muted);
}

/* ── node that failed to load (owner 2026-10-01) ─────────────────────────
   NodeErrorCard: takes the .matrix-error area, but in place of the bare red
   server text it says what failed, keeps the server message behind "Details
   for support" and offers Back / Try again. */
.matrix-error.node-error {
  padding: 16px;
  font-weight: 400;
  color: var(--text-normal);
}

.matrix-panel .matrix-error.node-error {
  font-size: 14px;
}

.node-error-card {
  display: grid;
  max-width: 380px;
  justify-items: center;
  gap: 12px;
  text-align: center;
}

.node-error-icon {
  display: grid;
  width: 40px;
  height: 40px;
  place-items: center;
  border-radius: 999px;
  background: rgba(255, 93, 93, 0.12);
  color: #ff5d5d;
}

/* "This line is never played": information, not an error. */
.node-error-icon.is-info {
  background: rgba(255, 255, 255, 0.08);
  color: var(--text-soft);
}

.node-error-title {
  font-size: 17px;
  font-weight: 700;
  color: var(--text-strong);
}

.node-error-text {
  font-size: 14px;
  line-height: 1.5;
  color: var(--text-muted);
}

.node-error-text b {
  font-weight: 600;
  color: var(--text-strong);
}

.node-error-buttons {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
}

.node-error-button {
  border: none;
  border-radius: 6px;
  background: var(--surface-raised);
  padding: 8px 14px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-strong);
  cursor: pointer;
  transition: filter 200ms ease;
}

.node-error-button.is-primary {
  background: var(--accent-brand);
  color: var(--text-on-brand);
}

.node-error-button:hover { filter: brightness(1.1); }
.node-error-button:focus-visible { outline: 2px solid var(--text-strong); outline-offset: 2px; }

.node-error-notice {
  display: flex;
  align-items: center;
  gap: 8px;
  border-radius: 6px;
  background: var(--surface-sunken);
  padding: 8px 12px;
  font-size: 12px;
  color: var(--text-soft);
}

.node-error-notice-dot {
  width: 8px;
  height: 8px;
  flex: none;
  border-radius: 999px;
  background: var(--action-passive);
}
/* ============================================================================
   Node strip — the horizontal carousel of decision cards above the matrix.
   ============================================================================ */

.node-strip {
  display: flex;
  align-items: flex-start;
  gap: 8px;
}

.node-strip-viewport {
  position: relative;
  min-width: 0;
  flex: 1;
}

.node-strip-track {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 4px;
}

/* ── hover arrows on both edges ───────────────────────────────────────── */
.strip-arrow {
  position: absolute;
  inset-block: 0;
  z-index: 10;
  display: flex;
  width: 56px;
  align-items: center;
  cursor: pointer;
  transition: opacity 300ms ease-out;
}

/* The fade has to be the colour of whatever the strip is lying on, or it draws
   a grey smear across it. The study's strip sits on the page; the practice
   table's sits on the felt's surround, which sets these two for itself. */
.strip-arrow.is-left {
  left: 0;
  justify-content: flex-start;
  padding-left: 4px;
  background: linear-gradient(
    to right,
    var(--strip-fade, var(--surface-page)),
    var(--strip-fade-soft, rgba(36, 36, 36, 0.7)),
    transparent
  );
}

.strip-arrow.is-right {
  right: 0;
  justify-content: flex-end;
  padding-right: 4px;
  background: linear-gradient(
    to left,
    var(--strip-fade, var(--surface-page)),
    var(--strip-fade-soft, rgba(36, 36, 36, 0.7)),
    transparent
  );
}

.strip-arrow.is-active {
  opacity: 1;
}

.strip-arrow.is-active:hover {
  opacity: 0.3;
}

.strip-arrow.is-hidden {
  pointer-events: none;
  opacity: 0;
}

.strip-arrow-icon {
  width: 28px;
  height: 28px;
  color: rgba(255, 255, 255, 0.92);
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.85));
}

/* ── cards ────────────────────────────────────────────────────────────── */

/* Card dimensions: edit here in DevTools; no inline size overrides. */
.node-card,
.node-card-center,
.node-card-pending,
.node-card-failed,
.node-card-empty {
  width: 116px;
  height: 170px;
}

/* Wider cards for FLOP / TURN / RIVER. */
.node-card.is-board,
.node-card-center.is-board {
  width: 134px;
}

.strip-rail {
  height: 170px;
}

.node-card {
  position: relative;
  display: flex;
  flex-shrink: 0;
  flex-direction: column;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-panel);
  padding: 8px;
  text-align: left;
  cursor: pointer;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 3px 0 rgba(0, 0, 0, 0.38);
  transition: background-color 120ms ease, border-color 120ms ease;
}

.node-card:hover {
  background: var(--surface-rail);
}

/* The card being viewed carries a 2px ring — drawn as an OUTLINE, not as a
   thicker border (owner report 2026-08-17). border-box keeps the card's outer
   size fixed, but a 2px border still eats a pixel off the CONTENT box on each
   side, so everything inside the card crept when it was selected. An outline
   takes no layout space at all, and a negative offset paints it exactly where
   the border would have been. */
.node-card.is-focused {
  outline: 2px solid var(--accent-brand);
  outline-offset: -2px;
  background: var(--surface-raised);
}

/* ── opened Custom card: content column + tool rail ───────────────────── */
/* The card keeps its column layout in the normal 116px width; opening it in a
   Custom solution turns it into a row so the edit / lock / compare rail can
   stand beside the actions instead of covering them (owner request
   2026-09-15). The extra width is exactly the rail plus its gap. */
.node-card-main {
  position: relative;
  display: flex;
  min-width: 0;
  min-height: 0;
  flex: 1;
  flex-direction: column;
}

.node-card.is-expanded {
  width: 152px;
  flex-direction: row;
  gap: 6px;
}

/* The selected-action band runs to the card's padding edge (5a). With the rail
   beside it, "the edge" is now the rail's border, so the band gives up its
   right overhang instead of sliding under the tools. */
.node-card.is-expanded .node-action {
  margin-inline: -8px 0;
  padding-inline: 8px 0;
}

.node-card-tools {
  display: flex;
  width: 24px;
  flex-shrink: 0;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  border-left: 1px solid rgba(255, 255, 255, 0.07);
  padding-left: 6px;
}

.node-card-tool {
  display: grid;
  width: 24px;
  height: 24px;
  place-items: center;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 6px;
  background: rgba(15, 15, 15, 0.45);
  padding: 0;
  color: var(--text-muted);
  cursor: pointer;
  transition: border-color 120ms ease, color 120ms ease, background-color 120ms ease;
}

.node-card-tool:disabled {
  opacity: 0.4;
  cursor: default;
}

.node-card-tool.is-enabled:hover {
  border-color: var(--accent-brand);
  background: var(--surface-raised);
  color: var(--text-strong);
}

/* Marker on a node whose frequencies are frozen. */
.node-card-lock {
  flex-shrink: 0;
  color: var(--accent-brand);
}

.node-card.is-static {
  cursor: default;
}

.node-card.is-static:hover {
  background: var(--surface-panel);
}

.node-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* Inline row, not a plain <strong>: the preflight makes every svg a block, so
   the lock badge of a node with frozen frequencies dropped onto its own line
   under the seat (owner report 2026-09-15). */
.node-card-seat {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 14px;
  color: var(--text-normal);
}

.node-card-seat.is-dim {
  color: var(--text-soft);
}

.node-card-stack,
.node-card-meta {
  font-size: 11px;
  color: var(--text-muted);
}

.node-card-meta {
  margin: 2px 0 6px;
}

.node-card-body {
  display: grid;
  flex: 1;
  place-items: center;
}

.node-card-footer {
  text-align: right;
  font-size: 11px;
  color: var(--text-muted);
}

.node-card-center {
  display: flex;
  flex-shrink: 0;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-panel);
  padding: 8px;
  text-align: center;
  font-size: 12px;
  color: var(--text-muted);
  cursor: pointer;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 3px 0 rgba(0, 0, 0, 0.38);
  transition: background-color 120ms ease;
}

.node-card-center:hover {
  background: var(--surface-rail);
}

.node-card-center.is-static {
  cursor: default;
}

.node-card-center strong {
  font-size: 14px;
  color: var(--text-normal);
}

.node-card-chance {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 14px;
  color: var(--text-normal);
}

/* ── action list inside a decision card ───────────────────────────────── */
.node-actions {
  display: flex;
  min-height: 0;
  flex: 1;
  flex-direction: column;
  gap: 0px;
  padding-bottom: 16px;
}

/* ── more actions than the card holds (owner 2026-10-02) ─────────────────
   Hovering the card opens the same card over the strip (.node-card-overlay,
   a portal: the strip scrolls sideways and would clip it), grown down to
   show every action. Leaving the card folds it back. */
/* Clipped at the card's own edge, so the selected band can still reach it. */
.node-card.is-decision {
  overflow: hidden;
}

/* The height is animated from script (NodeStrip DecisionCard); only the
   shadow eases here. */
/* z-index 45: over the matrix layers (up to 30) it grows across, under every
   dialog (.modal-backdrop 50, Custom Solutions 60, its pickers 65-80),
   tooltips (50) and menus (owner 2026-10-02: it covered a dialog at 85). */
.node-card-overlay {
  position: fixed;
  z-index: 45;
  overflow: hidden;
  transition: box-shadow 300ms ease;
}

.node-card-overlay.is-grown {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 18px 40px rgba(0, 0, 0, 0.6);
}



.node-explain-button {
  position: absolute;
  right: 5px;
  bottom: 4px;
  display: grid;
  width: 20px;
  height: 20px;
  place-items: center;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 50%;
  background: rgba(15, 15, 15, 0.55);
  padding: 0;
  color: var(--text-muted);
  cursor: pointer;
  transition: border-color 120ms ease, color 120ms ease, background-color 120ms ease;
}

.node-explain-button:hover {
  border-color: var(--accent-brand);
  background: var(--surface-raised);
  color: var(--text-strong);
}

/* No border at all, and the row runs the full inner width of the card: the
   negative margin takes it out to the card's padding edge on both sides and
   the padding puts the text back on the same line as the card's own title
   (owner request 2026-08-17). Selecting an action then lights up a band from
   edge to edge instead of drawing a pill inside the card. */
.node-action {
  display: flex;
  min-height: 22px;
  align-items: center;
  gap: 6px;
  border: none;
  background: transparent;
  margin-inline: -8px;
  padding: 0px 8px;
  text-align: left;
  font-size: 13px;
  line-height: 1.15;
  color: var(--text-soft);
  cursor: pointer;
  transition: background-color 120ms ease, box-shadow 120ms ease;
}

.node-action:hover {
  background: #404040;
}

.node-action.is-selected {
  background: linear-gradient(
    90deg,
    rgba(255, 158, 69, 0.34),
    rgba(255, 158, 69, 0.14)
  );
  color: var(--text-strong);
  box-shadow: inset 2px 0 0 var(--accent-brand);
}

.node-action-swatch {
  width: 10px;
  height: 10px;
  flex-shrink: 0;
  border-radius: 3px;
}

/* ── board cards inside a strip card ──────────────────────────────────── */
.strip-board {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 3px;
}

/* ── side rail of (not yet wired) tools ───────────────────────────────── */
.strip-rail {
  display: flex;
  width: 36px;
  flex-shrink: 0;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-panel);
  padding: 6px 0;
}

.strip-rail-button {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}

.strip-rail-button:hover {
  background: var(--surface-rail);
  color: rgba(255, 255, 255, 0.82);
}

/* ── pending card ─────────────────────────────────────────────────────── */
.node-card-pending {
  position: relative;
  display: grid;
  flex-shrink: 0;
  place-items: center;
  overflow: hidden;
  border: 1px solid rgba(255, 158, 69, 0.45);
  border-radius: 8px;
  background: var(--surface-panel);
  box-shadow:
    0 0 0 1px rgba(255, 158, 69, 0.08),
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 3px 0 rgba(0, 0, 0, 0.38);
}

.node-card-empty {
  flex-shrink: 0;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-panel);
  padding: 12px;
  font-size: 14px;
  color: var(--text-muted);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 3px 0 rgba(0, 0, 0, 0.38);
}

/* Approved node loading style: neutral shimmer, always animated. */
.node-card-pending { border-color: #515151; }
.node-loading-skeleton { position: absolute; inset: 13px 9px; z-index: 1; }
.node-loading-heading, .node-loading-subheading { position: relative; overflow: hidden; display: block; height: 6px; width: 58px; border-radius: 2px; background: #414141; }
.node-loading-subheading { width: 40px; height: 5px; margin-top: 7px; background: #383838; }
.node-loading-rows { display: flex; flex-direction: column; gap: 9px; margin-top: 24px; }
.node-loading-rows > span { position: relative; display: block; height: 24px; overflow: hidden; border: 1px solid #484848; border-radius: 4px; background: #343434; }
.node-card-pending .node-loading-heading::before,
.node-card-pending .node-loading-subheading::before,
.node-card-pending .node-loading-rows > span::before { content: ""; position: absolute; z-index: 1; top: -6px; bottom: -6px; left: -65%; width: 60%; pointer-events: none; background: linear-gradient(90deg, transparent, #ffffff18, #ffffff50, #ffffff18, transparent); animation: node-loading-row-sheen 700ms ease-in-out infinite; }
.node-card-pending .node-loading-subheading::before,
.node-card-pending .node-loading-rows > span:nth-child(2)::before { animation-delay: 50ms; }
.node-card-pending .node-loading-rows > span:nth-child(3)::before { animation-delay: 100ms; }
@keyframes node-loading-row-sheen { 0% { transform: translateX(0) skewX(-18deg); opacity: 0; } 15% { opacity: 1; } 80%, 100% { transform: translateX(300%) skewX(-18deg); opacity: 0; } }

/* A node that failed to load: same box as the loading card, red outline and
   a short label instead of the shimmer (owner 2026-10-01). */
.node-card-failed {
  display: grid;
  flex-shrink: 0;
  place-content: center;
  justify-items: center;
  gap: 8px;
  border: 1px solid #ff5d5d;
  border-radius: 8px;
  background: rgba(255, 93, 93, 0.1);
  font-size: 13px;
  font-weight: 600;
  color: #ff5d5d;
}

/* Turn / river card: the FLOP card layout with one card, so narrower
   (owner 2026-10-01): fits "RIVER" and a three-digit pot in the head. */
.node-card.is-board.is-chance {
  width: 108px;
}

.node-card-chance-empty {
  font-size: 13px;
  color: var(--text-muted);
}
/* ============================================================================
   Right-hand panels: Actions summary, Hands / Summary / Blockers tabs and the
   per-combo cards.
   ============================================================================ */

.panel-empty {
  font-size: 14px;
  color: var(--text-muted);
}

/* "No strategy for this node" (a node that failed to load): centred in the
   Actions card, about as tall as the row of action tiles it stands in for. */
.panel-empty.is-centered {
  display: grid;
  min-height: 112px;
  place-items: center;
  text-align: center;
}

.panel-placeholder {
  display: grid;
  /* Fills the tab body instead of forcing a height into it: the rail is sized
     by the matrix now, so a panel that insisted on 220px could overflow it. */
  flex: 1;
  min-height: 0;
  place-items: center;
  padding: 16px;
  text-align: center;
}

.panel-placeholder-title {
  font-size: 14px;
  font-weight: 900;
  color: rgba(255, 255, 255, 0.84);
}

.panel-placeholder-text {
  margin-top: 4px;
  max-width: 260px;
  font-size: 12px;
  line-height: 1.55;
  color: var(--text-muted);
}

.panel-message {
  display: grid;
  min-height: 96px;
  place-items: center;
  font-size: 14px;
  color: var(--text-muted);
}

.panel-message.is-error {
  font-weight: 600;
  color: #ff5d5d;
}

/* Same bar as `.details-tabs-bar` and `.toolbar` — see the note there. */
.actions-card > .ui-card-header {
  min-height: 36px;
  gap: 8px;
  border-radius: 6px;
  background: #202020;
  padding: 0 8px;
}

/* An even 4px all round: enough to separate the tiles from the card's border
   without the wide gutter the shared card content had (owner edit 2026-09-21,
   sent from DevTools). */
.actions-card > .ui-card-content {
  padding: 4px;
}

/* ── action tiles ─────────────────────────────────────────────────────── */
.action-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(124px, 100%), 1fr));
  gap: 6px;
}

/* Each tile is a filter switch for the matrix (owner request 2026-08-17): the
   name on top, the share and the combos behind it along the bottom. */
.action-tile {
  container-type: inline-size;
  min-width: 0;
  display: flex;
  min-height: 80px;
  flex-direction: column;
  justify-content: space-between;
  border: none;
  border-radius: 6px;
  padding: 8px;
  text-align: left;
  color: #f5f5f5;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
  cursor: pointer;
  transition: filter 300ms ease, opacity 300ms ease;
}

/* Once a filter is on, the tiles say which way they went: the chosen ones keep
   a bright ring, the rest fade back but stay readable and still clickable —
   that is how a second action is added to the selection. */
.action-tile.is-on {
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.12),
    0 0 0 2px #f5f5f5;
}

.action-tile.is-off {
  opacity: 0.36;
}

.action-tile.is-off:hover {
  opacity: 0.36;
}

.action-tile-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 14px;
  font-weight: 900;
  line-height: 1.15;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
}

.action-tile-foot {
  display: flex;
  flex-wrap: wrap;
  min-width: 0;
  align-items: flex-end;
  justify-content: space-between;
  gap: 6px;
  margin-top: 16px;
}

.action-tile-value {
  white-space: nowrap;
  font-size: clamp(16px, 17cqw, 24px);
  font-weight: 900;
  line-height: 1;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
}

/* Sits in the bottom-right corner, the way a solver quotes it. */
.action-tile-combos {
  margin-left: auto;
  white-space: nowrap;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  font-size: 12px;
  font-weight: 800;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
  opacity: 0.92;
}

.action-tile-combos i {
  font-size: 10px;
  font-style: normal;
  font-weight: 700;
  opacity: 0.8;
}

/* ── active filters, next to the panel title ──────────────────────────── */
/* The title row is 23px from the start — the height a filter chip needs —
   whether or not there is a filter on. Sized by its own content it grew the
   moment one appeared and shrank again when it was cleared, and the whole
   panel twitched with it (owner report 2026-08-17). The chips themselves are
   untouched; only the room they land in is reserved.

   23px is arithmetic, not a guess: the chips below pin their line-height, so a
   chip is 12px x 1.2 + 6px padding + 2px border = 22.4px and Clear is
   11px x 1.2 + 8px padding = 21.2px. Change either and change this. */
.action-title {
  min-height: 23px;
}

.action-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-left: 4px;
}

.action-filter {
  display: flex;
  align-items: center;
  gap: 5px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--surface-raised);
  padding: 3px 9px;
  font-size: 12px;
  font-weight: 700;
  /* Pinned so the row above can reserve exactly the right height. */
  line-height: 1.2;
  color: var(--text-normal);
  cursor: pointer;
}

.action-filter:hover {
  border-color: #f5f5f5;
}

.action-filter-clear {
  border: none;
  border-radius: 999px;
  background: #e05a52;
  padding: 4px 12px;
  font-size: 11px;
  font-weight: 900;
  line-height: 1.2;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #ffffff;
  cursor: pointer;
}

.action-filter-clear:hover {
  background: #c9483f;
}

/* ── horizontal mix bar ───────────────────────────────────────────────── */
.mix-bar {
  display: flex;
  height: 16px;
  overflow: hidden;
  border-radius: 6px;
  background: var(--surface-raised);
  margin-top: 8px;
}

.mix-bar.is-tight {
  border-radius: 3px;
  margin-top: 0;
}

.mix-bar i {
  display: block;
}

.mix-bar-empty {
  display: block;
  width: 100%;
  background: var(--surface-page);
}

/* ── tabs ─────────────────────────────────────────────────────────────── */
.details-card {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

.details-tabs {
  display: flex;
  flex: 1;
  min-height: 0;
  flex-direction: column;
}

/* Both right-hand headers are the matrix toolbar: the same 36px bar, the same
   #202020, the same 6px radius and 0 6px padding, so the three read as one
   strip of controls instead of three different panels (owner request
   2026-09-21). Keep them in step with `.toolbar` in study.css. */
.details-tabs-bar {
  display: flex;
  min-height: 36px;
  align-items: center;
  border-radius: 6px;
  background: #202020;
  padding: 0 6px;
}

.details-tabs-list {
  display: grid;
  width: 100%;
  grid-template-columns: repeat(4, 1fr);
  border: 0;
  background: transparent;
  padding: 0;
}

.details-tab {
  height: 32px;
  border-radius: 6px 6px 0 0;
  font-size: 12px;
}

.details-tabs-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-height: 0;
}

/* Radix renders each tab's panel as a plain div; it has to pass the height on
   to the list inside rather than wrap it. */
.details-tabs-body > [role="tabpanel"][data-state="active"] {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-height: 0;
}

/* ── action breakdown list ────────────────────────────────────────────── */
.breakdown-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.breakdown-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--line);
  padding-bottom: 6px;
  font-size: 14px;
}

.breakdown-name {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--text-soft);
}

.breakdown-swatch {
  width: 10px;
  height: 10px;
  border-radius: 3px;
}

.breakdown-value {
  color: var(--text-normal);
}

.breakdown-total {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 12px;
  font-size: 12px;
  color: var(--text-muted);
}

/* ── scrolling tables (summary, blockers) ─────────────────────────────── */
.data-table {
  max-height: calc(100vh - 300px);
  min-height: 220px;
  overflow: auto;
}

.data-table-head {
  position: sticky;
  top: 0;
  z-index: 10;
  display: grid;
  align-items: center;
  gap: 8px;
  border-bottom: 1px solid var(--line);
  background: var(--surface-page);
  padding: 8px 12px;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

.data-table-head.is-tight {
  gap: 4px;
  font-size: 11px;
}

.data-table-row {
  display: grid;
  align-items: center;
  gap: 8px;
  border-bottom: 1px solid var(--line);
  padding: 6px 12px;
  font-size: 14px;
}

.data-table-row.is-tight {
  gap: 4px;
  padding: 4px 12px;
  font-size: 13px;
}

.data-table-row.is-selected {
  background: #33302a;
}

.data-table-row:hover {
  background: #262626;
}

.cell-right {
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--text-soft);
}

.cell-right.is-strong {
  color: var(--text-normal);
}

.cell-center {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: center;
}

.cell-cards {
  display: flex;
  align-items: center;
  gap: 2px;
}

.cell-cards .card-chip,
.combo-cards .card-chip {
  width: 18px;
  height: 24px;
  font-size: 12px;
}



/* ── combo drill-down list ────────────────────────────────────────────── */
.combo-scroll {
  max-height: 390px;
  overflow: auto;
  padding-right: 4px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.combo-line {
  display: grid;
  grid-template-columns: 54px 54px 1fr;
  align-items: center;
  gap: 8px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface-panel);
  padding: 6px 8px;
  font-size: 12px;
}

.combo-line-combo { color: var(--text-normal); }
.combo-line-reach { color: var(--text-muted); }

.combo-line-actions {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: right;
  color: rgba(255, 255, 255, 0.68);
}

/* ── hands panel ──────────────────────────────────────────────────────── */
/* GTOWizard-style Hands (owner request 2026-09-28): every combo of the class
   gets a fixed seat and the tiles share the whole panel — no scroll. The line
   font is fitted to the tile in DetailsPanels.tsx (handsFonts); the gap,
   padding and head height below are the numbers it assumes. */
.hands-panel {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

.hands-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  padding-top: 6px;
}

.combo-grid {
  display: grid;
  flex: 1;
  min-height: 0;
  /* No spacing between tiles: the 1px gap shows the black grid background
     as a divider (owner 2026-09-28). */
  gap: 1px;
  overflow: hidden;
  border-radius: 6px;
  background: #000;
}

/* ── one combo tile ───────────────────────────────────────────────────── */
.combo-tile {
  position: relative;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  background: #2a2a2a;
  padding: 8px;
  color: #1e1e1e;
}

/* Blocked by the board or not in range at this node: the seat stays, only
   the dimmed label is left (GTOWizard's dark tiles). */
.combo-tile.is-dead {
  background: #1d1d1d;
}

.combo-tile.is-dead .combo-label {
  background: #5a5a5a;
  opacity: 0.7;
}

.combo-tile.is-pending {
  background: #8c8c8c;
  animation: combo-pulse 1.6s ease-in-out infinite;
}

@keyframes combo-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.6; }
}

.combo-tile-fill {
  position: absolute;
  inset: 0;
  display: flex;
}

.combo-tile-fill i {
  display: block;
  height: 100%;
}

.combo-tile-content {
  position: relative;
  z-index: 1;
  display: flex;
  height: 100%;
  flex-direction: column;
  justify-content: space-between;
}

/* Pulled out over the tile padding so the card label sits flush in the
   top-left corner (owner 2026-09-28). */
.combo-tile-head {
  display: flex;
  height: 28px;
  flex: none;
  align-items: flex-start;
  gap: 6px;
  margin: -8px -8px 0;
}

.combo-label {
  display: inline-flex;
  align-items: center;
  gap: 1px;
  border-radius: 0 0 5px 0;
  background: #f2f5f8;
  padding: 2px 6px;
  font-size: 15px;
  font-weight: 800;
  line-height: 1.25;
}

/* Blocker badges: a second light pill with two shields and their counts. */
.combo-blockers {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  border-radius: 0 0 5px 5px;
  background: #f2f5f8;
  padding: 2px 6px;
  font-size: 14px;
  font-weight: 700;
  line-height: 1.25;
  color: #1e1e1e;
  font-variant-numeric: tabular-nums;
}

.combo-shield {
  width: 14px;
  height: 16px;
}

.combo-blocker {
  display: inline-flex;
  align-items: center;
  gap: 3px;
}

.combo-blocker + .combo-blocker {
  margin-left: 4px;
}

.combo-shield.is-up { fill: #ef7a3c; }
.combo-shield.is-down { fill: #f3a13c; }

.combo-shield-mark {
  fill: none;
  stroke: #fff;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.combo-label-card.is-spade { color: #2b2f35; }
.combo-label-card.is-heart { color: #c23845; }
.combo-label-card.is-diamond { color: #2879d0; }
.combo-label-card.is-club { color: #1e8a58; }

.combo-tile.is-dead .combo-label-card { color: #d0d0d0; }

.combo-tag {
  margin-left: auto;
  padding: 4px 8px 0 0;
  font-size: 18px;
  font-weight: 600;
  line-height: 1;
  color: #1e1e1e;
}

/* font-size comes from .combo-grid (fitted at runtime); rows inherit it. */
.combo-lines {
  display: flex;
  flex-direction: column;
  font-weight: 400;
  line-height: 1.15;
  color: #1e1e1e;
  font-variant-numeric: tabular-nums;
}

.combo-line-row {
  display: flex;
  justify-content: space-between;
  gap: 8px;
}

.combo-line-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.combo-line-value {
  flex: none;
  font-weight: 400;
  text-align: right;
}

.combo-metric {
  align-self: flex-end;
  font-weight: 400;
  line-height: 1;
  color: #1e1e1e;
  font-variant-numeric: tabular-nums;
}


.action-tile:not(.is-on):not(:active):hover { filter: brightness(0.78); }
/* Selection takes over immediately, even while the pointer stays on the tile. */
.action-tile.is-on,
.action-tile:active {
  filter: brightness(1);
  transition: opacity 300ms ease;
}

/* Takes whatever the rail has left, so the box is the same size whether it
   lists one hand class or the whole node — that is what stopped the page
   jumping when the list switched (owner report 2026-09-13) — and the rail
   itself is the height of the matrix. */
.summary-scroll { flex: 1; min-height: 0; overflow: auto; }
.summary-scroll.is-centered { display: grid; place-items: center; padding: 16px; text-align: center; }
.summary-table { width: 100%; min-width: 430px; border-collapse: collapse; table-layout: fixed; color: var(--text-soft); font-size: 13px; font-variant-numeric: tabular-nums; }
.summary-table th { position: sticky; top: 0; z-index: 1; height: 32px; padding: 4px 8px; background: var(--surface-page); text-align: right; color: var(--text-normal); font-weight: 600; white-space: nowrap; }
.summary-table th:first-child { width: 76px; text-align: left; }
.summary-table th:nth-child(2) { width: 28%; text-align: left; }
.summary-table th button { border: none; padding: 0; background: transparent; color: inherit; font: inherit; cursor: pointer; }
.summary-table td { height: 30px; padding: 0 8px; border-bottom: 1px solid var(--line); text-align: right; }
.summary-table td:first-child { text-align: left; }
.summary-table .summary-strategy { padding: 0; }
.summary-row:hover { background: #191919; }
.summary-row.is-selected { background: #302a22; }
.summary-hand { display: inline-flex; gap: 3px; font-weight: 600; white-space: nowrap; }
.summary-card.is-spade { color: #c5c5c5; }
.summary-card.is-heart { color: #f05252; }
.summary-card.is-diamond { color: #568bff; }
.summary-card.is-club { color: #58b967; }
.summary-mix { display: flex; height: 30px; width: 100%; overflow: hidden; background: #191919; }
.summary-mix i { display: block; height: 100%; }

.blockers-scroll { flex: 1; min-height: 0; overflow: auto; }
.blockers-table {
  --blocker-positive: #72c55e;
  --blocker-negative: #f34e43;
  --blocker-neutral: #efb348;
  width: 100%;
  min-width: 280px;
  border-collapse: collapse;
  table-layout: fixed;
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
}
.blockers-table thead th { position: sticky; top: 0; z-index: 1; height: 42px; padding: 8px 10px 12px; background: var(--surface-page); font-size: 14px; font-weight: 600; text-align: center; }
.blockers-table th:first-child { width: 76px; text-align: left; }
.blocker-action-stripe { position: absolute; bottom: 0; left: 0; right: 0; height: 4px; }
.blockers-table tbody th { padding: 0 12px; background: var(--surface-panel); border-bottom: 1px solid var(--line); }
.blocker-card { font-size: 18px; font-weight: 700; white-space: nowrap; }
.blockers-table td { padding: 0; border: 1px solid var(--line-strong); }
.blocker-value { display: block; width: 100%; min-height: 38px; padding: 7px 4px; border: none; background: var(--blocker-color); color: #fff; font-size: 17px; font-weight: 600; font-variant-numeric: tabular-nums; text-shadow: 0 1px 2px #0009; cursor: help; transition: filter 300ms ease; }
.blocker-value:hover { filter: brightness(1.08); }
.blocker-value:focus-visible { outline: 2px solid var(--text-strong); outline-offset: -3px; }
.blocker-tooltip { max-width: 360px; padding: 12px 15px; border: 1px solid var(--line-strong); border-radius: 8px; background: #3b3b3b; color: #f5f5f5; font-size: 14px; line-height: 1.5; box-shadow: 0 4px 16px #0006; }
@media (max-width: 1800px) { .blocker-value { font-size: 15px; } }

.table-sort-controls { display: flex; align-items: center; gap: 3px; }
.table-sort-controls select, .table-strategy-sort select { min-width: 0; max-width: 100%; border: none; padding: 0; background: var(--surface-page); color: var(--text-normal); font: inherit; cursor: pointer; }
.table-sort-controls button, .blocker-sort-button { border: none; padding: 0; background: transparent; color: inherit; font: inherit; cursor: pointer; }
.table-strategy-sort { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; }
.table-strategy-sort select { font-size: 11px; color: var(--text-muted); }
.blockers-table th:first-child { width: 100px; }
.summary-table th:first-child { width: 92px; }
.blocker-sort-button:hover, .summary-table th button:hover { color: var(--accent-brand); }


/* Keep wheel/touch scrolling without visible table scrollbars. */
.summary-scroll,
.blockers-scroll {
  scrollbar-width: none;
}

.summary-scroll::-webkit-scrollbar,
.blockers-scroll::-webkit-scrollbar {
  display: none;
}

/* Blockers follow the matrix: the rows for the two cards the selected hand
   class holds are lit, so the table stops reading as static while the cursor
   moves over the grid (owner request 2026-09-13). Numbers are untouched. */
.blockers-hand-note { padding: 6px 2px 8px; font-size: 12px; color: var(--text-muted); }
.blockers-hand-note strong { color: var(--text-strong); font-weight: 700; }
/* The value buttons paint their own blocker colour over the whole cell, so the
   row is lit by brightening THEM plus an amber edge on the card header — a td
   background would sit behind the button and never show. */
.blockers-row.is-hand-card > th { background: #3a3128; box-shadow: inset 2px 0 0 var(--accent-brand); }
.blockers-row.is-hand-card .blocker-value { filter: brightness(1.3); }
.blockers-row.is-hand-card .blocker-value:hover { filter: brightness(1.42); }
/* ============================================================================
   Board card picker — the full-screen dialog that chooses a flop or a
   turn/river card.
   ============================================================================ */

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.72);
  backdrop-filter: blur(1px);
  padding: 32px;
}

/* Exactly as wide as the deck it holds: thirteen 34px cards plus twelve 5px
   gaps is 502px, and 8px of padding either side makes 518. Any wider and the
   row — which is left-aligned so it lines up with the chosen cards above —
   leaves a strip of dead space down the right (owner report 2026-08-17).
   Change a card size or a gap and this has to move with it. */
.modal-panel {
  width: min(518px, calc(100vw - 64px));
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-page);
  padding: 16px 8px;
  box-shadow: 0 28px 90px rgba(0, 0, 0, 0.62);
}

.modal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 16px;
}

.modal-head-left {
  display: flex;
  align-items: flex-end;
  gap: 12px;
}

.modal-street {
  margin-bottom: 8px;
  font-size: 12px;
  font-weight: 900;
  letter-spacing: 0.12em;
  color: var(--text-muted);
}

.modal-picked-row {
  display: flex;
  align-items: center;
  gap: 6px;
  border-bottom: 2px solid #98f3a9;
  padding-bottom: 4px;
}

.modal-side-column {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-bottom: 4px;
}

.modal-close {
  display: grid;
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  place-items: center;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--text-normal);
  cursor: pointer;
  transition: background-color 120ms ease;
}

.modal-close:hover {
  background: var(--surface-rail);
}

/* ── the 4x13 deck ────────────────────────────────────────────────────── */
/* Full width, NOT a centred block: the chosen cards above start at the panel's
   own left edge, so a centred grid would sit indented from them (owner request
   2026-08-17). The row spreads its thirteen fixed columns across whatever is
   there instead, which lines the first card up with the first chosen one. */
.deck-grid {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.deck-row {
  display: grid;
  /* Fixed, not 1fr: the deck is drawn at the SAME size as a card in the study
     node strip (owner request 2026-08-17), and a fractional column would let it
     grow with the modal instead. The spare width goes into the gaps, so the row
     reaches both edges and the first card sits under the first chosen one. */
  grid-template-columns: repeat(13, 34px);
  justify-content: start;
  gap: 5px;
}

/* Same 34x60 as .playing-card--strip. The cards ALREADY chosen, above the
   grid, stay bigger on purpose — they are the board being built, not a list to
   pick from. */
.deck-card {
  position: relative;
  width: 34px;
  height: 60px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  padding: 0;
  line-height: 1;
  cursor: pointer;
  transition: filter 120ms ease, transform 120ms ease;
}

/* A light wash laid OVER the card, not a lift: the row used to hop under the
   cursor while picking, which made a 52-card grid tiring to aim at (owner
   report 2026-08-17). The overlay is a pseudo-element of the button, so it
   paints above the card and follows its corner. */
.deck-card::after {
  content: "";
  pointer-events: none;
  position: absolute;
  inset: 0;
  border-radius: var(--card-radius, 6px);
  background: #ffffff;
  opacity: 0;
  transition: opacity 120ms ease;
}

.deck-card:hover:not(:disabled)::after {
  opacity: 0.18;
}

.deck-card:disabled {
  cursor: not-allowed;
}

/* a card that is already on the board or dead */
.deck-card-unavailable {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 6px;
  background: repeating-linear-gradient(135deg, #242424 0, #242424 8px, #303030 8px, #303030 16px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.05),
    0 2px 0 rgba(0, 0, 0, 0.32);
}

.deck-card-unavailable-glow {
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 50% 62%,
    rgba(255, 255, 255, 0.07) 0,
    rgba(255, 255, 255, 0.03) 34%,
    transparent 35%);
}

.deck-card-unavailable-inner {
  position: absolute;
  inset: 4px;
  border: 1px solid rgba(255, 255, 255, 0.04);
  border-radius: 4px;
}

/* ── picked slots above the deck ──────────────────────────────────────── */
.picked-slot {
  display: block;
  width: 48px;
  height: 66px;
  flex-shrink: 0;
  border-radius: 6px;
  border: none;
  background: transparent;
  padding: 0;
  cursor: pointer;
  transition: filter 120ms ease;
}

.picked-slot:hover {
  filter: brightness(1.1);
}

.picked-slot-empty {
  display: grid;
  width: 48px;
  height: 66px;
  place-items: center;
  border: 1px dashed var(--line);
  border-radius: 6px;
  background: #303030;
  font-size: 24px;
  font-weight: 900;
  color: var(--text-muted);
}

/* ── footer ───────────────────────────────────────────────────────────── */
.modal-footer {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  margin-top: 24px;
}

.modal-actions {
  display: flex;
  align-items: center;
  gap: 12px;
}

.modal-button {
  height: 44px;
  padding: 0 24px;
}

/* White, not the near-black --text-on-accent: on this orange the default token
   reads as a black label with a black tick (owner report 2026-08-17). Through
   the token now, because the trainer's Next hand button reached the same
   conclusion and one answer is enough. */
.modal-button.is-confirm {
  min-width: 160px;
  gap: 8px;
  color: var(--text-on-brand);
}

/* ── persisted sizing explanation ───────────────────────────────────── */
.sizing-explanation {
  width: min(700px, calc(100vw - 48px));
  max-height: calc(100vh - 48px);
  overflow: auto;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface-page);
  box-shadow: 0 28px 90px rgba(0, 0, 0, 0.62);
}

.sizing-explanation-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
  border-bottom: 1px solid var(--line);
  padding: 20px 22px 16px;
}

.sizing-explanation-head h2 {
  margin: 2px 0 0;
  font-size: 22px;
  font-weight: 800;
  color: var(--text-strong);
}

.sizing-explanation-kicker {
  margin: 0;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--brand);
}

.sizing-explanation-state {
  display: flex;
  min-height: 220px;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 30px;
  font-size: 15px;
  color: var(--text-muted);
}

.sizing-explanation-state.is-error {
  color: var(--danger);
}

.sizing-explanation-body {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 20px 22px 22px;
}

.sizing-explanation-context {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-muted);
  text-transform: capitalize;
}

.sizing-explanation-comparison {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.sizing-candidate {
  display: flex;
  min-height: 116px;
  flex-direction: column;
  justify-content: center;
  gap: 7px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-rail);
  padding: 16px;
}

.sizing-candidate.is-selected {
  border-color: var(--brand);
  background: color-mix(in srgb, var(--brand) 10%, var(--surface-rail));
}

.sizing-candidate > span,
.sizing-candidate > small {
  font-size: 12px;
  color: var(--text-muted);
}

.sizing-candidate > strong {
  font-size: 20px;
  font-weight: 800;
  color: var(--text-strong);
}

.sizing-explanation-gap {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 4px 16px;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 12px 14px;
  font-size: 13px;
  color: var(--text-normal);
}

.sizing-explanation-gap strong {
  grid-column: 2;
  grid-row: 1 / 3;
  align-self: center;
  font-size: 18px;
  color: var(--text-strong);
}

.sizing-explanation-gap small {
  color: var(--text-muted);
}

.sizing-explanation-reason {
  margin: 0;
  border-left: 3px solid var(--brand);
  padding: 4px 0 4px 13px;
  font-size: 14px;
  line-height: 1.55;
  color: var(--text-normal);
}

.sizing-explanation-table {
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 8px;
}

.sizing-explanation-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 16px;
  min-height: 42px;
  border-top: 1px solid var(--line);
  padding: 8px 12px;
  font-size: 13px;
}

.sizing-explanation-row:first-child {
  border-top: 0;
}

.sizing-explanation-row.is-head {
  min-height: 36px;
  background: var(--surface-rail);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.sizing-explanation-row em {
  margin-left: 8px;
  border-radius: 999px;
  background: var(--brand);
  padding: 2px 7px;
  font-size: 10px;
  font-style: normal;
  font-weight: 800;
  color: var(--text-on-brand);
}

.sizing-explanation-row strong {
  color: var(--text-strong);
}

.sizing-explanation-note {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 12px;
  color: var(--text-muted);
}

@media (max-width: 620px) {
  .sizing-explanation-comparison {
    grid-template-columns: 1fr;
  }

  .sizing-explanation-context {
    flex-direction: column;
    gap: 4px;
  }
}
/* ============================================================================
   Appearance panel — tuning how the playing cards are drawn. Three columns:
   what the card is made of, what each suit does, and what it all looks like.
   Reuses .modal-backdrop / .modal-close from study-modal.css.
   ============================================================================ */

.appearance-panel {
  display: flex;
  width: min(1180px, 96vw);
  max-height: 90vh;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--line-strong);
  border-radius: 12px;
  background: var(--surface-panel);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.6);
}

.appearance-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--line);
  padding: 14px 16px;
}

.appearance-title {
  font-size: 16px;
  font-weight: 900;
  color: var(--text-normal);
}

.appearance-body {
  display: grid;
  grid-template-columns: 260px 300px minmax(280px, 1fr);
  gap: 14px;
  overflow-y: auto;
  padding: 14px 16px 16px;
}

@media (max-width: 1100px) {
  .appearance-body {
    grid-template-columns: 1fr;
  }
}

.appearance-column {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 12px;
}

.appearance-section {
  display: flex;
  flex-direction: column;
  gap: 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-page);
  padding: 12px;
}

.appearance-section-title {
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* ── Colored / Blank ──────────────────────────────────────────────────── */
.appearance-toggle {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-panel);
  padding: 3px;
}

.appearance-toggle-option {
  height: 28px;
  border: none;
  border-radius: 6px;
  background: transparent;
  font-size: 12px;
  font-weight: 800;
  color: var(--text-soft);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}

.appearance-toggle-option:hover {
  background: #404040;
}

.appearance-toggle-option.is-on {
  background: var(--surface-raised);
  color: var(--accent-brand);
}

/* ── one labelled control ─────────────────────────────────────────────────
   Three columns so every row lines up down the panel: what it is, the control,
   and what it currently reads. */
.knob {
  display: grid;
  grid-template-columns: 78px 1fr 48px;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--text-soft);
}

.knob-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.knob-value {
  text-align: right;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

.knob-range {
  width: 100%;
  height: 4px;
  appearance: none;
  border-radius: 999px;
  background: var(--surface-raised);
  cursor: pointer;
}

.knob-range::-webkit-slider-thumb {
  appearance: none;
  width: 14px;
  height: 14px;
  border: none;
  border-radius: 999px;
  background: var(--accent-brand);
  cursor: pointer;
}

.knob-range::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border: none;
  border-radius: 999px;
  background: var(--accent-brand);
  cursor: pointer;
}

.appearance-select,
.appearance-input {
  width: 100%;
  height: 26px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface-panel);
  padding: 0 8px;
  font-size: 12px;
  color: var(--text-normal);
  outline: none;
}

.appearance-color {
  width: 100%;
  height: 26px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface-panel);
  padding: 2px;
  cursor: pointer;
}

.appearance-color:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

/* The "back to automatic" affordance next to a colour: it has to read as an
   action AND as the current state, so it says which one is in force. */
.knob-clear {
  border: none;
  border-radius: 4px;
  background: transparent;
  padding: 0;
  text-align: right;
  font-size: 10px;
  font-weight: 700;
  color: var(--text-muted);
  cursor: pointer;
}

.knob-clear:hover {
  color: var(--accent-brand);
}

.appearance-check {
  width: 16px;
  height: 16px;
  accent-color: var(--accent-brand);
  cursor: pointer;
}

/* ── per-suit block ───────────────────────────────────────────────────── */
.suit-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  border-top: 1px solid var(--line);
  padding-top: 8px;
}

.suit-row:first-of-type {
  border-top: none;
  padding-top: 0;
}

.suit-row-head {
  display: grid;
  grid-template-columns: 20px 1fr 48px;
  align-items: center;
  gap: 8px;
}

.suit-row-glyph {
  font-size: 16px;
  line-height: 1;
}

.suit-row-name {
  font-size: 12px;
  font-weight: 800;
  color: var(--text-normal);
}

/* ── suit set chooser ─────────────────────────────────────────────────── */
.suit-sets {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
}

.suit-set {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-panel);
  padding: 8px;
  cursor: pointer;
  transition: border-color 120ms ease, background-color 120ms ease;
}

.suit-set:hover {
  background: #404040;
}

.suit-set.is-on {
  border-color: var(--accent-brand);
  background: var(--surface-raised);
}

.suit-set-name {
  font-size: 11px;
  font-weight: 800;
  color: var(--text-soft);
}

.suit-set.is-on .suit-set-name {
  color: var(--accent-brand);
}

.suit-set-sample {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 16px;
  line-height: 1;
  color: var(--text-normal);
}

.suit-set-glyph {
  font-style: normal;
}

/* Drawn samples are the same masks the cards use, at chooser size. */
.suit-set-glyph.is-drawn {
  display: block;
  width: 16px;
  height: 16px;
  background: var(--text-normal);
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* ── preview ──────────────────────────────────────────────────────────── */
.appearance-preview {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  border-radius: 8px;
  background: #1d1d1d;
  padding: 14px 10px;
}

.appearance-preview-row {
  display: flex;
  align-items: flex-end;
  gap: 8px;
}

/* ── the CSS it comes down to ─────────────────────────────────────────── */
/* Fixed, not max-height: the block grows a line every time a knob leaves its
   default, and a growing block moved the whole panel under the cursor (owner
   report 2026-08-17). It scrolls instead. */
.appearance-css {
  height: 190px;
  overflow: auto;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface-panel);
  padding: 10px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  line-height: 1.5;
  white-space: pre;
  color: var(--text-soft);
}

.appearance-actions {
  display: flex;
  gap: 8px;
}

.appearance-button {
  display: flex;
  flex: 1;
  height: 32px;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border: none;
  border-radius: 6px;
  background: var(--accent-brand);
  font-size: 12px;
  font-weight: 900;
  color: var(--text-on-accent);
  cursor: pointer;
  transition: background-color 120ms ease;
}

.appearance-button:hover {
  background: var(--accent-brand-hover);
}

.appearance-button.is-reset {
  background: var(--surface-raised);
  color: var(--text-normal);
}

.appearance-button.is-reset:hover {
  background: #404040;
}
.auth-page {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
  background: var(--surface-page);
}
.auth-card {
  width: 420px;
  max-width: 100%;
  padding: 36px;
  border: 1px solid var(--line-strong);
  border-radius: 16px;
  background: var(--surface-panel);
  color: var(--text-strong);
  text-align: center;
}
.auth-logo { width: 180px; margin: 0 auto 28px; }
.auth-card h1 { margin: 0 0 12px; font-size: 24px; font-weight: 700; }
.auth-card p { margin: 12px 0; font-size: 14px; line-height: 1.5; }
.auth-google-button {
  width: 100%;
  height: 44px;
  margin: 20px 0 8px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--text-strong);
  color: var(--surface-page);
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
}
.auth-google-button:hover { opacity: 0.9; }
.auth-card .auth-note { font-size: 12px; color: var(--text-muted); }
.auth-error { color: var(--grade-bad); }
.sidebar-account {
  flex-shrink: 0;
  margin-top: auto;
  padding-top: 14px;
  border-top: 1px solid var(--line-strong);
}
.sidebar-account-row { display: flex; align-items: center; gap: 10px; }
.sidebar-account-avatar {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  overflow: hidden;
  border-radius: 50%;
  background: var(--surface-panel);
  color: var(--accent-brand);
  font-size: 16px;
  font-weight: 700;
}
.sidebar-account-avatar img { width: 100%; height: 100%; object-fit: cover; }
.sidebar-account-info { min-width: 0; flex: 1; }
.sidebar-account-info strong,
.sidebar-account-info span { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidebar-account-info strong { color: var(--text-strong); font-size: 13px; }
.sidebar-account-info span { margin-top: 3px; color: var(--text-muted); font-size: 11px; }
.sidebar-account-logout {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  border: none;
  border-radius: 7px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}
.sidebar-account-logout:hover { background: var(--surface-hover); color: var(--text-strong); }
.sidebar-account-logout:disabled { opacity: 0.5; cursor: wait; }
.sidebar-account .auth-error { margin: 8px 0 0; font-size: 12px; }
.solutions-backdrop { position: fixed; inset: 0; z-index: 100; display: grid; place-items: center; padding: 28px; background: #000a; }
.solutions-library { width: 1020px; max-width: 100%; max-height: calc(100vh - 56px); overflow: auto; border: 1px solid var(--line-strong); border-radius: 14px; background: #1d1d1d; color: var(--text-normal); box-shadow: 0 20px 80px #0009; }
.solutions-header { display: flex; align-items: center; justify-content: space-between; padding: 14px 20px; border-bottom: 1px solid var(--line); background: #171717; }
.solutions-header h2 { display: flex; align-items: center; gap: 12px; margin: 0; font-size: 20px; font-weight: 600; }
.solutions-close { display: grid; place-items: center; width: 30px; height: 30px; border: none; border-radius: 6px; background: transparent; color: var(--text-strong); cursor: pointer; }
.solutions-close:hover { background: var(--surface-hover); }
.solutions-body { display: flex; flex-direction: column; gap: 20px; padding: 18px 20px 20px; }
.solutions-filters { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px 20px; }
.solutions-results { display: flex; min-width: 0; flex-direction: column; gap: 8px; padding-top: 16px; border-top: 1px solid var(--line); }
.solution-filter { min-width: 0; margin: 0; padding: 0; border: none; }
.solution-filter legend { margin-bottom: 7px; padding: 0; color: var(--text-muted); font-size: 15px; }
.solution-filter-options { display: flex; flex-wrap: wrap; gap: 4px; width: fit-content; max-width: 100%; padding: 4px; border-radius: 7px; background: #292929; }
.solution-filter-button { border: none; border-radius: 5px; padding: 5px 8px; background: transparent; color: var(--text-muted); font-size: 13px; cursor: pointer; }
.solution-filter-button:hover { background: #333; color: var(--text-strong); }
.solution-filter-button.is-selected { background: #484848; color: #fff; }
.solutions-list-heading { display: flex; justify-content: space-between; gap: 12px; color: var(--text-muted); font-size: 15px; }
.solutions-list { min-width: 0; overflow-x: auto; }
.solution-row-head, .solution-row { display: grid; grid-template-columns: 64px minmax(120px, 1.4fr) 50px 36px 76px 95px minmax(130px, 1.3fr); align-items: center; gap: 10px; min-width: 650px; width: 100%; padding: 12px; text-align: left; }
.solution-row-head { color: var(--text-soft); font-size: 12px; font-weight: 600; }
.solution-row { height: 44px; padding: 6px 10px; line-height: 16px; margin-bottom: 6px; border: none; border-radius: 6px; background: #2c2c2c; color: var(--text-normal); font-size: 12px; cursor: pointer; transition: background-color 150ms ease; }
.solution-row:hover { background: #383838; }
.solution-row.is-selected { background: linear-gradient(90deg, rgba(255,158,69,.34), rgba(255,158,69,.14)); color: var(--text-strong); box-shadow: inset 2px 0 0 var(--accent-brand); }
.solution-row small { display: block; margin-top: 4px; color: var(--text-muted); font-size: 11px; }
.solution-availability { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.solution-name { min-width: 0; }
.solution-name strong { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.solution-name small { margin-top: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.solution-availability > svg { flex-shrink: 0; }
.solutions-empty { padding: 30px 12px; color: var(--text-muted); font-size: 14px; }
.solutions-empty button { display: block; margin-top: 12px; border: none; padding: 0; background: transparent; color: var(--accent-brand); font-size: 13px; cursor: pointer; }
.solutions-tabs { display: flex; gap: 4px; margin: 14px 20px 0; padding: 4px; width: fit-content; border-radius: 7px; background: #292929; }
.solutions-tab { border: none; border-radius: 5px; padding: 7px 14px; background: transparent; color: var(--text-muted); font-size: 14px; font-weight: 600; cursor: pointer; }
.solutions-tab:hover { background: #333; color: var(--text-strong); }
.solutions-tab.is-active { background: #484848; color: #fff; }
.solutions-results.is-custom { padding-top: 0; border-top: none; }
.solutions-loading { margin: 0; padding: 24px 12px; color: var(--text-muted); font-size: 14px; }
.custom-solve-row-head, .custom-solve-row { display: grid; grid-template-columns: 110px 100px 120px minmax(150px, 1.4fr) minmax(130px, 1fr) 32px; align-items: center; gap: 10px; min-width: 650px; width: 100%; text-align: left; }
.custom-solve-row-head { padding: 12px 10px; color: var(--text-soft); font-size: 12px; font-weight: 600; }
.custom-solve-row { min-height: 44px; margin-bottom: 6px; padding: 6px 10px; border-radius: 6px; background: #2c2c2c; color: var(--text-normal); font-size: 12px; line-height: 16px; cursor: pointer; transition: background-color 150ms ease; }
.custom-solve-row:hover, .custom-solve-row:focus-visible { background: #383838; outline: none; }
.custom-solve-row.is-unavailable { cursor: default; color: var(--text-muted); }
.custom-solve-board { color: var(--text-strong); font-weight: 600; letter-spacing: 1px; }
.custom-solve-engine small { display: block; margin-top: 2px; color: var(--text-muted); font-size: 11px; }
.custom-solve-settings { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border: none; border-radius: 5px; padding: 0; background: transparent; color: var(--text-muted); cursor: pointer; }
.custom-solve-settings:hover, .custom-solve-settings:focus-visible { background: #484848; color: var(--text-strong); outline: none; }
@media (max-width: 760px) { .solutions-filters { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; } .solutions-backdrop { padding: 12px; } .solutions-library { max-height: calc(100vh - 24px); } }
@media (max-width: 480px) { .solutions-filters { grid-template-columns: 1fr; } }

.solutions-error { margin: 0; color: var(--text-muted); font-size: 13px; }

.solution-limp { display: flex; align-items: center; color: var(--text-strong); }
.solutions-close svg { width: 20px; height: 20px; }
.run-page { min-height: 100vh; background: #17191d; color: #e8eaf0; padding: 24px 32px; font-size: 14px; }
.run-header { display: flex; align-items: center; justify-content: space-between; padding-bottom: 24px; border-bottom: 1px solid #34373e; }
.run-header > div { display: flex; align-items: baseline; gap: 22px; }
.run-header h1 { font-size: 22px; margin: 0; }
.run-header span, .run-title p, .run-library-note { color: #939baa; }
.run-header a { color: #c5cbd6; text-decoration: none; }
.run-brand { font-weight: 800; letter-spacing: 2px; }
.run-workspace { display: grid; grid-template-columns: 250px minmax(0, 1fr); gap: 28px; max-width: 1550px; margin: 28px auto; }
.run-library { background: #202329; border-radius: 10px; align-self: start; overflow: hidden; }
.run-section-title { display: flex; align-items: center; justify-content: space-between; font-weight: 600; padding: 16px; }
.run-section-title span { font-size: 12px; font-weight: 400; color: #939baa; }
.run-solution { display: flex; flex-direction: column; gap: 7px; width: 100%; border: 0; background: transparent; padding: 16px; text-align: left; color: #dddfe7; cursor: pointer; }
.run-solution:hover { background: #404040; }
.run-solution.is-selected { background: linear-gradient(90deg, rgba(255,158,69,.34), rgba(255,158,69,.14)); box-shadow: inset 2px 0 0 var(--accent-brand); color: var(--text-strong); }
.run-solution span { font-size: 12px; color: #c7cbd4; }
.run-solution small { color: #929baa; }
.run-library-note { margin: 20px 16px; font-size: 12px; line-height: 1.7; }
.run-title { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.run-title h2 { font-size: 22px; margin: 0 0 8px; }
.run-title p { margin: 0; font-size: 13px; }
.run-badge { padding: 7px 12px; border-radius: 18px; background: #26392f; color: #97d4ab; white-space: nowrap; font-size: 12px; }
.run-stages { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(140px,1fr); margin: 26px 0; border: 1px solid #393d45; border-radius: 10px; overflow-x: auto; }
.run-stage { display: grid; grid-template-columns: 26px 1fr; gap: 6px 9px; padding: 16px 12px; border-right: 1px solid #393d45; color: #929baa; }
.run-stage:last-child { border-right: none; }
.run-stage strong { font-size: 12px; align-self: center; }
.run-stage small { grid-column: 2; font-size: 11px; }
.run-stage-number { display: grid; place-items: center; width: 25px; height: 25px; border-radius: 50%; background: #30353f; font-size: 12px; }
.run-stage.is-done { color: #b3d8bf; background: #202b25; }
.run-stage.is-done .run-stage-number { background: #355840; }
.run-stage.is-partial { color: #e9c18c; }
.run-metrics { display: grid; grid-template-columns: repeat(4,1fr); gap: 12px; }
.run-metrics > div { padding: 16px; border-radius: 8px; background: #22252b; }
.run-metrics span { display: block; color: #929baa; margin-bottom: 10px; font-size: 12px; }
.run-metrics strong { font-size: 23px; }
.run-metrics small { font-size: 13px; font-weight: 400; color: #929baa; }
.run-controls { margin-top: 22px; background: #22252b; border-radius: 10px; padding: 4px 4px 12px; }
.run-fields { display: grid; grid-template-columns: 130px 190px 150px 1fr; gap: 14px; align-items: end; padding: 0 16px; }
.run-fields label { color: #a4acb9; font-size: 12px; display: flex; flex-direction: column; gap: 8px; }
.run-fields input, .run-fields select { width: 100%; height: 36px; border: 1px solid #454b56; background: #191c22; color: #eee; padding: 0 10px; border-radius: 6px; font-size: 13px; }
.run-continue { justify-self: end; height: 36px; padding: 0 22px; border: 0; border-radius: 6px; background: #c7853d; color: #161616; font-weight: 600; }
.run-continue:disabled { opacity: .45; cursor: not-allowed; }
.run-controls p { padding: 0 16px; color: #949dab; font-size: 12px; margin: 14px 0 0; }
.run-controls .run-hold { color: #d1b387; }
.run-files { padding: 18px 0; color: #aab2bf; }
.run-files summary { cursor: pointer; }
.run-files dl { background: #202329; padding: 16px; border-radius: 8px; font-size: 12px; }
.run-files dt { color: #919bad; margin-top: 12px; }
.run-files dd { margin: 5px 0; overflow-wrap: anywhere; font-family: monospace; color: #ccd2dc; }
.run-console { border: 1px solid #343941; border-radius: 10px; overflow: hidden; }
.run-console pre { background: #121519; margin: 0; padding: 20px; color: #a7b5c6; font-size: 12px; line-height: 1.9; min-height: 130px; white-space: pre-wrap; }
.run-error { background: #4a272a; padding: 16px; border-radius: 8px; }
.run-loading { color: #939baa; }
@media (max-width: 1050px) { .run-page { padding: 18px; } .run-workspace { grid-template-columns: 210px minmax(0,1fr); gap: 18px; } .run-fields { grid-template-columns: repeat(2, minmax(0,1fr)); } .run-metrics { grid-template-columns: 1fr 1fr; } }
@media (max-width: 640px) { .run-workspace { grid-template-columns: 1fr; } .run-header span { display: none; } .run-title { align-items: start; flex-direction: column; } }
.run-control-buttons { display: flex; justify-content: end; gap: 8px; }
.run-prepare { height: 36px; border: 1px solid #596170; border-radius: 6px; padding: 0 12px; color: #dbe2ed; background: #303741; cursor: pointer; }
.run-prepare:disabled { opacity: .5; cursor: wait; }
.run-fleet { max-width: 1550px; margin: 28px auto 0; background: #202329; border-radius: 10px; }
.run-section-title .run-fleet-heading { display: flex; align-items: center; gap: 9px; color: #e8eaf0; font-size: 14px; font-weight: 600; }
.run-fleet-heading i { width: 6px; height: 6px; background: #6ba980; border-radius: 50%; }
.run-fleet-heading i.is-stale { background: #d7a667; }
.run-fleet-summary { display: flex; align-items: baseline; gap: 25px; padding: 0 16px 16px; }
.run-fleet-summary strong { font-size: 21px; }
.run-fleet-summary small, .run-fleet-summary > span { font-size: 12px; color: #929baa; font-weight: 400; }
.run-fleet-summary > span { margin-left: auto; }
.run-fleet-empty { padding: 26px 16px; text-align: center; border-top: 1px solid #343942; color: #aeb7c5; }
.run-fleet-empty span { display: block; margin-top: 7px; font-size: 12px; color: #8590a1; }
.run-fleet-error { padding: 0 16px 12px; color: #d7a667; font-size: 12px; }
.run-fleet-group-head { display: flex; align-items: baseline; gap: 10px; padding: 4px 16px 8px; font-size: 13px; text-align: left; }
.run-fleet-group-head strong { color: #e7ebf0; font-family: monospace; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.run-fleet-group-head span { flex: none; color: #8993a1; font-size: 11px; }
.run-fleet-hosts { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 12px; padding: 0 16px 16px; }
.run-host { padding: 12px; background: #191c21; border: 1px solid #363c45; border-radius: 8px; }
.run-host header { display: flex; justify-content: space-between; gap: 10px; font-size: 12px; }
.run-host header span { color: #8fb29b; }
.run-host-note { margin: 8px 0 0; padding: 8px 10px; color: #aeb7c2; background: #20242a; border-radius: 6px; font-size: 12px; }
.run-host-address { display: flex; justify-content: space-between; gap: 10px; margin-top: 5px; color: #8993a1; font-family: monospace; font-size: 11px; }
.run-host-task { color: #b8a489; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; margin: 10px 0; }
.run-host-gpus { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; margin-top: 12px; }
.run-gpu { padding: 10px; border-radius: 6px; background: #252a31; }
.run-gpu-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; font-size: 11px; }
.run-gpu strong { font-size: 16px; color: #9bcdac; }
.run-gpu progress { display: block; appearance: none; border: 0; width: 100%; height: 5px; margin: 10px 0; background: #39434a; border-radius: 3px; }
.run-gpu progress::-webkit-progress-bar { background: #39434a; border-radius: 3px; }
.run-gpu progress::-webkit-progress-value { background: #75ac88; border-radius: 3px; }
.run-gpu progress::-moz-progress-bar { background: #75ac88; border-radius: 3px; }
.run-gpu small { font-size: 11px; color: #99a3b1; }
.run-gpu.is-stale { opacity: .5; }
.run-host-progress { margin-top: 12px; border-radius: 3px; cursor: pointer; user-select: none; }
.run-host-progress:focus-visible { outline: 1px solid #5b8fd6; outline-offset: 3px; }
.run-host-progress-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; font-size: 11px; }
.run-gpu .run-host-progress strong { font-size: 16px; color: #8ab4e8; }
.run-gpu .run-host-progress:hover strong { color: #aecbf0; }
.run-gpu .run-host-progress progress { display: block; appearance: none; border: 0; width: 100%; height: 5px; margin: 10px 0 0; background: #39434a; border-radius: 3px; transition: margin-bottom 220ms ease; }
.run-host-progress-details { display: grid; grid-template-rows: 0fr; opacity: 0; transition: grid-template-rows 220ms ease, opacity 220ms ease; }
.run-host-progress-details > div { overflow: hidden; min-height: 0; }
.run-host-progress.is-open .run-host-progress-details { grid-template-rows: 1fr; opacity: 1; }
.run-gpu .run-host-progress small { display: block; font-size: 11px; color: #99a3b1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.run-gpu .run-host-progress.is-open progress { margin-bottom: 8px; }
.run-gpu .run-host-progress progress::-webkit-progress-bar { background: #39434a; border-radius: 3px; }
.run-gpu .run-host-progress progress::-webkit-progress-value { background: #5b8fd6; border-radius: 3px; }
.run-gpu .run-host-progress progress::-moz-progress-bar { background: #5b8fd6; border-radius: 3px; }
.run-host-progress-card { margin-top: 10px; }
.run-host-progress-card .run-host-progress { margin-top: 0; }
.run-new-button { margin: 0 16px 16px; width: calc(100% - 32px); padding: 9px; border: 1px solid #525a67; border-radius: 6px; background: #2d333d; color: #d7deea; font-size: 13px; cursor: pointer; }
.run-modal-backdrop { position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; background: rgba(0,0,0,.68); padding: 20px; }
.run-modal { width: 550px; max-width: 100%; max-height: calc(100vh - 40px); overflow-y: auto; background: #24272e; border: 1px solid #454b55; border-radius: 12px; padding: 24px; box-shadow: 0 18px 70px rgba(0,0,0,.5); }
.run-modal header { display: flex; justify-content: space-between; align-items: start; }
.run-modal h2 { margin: 0; font-size: 22px; }
.run-modal header p { color: #929baa; margin: 7px 0 22px; font-size: 13px; }
.run-modal header button { background: transparent; color: #a5adbb; border: 0; font-size: 26px; cursor: pointer; }
.run-new-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.run-new-fields label { display: flex; flex-direction: column; gap: 7px; color: #b1b8c5; font-size: 12px; }
.run-new-fields .run-name-field { grid-column: 1 / -1; }
.run-new-fields input, .run-new-fields select { height: 37px; border: 1px solid #505763; border-radius: 6px; background: #191c22; color: #e5e8ef; padding: 0 10px; font-size: 13px; }
.run-new-fields input:disabled, .run-new-fields select:disabled { opacity: .5; cursor: not-allowed; }
.run-preset-detail { color: #9da6b5; font-size: 11px; }
.run-new-fields .run-learning-field { grid-column: 1 / -1; flex-direction: row; align-items: flex-start; gap: 10px; padding-top: 4px; }
.run-new-fields .run-learning-field input { width: 17px; height: 17px; margin: 2px 0 0; padding: 0; flex-shrink: 0; accent-color: var(--accent-brand); }
.run-learning-copy { display: flex; flex-direction: column; gap: 4px; }
.run-learning-copy strong { color: #e5e8ef; font-size: 13px; font-weight: 600; }
.run-learning-copy small { color: #9da6b5; font-size: 12px; line-height: 1.4; }
.run-settings-error .run-error { margin-top: 0; }
.run-recipe { display: flex; flex-wrap: wrap; gap: 7px; margin: 20px 0 14px; }
.run-recipe span { padding: 5px 9px; border-radius: 5px; background: #343941; color: #c3cbd7; font-size: 11px; }
.run-new-note { font-size: 12px; color: #9da6b5; line-height: 1.6; }
.run-modal footer { display: flex; align-items: center; justify-content: space-between; gap: 18px; border-top: 1px solid #3e444e; margin-top: 22px; padding-top: 18px; }
.run-modal footer span { font-size: 11px; color: #8f9aab; max-width: 240px; }
.run-header { display: grid; grid-template-columns: auto 1fr auto; gap: 25px; }
.run-header .run-header-costs { display: flex; justify-content: center; align-items: center; gap: 35px; }
.run-header-costs > div { display: flex; flex-direction: column; gap: 5px; }
.run-header-costs span { font-size: 11px; }
.run-header-costs strong { font-size: 19px; font-weight: 600; }
.run-header-costs.is-stale { opacity: .6; }
.run-header-costs small { font-size: 11px; color: #d5b17f; }
.run-config-cost { display: flex; align-items: center; justify-content: space-between; margin-top: 12px; padding: 14px 16px; border-radius: 8px; background: #272c32; }
.run-config-cost > div { display: flex; align-items: baseline; gap: 14px; }
.run-config-cost span { color: #a8b2bf; font-size: 12px; }
.run-config-cost strong { color: #c9ddcf; font-size: 23px; }
.run-config-cost p { margin: 0; font-size: 12px; color: #98a5b4; }
@media (max-width: 850px) { .run-header { grid-template-columns: 1fr auto; } .run-header .run-header-costs { grid-column: 1 / -1; grid-row: 2; justify-content: start; } .run-header-costs span { display: block; } .run-config-cost { align-items: start; flex-direction: column; gap: 10px; } }
.run-stage.is-running { background: #30302b; color: #e6c28d; }
.run-stage.is-running .run-stage-number { background: #695234; color: #fff0d3; }

.run-config-id { font-family: monospace; font-size: 11px; overflow-wrap: anywhere; user-select: text; }
.run-config-identity { margin: 8px 0 16px; color: #939baa; font-size: 13px; overflow-wrap: anywhere; user-select: text; }
.run-config-identity code { color: #dddfe7; }
.study-columns.is-ranges > .matrix-panel > .toolbar .toolbar-group.is-tools > button:first-child { display: none; }
.toolbar-button.is-active { background: #383838; color: var(--text-strong); border-radius: 5px; }
.ranges-view { animation: none; }
.ranges-view button:not(.matrix-cell), .ranges-view select { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--line); border-radius: 5px; background: #292929; color: var(--text-soft); padding: 5px 8px; font-size: 12px; cursor: pointer; }
.ranges-view button:not(.matrix-cell):hover { background: #3b3b3b; color: var(--text-strong); }
.ranges-view button:not(.matrix-cell):disabled { opacity: .35; cursor: default; }
.ranges-export-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 4px; font-size: 12px; color: var(--text-muted); }
.ranges-comparison { display: grid; grid-template-columns: minmax(0, 1fr) 190px minmax(0, 1fr); gap: 12px; align-items: start; }
.ranges-player.matrix-panel { padding: 0; border: 0; background: transparent; min-width: 0; }
.ranges-player.is-bb { grid-column: 1; grid-row: 1; }
.ranges-player.is-sb { grid-column: 3; grid-row: 1; }
.ranges-player-header { display: flex; align-items: center; justify-content: space-between; gap: 6px; min-height: 36px; }
.ranges-player-header strong { display: flex; align-items: center; gap: 5px; font-size: 14px; }
.ranges-actor { color: var(--accent-brand); font-size: 10px; font-weight: 400; }
.ranges-player .matrix-cell { min-height: 0; }
.ranges-player .matrix-cell-label { position: absolute; inset: 0; display: grid; place-items: center; }
.ranges-player .matrix-cell-metric { display: none; }
.ranges-stats { grid-column: 2; grid-row: 1; padding-top: 8px; }
.ranges-stats h3, .ranges-equity-chart h3 { font-size: 13px; font-weight: 500; margin: 0 0 12px; }
.ranges-stats h3 { text-align: center; color: var(--text-muted); }
.ranges-stat { margin-bottom: 18px; }
.ranges-stat > div:first-child { display: flex; align-items: center; justify-content: space-between; gap: 5px; font-size: 12px; margin-bottom: 6px; }
.ranges-stat strong { font-size: 13px; font-weight: 500; color: #8ce0ef; }
.ranges-stat strong:last-child { color: #58bb77; }
.ranges-stat-track { height: 6px; background: #4b7558; border-radius: 3px; overflow: hidden; }
.ranges-stat-track i { display: block; height: 100%; background: #71bdc9; }
.ranges-stats p { font-size: 11px; line-height: 1.5; color: var(--text-muted); }
.ranges-error { padding: 14px; color: #f2b1a9; font-size: 13px; }
.ranges-equity-chart { margin-top: 16px; padding: 14px 16px 8px; background: #222; border: 1px solid var(--line); border-radius: 8px; }
.ranges-equity-chart header { display: flex; align-items: center; justify-content: space-between; }
.ranges-chart-legend { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--text-soft); }
.ranges-chart-legend i { width: 7px; height: 7px; background: #8ce0ef; border-radius: 50%; }
.ranges-chart-legend i:last-of-type { background: #58bb77; margin-left: 10px; }
.ranges-chart-plot { position: relative; height: 260px; margin-left: 36px; }
.ranges-chart-plot svg { display: block; width: 100%; height: 100%; overflow: hidden; }
.ranges-chart-line { fill: none; stroke: #8ce0ef; stroke-width: 2; vector-effect: non-scaling-stroke; stroke-linejoin: round;  animation: ranges-line-enter 350ms ease-out both; }
.ranges-chart-line.is-sb { stroke: #58bb77; animation-delay: 40ms; }
.ranges-chart-grid { stroke: #ffffff09; stroke-width: 1; vector-effect: non-scaling-stroke; }
.ranges-chart-cursor { stroke: #ffffff50; stroke-width: 1; vector-effect: non-scaling-stroke; }
.ranges-chart-y { position: absolute; top: 0; bottom: 0; left: -36px; display: flex; flex-direction: column; justify-content: space-between; font-size: 10px; color: var(--text-muted); }
.ranges-chart-x { display: flex; justify-content: space-between; margin: 8px 0 0 36px; font-size: 10px; color: var(--text-muted); }
.ranges-equity-chart p { color: var(--text-muted); font-size: 10px; text-align: center; }
.ranges-chart-tooltip { position: absolute; top: 8px; right: 8px; display: flex; flex-direction: column; gap: 6px; padding: 9px; border: 1px solid var(--line); border-radius: 5px; background: #202020ed; color: var(--text-strong); font-size: 12px; pointer-events: none; }
.ranges-chart-empty { min-height: 180px; display: grid; place-items: center; color: var(--text-muted); font-size: 13px; }
@keyframes ranges-enter { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
@keyframes ranges-line-enter { from { clip-path: inset(0 100% 0 0); opacity: 0; } to { clip-path: inset(0 0 0 0); opacity: 1; } }
@media (max-width: 1150px) { .ranges-comparison { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } .ranges-player.is-sb { grid-column: 2; } .ranges-stats { grid-column: 1 / -1; grid-row: 2; display: flex; flex-wrap: wrap; gap: 12px; } .ranges-stats h3 { width: 100%; } .ranges-stat { flex: 1; min-width: 120px; margin-bottom: 0; } .ranges-stats p { width: 100%; margin: 0; } }
@media (max-width: 650px) { .ranges-comparison { grid-template-columns: minmax(0, 1fr); } .ranges-player.is-sb { grid-column: 1; grid-row: 2; } .ranges-stats { grid-row: 3; } .study-columns.is-ranges > .study-right { grid-template-columns: 1fr; } .ranges-export-bar { flex-wrap: wrap; } .ranges-chart-plot { height: 200px; } }

.ranges-equity-chart { min-width: 0; max-width: 100%; overflow: hidden; }
.ranges-player .matrix-cell-label.is-compact { inset: 0; }

/* Owner temporarily disabled chart entrance animation; retain keyframes. */
.ranges-equity-chart.is-animation-paused .ranges-chart-line { animation: none; }
.ranges-view button.ranges-copy { width: 110px; justify-content: center; flex-shrink: 0; }
.ranges-view button.ranges-copy.is-copied { background: #2c4233; color: #91d5a2; border-color: #44664e; }
/* ============================================================================
   Custom Solutions — the solve builder.

   It is a DIALOG, not a page: it opens over the study page and gives it back
   untouched when it closes (owner request 2026-09-15). Everything below is
   plain px and semantic class names, per docs/frontend_rules.md.

   The dialog carries its own green accent rather than the app's orange: it is
   a workbench laid over the product, and the green marks "this control is on"
   without competing with the orange that means "this is the line you are
   studying" underneath.
   ============================================================================ */

.custom-backdrop {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.74);
  backdrop-filter: blur(2px);
  padding: 24px;
}

/* One fixed size whatever tab is open (owner 2026-10-02: it jumped between
   Hand Details, Rake and Bet Sizes); a longer tab scrolls inside. */
.custom-shell {
  display: flex;
  width: min(1320px, 100%);
  height: min(920px, 100%);
  flex-direction: column;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 14px;
  background: #1b1b1b;
  color: var(--text-normal);
  font-size: 14px;
  box-shadow: 0 32px 110px rgba(0, 0, 0, 0.7);
}

/* ── head ─────────────────────────────────────────────────────────────── */
.custom-shell-head {
  display: flex;
  align-items: center;
  gap: 14px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.07);
  padding: 16px 16px 16px 24px;
}

.custom-shell-head h1 {
  margin: 0;
  font-size: 24px;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: #ffffff;
}

.custom-badge {
  border-radius: 6px;
  background: var(--accent-brand);
  padding: 5px 12px;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-on-brand);
}

.custom-shell-head-right {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

/* ── body: left rail + panel ──────────────────────────────────────────── */
.custom-shell-body {
  display: grid;
  min-height: 0;
  flex: 1;
  grid-template-columns: 268px 1fr;
}

.custom-nav {
  display: flex;
  flex-direction: column;
  gap: 8px;
  overflow-y: auto;
  border-right: 1px solid rgba(255, 255, 255, 0.07);
  padding: 16px;
}

.custom-nav-tabs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
  border-radius: 10px;
  background: #242424;
  padding: 4px;
  margin-bottom: 8px;
}

.custom-nav-tab {
  border: none;
  border-radius: 8px;
  background: transparent;
  padding: 9px 8px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}

.custom-nav-tab.is-active {
  background: #333333;
  color: var(--text-strong);
}

.custom-nav-tab.is-disabled {
  cursor: not-allowed;
  opacity: 0.4;
}

.custom-nav-item {
  display: flex;
  align-items: center;
  gap: 12px;
  border: 1px solid transparent;
  border-radius: 10px;
  background: transparent;
  padding: 12px 14px;
  text-align: left;
  color: var(--text-soft);
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
}

.custom-nav-item:hover:not(:disabled) {
  background: #242424;
}

.custom-nav-item.is-active {
  border-color: rgba(255, 255, 255, 0.08);
  background: #2b2b2b;
  color: var(--text-strong);
}

.custom-nav-item:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

.custom-nav-item span {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 2px;
}

.custom-nav-item strong {
  font-size: 15px;
  font-weight: 700;
}

.custom-nav-item small {
  overflow: hidden;
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-muted);
}

.custom-main {
  min-height: 0;
  overflow-y: auto;
  /* The scrollbar's room is kept even when it is not shown, so a tab change
     never shifts the content sideways. */
  scrollbar-gutter: stable;
  padding: 24px 28px;
}

.custom-panel {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

/* ── fields, pills, steppers ──────────────────────────────────────────── */
.custom-field {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 8px;
}

.custom-field-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
}

.custom-hint {
  display: grid;
  width: 15px;
  height: 15px;
  place-items: center;
  border: 1px solid rgba(255, 255, 255, 0.24);
  border-radius: 50%;
  background: transparent;
  padding: 0;
  font-size: 10px;
  font-style: normal;
  font-weight: 700;
  line-height: 1;
  color: var(--text-muted);
  cursor: help;
}

/* The same ? as a real button when its help is a tooltip (Rake Type): it opens
   on hover and on keyboard focus. */
button.custom-hint:hover,
button.custom-hint:focus-visible {
  border-color: var(--accent-brand);
  color: var(--accent-brand);
  outline: none;
}

.custom-top-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 20px 28px;
}

.custom-pill-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.custom-pill-row.is-right {
  justify-content: flex-end;
}

/* A long pill list (Auto iterations) wraps instead of overflowing the seat card. */
.custom-pill-row.is-wrap {
  flex-wrap: wrap;
  row-gap: 6px;
}

.custom-pill {
  border: 1px solid transparent;
  border-radius: 999px;
  background: #2c2c2c;
  padding: 8px 18px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-soft);
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
}

.custom-pill:hover:not(:disabled) {
  background: #363636;
}

.custom-pill.is-active {
  border-color: var(--accent-brand);
  background: transparent;
  color: var(--accent-brand);
}

.custom-pill.is-disabled,
.custom-pill:disabled {
  cursor: not-allowed;
  opacity: 0.38;
}

.custom-round {
  display: grid;
  width: 38px;
  height: 38px;
  place-items: center;
  border: 1px solid transparent;
  border-radius: 50%;
  background: #2c2c2c;
  font-size: 15px;
  font-weight: 700;
  color: var(--text-soft);
  cursor: pointer;
}

.custom-round.is-active {
  border-color: var(--accent-brand);
  background: transparent;
  color: var(--accent-brand);
}

.custom-round.is-disabled,
.custom-round:disabled {
  cursor: not-allowed;
  opacity: 0.38;
}

.custom-stepper {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 6px;
}

.custom-stepper-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
}

.custom-stepper-box {
  display: flex;
  align-items: center;
  height: 42px;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 10px;
  background: #2c2c2c;
}

.custom-stepper-box > button {
  width: 40px;
  height: 100%;
  flex-shrink: 0;
  border: none;
  background: transparent;
  font-size: 19px;
  line-height: 1;
  color: var(--text-soft);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}

.custom-stepper-box > button:hover:not(:disabled) {
  background: #383838;
  color: var(--text-strong);
}

.custom-stepper-box > input {
  width: 100%;
  min-width: 0;
  border: none;
  background: transparent;
  padding: 0;
  text-align: right;
  font-size: 16px;
  font-weight: 700;
  color: var(--text-strong);
  outline: none;
}

.custom-stepper-box > em {
  padding: 0 4px 0 6px;
  font-size: 13px;
  font-style: normal;
  color: var(--text-muted);
}

.custom-stepper.is-disabled {
  opacity: 0.4;
}

.custom-input {
  height: 36px;
  min-width: 0;
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 8px;
  background: #2c2c2c;
  padding: 0 10px;
  font-size: 14px;
  color: var(--text-strong);
  outline: none;
}

.custom-input:focus {
  border-color: var(--accent-brand);
}

.custom-textarea {
  width: 100%;
  min-height: 84px;
  resize: vertical;
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 8px;
  background: #2c2c2c;
  padding: 8px 10px;
  font-family: inherit;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-strong);
  outline: none;
}

.custom-chip-button,
.custom-ghost-button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 34px;
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 8px;
  background: #2c2c2c;
  padding: 0 12px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-soft);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
}

.custom-chip-button:hover:not(:disabled),
.custom-ghost-button:hover:not(:disabled) {
  background: #383838;
  color: var(--text-strong);
}

.custom-chip-button:disabled,
.custom-ghost-button:disabled {
  cursor: not-allowed;
  opacity: 0.4;
}

.custom-ghost-button {
  border-color: transparent;
  background: transparent;
}

.custom-icon-button {
  display: grid;
  width: 42px;
  height: 42px;
  flex-shrink: 0;
  place-items: center;
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 10px;
  background: #2c2c2c;
  color: var(--text-soft);
  cursor: pointer;
}

.custom-icon-button:hover {
  background: #383838;
  color: var(--text-strong);
}

.custom-toggle {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  border: none;
  background: transparent;
  padding: 0;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-soft);
  cursor: pointer;
}

.custom-toggle-track {
  display: block;
  position: relative;
  width: 42px;
  height: 24px;
  flex-shrink: 0;
  border-radius: 999px;
  background: #414141;
  transition: background-color 150ms ease;
}

.custom-toggle-track > b {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #d8d8d8;
  transition: transform 150ms ease, background-color 150ms ease;
}

.custom-toggle.is-on .custom-toggle-track {
  background: #8a4f1c;
}

.custom-toggle.is-on .custom-toggle-track > b {
  background: var(--accent-brand);
  transform: translateX(18px);
}

.custom-toggle.is-on {
  color: var(--text-strong);
}

/* A native select dressed as the stepper box; the chevron is an icon laid over
   it, so the whole box stays the click target. */
.custom-select {
  position: relative;
  display: flex;
  align-items: center;
}

.custom-select > select {
  width: 100%;
  height: 42px;
  appearance: none;
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 10px;
  background: #2c2c2c;
  padding: 0 38px 0 14px;
  font-size: 15px;
  font-weight: 600;
  color: var(--text-strong);
  color-scheme: dark;
  cursor: pointer;
  outline: none;
  transition: background-color 120ms ease, border-color 120ms ease;
}

.custom-select > select:hover {
  background: #333333;
}

.custom-select > select:focus-visible {
  border-color: var(--accent-brand);
}

.custom-select > svg {
  position: absolute;
  right: 14px;
  color: var(--text-muted);
  pointer-events: none;
}

.custom-note {
  margin: 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-muted);
}

.custom-error {
  margin: 0;
  border-left: 3px solid #ff6b6b;
  padding-left: 10px;
  font-size: 13px;
  color: #ff9d9d;
}

/* ── hand details: board and player rows ──────────────────────────────── */
/* As tall as the Pot Size field (owner 2026-10-02); the cards shrink to fit. */
.custom-board-button {
  display: flex;
  align-items: center;
  height: 42px;
  gap: 4px;
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 10px;
  background: #2c2c2c;
  padding: 0 10px;
  cursor: pointer;
  transition: border-color 120ms ease, background-color 120ms ease;
}

.custom-board-button:hover {
  border-color: var(--accent-brand);
  background: #333333;
}

.custom-board-cards .playing-card--strip {
  --card-width: 22px;
  --card-height: 32px;
}

.custom-board-empty {
  font-size: 13px;
  color: var(--text-muted);
}

.custom-default-stack {
  display: flex;
  align-items: flex-end;
  gap: 8px;
}

.custom-players {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.custom-players-head,
.custom-player-row {
  display: grid;
  grid-template-columns: 236px 1fr 1fr 1fr;
  align-items: center;
  gap: 20px;
}

.custom-players-head {
  padding: 0 16px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
}

.custom-player-row {
  border: 1px solid rgba(255, 255, 255, 0.05);
  border-radius: 12px;
  background: #212121;
  padding: 14px 16px;
}

.custom-range-card {
  display: flex;
  align-items: center;
  gap: 12px;
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 10px;
  background: #2a2a2a;
  padding: 8px 14px 8px 8px;
  cursor: pointer;
  transition: border-color 120ms ease, background-color 120ms ease;
}

.custom-range-card:hover {
  border-color: var(--accent-brand);
  background: #303030;
}

.custom-range-card-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: left;
}

.custom-range-card-combos {
  font-size: 12px;
  color: var(--text-muted);
}

.custom-range-card-combos strong {
  font-size: 13px;
  color: var(--text-normal);
}

.custom-range-card-percent {
  font-size: 22px;
  font-weight: 800;
  color: var(--text-strong);
}

.custom-range-card-percent em {
  font-size: 13px;
  font-style: normal;
  font-weight: 600;
  color: var(--text-muted);
}

.custom-cell {
  min-width: 0;
}

.custom-position,
.custom-profile {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 42px;
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 10px;
  background: #2a2a2a;
  padding: 0 14px;
  font-size: 15px;
  font-weight: 700;
  color: var(--text-strong);
}

.custom-position em {
  font-size: 13px;
  font-style: normal;
  font-weight: 600;
  color: var(--text-muted);
}

.custom-profile > i {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent-brand);
}

/* ── the 13x13 postage stamp ──────────────────────────────────────────── */
.range-thumb {
  display: grid;
  width: 74px;
  height: 74px;
  flex-shrink: 0;
  grid-template-columns: repeat(13, 1fr);
  gap: 1px;
  overflow: hidden;
  border-radius: 6px;
  background: #1d1d1d;
  padding: 2px;
}

.range-thumb-cell {
  border-radius: 1px;
  background: #313131;
}

.range-thumb-cell.is-on {
  background: var(--accent-brand);
}

.custom-range-tab-thumb,
.custom-range-card-thumb {
  width: 66px;
  height: 66px;
}

/* ── advanced blocks ──────────────────────────────────────────────────── */
.custom-advanced {
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 12px;
  background: #202020;
  padding: 4px 18px;
}

.custom-advanced > summary {
  padding: 14px 0;
  font-size: 15px;
  font-weight: 700;
  color: var(--text-strong);
  cursor: pointer;
  list-style: none;
}

.custom-advanced > summary::-webkit-details-marker {
  display: none;
}

.custom-advanced > summary::after {
  content: "▾";
  float: right;
  color: var(--text-muted);
}

.custom-advanced[open] > summary::after {
  content: "▴";
}

.custom-advanced-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 18px 24px;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
  padding: 18px 0;
}

/* ── rake ─────────────────────────────────────────────────────────────── */
.custom-rake-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 20px 28px;
}

.custom-preset-menu {
  position: relative;
}

.custom-preset-list {
  position: absolute;
  z-index: 5;
  top: 38px;
  left: 0;
  display: none;
  min-width: 190px;
  flex-direction: column;
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 10px;
  background: #2c2c2c;
  padding: 6px;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.55);
}

/* The footer's Load/Save open UPWARD and on a click, not on hover: a menu that
   drops out of the bottom bar would fall off the dialog. */
.custom-preset-list.is-up {
  top: auto;
  bottom: 40px;
}

.custom-preset-list.is-open {
  display: flex;
}

.custom-preset-row {
  display: flex;
  align-items: center;
  gap: 2px;
}

.custom-preset-row > button:first-child {
  flex: 1;
  overflow: hidden;
  border: none;
  border-radius: 7px;
  background: transparent;
  padding: 8px 10px;
  text-align: left;
  font-size: 13px;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-soft);
  cursor: pointer;
}

.custom-preset-row > button:first-child:hover {
  background: #3a3a3a;
  color: var(--text-strong);
}

.custom-preset-delete {
  display: grid;
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  place-items: center;
  border: none;
  border-radius: 7px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.custom-preset-delete:hover {
  background: #444444;
  color: #ff9d9d;
}

.custom-preset-empty {
  padding: 8px 10px;
  font-size: 12px;
  color: var(--text-muted);
}

.custom-preset-save {
  display: flex;
  gap: 6px;
  padding: 4px;
}

.custom-preset-save .custom-input {
  width: 180px;
}

/* ── "Choose rake": rooms on the left, the room's tables on the right ─── */
.custom-rake-backdrop {
  z-index: 65;
}

.custom-rake-picker {
  display: flex;
  width: min(940px, 100%);
  max-height: 100%;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 14px;
  background: #1b1b1b;
  color: var(--text-normal);
  font-size: 14px;
  box-shadow: 0 32px 110px rgba(0, 0, 0, 0.7);
}

.custom-rake-picker-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.07);
  padding: 12px 12px 12px 20px;
}

.custom-rake-picker-head h2 {
  margin: 0;
  font-size: 18px;
  font-weight: 800;
  color: #ffffff;
}

.custom-rake-picker-body {
  display: grid;
  min-height: 0;
  flex: 1;
  grid-template-columns: 190px minmax(0, 1fr);
}

.custom-rake-rooms {
  display: flex;
  flex-direction: column;
  gap: 2px;
  overflow-y: auto;
  border-right: 1px solid rgba(255, 255, 255, 0.07);
  padding: 12px;
}

/* A list with one chosen item: the full-width strip of frontend_rules 5a, not
   a frame. The negative margin matches the rail's 12px padding. */
.custom-rake-room {
  margin-inline: -12px;
  border: none;
  background: transparent;
  padding: 10px 12px;
  text-align: left;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-soft);
  cursor: pointer;
}

.custom-rake-room:hover {
  background: #404040;
}

.custom-rake-room.is-selected {
  background: linear-gradient(90deg, rgba(255, 158, 69, 0.34), rgba(255, 158, 69, 0.14));
  color: var(--text-strong);
  box-shadow: inset 2px 0 0 var(--accent-brand);
}

.custom-rake-tables {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 12px;
  overflow-y: auto;
  padding: 16px 20px 20px;
}

.custom-rake-filter {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

.custom-rake-filter-title {
  width: 44px;
  flex-shrink: 0;
  padding-top: 6px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
}

.custom-rake-filter-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.custom-rake-filter-pills .custom-pill {
  padding: 5px 12px;
  font-size: 13px;
}

/* flex-shrink 0: with overflow hidden it would otherwise shrink and clip rows
   instead of letting the column scroll. */
.custom-rake-table {
  display: flex;
  flex-shrink: 0;
  flex-direction: column;
  overflow: hidden;
  margin-top: 4px;
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 10px;
}

.custom-rake-table-head,
.custom-rake-row {
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(0, 0.9fr) minmax(0, 0.8fr) minmax(0, 0.9fr) minmax(0, 1.4fr);
  align-items: center;
  gap: 12px;
  padding: 0 14px;
}

.custom-rake-table-head {
  height: 34px;
  background: #242424;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
}

.custom-rake-row {
  height: 38px;
  border: none;
  border-top: 1px solid rgba(255, 255, 255, 0.05);
  background: transparent;
  text-align: left;
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  color: var(--text-soft);
  cursor: pointer;
}

.custom-rake-row > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.custom-rake-row:hover {
  background: #404040;
  color: var(--text-strong);
}

.custom-rake-row.is-selected {
  background: linear-gradient(90deg, rgba(255, 158, 69, 0.34), rgba(255, 158, 69, 0.14));
  color: var(--text-strong);
  box-shadow: inset 2px 0 0 var(--accent-brand);
}

.custom-rake-empty {
  margin: 0;
  border-top: 1px solid rgba(255, 255, 255, 0.05);
  padding: 14px;
  font-size: 13px;
  color: var(--text-muted);
}

.custom-rake-picker-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  border-top: 1px solid rgba(255, 255, 255, 0.07);
  padding: 12px 20px;
}

/* Rake Type help. The tooltip is portalled to <body>, so it has to clear the
   builder's own backdrop (z-index 60) to be seen at all. */
.custom-rake-tooltip {
  z-index: 80;
  display: flex;
  max-width: 380px;
  flex-direction: column;
  gap: 6px;
  padding: 10px 12px;
  font-size: 13px;
  line-height: 1.45;
}

.custom-rake-tooltip p {
  margin: 0;
}

.custom-rake-tooltip strong {
  color: var(--text-strong);
}

/* ── bet sizes ────────────────────────────────────────────────────────── */
.custom-street-bar {
  display: flex;
  align-items: center;
  gap: 20px;
}

.custom-seat-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
}

.custom-seat-card {
  display: flex;
  flex-direction: column;
  gap: 16px;
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 12px;
  background: #212121;
  padding: 18px 20px;
}

.custom-seat-card.is-mirrored {
  background: #1e1e1e;
}

.custom-seat-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.custom-seat-head strong {
  font-size: 18px;
  font-weight: 800;
  color: var(--text-strong);
}

.custom-size-row {
  display: flex;
  flex-direction: column;
  gap: 8px;
  border-top: 1px solid rgba(255, 255, 255, 0.05);
  padding-top: 14px;
}

.custom-size-row.is-disabled {
  opacity: 0.45;
}

.custom-size-row-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-soft);
}

.custom-size-row-values {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.custom-size-row-note {
  font-size: 11px;
  color: var(--text-muted);
}

.custom-size-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 36px;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 8px;
  background: #2c2c2c;
  padding: 0 8px 0 12px;
}

.custom-size-chip b {
  font-size: 15px;
  font-weight: 700;
  color: var(--text-strong);
}

.custom-size-chip em {
  font-size: 12px;
  font-style: normal;
  color: var(--text-muted);
}

.custom-size-chip > button {
  display: grid;
  width: 20px;
  height: 20px;
  place-items: center;
  border: none;
  border-radius: 5px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.custom-size-chip > button:hover {
  background: #444444;
  color: #ff9d9d;
}

.custom-size-add-inline {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.custom-size-add-inline .custom-input {
  width: 74px;
  text-align: right;
}

/* ── tree preview ─────────────────────────────────────────────────────── */
.custom-tree-panel {
  border-bottom: 1px solid rgba(255, 255, 255, 0.07);
  background: #1e1e1e;
}

.custom-tree-toggle {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  border: none;
  background: transparent;
  padding: 10px 24px;
  font-size: 16px;
  font-weight: 700;
  color: var(--text-strong);
  cursor: pointer;
}

.custom-tree-chevron {
  color: var(--text-muted);
  transition: transform 160ms ease;
}

.custom-tree-chevron.is-open {
  transform: rotate(180deg);
}

.custom-tree-strip {
  padding: 0 24px 8px;
}

/* The strip lives on the panel, so its edge fades have to be that colour and
   not the page's (see .strip-arrow in study-strip.css). */
.custom-tree-strip .node-strip-viewport {
  --strip-fade: #1e1e1e;
  --strip-fade-soft: rgba(30, 30, 30, 0.7);
}

.custom-board-card {
  cursor: default;
}

.custom-preview-card .node-actions {
  padding-bottom: 18px;
}

/* ── the pencil's popover ─────────────────────────────────────────────── */
.custom-size-popover {
  position: fixed;
  z-index: 70;
  width: 268px;
  transform: translate(-50%, -100%) translateY(-10px);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 12px;
  background: #2a2a2a;
  padding: 14px;
  box-shadow: 0 22px 60px rgba(0, 0, 0, 0.62);
}

.custom-size-popover-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 12px;
}

.custom-size-popover-head strong {
  display: block;
  font-size: 15px;
  font-weight: 700;
  color: var(--text-strong);
}

.custom-size-popover-head small {
  font-size: 12px;
  color: var(--text-muted);
}

.custom-size-popover-close {
  display: grid;
  width: 24px;
  height: 24px;
  place-items: center;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.custom-size-popover-close:hover {
  background: #3a3a3a;
  color: var(--text-strong);
}

.custom-size-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 10px;
}

.custom-size-empty {
  font-size: 12px;
  color: var(--text-muted);
}

.custom-size-add {
  display: flex;
  gap: 6px;
}

.custom-size-add .custom-input {
  width: 100%;
}

.custom-size-presets {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 10px;
}

.custom-preset {
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 7px;
  background: #313131;
  padding: 5px 9px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-soft);
  cursor: pointer;
}

.custom-preset:hover {
  background: #3d3d3d;
}

.custom-preset.is-on {
  border-color: var(--accent-brand);
  background: rgba(255, 158, 69, 0.14);
  color: var(--accent-brand);
}

.custom-size-popover-foot {
  margin-top: 12px;
  border-top: 1px solid rgba(255, 255, 255, 0.07);
  padding-top: 9px;
  font-size: 11px;
  color: var(--text-muted);
}

/* ── foot ─────────────────────────────────────────────────────────────── */
.custom-shell-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  border-top: 1px solid rgba(255, 255, 255, 0.07);
  background: #191919;
  padding: 14px 24px;
}

.custom-foot-left,
.custom-foot-right {
  display: flex;
  align-items: center;
  gap: 10px;
}

.custom-estimate {
  font-size: 12px;
  color: var(--text-muted);
}

.custom-estimate.is-problem {
  color: #ffbe7a;
}

/* Orange like the Apply of Edit bet sizes (owner 2026-10-02): white label,
   lifts and glows on hover. */
.custom-primary-button {
  background: var(--accent-brand);
  color: var(--text-on-brand);
  transition: transform 180ms ease-out, filter 180ms ease-out;
}

.custom-primary-button:hover:not(:disabled) {
  background: var(--accent-brand);
  color: var(--text-on-brand);
  transform: translateY(-1px);
  filter: brightness(1.07) drop-shadow(0 4px 10px rgba(255, 158, 69, 0.45));
}

/* ── range picker ─────────────────────────────────────────────────────── */
.custom-range-backdrop {
  z-index: 65;
}

/* As wide as the grid and the tool column need (owner 2026-10-02: no empty
   space, the grid fits without scrolling). */
.custom-range-panel {
  display: flex;
  width: fit-content;
  max-width: 100%;
  max-height: 100%;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 14px;
  background: #1b1b1b;
  box-shadow: 0 32px 110px rgba(0, 0, 0, 0.7);
}

.custom-range-head-slot {
  display: flex;
  align-items: center;
  gap: 12px;
}

.custom-range-tab {
  display: flex;
  align-items: center;
  gap: 12px;
  border: 1px solid transparent;
  border-bottom: 2px solid transparent;
  border-radius: 10px;
  background: #242424;
  padding: 8px 16px 8px 8px;
  cursor: pointer;
  transition: border-color 120ms ease, background-color 120ms ease;
}

.custom-range-tab.is-active {
  border-bottom-color: var(--accent-brand);
  background: #2c2c2c;
}

.custom-range-tab-body {
  display: flex;
  flex-direction: column;
  gap: 1px;
  text-align: left;
}

.custom-range-tab-title {
  font-size: 15px;
  font-weight: 700;
  color: var(--text-strong);
}

.custom-range-tab-title em {
  font-size: 13px;
  font-style: normal;
  color: var(--text-muted);
}

.custom-range-tab-meta {
  font-size: 12px;
  color: var(--text-muted);
}

.custom-range-tab-percent {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-normal);
}

.custom-range-swap {
  display: grid;
  width: 38px;
  height: 38px;
  place-items: center;
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 9px;
  background: #2c2c2c;
  color: var(--text-soft);
  cursor: pointer;
}

.custom-range-swap:hover {
  background: #383838;
  color: var(--text-strong);
}

.custom-range-body {
  display: grid;
  min-height: 0;
  flex: 1;
  grid-template-columns: 240px auto;
  gap: 20px;
  overflow: hidden;
  padding: 16px 20px 20px;
}

.custom-range-left {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 12px;
}

.custom-range-note:empty {
  display: none;
}

.custom-range-note {
  margin: 0;
  font-size: 12px;
  color: var(--accent-brand);
}

.custom-range-transfer {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.custom-range-transfer-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

/* Range editor grid: the study matrix's own .matrix / .matrix-cell /
   .cell-fill / .matrix-cell-label (study.css), like node locking
   (custom-lock.css), so all three grids look the same (owner 2026-10-02).
   Only the brush cursor and the one-colour weight band belong here. */
/* Fitted to the window HEIGHT like node locking: 305px is the backdrop
   padding, the brush row and the Close / Confirm row under the grid, and
   the body padding; 1.15 keeps the
   popup narrower than the 800/620 grid would (owner 2026-10-02), the cells
   just get a little taller. It also leaves the tool column its width. */
.custom-range-matrix {
  width: clamp(420px, min(calc((100vh - 305px) * 1.15), calc(100vw - 390px)), 780px);
  aspect-ratio: 1.15;
  /* The labels scale with the grid (cqw). On the grid itself, which has its
     own width: on the auto-width column it collapsed that column to nothing. */
  container-type: inline-size;
  touch-action: none;
  user-select: none;
}

.custom-range-matrix .matrix-cell-label {
  font-size: clamp(11px, 2cqw, 15px);
}

.matrix-cell.custom-range-cell {
  cursor:
    url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M3%2021c0-2.2%201.1-3.4%202.2-3.9l2.7%202.7C7.4%2020.9%206.2%2022%204%2022H3z'%20fill='%23ffffff'%20stroke='%23111111'%20stroke-width='1.1'%20stroke-linejoin='round'/%3E%3Cpath%20d='M9.1%2018.2%205.8%2014.9%2016.4%204.3l3.3%203.3z'%20fill='%23f0921f'%20stroke='%23111111'%20stroke-width='1.1'%20stroke-linejoin='round'/%3E%3C/svg%3E")
      3 21,
    crosshair;
}

/* No hover tint: the brush preview itself is the hover cue. */
.matrix-cell.custom-range-cell:hover {
  z-index: 10;
  filter: none;
}

/* Hover preview of the brush: the band as a click would leave it, a little
   see-through so it reads as "not painted yet". */
.matrix-cell.custom-range-cell.is-preview .custom-range-cell-fill {
  opacity: 0.6;
}

.custom-range-cell-fill {
  width: 100%;
  background: var(--accent-brand);
}

/* Tools on the left, the grid on the right (owner 2026-10-02). */
.custom-range-seats {
  display: flex;
  flex-direction: column;
}

.custom-range-seats .custom-range-head-slot {
  flex-direction: column;
  align-items: stretch;
  gap: 0;
}

.custom-range-seats .custom-range-tab {
  width: 100%;
  border: 1px solid transparent;
  border-radius: 12px;
}

.custom-range-seats .custom-range-tab.is-active {
  border-color: var(--accent-brand);
}

/* Between the two seats: Export, Swap (arrows up/down), Import. */
.custom-range-seat-tools {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 8px;
  margin: 10px 0;
}

.custom-range-seat-tools .custom-chip-button {
  justify-content: center;
  padding: 0 8px;
}

.custom-range-seats .custom-range-swap {
  width: 32px;
  height: 28px;
}

.custom-range-seats .custom-range-swap svg {
  transform: rotate(90deg);
}

/* height 0 + min-height 100%: the column takes the grid's height and never
   sets it, so a long Saved ranges list scrolls instead of growing the popup
   (owner 2026-10-02). */
.custom-range-side {
  order: -1;
  display: flex;
  min-width: 0;
  height: 0;
  min-height: 100%;
  flex-direction: column;
  gap: 14px;
}

/* Under the grid: [brush weight] [top hands] [select all / clear], all
   aligned to the bottom so both sliders sit on one line. */
.custom-range-tools {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 110px;
  align-items: end;
  gap: 16px;
  border-radius: 12px;
  background: #222222;
  padding: 12px;
}

/* Compact controls inside the brush card (owner 2026-10-02: they took too
   much room): 32px fields and 28px buttons instead of 42 / 34. */
.custom-range-tools .custom-stepper {
  gap: 4px;
}

.custom-range-tools .custom-stepper-label,
.custom-range-tools .custom-readout {
  font-size: 11px;
}

.custom-range-tools .custom-stepper-box {
  height: 32px;
  border-radius: 8px;
}

.custom-range-tools .custom-stepper-box > button {
  width: 30px;
  font-size: 16px;
}

.custom-range-tools .custom-stepper-box > input {
  font-size: 14px;
}

.custom-range-tools .custom-readout {
  gap: 4px;
}

.custom-range-tools .custom-readout strong {
  height: 30px;
  border-radius: 8px;
  padding: 0 10px;
  font-size: 13px;
}

.custom-range-tools .custom-chip-button,
.custom-range-seat-tools .custom-chip-button {
  height: 28px;
  border-radius: 7px;
  font-size: 12px;
}

.custom-range-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

.custom-range-brush,
.custom-range-top {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 10px;
}

.custom-range-top-head {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-muted);
}

/* From / To fields of the strength band. */
.custom-range-band-fields {
  display: flex;
  align-items: center;
  gap: 4px;
}

.custom-range-band-fields em {
  font-style: normal;
  color: var(--text-muted);
}

.custom-input.custom-range-band-input {
  width: 46px;
  height: 24px;
  border-radius: 6px;
  padding: 0 6px;
  text-align: right;
  font-size: 12px;
  font-weight: 700;
  -moz-appearance: textfield;
}

.custom-range-band-input::-webkit-inner-spin-button,
.custom-range-band-input::-webkit-outer-spin-button {
  margin: 0;
  -webkit-appearance: none;
}

/* Two knobs on one track: both inputs lie on the drawn track, transparent and
   click-through; only their knobs take the pointer. */
.custom-range-dual {
  position: relative;
  height: 14px;
}

.custom-range-dual-track {
  position: absolute;
  top: 5px;
  right: 0;
  left: 0;
  height: 4px;
  border-radius: 999px;
}

.custom-range-tools .custom-range-dual-input {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 14px;
  margin: 0;
  background: transparent;
  pointer-events: none;
}

.custom-range-tools .custom-range-dual-input.is-raised {
  z-index: 1;
}

.custom-range-dual-input::-webkit-slider-thumb {
  pointer-events: auto;
}

.custom-range-dual-input::-moz-range-thumb {
  pointer-events: auto;
}

.custom-range-dual-input::-moz-range-track {
  background: transparent;
}

.custom-range-top-head strong {
  color: var(--text-strong);
}

.custom-range-bulk {
  display: grid;
  gap: 6px;
}

.custom-range-bulk .custom-chip-button {
  justify-content: center;
}

/* Saved ranges take what is left of the column and scroll on their own. */
.custom-range-saved {
  display: flex;
  min-height: 0;
  flex: 1;
  flex-direction: column;
  gap: 10px;
}

.custom-range-saved .custom-saved-list {
  min-height: 0;
  overflow-y: auto;
}

.custom-range-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

.custom-range-actions button {
  min-width: 120px;
}

.custom-range-side-head h3 {
  margin: 0 0 8px;
  font-size: 17px;
  font-weight: 700;
  color: var(--text-strong);
}

.custom-range-side-save {
  display: flex;
  gap: 6px;
}

.custom-range-side-save .custom-input {
  width: 100%;
}

.custom-saved-empty {
  margin: 0;
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 10px;
  background: #212121;
  padding: 16px;
  font-size: 13px;
  color: var(--text-muted);
}

.custom-saved-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 0;
  list-style: none;
  padding: 0;
}

.custom-saved-list > li {
  display: flex;
  align-items: center;
  gap: 4px;
}

.custom-saved-row {
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  border: none;
  border-radius: 8px;
  background: #242424;
  padding: 9px 12px;
  text-align: left;
  font-size: 13px;
  color: var(--text-soft);
  cursor: pointer;
}

.custom-saved-row:hover {
  background: #303030;
  color: var(--text-strong);
}

.custom-saved-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.custom-saved-delete {
  display: grid;
  width: 30px;
  height: 30px;
  place-items: center;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.custom-saved-delete:hover {
  background: #3a3a3a;
  color: #ff9d9d;
}

.custom-slider {
  width: 100%;
  height: 6px;
  appearance: none;
  border-radius: 999px;
  background: linear-gradient(90deg, #4a4a4a, var(--accent-brand));
  outline: none;
}

.custom-slider::-webkit-slider-thumb {
  width: 18px;
  height: 18px;
  appearance: none;
  border: none;
  border-radius: 50%;
  background: var(--accent-brand);
  cursor: pointer;
}

.custom-slider::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border: none;
  border-radius: 50%;
  background: var(--accent-brand);
  cursor: pointer;
}

.custom-readout {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 12px;
  color: var(--text-muted);
}

.custom-readout strong {
  display: flex;
  align-items: center;
  height: 38px;
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 10px;
  background: #2c2c2c;
  padding: 0 14px;
  font-size: 16px;
  font-weight: 700;
  color: var(--text-strong);
}

/* ── solution ─────────────────────────────────────────────────────────── */
.custom-solution-head {
  display: flex;
  align-items: center;
  gap: 14px;
}

.custom-status {
  display: flex;
  align-items: center;
  gap: 10px;
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 999px;
  background: #242424;
  padding: 7px 16px;
  font-size: 13px;
  color: var(--text-muted);
}

.custom-status strong {
  font-size: 14px;
  color: var(--text-strong);
}

.custom-status.is-done {
  border-color: rgba(255, 158, 69, 0.5);
}

.custom-status.is-error {
  border-color: rgba(255, 107, 107, 0.5);
}

.custom-spin {
  animation: custom-spin 900ms linear infinite;
}

@keyframes custom-spin {
  to { transform: rotate(360deg); }
}

.custom-solution-id {
  margin-left: auto;
  font-size: 12px;
  color: var(--text-muted);
}

.custom-solution-progress {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.custom-progress-bar {
  height: 8px;
  overflow: hidden;
  border-radius: 999px;
  background: #2c2c2c;
}

.custom-progress-bar > i {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, #b8661f, var(--accent-brand));
  transition: width 400ms ease;
}

.custom-solution-board {
  display: flex;
  align-items: center;
  gap: 6px;
}

.custom-solution-money {
  margin-left: 12px;
  font-size: 13px;
  color: var(--text-muted);
}

.custom-solution-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.custom-action-button {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 38px;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 9px;
  background: #2a2a2a;
  padding: 0 14px;
  font-size: 14px;
  color: var(--text-normal);
  cursor: pointer;
}

.custom-action-button:hover {
  background: #343434;
}

.custom-action-button b {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-muted);
}

.custom-card-button {
  border: none;
  background: transparent;
  padding: 0;
  cursor: pointer;
}

.custom-table-scroll {
  overflow: auto;
  max-height: 420px;
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 10px;
}

.custom-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.custom-table th,
.custom-table td {
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
  padding: 7px 12px;
  text-align: right;
  white-space: nowrap;
}

.custom-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: #262626;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.custom-table tbody th {
  text-align: left;
  font-weight: 700;
  color: var(--text-strong);
}

.custom-table tbody tr:hover,
.custom-table tbody tr.is-selected {
  background: #2b2b2b;
}

.custom-table-mix {
  position: relative;
}

.custom-mix-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  opacity: 0.22;
}

.custom-table-mix > span {
  position: relative;
  z-index: 1;
}

.custom-table-mix em {
  margin-left: 8px;
  font-size: 11px;
  font-style: normal;
  color: var(--text-muted);
}

/* ── narrow screens ───────────────────────────────────────────────────── */
@media (max-width: 1080px) {
  .custom-backdrop { padding: 10px; }
  .custom-shell-body { grid-template-columns: 1fr; }
  .custom-nav { flex-direction: row; overflow-x: auto; border-right: none; border-bottom: 1px solid rgba(255, 255, 255, 0.07); }
  .custom-nav-tabs { display: none; }
  .custom-nav-item { flex-shrink: 0; }
  .custom-players-head { display: none; }
  .custom-player-row { grid-template-columns: 1fr; }
  .custom-seat-grid { grid-template-columns: 1fr; }
  .custom-range-panel { width: 100%; }
  .custom-range-body { grid-template-columns: 1fr; overflow-y: auto; }
  .custom-range-matrix { width: 100%; }
  .custom-main { padding: 16px; }
  .custom-rake-picker-body { grid-template-columns: 1fr; }
  .custom-rake-rooms { flex-direction: row; border-right: none; border-bottom: 1px solid rgba(255, 255, 255, 0.07); }
  .custom-rake-room { margin-inline: 0; }
}

@media (max-width: 720px) {
  .custom-shell-foot { flex-direction: column; align-items: stretch; }
  .custom-foot-right { justify-content: flex-end; }
}


/* Admin-only engine controls (owner 2026-09-28): GPU state, iterations, measured solve time. */
.custom-admin-engine {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12px;
  color: var(--text-muted);
}

.custom-admin-engine.is-gpu {
  color: var(--accent-brand);
}

.custom-admin-engine.is-cpu {
  color: var(--grade-warn);
}

.custom-admin-iterations {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--text-muted);
}

.custom-iteration-button {
  height: 24px;
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 6px;
  background: #2c2c2c;
  padding: 0 8px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-soft);
  cursor: pointer;
}

.custom-iteration-button:hover {
  background: #383838;
  color: var(--text-strong);
}

.custom-iteration-button.is-active {
  border-color: var(--accent-brand);
  background: #8a4f1c;
  color: var(--text-strong);
}

/* Tree preview size popover: the BB/SB "same sizes" switch above the scope note (owner 2026-09-28). */
.custom-size-popover-foot {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
}

.custom-size-popover-foot .custom-toggle {
  font-size: 12px;
}

/* ============================================================================
   Custom Solutions — "Set Frequencies": the node-locking dialog opened from the
   lock icon of a node card in the study carousel.

   Left: the brush — one probability row over this node's actions.
   Right: the 13x13 grid of the acting player's range, where a square is
   painted with that row and pinned. Apply sends the pinned squares to the
   solver; nothing here edits a strategy on its own.
   ============================================================================ */

.custom-lock-backdrop {
  z-index: 70;
}

.custom-lock-panel {
  display: flex;
  max-width: 100%;
  max-height: 100%;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 14px;
  background: #1b1b1b;
  box-shadow: 0 32px 110px rgba(0, 0, 0, 0.7);
}

.custom-lock-panel.is-busy {
  pointer-events: none;
}

/* ── brush column ─────────────────────────────────────────────────────── */
.custom-lock-side {
  display: flex;
  width: 300px;
  flex-shrink: 0;
  flex-direction: column;
  gap: 12px;
  overflow-y: auto;
  border-right: 1px solid rgba(255, 255, 255, 0.07);
  padding: 16px;
}

.custom-lock-side-head {
  display: flex;
  align-items: center;
  gap: 8px;
}

.custom-lock-side-head h3 {
  font-size: 16px;
  font-weight: 700;
  color: var(--text-strong);
}

.custom-lock-hint {
  display: grid;
  place-items: center;
  color: var(--text-muted);
  cursor: help;
}

.custom-lock-tabs {
  display: flex;
  gap: 4px;
  border-radius: 8px;
  background: #242424;
  padding: 3px;
}

.custom-lock-tab {
  flex: 1;
  border: none;
  border-radius: 6px;
  background: transparent;
  padding: 6px 10px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
}

.custom-lock-tab.is-active {
  background: #333333;
  color: var(--text-strong);
}

.custom-lock-tab:disabled {
  opacity: 0.4;
  cursor: default;
}

.custom-lock-note {
  font-size: 12px;
  color: var(--text-muted);
}

.custom-lock-paint {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.custom-lock-paint-title {
  font-size: 12px;
  color: var(--text-soft);
}

/* Shared readout of a mix: horizontal segments, red-first, like the study
   matrix band. */
.custom-lock-bar {
  display: flex;
  height: 10px;
  overflow: hidden;
  border-radius: 999px;
  background: #2b2b2b;
}

.custom-lock-bar > i {
  height: 100%;
}

.custom-lock-row {
  display: grid;
  align-items: center;
  gap: 4px 10px;
  grid-template-columns: 1fr 70px;
}

.custom-lock-row-name {
  display: flex;
  flex-direction: column;
  grid-column: 1 / -1;
}

.custom-lock-row-name strong {
  font-size: 13px;
  color: var(--text-normal);
}

.custom-lock-row-name small {
  font-size: 11px;
  color: var(--text-muted);
}

.custom-lock-percent {
  width: 70px;
  height: 30px;
  padding: 0 8px;
  text-align: center;
}

.custom-lock-side-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.custom-lock-scope {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 12px;
  color: var(--text-muted);
}

.custom-lock-clear {
  align-self: flex-start;
}

/* ── matrix column ────────────────────────────────────────────────────── */
.custom-lock-main {
  display: flex;
  min-width: 0;
  /* As wide as the matrix (+ padding), but never narrower than the header
     row (board, pot, seat, view tabs, close) needs. The container makes the
     hand labels scale with the grid, as in the study matrix panel. */
  width: max(calc(clamp(420px, calc((100vh - 300px) * 1.29), 760px) + 32px), 560px);
  container-type: inline-size;
  flex-direction: column;
  gap: 12px;
  overflow-y: auto;
  padding: 16px;
}

.custom-lock-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
}

.custom-lock-head .custom-lock-tab {
  white-space: nowrap;
}

.custom-lock-board {
  display: flex;
  align-items: center;
  gap: 4px;
}

.custom-lock-pot,
.custom-lock-seat {
  margin-left: 6px;
  border-radius: 6px;
  background: #2b2b2b;
  padding: 4px 8px;
  font-size: 12px;
  color: var(--text-soft);
}

.custom-lock-head .custom-lock-tabs {
  margin-left: auto;
}

.custom-lock-current {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 12px;
  color: var(--text-muted);
}

.custom-lock-blocked {
  border-radius: 8px;
  background: rgba(255, 158, 69, 0.12);
  padding: 8px 10px;
  font-size: 12px;
  color: var(--text-soft);
}

.custom-lock-matrix {
  /* The cells are the study matrix's own (.matrix / .matrix-cell /
     .cell-fill / .matrix-cell-label in study.css), so both grids look the
     same (owner 2026-10-01). Only the size is this dialog's: the 800x620
     grid is fitted to the window HEIGHT so the dialog, header to Apply, fits
     a laptop screen; 300px is what the backdrop padding, header, strategy
     bar, footer and gaps take, 1.29 = 800 / 620. */
  width: clamp(420px, calc((100vh - 300px) * 1.29), 760px);
  align-self: center;
  touch-action: none;
  user-select: none;
}

/* Hand labels scale with the grid exactly as in the study matrix panel. */
.custom-lock-matrix .matrix-cell-label {
  font-size: clamp(12px, 2cqw, 22px);
}

/* A brush, so it is obvious the square is about to be painted rather than
   selected. The hotspot sits on the bristle tip. */
.matrix-cell.custom-lock-cell.is-paintable {
  cursor:
    url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M3%2021c0-2.2%201.1-3.4%202.2-3.9l2.7%202.7C7.4%2020.9%206.2%2022%204%2022H3z'%20fill='%23ffffff'%20stroke='%23111111'%20stroke-width='1.1'%20stroke-linejoin='round'/%3E%3Cpath%20d='M9.1%2018.2%205.8%2014.9%2016.4%204.3l3.3%203.3z'%20fill='%23f0921f'%20stroke='%23111111'%20stroke-width='1.1'%20stroke-linejoin='round'/%3E%3C/svg%3E")
      3 21,
    crosshair;
}

/* A pinned class: the study matrix's selection frame colour, drawn inside the
   square so neighbours do not shift. */
.matrix-cell.custom-lock-cell.is-locked {
  box-shadow: inset 0 0 0 2px var(--accent-brand);
}

.custom-lock-cell-pin {
  position: absolute;
  z-index: 20;
  top: 2px;
  right: 2px;
  display: grid;
  width: 17px;
  height: 17px;
  place-items: center;
  border: none;
  border-radius: 3px;
  background: rgba(15, 15, 15, 0.55);
  padding: 0;
  color: rgba(255, 255, 255, 0.45);
  cursor: pointer;
  opacity: 0;
  transition: opacity 120ms ease, color 120ms ease;
}

.custom-lock-cell:hover .custom-lock-cell-pin,
.custom-lock-cell-pin.is-on {
  opacity: 1;
}

.custom-lock-cell-pin.is-on {
  background: var(--accent-brand);
  color: #1d1205;
}

.custom-lock-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  border-top: 1px solid rgba(255, 255, 255, 0.07);
  padding-top: 12px;
}

.custom-lock-count {
  margin-right: auto;
  font-size: 12px;
  color: var(--text-muted);
}

@media (max-width: 900px) {
  .custom-lock-panel {
    flex-direction: column;
    overflow-y: auto;
  }

  .custom-lock-side {
    width: 100%;
    border-right: none;
    border-bottom: 1px solid rgba(255, 255, 255, 0.07);
  }

  /* One column: the grid takes the dialog's width instead of the height fit. */
  .custom-lock-main,
  .custom-lock-matrix {
    width: 100%;
  }
}

/* Each brush slider wears its own action colour, so the row reads like the
   mix bar above it instead of three identical greys. */
.custom-lock-row .custom-slider.is-fold { background: linear-gradient(90deg, #4a4a4a, var(--action-fold)); }
.custom-lock-row .custom-slider.is-call { background: linear-gradient(90deg, #4a4a4a, var(--action-passive)); }
.custom-lock-row .custom-slider.is-bet { background: linear-gradient(90deg, #4a4a4a, var(--action-aggressive)); }
.custom-lock-row .custom-slider.is-raise { background: linear-gradient(90deg, #4a4a4a, var(--action-raise)); }
.custom-lock-row .custom-slider.is-allin { background: linear-gradient(90deg, #4a4a4a, var(--action-allin)); }
/* ============================================================================
   Custom Solutions — "Edit bet sizes": the pencil of a node card in the study
   carousel (C06, owner 2026-10-01). One row per bet/raise size (slider, value,
   % / bb, remove), an all-in switch, Add size; Apply re-solves the street.
   Same surface and controls as Set Frequencies (custom-lock.css).
   ============================================================================ */

.custom-edit-backdrop {
  z-index: 70;
}

/* Compact look settled on the under-the-card panel (owner 2026-10-01) and
   now used by the centred dialog: 420px, tight padding, no meta line. */
.custom-edit-panel {
  display: flex;
  width: min(420px, 100%);
  max-height: 100%;
  flex-direction: column;
  gap: 12px;
  overflow-y: auto;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 12px;
  background: #1b1b1b;
  padding: 8px 10px 12px 12px;
  box-shadow:
    0 22px 60px rgba(0, 0, 0, 0.62),
    0 2px 10px rgba(0, 0, 0, 0.4);
}

.custom-edit-panel.is-busy {
  pointer-events: none;
}

.custom-edit-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.custom-edit-head h3 {
  font-size: 15px;
  font-weight: 700;
  color: var(--text-strong);
}

.custom-edit-close {
  display: grid;
  width: 28px;
  height: 28px;
  place-items: center;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.custom-edit-close:hover:not(:disabled) {
  background: #2b2b2b;
  color: var(--text-strong);
}

.custom-edit-message {
  border-radius: 8px;
  background: #242424;
  padding: 10px 12px;
  font-size: 13px;
  line-height: 1.45;
  color: var(--text-soft);
}

.custom-edit-message.is-error {
  background: rgba(255, 93, 93, 0.12);
  color: #ff8a8a;
}

.custom-edit-rows {
  display: flex;
  /* Fixed height (about three sizes) with its own scroll: adding a size never
     resizes the dialog, so Add size below stays under the cursor (owner
     2026-10-01). */
  height: 128px;
  flex-direction: column;
  gap: 4px;
  overflow-y: auto;
  padding-right: 4px;
  scrollbar-width: thin;
  scrollbar-color: #3a3a3a transparent;
}

.custom-edit-empty {
  display: grid;
  flex: 1;
  place-items: center;
  border: 1px dashed rgba(255, 255, 255, 0.12);
  border-radius: 10px;
  padding: 16px;
  text-align: center;
  font-size: 13px;
  color: var(--text-muted);
}

/* Add size (left) and the all-in switch (right) share one row. */
.custom-edit-actions {
  display: flex;
  align-items: stretch;
  gap: 10px;
}

.custom-edit-actions .custom-edit-add {
  flex: 1;
}

.custom-edit-actions .custom-edit-allin {
  flex: none;
  padding: 0 14px;
}

/* Inputs focus in the site orange, not the shared green. */
.custom-edit-panel .custom-input:focus {
  border-color: var(--accent-brand);
  box-shadow: 0 0 0 2px rgba(255, 158, 69, 0.22);
}

.custom-edit-row {
  display: flex;
  flex: none;
  flex-direction: column;
  border-radius: 10px;
  background: #222222;
  padding: 4px 10px;
}

/* Slider, value, unit switch and remove on ONE line (owner 2026-10-01). */
.custom-edit-row-controls {
  display: grid;
  align-items: center;
  gap: 10px;
  grid-template-columns: 80px minmax(0, 1fr) 76px auto 20px;
}

.custom-edit-row-name {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-normal);
}

/* Value field with its own arrows: the native spinners are hidden (too small,
   and they sat on the digits); the digits keep clear of the arrows. */
.custom-edit-value-box {
  position: relative;
}

.custom-edit-value-box .custom-edit-value {
  width: 100%;
  padding: 0 26px 0 8px;
  -moz-appearance: textfield;
}

.custom-edit-value-box .custom-edit-value::-webkit-outer-spin-button,
.custom-edit-value-box .custom-edit-value::-webkit-inner-spin-button {
  margin: 0;
  -webkit-appearance: none;
}

.custom-edit-stepper {
  position: absolute;
  top: 2px;
  right: 2px;
  bottom: 2px;
  display: flex;
  width: 22px;
  flex-direction: column;
}

.custom-edit-stepper button {
  display: grid;
  flex: 1;
  place-items: center;
  border: none;
  border-radius: 4px;
  background: transparent;
  padding: 0;
  color: var(--text-muted);
  cursor: pointer;
}

.custom-edit-stepper button:hover:not(:disabled) {
  background: #3a3a3a;
  color: var(--accent-brand);
}

.custom-edit-value {
  height: 30px;
  text-align: right;
}

.custom-edit-value.is-invalid {
  border-color: #ff5d5d;
}

.custom-edit-unit {
  display: flex;
  gap: 2px;
  border-radius: 8px;
  background: #2c2c2c;
  padding: 2px;
}

.custom-edit-unit-button {
  min-width: 32px;
  height: 26px;
  border: none;
  border-radius: 6px;
  background: transparent;
  font-size: 12px;
  font-weight: 700;
  color: var(--text-muted);
  cursor: pointer;
}

.custom-edit-unit-button.is-active {
  background: #3d3d3d;
  color: var(--text-strong);
}

/* All-in: a switch, since it is one fixed size that is either in or out. */
.custom-edit-allin {
  display: flex;
  align-items: center;
  gap: 10px;
  border-radius: 10px;
  background: #222222;
  padding: 12px;
  font-size: 14px;
  color: var(--text-normal);
  cursor: pointer;
}

.custom-edit-allin input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

/* The switch: grey track, an orange layer that fades in when on (opacity, not
   a background transition), a white knob that slides, grows on hover and
   stretches while pressed. Only transform/opacity/filter animate (5b.3). */
.custom-edit-allin-switch {
  position: relative;
  width: 36px;
  height: 20px;
  flex: none;
  overflow: hidden;
  border-radius: 999px;
  background: #3a3a3a;
}

.custom-edit-allin-switch::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 999px;
  background: var(--accent-brand);
  opacity: 0;
  transition: opacity 220ms ease;
}

.custom-edit-allin-switch::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 14px;
  height: 14px;
  border-radius: 999px;
  background: #f5f5f5;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
  transition: transform 260ms cubic-bezier(0.3, 1.4, 0.5, 1), filter 200ms ease;
}

.custom-edit-allin.is-on .custom-edit-allin-switch::before {
  opacity: 1;
}

.custom-edit-allin.is-on .custom-edit-allin-switch::after {
  transform: translateX(16px);
}

.custom-edit-allin:hover .custom-edit-allin-switch::after {
  transform: scale(1.12);
  filter: drop-shadow(0 0 4px rgba(255, 158, 69, 0.75));
}

.custom-edit-allin.is-on:hover .custom-edit-allin-switch::after {
  transform: translateX(16px) scale(1.12);
}

.custom-edit-allin:active .custom-edit-allin-switch::after {
  transform: scaleX(1.35);
}

.custom-edit-allin.is-on:active .custom-edit-allin-switch::after {
  transform: translateX(16px) scaleX(1.35);
}

.custom-edit-add {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 36px;
  border: 1px dashed rgba(255, 255, 255, 0.18);
  border-radius: 10px;
  background: transparent;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-soft);
  cursor: pointer;
}

.custom-edit-add:hover:not(:disabled) {
  border-color: var(--accent-brand);
  color: var(--text-strong);
}

.custom-edit-add:disabled {
  opacity: 0.4;
  cursor: default;
}

.custom-edit-hint {
  font-size: 12px;
  color: var(--text-muted);
}

.custom-edit-hint.is-warning {
  color: var(--accent-brand);
}

.custom-edit-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  border-top: 1px solid rgba(255, 255, 255, 0.07);
  padding-top: 14px;
}

.custom-edit-footer-actions {
  display: flex;
  gap: 8px;
}


/* Remove: a plain red cross, no button chrome and no motion. */
.custom-edit-remove {
  display: grid;
  width: 24px;
  height: 24px;
  place-items: center;
  border: none;
  border-radius: 6px;
  background: transparent;
  padding: 0;
  color: #e5534b;
  cursor: pointer;
}

.custom-edit-remove svg {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
}

.custom-edit-remove:hover:not(:disabled) {
  color: #ff6b63;
}

.custom-edit-remove:focus-visible {
  outline: 2px solid var(--text-strong);
  outline-offset: 2px;
}

/* Apply in the site orange (the shared custom primary button is green). */
.custom-edit-footer .custom-primary-button {
  background: var(--accent-brand);
  color: var(--text-on-brand);
  transition: transform 180ms ease-out, filter 180ms ease-out;
}

/* Hover keeps the white label (the shared rule turns it near-black): the
   button lifts a pixel, brightens and glows softly; a press sinks it. */
.custom-edit-footer .custom-primary-button:hover:not(:disabled) {
  background: var(--accent-brand);
  color: var(--text-on-brand);
  transform: translateY(-1px);
  filter: brightness(1.07) drop-shadow(0 4px 10px rgba(255, 158, 69, 0.45));
}

.custom-edit-footer .custom-primary-button:active:not(:disabled) {
  transform: translateY(0) scale(0.98);
  filter: brightness(0.97);
}

/* Size slider (owner 2026-10-01): a thin neutral track filled in orange up to
   the knob (gradient set at runtime), a small white knob that grows slightly on
   hover and a little more while dragged, with a soft orange glow. Calm easing,
   transform/filter only (5b.3). */
/* Shared with the range editor's brush weight (owner 2026-10-02). */
:is(.custom-edit-row, .custom-range-tools) .custom-edit-slider {
  height: 4px;
  border-radius: 999px;
  cursor: pointer;
}

:is(.custom-edit-row, .custom-range-tools) .custom-edit-slider::-webkit-slider-thumb {
  width: 14px;
  height: 14px;
  border: none;
  border-radius: 50%;
  background: #f5f5f5;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
  transition: transform 160ms ease-out, filter 160ms ease-out;
}

:is(.custom-edit-row, .custom-range-tools) .custom-edit-slider::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border: none;
  border-radius: 50%;
  background: #f5f5f5;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
  transition: transform 160ms ease-out, filter 160ms ease-out;
}

:is(.custom-edit-row, .custom-range-tools) .custom-edit-slider:hover:not(:disabled)::-webkit-slider-thumb {
  transform: scale(1.15);
  filter: drop-shadow(0 0 4px rgba(255, 158, 69, 0.7));
}

:is(.custom-edit-row, .custom-range-tools) .custom-edit-slider:hover:not(:disabled)::-moz-range-thumb {
  transform: scale(1.15);
  filter: drop-shadow(0 0 4px rgba(255, 158, 69, 0.7));
}

:is(.custom-edit-row, .custom-range-tools) .custom-edit-slider:active::-webkit-slider-thumb {
  cursor: grabbing;
  transform: scale(1.25);
  filter: drop-shadow(0 0 6px rgba(255, 158, 69, 0.85));
}

:is(.custom-edit-row, .custom-range-tools) .custom-edit-slider:active::-moz-range-thumb {
  cursor: grabbing;
  transform: scale(1.25);
  filter: drop-shadow(0 0 6px rgba(255, 158, 69, 0.85));
}

:is(.custom-edit-row, .custom-range-tools) .custom-edit-slider:focus-visible {
  outline: 2px solid var(--text-strong);
  outline-offset: 4px;
}

/* The all-in row: fixed at the whole stack, so its slider and value are
   static (dark all-in colour), only the cross is live. */
.custom-edit-row.is-allin .custom-edit-slider {
  cursor: default;
}

.custom-edit-row.is-allin .custom-edit-slider::-webkit-slider-thumb {
  cursor: default;
}

.custom-edit-row.is-allin .custom-edit-value {
  padding-right: 8px;
  color: var(--text-soft);
}

.custom-edit-allin-unit {
  min-width: 68px;
  text-align: center;
  font-size: 12px;
  font-weight: 700;
  color: var(--text-muted);
}

/* ----------------------------------------------------------------------------
   Anchored "Edit bet sizes" (owner 2026-10-01; switched off, Study uses the
   dialog): the same editor fixed under the opened card with an arrow.
   It appears and disappears at once, without animation. Position comes from
   the card at runtime (top/left/arrow); everything else lives here.
   ---------------------------------------------------------------------------- */

.custom-edit-anchor {
  position: fixed;
  z-index: 70;
  display: flex;
  width: min(420px, calc(100vw - 32px));
  flex-direction: column;
  visibility: hidden;
}

/* No animation (owner 2026-10-01): the panel appears and disappears at once.
   It stays hidden only until its place under the card is known. */
.custom-edit-anchor.is-placed {
  visibility: visible;
}

.custom-edit-anchor .custom-edit-panel {
  width: 100%;
  min-height: 0;
}

/* The arrow joins the panel to the card: drawn over the panel's top border so
   the two read as one shape. */
.custom-edit-arrow {
  position: absolute;
  top: -7px;
  z-index: 1;
  width: 14px;
  height: 14px;
  margin-left: -7px;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  border-left: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 3px 0 0 0;
  background: #1b1b1b;
  transform: rotate(45deg);
}

/* Check / call / fold rows (owner 2026-10-01): shaped like a size row, the
   slider and value replaced by a quiet placeholder line. A removed move stays
   in the list, dimmed, with a green plus to bring it back. */
.custom-edit-row.is-passive .custom-edit-row-controls {
  min-height: 30px;
}

.custom-edit-passive-track {
  grid-column: 2 / 5;
  height: 4px;
  border-radius: 999px;
  background: #2e2e2e;
}

.custom-edit-row.is-removed {
  background: transparent;
  outline: 1px dashed #333333;
  outline-offset: -1px;
}

.custom-edit-row.is-removed .custom-edit-row-name {
  color: var(--text-muted);
  text-decoration: line-through;
}

.custom-edit-row.is-removed .custom-edit-passive-track {
  background: transparent;
}

/* Restore: a plain green plus, the counterpart of the red cross. */
.custom-edit-restore {
  display: grid;
  width: 24px;
  height: 24px;
  place-items: center;
  border: none;
  border-radius: 6px;
  background: transparent;
  padding: 0;
  color: #3fb950;
  cursor: pointer;
}

.custom-edit-restore svg {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
}

.custom-edit-restore:hover:not(:disabled) {
  color: #56d364;
}
