

@layer reset, base, components, utilities;

/* Entry point for your PostCSS build */

/********************
Start from good reset
*********************/

/*
 * With Propshaft, assets are served efficiently without preprocessing steps.
 * CSS precedence follows the standard cascading order — styles declared
 * later in the manifest win over earlier ones at equal specificity — except
 * where @layer below overrides that (a layered rule always loses to any
 * unlayered rule, regardless of source order).
 */

@layer reset {
  [hidden] { display: none !important; }

  /* Kill the ~300ms "is this a double-tap-to-zoom?" wait and the grey tap
     flash on anything interactive -- on mobile the first tap on a button
     would otherwise sometimes just get eaten. */
  :where(a, button, input, textarea, select, label, summary, [role="button"], [data-action]) {
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
  }

  /* Box sizing and border defaults */
  :where(*, *::before, *::after) {
    box-sizing: border-box;
    border-width: 0;
    border-style: solid;
    min-width: 0;
  }

  /* Core root defaults */
  :where(html) {
    block-size: 100%;
    -webkit-text-size-adjust: none;
       -moz-text-size-adjust: none;
            text-size-adjust: none;
    interpolate-size: allow-keywords;
  }

  :where(body) {
    min-block-size: 100vh;
    line-height: 1.5;
    margin: 0;
    -webkit-font-smoothing: antialiased;
  }

  /* Typography improvements */
  :where(h1, h2, h3, h4, h5, h6) {
    text-wrap: balance;
    font-weight: bold;
    line-height: 1.35;
    margin: 0 0 0.5rem;
    font-family: var(--font-heading);
  }

  :where(p) {
    text-wrap: pretty;
    margin-block-end: 1em;
  }

  :where(p + p) {
    margin-block-start: 0.75em;
  }

  /* Ported from ~/Repos/bhamdiy's own typography.css -- list/definition-list
     spacing this app never had its own opinion on. A component's more
     specific class (e.g. .table-list's own list-style/padding, or
     .player-info__stat-value's own margin: 0) already wins over these bare
     tag rules on specificity, so nothing already hand-tuned shifts. */
  :where(ul, ol) {
    margin: 0 0 1rem;
    padding-left: 1.5rem;
    line-height: 1.625;
  }

  :where(li) {
    margin-bottom: 0.25rem;
  }

  :where(dl) {
    margin: 0 0 1rem;
  }

  :where(dt) {
    font-weight: 600;
    margin-top: 0.75rem;
  }

  :where(dt:first-child) {
    margin-top: 0;
  }

  :where(dd) {
    margin: 0.125rem 0 0 1rem;
    line-height: 1.5;
  }

  /* Media handling */
  :where(img, svg, video, canvas) {
    display: block;
    max-inline-size: 100%;
    block-size: auto;
  }

  /* Form control modernization */
  :where(input, textarea, select, button) {
    font: inherit;
    letter-spacing: inherit;
    word-spacing: inherit;
    color: currentColor;
  }

  :where(select, input) {
    field-sizing: content;
  }

  /* iOS Safari auto-zooms the whole page when a focused input's computed
     font-size is under 16px — and unlike a manual pinch-zoom, dismissing
     the keyboard afterward doesn't zoom back out on its own, leaving the
     page stuck zoomed in. Forcing every text input to at least 16px (this
     app's own 1rem is already 17.6px, comfortably above that) prevents the
     zoom from triggering in the first place, rather than trying to
     reactively undo it once it has. */
  :where(input, textarea, select) {
    font-size: max(1rem, 16px);
  }

  /* Accessibility */
  :where(:focus-visible) {
    outline: 2px solid canvasText;
    outline-offset: 2px;
  }

  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 0.01ms !important;
      transition-duration: 0.01ms !important;
    }
  }
}

@layer base {
  /* Full-bleed elements (e.g. the hole-cards hero) are allowed to run a
     little wide on very small screens — this crops that overflow instead
     of introducing a horizontal scrollbar. Set on both html and body (the
     one that actually controls viewport scrolling varies by browser); this
     is safe for the action bar's `position: sticky` too, since neither
     element is height-constrained, so the resulting scroll box still
     matches the viewport's own. */
  html {
    font-size: 17.6px;
    overflow-x: hidden;
  }

  body {
    margin: 0;
    overflow-x: hidden;
    font-family: var(--font-sans);
    background: var(--color-background);
    color: var(--color-foreground);
  }

  /* Turbo Drive's own top-of-page loading bar -- Turbo injects its own
     \3c style> at runtime with this same selector (ProgressBar.defaultCSS in
     turbo.js), unlayered and with a hardcoded #0076ff blue. Unlayered
     always beats a layered rule regardless of source order (see the top
     of this file), so !important is the only way to actually win here --
     same green as .btn--primary (button.css), the app's one "in-progress"
     accent elsewhere. */
  .turbo-progress-bar {
    background-color: var(--color-primary) !important;
  }

  a {
    color: var(--color-primary-text);
    text-decoration: none;
    transition: color 140ms;
  }

  a:hover {
    color: var(--color-chip-ivory);
  }

  /* Unlike <a href>, browsers don't give <button> a pointer cursor by
     default (it's "default"/arrow) -- every clickable button in the app
     should still read as clickable, whatever component-specific styling
     it also gets. */
  :where(button, [role="button"], input[type="submit"], input[type="button"], input[type="reset"]) {
    cursor: pointer;
  }

  :where(button, [role="button"], input[type="submit"], input[type="button"], input[type="reset"]):disabled {
    cursor: not-allowed;
  }
}

.container {
  padding: var(--container-padding);
  max-width: 1200px;
  margin: 0 auto;
  /* Belt-and-suspenders alongside html/body's overflow-x: hidden above —
     iOS Safari can still let the page pan horizontally if some descendant
     (e.g. a wrapping flex row that doesn't wrap cleanly at odd widths)
     nudges layout width past the viewport, even with that in place. Clip
     here too, right at the ancestor everything else nests inside. */
  overflow-x: clip;
}

/* The table UI (see tables/show.html.slim's own body_class) is a fixed,
   exactly-one-screen layout (.u-locked-viewport) -- the container's own
   vertical breathing room would just eat into that fixed height for
   nothing to actually scroll to, so it's the one page that opts back out. */
body.u-locked-viewport .container {
  /* No vertical breathing room (nothing scrolls), and only a sliver of
     side padding -- the table wants the width. */
  padding: 0 0.25rem;
}
/******************************
Establish some styling opinions
*******************************/
/*
 * Design tokens. Deliberately simple — hand-picked values, no color-mix()
 * ramp of light/lighter/dark/darker variants beyond what's declared here.
 * Add a token here when a component needs one; don't reach for a bare hex
 * or rgba() value in a component file — spec/stylesheets/bem_spec.rb fails
 * the build if one shows up there.
 *
 * No light mode: the app is always a dark background. Every accent color
 * below is deliberately mid-bright (picked from a single 16-hue wheel, see
 * --color-red..--color-crimson) so the same pairing rule holds everywhere
 * it's used as a fill — dark text (--color-ink) on top of it, never light.
 * Used as bare text/icon color directly on the dark app background instead
 * (a link, a hint, an avatar), the same brightness reads fine on its own —
 * that's the "bright text on a dark element" half of the same rule.
 */
:root {
  /* Both loaded from Google Fonts (see layouts/application.html.slim's own
     <link> tags) -- a real fallback stack behind each in case that request
     is ever slow or blocked, not just the font name alone. */
  --font-sans: "Urbanist", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-heading: "Antic Didone", Georgia, "Times New Roman", serif;
  /* A plain system stack, not a Google Font -- every platform already
     ships a real terminal-style monospace face, so there's no request to
     make for one. Used sparingly, e.g. the room code (see
     tables/_footer_qr.html.slim's own .qr-modal__code-text). */
  --font-mono: ui-monospace, "SF Mono", "Cascadia Code", "Roboto Mono", Menlo, Consolas, monospace;

  --color-background: #101715;
  --color-foreground: #f1f0e8;
  --color-muted: #a7b5ad;
  --color-border: #35453d;
  --color-surface: rgba(255, 255, 255, 0.06);
  --color-surface-raised: rgba(255, 255, 255, 0.14);

  /* Near-black — the "dark text" half of the dark-text-on-bright-elements
     rule, used as the foreground for anything filled with one of the
     16-hue colors below (buttons, badges, the site header). */
  --color-ink: #111;
  --color-white: #fff;

  /* The 16-hue wheel itself, evenly spaced, one consistent mid-bright
     tone throughout — every semantic token below is just a name for one
     of these, not a separately hand-picked shade. */
  --color-red: #df5858;
  --color-orange: #df8b58;
  --color-gold: #dfbd58;
  --color-yellow: #cedf58;
  --color-lime: #9cdf58;
  --color-green: #69df58;
  --color-emerald: #9dd1ac;
  --color-teal-green: #58dfac;
  --color-teal: #58dfdf;
  --color-sky: #58acdf;
  --color-indigo: #587adf;
  --color-purple: #6958df;
  --color-violet: #9c58df;
  --color-magenta: #ce58df;
  --color-pink: #df58bd;
  --color-crimson: #df588b;

  --color-primary: var(--color-emerald);
  /* Every *-hover token below is its own base color mixed 10% toward
     white -- a consistent "brighten on hover" rule across every .btn
     variant (see button.css), not a bespoke shade picked per button. */
  --color-primary-hover: color-mix(in oklch, var(--color-emerald) 90%, white);
  /* The one button variant that's still a dark fill, not a bright one (a
     deliberately lower-emphasis "Cancel"/"Back" affordance next to a
     brighter primary/danger) — --color-foreground text, not --color-ink. */
  --color-secondary: #37474f;
  --color-secondary-hover: color-mix(in oklch, var(--color-secondary) 90%, white);
  --color-danger: var(--color-red);
  --color-danger-hover: color-mix(in oklch, var(--color-red) 90%, white);
  --color-shove-hover: color-mix(in oklch, var(--color-shove) 90%, white);
  --color-accent: var(--color-gold);
  --color-primary-text: var(--color-menu-trim);
  /* A deliberate direct hex, not another token reference -- the site
     header's own default background (site-header.css) and a few
     selection borders/outlines (table-join.css, seat.css,
     choice-picker.css) that wanted the same dark-green fill, but
     deliberately NOT --color-primary-text's own links (those stay the
     brighter --color-menu-trim above) or any .btn (those stay on
     --color-primary/-secondary/-danger/-shove, untouched). A dark fill
     for the header means its own default foreground has to flip to light
     text too -- see site-header.css. */
  --color-header-accent: #325143;

  /* Per-seat betting-escalation bar (see HandOrchestrator#bet_indicators) —
     the warm half of the wheel climbs with the aggression (bet, then
     raise, then 3bet), pink stands apart entirely for an all-in (nothing
     left to escalate past it), and call sits on the cool side, visually
     unrelated to the aggression ramp since it isn't one. */
  --color-bet: var(--color-gold);
  --color-call: var(--color-teal);
  --color-raise: var(--color-orange);
  --color-3bet: var(--color-red);
  --color-shove: var(--color-pink);

  /* The glowing outline around whichever seat is up next — deliberately
     not the same green as --color-primary, so a button rendered inside an
     acting seat's own card (there isn't one today, but nothing stops a
     future one) wouldn't visually merge into the glow around it. */
  --color-acting-glow: var(--color-lime);

  /* Blinds — the small/big blind badges pinned to a seat's corner. */
  --color-small-blind: var(--color-sky);
  --color-big-blind: var(--color-red);
  /* The dealer button itself is a real poker object with its own
     traditional color (an ivory/cream disc), not a semantic app accent —
     named on its own rather than reused from the 16-hue wheel. */
  --color-dealer-button: #fdfaf0;

  /* The hole-cards drag hints and the "You won/lost" readout — positive
     mirrors --color-primary's green, "stop" mirrors --color-danger's red,
     both as bare text on the dark background rather than a fill. */
  --color-positive: var(--color-emerald);
  --color-negative: var(--color-crimson);

  /* A card back's own frame, and the fallback fill for --card-back-color
     on the rare render where a table's own value genuinely isn't set
     (every table always has one; this is just the defensive default). */
  --color-card-back-border: #14264a;
  --color-card-back-fallback: #2a4d8f;

  /* The card face itself (paper + ink), the court-card portrait palette,
     and the gold ruling around them — used by the inline-SVG card faces
     (see PlayingCardsHelper / card.css), not a photographic asset. */
  --color-card: #fffdf5;
  --color-card-ink: #192720;
  --color-card-red: #b32e3b;
  /* Four-color-deck alternates for diamonds/clubs (Table#four_color_deck) --
     hearts/spades stay --color-card-red/--color-card-ink either way. */
  --color-card-blue: #185fa2;
  --color-card-green: #226942;
  --color-paper-edge: #d6d4c7;
  --color-paper-shade: #e8e5d8;
  --color-court-blue: #294663;
  --color-court-red: #ab3640;
  --color-court-gold: #c69a49;
  --color-court-skin: #f6e5c7;
  --color-brass: #a78748;
  --color-deep-shadow: rgba(0, 0, 0, 0.48);

  /* Backdrops and shadows — every rgba() in the app is one of these four,
     not a bespoke opacity picked per component. */
  --color-overlay: rgba(0, 0, 0, 0.66); /* full-screen scrim behind a modal/panel */
  --color-scrim: rgba(0, 0, 0, 0.55); /* translucent backdrop directly behind text on an image-like surface */
  --color-shadow: rgba(0, 0, 0, 0.3); /* drop shadows */
  --color-shadow-ring: rgba(0, 0, 0, 0.4); /* thin outer ring, e.g. under a badge */

  /* Dark base the desktop poker-table felt fades to at the rail, and the
     empty-board-slot fill (tables/_poker_table, poker-table.css). The felt
     gradient itself is derived from the table's own card-back colour. */
  --color-felt-edge: #254d3c;

  /* The homepage's illustrated poker-table hero (welcome.css's .poker-disc)
     and, later, the live table's own redesigned rail/menu chrome -- a
     forest-green/ivory/brass identity distinct from the app's own
     background/foreground pair above. */
  --color-felt: #173e30;
  --color-felt-light: rgba(172, 207, 142, 0.12);
  --color-felt-shade: rgba(0, 17, 9, 0.42);
  --color-menu-background: #0b1d17;
  --color-menu-trim: #a7bca0;
  --color-menu-chip-teal: #477d79;
  --color-rail: #17201b;
  --color-rail-edge: #435047;
  --color-glint: rgba(255, 255, 244, 0.18);
  --color-chip-ivory: #eee8d6;
  --color-chip-red: #be4c47;
  --color-chip-blue: #487ca5;
  --color-chip-green: #43865d;
  --color-chip-black: #313b38;
  --color-chip-edge: rgba(0, 0, 0, 0.4);
  --color-action-edge: #15231b;
  --color-action-shade: rgba(0, 0, 0, 0.15);
  /* A six-pixel woven felt tile -- no downloaded texture or SVG filter. */
  --texture-felt: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='6' height='6'%3E%3Cpath d='M0 1h6M0 4h6' stroke='%23fff' stroke-opacity='.025'/%3E%3Cpath d='M1 0v6M4 0v6' stroke='%23000' stroke-opacity='.04'/%3E%3C/svg%3E");

  /* Vertical padding here is overridden back to 0 for the table UI itself
     (see reset.css's own body.u-locked-viewport rule) -- that page is a
     fixed, exactly-one-screen layout with nothing to breathe room around. */
  --container-padding: 1.5rem 1rem;
  --border-radius: 0.5rem;
  --border-radius-sm: 0.25rem;
}
/******************
Load all components
*******************/
/* devise/registrations/edit's whole page (heading, form, the cancel-account
   section below it) -- one shared, centered column (margin: 0 auto), wide
   enough for the form's own two-column split (see components/grid.css,
   the shared grid this page uses for that instead of a one-off of its
   own). Previously just the .form itself had this max-width, which left
   the heading and everything after the form still flush against the
   page's own wider .container -- this wraps the entire page instead so
   every bit of content lines up under the same left edge. */
.account-page {
  max-width: 42rem;
  margin: 0 auto;
}

/* The shared .form's own 24rem cap (see form.css, sized for every other,
   single-column .form on the site) would otherwise still bottleneck this
   one down from the full width .account-page above just gave it.
   Specificity match (.form.account-form) so it wins regardless of the
   two files' load order. */
.form.account-form {
  max-width: none;
}

/* .grid__cell (see grid.css) is just a flex sizing box on its own -- the
   vertical rhythm between the fields stacked inside each one here is this
   page's own, same spacing .form's own flex column already uses
   elsewhere (see form.css), scoped to stay out of the shared grid system
   itself since not every .grid__cell wants its children stacked this
   way. */
.account-form .grid__cell {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
/* Locked to the viewport bottom always (not just while scrolled within the
   page) — position: fixed rather than sticky. Column layout so the slider
   panel, when present, stacks above the button row rather than beside it. */
.action-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
}

.action-bar__row {
  position: relative;
  display: flex;
  background: var(--color-background);
  border-top: 1px solid var(--color-border);
}

/* Dims the fold/check/call row while the slider panel above is open —
   sits on top of .action-bar__buttons, so it also blocks clicks reaching
   them, not just signals it visually. */
.action-bar__overlay {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--color-overlay);
  cursor: pointer;
}

.action-bar__overlay--visible {
  display: block;
}

.action-bar__buttons {
  display: flex;
  align-items: center;
  flex: 1;
  gap: 0.35rem;
  /* Vertical padding kept minimal -- the fixed footer is tight on height,
     especially on a short mobile viewport. */
  padding: 0.125rem 0.5rem;
}

/* The whole footer runs ~20% smaller than its defaults. .action-bar .btn
   (not .action-bar__button) so it actually wins over .btn's own
   font-size; the p covers the "Waiting for X..." / "You busted out"
   status lines yielded into the row (their .u-text-sm is unlayered-beaten
   here). */
.action-bar .btn {
  font-size: 0.76rem;
}

.action-bar__buttons p {
  font-size: 0.7rem;
}

/* Always the far-right corner of the footer row, whichever state's
   occupying the rest of it (see tables/_footer_corner, rendered from
   every footer state) -- the last-hand, chip-requests and QR buttons as
   one grouped unit (no dividers between them), set off from the rest of
   the row by this single left border. */
.action-bar__corner {
  display: flex;
  align-items: stretch;
  flex: 0 0 auto;
  border-left: 1px solid var(--color-border);
}

/* The QR button with the sit-n-go turn clock stacked beneath it (see
   tables/_footer_corner). */
.action-bar__corner-stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.action-bar__qr {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 2.5rem;
  padding: 0.15rem;
  background: none;
  border: none;
  cursor: pointer;
}

/* Narrower than .action-bar__qr -- just an icon, no thumbnail image to
   size around. Disabled (see tables/_footer_results) before any hand has
   ever concluded at this table yet. */
.action-bar__results {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 2.3rem;
  padding: 0.25rem;
  background: none;
  border: none;
  color: var(--color-foreground);
  cursor: pointer;
}

.action-bar__results:disabled {
  color: var(--color-muted);
  cursor: default;
}

