/* Shared chrome across all 4 apps (table/, singleplayer/, visual/, multiplayer/).
   Loaded first on every page, before that page's own stylesheet — the page's
   own file can override anything here since it loads after this one and wins
   ties at equal specificity. Genuinely page-specific CSS (tile pickers, seat
   scorers, lobby forms, sticky-results layouts, etc.) stays in each app's own
   stylesheet; only rules that were byte-identical (or differed by a trivial,
   documented amount) across 3+ apps live here. */

*, *:before, *:after {
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    margin: 0;
    padding: clamp(8px, 3vw, 16px);
    color: #222;
    background-color: #f0f2f5;
    -webkit-text-size-adjust: 100%;
}

/* Common app-container look; centering mechanism and padding differ per app
   (margin:0 auto + padding vs. flex-centered body + width:100%), so those
   stay as small per-app overrides in each app's own stylesheet. */
.app-container {
    max-width: 1100px;
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.08);
}

/* Matches table/game.css and multiplayer/style.css exactly. Visual overrides
   flex-wrap back to nowrap and zeroes margin-bottom (it never had either);
   singleplayer overrides border-bottom/margin-bottom off and supplies its
   own padding instead (see singleplayer/style.css's own .header rule and
   its explanatory comment about .results::before taking over the divider). */
.header {
    position: relative;
    display: flex;
    flex-direction: column;
    flex-wrap: wrap;
    gap: 16px;
    border-bottom: 2px solid #b32424;
    padding-bottom: 16px;
    margin-bottom: 16px;
}

.title-block {
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.title-grid-table {
    display: flex;
    align-items: center;
}
.title-icon {
    font-size: 28px;
    padding-right: 10px;
    line-height: 1;
}
.title-text-stack {
    display: flex;
    flex-direction: column;
}
.title-line1 {
    font-size: clamp(18px, 5vw, 22px);
    color: #b32424;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-weight: 800;
    line-height: 1.2;
}
.title-ruleset-subtitle {
    font-size: 12px;
    font-weight: 600;
    color: #6b5a3a;
    margin-top: 2px;
}

/* Settings gear (toggle button + floating dropdown) — only table/game.html,
   singleplayer/index.html, visual/index.html, and multiplayer/game.html
   actually have this element; harmless unused CSS on the pages without it. */
.settings-dropdown {
    position: absolute;
    top: 0;
    right: 0;
    z-index: 21;
}
.settings-toggle-btn {
    height: 36px;
    width: 36px;
    padding: 0;
    border-radius: 8px;
    border: 1px solid #ccc;
    background: #f8f8f8;
    color: #222;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.settings-toggle-btn:active { background: #eee; }
#settings-toggle-icon {
    transition: transform 0.2s;
    display: inline-block;
}
#settings-toggle-icon.open {
    transform: rotate(30deg);
}
#settings-panel {
    position: absolute;
    top: 100%;
    right: 0;
    margin-top: 8px;
    background: #fff;
    border: 1px solid #ddd;
    border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.15);
    padding: 16px 20px;
    display: flex;
    flex-direction: column;
    gap: 16px;
    width: 360px;
    max-width: 90vw;
}
#settings-panel.hidden-element { display: none; }

.meta-inputs {
    display: grid;
    /* Bare 1fr behaves like minmax(auto, 1fr) — its minimum size is based
       on its content rather than clamped to the grid container, so any row
       whose content can't shrink (e.g. a long settings-field label) pushes
       the whole panel wider than its own box on narrow screens.
       minmax(0, 1fr) clamps the track properly so rows can actually shrink
       to fit, instead of overflowing. */
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;
    font-size: 14px;
    font-weight: bold;
}
.meta-group {
    display: flex;
    flex-direction: column;
    gap: 12px;
    /* Flex items default to min-width: auto (their content's natural
       size), which stops them shrinking below that even when a child
       further down (the field box) is explicitly shrinkable — see
       .meta-select below. Needed at every level of this flex/grid chain
       (.meta-group, .meta-item) for a field to actually be able to
       narrow on a small screen instead of overflowing. */
    min-width: 0;
}
.meta-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    min-width: 0;
}
.meta-item span {
    white-space: nowrap;
}

