

@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);
  }

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

.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: #0b1220;
  --color-foreground: #e6edf5;
  --color-muted: #9fb3c8;
  --color-border: #2a3a4f;
  --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: #58df7a;
  --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);
  --color-primary-hover: color-mix(in oklch, var(--color-emerald) 82%, black);
  /* 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: #2c383e;
  --color-danger: var(--color-red);
  --color-danger-hover: color-mix(in oklch, var(--color-red) 82%, black);
  --color-accent: var(--color-gold);
  --color-primary-text: var(--color-sky);

  /* 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;

  /* 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.6); /* 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.5); /* 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: #0a1a29;

  /* 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.5rem;
  /* Vertical padding kept minimal -- the fixed footer is tight on height,
     especially on a short mobile viewport. */
  padding: 0.125rem 0.75rem;
}

/* 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 results button and the QR corner side by
   side, sharing one left border between them and the rest of the row. */
.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.9rem;
  padding: 0.2rem;
  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.75rem;
  padding: 0.35rem;
  background: none;
  border: none;
  border-right: 1px solid var(--color-border);
  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.75rem;
  padding: 0.35rem;
  background: none;
  border: none;
  border-right: 1px solid var(--color-border);
  color: var(--color-muted);
  cursor: default;
}

.action-bar__chip-requests--pending {
  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.75rem;
}

.action-bar__presets {
  display: flex;
  gap: 0.4rem;
}

.action-bar__preset {
  padding: 0.3rem 0.6rem;
  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;
  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.5rem;
}

.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);
}
.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);
}
.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;
}

/* 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--danger {
  background: var(--color-danger);
  color: var(--color-ink);
}

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

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

.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);
}

/* The face PNGs already have rounded corners baked in (transparent outside
   the card outline) — the container deliberately does NOT clip/round on top
   of that, or the two roundings mismatch and cut the corners oddly.
   position: relative + a fixed aspect-ratio (rather than sizing purely from
   an inner <img>'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 image of
   their own to fall back on. */
.card {
  position: relative;
  display: inline-flex;
  width: 2.75rem;
  aspect-ratio: 500 / 726;
  margin: 0 0.15rem;
}

.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 {
  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: 1px solid var(--color-card-back-border);
  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);
}

/* 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%;
  -o-object-fit: contain;
     object-fit: contain;
  display: block;
}

/* The community board's "not dealt yet" placeholder (see tables/_card.html.slim)
   reads as present but deliberately secondary — full-opacity would make an
   empty slot compete for attention with the actual cards next to it. */
.card--placeholder .card__face {
  opacity: 0.5;
}

/* Stacked directly behind the incoming real face image 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;
}

.card__placeholder-ghost .card__face {
  opacity: 0.5;
}

.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;
  }
}

/* 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%;
}

.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;
}

.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 {
  margin: 0 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;
}

.footer__logo {
  display: block;
  height: 4rem;
  width: auto;
}

.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;
}

.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__checkbox {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
}

/* 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;
    grid-template-columns: 1fr 2fr;
    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;
  }

  .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);
    --shell-col: calc((min(100vw, 1200px) - 2.5rem - 1.25rem) / 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 pot -- 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;
}

.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;
  width: calc(100% + 3.5rem);
  max-width: 400px;
  min-height: 20rem;
  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 {
  display: none;
  -o-object-fit: contain;
     object-fit: contain;
}

.hole-cards--peeking .hole-cards__face {
  display: block;
}

.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: 1px solid var(--color-card-back-border);
  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);
}

/* 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: 58%;
  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: 66%;
  height: 66%;
}

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

.home-hero {
  padding: 2rem 0 1.5rem;
  text-align: center;
}

.home-hero__title {
  margin: 0 0 0.5rem;
}

.home-hero__logo {
  display: block;
  width: min(85vw, 20rem);
  height: auto;
  margin: 0 auto;
}

.home-hero__tagline {
  max-width: 32rem;
  margin: 0 auto 1.25rem;
  color: var(--color-muted);
}

.home-hero__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
}

.home-hero__actions--secondary {
  margin-top: 0.5rem;
}

/* Layout itself (2 per row, then 4 once .grid__cell--width-25-md kicks in)
   is the shared grid's own job now (see components/grid.css and this
   class paired directly onto that .grid element in the view) -- just the
   bottom margin belongs to this page specifically. */