/* Owner-only (see tables/_footer_chip_requests) -- same footprint as
   .action-bar__results, disabled/dim with nothing pending, flashing
   yellow the moment a busted seat requests a rebuy so the owner notices
   it without a banner shoving the whole table down every time (what this
   replaced -- see the partial's own comment). */
.action-bar__chip-requests {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 2.3rem;
  padding: 0.25rem;
  background: none;
  border: none;
  color: var(--color-muted);
  /* Barely there with nothing pending -- it's disabled/inert most of the
     time (only ever relevant the moment someone actually requests a
     rebuy), so it shouldn't compete for attention with the icons that are
     always actionable. --pending below brings it back to full opacity
     along with the color/animation swap. */
  opacity: 0.35;
  cursor: default;
}

.action-bar__chip-requests--pending {
  opacity: 1;
  color: var(--color-yellow);
  cursor: pointer;
  animation: action-bar-chip-requests-flash 1.2s ease-in-out infinite;
}

/* No .action-bar to sit inside yet -- see tables/_game.html.slim's own
   seat.nil? branch (the owner hasn't taken a seat at their own table).
   Floats in that same bottom-right corner on its own instead. */
.action-bar__chip-requests--floating {
  position: fixed;
  right: 0;
  bottom: 0;
  z-index: 20;
  border-right: none;
  border-left: 1px solid var(--color-border);
  border-top: 1px solid var(--color-border);
  background: var(--color-background);
}

@keyframes action-bar-chip-requests-flash {
  0%, 100% {
    opacity: 1;
  }
  50% {
    opacity: 0.35;
  }
}

@media (prefers-reduced-motion: reduce) {
  .action-bar__chip-requests--pending {
    animation: none;
  }
}

.action-bar__qr-thumb {
  width: 100%;
  max-width: 2.4rem;
  aspect-ratio: 1;
  background: var(--color-white);
  border-radius: var(--border-radius-sm);
  padding: 0.15rem;
}

.action-bar__item {
  flex: 1;
  display: flex;
}

.action-bar__button {
  flex: 1;
  padding-top: 0.3rem; /* well under .btn's own 0.65rem — the footer bar is tight on height */
  padding-bottom: 0.3rem;
}

.action-bar__slider-panel {
  display: none;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.15rem 0.75rem 0.35rem;
  background: var(--color-background);
  border-top: 1px solid var(--color-border);
}

.action-bar__slider-panel--open {
  display: flex;
}

.action-bar__slider-form {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.action-bar__slider-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 0.75rem;
  flex-wrap: wrap;
}

.action-bar__presets {
  display: flex;
  flex: 1;
  flex-wrap: wrap;
  gap: 0.3rem;
}

.action-bar__preset {
  flex: 1;
  min-width: 2.75rem;
  padding: 0.3rem 0.4rem;
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius-sm);
  background: var(--color-surface-raised);
  color: var(--color-foreground);
  font-size: 0.75rem;
  font-weight: 600;
  text-align: center;
  cursor: pointer;
}

.action-bar__slider-amount {
  font-size: 1.1rem;
  font-weight: 700;
}

.action-bar__slider {
  width: 100%;
  accent-color: var(--color-primary);
}

.action-bar__slider--shove {
  accent-color: var(--color-shove);
}

.action-bar__slider-buttons {
  display: flex;
  gap: 0.35rem;
}

.action-bar__slider-buttons .btn {
  flex: 1;
  padding-top: 0.3rem;
  padding-bottom: 0.3rem;
}

/* A bot's own turn (see tables/_game.html.slim) -- three dots pulsing in
   sequence, the universal "someone's typing/thinking" signal, standing in
   for the THINKING_DELAY a real BotActJob run takes (see its own comment,
   0.5-2s) before its action actually lands. */
.action-bar__bot-thinking {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
}

.action-bar__bot-thinking-dots {
  display: inline-flex;
  gap: 0.2rem;
}

.action-bar__bot-thinking-dot {
  width: 0.3rem;
  height: 0.3rem;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.3;
  animation: action-bar-bot-thinking-dot 1.2s ease-in-out infinite;
}

.action-bar__bot-thinking-dot:nth-child(2) {
  animation-delay: 0.2s;
}

.action-bar__bot-thinking-dot:nth-child(3) {
  animation-delay: 0.4s;
}

@keyframes action-bar-bot-thinking-dot {
  0%, 80%, 100% {
    opacity: 0.3;
  }
  40% {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .action-bar__bot-thinking-dot {
    animation: none;
    opacity: 0.7;
  }
}

/* The 30s turn clock on public sit-n-gos (see tables/_action_clock),
   tucked under the QR button in the footer corner. Server-authoritative
   timing lives in ActionClockJob -- this is just the visible tick, kept
   small so it costs the fixed footer almost no height. */
.action-clock {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.1rem 0.2rem 0.3rem;
  line-height: 1;
  white-space: nowrap;
}

/* Monospace + a reserved min-width so the corner doesn't twitch as the
   count drops from two digits to one. */
.action-clock__value {
  display: inline-block;
  min-width: 2.4em;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--color-accent);
}
/* The thin icon-only strip always sitting at the top of the fixed footer
   stack (see .action-bar in action-bar.css, which this is a flex child
   of, first in DOM order so it lands above the slider panel/main row
   beneath it). Deliberately tighter than .action-bar__row's own buttons --
   icons only, no labels, nothing that needs real tap-target padding
   beyond the icon itself. */
.action-icons-bar {
  display: flex;
  align-items: center;
  gap: 0.1rem;
  padding: 0.15rem 0.4rem;
  background: var(--color-background);
  border-top: 1px solid var(--color-border);
}

.action-icons-bar__button {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 1.9rem;
  height: 1.9rem;
  padding: 0.2rem;
  background: none;
  border: none;
  border-radius: var(--border-radius-sm);
  color: var(--color-foreground);
  cursor: pointer;
  transition: background-color 0.15s ease;
}

.action-icons-bar__button:hover {
  background: var(--color-surface);
}

/* tables/_footer_results and _footer_chip_requests were sized for the
   wider corner (see action-bar.css's own .action-bar__results/
   .action-bar__chip-requests) -- reused here as-is (same classes, same
   BEM ancestor requirement -- still satisfied by the enclosing
   .action-bar, just nested one level deeper), just shrunk to match this
   bar's own tighter footprint instead of duplicating those partials. */
.action-icons-bar .action-bar__results,
.action-icons-bar .action-bar__chip-requests {
  width: 1.9rem;
  padding: 0.2rem;
}

.action-icons-bar__pot {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  margin-left: auto;
  padding-right: 0.2rem;
  font-size: 0.78rem;
}

.action-icons-bar__pot-icon {
  color: var(--color-accent);
  opacity: 0.85;
}

.action-icons-bar__pot-label {
  color: color-mix(in oklch, var(--color-accent) 70%, var(--color-muted));
}

.action-icons-bar__pot-amount {
  font-weight: 700;
  color: var(--color-accent);
}

/* The sign-in / sign-up pages: the form on the left, a screenshot of a
   live hand on the right at -md (stacked, form first, below that). Mirrors
   the homepage hero (see components/home.css .home-hero__shot). */
.auth {
  padding: 0.5rem 0 1.5rem;
}

/* Caps the form column from stretching wide on a big left cell. Below
   768px the grid stacks (see .grid__cell--width-50-md), so there's no
   second column left to align against -- centered there, same as
   .form-page elsewhere. At/above 768px, left-aligned instead (not
   margin: 0 auto centered), so its own left edge lines up with .flash
   above it (a direct .container child, spanning the full width, not
   scoped to this cell at all). */
.auth__form {
  max-width: 24rem;
  margin: 0 auto;
}

@media (min-width: 768px) {
  .auth__form {
    margin: 0;
  }
}

.auth__shot {
  width: 100%;
  height: auto;
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius);
  box-shadow: 0 1rem 2.5rem var(--color-shadow);
}

.avatar-picker__icons {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-top: 0.5rem;
}

.avatar-picker__grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 0.5rem;
}

.avatar-picker__icon-button {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  width: 100%;
  padding: 0;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius-sm);
  color: var(--color-foreground);
  cursor: pointer;
}

.avatar-picker__icon-button--selected {
  color: var(--avatar-color);
  border-color: var(--avatar-color);
}

.avatar-picker__colors {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

.avatar-picker__color-button {
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  background: var(--swatch-color);
  border: 2px solid transparent;
  border-radius: 50%;
  cursor: pointer;
}

.avatar-picker__color-button--selected {
  border-color: var(--color-foreground);
}

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--avatar-color);
}

.avatar__icon {
  display: block;
}

/* A sit-n-go's own level/blinds/countdown (see tables/_blind_clock) --
   sits at the very top of #table, above everything else, same width as
   the seat rail below it. */
.blind-clock {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: 0.3rem 0.6rem;
  margin-bottom: 0.2rem;
  background: var(--color-surface-raised);
  border-radius: var(--border-radius-sm);
  font-size: 0.8rem;
}

.blind-clock__level {
  font-weight: 700;
}

.blind-clock__blinds {
  color: var(--color-muted);
}

/* Digital-clock style: monospace + tabular figures so the m:ss readout
   holds a steady width as it counts down, and reads as a clock. */
.blind-clock__countdown {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  font-weight: 600;
  color: var(--color-primary);
}
/* The footer state for a Bot Game the viewer just won outright -- every
   bot busted (see tables/_bot_game_won and _game.html.slim's own guard
   ahead of table.closed?/busted_heads_up?). Stacked, not the single-line
   .sit-n-go-complete layout, since there's a second line counting down to
   the auto-redirect (see countdown_controller.js's redirectUrlValue). */
.bot-game-won {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  text-align: center;
}

.bot-game-won__icon {
  color: var(--color-gold);
}

.bot-game-won__message {
  font-size: 0.9rem;
}

.bot-game-won__redirect {
  color: var(--color-muted);
  font-size: 0.8rem;
}

/* Fixed width (fits "10") so the count ticking down doesn't jog the
   surrounding sentence, monospace to read as a clock. */
.bot-game-won__seconds {
  display: inline-block;
  min-width: 2ch;
  text-align: center;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
/* The "next hand in Ns…" pause shown once a game has no human left playing
   and just settled a real showdown -- see tables/_bots_only_countdown and
   BotActJob::SHOWDOWN_PAUSE. */
.bots-only-countdown {
  display: flex;
  justify-content: center;
}

/* Fixed width so the single digit ticking down (5 through 0) doesn't
   shift the surrounding text side to side every second. */
.bots-only-countdown__seconds {
  display: inline-block;
  width: 1ch;
  text-align: center;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--color-foreground);
}
/* color: a default full-brightness foreground so the mark stays crisp
   wherever it lands, rather than inheriting a container's own dimmer text
   color (e.g. the footer's own muted --color-muted, see footer.css) --
   see BrandHelper#rbpkr_logo for why this file's currentColor fill makes
   that just a CSS override instead of a separate asset. Anywhere that
   wants a different shade (site-header.css's own light-on-dark-fill
   default) overrides this the same way, not by swapping files. */
.brand-logo { display: block; width: 136px; max-width: 100%; height: auto; flex: none; color: var(--color-foreground); }
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.65rem 1rem;
  border: none;
  border-radius: var(--border-radius);
  font-weight: 600;
  font-size: 0.95rem;
  cursor: pointer;
  transition: background-color 140ms;
}

/* Primary/danger/shove are all filled with a bright color off the 16-hue
   wheel (see opinions.css) — --color-ink (dark) text, not
   --color-foreground, is what actually reads on top of them. Secondary
   stays a dark, low-emphasis fill on purpose (see its own token comment),
   so it keeps the light --color-foreground text instead. */
.btn--primary {
  background: var(--color-primary);
  color: var(--color-ink);
}

.btn--primary:hover {
  background: var(--color-primary-hover);
}

.btn--danger {
  background: var(--color-danger);
  color: var(--color-ink);
}

.btn--danger:hover {
  background: var(--color-danger-hover);
}

.btn--secondary {
  background: var(--color-secondary);
  color: var(--color-foreground);
}

.btn--secondary:hover {
  background: var(--color-secondary-hover);
}

.btn--shove {
  background: var(--color-shove);
  color: var(--color-ink);
}

.btn--shove:hover {
  background: var(--color-shove-hover);
}