/* Checkbox styling — custom red checkmark everywhere. */
input[type="checkbox"] {
    appearance: none;
    -webkit-appearance: none;
    width: 30px;
    height: 30px;
    border: 1px solid #ccc;
    border-radius: 6px;
    background: #f8f8f8;
    flex-shrink: 0;
    position: relative;
    margin: 0;
    cursor: pointer;
}
input[type="checkbox"]:checked {
    background: #b32424;
    border-color: #b32424;
}
input[type="checkbox"]:checked::after {
    content: '\2713';
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: 16px;
    font-weight: bold;
}
input[type="checkbox"]:disabled {
    cursor: default;
    opacity: 0.6;
}

/* Settings-panel checkbox rows. Visual builder overrides font-size/weight
   and adds its own :has(input:disabled) rule, plus keeps a separate
   .win-condition-label for its Win Conditions/Special Hands checkboxes
   (deliberately not the same class — see visual/style.css). */
.meta-checkbox-label {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
    cursor: pointer;
    user-select: none;
    padding: 4px 0;
}

.meta-select {
    border: 1px solid #ccc;
    width: 200px;
    flex-shrink: 0;
    height: 44px;
    background: #fcfcfc;
    font-weight: bold;
    text-align: center;
    font-size: 16px; /* 16px prevents iOS auto-zoom */
    border-radius: 8px;
    color: #222;
}
.meta-select:disabled { background: #f0f0f0; color: #aaa; cursor: not-allowed; }
/* Narrower than the header ddls' .meta-select (200px) — every settings
   field except Ruleset (both settings panels: the in-game gear panel and
   the start page's pre-game panel) shares this one width, sized to fit
   the longest value shown anywhere ("Tiered Blocks", SG's Scoring System)
   without truncating or wrapping — consistent sizing across every field
   and every ruleset, rather than each field/ruleset getting its own size.
   #ruleset-select is excluded (see its own wider override below) since
   "Hong Kong Old Style" doesn't fit even at this width. */
#settings-panel .meta-select,
#start-settings-panel .meta-select {
    width: 150px;
}
/* Safety net, not the normal case — fields stay at the full 150px above
   whenever there's room; this only kicks in on a screen so narrow that
   label + 130px field genuinely can't both fit on one line, letting the
   field narrow further rather than overflowing the panel. min-width: 0 on
   .meta-group/.meta-item (above) is required for this to take effect. */
#settings-panel .meta-select,
#start-settings-panel .meta-select {
    flex-shrink: 1;
    min-width: 0;
}

/* Custom dropdown replacing native <select> — see custom-select.js. The
   trigger button reuses .meta-select's own sizing/border/font (it carries
   both classes), so only the button-specific reset and the popup list
   itself need their own rules here. */
.custom-select {
    position: relative;
    display: inline-block;
    flex-shrink: 0;
}
.custom-select-trigger {
    appearance: none;
    -webkit-appearance: none;
    cursor: pointer;
    padding: 0 10px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
}
.custom-select-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    /* Flex items default to min-width: auto (their unshrunk content size),
       which for a nowrap span means "as wide as the full text" — silently
       overriding the overflow/ellipsis rules above and letting the label
       bleed past the trigger's own box instead of actually truncating.
       min-width: 0 lets it shrink to fit and makes the ellipsis real. */
    min-width: 0;
    flex: 1 1 auto;
}
.custom-select-chevron {
    flex-shrink: 0;
    color: #888;
    transition: transform 0.2s, color 0.2s;
}
/* Open state: flips the chevron and picks up the app's red accent — same
   rotate-on-open convention as the settings-gear icon
   (#settings-toggle-icon.open in this file). */
.custom-select-trigger.open {
    border-color: #b32424;
}
.custom-select-trigger.open .custom-select-chevron {
    transform: rotate(180deg);
    color: #b32424;
}
/* The start page's Ruleset picker's longest label ("Hong Kong Old Style")
   needs more room than .meta-select's default 200px once the trigger also
   reserves space for the chevron — widen just this one dropdown so its
   value is always fully visible (never ellipsis-truncated), rather than
   matching every other settings field's width.
   Scoped to #start-settings-panel specifically (the only place this
   schema-driven rulesetRow renders — see templates/settings-panel.js) —
   NOT a bare #ruleset-select selector, since visual/index.html and
   singleplayer/index.html each have their own, separately-styled element
   that happens to reuse the same id (see visual/style.css's
   .winner-discard-fields #ruleset-select). A bare id selector here would
   win over those pages' own class-based sizing (id beats class in
   specificity) and silently override it.
   The width/max-width/flex-shrink/min-width all live on the wrapper
   itself (the actual flex item inside .meta-item) rather than on the
   trigger button nested inside it — a fixed width on the trigger alone
   doesn't work: the wrapper .custom-select has no width of its own, so
   flexbox falls back to the trigger's fixed width as an unshrinkable
   floor, which then overflows every ancestor up the chain regardless of
   flex-shrink anywhere else. The trigger itself just fills whatever width
   the wrapper ends up with (width: 100%). */