.home-stats {
  margin: 0 0 2rem;
}

.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);
  text-align: center;
}

.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;
}

/* Two boards side by side at -md, stacked full-width below (layout is the
   shared grid's job -- see components/grid.css); just the bottom margin
   belongs to this page. */
.home-leaderboards {
  margin: 0 auto;
}

.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;
}

/* 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 --- */

.poker-table__seat {
  position: absolute;
  left: var(--seat-left);
  top: var(--seat-top);
  transform: translate(-50%, -50%);
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  width: 5.25rem;
  cursor: pointer;
  transition: opacity 0.2s ease;
}

.poker-table__seat--folded {
  opacity: 0.4;
}

.poker-table__seat-body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.15rem;
  padding: 0.35rem 0.45rem 0.3rem;
  border-radius: 0.65rem;
  /* Card-back colour + a knocked-back pattern (half-opaque wash of the
     same colour over it) -- matches the mobile seat rail. Text black,
     avatar on a dark disc for contrast. */
  background-color: var(--card-back-color, var(--color-card-back-fallback));
  background-image:
    linear-gradient(
      color-mix(in oklch, var(--card-back-color, var(--color-card-back-fallback)) 55%, transparent),
      color-mix(in oklch, var(--card-back-color, var(--color-card-back-fallback)) 55%, transparent)
    ),
    var(--card-back-pattern-image, none);
  background-size: cover, 4px 4px;
  border: 1px solid var(--color-card-back-border);
  color: var(--color-ink);
}

.poker-table__seat--acting .poker-table__seat-body {
  border-color: var(--color-acting-glow);
  box-shadow: 0 0 0 2px var(--color-acting-glow), 0 0 18px 2px color-mix(in oklch, var(--color-acting-glow) 70%, transparent);
}

.poker-table__seat--hero .poker-table__seat-body {
  border-color: var(--color-primary-text);
}

.poker-table__avatar {
  position: relative;
  display: flex;
}

/* Black disc behind the icon -- .avatar is shared, only touch it here. */
.poker-table__avatar .avatar {
  padding: 0.22rem;
  border-radius: 50%;
  background: var(--color-ink);
  box-shadow: 0 0 0 1px var(--color-shadow-ring);
}

.poker-table__badge {
  position: absolute;
  top: -0.35rem;
  right: -0.6rem;
  min-width: 1.15rem;
  height: 1.15rem;
  padding: 0 0.22rem;
  border-radius: 999px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.58rem;
  font-weight: 700;
  color: var(--color-ink);
  box-shadow: 0 0 0 1px var(--color-shadow-ring);
}

.poker-table__badge--dealer {
  background: var(--color-dealer-button);
}

.poker-table__badge--sb {
  background: var(--color-small-blind);
  color: var(--color-white);
}

.poker-table__badge--bb {
  background: var(--color-big-blind);
  color: var(--color-white);
}

.poker-table__seat-meta {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.15;
  max-width: 5.5rem;
}

.poker-table__seat-name {
  font-size: 0.72rem;
  font-weight: 600;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

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

.poker-table__seat-extra {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 1.1rem;
}

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

.poker-table__bet {
  display: flex;
  align-items: center;
  gap: 0.22rem;
}

.poker-table__bet-icon {
  width: 0.95rem;
  height: 0.95rem;
}

.poker-table__bet-amount {
  font-size: 0.66rem;
  font-weight: 700;
  color: var(--color-bet);
}

/* 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;
  font-size: 0.7rem;
}

.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 {
  position: relative;
  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(--border-radius);
  /* The table's card-back colour + pattern, but the pattern knocked back
     by a half-opaque wash of that same colour so the black text stays
     clean. The avatar sits on a dark disc to read over any colour. */
  background-color: var(--card-back-color, var(--color-card-back-fallback));
  background-image:
    linear-gradient(
      color-mix(in oklch, var(--card-back-color, var(--color-card-back-fallback)) 55%, transparent),
      color-mix(in oklch, var(--card-back-color, var(--color-card-back-fallback)) 55%, transparent)
    ),
    var(--card-back-pattern-image, none);
  background-size: cover, 4px 4px;
  border: 1px solid var(--color-card-back-border);
  color: var(--color-ink);
}

.seat__body--folded {
  opacity: 0.45;
}

.seat__body--you {
  border-color: var(--color-primary-text);
}