.card-back-picker__patterns {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

.card-back-picker__pattern-button {
  width: 2.75rem;
  aspect-ratio: 500 / 726;
  padding: 0;
  border: 2px solid var(--color-border);
  border-radius: var(--border-radius-sm);
  cursor: pointer;
  background-color: var(--card-back-color);
  background-image: var(--pattern-image);
  background-size: var(--pattern-size);
}

.card-back-picker__pattern-button--selected {
  border-color: var(--color-foreground);
}

.card-back-picker__colors {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

.card-back-picker__color-button {
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  background: var(--swatch-color);
  border: 2px solid transparent;
  border-radius: 50%;
  cursor: pointer;
}

.card-back-picker__color-button--selected {
  border-color: var(--color-foreground);
}

/* position: relative + a fixed aspect-ratio (rather than sizing purely from
   an inner element's own intrinsic proportions) is what lets .card__face and
   .card__placeholder-ghost below stack directly on top of each other at
   the same rect — every variant needs a real height to stack against, not
   just the two (.card--empty/.card--back) that have no face content of
   their own to fall back on. container-type lets .card__index/__pip's cqw
   units below scale with the card instead of needing a media query per
   size (see .card--sm and the >=768px rule at the bottom of this file). */
.card {
  position: relative;
  display: inline-flex;
  width: 2.75rem;
  aspect-ratio: 500 / 726;
  margin: 0 0.15rem;
  border-radius: 6%;
  background: linear-gradient(135deg, var(--color-white), var(--color-card) 55%, var(--color-paper-shade));
  color: var(--color-card-ink);
  container-type: inline-size;
}

.card--hearts, .card--diamonds { color: var(--color-card-red); }
[data-four-color="true"] .card--diamonds { color: var(--color-card-blue); }
[data-four-color="true"] .card--clubs { color: var(--color-card-green); }

.card--sm {
  width: 2.125rem; /* 34px — another ~15% on top of the previous 25%-larger 1.875rem (30px), landing on an even pixel value */
  margin: 0 -0.5rem 0 0;
}

.card--empty {
  background: none;
  border: 1px dashed var(--color-border);
  border-radius: var(--border-radius-sm);
}

/* Color + pattern both come from the table's house rules (see Table
   #card_back_color/#card_back_pattern_image/-size and tables/_game's
   --card-back-* custom properties) — the fallback values here are only
   ever seen somewhere that variable genuinely isn't set. */
.card--back {
  border: 3px solid var(--color-card);
  border-radius: var(--border-radius-sm);
  background-color: var(--card-back-color, var(--color-card-back-fallback));
  background-image: var(--card-back-pattern-image, none);
  background-size: var(--card-back-pattern-size, auto);
}

/* The thinner inset border that reads as a second, inner frame alongside
   .card--back's own thick outer one -- a plain border (no fill), so it
   never covers .card__back-mark's diamond regardless of paint order. */
.card--back::after {
  content: "";
  position: absolute;
  inset: 4px;
  border: 1px solid var(--color-card);
  border-radius: 2px;
  opacity: 0.7;
}

/* A small diamond suit badge centered on the back pattern (PlayingCardsHelper
   #card_back_mark) -- purely decorative, always spades regardless of the
   table's own card-back color/pattern underneath it. */
.card__back-mark {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 40%;
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  border: 1px solid var(--color-card);
  color: var(--color-card);
  background: var(--card-back-color, var(--color-card-back-fallback));
  transform: translate(-50%, -50%) rotate(45deg);
  box-shadow: 0 0 0 3px var(--color-shadow);
}

.card__back-mark svg {
  width: 65%;
  height: 65%;
  transform: rotate(-45deg);
}

/* Marks which cards in the winning hand (shown in place of the community
   cards once a hand's complete — see tables/_game.html.slim) came from
   that player's own hole cards, as opposed to the shared board. Every
   card in that row gets the same 1px border, transparent unless
   highlighted, so they stay the same size and aligned whether or not
   they're marked. */
.table__community .card {
  border: 1px solid transparent;
  border-radius: 5%;
}

/* translateY on top of the card's own resting --card-rotation (rather than
   replacing it) -- keeps each highlighted card's individual tilt instead of
   snapping it flat, while still lifting it clear of the row so which cards
   actually made the shown hand (as opposed to sitting unused in the board)
   reads at a glance, not just from the border color. */
.table__community .card--highlight {
  border-color: limegreen;
  transform: translateY(-20%) rotate(var(--card-rotation, 0deg));
}

.card__face {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* The community board's "not dealt yet" placeholder (see tables/_card.html.slim)
   has no face content at all -- just a quiet outline, deliberately secondary
   so an empty slot doesn't compete for attention with the actual cards next
   to it. */
.card--placeholder {
  background: var(--color-surface);
  box-shadow: inset 0 1px 3px var(--color-shadow);
  border: 1px solid var(--color-felt-edge);
}

/* Stacked directly behind the incoming real card in the very same slot (see
   tables/_card.html.slim) — invisible by default; card_deal_controller.js
   adds --fading only the first time a real card actually lands there (the
   same one-shot, sessionStorage-guarded moment .card--dealing itself gets
   added), so the placeholder outline doesn't just vanish the instant Turbo
   swaps the placeholder markup out for the real card's, but fades out
   underneath it at the same pace the real card flies in and settles on
   top of it. */
.card__placeholder-ghost {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  background: var(--color-surface);
  box-shadow: inset 0 1px 3px var(--color-shadow);
  border: 1px solid var(--color-felt-edge);
}

.card__placeholder-ghost--fading {
  animation: card-placeholder-fade-out 1s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes card-placeholder-fade-out {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .card__placeholder-ghost--fading {
    animation: none;
  }
}

/* --card-rotation (a few degrees, either direction — see
   Hand#community_card_rotations) is what keeps the dealt community cards
   from lining up perfectly straight with each other, like they were
   actually tossed onto the table rather than placed by a ruler. Applied at
   rest regardless of JS; .card--dealing (below, added once by
   card_deal_controller.js the first time a given card appears) layers the
   spin-in-from-the-corner entrance on top of that same resting angle. */
.card {
  transform: rotate(var(--card-rotation, 0deg));
}

@keyframes card-deal-in {
  from {
    transform: translate(220%, -220%) rotate(75deg) scale(0.7);
    opacity: 0;
  }

  to {
    transform: rotate(var(--card-rotation, 0deg));
    opacity: 1;
  }
}

.card--dealing {
  animation: card-deal-in 1s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@media (prefers-reduced-motion: reduce) {
  .card--dealing {
    animation: none;
  }
}

@media (min-width: 768px) {
  .card {
    width: 3.5rem;
  }
}

/* Face content (PlayingCardsHelper#card_face): rank+suit corner indices,
   plus a center ace glyph / pip layout / court portrait. cqw units below
   scale with .card's own container size (see container-type on .card),
   so .card--sm and the >=768px rule above don't need matching rules here. */
.card__index {
  position: absolute;
  width: 22%;
  display: grid;
  justify-items: center;
  line-height: 1;
  z-index: 1;
}

.card__index--top { top: 5%; left: 3%; }
.card__index--bottom { bottom: 5%; right: 3%; transform: rotate(180deg); }
.card__index b { font: 700 24cqw / 1 var(--font-sans); letter-spacing: -0.09em; }
.card__index svg { width: 12cqw; height: 12cqw; margin-top: 2cqw; }

.card__pip {
  position: absolute;
  width: 19%;
  height: 15%;
  left: var(--pip-x);
  top: var(--pip-y);
  transform: translate(-50%, -50%) rotate(var(--pip-turn));
}

.card__ace {
  position: absolute;
  inset: 31% 23%;
  width: 54%;
  height: 38%;
}

.card__court {
  position: absolute;
  inset: 17% 20%;
  border: 1px solid var(--color-court-blue);
  display: grid;
  place-items: center;
  background: var(--color-card);
}

.card:has(.card__court) .card__index { width: 18%; }
.card:has(.card__court) .card__index b { font-size: 20cqw; }
.card:has(.card__court) .card__index svg { width: 10cqw; height: 10cqw; }

.card__court-art { width: 100%; height: 100%; }
.card__court-blue { fill: var(--color-court-blue); stroke: var(--color-card-ink); }
.card__court-red { fill: var(--color-court-red); stroke: var(--color-card-ink); }
.card__court-gold { fill: var(--color-court-gold); stroke: var(--color-card-ink); }
.card__court-skin { fill: var(--color-court-skin); stroke: var(--color-card-ink); }
.card__court-line { stroke: var(--color-card-ink); }
.card__court-gold-line { stroke: var(--color-court-gold); stroke-width: 1.8; }
/* Injected into .confirm-modal__content (see confirm_modal_controller.js's
   #info) by qr_modal_controller.js#open, cloned from the <template> in
   tables/_footer_chip_requests.html.slim. */
.chip-requests-modal__heading {
  margin: 0 0 0.75rem;
}

.chip-requests {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.chip-request {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.5rem 0.75rem;
  border-radius: var(--border-radius-sm);
  background: var(--color-surface-raised);
}

.chip-request__name {
  font-size: 0.85rem;
}

.chip-request__grant {
  flex-shrink: 0;
  padding: 0.4rem 0.75rem;
  background: var(--color-primary);
  border: none;
  border-radius: var(--border-radius-sm);
  color: var(--color-ink);
  font-weight: 600;
  font-size: 0.8rem;
  cursor: pointer;
}

/* Injected into .confirm-modal__content (see confirm_modal_controller.js's
   #info) by chip_grant_controller.js#open, cloned from the <template> in
   tables/_chip_request.html.slim. */
.chip-grant {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.chip-grant__title {
  margin: 0;
  font-size: 1rem;
}

.chip-grant__form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.chip-grant__slider {
  width: 100%;
  accent-color: var(--color-primary);
}

.chip-grant__amount-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.chip-grant__label {
  flex-shrink: 0;
  font-size: 0.85rem;
  color: var(--color-muted);
}

.chip-grant__input {
  flex: 1;
  min-width: 0;
  padding: 0.4rem 0.6rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius-sm);
  color: var(--color-foreground);
}

.chip-grant__submit {
  width: 100%;
}

/* Poker chip stacks drawn entirely in CSS gradients (TableGraphicsHelper
   #chip_display) -- decorative; the real amount is always adjacent text. */
.chips {
  --chip-size: 23px;
  --chip-step: 2.4px;
  display: inline-flex;
  align-items: flex-end;
  justify-content: center;
  gap: 3px;
  height: 40px;
  min-width: 28px;
  padding: 3px 2px;
  vertical-align: middle;
  pointer-events: none;
}

.chips__stack {
  --chip-color: var(--color-chip-ivory);
  position: relative;
  display: block;
  flex: 0 0 auto;
  width: var(--chip-size);
  height: calc(var(--chip-size) * 0.48 + var(--chips-count) * var(--chip-step));
  border-radius: 50% / 7px;
  background: repeating-linear-gradient(0deg, var(--color-chip-edge) 0 1px, transparent 1px var(--chip-step)),
    linear-gradient(90deg, var(--color-shadow), transparent 30% 65%, var(--color-shadow)), var(--chip-color);
  box-shadow: 0 2px 2px var(--color-deep-shadow);
}

.chips__stack--1 { --chip-color: var(--color-chip-red); }
.chips__stack--2 { --chip-color: var(--color-chip-blue); }
.chips__stack--3 { --chip-color: var(--color-chip-green); }
.chips__stack--4 { --chip-color: var(--color-chip-black); }

.chips__top {
  position: absolute;
  top: -2px;
  left: 0;
  width: 100%;
  height: calc(var(--chip-size) * 0.57);
  border-radius: 50%;
  background: radial-gradient(ellipse, var(--chip-color) 0 37%, var(--color-chip-ivory) 39% 45%, transparent 47%),
    repeating-conic-gradient(var(--chip-color) 0deg 40deg, var(--color-chip-ivory) 40deg 60deg);
  box-shadow: inset 0 1px 1px var(--color-glint), 0 1px 0 var(--color-chip-edge);
}

.chips--small {
  --chip-size: 15px;
  --chip-step: 1.5px;
  height: 25px;
  gap: 1px;
  max-width: 56px;
}

.chips--small .chips__stack:nth-child(n+4) {
  display: none;
}

.chips--pile {
  width: 88px;
  position: relative;
}

.chips--pile .chips__stack {
  position: absolute;
  left: calc(6px + mod(var(--chips-order), 3) * 20px);
  bottom: calc(3px + mod(var(--chips-order), 2) * 7px);
  transform: rotate(var(--chips-lean));
}

.chips--pile .chips__stack:nth-child(n+4) {
  left: calc(13px + (var(--chips-order) - 3) * 19px);
  bottom: 0;
}

/* Two-up radio picker: each option an icon over its label, the checked one
   outlined. Used on tables/new for the game format ("Cards & Chips" vs
   "Just the Cards") and chip display ("US Dollar" vs "Round"). */
.choice-picker {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.25rem;
}

.choice-picker__option {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  padding: 0.75rem 0.5rem;
  border: 2px solid var(--color-border);
  border-radius: var(--border-radius-sm);
  background: var(--color-surface);
  cursor: pointer;
  text-align: center;
}

.choice-picker__option:hover {
  border-color: var(--color-muted);
}

.choice-picker__option:has(.choice-picker__input:checked) {
  border-color: var(--color-header-accent);
  background: var(--color-surface-raised);
}

.choice-picker__option:has(.choice-picker__input:focus-visible) {
  outline: 2px solid var(--color-header-accent);
  outline-offset: 2px;
}

.choice-picker__input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.choice-picker__icon {
  width: 3.5rem;
  height: 3.5rem;
  -o-object-fit: contain;
     object-fit: contain;
}

.choice-picker__label {
  font-weight: 600;
  color: var(--color-foreground);
}

.confirm-modal {
  position: fixed;
  inset: 0;
  z-index: 30;
  display: none;
  align-items: center;
  justify-content: center;
  /* Keeps the dialog off the very top/bottom viewport edge (see its own
     max-height: 100%, resolved against this padded box) -- without it, a
     tall dialog (e.g. the table owner's settings panel) could butt right
     up against the site header above it instead of leaving any breathing
     room. */
  padding: 1.5rem 0;
}

.confirm-modal--open {
  display: flex;
}

.confirm-modal__overlay {
  position: absolute;
  inset: 0;
  background: var(--color-overlay);
}

.confirm-modal__dialog {
  position: relative;
  width: 90%;
  max-width: 320px;
  padding: 1.25rem;
  background: var(--color-background);
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius);
  box-shadow: 0 12px 32px var(--color-shadow);
  max-height: 100%;
  overflow-y: auto;
}

/* Content that needs more room than the usual narrow popup -- see
   confirm_modal_controller.js's #info `wide` option. */
.confirm-modal__dialog--wide {
  max-width: 640px;
}

.confirm-modal__content {
  margin: 0 0 1rem;
}

.confirm-modal__buttons {
  display: flex;
  gap: 0.5rem;
}

/* The --wide content (currently only the table owner's settings panel, see
   tables/_footer_qr.html.slim) supplies its own sticky Save Settings/Close
   footer instead -- see qr-modal.css's .qr-modal__footer. */
.confirm-modal__dialog--wide .confirm-modal__buttons {
  display: none;
}

.confirm-modal__button {
  flex: 1;
}

/* The footer state once a homepage "Try it" demo has played its three
   hands (see tables/_demo_over, Table#demo_over?). Stacked, like
   .bot-game-won -- a prompt, a button, and a line counting down to the
   auto-redirect to sign-up (countdown_controller.js's redirectUrlValue). */
.demo-over {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  text-align: center;
}

.demo-over__heading {
  font-family: var(--font-heading);
  font-size: 1rem;
}

.demo-over__text {
  max-width: 22rem;
  color: var(--color-muted);
  font-size: 0.85rem;
}

.demo-over__button {
  margin-top: 0.1rem;
}

.demo-over__countdown {
  color: var(--color-muted);
  font-size: 0.8rem;
}

/* Fixed width so the count ticking down doesn't jog the sentence. */
.demo-over__seconds {
  display: inline-block;
  min-width: 2ch;
  text-align: center;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.dev-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1rem;
}

.dev-grid__pane {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.dev-grid__label {
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--color-muted);
}

.dev-grid__frame {
  width: 100%;
  height: 700px;
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius-sm);
  background: var(--color-background);
}

/* The "Don't have an account yet?" sign-up callout at the bottom of the
   sign-in page (see devise/shared/_links.html.slim) -- set apart from the
   plain "Forgot your password?" link above it since sign-up is the one
   path most people arriving there actually need. */
.devise-links__signup {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.4rem;
  margin-top: 0.5rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--color-border);
}
/* Ported from ~/Repos/bhamdiy's own FAQ accordion (see
   accordion_controller.js), retimed onto this app's own tokens. */
.faq {
  margin: 0 auto;
}

.faq__list {
  border-top: 1px solid var(--color-border);
}

.faq__item {
  border-bottom: 1px solid var(--color-border);
}

.faq__question {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: 1rem 0;
  background: none;
  border: none;
  cursor: pointer;
  text-align: left;
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--color-foreground);
}

.faq__question:hover {
  color: var(--color-primary);
}

.faq__question:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

.faq__icon {
  font-size: 1.5rem;
  font-weight: 400;
  line-height: 1;
  color: var(--color-primary);
  flex-shrink: 0;
}

.faq__answer {
  padding-top: 0.5rem;
  padding-bottom: 1rem;
}

.faq__answer[aria-hidden="true"] {
  display: none;
}

.faq__answer p {
  margin: 0;
  color: var(--color-muted);
}

.faq__answer p + p {
  margin-top: 0.75em;
}

.flash {
  /* Pulls it up out of .container's own top padding a bit -- that
     padding alone reads as too much empty space above a flash banner
     specifically, without wanting to shrink it for every other page's
     content too. */
  margin: -0.75rem 0 1rem;
  opacity: 1;
  transition: opacity 0.3s ease;
}

/* See flash_controller.js -- faded out before being removed from the DOM. */
.flash--dismissed {
  opacity: 0;
}

.flash__notice,
.flash__alert {
  padding: 0.65rem 0.85rem;
  border-radius: var(--border-radius-sm);
  font-size: 0.9rem;
}

.flash__notice {
  background: color-mix(in oklch, var(--color-primary) 25%, var(--color-surface));
  color: var(--color-foreground);
}

.flash__alert {
  background: color-mix(in oklch, var(--color-danger) 35%, var(--color-surface));
  color: var(--color-foreground);
}

/* Rendered last by layouts/application (see layouts/_footer) -- opposite of
   site-header.css: the ordinary dark background/light foreground the rest
   of the page uses, not site-header's reversed blue. */
.footer {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: 1rem 0.75rem;
  border-top: 1px solid var(--color-border);
  color: var(--color-muted);
  font-size: 0.8rem;
}

/* Two classes' worth of specificity so this reliably overrides
   brand-logo.css's own default 136px width regardless of @import-glob's
   file load order -- a bare .brand-logo rule there is only one class'
   worth otherwise (see site-header.css's own .brand-logo.site-header__logo
   for the same idea, just as a same-element compound selector instead of
   a descendant one). */
.footer .brand-logo {
  width: 90px;
}

.footer__link {
  color: var(--color-muted);
}

.footer__link:hover {
  color: var(--color-primary-text);
}

.form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  max-width: 24rem;
}

/* A standalone narrow form as its own page (see tables/new) -- centred in
   the page's wider .container, the same way the Devise auth pages center
   .auth__form. Wraps the heading too so it aligns with the form column. */
.form-page {
  max-width: 24rem;
  margin: 0 auto;
}

.form__field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.form__input {
  padding: 0.55rem 0.65rem;
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius-sm);
  background: var(--color-surface);
  color: var(--color-foreground);
}

/* .form's own flat gap (above) is enough breathing room when something
   else -- a checkbox row, say -- sits between the last field and the
   submit button (login's own "Remember me"), but reads as too tight when
   the submit button lands directly after a field with nothing between
   them (password reset, confirmation resend, sign up). The adjacent-
   sibling selector is what keeps this scoped to exactly that case --
   login's own submit button follows a .form__checkbox, not a
   .form__field, so it never matches this rule at all. */
.form__field + input[type="submit"] {
  margin-top: 0.5rem;
}

.form__checkbox {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
}

/* A row of short fields side by side (see tables/new's custom blinds). */
.form__row {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

.form__row .form__field {
  flex: 1;
  min-width: 0;
}

/* The public matchmaking waiting room (see game_queue/show.html.slim) and
   its "match found, redirecting" state (game_queue/_matched.html.slim) --
   same block, both states just swap what's inside via #queue-status. */
.game-queue {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  padding: 3rem 1rem;
  text-align: center;
}

.game-queue__spinner {
  width: 2.5rem;
  height: 2.5rem;
  border: 3px solid var(--color-border);
  border-top-color: var(--color-primary);
  border-radius: 50%;
  animation: game-queue-spin 0.8s linear infinite;
}

@keyframes game-queue-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .game-queue__spinner {
    animation: none;
  }
}

.game-queue__count {
  font-size: 1.1rem;
  font-weight: 600;
}

/* Progressive enhancement for the table page. Below 1024px .game-shell is
   invisible (display: contents) so #table lays out exactly as it did
   before this split existed and the mobile experience is untouched. At
   >= 1024px it becomes a two-column grid: the full mobile game view on the
   left (1fr), the read-only felt overview on the right (2fr). See
   tables/_game.html.slim. */

.game-shell {
  display: contents;
}

.game-shell__felt {
  display: none;
}

@media (min-width: 1024px) {
  .game-shell {
    display: grid;
    /* Left (mobile game view) is ~10% wider than a clean third; the felt
       takes the rest. The .action-bar clamp below tracks the same 1.1/3. */
    grid-template-columns: 1.1fr 1.9fr;
    gap: 1.25rem;
    align-items: start;
    /* Restore the side gutters the table page's .container drops on mobile
       (reset.css) -- desktop has the room and wants the columns inset. */
    padding-inline: 1rem;
  }

  /* No felt overview yet (nobody seated) -- the join panel is all there is,
     so give it the whole width and center it, like the Devise auth pages. */
  .game-shell:not(:has(.game-shell__felt)) {
    grid-template-columns: 1fr;
  }

  .game-shell__main {
    min-width: 0;
  }

  /* The seat rail is redundant with the felt overview on the right. */
  .game-shell__main .table__seats {
    display: none;
  }

  .game-shell__felt {
    display: block;
    position: sticky;
    top: 0.5rem;
    /* Clear the .action-bar, now pinned to the bottom of the left column. */
    padding-bottom: 5rem;
  }

  /* The fixed footer belongs to the mobile interface, so on desktop it
     stays confined to the left 1/3 column instead of spanning the page.
     .container is max-width 1200px, centred, with 0.25rem side padding on
     this page; .game-shell adds a 1rem inset (1.25rem total each side) and
     the grid gap is 1.25rem -- so the left column's box is: */
  .game-shell__main .action-bar,
  .game-shell__main .action-bar__chip-requests--floating {
    --shell-left: calc((100vw - min(100vw, 1200px)) / 2 + 1.25rem);
    /* 1.1 / 3 -- matches grid-template-columns: 1.1fr 1.9fr above. */
    --shell-col: calc((min(100vw, 1200px) - 2.5rem - 1.25rem) * 1.1 / 3);
  }

  .game-shell__main .action-bar {
    left: var(--shell-left);
    right: auto;
    width: var(--shell-col);
    border-inline: 1px solid var(--color-border);
  }

  .game-shell__main .action-bar__chip-requests--floating {
    left: calc(var(--shell-left) + var(--shell-col));
    right: auto;
    transform: translateX(-100%);
  }
}

/* ── Grid ───
   Ported from ~/Repos/bhamdiy (a flexbox grid inspired by
   blazeui.com/grid), Slim-flavored views instead of ERB. A page-layout
   grid -- N cells splitting a row by percentage width, with responsive
   breakpoint variants -- not the single-purpose display:grid rules
   elsewhere in this app for a specific fixed shape (e.g. player-info.css's
   own label/value pairs, avatar-picker.css's own icon squares): reach for
   this one first for anything that's actually "split this content into
   columns," and add a bespoke .component__grid only for something this
   one genuinely can't express. Usage: .grid wraps sibling .grid__cell
   elements, each also carrying a .grid__cell--width-N (N one of the
   5/10/.../100 fractions below) -- and optionally a breakpoint-suffixed
   one too (-sm/-md/-lg/-xl) that only takes over at that width, letting a
   cell stack full width on mobile and split further up, e.g.
   `.grid__cell.grid__cell--width-100` with `class="grid__cell--width-50-md"`
   alongside it -- see devise/registrations/edit for a real example. Center
   a .grid that's narrower than its own container with margin: 0 auto on
   whatever wrapper sets that max-width, same as any other block element --
   .grid itself stays full width by default, same as bhamdiy's own. */

.grid {
  --grid-gutter: 1.5rem;
  display: flex;
  flex-wrap: wrap;
  gap: var(--grid-gutter);
}

.grid__cell {
  flex: 1;
  min-width: 0;
}

.grid__cell--width-auto { flex: 1; max-width: none; }