#start-settings-panel #ruleset-select {
    flex: 1 1 auto;
    min-width: 0;
    max-width: 240px;
}
#start-settings-panel #ruleset-select .custom-select-trigger {
    width: 100%;
}
.custom-select-list {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin-top: 4px;
    background: #fff;
    border: 1px solid #ddd;
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.15);
    padding: 4px;
    list-style: none;
    max-height: 240px;
    overflow-y: auto;
    z-index: 60;
}
.custom-select-list.hidden-element { display: none; }
.custom-select-option {
    padding: 8px 10px;
    border-radius: 6px;
    font-size: 14px;
    font-weight: bold;
    text-align: center;
    color: #222;
    cursor: pointer;
}
.custom-select-option:hover { background: #f5f5f5; }
.custom-select-option.selected { background: #fbeaea; color: #b32424; }

.hidden-element {
    display: none !important;
}

.modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 50;
    padding: 20px;
}
.modal-box {
    background: #fff;
    border-radius: 12px;
    padding: 20px;
    max-width: 320px;
    text-align: center;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
}
.modal-box p {
    margin: 0 0 16px;
    font-size: 14px;
    color: #333;
}
.modal-actions {
    display: flex;
    gap: 8px;
    justify-content: center;
}

.status-badge {
    display: none;
    font-size: 9px;
    font-weight: 700;
    color: #fff;
    padding: 2px 5px;
    border-radius: 4px;
    vertical-align: middle;
    letter-spacing: 0.3px;
    text-transform: uppercase;
}
.status-badge.show { display: inline-block; }
.limit-badge { background: #b32424; }
.east-badge { background: #185fa5; }
.below-min-badge { background: #b36b00; }
.instant-payout-badge { background: #2e7d32; }

/* A longer phrase than the other status-badges (e.g. "2 mismatches"), so
   normal case reads better than the all-caps convention above. Lives in
   .point-total next to the other status-badges (singleplayer/index.html) —
   same "something needs your attention" row as Limit/East/Below min. */
.mismatch-badge {
    text-transform: none;
    letter-spacing: normal;
    font-size: 11px;
    background: #b36b00;
}
.hand-row.mismatch { background: rgba(179, 107, 0, 0.12); }
.hand-row.mini-points-disabled { opacity: 0.45; }
.hand-row.mini-points-disabled .qty-btn { cursor: not-allowed; }
.base-total { display: none; font-size: 13px; color: #888; }
.base-total.show { display: inline-block; }
.stat-count { display: none; font-size: 13px; color: #888; }
.stat-count.show { display: inline-block; }

.winner-discard-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 12px;
}

/* ---- Shared responsive header-dropdown grid -----------------------------
   Used by the visual and singleplayer scorers for their header ddl row
   (Ruleset + seat/round/you-are + winner). Opt-in via .hdr-container (on the
   row) and .hdr-grid (on the fields wrapper) so table's own
   .winner-discard-fields layout stays untouched until it migrates too.

   The layout switches on the *row's own* container inline-size, never the
   viewport — the row is a flex child inside .app-container, so its width
   already reflects the real space left after the container's max-width and
   padding. Columns drop (widest -> 2-up -> stacked) exactly when the row
   can no longer hold more, so the ddls can never extend past the container
   regardless of viewport, margins, or how many fields a page has.

   .meta-item is display:contents so each field's label + ddl flatten into
   direct grid items, letting the grid's own column tracks keep every label
   and every ddl aligned across rows. A page with fewer fields than the
   template has columns (e.g. singleplayer's 3 vs the 4-column widest layout)
   just leaves the trailing column-pair empty via normal auto-placement. */
.winner-discard-row.hdr-container {
    container-type: inline-size;
    container-name: hdrfields;
}
/* Selector is single-class .hdr-grid (not .winner-discard-fields.hdr-grid) so
   it has the same specificity as the @container rules below — container
   queries add no specificity, so a higher-specificity base rule would beat
   them and the layout would never switch. */
.hdr-grid {
    /* Still a flex item of the row — without flex-grow it shrink-wraps to
       its content width instead of filling the row. */
    flex: 1 1 auto;
    min-width: 0;
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    row-gap: 12px;
    column-gap: 10px;
    align-items: center;
    font-size: 14px;
    font-weight: bold;
}
.hdr-grid .meta-item { display: contents; }
.hdr-grid .meta-item span { white-space: nowrap; }
/* Stacked (narrowest): every ddl the same 210px. max-width lets a ddl shrink
   below 210px only when its column physically can't hold that (sub-~357px
   viewports), so it never breaches the container, with no visible change at
   any real device width. In the multi-column layouts below the tracks are
   always >= their fixed width, so 100% never forces a shrink there. */
.hdr-grid .custom-select,
.hdr-grid .custom-select-trigger {
    width: 210px;
    max-width: 100%;
}
/* 2-up: all columns uniform 210px. minmax's minimum must match the ddl width
   so the track reserves room for the explicitly-widthed box; a bare
   minmax(0, 1fr) doesn't grow to fit it. Threshold is the row width at which
   two 210px columns + labels + gap actually fit (measured, not a viewport
   guess). */
@container hdrfields (min-width: 635px) {
    .hdr-grid { grid-template-columns: auto minmax(210px, 1fr) auto minmax(210px, 1fr); column-gap: 16px; }
}
/* Widest, 4-field pages (visual): Ruleset stays 210px, every other field
   narrows to 130px — 210 + 3 x 130 + labels + gaps is the widest this can be,
   and 4 x 210 can't fit under .app-container's 1100px max-width. Targeting
   :not(#ruleset-select) keeps this field-agnostic without hard-coding per-page
   ids. Excluded on .hdr-grid-3up (singleplayer's uniform variant below). */
@container hdrfields (min-width: 990px) {
    .hdr-grid:not(.hdr-grid-3up) { grid-template-columns: auto minmax(210px, 1fr) auto minmax(130px, 1fr) auto minmax(130px, 1fr) auto minmax(130px, 1fr); }
    .hdr-grid:not(.hdr-grid-3up) .custom-select:not(#ruleset-select),
    .hdr-grid:not(.hdr-grid-3up) .custom-select:not(#ruleset-select) .custom-select-trigger {
        width: 130px;
        justify-self: start;
    }
}
/* Widest, 3-field pages (singleplayer, opt-in via .hdr-grid-3up): all three
   fields stay a uniform 210px in one row, packed left (justify-content:start
   leaves any slack on the right rather than stretching the ddls or reserving
   an empty 4th column). 3 x 210 + labels + gaps fits well under 1100px, so no
   narrowing is needed here the way the 4-field layout above requires it. */
@container hdrfields (min-width: 885px) {
    .hdr-grid-3up {
        grid-template-columns: auto 210px auto 210px auto 210px;
        justify-content: start;
        column-gap: 16px;
    }
}
.collapsible-body {
    display: none;
}
.collapsible-body.open {
    display: block;
}

/* Fold/collapse toggle button — 30x30 everywhere except multiplayer/game.css,
   which overrides to 26x26 for its own smaller scale. */
.fold-all-btn {
    width: 30px;
    height: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid #ccc;
    border-radius: 6px;
    background: #f8f8f8;
    color: #555;
    cursor: pointer;
    padding: 0;
    flex-shrink: 0;
}
.fold-all-btn:active { background: #eee; }

/* Hand-picker section chrome (table/game.css, singleplayer/style.css,
   multiplayer/game.css all share this shape) — the .hand-section itself
   deliberately does NOT get overflow:hidden/break-inside here since
   singleplayer's position:sticky section head depends on not having an
   overflow:hidden ancestor; table/multiplayer add those as their own
   overrides instead. */
.hand-section {
    border: 1px solid #ddd;
    border-radius: 8px;
    margin-bottom: 8px;
}
.hand-section-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 6px 10px;
    font-size: 11px;
    font-weight: 800;
    text-transform: uppercase;
    color: #555;
    background: #f2f2f2;
    cursor: pointer;
    user-select: none;
    letter-spacing: 0.3px;
}
.hand-section-label-wrap, .hand-section-right {
    display: flex;
    align-items: center;
    gap: 6px;
}
/* Both hidden by default — shown only when updateMismatchBadge() (singleplayer/
   index.js) finds a mismatch inside this section, via the same .show
   convention as .status-badge. Same amber (#b36b00) as .mismatch-badge in
   the point total (shared.css below) — one color for "needs attention"
   across both places it shows up. Rulesets without hasDoublesLinkage
   (HKOS/SG) never populate these, so they just stay hidden. */
.hand-section-mismatch-count {
    display: none;
    min-width: 16px;
    height: 16px;
    box-sizing: border-box;
    align-items: center;
    justify-content: center;
    background: #b36b00;
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: normal;
    text-transform: none;
    border-radius: 8px;
    padding: 0 4px;
}
.hand-section-mismatch-count.show { display: inline-flex; }
.section-chevron {
    flex-shrink: 0;
    color: #999;
    transition: transform 0.2s;
}
.hand-section-head.open .section-chevron {
    transform: rotate(180deg);
}
.hand-section-body {
    display: none;
}
/* Items should read column-down-first (fill the first column top to
   bottom, then the next). CSS multi-column (column-count) looks like the
   natural tool for that, but its default column-fill:balance distributes
   by estimated height, not item count, and can split unevenly (e.g. 3
   items in column 1, 4 in each of the other two, instead of a clean
   4/4/3) — not predictable enough for a fixed-height row list like this.
   grid-auto-flow: column gives the same column-down-first read but with
   an EXACT split, as long as the row count is explicit — shared/
   hand-section-toggle.js's applyHandColumnLayout() computes
   --hand-columns/--hand-rows per section (each section has its own item
   count) and sets them as inline custom properties after building/
   resizing the panel; the 1fr/1-row fallbacks below are just what shows
   before that JS pass has run once. */
.hand-section-body.open {
    display: grid;
    grid-auto-flow: column;
    grid-template-columns: repeat(var(--hand-columns, 1), minmax(0, 1fr));
    grid-template-rows: repeat(var(--hand-rows, 1), auto);
}

.hand-row {
    display: flex;
    align-items: center;
    padding: 5px 10px;
    font-size: 13px;
    border-top: 1px solid #eee;
    position: relative;
}
/* Info icon + floating hover tooltip — data-driven from an item's own
   `description` field (see rulesets/*.js), so any ruleset/app can opt a
   hand into this by just adding that field; nothing else to wire up.
   .hand-name-wrap takes over .hand-name's old flex:1/min-width:0 sizing
   (the row's original layout depended on that growing to fill the middle
   of the row) so the icon sits immediately after the text instead of
   being pushed to the far right. The tooltip itself is position:absolute
   off the row's own positioned context, so it never affects row
   height/layout even while visible; :has() reveals it on hover of the
   icon anywhere inside the row, regardless of DOM nesting depth. */
.hand-name-wrap {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex: 1;
    min-width: 0;
}
.hand-info-icon {
    display: none;
    flex-shrink: 0;
    color: #999;
    cursor: help;
}
.hand-info-icon.show {
    display: inline-block;
}
.hand-desc {
    display: none;
    position: absolute;
    top: 100%;
    left: 10px;
    margin-top: 4px;
    width: max-content;
    max-width: 240px;
    background: #fff;
    border: 1px solid #ddd;
    border-radius: 6px;
    padding: 8px 10px;
    font-size: 12px;
    line-height: 1.5;
    color: #555;
    box-shadow: 0 4px 16px rgba(0,0,0,0.12);
    z-index: 30;
    pointer-events: none;
}
.hand-row:has(.hand-info-icon:hover) .hand-desc {
    display: block;
}
/* Second, visually distinct icon + tooltip for Conflict-type mismatch
   explanations (rulesets/*.js's mismatchWithKeys/mismatchReason,
   surfaced via getConflictMismatches in shared/hand-builder.js). Kept
   fully separate from .hand-info-icon/.hand-desc above rather than
   reused, since a mismatched row may already have its own item
   description — reusing the same tooltip would mean overwriting it, and
   triggering on whole-row hover would show both simultaneously (hovering
   the info icon is also hovering the row). Each icon here only reveals
   its own tooltip. Toggled dynamically (`.show`) by
   syncDisabledMiniPointsControls's sibling, updateMismatchBadge, not at
   row-build time — a row's mismatch state changes reactively, unlike its
   base description. */
.hand-mismatch-icon {
    display: none;
    flex-shrink: 0;
    color: #b36b00;
    cursor: help;
}
.hand-mismatch-icon.show {
    display: inline-block;
}
.mismatch-reason-desc {
    display: none;
    position: absolute;
    top: 100%;
    left: 10px;
    margin-top: 4px;
    width: max-content;
    max-width: 240px;
    background: #fff;
    border: 1px solid #e0b060;
    border-radius: 6px;
    padding: 8px 10px;
    font-size: 12px;
    line-height: 1.5;
    color: #555;
    box-shadow: 0 4px 16px rgba(0,0,0,0.12);
    z-index: 30;
    pointer-events: none;
}
.hand-row:has(.hand-mismatch-icon:hover) .mismatch-reason-desc {
    display: block;
}
/* Vertical divider between columns — added by applyHandColumnLayout() to
   every item that isn't in the last column. Not a simple nth-child
   pattern under column-major flow (unlike the old row-major grid's
   :nth-child(3n)), since which items land in the last column depends on
   each section's own row count, not a fixed multiple. */
.hand-row.hand-row-col-divider {
    border-right: 1px solid #eee;
}
.hand-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.hand-val {
    color: #999;
    font-size: 11px;
    margin-right: 8px;
    white-space: nowrap;
}

.qty-ctrl {
    display: flex;
    align-items: center;
    border: 1px solid #ccc;
    border-radius: 6px;
    overflow: hidden;
    flex-shrink: 0;
}
.qty-btn {
    width: 30px;
    height: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #f8f8f8;
    border: none;
    font-size: 16px;
    font-weight: bold;
    color: #555;
    cursor: pointer;
    padding: 0;
}
.qty-btn:active { background: #ddd; }
.qty-btn:disabled { opacity: 0.35; cursor: not-allowed; color: #999; }
.qty-num {
    width: 26px;
    text-align: center;
    font-size: 13px;
    font-weight: bold;
    color: #222;
    border-left: 1px solid #ccc;
    border-right: 1px solid #ccc;
    line-height: 30px;
}
.qty-num.has-val { color: #b32424; }

/* Only table/game.html and multiplayer/game.html actually render
   <input type="radio">; multiplayer overrides this to its own 26px/14px
   scale (see multiplayer/game.css's combined radio+checkbox selector). */
.picker-label input[type="radio"] {
    appearance: none;
    -webkit-appearance: none;
    width: 30px;
    height: 30px;
    border: 1px solid #ccc;
    border-radius: 6px;
    background: #f8f8f8;
    flex-shrink: 0;
    margin: 0;
    cursor: pointer;
    position: relative;
    padding: 0;
}
.picker-label input[type="radio"]:checked {
    background: #b32424;
    border-color: #b32424;
}
.picker-label input[type="radio"]:checked::after {
    content: '\2713';
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: 16px;
    font-weight: bold;
}
.picker-label input[type="radio"]:disabled {
    cursor: default;
    opacity: 0.6;
}

.btn {
    background: #b32424;
    color: #fff;
    border: none;
    padding: 0 24px;
    height: 44px;
    font-weight: bold;
    border-radius: 8px;
    cursor: pointer;
    font-size: 15px;
    box-shadow: 0 2px 4px rgba(179, 36, 36, 0.3);
}
.btn:active { background: #911d1d; transform: translateY(1px); }

/* Muted variant for terminal/de-emphasized actions (End Game) — matches
   multiplayer's existing inline #444 treatment for the same button. */
.btn-muted { background: #444; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); }
.btn-muted:active { background: #2e2e2e; }

/* Gray variant for non-destructive reset-style actions (Clear, Reset) —
   matches multiplayer's existing inline #777 treatment for its Clear button. */
.btn-gray { background: #777; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); }
.btn-gray:active { background: #5c5c5c; }

/* Game Total card — table/game.css, multiplayer/results.css, and
   multiplayer/final-results.css all had byte-identical copies. */
.game-total-card {
    margin-bottom: 10px;
    padding: 8px 10px;
    background: #f7f2ea;
    border: 1px solid #ddd0b8;
    border-radius: 8px;
}
.game-total-label {
    font-size: 12px;
    font-weight: 700;
    color: #6b5a3a;
    margin-bottom: 6px;
}
.game-total-row {
    display: flex;
    justify-content: space-around;
    gap: 8px;
}
.game-total-cell {
    display: flex;
    flex-direction: column;
    align-items: center;
    flex: 1 1 0;
}
.game-total-seat {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: #888;
}
.game-total-val {
    font-size: 18px;
    font-weight: 800;
    color: #222;
}
.game-total-val.pos-val { color: #1a7a33; }
.game-total-val.neg-val { color: #b32424; }

/* Payout/results table — same 3-file byte-identical match as Game Total
   card above. */
.table-responsive {
    width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    border-radius: 8px;
    border: 1px solid #ddd;
    /* Matches .hand-breakdown-list's own margin-bottom (and
       .payout-mobile-cards' mobile-width margin-bottom below) — without
       this, Running Total sat right up against whatever followed it while
       Hand Breakdown had a full 16px gap, an inconsistent look between two
       sibling collapsible sections. */
    margin-bottom: 16px;
}
table {
    width: 100%;
    min-width: 360px;
    border-collapse: collapse;
    font-size: 13px;
}
th, td {
    border: 1px solid #ddd;
    text-align: center;
    padding: 12px 4px;
}
th {
    background-color: #f2f2f2;
    font-weight: bold;
    color: #444;
    font-size: 11px;
    text-transform: uppercase;
}
td:first-child {
    background-color: #f2f2f2;
    font-weight: bold;
    width: 14%;
    color: #444;
}
.pay-col { width: 17%; }
.net-col {
    background-color: #fdf2f2;
    font-weight: bold;
    width: 18%;
    border-left: 2px solid #b32424;
}
th.net-col {
    background-color: #b32424 !important;
    color: #fff;
    border: 2px solid #b32424;
}
.diagonal {
    background-color: #eaeaea !important;
    color: #999;
}
.pos-val { color: #2e7d32; font-weight: bold; }
.neg-val { color: #c62828; font-weight: bold; }
.zero-val { color: #888; }

/* Mobile payout cards — same 3-file byte-identical match. */
.payout-mobile-cards { display: none; }
.mobile-payout-card {
    background: #fff;
    border: 1px solid #ddd;
    border-radius: 10px;
    padding: 10px 12px;
    margin-bottom: 8px;
    box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.mobile-payout-card-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 8px;
}
.mobile-payout-card-name {
    display: flex;
    align-items: center;
    gap: 6px;
}
.mobile-payout-card-name > span:first-child {
    font-weight: bold;
    font-size: 15px;
    color: #333;
}
.mobile-payout-card-badge {
    font-size: 11px;
    font-weight: bold;
    padding: 2px 6px;
    border-radius: 6px;
    background: #f2f2f2;
    color: #888;
}
.mobile-payout-card-net {
    font-size: 18px;
    font-weight: bold;
}
.mobile-tile-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 6px;
}
.mobile-tile {
    background: #f7f7f7;
    border-radius: 6px;
    padding: 6px 8px;
}
.mobile-tile-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.mobile-tile-seat {
    font-size: 12px;
    color: #333;
}
.mobile-tile-badge {
    font-size: 9px;
    font-weight: bold;
    padding: 1px 4px;
    border-radius: 4px;
    background: #eee;
    color: #888;
}
.mobile-tile-val {
    font-size: 13px;
    font-weight: bold;
    text-align: center;
    margin-top: 2px;
}

/* Hand Breakdown cards — multiplayer/results.css and final-results.css only,
   byte-identical between the two. */
.hand-breakdown-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-bottom: 16px;
}
.hand-breakdown-card {
    background: #fff;
    border: 1px solid #ddd;
    border-radius: 10px;
    overflow: hidden;
}
.hand-breakdown-card.pending {
    border-style: dashed;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 12px;
}
.hand-breakdown-note { font-size: 11px; color: #999; }
.hand-breakdown-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 8px 12px;
    background: #f8f8f8;
    flex-wrap: wrap;
}
.hand-breakdown-title { font-size: 12px; font-weight: 700; color: #555; }
.hand-breakdown-meta { font-size: 12px; color: #888; }
.hand-breakdown-meta b { color: #222; font-weight: 700; }
.hand-breakdown-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1px;
    background: #eee;
}
.hand-breakdown-cell {
    background: #fff;
    padding: 8px 12px;
    display: flex;
    justify-content: space-between;
    font-size: 13px;
    color: #555;
}
.hand-breakdown-score { font-weight: 700; color: #222; }
.hand-breakdown-score.win { color: #2e7d32; }

@media (max-width: 560px) {
    .table-responsive { display: none; }
    .payout-mobile-cards { display: block; margin-bottom: 16px; }
}