/* Cards shown: drop the card-back fill and the border (compound selector
   so it also wins over --you / --acting), name/stack go white and flush
   to the bottom. */
.seat__body.seat__body--shown {
  background: var(--color-background);
  background-image: none;
  border-color: transparent;
  padding-bottom: 0;
}

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

@keyframes seat-acting-glow {
  0%, 100% {
    box-shadow: 0 0 0.15rem 0 color-mix(in oklch, var(--color-acting-glow) 70%, transparent);
  }
  50% {
    box-shadow: 0 0 0.6rem 0.15rem color-mix(in oklch, var(--color-acting-glow) 70%, transparent);
  }
}

.seat__body--acting {
  border-color: var(--color-acting-glow);
  animation: seat-acting-glow 1.6s ease-in-out infinite;
}

/* Pinned to the pod's top-left corner (see tables/_seat) -- out of flow so
   the name/stack below get the whole pod width. The D/SB/BB badge takes
   the top-right corner the same way. */
.seat__avatar {
  position: absolute;
  top: 0.2rem;
  left: 0.2rem;
  z-index: 1;
  display: flex;
}

/* 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__avatar .avatar {
  padding: 0.22rem;
  border-radius: 50%;
  background: var(--color-ink);
  box-shadow: 0 0 0 1px var(--color-shadow-ring);
}

/* 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;
}

/* Non-cards pod: name/stack sit flush to the top of the pod, centred in
   the band between the top-left avatar and the top-right badge so neither
   ever overlaps the text. The shown-cards pod keeps its own centred
   layout (no avatar there). */
.seat__body:not(.seat__body--shown) {
  justify-content: flex-start;
}

.seat__body:not(.seat__body--shown) .seat__info {
  padding: 0.05rem 1.25rem 0 1.4rem;
}

.seat__name {
  max-width: 100%;
  font-size: 0.72rem;
  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__extra {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 1.05rem;
  margin-top: 0;
  overflow: hidden;
}

.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);
}

.seat__bet-chip-icon {
  width: 0.9rem;
  height: 0.9rem;
  flex-shrink: 0;
}

.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 {
    animation: none;
    box-shadow: 0 0 0.4rem 0.1rem color-mix(in oklch, var(--color-acting-glow) 70%, transparent);
  }
}

/* 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;
}

/* Reversed from the rest of the app everywhere: a blue background with a
   dark foreground, not the dark-background/light-foreground the page
   itself uses. On a table page, --site-header-background/-foreground are
   set inline from that table's own card_back_color (see tables/show's
   content_for and layouts/_site_header) so the header matches its deck;
   every other page falls back to --color-primary-text (the app's one
   fixed shade of blue, already used for links) with the same static dark
   foreground table pages use. Every card_back_color in
   Table::CARD_BACK_COLORS is light/mid-tone too, so that fixed dark
   foreground reads fine against any of them without being computed
   per-color. */
.site-header {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.225rem 0.75rem;
  background: var(--site-header-background, var(--color-primary-text));
  color: var(--site-header-foreground, var(--color-ink));
  border-bottom: 1px solid var(--color-border);
}

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

/* Active state: whichever link points at the page you're already on (see
   ApplicationHelper#header_link_to, which stamps aria-current) goes pure
   white against the header's own mid-tone background. Lucide icons stroke
   with currentColor, so an icon-link picks this up too. */
.site-header a[aria-current="page"] {
  color: var(--color-white);
}

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

.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;
}

.site-header__logo {
  display: block;
  height: 1.75rem;
  width: auto;
}

.site-header__table {
  overflow: hidden;
  color: color-mix(in oklch, var(--site-header-foreground, var(--color-ink)) 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-ink));
}

.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. */
.site-header__icon-link {
  display: inline-flex;
}
/* 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. 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;
  padding: 1rem;
}

.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;
}

.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;
  }
}

.table__pot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  /* Pulls back most of .table's flex gap above and (via .hole-cards'
     negative margin below) beneath -- the board / pot / hero stack sits
     tight on mobile. */
  margin-top: -0.25rem;
  font-size: 0.95rem;
}

.table__pot-icon {
  color: var(--color-accent);
  opacity: 0.85;
}

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

.table__pot-amount {
  font-weight: 700;
  color: var(--color-accent);
}

/* 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;
}

@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;
  }
}