/* Width fractions — calc subtracts each cell's share of the gap so rows sum to 100% */
.grid__cell--width-5   { flex: 0 0 calc(5%       - var(--grid-gutter) * 0.95);    max-width: calc(5%       - var(--grid-gutter) * 0.95); }
.grid__cell--width-10  { flex: 0 0 calc(10%      - var(--grid-gutter) * 0.9);     max-width: calc(10%      - var(--grid-gutter) * 0.9); }
.grid__cell--width-15  { flex: 0 0 calc(15%      - var(--grid-gutter) * 0.85);    max-width: calc(15%      - var(--grid-gutter) * 0.85); }
.grid__cell--width-20  { flex: 0 0 calc(20%      - var(--grid-gutter) * 0.8);     max-width: calc(20%      - var(--grid-gutter) * 0.8); }
.grid__cell--width-25  { flex: 0 0 calc(25%      - var(--grid-gutter) * 0.75);    max-width: calc(25%      - var(--grid-gutter) * 0.75); }
.grid__cell--width-30  { flex: 0 0 calc(30%      - var(--grid-gutter) * 0.7);     max-width: calc(30%      - var(--grid-gutter) * 0.7); }
.grid__cell--width-33  { flex: 0 0 calc(33.333%  - var(--grid-gutter) * 0.66667); max-width: calc(33.333%  - var(--grid-gutter) * 0.66667); }
.grid__cell--width-35  { flex: 0 0 calc(35%      - var(--grid-gutter) * 0.65);    max-width: calc(35%      - var(--grid-gutter) * 0.65); }
.grid__cell--width-40  { flex: 0 0 calc(40%      - var(--grid-gutter) * 0.6);     max-width: calc(40%      - var(--grid-gutter) * 0.6); }
.grid__cell--width-45  { flex: 0 0 calc(45%      - var(--grid-gutter) * 0.55);    max-width: calc(45%      - var(--grid-gutter) * 0.55); }
.grid__cell--width-50  { flex: 0 0 calc(50%      - var(--grid-gutter) * 0.5);     max-width: calc(50%      - var(--grid-gutter) * 0.5); }
.grid__cell--width-55  { flex: 0 0 calc(55%      - var(--grid-gutter) * 0.45);    max-width: calc(55%      - var(--grid-gutter) * 0.45); }
.grid__cell--width-60  { flex: 0 0 calc(60%      - var(--grid-gutter) * 0.4);     max-width: calc(60%      - var(--grid-gutter) * 0.4); }
.grid__cell--width-65  { flex: 0 0 calc(65%      - var(--grid-gutter) * 0.35);    max-width: calc(65%      - var(--grid-gutter) * 0.35); }
.grid__cell--width-66  { flex: 0 0 calc(66.666%  - var(--grid-gutter) * 0.33334); max-width: calc(66.666%  - var(--grid-gutter) * 0.33334); }
.grid__cell--width-70  { flex: 0 0 calc(70%      - var(--grid-gutter) * 0.3);     max-width: calc(70%      - var(--grid-gutter) * 0.3); }
.grid__cell--width-75  { flex: 0 0 calc(75%      - var(--grid-gutter) * 0.25);    max-width: calc(75%      - var(--grid-gutter) * 0.25); }
.grid__cell--width-80  { flex: 0 0 calc(80%      - var(--grid-gutter) * 0.2);     max-width: calc(80%      - var(--grid-gutter) * 0.2); }
.grid__cell--width-85  { flex: 0 0 calc(85%      - var(--grid-gutter) * 0.15);    max-width: calc(85%      - var(--grid-gutter) * 0.15); }
.grid__cell--width-90  { flex: 0 0 calc(90%      - var(--grid-gutter) * 0.1);     max-width: calc(90%      - var(--grid-gutter) * 0.1); }
.grid__cell--width-95  { flex: 0 0 calc(95%      - var(--grid-gutter) * 0.05);    max-width: calc(95%      - var(--grid-gutter) * 0.05); }
.grid__cell--width-100 { flex: 0 0 100%; max-width: 100%; }

/* Offsets */
.grid__cell--offset-5   { margin-left: calc(5%       + var(--grid-gutter) * 0.05); }
.grid__cell--offset-10  { margin-left: calc(10%      + var(--grid-gutter) * 0.1); }
.grid__cell--offset-15  { margin-left: calc(15%      + var(--grid-gutter) * 0.15); }
.grid__cell--offset-20  { margin-left: calc(20%      + var(--grid-gutter) * 0.2); }
.grid__cell--offset-25  { margin-left: calc(25%      + var(--grid-gutter) * 0.25); }
.grid__cell--offset-30  { margin-left: calc(30%      + var(--grid-gutter) * 0.3); }
.grid__cell--offset-33  { margin-left: calc(33.333%  + var(--grid-gutter) * 0.33333); }
.grid__cell--offset-35  { margin-left: calc(35%      + var(--grid-gutter) * 0.35); }
.grid__cell--offset-40  { margin-left: calc(40%      + var(--grid-gutter) * 0.4); }
.grid__cell--offset-45  { margin-left: calc(45%      + var(--grid-gutter) * 0.45); }
.grid__cell--offset-50  { margin-left: calc(50%      + var(--grid-gutter) * 0.5); }
.grid__cell--offset-55  { margin-left: calc(55%      + var(--grid-gutter) * 0.55); }
.grid__cell--offset-60  { margin-left: calc(60%      + var(--grid-gutter) * 0.6); }
.grid__cell--offset-65  { margin-left: calc(65%      + var(--grid-gutter) * 0.65); }
.grid__cell--offset-66  { margin-left: calc(66.666%  + var(--grid-gutter) * 0.66666); }
.grid__cell--offset-70  { margin-left: calc(70%      + var(--grid-gutter) * 0.7); }
.grid__cell--offset-75  { margin-left: calc(75%      + var(--grid-gutter) * 0.75); }
.grid__cell--offset-80  { margin-left: calc(80%      + var(--grid-gutter) * 0.8); }
.grid__cell--offset-85  { margin-left: calc(85%      + var(--grid-gutter) * 0.85); }
.grid__cell--offset-90  { margin-left: calc(90%      + var(--grid-gutter) * 0.9); }
.grid__cell--offset-95  { margin-left: calc(95%      + var(--grid-gutter) * 0.95); }

/* Gutter modifiers */
.grid--no-gutter { --grid-gutter: 0rem; }
.grid__cell--no-gutter { padding-left: 0; padding-right: 0; }

/* Full-width all cells */
.grid--full > .grid__cell { flex: 0 0 100%; max-width: 100%; }

/* Responsive fit/full — forces all cells to 100% at breakpoints */
@media (min-width: 576px) { .grid--sm-full  > .grid__cell { flex: 0 0 100%; max-width: 100%; } }
@media (min-width: 768px) { .grid--md-full  > .grid__cell { flex: 0 0 100%; max-width: 100%; } }
@media (min-width: 1024px) { .grid--lg-full > .grid__cell { flex: 0 0 100%; max-width: 100%; } }
@media (min-width: 1280px) { .grid--xl-full > .grid__cell { flex: 0 0 100%; max-width: 100%; } }

/* Vertical alignment */
.grid--top    { align-items: flex-start; }
.grid--center { align-items: center; }
.grid--bottom { align-items: flex-end; }

.grid__cell--top    { align-self: flex-start; }
.grid__cell--center { align-self: center; }
.grid__cell--bottom { align-self: flex-end; }

/* Horizontal alignment */
.grid--halign-center { justify-content: center; }
.grid--halign-right  { justify-content: flex-end; }

/* Per-cell */
.grid__cell--width-fixed { flex: none; }

/* Responsive: -sm ≥ 576px */
@media (min-width: 576px) {
  .grid__cell--width-5-sm   { flex: 0 0 calc(5%       - var(--grid-gutter) * 0.95);    max-width: calc(5%       - var(--grid-gutter) * 0.95); }
  .grid__cell--width-10-sm  { flex: 0 0 calc(10%      - var(--grid-gutter) * 0.9);     max-width: calc(10%      - var(--grid-gutter) * 0.9); }
  .grid__cell--width-15-sm  { flex: 0 0 calc(15%      - var(--grid-gutter) * 0.85);    max-width: calc(15%      - var(--grid-gutter) * 0.85); }
  .grid__cell--width-20-sm  { flex: 0 0 calc(20%      - var(--grid-gutter) * 0.8);     max-width: calc(20%      - var(--grid-gutter) * 0.8); }
  .grid__cell--width-25-sm  { flex: 0 0 calc(25%      - var(--grid-gutter) * 0.75);    max-width: calc(25%      - var(--grid-gutter) * 0.75); }
  .grid__cell--width-30-sm  { flex: 0 0 calc(30%      - var(--grid-gutter) * 0.7);     max-width: calc(30%      - var(--grid-gutter) * 0.7); }
  .grid__cell--width-33-sm  { flex: 0 0 calc(33.333%  - var(--grid-gutter) * 0.66667); max-width: calc(33.333%  - var(--grid-gutter) * 0.66667); }
  .grid__cell--width-35-sm  { flex: 0 0 calc(35%      - var(--grid-gutter) * 0.65);    max-width: calc(35%      - var(--grid-gutter) * 0.65); }
  .grid__cell--width-40-sm  { flex: 0 0 calc(40%      - var(--grid-gutter) * 0.6);     max-width: calc(40%      - var(--grid-gutter) * 0.6); }
  .grid__cell--width-45-sm  { flex: 0 0 calc(45%      - var(--grid-gutter) * 0.55);    max-width: calc(45%      - var(--grid-gutter) * 0.55); }
  .grid__cell--width-50-sm  { flex: 0 0 calc(50%      - var(--grid-gutter) * 0.5);     max-width: calc(50%      - var(--grid-gutter) * 0.5); }
  .grid__cell--width-55-sm  { flex: 0 0 calc(55%      - var(--grid-gutter) * 0.45);    max-width: calc(55%      - var(--grid-gutter) * 0.45); }
  .grid__cell--width-60-sm  { flex: 0 0 calc(60%      - var(--grid-gutter) * 0.4);     max-width: calc(60%      - var(--grid-gutter) * 0.4); }
  .grid__cell--width-65-sm  { flex: 0 0 calc(65%      - var(--grid-gutter) * 0.35);    max-width: calc(65%      - var(--grid-gutter) * 0.35); }
  .grid__cell--width-66-sm  { flex: 0 0 calc(66.666%  - var(--grid-gutter) * 0.33334); max-width: calc(66.666%  - var(--grid-gutter) * 0.33334); }
  .grid__cell--width-70-sm  { flex: 0 0 calc(70%      - var(--grid-gutter) * 0.3);     max-width: calc(70%      - var(--grid-gutter) * 0.3); }
  .grid__cell--width-75-sm  { flex: 0 0 calc(75%      - var(--grid-gutter) * 0.25);    max-width: calc(75%      - var(--grid-gutter) * 0.25); }
  .grid__cell--width-80-sm  { flex: 0 0 calc(80%      - var(--grid-gutter) * 0.2);     max-width: calc(80%      - var(--grid-gutter) * 0.2); }
  .grid__cell--width-85-sm  { flex: 0 0 calc(85%      - var(--grid-gutter) * 0.15);    max-width: calc(85%      - var(--grid-gutter) * 0.15); }
  .grid__cell--width-90-sm  { flex: 0 0 calc(90%      - var(--grid-gutter) * 0.1);     max-width: calc(90%      - var(--grid-gutter) * 0.1); }
  .grid__cell--width-95-sm  { flex: 0 0 calc(95%      - var(--grid-gutter) * 0.05);    max-width: calc(95%      - var(--grid-gutter) * 0.05); }
  .grid__cell--width-100-sm { flex: 0 0 100%; max-width: 100%; }
  .grid__cell--width-auto-sm { flex: 1; max-width: none; }
  .grid__cell--offset-5-sm   { margin-left: calc(5%       + var(--grid-gutter) * 0.05); }
  .grid__cell--offset-10-sm  { margin-left: calc(10%      + var(--grid-gutter) * 0.1); }
  .grid__cell--offset-15-sm  { margin-left: calc(15%      + var(--grid-gutter) * 0.15); }
  .grid__cell--offset-20-sm  { margin-left: calc(20%      + var(--grid-gutter) * 0.2); }
  .grid__cell--offset-25-sm  { margin-left: calc(25%      + var(--grid-gutter) * 0.25); }
  .grid__cell--offset-30-sm  { margin-left: calc(30%      + var(--grid-gutter) * 0.3); }
  .grid__cell--offset-33-sm  { margin-left: calc(33.333%  + var(--grid-gutter) * 0.33333); }
  .grid__cell--offset-35-sm  { margin-left: calc(35%      + var(--grid-gutter) * 0.35); }
  .grid__cell--offset-40-sm  { margin-left: calc(40%      + var(--grid-gutter) * 0.4); }
  .grid__cell--offset-45-sm  { margin-left: calc(45%      + var(--grid-gutter) * 0.45); }
  .grid__cell--offset-50-sm  { margin-left: calc(50%      + var(--grid-gutter) * 0.5); }
  .grid__cell--offset-55-sm  { margin-left: calc(55%      + var(--grid-gutter) * 0.55); }
  .grid__cell--offset-60-sm  { margin-left: calc(60%      + var(--grid-gutter) * 0.6); }
  .grid__cell--offset-65-sm  { margin-left: calc(65%      + var(--grid-gutter) * 0.65); }
  .grid__cell--offset-66-sm  { margin-left: calc(66.666%  + var(--grid-gutter) * 0.66666); }
  .grid__cell--offset-70-sm  { margin-left: calc(70%      + var(--grid-gutter) * 0.7); }
  .grid__cell--offset-75-sm  { margin-left: calc(75%      + var(--grid-gutter) * 0.75); }
  .grid__cell--offset-80-sm  { margin-left: calc(80%      + var(--grid-gutter) * 0.8); }
  .grid__cell--offset-85-sm  { margin-left: calc(85%      + var(--grid-gutter) * 0.85); }
  .grid__cell--offset-90-sm  { margin-left: calc(90%      + var(--grid-gutter) * 0.9); }
  .grid__cell--offset-95-sm  { margin-left: calc(95%      + var(--grid-gutter) * 0.95); }
}

/* Responsive: -md ≥ 768px */
@media (min-width: 768px) {
  .grid__cell--width-5-md   { flex: 0 0 calc(5%       - var(--grid-gutter) * 0.95);    max-width: calc(5%       - var(--grid-gutter) * 0.95); }
  .grid__cell--width-10-md  { flex: 0 0 calc(10%      - var(--grid-gutter) * 0.9);     max-width: calc(10%      - var(--grid-gutter) * 0.9); }
  .grid__cell--width-15-md  { flex: 0 0 calc(15%      - var(--grid-gutter) * 0.85);    max-width: calc(15%      - var(--grid-gutter) * 0.85); }
  .grid__cell--width-20-md  { flex: 0 0 calc(20%      - var(--grid-gutter) * 0.8);     max-width: calc(20%      - var(--grid-gutter) * 0.8); }
  .grid__cell--width-25-md  { flex: 0 0 calc(25%      - var(--grid-gutter) * 0.75);    max-width: calc(25%      - var(--grid-gutter) * 0.75); }
  .grid__cell--width-30-md  { flex: 0 0 calc(30%      - var(--grid-gutter) * 0.7);     max-width: calc(30%      - var(--grid-gutter) * 0.7); }
  .grid__cell--width-33-md  { flex: 0 0 calc(33.333%  - var(--grid-gutter) * 0.66667); max-width: calc(33.333%  - var(--grid-gutter) * 0.66667); }
  .grid__cell--width-35-md  { flex: 0 0 calc(35%      - var(--grid-gutter) * 0.65);    max-width: calc(35%      - var(--grid-gutter) * 0.65); }
  .grid__cell--width-40-md  { flex: 0 0 calc(40%      - var(--grid-gutter) * 0.6);     max-width: calc(40%      - var(--grid-gutter) * 0.6); }
  .grid__cell--width-45-md  { flex: 0 0 calc(45%      - var(--grid-gutter) * 0.55);    max-width: calc(45%      - var(--grid-gutter) * 0.55); }
  .grid__cell--width-50-md  { flex: 0 0 calc(50%      - var(--grid-gutter) * 0.5);     max-width: calc(50%      - var(--grid-gutter) * 0.5); }
  .grid__cell--width-55-md  { flex: 0 0 calc(55%      - var(--grid-gutter) * 0.45);    max-width: calc(55%      - var(--grid-gutter) * 0.45); }
  .grid__cell--width-60-md  { flex: 0 0 calc(60%      - var(--grid-gutter) * 0.4);     max-width: calc(60%      - var(--grid-gutter) * 0.4); }
  .grid__cell--width-65-md  { flex: 0 0 calc(65%      - var(--grid-gutter) * 0.35);    max-width: calc(65%      - var(--grid-gutter) * 0.35); }
  .grid__cell--width-66-md  { flex: 0 0 calc(66.666%  - var(--grid-gutter) * 0.33334); max-width: calc(66.666%  - var(--grid-gutter) * 0.33334); }
  .grid__cell--width-70-md  { flex: 0 0 calc(70%      - var(--grid-gutter) * 0.3);     max-width: calc(70%      - var(--grid-gutter) * 0.3); }
  .grid__cell--width-75-md  { flex: 0 0 calc(75%      - var(--grid-gutter) * 0.25);    max-width: calc(75%      - var(--grid-gutter) * 0.25); }
  .grid__cell--width-80-md  { flex: 0 0 calc(80%      - var(--grid-gutter) * 0.2);     max-width: calc(80%      - var(--grid-gutter) * 0.2); }
  .grid__cell--width-85-md  { flex: 0 0 calc(85%      - var(--grid-gutter) * 0.15);    max-width: calc(85%      - var(--grid-gutter) * 0.15); }
  .grid__cell--width-90-md  { flex: 0 0 calc(90%      - var(--grid-gutter) * 0.1);     max-width: calc(90%      - var(--grid-gutter) * 0.1); }
  .grid__cell--width-95-md  { flex: 0 0 calc(95%      - var(--grid-gutter) * 0.05);    max-width: calc(95%      - var(--grid-gutter) * 0.05); }
  .grid__cell--width-100-md { flex: 0 0 100%; max-width: 100%; }
  .grid__cell--width-auto-md { flex: 1; max-width: none; }
  .grid__cell--offset-5-md   { margin-left: calc(5%       + var(--grid-gutter) * 0.05); }
  .grid__cell--offset-10-md  { margin-left: calc(10%      + var(--grid-gutter) * 0.1); }
  .grid__cell--offset-15-md  { margin-left: calc(15%      + var(--grid-gutter) * 0.15); }
  .grid__cell--offset-20-md  { margin-left: calc(20%      + var(--grid-gutter) * 0.2); }
  .grid__cell--offset-25-md  { margin-left: calc(25%      + var(--grid-gutter) * 0.25); }
  .grid__cell--offset-30-md  { margin-left: calc(30%      + var(--grid-gutter) * 0.3); }
  .grid__cell--offset-33-md  { margin-left: calc(33.333%  + var(--grid-gutter) * 0.33333); }
  .grid__cell--offset-35-md  { margin-left: calc(35%      + var(--grid-gutter) * 0.35); }
  .grid__cell--offset-40-md  { margin-left: calc(40%      + var(--grid-gutter) * 0.4); }
  .grid__cell--offset-45-md  { margin-left: calc(45%      + var(--grid-gutter) * 0.45); }
  .grid__cell--offset-50-md  { margin-left: calc(50%      + var(--grid-gutter) * 0.5); }
  .grid__cell--offset-55-md  { margin-left: calc(55%      + var(--grid-gutter) * 0.55); }
  .grid__cell--offset-60-md  { margin-left: calc(60%      + var(--grid-gutter) * 0.6); }
  .grid__cell--offset-65-md  { margin-left: calc(65%      + var(--grid-gutter) * 0.65); }
  .grid__cell--offset-66-md  { margin-left: calc(66.666%  + var(--grid-gutter) * 0.66666); }
  .grid__cell--offset-70-md  { margin-left: calc(70%      + var(--grid-gutter) * 0.7); }
  .grid__cell--offset-75-md  { margin-left: calc(75%      + var(--grid-gutter) * 0.75); }
  .grid__cell--offset-80-md  { margin-left: calc(80%      + var(--grid-gutter) * 0.8); }
  .grid__cell--offset-85-md  { margin-left: calc(85%      + var(--grid-gutter) * 0.85); }
  .grid__cell--offset-90-md  { margin-left: calc(90%      + var(--grid-gutter) * 0.9); }
  .grid__cell--offset-95-md  { margin-left: calc(95%      + var(--grid-gutter) * 0.95); }
}

/* Responsive: -lg ≥ 1024px */
@media (min-width: 1024px) {
  .grid__cell--width-5-lg   { flex: 0 0 calc(5%       - var(--grid-gutter) * 0.95);    max-width: calc(5%       - var(--grid-gutter) * 0.95); }
  .grid__cell--width-10-lg  { flex: 0 0 calc(10%      - var(--grid-gutter) * 0.9);     max-width: calc(10%      - var(--grid-gutter) * 0.9); }
  .grid__cell--width-15-lg  { flex: 0 0 calc(15%      - var(--grid-gutter) * 0.85);    max-width: calc(15%      - var(--grid-gutter) * 0.85); }
  .grid__cell--width-20-lg  { flex: 0 0 calc(20%      - var(--grid-gutter) * 0.8);     max-width: calc(20%      - var(--grid-gutter) * 0.8); }
  .grid__cell--width-25-lg  { flex: 0 0 calc(25%      - var(--grid-gutter) * 0.75);    max-width: calc(25%      - var(--grid-gutter) * 0.75); }
  .grid__cell--width-30-lg  { flex: 0 0 calc(30%      - var(--grid-gutter) * 0.7);     max-width: calc(30%      - var(--grid-gutter) * 0.7); }
  .grid__cell--width-33-lg  { flex: 0 0 calc(33.333%  - var(--grid-gutter) * 0.66667); max-width: calc(33.333%  - var(--grid-gutter) * 0.66667); }
  .grid__cell--width-35-lg  { flex: 0 0 calc(35%      - var(--grid-gutter) * 0.65);    max-width: calc(35%      - var(--grid-gutter) * 0.65); }
  .grid__cell--width-40-lg  { flex: 0 0 calc(40%      - var(--grid-gutter) * 0.6);     max-width: calc(40%      - var(--grid-gutter) * 0.6); }
  .grid__cell--width-45-lg  { flex: 0 0 calc(45%      - var(--grid-gutter) * 0.55);    max-width: calc(45%      - var(--grid-gutter) * 0.55); }
  .grid__cell--width-50-lg  { flex: 0 0 calc(50%      - var(--grid-gutter) * 0.5);     max-width: calc(50%      - var(--grid-gutter) * 0.5); }
  .grid__cell--width-55-lg  { flex: 0 0 calc(55%      - var(--grid-gutter) * 0.45);    max-width: calc(55%      - var(--grid-gutter) * 0.45); }
  .grid__cell--width-60-lg  { flex: 0 0 calc(60%      - var(--grid-gutter) * 0.4);     max-width: calc(60%      - var(--grid-gutter) * 0.4); }
  .grid__cell--width-65-lg  { flex: 0 0 calc(65%      - var(--grid-gutter) * 0.35);    max-width: calc(65%      - var(--grid-gutter) * 0.35); }
  .grid__cell--width-66-lg  { flex: 0 0 calc(66.666%  - var(--grid-gutter) * 0.33334); max-width: calc(66.666%  - var(--grid-gutter) * 0.33334); }
  .grid__cell--width-70-lg  { flex: 0 0 calc(70%      - var(--grid-gutter) * 0.3);     max-width: calc(70%      - var(--grid-gutter) * 0.3); }
  .grid__cell--width-75-lg  { flex: 0 0 calc(75%      - var(--grid-gutter) * 0.25);    max-width: calc(75%      - var(--grid-gutter) * 0.25); }
  .grid__cell--width-80-lg  { flex: 0 0 calc(80%      - var(--grid-gutter) * 0.2);     max-width: calc(80%      - var(--grid-gutter) * 0.2); }
  .grid__cell--width-85-lg  { flex: 0 0 calc(85%      - var(--grid-gutter) * 0.15);    max-width: calc(85%      - var(--grid-gutter) * 0.15); }
  .grid__cell--width-90-lg  { flex: 0 0 calc(90%      - var(--grid-gutter) * 0.1);     max-width: calc(90%      - var(--grid-gutter) * 0.1); }
  .grid__cell--width-95-lg  { flex: 0 0 calc(95%      - var(--grid-gutter) * 0.05);    max-width: calc(95%      - var(--grid-gutter) * 0.05); }
  .grid__cell--width-100-lg { flex: 0 0 100%; max-width: 100%; }
  .grid__cell--width-auto-lg { flex: 1; max-width: none; }
  .grid__cell--offset-5-lg   { margin-left: calc(5%       + var(--grid-gutter) * 0.05); }
  .grid__cell--offset-10-lg  { margin-left: calc(10%      + var(--grid-gutter) * 0.1); }
  .grid__cell--offset-15-lg  { margin-left: calc(15%      + var(--grid-gutter) * 0.15); }
  .grid__cell--offset-20-lg  { margin-left: calc(20%      + var(--grid-gutter) * 0.2); }
  .grid__cell--offset-25-lg  { margin-left: calc(25%      + var(--grid-gutter) * 0.25); }
  .grid__cell--offset-30-lg  { margin-left: calc(30%      + var(--grid-gutter) * 0.3); }
  .grid__cell--offset-33-lg  { margin-left: calc(33.333%  + var(--grid-gutter) * 0.33333); }
  .grid__cell--offset-35-lg  { margin-left: calc(35%      + var(--grid-gutter) * 0.35); }
  .grid__cell--offset-40-lg  { margin-left: calc(40%      + var(--grid-gutter) * 0.4); }
  .grid__cell--offset-45-lg  { margin-left: calc(45%      + var(--grid-gutter) * 0.45); }
  .grid__cell--offset-50-lg  { margin-left: calc(50%      + var(--grid-gutter) * 0.5); }
  .grid__cell--offset-55-lg  { margin-left: calc(55%      + var(--grid-gutter) * 0.55); }
  .grid__cell--offset-60-lg  { margin-left: calc(60%      + var(--grid-gutter) * 0.6); }
  .grid__cell--offset-65-lg  { margin-left: calc(65%      + var(--grid-gutter) * 0.65); }
  .grid__cell--offset-66-lg  { margin-left: calc(66.666%  + var(--grid-gutter) * 0.66666); }
  .grid__cell--offset-70-lg  { margin-left: calc(70%      + var(--grid-gutter) * 0.7); }
  .grid__cell--offset-75-lg  { margin-left: calc(75%      + var(--grid-gutter) * 0.75); }
  .grid__cell--offset-80-lg  { margin-left: calc(80%      + var(--grid-gutter) * 0.8); }
  .grid__cell--offset-85-lg  { margin-left: calc(85%      + var(--grid-gutter) * 0.85); }
  .grid__cell--offset-90-lg  { margin-left: calc(90%      + var(--grid-gutter) * 0.9); }
  .grid__cell--offset-95-lg  { margin-left: calc(95%      + var(--grid-gutter) * 0.95); }
}

/* Responsive: -xl ≥ 1280px */
@media (min-width: 1280px) {
  .grid__cell--width-5-xl   { flex: 0 0 calc(5%       - var(--grid-gutter) * 0.95);    max-width: calc(5%       - var(--grid-gutter) * 0.95); }
  .grid__cell--width-10-xl  { flex: 0 0 calc(10%      - var(--grid-gutter) * 0.9);     max-width: calc(10%      - var(--grid-gutter) * 0.9); }
  .grid__cell--width-15-xl  { flex: 0 0 calc(15%      - var(--grid-gutter) * 0.85);    max-width: calc(15%      - var(--grid-gutter) * 0.85); }
  .grid__cell--width-20-xl  { flex: 0 0 calc(20%      - var(--grid-gutter) * 0.8);     max-width: calc(20%      - var(--grid-gutter) * 0.8); }
  .grid__cell--width-25-xl  { flex: 0 0 calc(25%      - var(--grid-gutter) * 0.75);    max-width: calc(25%      - var(--grid-gutter) * 0.75); }
  .grid__cell--width-30-xl  { flex: 0 0 calc(30%      - var(--grid-gutter) * 0.7);     max-width: calc(30%      - var(--grid-gutter) * 0.7); }
  .grid__cell--width-33-xl  { flex: 0 0 calc(33.333%  - var(--grid-gutter) * 0.66667); max-width: calc(33.333%  - var(--grid-gutter) * 0.66667); }
  .grid__cell--width-35-xl  { flex: 0 0 calc(35%      - var(--grid-gutter) * 0.65);    max-width: calc(35%      - var(--grid-gutter) * 0.65); }
  .grid__cell--width-40-xl  { flex: 0 0 calc(40%      - var(--grid-gutter) * 0.6);     max-width: calc(40%      - var(--grid-gutter) * 0.6); }
  .grid__cell--width-45-xl  { flex: 0 0 calc(45%      - var(--grid-gutter) * 0.55);    max-width: calc(45%      - var(--grid-gutter) * 0.55); }
  .grid__cell--width-50-xl  { flex: 0 0 calc(50%      - var(--grid-gutter) * 0.5);     max-width: calc(50%      - var(--grid-gutter) * 0.5); }
  .grid__cell--width-55-xl  { flex: 0 0 calc(55%      - var(--grid-gutter) * 0.45);    max-width: calc(55%      - var(--grid-gutter) * 0.45); }
  .grid__cell--width-60-xl  { flex: 0 0 calc(60%      - var(--grid-gutter) * 0.4);     max-width: calc(60%      - var(--grid-gutter) * 0.4); }
  .grid__cell--width-65-xl  { flex: 0 0 calc(65%      - var(--grid-gutter) * 0.35);    max-width: calc(65%      - var(--grid-gutter) * 0.35); }
  .grid__cell--width-66-xl  { flex: 0 0 calc(66.666%  - var(--grid-gutter) * 0.33334); max-width: calc(66.666%  - var(--grid-gutter) * 0.33334); }
  .grid__cell--width-70-xl  { flex: 0 0 calc(70%      - var(--grid-gutter) * 0.3);     max-width: calc(70%      - var(--grid-gutter) * 0.3); }
  .grid__cell--width-75-xl  { flex: 0 0 calc(75%      - var(--grid-gutter) * 0.25);    max-width: calc(75%      - var(--grid-gutter) * 0.25); }
  .grid__cell--width-80-xl  { flex: 0 0 calc(80%      - var(--grid-gutter) * 0.2);     max-width: calc(80%      - var(--grid-gutter) * 0.2); }
  .grid__cell--width-85-xl  { flex: 0 0 calc(85%      - var(--grid-gutter) * 0.15);    max-width: calc(85%      - var(--grid-gutter) * 0.15); }
  .grid__cell--width-90-xl  { flex: 0 0 calc(90%      - var(--grid-gutter) * 0.1);     max-width: calc(90%      - var(--grid-gutter) * 0.1); }
  .grid__cell--width-95-xl  { flex: 0 0 calc(95%      - var(--grid-gutter) * 0.05);    max-width: calc(95%      - var(--grid-gutter) * 0.05); }
  .grid__cell--width-100-xl { flex: 0 0 100%; max-width: 100%; }
  .grid__cell--width-auto-xl { flex: 1; max-width: none; }
  .grid__cell--offset-5-xl   { margin-left: calc(5%       + var(--grid-gutter) * 0.05); }
  .grid__cell--offset-10-xl  { margin-left: calc(10%      + var(--grid-gutter) * 0.1); }
  .grid__cell--offset-15-xl  { margin-left: calc(15%      + var(--grid-gutter) * 0.15); }
  .grid__cell--offset-20-xl  { margin-left: calc(20%      + var(--grid-gutter) * 0.2); }
  .grid__cell--offset-25-xl  { margin-left: calc(25%      + var(--grid-gutter) * 0.25); }
  .grid__cell--offset-30-xl  { margin-left: calc(30%      + var(--grid-gutter) * 0.3); }
  .grid__cell--offset-33-xl  { margin-left: calc(33.333%  + var(--grid-gutter) * 0.33333); }
  .grid__cell--offset-35-xl  { margin-left: calc(35%      + var(--grid-gutter) * 0.35); }
  .grid__cell--offset-40-xl  { margin-left: calc(40%      + var(--grid-gutter) * 0.4); }
  .grid__cell--offset-45-xl  { margin-left: calc(45%      + var(--grid-gutter) * 0.45); }
  .grid__cell--offset-50-xl  { margin-left: calc(50%      + var(--grid-gutter) * 0.5); }
  .grid__cell--offset-55-xl  { margin-left: calc(55%      + var(--grid-gutter) * 0.55); }
  .grid__cell--offset-60-xl  { margin-left: calc(60%      + var(--grid-gutter) * 0.6); }
  .grid__cell--offset-65-xl  { margin-left: calc(65%      + var(--grid-gutter) * 0.65); }
  .grid__cell--offset-66-xl  { margin-left: calc(66.666%  + var(--grid-gutter) * 0.66666); }
  .grid__cell--offset-70-xl  { margin-left: calc(70%      + var(--grid-gutter) * 0.7); }
  .grid__cell--offset-75-xl  { margin-left: calc(75%      + var(--grid-gutter) * 0.75); }
  .grid__cell--offset-80-xl  { margin-left: calc(80%      + var(--grid-gutter) * 0.8); }
  .grid__cell--offset-85-xl  { margin-left: calc(85%      + var(--grid-gutter) * 0.85); }
  .grid__cell--offset-90-xl  { margin-left: calc(90%      + var(--grid-gutter) * 0.9); }
  .grid__cell--offset-95-xl  { margin-left: calc(95%      + var(--grid-gutter) * 0.95); }
}
.hole-cards {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  /* Sits tight under the community board -- negative margin cancels most
     of .table's own flex gap above it. */
  margin-top: -0.2rem;
  padding: 0 0 0.35rem;
}

.hole-cards__hints {
  display: flex;
  gap: 1rem;
  font-size: 0.8rem;
  /* Real breathing room below the hint text before the cards -- they'd
     otherwise sit almost flush against it (.hole-cards__cards' own
     -1.1rem margin-top already pulls them up toward .hole-cards__best
     right below this, so the default 0.3rem flex gap alone wasn't enough
     on top of that). */
  margin-bottom: 0.6rem;
}

.hole-cards__hint {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  white-space: nowrap;
}

.hole-cards__hint-icon {
  flex-shrink: 0;
}

.hole-cards__hint--peek {
  color: var(--color-positive);
}

.hole-cards__hint--fold {
  color: var(--color-negative);
}

/* A fixed, locked-down 1.3rem, never display:none — the drag gesture below
   toggles --peeking continuously as the cards move, and swapping display
   on/off mid-drag reflowed the cards' own position every time, making the
   drag look jumpy. Opacity alone has no layout effect, but min-height on
   its own still let a longer string ("You won $12.34 with: Straight
   Flush") grow the box past it if that's what the actual content needed
   to render — invisible or not, that's still a real layout change, so it
   still shifted .hole-cards__cards (overlapping this box; see its own
   comment) the instant a drag crossed the peeking threshold and the
   content underneath happened to need more room. height (not min-height)
   plus clipping any overflow instead of wrapping it onto a 2nd line makes
   this box truly fixed-size no matter what text ends up inside it. */
.hole-cards__best {
  height: 1.3rem;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 0.95rem;
  opacity: 0;
  transition: opacity 0.15s ease-out;
}

.hole-cards__best--won {
  color: var(--color-positive);
}

.hole-cards--peeking .hole-cards__best {
  opacity: 1;
}

/* Full-bleed to the viewport edge, capped at 400px — below that width it
   breaks out of .container's + .table's combined 1.75rem side padding (the
   -1.75rem/+3.5rem cancel it out exactly); at/above ~456px viewport (where
   400px content no longer needs the full width) it recenters instead,
   leaving symmetric space. */
.hole-cards__cards {
  position: relative;
  /* Higher than .seat__button-badge/.seat__blind-badge's own z-index: 1
     (see seat.css) — dragging up toward the seat rail (the fold gesture)
     visually overlaps the two, and without this the D/SB/BB badges (being
     the higher of two otherwise-equal-stacking-context elements) would
     paint over the dragged cards instead of the other way around. */
  z-index: 2;
  display: flex;
  justify-content: center;
  /* flex-start, not the default stretch -- the cards must keep their own
     aspect-ratio (500 / 726, matching the PNG fronts), not be pulled to
     the container's height. */
  align-items: flex-start;
  width: calc(100% + 3.5rem);
  max-width: 400px;
  margin-inline: -1.75rem;
  /* Negative, not positive — .hole-cards__best (invisible until peeking;
     see its own comment) sits in normal flow right above this, reserving
     its own box the same way whether shown or not, so on its own it'd
     leave an awkward gap between the drag hints and the cards. Pulling the
     cards up to overlap that reserved box instead (on top of it, not just
     closer to it — paint order alone, no z-index needed, since this is
     later in the DOM and also position: relative) closes that gap, and is
     also exactly the "hidden behind the cards" spot that text's for. */
  margin-top: -1.1rem;
  touch-action: none;
  cursor: grab;
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
  transition: transform 0.15s ease-out;
}

/* Whenever .hole-cards__best is permanently visible (already_shown — see
   tables/_hole_cards, not just a temporary drag-to-peek) — shift the cards
   ~26px further down than their usual overlap (see .hole-cards__cards's
   own comment) so that text gets some real daylight above them instead of
   being almost entirely covered the same way it is mid-hand. */
.hole-cards--revealed .hole-cards__cards {
  margin-top: calc(-1.1rem + 26px);
}

@media (min-width: 456px) {
  .hole-cards__cards {
    width: 100%;
    margin-inline: auto;
  }
}

/* Plays once, the first time a freshly-dealt hand's own cards actually
   render (see hole_cards_controller.js) — the whole pair spins and slides
   in together from above; each card's own individual ±6deg fan (below)
   still applies throughout, layered on top of this shared motion. */
@keyframes hole-cards-deal-in {
  from {
    transform: translateY(-160%) rotate(-15deg);
    opacity: 0;
  }

  to {
    transform: translateY(0) rotate(0deg);
    opacity: 1;
  }
}

.hole-cards__cards--dealing {
  animation: hole-cards-deal-in 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@media (prefers-reduced-motion: reduce) {
  .hole-cards__cards--dealing {
    animation: none;
  }
}

.hole-cards__card {
  position: relative;
  width: 59%;
  aspect-ratio: 500 / 726;
  margin: 0 -12%;
}

.hole-cards__card:first-child {
  transform: rotate(-6deg);
}

.hole-cards__card:last-child {
  transform: rotate(6deg);
}

.hole-cards__face,
.hole-cards__back {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* .hole-cards__face is dual-classed .card (see tables/_hole_cards.html.slim)
   to reuse card.css's face-content styling — the compound selector here
   overrides .card's own display/sizing/margin regardless of stylesheet
   load order, since a bare .hole-cards__face rule and a bare .card rule
   have identical specificity. */
.hole-cards__face.card {
  display: none;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  aspect-ratio: auto;
}

.hole-cards--peeking .hole-cards__face.card {
  display: flex;
}

.hole-cards--peeking .hole-cards__back {
  display: none;
}

/* Color + pattern come from the table's house rules — see .card--back in
   card.css for the same custom properties. A percentage radius (matching
   .table__community .card's own 5%, not --border-radius-sm's fixed
   0.25rem) so the rounding actually scales with the card the way the real
   face PNGs' own baked-in corners do — these are rendered much larger than
   any other card in the app, and a fixed few px reads as barely-rounded at
   that size next to a face card's proportionally-rounded corner. */
.hole-cards__back {
  border: 5px solid var(--color-card);
  border-radius: 4%;
  background-color: var(--card-back-color, var(--color-card-back-fallback));
  background-image: var(--card-back-pattern-image, none);
  background-size: var(--card-back-pattern-size, auto);
}

/* The same thicker-outer/thinner-inner white frame as .card--back
   (card.css), scaled in % instead of px since hole cards render at a much
   bigger, more variable size than a community/seat card ever does. */
.hole-cards__back::after {
  content: "";
  position: absolute;
  inset: 5%;
  border: 1px solid var(--color-card);
  border-radius: 2%;
  opacity: 0.7;
}

/* The diamond frame .card__back-mark draws around a suit icon (card.css) --
   same idea here, just around the avatar disc instead of a suit icon. A
   plain border (no fill), so the avatar itself (a real element painted
   after this pseudo-element, see .hole-cards__back-avatar) always shows
   through its center. */
.hole-cards__back::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 43%;
  aspect-ratio: 1;
  border: 2px solid var(--color-card);
  transform: translate(-50%, -50%) rotate(45deg);
  box-shadow: 0 0 0 3px var(--color-shadow);
}

/* Centered watermark -- rbpkr-logo-opaque-white.svg (opaque white card
   faces, not just thin outlines) reads clearly enough at this size to
   stand out as an actual mark, not just printed texture the way the low-
   opacity card-back patterns themselves do (see card-back-picker.css's own
   --pattern-image swatches) -- deliberately higher opacity than those. */
/* The viewer's own avatar icon on a black disc, in place of a generic
   card back -- same treatment as the seat indicators (seat.css). */
.hole-cards__back-avatar {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 32%;
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: color-mix(in oklch, var(--color-ink) 75%, transparent);
  color: var(--avatar-color);
  box-shadow: 0 0 0 1px var(--color-shadow-ring);
  pointer-events: none;
}

.hole-cards__back-icon {
  width: 82%;
  height: 82%;
}

.hole-cards__fold-form {
  display: none;
}

/* The homepage is one playable-table illustration (the .poker-disc), using
   the same card/chip components the live table page uses -- see
   pages/home.html.slim. Ported from the Astra redesign's welcome.css, with
   the hero type scaled back from its original clamp(3rem, 8.5vw, 136px) --
   dialed in per feedback that it read as too "in your face" at full size. */
.start-page { background: var(--color-menu-background); }
.start-page .container { max-width: none; padding: 0; }
.start-page .footer { justify-content: flex-start; padding: 24px max(20px, calc((100% - 1296px) / 2)); border: 0; gap: 12px; }
.start-page .footer__copyright { margin-right: auto; }
.start-page .footer__link { margin-left: 20px; }
.start-page .flash { max-width: 1296px; margin: 16px auto; }

.welcome__heading { max-width: 1296px; margin: 8px auto 0; padding-inline: 20px; }
.welcome__title { color: var(--color-chip-ivory); font: 700 clamp(2.25rem, 5.5vw, 4.25rem)/1.05 var(--font-heading); letter-spacing: -0.03em; margin: 0; text-wrap: balance; }
.welcome__tagline { color: var(--color-menu-trim); font: 400 clamp(1rem, 1.5vw, 1.3rem)/1.5 var(--font-heading); text-align: right; margin: 0 0 10px; }

.poker-disc { max-width: 1440px; width: calc(100% - 7.5%); margin-inline: auto; }
.poker-disc__stage { position: relative; display: flex; align-items: center; min-height: clamp(430px, 33vw, 525px); perspective: 1600px; touch-action: pan-y; cursor: grab; border-radius: 50% / 46%; -webkit-user-select: none; -moz-user-select: none; user-select: none; }
.poker-disc__stage:active { cursor: grabbing; }
.poker-disc__stage:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 5px; }
.poker-disc__table { position: absolute; inset: 0; transform-style: preserve-3d; transform: rotateX(calc(var(--tilt-x, 0deg) * 0.22)) rotateY(calc(var(--tilt-y, 0deg) * 0.2)); transition: transform 240ms ease-out; pointer-events: none; border-radius: inherit; }
.poker-disc__shadow { position: absolute; inset: 20% 2% 1%; border-radius: 50%; box-shadow: 0 22px 28px var(--color-deep-shadow); transform: translate(var(--shadow-x, 0px), var(--shadow-y, 0px)); transition: transform 240ms ease-out; pointer-events: none; }
.poker-disc__edge { position: absolute; inset: 0; border-radius: inherit; border: 1px solid var(--color-rail-edge); background: var(--texture-felt), linear-gradient(180deg, var(--color-rail-edge), var(--color-rail) 12% 90%, var(--color-menu-background)); box-shadow: inset 0 2px 2px var(--color-glint), 0 6px 0 var(--color-rail), 0 12px 18px var(--color-deep-shadow); }
.poker-disc__felt { position: absolute; inset: 14px 18px 20px; border-radius: inherit; background: var(--texture-felt), radial-gradient(ellipse at 45% 20%, var(--color-felt-light), transparent 65%), radial-gradient(ellipse, transparent 35%, var(--color-felt-shade)), var(--color-felt); box-shadow: inset 0 0 0 1px var(--color-menu-trim), inset 0 4px 14px var(--color-deep-shadow); }
.poker-disc__felt::after { content: ""; position: absolute; inset: 9px; border: 2px solid var(--color-menu-trim); opacity: 0.8; border-radius: inherit; }
.poker-disc__copy { position: relative; width: 42%; margin-inline: auto; padding: 40px 0 84px; text-align: center; cursor: auto; -webkit-user-select: text; -moz-user-select: text; user-select: text; }
.poker-disc__title { color: var(--color-chip-ivory); font: 700 clamp(1.9rem, 3.8vw, 3.25rem)/1.15 var(--font-heading); letter-spacing: -0.02em; margin: 0 0 16px; }
.poker-disc__intro { color: var(--color-foreground); font-size: clamp(0.9375rem, 1.3vw, 1.125rem); line-height: 1.6; margin: 0 auto 25px; max-width: 29rem; text-wrap: pretty; }
.poker-disc__start { display: flex; align-items: center; justify-content: center; gap: 24px; width: -moz-fit-content; width: fit-content; max-width: 100%; margin-inline: auto; min-height: 70px; padding: 12px 30px; border: 2px solid var(--color-action-edge); border-radius: 4px; background: linear-gradient(var(--color-glint), transparent), var(--color-primary); box-shadow: inset 0 1px 0 var(--color-glint), 0 3px 0 var(--color-deep-shadow), 0 8px 16px var(--color-action-shade); color: var(--color-menu-background); font: 700 clamp(1.05rem, 1.9vw, 1.5rem)/1.25 var(--font-heading); text-decoration: none; transition: background-color 140ms, box-shadow 140ms; }
.poker-disc__start svg { flex: none; }
.poker-disc__start:hover { background-color: var(--color-menu-trim); box-shadow: inset 0 1px 0 var(--color-glint), 0 3px 0 var(--color-deep-shadow), 0 10px 20px var(--color-shadow); }
.poker-disc__start:active { box-shadow: inset 0 2px 4px var(--color-action-shade); }
.poker-disc__start:focus-visible { outline: 3px solid var(--color-chip-ivory); outline-offset: 5px; }
.poker-disc__note { margin: 20px 0 0; color: var(--color-menu-trim); font: 400 0.9375rem/1.5 var(--font-heading); }
/* Two co-equal .poker-disc__start buttons (public matchmaking, the #1
   path for a signed-in visitor -- see pages/home's own comment) side by
   side instead of one; wraps to stacked on narrow viewports same as a
   single one already did. */
.poker-disc__primary-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 16px; }

/* Quick Game / Create a Game (signed in) or Sign up (signed out) -- real
   paths, just secondary to the primary action(s) above. Quick Game is a
   button_to (a real POST, see TablesController#quick), so its <form>
   wrapper needs to get out of this row's own flex layout's way. */
.poker-disc__secondary-actions { display: flex; justify-content: center; gap: 20px; margin: 18px 0 0; }
.poker-disc__secondary-actions form { display: contents; }
.poker-disc__secondary-link { color: var(--color-menu-trim); font: 400 0.9375rem var(--font-heading); background: none; border: 0; cursor: pointer; transition: color 140ms; }
.poker-disc__secondary-link:hover { color: var(--color-chip-ivory); }
.poker-disc__cards { position: absolute; top: 24%; left: 9%; width: 16%; display: flex; transform: translateZ(18px) rotate(-9deg); }
.poker-disc__cards .card { --card-width: 60%; width: var(--card-width); flex: 0 0 var(--card-width); border-radius: 7px; transform: rotate(-16deg); box-shadow: 1px 3px 0 var(--color-paper-edge), 0 8px 12px var(--color-shadow); }
.poker-disc__cards .card + .card { margin-left: -20%; transform: translateY(-13%) rotate(-2deg); }
.poker-disc__chips { position: absolute; left: 6.8%; bottom: 32%; transform: translateZ(24px) rotate(-7deg); }
.poker-disc .chips { --chip-size: clamp(35px, 4.3vw, 68px); --chip-step: 3px; height: auto; gap: 8px; }
.poker-disc .chips__stack { height: calc(var(--chip-size) * 0.76 + var(--chips-count) * var(--chip-step)); border-radius: 50% / calc(var(--chip-size) * 0.43); background: repeating-linear-gradient(0deg, var(--color-chip-edge) 0 1px, transparent 1px var(--chip-step)), linear-gradient(90deg, var(--color-shadow), transparent 30% 65%, var(--color-shadow)), linear-gradient(90deg, var(--chip-color) 0 12%, var(--color-chip-ivory) 12% 20%, var(--chip-color) 20% 80%, var(--color-chip-ivory) 80% 88%, var(--chip-color) 88%); }
.poker-disc .chips__top { height: calc(var(--chip-size) * 0.86); background: radial-gradient(ellipse, var(--chip-color) 0 46%, var(--color-chip-ivory) 48% 50%, var(--chip-color) 52% 58%, transparent 60%), repeating-conic-gradient(var(--chip-color) 0deg 45deg, var(--color-chip-ivory) 45deg 60deg); box-shadow: inset 0 2px 2px var(--color-glint), 0 1px 0 var(--color-chip-edge); }
.poker-disc .chips__stack--2, .poker-disc__loose .chips__stack { --chip-color: var(--color-menu-chip-teal); }
.poker-disc__chips .chips__stack--0 { --chip-color: var(--color-chip-black); }
.poker-disc__chips .chips__stack:nth-child(2) { transform: translateY(22%); }
.poker-disc__chips .chips__stack:nth-child(3) { transform: translateY(38%) scale(0.85); }
.poker-disc__dealer { position: absolute; right: 17%; top: 16%; display: grid; place-items: center; width: 4.8%; aspect-ratio: 1; border: 3px double var(--color-brass); background: var(--color-dealer-button); color: var(--color-card-ink); border-radius: 50%; font: 700 clamp(16px, 2vw, 32px) var(--font-sans); box-shadow: 0 4px 0 var(--color-paper-edge), 0 7px 10px var(--color-shadow); transform: translateZ(18px) rotate(8deg); }
.poker-disc__pot { position: absolute; right: 5%; top: 22%; display: flex; align-items: flex-end; gap: 4px; transform: translateZ(24px) rotate(12deg); }
.poker-disc__pot > .chips + .chips { transform: translateY(25%); }
.poker-disc__phone { position: absolute; right: 12%; top: 37%; width: 12%; aspect-ratio: 0.76; padding: 5px; border: 2px solid var(--color-rail-edge); border-radius: 12% / 9%; background: var(--color-ink); box-shadow: inset 0 0 0 1px var(--color-glint), 0 4px 0 var(--color-ink), 0 10px 14px var(--color-deep-shadow); transform: translateZ(20px) rotate(17deg); }
.poker-disc__phone::after { content: ""; position: absolute; top: 4px; left: 29%; width: 42%; height: 5%; border-radius: 0 0 9px 9px; background: var(--color-ink); }
.poker-disc__screen { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8%; height: 100%; padding: 14% 8% 10%; border-radius: 9% / 7%; background: radial-gradient(ellipse at top, var(--color-felt), var(--color-menu-background)); }
.poker-disc__phone-title, .poker-disc__phone-note { color: var(--color-chip-ivory); font-size: clamp(8px, 0.9vw, 14px); line-height: 1.25; }
.poker-disc__phone-cards { display: flex; justify-content: center; gap: 6%; width: 100%; }
.poker-disc__phone-cards .card { --card-width: 47%; border-radius: 3px; }
.poker-disc__loose { position: absolute; right: 6%; bottom: 20%; transform: translateZ(26px) rotate(-12deg); }
.poker-disc__controls { text-align: center; padding: 4px 0; min-height: 24px; }
.poker-disc__hint { color: var(--color-muted); font-size: 0.6875rem; line-height: 1.5; margin: 0; opacity: 0; }
.poker-disc:hover .poker-disc__hint, .poker-disc:focus-within .poker-disc__hint { opacity: 1; }
.poker-disc__motion { background: transparent; border: 1px solid var(--color-border); border-radius: 6px; color: var(--color-primary); font: inherit; font-size: 0.8125rem; min-height: 44px; padding: 8px 12px; margin-top: 8px; cursor: pointer; }
.poker-disc__motion:hover { border-color: var(--color-primary); background: var(--color-surface); }
.poker-disc__motion:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }
.poker-disc__motion:disabled { opacity: 0.6; cursor: wait; }
.poker-disc__status { color: var(--color-muted); font-size: 0.75rem; margin: 8px 0 0; }
.poker-disc__status:empty, .poker-disc [hidden] { display: none; }
.poker-disc[data-still="true"] .poker-disc__stage { cursor: default; }
.poker-disc[data-still="true"] .poker-disc__table, .poker-disc[data-still="true"] .poker-disc__shadow { transition: none; transform: none; }

.welcome__actions { background: var(--color-chip-ivory); color: var(--color-menu-background); }
.welcome__strip { --grid-gutter: 3rem; align-items: center; max-width: 1296px; margin-inline: auto; padding: 23px 20px; }
.welcome__practice-link { display: inline-block; min-height: 44px; padding: 0; border: 0; background: transparent; color: inherit; cursor: pointer; font: 700 1.375rem/1.25 var(--font-heading); text-align: left; transition: color 140ms; }
.welcome__practice-note { margin: 0; font-size: 0.9375rem; line-height: 1.6; }
.welcome__return-cell { border-left: 1px solid var(--color-menu-trim); align-self: stretch; display: flex; align-items: center; justify-content: center; padding-left: 24px; }
.welcome__return { display: inline-flex; align-items: center; min-height: 44px; color: inherit; font: 400 1.2rem/1.4 var(--font-heading); transition: color 140ms; }
.welcome__practice-link:hover, .welcome__return:hover { color: var(--color-primary-hover); }
.welcome__practice-link:focus-visible, .welcome__return:focus-visible { outline: 2px solid var(--color-menu-background); outline-offset: 5px; }

.welcome-leaders { max-width: 1296px; margin: 32px auto; padding-inline: 20px; }

/* Rails-only stat tiles + leaderboards (v2 didn't have these) -- kept from
   the original home.css, restyled only enough to sit under the new
   .poker-disc hero (max-width/padding matching .welcome-leaders); every
   color here is still a token, so it already picks up the new palette. */
.home-stats {
  max-width: 1296px;
  margin: 0 auto 2rem;
  padding-inline: 20px;
}

.home-stats__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.15rem;
  height: 100%;
  padding: 1rem 0.5rem;
  background: var(--color-surface);
  border-radius: var(--border-radius-sm);
}

.home-stats__value {
  font-family: var(--font-heading);
  font-size: 1.75rem;
  color: var(--color-primary-text);
}

.home-stats__label {
  color: var(--color-muted);
  font-size: 0.75rem;
}

.home-leaderboards {
  max-width: 1296px;
  margin: 0 auto;
  padding-inline: 20px;
}

.home-leaderboard {
  max-width: 32rem;
  margin: 0 auto;
}

.home-leaderboard__empty {
  color: var(--color-muted);
  font-size: 0.85rem;
  text-align: center;
}

.home-leaderboard__heading {
  margin: 0 0 0.75rem;
  font-size: 1.1rem;
  text-align: center;
}

.home-leaderboard__list {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.home-leaderboard__row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.75rem;
  background: var(--color-surface);
  border-radius: var(--border-radius-sm);
}

.home-leaderboard__rank {
  flex-shrink: 0;
  width: 1.25rem;
  color: var(--color-muted);
  font-size: 0.8rem;
  text-align: right;
}

.home-leaderboard__name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.home-leaderboard__wins {
  flex-shrink: 0;
  color: var(--color-primary-text);
  font-weight: 600;
  font-size: 0.9rem;
}

@media (max-width: 1023px) {
  .poker-disc { width: calc(100% - 32px); }
  .poker-disc__copy { width: 44%; padding-block: 50px; }
  .poker-disc__title { font-size: clamp(1.7rem, 4vw, 2.5rem); }
  .poker-disc__start { padding: 12px 18px; gap: 12px; min-height: 60px; }
  .poker-disc__phone { right: 9%; width: 15%; top: 43%; }
  .poker-disc__pot { right: 6%; }
  .poker-disc__cards { left: 7%; width: 18%; top: 31%; }
  .poker-disc__chips { left: 5%; }
  .poker-disc__note { font-size: 0.8125rem; }
}

@media (max-width: 767px) {
  .start-page .footer { padding: 22px 20px; }
  .start-page .footer__link { margin-left: 0; }
  .welcome__heading { margin-top: 22px; padding-inline: 24px; }
  .welcome__title { font-size: clamp(1.9rem, 7vw, 3rem); line-height: 1; text-align: center; max-width: 580px; margin-inline: auto; }
  .welcome__tagline { text-align: center; margin: 18px auto 24px; max-width: 24rem; font-size: 1rem; }
  .poker-disc { width: calc(100% - 24px); }
  .poker-disc__stage { min-height: 590px; border-radius: 46% / 14%; }
  .poker-disc__felt { inset: 10px 9px 16px; }
  .poker-disc__felt::after { inset: 6px; border-width: 1px; }
  .poker-disc__copy { width: calc(100% - 52px); padding: 46px 0 clamp(275px, calc(25vw + 160px), 360px); }
  .poker-disc__title { font-size: clamp(1.75rem, 6.5vw, 2.6rem); margin-bottom: 15px; overflow-wrap: anywhere; }
  .poker-disc__intro { font-size: 0.9375rem; line-height: 1.6; margin-bottom: 22px; }
  .poker-disc__start { width: 100%; gap: 10px; font-size: 1.15rem; min-height: 60px; padding-inline: 12px; }
  .poker-disc__start svg { width: 20px; }
  .poker-disc__note { font-size: 0.8125rem; margin: 18px auto 0; max-width: 16rem; }
  .poker-disc__cards { top: auto; bottom: 125px; left: 13%; width: 29%; max-width: 160px; }
  .poker-disc .chips { --chip-size: clamp(24px, 7vw, 38px); --chip-step: 1.8px; gap: 4px; }
  .poker-disc__chips { left: 11%; bottom: 112px; }
  .poker-disc__phone { top: auto; bottom: 92px; right: 17%; width: 25%; max-width: 130px; }
  .poker-disc__screen { gap: 8%; }
  .poker-disc__phone-title, .poker-disc__phone-note { font-size: clamp(7px, 1.8vw, 12px); }
  .poker-disc__dealer { top: auto; bottom: 227px; right: 38%; width: 8%; font-size: 14px; }
  .poker-disc__pot { top: auto; bottom: clamp(218px, calc(195px + 6vw), 244px); right: 10%; }
  .poker-disc__loose { right: 9%; bottom: 94px; }
  .poker-disc__controls { padding-block: 10px 15px; }
  .poker-disc__hint { opacity: 1; font-size: 0.625rem; }
  .welcome__strip { --grid-gutter: 16px; padding: 22px 24px; }
  .welcome__practice-link { font-size: 1.25rem; }
  .welcome__practice-note { font-size: 0.875rem; }
  .welcome__return-cell { border-left: 0; border-top: 1px solid var(--color-menu-trim); padding: 12px 0 0; justify-content: flex-start; }
  .welcome__return { font-size: 1.1rem; }
}

@media (prefers-reduced-motion: reduce) {
  .poker-disc__table, .poker-disc__shadow, .poker-disc__start { transition: none; }
}

/* The shared "next hand in Ns…" countdown on a public sit-n-go between
   hands (see tables/_public_next_hand_countdown, NextHandJob) -- there's
   no "Next hand" button there, the deal is automatic. */
.next-hand-countdown {
  display: flex;
  justify-content: center;
}

/* Fixed width (fits "10") so the digit ticking down doesn't jog the
   surrounding sentence, monospace to read as a clock. */
.next-hand-countdown__seconds {
  display: inline-block;
  min-width: 2ch;
  text-align: center;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--color-foreground);
}
.player-info {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  text-align: center;
}

.player-info__name {
  margin: 0;
  font-size: 1.375rem; /* 1.1rem, 25% larger */
  font-weight: bold;
}

.player-info__stats {
  display: grid;
  grid-template-columns: auto auto;
  gap: 0.25rem 0.75rem;
  margin: 0.5rem 0 0;
}

.player-info__stat-label {
  /* reset.css's own :where(dt) rule adds margin-top to every dt after the
     first (mimicking a plain definition list's spacing) -- that's exactly
     wrong inside this grid, where each dt sits beside its dd in the same
     row: it pushed every label but the first down out of line with its
     paired value. */
  margin: 0;
  color: var(--color-muted);
  text-align: right;
}

.player-info__stat-value {
  margin: 0;
  text-align: left;
  font-weight: 600;
}

/* Desktop-only read-only overview of the felt (tables/_poker_table),
   ported from ~/Repos/rbpkr's css/table.css. A tall stadium so it fills
   the vertical space of the 2/3 column; seats are absolutely positioned
   around its ellipse (--seat-left / --seat-top computed server-side). */

.poker-table {
  position: relative;
  width: 100%;
  max-width: 46rem;
  /* Wide oval -- fills the column and keeps the negative space above and
     below the seat ring to a minimum rather than reading as a tall egg. */
  aspect-ratio: 1.32 / 1;
  min-height: 360px;
  max-height: calc(100vh - 7rem);
  margin: 1.5rem auto 0;
  border-radius: 50%;
  background: linear-gradient(180deg, var(--color-surface-raised), var(--color-background));
  border: 2px solid var(--color-border);
  box-shadow: 0 4px 24px var(--color-shadow);
}

.poker-table__felt {
  position: absolute;
  inset: 11px;
  border-radius: 50%;
  /* A deep, desaturated take on the table's own card-back colour -- a bit
     more of it in the centre, fading to near-black at the rail. Still dark
     enough for the seat pods and cards to read over. The trim line picks
     up the same colour, brighter. */
  --felt-tint: var(--card-back-color, var(--color-card-back-fallback));
  border: 2px solid color-mix(in oklch, var(--felt-tint) 62%, var(--color-border));
  background: radial-gradient(
    ellipse at 50% 46%,
    color-mix(in oklch, var(--felt-tint) 42%, black),
    color-mix(in oklch, var(--felt-tint) 16%, black)
  );
  box-shadow: inset 0 0 90px var(--color-shadow);
}

/* Faint RbPkr wordmark washed across the felt behind everything. */
.poker-table__watermark {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 70%;
  opacity: 0.05;
  pointer-events: none;
  z-index: 1;
}

/* --- center: board + pot + phase --- */

.poker-table__center {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  width: 72%;
}

.poker-table__board {
  display: flex;
  gap: 0.3rem;
  justify-content: center;
}

.poker-table__board .card {
  width: 3rem;
}

.poker-table__board .card--placeholder {
  border: 1px solid var(--color-border);
  border-radius: 0.35rem;
  background: color-mix(in oklch, var(--color-felt-edge) 70%, black);
}

.poker-table__pot {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  color: var(--color-accent);
  font-weight: 700;
  font-size: 0.95rem;
  text-shadow: 0 1px 3px var(--color-shadow);
}

.poker-table__pot-icon {
  opacity: 0.8;
}

.poker-table__phase {
  font-family: var(--font-heading);
  color: var(--color-muted);
  font-size: 0.95rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* --- seats around the rail --- */

/* Positioning only. The pod is the shared .seat / .seat__body (see
   tables/_seat, seat.css) in its fixed-width felt variant
   (.seat__body--table); state (folded / acting / hero) rides along on
   that. */
.poker-table__seat {
  position: absolute;
  left: var(--seat-left);
  top: var(--seat-top);
  transform: translate(-50%, -50%);
  z-index: 3;
  transition: opacity 0.2s ease;
}

.poker-table__seat .seat {
  width: auto;
}

/* Injected into .confirm-modal__content (see confirm_modal_controller.js's
   #info) by qr_modal_controller.js#open, cloned from the <template> in
   tables/_footer_qr.html.slim. */
.qr-modal {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
}

.qr-modal__code {
  width: 100%;
  max-width: 220px;
  padding: 0.6rem;
  background: var(--color-white);
  border-radius: var(--border-radius-sm);
}

.qr-modal__code-svg {
  display: block;
  width: 100%;
  height: auto;
}

.qr-modal__code-heading {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.qr-modal__code-text {
  font-family: var(--font-mono);
  letter-spacing: 0.05em;
}

.qr-modal__copy {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  padding: 0.35rem;
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius-sm);
  color: var(--color-foreground);
  cursor: pointer;
}

.qr-modal__copy--copied {
  border-color: var(--color-primary);
  color: var(--color-primary);
}

.qr-modal__copy-icon--copied {
  display: none;
}

.qr-modal__copy--copied .qr-modal__copy-icon--default {
  display: none;
}

.qr-modal__copy--copied .qr-modal__copy-icon--copied {
  display: block;
}

.qr-modal__settings {
  width: 100%;
  padding-top: 0.75rem;
  border-top: 1px solid var(--color-border);
}

.qr-modal__settings .form {
  margin-top: 0.75rem;
}

.qr-modal__seat-list {
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
}

.qr-modal__seat-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0 0.35rem 0 0.55rem;
}

.qr-modal__seat-name {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  flex: 1;
  min-width: 0;
  font-size: 0.7rem;
}

/* Owner's inline stack editor for one seat (see tables/_footer_qr) --
   submits with the table settings form. */
.qr-modal__seat-chips {
  width: 4.75rem;
  padding: 0.15rem 0.35rem;
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius-sm);
  background: var(--color-surface-raised);
  color: var(--color-foreground);
  font-size: 0.7rem;
  font-variant-numeric: tabular-nums;
}

.qr-modal__seat-chips-static {
  color: var(--color-muted);
  font-size: 0.7rem;
  font-variant-numeric: tabular-nums;
}

.qr-modal__seat-bot-icon {
  display: inline-flex;
  color: var(--color-muted);
}

.qr-modal__add-bot {
  width: 100%;
  margin-bottom: 0.5rem;
}

/* One row per seat: username, then icon buttons (see
   tables/_footer_qr.html.slim's own comment on why icons over labels). */
.qr-modal__seat-actions {
  display: flex;
  align-items: center;
  gap: 0.1rem;
}

.qr-modal__seat-icon-form {
  display: block;
}

.qr-modal__seat-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: var(--border-radius-sm);
  color: var(--color-muted);
}

.qr-modal__seat-icon--dealer {
  color: var(--color-primary);
}

.qr-modal__seat-icon--button {
  cursor: pointer;
}

.qr-modal__seat-icon--button:hover {
  background: var(--color-surface);
  color: var(--color-primary);
}

.qr-modal__seat-icon--danger:hover {
  color: var(--color-danger);
}

.qr-modal__seat-remove {
  position: relative;
}

/* Anchored over the row so confirming/canceling doesn't reflow anything
   else in the settings list (see remove_confirm_controller.js). */
.qr-modal__seat-confirm {
  position: absolute;
  top: 50%;
  right: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.3rem 0.5rem;
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius-sm);
  transform: translateY(-50%);
  white-space: nowrap;
}

.qr-modal__seat-confirm-label {
  font-size: 0.8rem;
  color: var(--color-muted);
}

.qr-modal__seat-confirm-button {
  padding: 0.2rem 0.55rem;
  font-size: 0.8rem;
}

/* Sticky to the bottom of the scrolling dialog (see confirm-modal.css's
   .confirm-modal__dialog) so Save Settings/Close stay reachable no matter
   how far the form above has scrolled -- takes over from the shared
   modal's own generic Close button, hidden for this --wide content by that
   same stylesheet. */
.qr-modal__footer {
  position: sticky;
  bottom: -1.25rem;
  z-index: 2;
  display: flex;
  gap: 0.5rem;
  width: 100%;
  margin: 0.75rem -1.25rem -1.25rem;
  padding: 0.75rem 1.25rem;
  background: var(--color-background);
  border-top: 1px solid var(--color-border);
}

.qr-modal__footer-button {
  flex: 1;
}

/* Locked (see invite_required_controller.js) hides this footer's own Close
   button too, same as confirm-modal.css hides the shared modal's generic
   one -- Save Settings stays available; there's just no way to dismiss the
   "you need more players" popup out from under it. */
.confirm-modal--locked .qr-modal__footer-button[data-action*="qr-modal#close"] {
  display: none;
}
/* Injected into .confirm-modal__content (see confirm_modal_controller.js's
   #info) by qr_modal_controller.js#open, cloned from the <template> in
   tables/_footer_results.html.slim. */
.results-modal {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.results-modal__heading {
  margin: 0;
}

.results-modal__board {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}

.results-modal__seats {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.results-modal__seat {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.4rem 0.5rem;
  background: var(--color-surface-raised);
  border-radius: var(--border-radius-sm);
}

.results-modal__seat--winner {
  outline: 1px solid var(--color-primary);
}

.results-modal__seat-name {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  font-weight: 600;
}

.results-modal__seat-trophy {
  color: var(--color-primary);
}

.results-modal__seat-cards {
  display: flex;
  gap: 0.3rem;
}

.results-modal__seat-status {
  color: var(--color-muted);
  font-size: 0.85rem;
}

.results-modal__seat-stack {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
}

.results-modal__card {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.6rem;
  padding: 0.15rem 0.3rem;
  background: var(--color-white);
  border-radius: var(--border-radius-sm);
  color: var(--color-ink);
  font-weight: 700;
  font-size: 0.85rem;
}

.results-modal__card--red {
  color: var(--color-danger);
}

.results-modal__pot {
  margin: 0;
  display: flex;
  gap: 0.35rem;
}

/* The compact "who's at the table" rail on mobile (tables/_seat, laid out
   by .table__seats in table.css). Shares the avatar-pod look of the
   desktop felt overview (.poker-table__seat, poker-table.css) -- avatar +
   D/SB/BB badge, name, stack, then a bet chip or a "Fold" marker. Cards
   only appear once a seat is actually revealed at showdown. The rail
   itself is hidden >= 1024px, where the felt takes over (game-shell.css). */

.seat {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Fixed width in the single-row flex fallback (2-5 players). In the
     6+ two-row grid the cell is an even 1fr and .seat stretches to it --
     see .table__seats--grid in table.css -- so every indicator is exactly
     the same size either way. */
  width: 5rem;
  cursor: pointer;
}

.seat__body {
  /* Outer corners barely rounder than the seam corners between the two
     halves (--border-radius-sm), so the pod reads as two cards, not one
     pill. */
  --seat-pod-radius: 0.3rem;
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.08rem;
  width: 100%;
  /* Fixed footprint so an avatar pod and a shown-cards pod are the same
     size and rows don't stretch unevenly. */
  min-height: 3.3rem;
  padding: 0.3rem 0.3rem 0.25rem;
  border-radius: var(--seat-pod-radius);
  color: var(--color-ink);
}

.seat__body--folded {
  opacity: 0.45;
}

/* Took a seat mid-hand -- shown on the felt but dimmed until the next hand
   deals them in (see tables/_poker_table). */
.seat__body--pending {
  opacity: 0.4;
}

/* Felt-overview variant (see tables/_poker_table): a fixed width, since it
   isn't laid out by the rail's flex/grid -- everything else about the pod
   is identical to the mobile rail. */
.seat__body--table {
  width: 5.25rem;
}

/* The mini-cards' own border needs its own --you/--acting override to
   match -- see .seat__mini-card below. */
.seat__body--you .seat__mini-card {
  border-color: var(--color-header-accent);
}

.seat__body--acting .seat__mini-card {
  border-color: var(--color-acting-glow);
}

/* Cards shown: the card backs give way to the real face-up cards,
   name/stack go white and flush to the bottom. */
.seat__body.seat__body--shown {
  background: var(--color-background);
  padding-bottom: 0;
}

.seat__body--shown .seat__name,
.seat__body--shown .seat__stack {
  color: var(--color-foreground);
}

/* Whose turn it is reads off the name itself now, not a blinking border
   around the whole pod -- the mini-cards keep only the static green
   border above (matching --you's own, just a different color), and the
   name pulses between that same green and its usual color. Sibling
   selector, not a descendant one -- .seat__info--below (and its
   .seat__name--below) no longer live inside .seat__body, see tables/_seat. */
@keyframes seat-name-acting-pulse {
  0%, 100% {
    color: var(--color-foreground);
  }
  50% {
    color: var(--color-acting-glow);
  }
}

.seat__body--acting ~ .seat__info--below .seat__name--below {
  animation: seat-name-acting-pulse 1.6s ease-in-out infinite;
}

/* Black disc behind the avatar icon so its bright stroke reads on the
   card-back colour. .avatar is a shared class -- only touch it here. */
.seat__mini-card-avatar .avatar {
  padding: 0.22rem;
  border-radius: 50%;
  background: var(--color-ink);
  box-shadow: 0 0 0 1px var(--color-shadow-ring);
}

/* Mobile rail: two small fanned card backs instead of one bordered box --
   mirrors .hole-cards__cards/__card/__back (hole-cards.css) at a much
   smaller scale, same idea as the viewer's own hole cards at the bottom
   of the page. Each carries its own diamond frame + this seat's avatar
   (both cards show it, not just one -- same as the real hole cards, where
   both happen to be the same player's). */
.seat__mini-cards {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}

.seat__mini-card {
  position: relative;
  width: 46%;
  aspect-ratio: 500 / 726;
  border: 2px solid var(--color-card);
  border-radius: 8%;
  background-color: var(--card-back-color, var(--color-card-back-fallback));
  background-image: var(--card-back-pattern-image, none);
  background-size: 4px 4px;
  margin: 0 -12%;
}

.seat__mini-card:first-child {
  transform: rotate(-6deg);
}

.seat__mini-card:last-child {
  transform: rotate(6deg);
}

.seat__mini-card::after {
  content: "";
  position: absolute;
  inset: 8%;
  border: 1px solid var(--color-card);
  border-radius: 4%;
  opacity: 0.7;
}

.seat__mini-card::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 55%;
  aspect-ratio: 1;
  border: 1.5px solid var(--color-card);
  transform: translate(-50%, -50%) rotate(45deg);
  box-shadow: 0 0 0 1.5px var(--color-shadow);
}

.seat__mini-card-avatar {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 1;
  display: flex;
  transform: translate(-50%, -50%);
}

/* The two face-up hole cards, fanned, lifted so they break just above the
   top edge of the pod. */
.seat__cards {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: -0.3rem;
}

.seat__cards .card--sm {
  width: 2.37rem;
  margin: 0 -0.78rem 0 0;
  transform-origin: bottom center;
}

.seat__cards .card--sm:first-child {
  transform: rotate(-8deg);
}

.seat__cards .card--sm:last-child {
  margin-right: 0;
  transform: rotate(8deg);
}

.seat__button-badge,
.seat__blind-badge {
  position: absolute;
  top: -0.35rem;
  right: -0.5rem;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 1.15rem;
  height: 1.15rem;
  padding: 0 0.22rem;
  border-radius: 999px;
  color: var(--color-ink);
  font-weight: 700;
  box-shadow: 0 0 0 1px var(--color-shadow-ring), 0 1px 3px var(--color-shadow);
}

.seat__button-badge {
  background: var(--color-dealer-button);
  font-size: 0.68rem;
}

.seat__blind-badge {
  font-size: 0.58rem;
  color: var(--color-white);
}

.seat__blind-badge--sb {
  background: var(--color-small-blind);
}

.seat__blind-badge--bb {
  background: var(--color-big-blind);
}

.seat__info {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 0;
  max-width: 100%;
  line-height: 1.05;
}

.seat__name {
  max-width: 100%;
  font-size: 0.64rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.seat__stack {
  font-size: 0.68rem;
  font-weight: 800;
  color: var(--color-ink);
}

/* .seat__info no longer shares the pod with it (see .seat__info--below
   below) -- .seat__extra is left as the pod's only real flow child, which
   would otherwise float to the pod's centre with nothing to anchor it.
   Anchored to the pod's own bottom edge instead (absolute, out of flow),
   clear of the mini-cards/avatar above it, same as the D/SB/BB badge
   already anchors to the top corner. */
.seat__extra {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0.2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 1.05rem;
  overflow: hidden;
}

/* Name/stack live below the pod, in white, now that both the mobile rail
   (its own swipeable panel, see table-panels.css) and the felt overview
   have real room for them instead of squeezing them into the pod itself. */
.seat__info--below {
  margin-top: 0.3rem;
  text-align: center;
}

.seat__name--below,
.seat__stack--below {
  color: var(--color-foreground);
}

.seat__status {
  font-size: 0.6rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-negative);
}

.seat__bet-chip {
  display: flex;
  align-items: center;
  gap: 0.2rem;
  padding: 0.05rem 0.35rem 0.05rem 0.1rem;
  border-radius: 999px;
  background: var(--color-ink);
}

/* A small CSS-drawn poker chip disc, colored per bet tier (matching the
   --color-bet/-call/-raise/-3bet/-shove tokens the betting-escalation bar
   already uses — see HandOrchestrator#bet_indicators) -- not a photographic
   asset. */
.seat__bet-chip-icon {
  width: 0.9rem;
  height: 0.9rem;
  flex-shrink: 0;
  border-radius: 50%;
  background: repeating-conic-gradient(var(--chip-color) 0deg 40deg, var(--color-white) 40deg 60deg);
  box-shadow: inset 0 0 0 1px var(--color-shadow), 0 1px 1px var(--color-deep-shadow);
}

.seat__bet-chip-icon--bet { --chip-color: var(--color-bet); }
.seat__bet-chip-icon--call { --chip-color: var(--color-call); }
.seat__bet-chip-icon--raise { --chip-color: var(--color-raise); }
.seat__bet-chip-icon--three-bet { --chip-color: var(--color-3bet); }
.seat__bet-chip-icon--shove { --chip-color: var(--color-shove); }

.seat__bet-chip-amount {
  font-size: 0.62rem;
  font-weight: 700;
  color: var(--color-bet);
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  .seat__body--acting ~ .seat__info--below .seat__name--below {
    animation: none;
    color: var(--color-acting-glow);
  }
}

/* The footer state for a finished sit-n-go (see tables/_sit_n_go_complete
   and _game.html.slim's own table.closed? branch) -- replaces the usual
   "Next hand"/ready-up controls once one player holds every chip. */
.sit-n-go-complete {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  text-align: center;
}

.sit-n-go-complete__icon {
  color: var(--color-gold);
  flex-shrink: 0;
}

.sit-n-go-complete__message {
  font-size: 0.9rem;
}

.sit-n-go-complete__wins {
  color: var(--color-muted);
  font-size: 0.8rem;
  margin-left: 0.3rem;
}

/* A dark-green fill (--color-primary-text, #325143 -- the app's one fixed
   accent, already used for links) with the same light foreground the rest
   of the app's own dark background uses -- not reversed any more now that
   this fill itself is dark. On a table page, --site-header-background/
   -foreground can still be set inline from that table's own
   card_back_color (see tables/show's content_for and layouts/
   _site_header), same fallback mechanism either way. */
.site-header {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.75rem;
  background: var(--site-header-background, var(--color-header-accent));
  color: var(--site-header-foreground, var(--color-foreground));
  border-bottom: 1px solid var(--color-border);
}

.site-header a {
  color: var(--site-header-foreground, var(--color-foreground));
}

/* Active state: whichever link points at the page you're already on (see
   ApplicationHelper#header_link_to, which stamps aria-current) goes
   accent gold against the header's own dark-green background -- not pure
   white, which would barely stand out from the header's own already-light
   default foreground text. Lucide icons stroke with currentColor, so an
   icon-link picks this up too. */
.site-header a[aria-current="page"] {
  color: var(--color-accent);
}

.site-header__avatar-link[aria-current="page"] {
  box-shadow: 0 0 0 2px var(--color-accent);
}

.site-header__title {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
  margin-right: auto;
}

.site-header__brand {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}

/* Compound selector (not a bare .site-header__logo rule) so this reliably
   overrides brand-logo.css's own fixed width/color regardless of
   @import-glob's file load order — both are single-class rules otherwise,
   same specificity. The color override (not just inheriting from
   `.site-header a`) matters because BrandHelper#rbpkr_logo sets its own
   `color` directly on the <svg> itself (see brand-logo.css) — a rule
   matching the element directly always wins over one it would otherwise
   inherit, however specific. Same --site-header-foreground fallback as
   everything else in this file, so a table's own card_back_color flipping
   it still carries the logo along, not just the text/links. */
.brand-logo.site-header__logo {
  display: block;
  height: 1rem;
  width: auto;
  color: var(--site-header-foreground, var(--color-foreground));
}

.site-header__table {
  overflow: hidden;
  color: color-mix(in oklch, var(--site-header-foreground, var(--color-foreground)) 65%, transparent);
  font-size: 0.8rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A small circular badge, same 1.625rem size as .seat__button-badge (see
   seat.css) -- background is the header's own foreground shade (matching
   its text/links, reversed against the header's own background), with the
   avatar itself sitting inside still painted in the user's own chosen
   --avatar-color (see .avatar in avatar.css) rather than forced to match,
   so it still reads as personal, just framed by the header's own chrome. */
.site-header__avatar-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.625rem;
  height: 1.625rem;
  border-radius: 50%;
  background: var(--site-header-foreground, var(--color-foreground));
}

.site-header__link {
  font-size: 0.85rem;
  font-weight: 600;
}

/* The "Your tables" link (see _site_header) -- an icon instead of text, so
   it just needs to sit inline at the same height as the other links/avatar
   badge beside it rather than carry any text sizing of its own. Also used
   for the sound-toggle/refresh <button>s beside it -- a plain <button>
   doesn't get the pointer cursor an <a> does for free, so that's set
   explicitly here rather than left to only apply to half of this class's
   own elements. Hover is a background tint mixed off the header's own
   foreground (light normally, but a table's own card_back_color can still
   flip --site-header-foreground -- see the color-scheme note up top),
   never a foreground color swap: a swap to --color-accent (matching the
   aria-current active state above) reads fine as small body text but
   washes a whole icon out against it. */
.site-header__icon-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.2rem;
  margin: -0.2rem;
  border-radius: var(--border-radius-sm);
  cursor: pointer;
  transition: background-color 0.15s ease;
}

.site-header__icon-link:hover {
  background: color-mix(in oklch, var(--site-header-foreground, var(--color-foreground)) 15%, transparent);
}

/* Set by refresh_button_controller.js the instant it's clicked -- runs
   until the Turbo.visit it triggers actually lands and replaces this
   whole element, not stopped explicitly (see that controller's own
   comment). Targets the icon's own <svg>, not this element itself, so the
   hover background tint above doesn't spin along with it. */
.site-header__icon-link--spinning svg {
  animation: site-header-icon-spin 0.8s linear infinite;
}

@keyframes site-header-icon-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .site-header__icon-link--spinning svg {
    animation: none;
  }
}

/* The "you're not seated / not signed in" panel -- tables/_anonymous_visitor
   (guest join) and tables/_game_main's own not-seated buy-in form (which
   adds the chair illustration, heading, and pill password field). Stacks
   everything in a single narrow column so it never overflows on mobile. */

.table-join {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.75rem;
  width: 100%;
  max-width: 22rem;
  margin: 0 auto;
  padding: 1rem;
}

/* The chair-at-a-table illustration above the buy-in prompt. */
.table-join__art {
  width: 100%;
  max-width: 15rem;
  height: auto;
  margin: 0 auto 0.25rem;
}

.table-join__title {
  margin: 0;
  font-size: 1.5rem;
  text-align: center;
}

.table-join__note {
  margin: 0;
  font-size: 0.85rem;
  color: var(--color-muted);
  text-align: center;
}

/* Fancier table-password field: a lock icon tucked inside a pill input,
   spaced-out characters, a primary-tinted focus ring. */
.table-join__password {
  position: relative;
  display: flex;
  align-items: center;
}

.table-join__password-icon {
  position: absolute;
  left: 0.85rem;
  color: var(--color-muted);
  pointer-events: none;
}

.table-join__password-input {
  width: 100%;
  padding: 0.7rem 0.9rem 0.7rem 2.5rem;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-surface);
  color: var(--color-foreground);
  letter-spacing: 0.15em;
  text-align: center;
}

.table-join__password-input::-moz-placeholder {
  letter-spacing: normal;
  color: var(--color-muted);
}

.table-join__password-input::placeholder {
  letter-spacing: normal;
  color: var(--color-muted);
}

.table-join__password-input:focus {
  outline: none;
  border-color: var(--color-header-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-header-accent) 30%, transparent);
}

.table-join__form {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.5rem;
}

.table-join__label {
  font-size: 0.85rem;
  color: var(--color-muted);
}

.table-join__input {
  width: 100%;
  padding: 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius-sm);
  background: var(--color-surface);
  color: var(--color-foreground);
}

.table-join__form .btn,
.table-join > .btn {
  width: 100%;
}

.tables-index__actions {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

.tables-index__heading {
  margin: 0 0 1rem;
  font-size: 1.3rem;
}

.tables-index__subheading {
  margin: 1.5rem 0 0.5rem;
  font-size: 1.1rem;
}

.tables-index__blurb {
  color: var(--color-muted);
  margin-bottom: 1rem;
}

.tables-index__wins {
  color: var(--color-muted);
  font-size: 0.85rem;
  margin-top: 0.75rem;
}

.table-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  list-style: none;
  padding: 0;
}

.table-list__item {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  padding: 0.65rem 0.85rem;
  border-radius: var(--border-radius);
  background: var(--color-surface);
}

.table-list__owner {
  color: var(--color-muted);
  font-size: 0.8rem;
}

/* Mobile-only swipeable toggle between the seat rail and the community
   board (see tables/_game_main -- table_panels_controller.js drives the
   actual drag/swipe). .table__panels is the controller's own root element
   (carries data-controller and the hint text); .table__panels-viewport is
   just the swipeable region inside it -- kept separate so the hint text
   above it sits in normal flow, not fighting the viewport's own grid
   stacking. Only two panels, and either swipe direction just means "show
   the other one" -- there's no fixed left/right order between them, so
   both panels share the same grid cell (stacked, not laid out side by
   side in a fixed filmstrip) and the controller itself decides which side
   each one slides to/from based on which way you actually dragged.
   Community is the default (see the controller's own initial #active).
   >= 1024px resets all of this back to plain stacked block layout below --
   the felt overview already shows seats separately there (game-shell.css's
   own .table__seats { display: none }), so there's nothing left to swipe
   between. */
.table__panels-viewport {
  position: relative;
  display: grid;
  /* Not the grid default (stretch) -- table_panels_controller.js needs
     each panel's own OWN content height (offsetHeight) to size the
     viewport itself off whichever one is actually active (see its own
     #updateHeight), which stretch would otherwise mask by forcing both
     panels to the shared row's already-tallest-of-the-two height. */
  align-items: start;
  overflow: hidden;
  /* JS-driven (see #updateHeight) -- animates alongside the horizontal
     swipe itself so the area visibly grows/shrinks with it, not just
     jumps. */
  transition: height 0.3s cubic-bezier(0.16, 1, 0.3, 1);
  touch-action: none;
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
}

/* Default (pre-JS) position: community (the 2nd panel) at rest, seats (the
   1st) pushed off to the left -- matches table_panels_controller.js's own
   initial #active/#applyPosition so there's no flash of both stacked
   before Stimulus connects and takes over inline. */
.table__panels-panel {
  grid-area: 1 / 1;
  width: 100%;
  min-width: 0;
  transform: translateX(-100%);
}

.table__panels-panel:last-child {
  transform: translateX(0);
}

/* Same treatment as .hole-cards__hint (hole-cards.css) -- a small icon +
   text line, just one direction here (only two panels, so there's only
   ever one thing to point out: swipe to get to the other one). Its own
   text (table_panels_controller.js's #hintLabel target) always names
   whichever panel is NOT currently showing -- see the view's own comment. */
.table__panels-hint {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  margin-bottom: 0.2rem;
  color: var(--color-muted);
  font-size: 0.75rem;
  text-align: center;
}

.table__panels-hint-icon {
  flex-shrink: 0;
}

/* After the hint's own base rule above (not before it) -- equal
   specificity ties go to whichever comes last in source order, so this
   has to follow it to actually win at >= 1024px. See the class comment up
   top for what else resets here. */
@media (min-width: 1024px) {
  .table__panels-hint {
    display: none;
  }

  .table__panels-viewport {
    display: block;
    overflow: visible;
    /* Overrides table_panels_controller.js's own JS-set inline height
       (see its #updateHeight) the same way the panel transform below
       overrides its inline transform -- back to sizing naturally. */
    height: auto !important;
    transition: none !important;
    touch-action: auto;
    -webkit-user-select: auto;
       -moz-user-select: auto;
            user-select: auto;
  }

  .table__panels-panel {
    transform: none !important;
    transition: none !important;
  }
}
.table {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.5rem 0.75rem;
  /* The action bar is fixed to the viewport bottom — reserve room so it
     never covers the last seat/pot content when the page is scrolled. */
  padding-bottom: 4.5rem;
}

/* Full-bleed like .hole-cards__cards (breaks out of .container's + .table's
   combined 1.75rem of side padding) so the 5 cards can fill ~85% of the
   actual screen width on mobile, instead of ~85% of an already-shrunk
   container. */
.table__community {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  gap: 1.5%;
  width: calc(100% + 3.5rem);
  margin-inline: -1.75rem;
  /* Pulls back most of .table's own 0.4rem flex gap above this (between it
     and .table__seats right before it in the DOM) — the seat rail sits
     nearly flush to the board on mobile, where vertical space is scarce.
     Overridden on desktop below, where the rail's gone entirely. */
  margin-top: -0.15rem;
  padding-inline: 7.5%;
  min-height: 3.5rem;
}

.table__community .card {
  width: auto;
  max-width: 4.5rem;
  flex: 1 1 0;
  margin: 0;
}

@media (min-width: 456px) {
  .table__community {
    width: 100%;
    max-width: 28rem;
    margin-inline: auto;
    padding-inline: 0;
  }
}

@media (min-width: 1024px) {
  /* The seat rail above the board is gone on desktop (game-shell.css), so
     give the board its own breathing room top and bottom instead. */
  .table__community {
    margin-block: 1.6rem;
  }
}

/* Full-bleed like .table__community (same -1.75rem/+3.5rem cancellation of
   .container's + .table's combined side padding) so seats reach all the
   way to the viewport edge instead of wrapping early inside an
   already-shrunk container. Seats stay a fixed size (their cards are
   fixed-width, not percentage — shrinking the seat box wouldn't shrink its
   content, just clip it) — this single-row layout only ever actually
   applies below 6 players (see --seat-columns below), but stays as the
   fallback for whatever a future seat count throws at it. */
.table__seats {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: flex-start;
  gap: 0.5rem;
  width: calc(100% + 3.5rem);
  margin-inline: -1.75rem;
  padding-inline: 2px;
  /* Top clearance for the D/SB/BB badge, pinned to each pod's own top-right
     corner with a negative offset (seat.css's own .seat__button-badge/
     .seat__blind-badge) -- it overhangs above the pod itself, and this
     rail sits inside .table__panels-viewport's own overflow: hidden
     (table-panels.css), which would otherwise clip it right off. */
  padding-top: 0.5rem;
  padding-bottom: 0.75rem;
}

@media (min-width: 456px) {
  .table__seats {
    width: 100%;
    margin-inline: auto;
    padding-inline: 0;
  }
}

/* 6+ players: exactly 2 rows of ceil(count / 2) even columns — see
   tables/_game.html.slim for --seat-columns. minmax(0, 1fr) columns share
   the width evenly and shrink to fit however many players there are, and
   .seat stretches to fill its cell (seat.css), so every indicator ends up
   exactly the same size with the row's gap always visible between them. */
.table__seats--grid {
  display: grid;
  /* Even columns capped at 6rem: they shrink below that to fit a narrow
     screen (minmax 0), and on a wide one the whole block just centres
     rather than the pods ballooning. No full-bleed (unlike the flex
     fallback above) -- overshooting the viewport only clips seats. */
  grid-template-columns: repeat(var(--seat-columns), minmax(0, 6rem));
  justify-content: center;
  gap: 0.6rem;
  width: 100%;
  margin-inline: 0;
  padding-inline: 0;
}

.table__seats--grid .seat {
  width: auto;
  min-width: 0;
}

.table__start-form {
  display: flex;
  justify-content: center;
}

@media (min-width: 1024px) {
  .table {
    max-width: 64rem;
    margin: 0 auto;
  }
}

/*****************************
Utilities last for specificity
******************************/

/* Utility classes — .u-* prefix, layered last so they win ties. */

@layer utilities {
  .u-text-muted { color: var(--color-muted); }
  .u-text-sm { font-size: 0.85rem; }
  .u-text-center { text-align: center; }
  .u-text-uppercase { text-transform: uppercase; }
  .u-color-primary { color: var(--color-primary); }
  .u-color-secondary { color: var(--color-secondary); }

  .u-mt-zero { margin-top: 0; }
  .u-mt-sm { margin-top: 0.5rem; }
  .u-mt-md { margin-top: 1rem; }
  .u-mt-lg { margin-top: 1.5rem; }
  .u-mb-zero { margin-bottom: 0; }
  .u-mb-sm { margin-bottom: 0.5rem; }
  .u-mb-md { margin-bottom: 1rem; }
  .u-mb-lg { margin-bottom: 1.5rem; }
  .u-mr-md { margin-right: 1rem; }
  .u-mx-auto { margin-left: auto; margin-right: auto; }

  .u-pt-zero { padding-top: 0; }
  .u-pt-sm { padding-top: 0.5rem; }
  .u-pt-md { padding-top: 1rem; }
  .u-pt-lg { padding-top: 1.5rem; }
  .u-pb-zero { padding-bottom: 0; }
  .u-pb-sm { padding-bottom: 0.5rem; }
  .u-pb-md { padding-bottom: 1rem; }
  .u-pb-lg { padding-bottom: 1.5rem; }
  .u-separate { padding-bottom: 2rem; }

  .u-max-width-sm { max-width: 480px; }

  /* Off-screen but still in the accessibility tree unless aria-hidden'd --
     used for the spam-trap honeypot (see app/views/application/_spam_trap),
     which is NOT display:none on purpose (some bots skip those). */
  .u-visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
  .u-float-left { float: left; }
  .u-clearfix::after { content: ""; display: table; clear: both; }
  .u-pointer { cursor: pointer; }
  .u-flex-center { display: flex; justify-content: center; }

  .u-img-full {
    width: 100%;
    border-radius: var(--border-radius);
    display: block;
  }

  .u-img-cover {
    width: 100%;
    aspect-ratio: 4 / 3;
    -o-object-fit: cover;
       object-fit: cover;
    border-radius: var(--border-radius);
    display: block;
  }

  .u-img-circle {
    aspect-ratio: 1;
    -o-object-fit: cover;
       object-fit: cover;
    border-radius: 50%;
    display: block;
  }

  /* Locks the page to exactly one screen, no scrolling in either
     direction — for app-like screens (the table view) where content that
     doesn't fit should be cropped, not scrolled to. 100dvh (not vh)
     accounts for mobile browser chrome collapsing on scroll — sized
     against vh here it'd measure taller than what's actually visible and
     defeat the point. Also locks <html>, matching the overflow-x rule
     above; which element actually controls viewport scroll varies by
     browser, and :has() reaching up from body's own class is simpler than
     threading a second class onto <html> from the layout. */
  .u-locked-viewport {
    overflow: hidden;
    height: 100dvh;
  }

  html:has(body.u-locked-viewport) {
    overflow: hidden;
    height: 100dvh;
  }

  .u-visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
}
