/* === Reset === */
*, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    /* Kill Android Chrome / WebView's default blue tap-highlight rectangle
       that flashes up on `.card` and `.btn` touches — we supply our own
       hover/active styles. */
    -webkit-tap-highlight-color: transparent;
    /* Disable text selection and long-tap context menu app-wide.
       Opt in selectively with `.selectable { user-select: text }`. */
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
}

html, body {
    height: 100%;
    font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
    background: #0d3d1a;
}

/* === Game Table === */
/* Safe-area insets are applied to individual edge elements (header, west,
   east, action-dock, you-hud) rather than to the outer grid container,
   so the header background extends edge-to-edge on devices with non-zero
   left/right insets (e.g. Android gesture navigation).

   `position: fixed` + `inset: 0` + `margin: auto` with max-width/max-height
   centers the table in the viewport and caps its proportions on wide desktop
   windows. Without these caps, cards/buttons/labels sit at their clamp
   ceilings while the grid stretches to fill 1920×1080, leaving the middle
   play row as an expanse of empty green. The body's #0d3d1a background
   shows through as letterbox on all four sides. On phones and tablets the
   caps don't kick in — `inset: 0` fills the viewport edge-to-edge. Fixed
   positioning also still creates a containing block for the absolutely-
   positioned play slots, hand, action dock, you HUD, animation layer, and
   hint tooltip below. */
.game-table {
    display: grid;
    grid-template-areas:
        "header  header  header"
        "west    play    east";
    grid-template-columns: minmax(80px, 1fr) 3fr minmax(80px, 1fr);
    grid-template-rows: auto 1fr;
    position: fixed;
    inset: 0;
    margin: auto;
    max-width: 1100px;
    max-height: 820px;
    background: radial-gradient(ellipse at center, #1e7a3a 0%, #145a28 60%, #0d3d1a 100%);
    color: #e0e0e0;
    overflow: hidden;
    font-family: var(--te-font-body);
}

.game-table.loading {
    display: flex;
    align-items: center;
    justify-content: center;
}

.loading-text {
    font-size: 1.5rem;
    color: #aaa;
}

/* === Header Bar === */
/* Compact pill bar across the top of the table: a meta pill on the left
   (Trick / Level), a centered pair of team pills (Team A · rank, Team B ·
   rank), and a small icon button cluster on the right (Undo, History).
   Safe-area insets live on the side pills' padding so the chrome reads
   edge-to-edge on devices with non-zero left/right insets, while content
   stays clear of display cutouts. */
.header-bar {
    grid-area: header;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 8px;
    padding: calc(env(safe-area-inset-top) + 6px)
             calc(10px + env(safe-area-inset-right))
             6px
             calc(10px + env(safe-area-inset-left));
    background: linear-gradient(180deg, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0));
    font-size: 0.72rem;
    color: var(--te-fg-strong);
}

/* Groups the leftmost cluster of header chrome (quit-to-menu button +
   trick/level meta pill) so they stay together at the start of the row,
   leaving the icon cluster flush to the right via the parent's
   space-between. */
.header-left {
    display: flex;
    align-items: center;
    gap: 6px;
}

/* Generic pill wrapper used by the meta + team pills. Sized to the same
   38px tap target as .icon-btn so the Trick · Level toggle is finger-
   friendly and visually balanced with the surrounding buttons. */
.header-pill {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: rgba(0, 0, 0, 0.45);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: var(--te-radius-lg);
    padding: 0 14px;
    min-height: 38px;
    font-size: 0.9rem;
    white-space: nowrap;
    line-height: 1;
}

/* Wraps the meta pill + its expandable dropdown so the dropdown
   anchors directly under the pill without disturbing the rest of
   the header layout. */
.header-meta-wrap {
    position: relative;
    display: inline-flex;
    flex-direction: column;
    align-items: flex-start;
}

/* The meta pill rendered as a button: clears default <button> chrome
   and inherits font/color from the pill. */
.header-meta-toggle {
    cursor: pointer;
    color: inherit;
    font: inherit;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.header-meta-toggle:hover,
.header-meta-toggle.is-open {
    background: rgba(0, 0, 0, 0.65);
    border-color: rgba(255, 255, 255, 0.2);
}

.header-meta-caret {
    color: #9fb7a8;
    font-size: 0.7rem;
    margin-left: 2px;
    transition: transform 0.18s ease;
}

.header-meta-toggle.is-open .header-meta-caret {
    transform: rotate(180deg);
}

/* Dropdown panel that hangs under the meta pill, holding the team-
   level pills. Matches the pill chrome (dark fill, soft border) and
   stacks above the play area so it isn't covered by the partner
   badge or play slots underneath. */
.header-meta-dropdown {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 6px;
    background: rgba(0, 0, 0, 0.78);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: var(--te-radius-lg);
    z-index: 20;
    animation: header-meta-dropdown-in 140ms ease-out;
}

@keyframes header-meta-dropdown-in {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}

.header-meta-label {
    color: #9fb7a8;
    letter-spacing: 0.04em;
}

.header-meta-value {
    font-weight: 700;
    color: var(--te-fg-strong);
}

.header-dot {
    color: #9fb7a8;
}

.header-rank-badge {
    display: inline-grid;
    place-items: center;
    width: 24px;
    height: 24px;
    border-radius: 6px;
    background: var(--te-accent-gold);
    color: #2a1a00;
    font-family: var(--te-font-display);
    font-weight: 800;
    font-size: 14px;
    line-height: 1;
    padding: 0 2px;
}

.header-rank-badge-sm {
    width: 22px;
    height: 22px;
    font-size: 13px;
}

.header-teams {
    display: flex;
    align-items: center;
    gap: 6px;
    flex: 1;
    justify-content: center;
    min-width: 0;
}

.team-pill {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    border-radius: var(--te-radius-lg);
    padding: 0 12px;
    min-height: 32px;
    line-height: 1;
    background: rgba(0, 0, 0, 0.45);
    border: 1px solid rgba(255, 255, 255, 0.1);
    white-space: nowrap;
}

/* Both team pills get a subtle gradient tint matching their team colour;
   the human's own team also picks up a coloured border so it reads as
   "this is you" without an explicit YOU tag dominating the row. */
.team-pill-a {
    background: linear-gradient(180deg, rgba(126, 200, 227, 0.2), rgba(0, 0, 0, 0.4));
}

.team-pill-b {
    background: linear-gradient(180deg, rgba(232, 168, 124, 0.18), rgba(0, 0, 0, 0.4));
}

.team-pill.is-you {
    border-color: var(--te-team-a);
}

.team-pill-name {
    font-weight: 800;
    letter-spacing: 0.06em;
    font-size: 0.78rem;
}

.team-pill-a .team-pill-name { color: var(--te-team-a); }
.team-pill-b .team-pill-name { color: var(--te-team-b); }

.team-pill-tag {
    color: var(--te-team-a);
    font-size: 0.6rem;
    letter-spacing: 0.08em;
    opacity: 0.85;
    font-weight: 700;
}

.header-icons {
    display: flex;
    gap: 8px;
}

/* Sized for thumb-tap on phones in landscape: 38×38 sits close to the
   recommended ~44pt touch target while keeping the header chrome dense
   enough to leave the play area intact. The previous 26×26 was hard to
   hit reliably with a finger. */
.icon-btn {
    background: rgba(0, 0, 0, 0.45);
    border: 1px solid rgba(255, 255, 255, 0.1);
    color: #cfd8d2;
    border-radius: 8px;
    width: 38px;
    height: 38px;
    cursor: pointer;
    display: grid;
    place-items: center;
    font-size: 18px;
    line-height: 1;
    padding: 0;
    transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.icon-btn-svg {
    width: 20px;
    height: 20px;
    display: block;
}

.icon-btn:not(:disabled):hover {
    background: rgba(0, 0, 0, 0.7);
    color: var(--te-fg-strong);
    border-color: rgba(255, 255, 255, 0.25);
}

.icon-btn:disabled {
    opacity: 0.35;
    cursor: default;
}

/* Legacy single-color team text classes still referenced by other panels
   (overlay, status messages). Keep them working while the header switches
   to the pill-based variants above. */
.team-a { color: var(--te-team-a); }
.team-b { color: var(--te-team-b); }
.level-separator { color: #666; }

/* === Opponent Areas === */
/* Each opponent renders as a flex container holding the badge AND the
   seat's .play-position (cards or pass chip). Pairing them as siblings
   lets the cards anchor to the badge regardless of the player's name
   length: short names → tight pill → cards land flush next to it;
   long names → wider pill → cards push outward in lockstep. Per-seat
   flex-direction puts the cards on the inside-of-table side:
   row → west (badge LEFT, cards RIGHT)
   row-reverse → east (badge RIGHT, cards LEFT)
   column → north (badge TOP, cards BOTTOM).
   .play-position inside opp-area is statically positioned (see the
   override under "=== Play Positions ==="); only the south slot keeps
   its absolute positioning because PlayArea still renders it outside
   any badge container. */
.opponent-area {
    display: flex;
    align-items: flex-end;
    gap: 10px;
    padding: 6px;
}

/* North opponent sits in the now-empty center of the header band, with
   the partner badge tucked between the Trick/Level pill on the left and
   the icon cluster on the right. Pulled out of the grid so the play row
   spans the full width between west/east, leaving more vertical room
   for tall hand stacks below. The +6px on top aligns the badge with the
   header-bar's content baseline (which itself adds 6px of breathing room
   under the safe-area inset) so the partner pill sits on the same line
   as the Trick/Level pill and the icon buttons. */
.opponent-area.pos-north {
    position: absolute;
    top: calc(env(safe-area-inset-top) + 6px);
    left: 50%;
    transform: translateX(-50%);
    padding-top: 0;
    z-index: 5;
    /* Column stack puts the partner's played cards below the badge. The
       gap is the breathing band between badge-bottom and card-stack-top
       — tuned to ~14px to match the user's calibrated taste from the
       side seats' horizontal gap. */
    flex-direction: column;
    align-items: center;
    gap: 14px;
}

/* East/West badges float absolutely on the table (out of the grid) so
   their vertical center sits at the same 38% line as the action dock —
   percentage-based, so it scales with viewport height instead of the
   old fixed `padding-top: 100px`. The grid columns reserved for them
   stay in place to keep the play row's width capped on wide screens.
   Their played cards (`.play-position.pos-west/east` below) are
   bottom-anchored to the same 38% line so cards and badge form a
   tight horizontal pair on every screen size. */
.opponent-area.pos-west {
    position: absolute;
    /* Anchored by BOTTOM (at `62% - 26px` from bottom = 38% + 26px from
       top — i.e., the y-line where the old center-anchored badge had
       its bottom edge, 26px below the 38% midpoint) rather than centred
       on 38% via translateY(-50%). Center-anchoring made the badge
       bottom shift down when a play-position was added (opp-area height
       grows from 52 to 84, center fixed, so bottom moves down 16px) and
       pop back up when the trick cleared. With a bottom anchor opp-area
       grows UPWARD where the empty space is — badge bottom stays put on
       the same baseline, no wiggle. */
    bottom: calc(62% - 26px);
    left: calc(6px + env(safe-area-inset-left));
    z-index: 5;
    /* Default row direction → badge on the LEFT (close to screen edge),
       cards on the RIGHT (toward the play area). align-items: flex-end
       from the parent rule bottom-aligns the cards with the badge so
       they form a horizontal pair growing upward. */
    flex-direction: row;
}

.opponent-area.pos-east {
    position: absolute;
    bottom: calc(62% - 26px);
    right: calc(6px + env(safe-area-inset-right));
    z-index: 5;
    /* row-reverse → badge on the RIGHT (close to screen edge), cards
       on the LEFT (toward the play area). Mirrors pos-west. */
    flex-direction: row-reverse;
}

.opponent-badge {
    /* East/West default: name on its own top row, avatar pairs with the
       status row below. Narrows the pill horizontally — the name no longer
       has to sit next to the avatar — so it intrudes less into the play
       area. Side seats can absorb the extra row of vertical height; the
       partner seat (pos-north) overrides this to a single-row layout
       below, since the header band is short on vertical room but has
       horizontal room to spare. */
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
        "name   name"
        "avatar status";
    align-items: center;
    column-gap: 8px;
    row-gap: 2px;
    padding: 6px 10px;
    background: rgba(0, 0, 0, 0.55);
    border: 1.5px solid rgba(255, 255, 255, 0.1);
    border-radius: var(--te-radius-lg);
    transition: border-color 0.18s ease, box-shadow 0.18s ease,
                background 0.18s ease;
    pointer-events: auto;
    max-width: 100%;
    /* Anchor + clip the active-turn timer bar to the pill's rounded edges. */
    position: relative;
    overflow: hidden;
}

.opponent-area.pos-north .opponent-badge {
    grid-template-columns: auto minmax(0, auto) auto;
    grid-template-areas: "avatar name status";
    row-gap: 0;
}

.opponent-area.team-a .opponent-badge { border-color: var(--te-team-a); }
.opponent-area.team-b .opponent-badge { border-color: rgba(232, 168, 124, 0.55); }

.opponent-badge > .avatar { grid-area: avatar; }

.opponent-name-row {
    grid-area: name;
    display: flex;
    align-items: center;
    gap: 5px;
    line-height: 1.1;
    min-width: 0;
}

.opponent-status-row {
    grid-area: status;
    display: flex;
    align-items: center;
    gap: 5px;
    line-height: 1.1;
    min-width: 0;
}

.opponent-name {
    font-weight: 700;
    font-size: 0.78rem;
    color: var(--te-fg-strong);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 90px;
}

.opponent-team {
    font-size: 0.52rem;
    letter-spacing: 0.08em;
    font-weight: 700;
}

.opponent-area.team-a .opponent-team { color: var(--te-team-a); }
.opponent-area.team-b .opponent-team { color: var(--te-team-b); }

.card-count {
    display: inline-grid;
    place-items: center;
    min-width: 22px;
    height: 18px;
    padding: 0 5px;
    border-radius: 4px;
    background: rgba(255, 255, 255, 0.12);
    color: var(--te-fg-strong);
    font-weight: 800;
    font-size: 11px;
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

.card-count.card-count-warning {
    background: linear-gradient(180deg, var(--te-accent-red), #b53e3e);
    box-shadow: 0 0 8px rgba(229, 79, 79, 0.5);
}

.card-count-label {
    font-size: 0.54rem;
    color: #9fb7a8;
    letter-spacing: 0.08em;
    font-weight: 700;
}

/* On narrow viewports every player pill needs to shed weight: the
   partner pill (centred in the header band) collides with the Trick /
   Level pill and the icon-button cluster on either side; the side
   pills and the you-hud are competing with the trick area and the
   hand for horizontal room. Drop the "CARDS" label and the TEAM A /
   TEAM B chip on every badge — the card-count chip alone communicates
   remaining cards, and team membership is already conveyed by the
   pill's coloured border (and team-coloured played cards). The
   you-hud-name "You" stays visible here; the existing <=480px rule
   below drops it entirely when the dock and the hud start sharing
   the same vertical band. */
@media (max-width: 650px) {
    .card-count-label,
    .opponent-team,
    .you-hud-tag {
        display: none;
    }
    /* The play-position and player-hand size overrides for this same
       breakpoint live further down in the file (search "<=650px layout
       follow-through") so they appear AFTER the base rules they
       override and actually win the cascade — these rules share the
       same selector specificity as the base ones, so source order
       decides. */
}

.out-badge {
    color: var(--te-team-b);
    font-weight: 700;
    font-size: 0.62rem;
    letter-spacing: 0.08em;
    background: rgba(232, 168, 124, 0.18);
    border: 1px solid rgba(232, 168, 124, 0.45);
    padding: 2px 6px;
    border-radius: 4px;
}

/* === Seat activity states ===
   Driven by SeatActivityState on the runner — see SeatActivityState.cs.
   Each state colours the badge border (and optionally the avatar / name)
   so a player can read AFK / disconnect / autoplay status at a glance.
   The classes layer on top of .pos-* and .team-*; .active-turn (gold)
   takes precedence on the colour by appearing later in the cascade for
   the seat actively taking its turn (the active player can also be
   AFK-warned simultaneously — the gold turn glow wins).

   .s-takeover never co-exists with active-turn (the autoplay seat does
   take turns, but the engine sends those turns through an AI provider
   that doesn't carry the human "active" treatment). */
.opponent-area.s-warn .opponent-badge,
.you-hud.s-warn {
    border-color: var(--te-accent-amber);
    box-shadow: 0 0 14px rgba(255, 211, 107, 0.45);
    background: linear-gradient(160deg, rgba(255, 211, 107, 0.20), rgba(0, 0, 0, 0.55));
}

.opponent-area.s-takeover .opponent-badge,
.you-hud.s-takeover {
    border-color: rgba(255, 255, 255, 0.14);
    box-shadow: none;
    background: rgba(0, 0, 0, 0.62);
}
.opponent-area.s-takeover .opponent-name,
.you-hud.s-takeover .you-hud-name {
    color: var(--te-fg-disabled);
    text-decoration: line-through;
    text-decoration-color: rgba(255, 255, 255, 0.35);
}
/* AvatarView renders a .avatar medallion inside the badge — dim it via a
   descendant selector rather than reaching into AvatarView's internals. */
.opponent-area.s-takeover .opponent-badge .avatar,
.you-hud.s-takeover .avatar {
    filter: grayscale(0.85) brightness(0.7);
}

.opponent-area.s-disc .opponent-badge,
.you-hud.s-disc {
    border-color: var(--te-accent-blue);
    box-shadow: 0 0 14px rgba(33, 150, 243, 0.45);
    background: linear-gradient(160deg, rgba(33, 150, 243, 0.18), rgba(0, 0, 0, 0.55));
}
.opponent-area.s-disc .opponent-badge .avatar,
.you-hud.s-disc .avatar {
    filter: brightness(0.7);
}

.opponent-area.s-reconn .opponent-badge,
.you-hud.s-reconn {
    border-color: var(--te-accent-green);
    box-shadow: 0 0 14px rgba(76, 175, 80, 0.4);
    background: linear-gradient(160deg, rgba(76, 175, 80, 0.18), rgba(0, 0, 0, 0.55));
}

/* active-turn paints over any seat-activity state. Placed AFTER the .s-*
   block so identical-specificity rules resolve in active-turn's favour for
   the seat actively taking its turn. (The you-hud rule mirrors this — it
   appears later in the file at the .you-hud.active-turn declaration.) */
.opponent-area.active-turn .opponent-badge {
    border-color: var(--te-accent-gold);
    box-shadow: 0 0 14px rgba(255, 215, 0, 0.4);
    background: linear-gradient(160deg, rgba(255, 215, 0, 0.22), rgba(0, 0, 0, 0.55));
}

/* Pulsing dot pinned to the avatar corner — at-a-glance status from across
   the table. Pinned to the badge, not the avatar, so it sits cleanly outside
   the avatar's circular border. */
.opponent-pulse {
    position: absolute;
    bottom: 2px;
    left: 32px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    border: 2px solid #0d3d1a;
    pointer-events: none;
}
.opponent-pulse.warn {
    background: var(--te-accent-amber);
    animation: opponent-pulse-soft 1.6s ease-in-out infinite;
}
.opponent-pulse.disc {
    background: var(--te-accent-blue);
    animation: opponent-pulse-soft 1.4s ease-in-out infinite;
}
.opponent-pulse.takeover {
    background: #6e6e6e;
}

@keyframes opponent-pulse-soft {
    0%, 100% { transform: scale(1); opacity: 1; }
    50%      { transform: scale(1.25); opacity: 0.75; }
}

/* Status tag sits in the .opponent-status-row alongside the card-count —
   the same slot the CARDS label normally occupies. Variants pick their
   colour from the palette; .reconn briefly fades in and out for the 1.5s
   "BACK" flash. */
.status-tag {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.56rem;
    font-weight: 800;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: 2px 6px;
    border-radius: 4px;
    line-height: 1;
    white-space: nowrap;
}
.status-tag-glyph { font-size: 0.72rem; line-height: 1; }
.status-tag.warn {
    background: rgba(255, 211, 107, 0.18);
    border: 1px solid rgba(255, 211, 107, 0.55);
    color: var(--te-accent-amber);
}
.status-tag.takeover {
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.18);
    color: var(--te-fg-quiet);
}
.status-tag.disc {
    background: rgba(33, 150, 243, 0.18);
    border: 1px solid rgba(33, 150, 243, 0.55);
    color: var(--te-accent-blue);
}
.status-tag.reconn {
    background: rgba(76, 175, 80, 0.18);
    border: 1px solid rgba(76, 175, 80, 0.55);
    color: var(--te-accent-green);
}

/* === Turn Timer Bar ===
   Slim depleting bar rendered by <TurnTimerBar/> inside any active-turn
   container (.opponent-badge, .you-hud, .action-dock). The parent
   provides positioning context (`position: relative`) and clipping
   (`overflow: hidden`) so the bar's edges follow the parent's
   border-radius. The component sets transform: scaleX(remaining/total)
   on the inner span; we deliberately omit a CSS transition because the
   100ms client-side tick refreshes the value frequently enough to look
   smooth without one (and re-anchors from server marks land cleanly). */
.turn-timer-bar {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 3px;
    background: rgba(0, 0, 0, 0.35);
    pointer-events: none;
}

.turn-timer-bar > span {
    display: block;
    height: 100%;
    width: 100%;
    transform-origin: left center;
}

.turn-timer-bar.fresh > span {
    background: var(--te-accent-gold);
    box-shadow: 0 0 6px rgba(246, 168, 6, 0.7);
}

.turn-timer-bar.danger > span {
    background: var(--te-accent-red);
    box-shadow: 0 0 8px rgba(231, 76, 60, 0.85);
    animation: turn-timer-pulse 1s ease-in-out infinite;
}

@keyframes turn-timer-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.55; }
}

/* Escalate the surrounding pill (opponent badge or you-hud) to a red
   border + glow whenever its timer bar is in the danger state. Driven
   by :has() so the danger threshold lives in one place — inside the
   TurnTimerBar component — rather than being recomputed per parent.
   The selectors anchor on the same `active-turn` ancestor as the gold
   rule above (specificity 030) so the danger rule wins by appearing
   later in the cascade — no !important needed. */
.opponent-area.active-turn .opponent-badge:has(.turn-timer-bar.danger),
.you-hud.active-turn:has(.turn-timer-bar.danger) {
    border-color: var(--te-accent-red);
    box-shadow: 0 0 18px rgba(231, 76, 60, 0.55);
    background: linear-gradient(160deg, rgba(231, 76, 60, 0.22), rgba(0, 0, 0, 0.55));
}

/* In the action dock the bar sits above the StatusBar/ActionBar in the
   column layout (align-items: center) and would otherwise collapse to
   its content width; stretch it to span the dock's natural width.
   Position is relative-static so it flows with the column rather than
   pinning to the dock's edge — the dock has no rounded clipping
   container of its own. */
.action-dock > .turn-timer-bar {
    position: relative;
    align-self: stretch;
    border-radius: var(--te-radius-pill);
    overflow: hidden;
}

/* === Play Area === */
/* `position: relative` anchors the absolutely-positioned `.play-position`
   children. No z-index here — we deliberately avoid creating a stacking
   context on `.play-area` so each `.play-position` can compete with
   `.player-hand` individually (see `.play-position` below).

   `pointer-events: none` lets clicks on the (mostly transparent)
   play area pass through to the player's hand below — without it,
   the hand's deeper-stacked cards (4th, 5th, …) couldn't be
   selected because the play area was eating their clicks. The
   `.play-position` children re-enable pointer events for the
   actually-painted played cards. */
.play-area {
    /* Pulled out of the grid and stretched over the entire game-table
       (inset: 0) so its absolutely-positioned `.play-position` children
       share the same coordinate system as the action dock and the
       opponent badges (both also absolute on .game-table). Without this,
       the side play-positions resolve against the play-area's grid cell
       which starts ~1/5 of the way in — leaving a stubborn horizontal
       gap between the badge (anchored at the table edge) and the cards.
       `pointer-events: none` keeps clicks passing through the now-
       fullscreen layer; only the play-position children re-enable them. */
    position: absolute;
    inset: 0;
    pointer-events: none;
}

/* `z-index: 10` stacks played cards above the absolutely-positioned
   `.player-hand` (z-index 1) so the human's own played cards
   (pos-south) render on top when a tall hand stack extends upward
   into the play area. Opponent positions (west/east/north) also
   need to sit above the hand because a tall hand stack can reach
   into those areas as well. */
.play-position {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    z-index: 10;
    pointer-events: auto;
}

/* South stays absolutely positioned inside .play-area (PlayArea renders
   it outside any opponent badge). The other three seats sit as flex
   children of their .opponent-area and don't need absolute positioning
   — the parent's flex-direction puts them next to / below the badge.
   The .play-area > selector scopes absolute positioning to PlayArea's
   direct children (south always, plus north in spectator mode), so the
   nested in-badge play-positions in OpponentArea retain static layout. */
.play-area > .play-position.pos-south,
.play-area > .play-position.pos-north {
    position: absolute;
    /* Smoothly slide between the default and `with-partner-hand` layouts
       so any cards already sitting in the slot don't visibly snap-jump
       on the toggle — without this, the human's just-landed last play
       would appear to "reappear" at a new location the moment
       partner-hand mounts. */
    transition: top 220ms cubic-bezier(0.2, 0.7, 0.3, 1),
                bottom 220ms cubic-bezier(0.2, 0.7, 0.3, 1);
}

/* Spectator-mode partner slot: PlayArea renders pos-north only when
   ShowPartnerHand is true. The slot lands at mid-screen so it doesn't
   tuck under the spectator partner-hand strip at the top. */
.play-area > .play-position.pos-north {
    top: 50%;
    left: 50%;
    transform: translateX(-50%);
    min-height: clamp(58.8px, 8.064vw, 84px);
    justify-content: flex-end;
}

/* The play-position inside an opponent badge keeps the big-card height
   reserved via min-height so it doesn't visually collapse when the seat
   has no play yet. */
.opponent-area .play-position {
    min-height: clamp(58.8px, 8.064vw, 84px);
    justify-content: flex-end;
}

/* Once the human's own played cards are no longer the winning play
   (they shrink via `IsSmall` in PlayArea.razor), drop them BELOW
   `.player-hand` so the shrunk cards no longer intercept clicks on
   the hand — in particular, tall hand stacks (bombs) that extend
   upward into the play-area remain selectable. */
.play-position.pos-south.losing {
    z-index: 0;
}

/* pos-east / pos-west are centered vertically; pos-south sits just below
   them so the top edge of the human's played cards lines up with the
   bottom of the small cards in pos-east / pos-west. pos-north sits at the
   top of the play-area, just below the header/north-label row.
   The winning play renders at "normal" (non-small) card size — see
   PlayArea.razor — which is the visual highlight; losing plays stay
   small. */
/* South (human) played cards sit just below the centered action dock —
   above mid-screen — so they read together with the played cards from
   the side opponents instead of being pushed down toward the hand. */
.play-position.pos-south {
    top: calc(50% - 30px);
    bottom: auto;
    left: 50%;
    transform: translateX(-50%);
    min-height: clamp(58.8px, 8.064vw, 84px);
    justify-content: flex-end;
}
/* pos-north / pos-east / pos-west play-positions live inside their
   .opponent-area and are positioned by the parent's flex layout — see
   .opponent-area.pos-* above and `.opponent-area .play-position` for
   the shared min-height. No absolute-positioning rules needed here. */
/* Side seats' play-positions are flex children of their .opponent-area
   (see .opponent-area.pos-east / .pos-west above). The parent's row /
   row-reverse direction puts cards on the inside-of-table side; the
   parent's `gap: 10px` is the breathing band between badge and cards;
   align-items: flex-end bottom-aligns cards with the badge. No further
   rules needed for the side play-positions. */

.played-cards {
    display: flex;
}

/* Smoothly shrink the previously-winning play when a new combination
   beats it. The `.small` class is toggled by PlayArea.razor (or
   kicked off synchronously by cardAnimations.play for the south case)
   when the play loses `winner` status. Scoped to `.play-position
   .card.small` -- not `.play-position .card` -- so the transition
   is defined ONLY when the element has `.small`. When `.small` is
   ADDED (losing), the new state's transition fires and width
   smoothly contracts; when `.small` is REMOVED (re-winning, e.g. an
   opponent plays a second combo on top of their own), the new state
   has no transition defined and width snaps instantly -- which is
   what ghost clones need, since they're measured right after Blazor
   re-renders and must read the final big rect. */
.play-position .card.small {
    transition:
        width 260ms cubic-bezier(0.2, 0.7, 0.3, 1),
        margin-left 260ms cubic-bezier(0.2, 0.7, 0.3, 1),
        box-shadow 260ms ease;
}

/* Played cards overlap horizontally at exactly the same proportion as the
   hand columns (`.card-column + .card-column`). Normal-size (winning)
   plays use the hand's clamp verbatim because they are the same width as
   hand cards. Small (losing) plays scale the clamp by the small/normal
   card-width ratio (36/60 = 0.6) so the visible portion of each card is
   the same fraction as in the hand. */
.played-cards .card + .card {
    margin-left: clamp(-13px, -2.5vw, -6px);
}

.played-cards .card.small + .card.small {
    margin-left: clamp(-8px, -1.5vw, -4px);
}

/* Absolute-positioned so the label's presence/absence doesn't change
   `.play-position`'s intrinsic height. When the seat transitions from
   winning to losing, Blazor removes the label element at re-render --
   if the label were a flex child, removing it would shrink the slot
   and the `translateY(-50%)` centering would re-center the cards,
   producing a visible vertical jump right after the shrink animation
   finishes. Sitting the label under the cards via absolute positioning
   keeps the slot's flex height equal to `.played-cards` alone. */
.play-label {
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    margin-top: 2px;
    font-size: 0.65rem;
    color: #ccc;
    white-space: nowrap;
    pointer-events: none;
}

.pass-label {
    font-size: 0.8rem;
    color: #999;
    font-style: italic;
    padding: 8px;
}

/* PASS chip rendered in a player's .play-position when they passed in
   the current trick. Solid dark fill (the dock and the cards both
   render opaque, so the trick area should match) with a quiet border.
   The chip is removed while it's that player's turn (PlayArea
   suppresses it via CurrentTurnPlayerId), so when they pass again the
   element is recreated and the pass-chip-in keyframe replays — every
   pass reads as a fresh event, never a silent re-render. */
.pass-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 5px 14px;
    border-radius: 999px;
    background: #1a1a1a;
    border: 1px solid rgba(255, 255, 255, 0.18);
    color: #cfd8d2;
    font-family: var(--te-font-body);
    font-weight: 800;
    font-size: 0.72rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    line-height: 1;
    pointer-events: none;
    animation: pass-chip-in 220ms ease-out;
}

@keyframes pass-chip-in {
    from { opacity: 0; transform: scale(0.85); }
    to   { opacity: 1; transform: scale(1); }
}

/* Partner's PASS chip sits at the TOP of the reserved slot (closest to
   the badge above) instead of bottom-anchored where the cards would
   have grown to. The slot is `justify-content: flex-end` by default
   (so cards shrink "to the bottom" of the reserved frame); `margin-
   bottom: auto` absorbs the slack below the chip and pulls it to the
   top of the column. Scoped to pos-north only — pos-south's chip stays
   bottom-anchored next to the action dock. */
.opponent-area.pos-north .play-position > .pass-chip {
    margin-bottom: auto;
}

/* === Action Dock === */
/* Centered horizontally and offset above mid-screen: the combo-status
   pill sits above HINT · PASS/PLAY, sized for thumb reach in
   landscape (mirrors the prototype's CenterActionDock). Z-index sits
   above `.player-hand` (z:1) and `.play-position` (z:10) so the dock
   is always readable; tall hand stacks that reach into the dock's
   vertical band pass behind it. */
.action-dock {
    position: absolute;
    top: 38%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 11;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    /* The dock layout container itself does NOT eat clicks — only the
       actual button/pill surfaces inside it do (re-enabled below).
       Without this, the 8px column gap between status-bar and
       action-bar plus the 10px row gap between the dock's buttons
       capture taps that should fall through to the cards underneath.
       Tall hand stacks (8-card bombs etc.) extend up into the dock's
       footprint on small screens and need those gaps transparent so
       their top cards remain selectable. */
    pointer-events: none;
    max-width: calc(100% - 24px);
}

/* When the human has played out, the dock collapses to a single SKIP
   button that stays visible while opponents auto-play through the
   trick. On small screens the partner's pos-north play reaches into
   the dock's footprint, and mid-flight the ghost on
   `.card-animation-layer` (z:20) would otherwise paint over SKIP. Lift
   the SKIP-only dock above the animation layer so the only control
   the player has left is never occluded by flying cards. Scoped to
   skip-only so the human's own play animation during their turn still
   arcs over the (now-hidden) dock as before. */
.action-dock.skip-only {
    z-index: 21;
}

/* Restore click capture on the actual interactive surfaces inside
   the (now click-through) action-dock. The flex containers around
   them stay click-through so dock-internal whitespace doesn't block
   the hand. */
.action-dock .status-pill,
.action-dock .btn {
    pointer-events: auto;
}

/* While the user is mid-drag on the player hand, raise the hand
   above the action dock instead of lowering the dock. Lowering the
   dock would also drop it below the opponents' played cards (pos-*
   at z-index 10), which the user explicitly does not want — partner
   / east / west cards must stay visually beneath the action dock
   even mid-drag. Raising .player-hand to z-index: 12 puts:
       12  .player-hand (cards on top)
       11  .action-dock (buttons above opponent cards)
       10  .play-position (opponent cards stay below the dock)
        1  default
   so the player's own cards visually overlap the buttons (allowing
   drag-to-select on cards that sit directly under the dock) while
   the dock remains the topmost layer for the opponent seats.

   `.player-hand` itself becomes pointer-events: none so its empty
   regions don't intercept the underlying buttons; the .card
   children re-enable hit-testing so drag-select still works. The
   buttons are also dropped to pointer-events: none so
   document.elementFromPoint inside drag-select skips past them
   to the hand card underneath.

   Toggled by `.cards-dragging` on .game-table from drag-select.js,
   scoped to actual drag movement (>6px slop) so simple taps don't
   trip the layer swap. */
.game-table.cards-dragging .player-hand {
    z-index: 12;
    pointer-events: none;
}

.game-table.cards-dragging .player-hand .card {
    pointer-events: auto;
}

.game-table.cards-dragging .action-dock .btn {
    pointer-events: none;
}

/* === Status Bar === */
/* Constant `min-height` keeps the dock stable when the status text changes
   so the buttons below don't jitter on selection. The status surface is a
   pill that swaps colour for success / error / info / muted variants. */
.status-bar {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    min-height: 24px;
    font-size: 0.72rem;
    line-height: 1.15;
}

/* Status pill backgrounds are OPAQUE for the same reason the action
   buttons are — the dock floats over the hand, and any translucency
   lets the cards bleed through and makes the label unreadable. */
.status-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 12px;
    border-radius: 999px;
    font-weight: 700;
    letter-spacing: 0.04em;
    white-space: nowrap;
    border: 1px solid transparent;
    background: #1a1a1a;
}

.status-pill-success {
    color: #9bd6a3;
    background: #1a3320;
    border-color: rgba(74, 156, 87, 0.6);
}

.status-pill-error {
    color: #e87b7b;
    background: #3a1818;
    border-color: rgba(229, 79, 79, 0.45);
}

.status-pill-info {
    color: var(--te-accent-gold);
    background: #1a1a1a;
    border-color: rgba(255, 215, 0, 0.35);
}

.status-pill-muted {
    color: #cfd8d2;
    background: #1a1a1a;
    border-color: rgba(255, 255, 255, 0.1);
}

.status-pill-text {
    line-height: 1.1;
}

/* === Player Hand === */
/* Absolutely-positioned inside the grid container so the hand owns
   a tall slot from just below the header down to the bottom of the
   table. Cards bottom-align inside, so short stacks stay low and
   tall (8-card) stacks extend upward — visually overlapping the
   north label / play area when needed rather than pushing other
   grid rows. The left/right insets match the middle grid column so
   the hand is centered under the north label and the actions column
   on the right stays clear. */
.player-hand {
    position: absolute;
    /* Hand is wider than the middle grid column so cards have room to
       spread on larger screens. On small screens the edges extend under
       the side opponent badges, but adjustLayout() in JS accounts for
       this by computing overlap based on the visible width (excluding
       the area hidden under those badges). The right inset matches the
       left so the hand stays visually centered between the seat badges. */
    left: 14%;
    right: 14%;
    /* `top` tracks the header row: ~38-44px of content height + whatever
       the device reports for the top safe-area inset (notches / status
       bars). Without the env() component the hand would overlap the
       header on notched devices where the header pills are pushed down
       by their own safe-area-inset-top padding. */
    top: calc(44px + env(safe-area-inset-top));
    /* Hand bottom-aligns just above the device's bottom safe-area inset.
       The action dock floats centered on top of the hand at z-index 11
       (above hand at 1 and play-positions at 10), so tall stacks pass
       BEHIND the buttons rather than displacing them. */
    bottom: calc(env(safe-area-inset-bottom) + 4px);
    /* Lower than `.play-position`'s `z-index: 10` so the winning
       played cards overlap a tall hand-stack, rather than the other
       way around. Still higher than the default stacking order of
       the opponent areas so the hand is free to extend upward into
       the north/east/west opponent cells. Losing pos-south cards
       drop to `z-index: 0` (see `.play-position.pos-south.losing`)
       so they don't intercept clicks on the hand below them. */
    z-index: 1;
    display: flex;
    justify-content: center;
    align-items: flex-end;
    /* Horizontal padding reserves clearance for the bottom-left
       .you-hud (and, symmetrically on the right, the bottom-right
       action dock's leftmost reach). The you-hud after the avatar
       addition is ~140px wide at left:env+10, so its right edge is
       at env + 150 — the 150px envelope keeps the leftmost card
       column flush with the hud without tucking behind it.

       West/East pills are deliberately NOT factored in: those badges
       sit at top:38% and the hand is bottom-anchored, so even an
       8-card bomb stack only reaches ~y=540 on a max-height table
       — well below the pills' vertical band. A hand column intruding
       horizontally under a side badge therefore never visually
       overlaps it, and reserving full side-pill clearance would shove
       the entire hand inward by another 6-10px for no visible gain.
       The full max-width 90 of .opponent-name is intentionally NOT
       reserved for: a 13-rank deal (with up to 2 joker columns = ~15
       columns) would otherwise eat so much padding on common desktop
       viewports that .card-column's fit-overlap kicks in and squeezes
       columns tighter than the base overlap — visible as cards being
       more overlapped right after the deal, springing back once columns
       are played. Long names take the trade-off via .opponent-name's
       existing text-overflow: ellipsis.

       Each side subtracts the hand container's own inset (left:14% /
       right:14%) so the padding is the *additional* clearance needed
       beyond what the inset already provides. The max(0, ...) clamps
       to zero on wide viewports where the inset alone is sufficient.
       Computed statically — and crucially, NOT a function of column
       count — so the padding doesn't change as the hand shrinks during
       a play. Earlier --col-count-driven scaling triggered a small
       post-animation reflow when the leftmost column collapsed and
       the survivors had to re-center against new padding. */
    padding:
        8px
        max(0px, calc(150px + env(safe-area-inset-right) - 14vw))
        4px
        max(0px, calc(150px + env(safe-area-inset-left) - 14vw));
    overflow: hidden;
    pointer-events: auto;
    /* Scope layout + paint to the hand so reflows during the collapse
       animation don't ripple out to the rest of the table. Formalizes
       the isolation the `overflow: hidden` + absolute positioning
       already implies. */
    contain: layout paint;
    /* Establish an inline-size query container so .card-column's
       fit-overlap can reference the hand's actual content width via
       `100cqi`. Lets us replace the JS-driven --col-margin with a
       static CSS calc — cards land at their final overlap on first
       paint instead of jumping after JS measures. */
    container-type: inline-size;
}

.card-column {
    display: flex;
    flex-direction: column;
}

/* Columns overlap horizontally so the hand packs tighter and matches
   the Figma fanned-hand look. With 8–10 columns the base overlap is
   enough; for large hands (13+ ranks) the fit-overlap takes over and
   squeezes columns so the entire hand fits between the badge clearance
   padding. Computed in pure CSS via container queries: `100cqi` is the
   inline size of the .player-hand content box (set as a query container
   below), so the formula resolves without measuring the DOM after first
   paint. --col-count is set inline by PlayerHand.razor; it changes with
   the hand size as cards are played. */
.card-column + .card-column {
    --card-w: clamp(42px, 5.76vw, 60px);
    --base-overlap: clamp(-13px, -2.5vw, -6px);
    /* fit-overlap solves: cardW + (N-1) * (cardW + margin) = 100cqi
       → margin = (100cqi - cardW) / (N-1) - cardW.
       Guard N=1 with max(..., 1) so a single-column hand doesn't divide
       by zero (the result is moot — there's no `+ .card-column` sibling
       to apply margin to — but the `calc()` still has to be valid). */
    --fit-overlap: calc(
        (100cqi - var(--card-w)) / max(var(--col-count, 1) - 1, 1) - var(--card-w)
    );
    /* Pick the MORE negative of the two: always honor the base overlap
       (so a few-column hand keeps its compact stacked look after a play
       collapses the gap), and only tighten further than that when a
       crowded hand would otherwise overflow the container. */
    margin-left: min(var(--base-overlap), var(--fit-overlap));
}

/* Vertical stack overlap: each non-last card exposes enough headroom
   to show the full rank glyph. The clamp is tuned so the exposed
   region (H − |margin|) stays at or above the glyph's bottom edge
   across the `.card` width clamp (42px → 60px). Scaled ~20% from the
   baseline along with the rest of the hand dimensions. */
.card-column .card + .card {
    margin-top: clamp(-50px, -4.9vw, -36px);
}

/* === Card ===
 * Layered card design sourced from the Figma artboard
 * (docs/old/table.css, reference size 58x77). Token names are
 * defined in figma-tokens.css.
 *
 * The live rendered size is proportionally ~20% larger than the
 * previous baseline (42x58 → 60x84 vs the old 35x49 → 50x70) so
 * cards are easier to tap on mobile. Aspect ratio is preserved
 * (50/70 ≈ 0.714) to match the joker SVG's card body proportions.
 */
.card {
    width: clamp(42px, 5.76vw, 60px);
    aspect-ratio: 50 / 70;
    background: var(--te-card-face-gradient);
    border: 1px solid var(--te-card-border);
    border-radius: var(--te-radius-sm);
    /* Three-layer shadow: the two outer layers match `filter0_dd` from
       the Figma card SVGs (soft Y-offset drop shadow, same parameters
       as `--te-shadow-card`); the inset layer matches `filter1_d` from
       the same SVGs, which puts a greyish depth-shadow along the card's
       top-left interior edges. Together they reproduce the subtle
       embossed look the joker SVG bakes into its artwork. */
    box-shadow:
        var(--te-shadow-card),
        inset 3px 3px 3px rgba(0, 0, 0, 0.18);
    position: relative;
    cursor: pointer;
    transition: transform 0.1s ease, box-shadow 0.1s ease;
    touch-action: none;
    font-family: var(--te-font-body);
    font-weight: 700;
    flex-shrink: 0;
}

.card:hover:not(.disabled):not(.selected) {
    box-shadow:
        var(--te-shadow-card),
        inset 3px 3px 3px rgba(0, 0, 0, 0.18),
        0 2px 8px rgba(255, 255, 255, 0.25);
}

/* Selected cards stay in place — the previous upward lift made cards
   jump in and out of the hand as the user dragged across them, which
   was disorienting on mobile. Instead, the face is muted to a greyish
   tone (see docs/old/hand.png) while keeping the rank/suit readable.
   The grey replaces the white card-face gradient; rank/suit icons and
   joker foreground artwork sit on top and keep their colour. */
.card.selected {
    background: linear-gradient(270.51deg, #a8a8a8 0.44%, #c4c4c4 99.56%);
}

.card.wild {
    border: 1px solid #ffd700;
}

/* Inline suit SVGs on wildcard hearts pick up this color via currentColor. */
.card.wild .card-suit-small,
.card.wild .card-suit-large {
    color: #ffd700;
}

.card.level-card {
    border: 1px solid #ffd700;
}

.card.disabled {
    cursor: default;
}

.card.small {
    width: clamp(24px, 3vw, 36px);
    box-shadow:
        0 2px 2px rgba(0, 0, 0, 0.25),
        0 5px 9px rgba(0, 0, 0, 0.12),
        inset 1px 1px 1px rgba(0, 0, 0, 0.18);
}

.card.red {
    color: var(--te-suit-red);
}

.card.black {
    color: var(--te-suit-black);
}

/* Rank/suit slot percentages are slightly larger than the raw Figma
   values (Rank 3.64/54.77/0/62.24, SuitSmall 50/22.95/9.69/68.71,
   SuitLarge 40.91/7.5/56.46/4.93) so the glyphs render at Figma size
   despite the inline SVG viewBoxes carrying a little padding around
   the shape. */
.card-rank {
    position: absolute;
    left: 3%;
    right: 52%;
    top: 0;
    bottom: 58%;
    display: block;
    line-height: 0;
}

/* RankIcon's inline SVG uses fill=currentColor so .card.red / .card.black
   flow through to the glyph. */
.card-rank .rank-svg {
    width: 100%;
    height: 100%;
    display: block;
}

.card-suit-small {
    position: absolute;
    left: 49%;
    right: 20%;
    top: 8%;
    bottom: 65%;
    object-fit: contain;
    pointer-events: none;
}

.card-suit-large {
    position: absolute;
    left: 39%;
    right: 6%;
    top: 54%;
    bottom: 3%;
    object-fit: contain;
    pointer-events: none;
}

/* Joker variant — the foreground SVG contains only artwork (text + icon);
   the card face, border, and shadow come from the base .card styles. */
.card.card-joker {
    overflow: hidden;
}

.card-joker-image {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: fill;
    pointer-events: none;
}

/* === Action Bar === */
/* Sits inside `.action-dock` below the status pill: HINT next to a
   context-aware PASS/PLAY button in a single horizontal row. The
   dock owns the bottom-edge safe-area; the bar itself just lays out
   its two children. */
.action-bar {
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    gap: 10px;
    padding: 0;
}

.btn {
    padding: 10px 22px;
    border: none;
    border-radius: var(--te-radius-xl);
    font-size: 0.82rem;
    font-weight: 800;
    cursor: pointer;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    transition: opacity 0.15s, transform 0.1s, box-shadow 0.15s;
    min-width: 84px;
    min-height: 44px;
    font-family: var(--te-font-body);
}

@media (max-width: 480px) {
    /* Phones: shrink dock buttons so HINT · PASS/PLAY fit on screen. */
    .btn {
        padding: 9px 16px;
        font-size: 0.74rem;
        min-width: 72px;
        min-height: 40px;
        letter-spacing: 0.06em;
    }
}

/* `.is-inactive` mirrors the visual + interactive state of `:disabled`
   for buttons that intentionally keep the `disabled` attribute OFF so
   their click events still reach Blazor (see ActionBar.razor for the
   full rationale — the short version is that disabled buttons swallow
   clicks before dispatch, which loses taps that arrive during the
   drag-commit JS-to-.NET round-trip in the MAUI WebView host). */
.btn:disabled,
.btn.is-inactive {
    opacity: 0.35;
    cursor: default;
}

.btn:not(:disabled):not(.is-inactive):hover {
    transform: scale(1.04);
}

.btn:not(:disabled):not(.is-inactive):active {
    transform: scale(0.97);
}

/* Action-dock variants: all three share .btn metrics.
   PASS is a solid charcoal secondary; HINT gets a blue tint; PLAY is
   the gold primary. Opaque fills prevent card-bleed from the hand strip
   below the centered dock. Each variant owns its own box-shadow. */
.btn-action { }

/* Charcoal — safe-destructive. 2 px rim lifts it above the status pill
   (which uses a hairline) so it reads as a real control. The rim is an
   inset box-shadow rather than a border so PASS occupies the exact same
   outer width as PLAY (which has no border) — otherwise toggling
   PASS↔PLAY would nudge the centered HINT button sideways. */
.btn-pass {
    background: #262626;
    color: #c8d0ca;
    box-shadow:
        0 4px 16px rgba(0, 0, 0, 0.55),
        0 0 0 2px rgba(255, 255, 255, 0.25) inset;
}

/* Blue tint — differentiates from PASS at a glance. */
.btn-hint {
    background: #131d28;
    border: 2px solid rgba(90, 170, 240, 0.4);
    color: #8ec5e8;
    box-shadow: 0 4px 16px rgba(90, 170, 240, 0.5);
}

.btn-play {
    background: linear-gradient(180deg, var(--te-accent-gold), #c69b00);
    color: #2a1a00;
    box-shadow:
        0 4px 16px rgba(255, 215, 0, 0.5),
        0 0 0 1px rgba(255, 255, 255, 0.2) inset;
}

/* When disabled the gold gradient stays — only the base .btn { opacity:
   0.35 } fades it. This keeps PLAY visible (and clearly identifiable as
   the primary action) when it's the human's turn but no cards are
   selected, mirroring how PASS reads in its disabled state. */
.btn-play:disabled,
.btn-play.is-inactive {
    box-shadow: none;
}

.btn-continue {
    background: var(--te-accent-blue);
    color: white;
    margin-top: 16px;
}

.btn-menu {
    background: var(--te-accent-slate);
    color: white;
    margin-top: 8px;
}

.overlay-buttons {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    margin-top: 16px;
}

/* === Overlay === */
.overlay-backdrop {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.7);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 100;
}

.overlay-panel {
    background: #1e3a2a;
    border: 1px solid #3a5a4a;
    border-radius: 12px;
    padding: 32px 48px;
    text-align: center;
    color: #e0e0e0;
    max-width: 400px;
}

/* ─── In-game Options popup ────────────────────────────────────────────
   Opened from the gear icon in HeaderBar. Mirrors the visual language of
   `.te-result-ranked` (gold accent strip + heading + right-aligned foot)
   so the in-game popups feel consistent with round-end / match-end.

   Owns its own backdrop and panel — does NOT inherit from `.overlay-panel`
   because the result-style panel handles its own sizing, padding, scroll,
   and animation. Body scrolls when the viewport is shorter than the panel
   (fixes Android overflow). Reuses keyframes from overlay-result.css. */
.ingame-options-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.66);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 100;
    padding: 16px;
}

.ingame-options-panel {
    position: relative;
    width: 460px;
    max-width: 100%;
    max-height: calc(100dvh - 32px);
    background: var(--te-table-panel-dark);
    border: 1px solid rgba(255, 215, 0, 0.4);
    border-radius: 16px;
    padding: 14px 18px;
    box-shadow: 0 0 60px rgba(255, 215, 0, 0.18), 0 16px 48px rgba(0, 0, 0, 0.6);
    overflow: hidden;
    color: var(--te-fg-body);
    display: flex;
    flex-direction: column;
    animation: te-result-panel-in 320ms ease-out both;
}

.ingame-options-strip {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    height: 6px;
    background: linear-gradient(90deg, var(--te-team-a), var(--te-accent-gold), var(--te-team-a));
    background-size: 200% 100%;
    animation: te-result-strip-sweep 1600ms ease-out 1 both;
}

.ingame-options-head {
    margin-top: 4px;
    flex-shrink: 0;
    min-width: 0;
}

.ingame-options-title {
    font: var(--te-font-weight-heavy) clamp(1.3rem, 4vw, 1.55rem) / 1 var(--te-font-display);
    color: var(--te-accent-gold);
    letter-spacing: 0.02em;
    text-shadow: 0 2px 10px rgba(255, 215, 0, 0.35);
}

.ingame-options-body {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-top: 12px;
    overflow-y: auto;
    min-height: 0;

    /* Extend the scroll container into the panel's right gutter so the
       scrollbar sits near the panel border instead of crowding the controls.
       The matching padding-right keeps content in its original horizontal
       position; only the scrollbar shifts right. */
    margin-right: -14px;
    padding-right: 14px;

    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, 0.18) transparent;
}

.ingame-options-body::-webkit-scrollbar { width: 6px; }
.ingame-options-body::-webkit-scrollbar-track { background: transparent; }
.ingame-options-body::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.18);
    border-radius: 3px;
}

/* Groups a section label, its control, and its caption as a single block so
   the parent gap separates sections — not the label/control/caption inside. */
.ingame-options-section {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

/* Pairs the hint caption (left) with a compact inline control (right, e.g. the
   Auto-pass toggle) on a single row, so the gold section label gets its own
   line above and the toggle sits a step down — vertically centred against the
   hint, not the title. */
.ingame-options-toggle-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}

/* Caption beneath a control (e.g. "Takes effect on the next round"). */
.ingame-options-hint {
    font-size: 0.7rem;
    color: var(--te-fg-muted, #aaa);
    line-height: 1.4;
}

.ingame-options-foot {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    margin-top: 12px;
    flex-shrink: 0;
}


.overlay-panel h2 {
    margin-bottom: 16px;
    font-size: 1.4rem;
    color: #ffd700;
}

.overlay-panel h3 {
    margin-bottom: 8px;
    font-size: 1rem;
    color: #ccc;
}

.finish-order {
    margin-bottom: 16px;
}

.finish-order div {
    padding: 2px 0;
}

.team-levels-overlay {
    margin: 12px 0;
    font-size: 0.9rem;
    color: #bbb;
}

.team-levels-overlay div {
    padding: 2px 0;
}

/* === You HUD === */
/* Bottom-left identity chip for the human: avatar + "You · TEAM A" + a
   single-line status (selected card count and detected combination).
   Mirrors the prototype's YouHud and keeps the player's identity visible
   while the centered action dock owns the buttons. */
.you-hud {
    position: absolute;
    left: calc(env(safe-area-inset-left) + 10px);
    bottom: calc(env(safe-area-inset-bottom) + 10px);
    z-index: 4;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    background: rgba(0, 0, 0, 0.55);
    border: 1.5px solid var(--te-team-a);
    border-radius: var(--te-radius-lg);
    pointer-events: auto;
    max-width: 200px;
    transition: border-color 0.18s ease, box-shadow 0.18s ease,
                background 0.18s ease;
    /* Clip the active-turn timer bar to the hud's rounded edges. */
    overflow: hidden;
}

/* Same gold glow as .opponent-area.active-turn so the human's seat
   matches the opponents' active-turn treatment. */
.you-hud.active-turn {
    border-color: var(--te-accent-gold);
    box-shadow: 0 0 14px rgba(255, 215, 0, 0.4);
    background: linear-gradient(160deg, rgba(255, 215, 0, 0.22), rgba(0, 0, 0, 0.55));
}

.you-hud-meta {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.you-hud-name-row {
    display: flex;
    align-items: center;
    gap: 6px;
    line-height: 1.1;
}

.you-hud-name {
    font-weight: 800;
    color: var(--te-fg-strong);
    font-size: 0.78rem;
}

.you-hud-tag {
    color: var(--te-team-a);
    font-size: 0.52rem;
    letter-spacing: 0.08em;
    font-weight: 700;
}

.you-hud-status {
    display: flex;
    align-items: center;
    gap: 5px;
    margin-top: 2px;
    line-height: 1;
}

@media (max-width: 480px) {
    /* On phones the dock and the you-hud share the same vertical band.
       Collapse the hud — drop the "You" name and the TEAM A tag so the
       avatar + card count stay visible while the dock stays readable
       and the two don't overlap. */
    .you-hud {
        gap: 6px;
        padding: 4px 6px;
        max-width: none;
    }

    .you-hud-name,
    .you-hud-tag {
        display: none;
    }

    .you-hud-status {
        margin-top: 0;
    }
}

/* === Trick History Overlay ===
   Hosted inside the shared `.ingame-options-panel` shell so it matches the
   in-game Options popup (gold accent strip + title head + right-aligned
   CTA). This block only styles the inner trick list. */
.trick-history-panel {
    width: 520px;
}

.trick-history-empty {
    color: var(--te-fg-quiet);
    font-style: italic;
    text-align: center;
    padding: 24px 0;
}

.trick-history-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.trick-history-item {
    background: rgba(0, 0, 0, 0.32);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: 10px;
    padding: 8px 10px 9px;
}

.trick-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 6px;
    padding-bottom: 4px;
    border-bottom: 1px dashed rgba(255, 255, 255, 0.08);
}

.trick-number {
    font-size: 0.62rem;
    letter-spacing: 0.16em;
    color: var(--te-accent-gold);
    font-weight: 800;
    text-transform: uppercase;
}

.trick-winner {
    font-size: 0.7rem;
    color: var(--te-fg-muted);
}

.trick-plays {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.trick-play {
    display: grid;
    grid-template-columns: 140px 90px 1fr;
    gap: 8px;
    align-items: center;
    padding: 3px 0;
    font-size: 0.78rem;
    color: var(--te-fg-body);
}

.trick-play.trick-pass .play-combo {
    color: var(--te-fg-quiet);
    font-style: italic;
}

.play-player {
    color: var(--te-fg-strong);
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.play-combo {
    color: var(--te-fg-muted);
    font-size: 0.72rem;
    letter-spacing: 0.04em;
}

.play-cards {
    display: flex;
    gap: 3px;
    flex-wrap: wrap;
}

.play-card {
    font-weight: 700;
    font-size: 0.95rem;
    font-variant-numeric: tabular-nums;
}

.play-card.red { color: var(--te-suit-red); }
.play-card.black { color: var(--te-fg-strong); }

.match-end-profile {
    display: flex;
    justify-content: center;
    margin: 12px 0;
}

/* === Rotate-device overlay ===
   The game table is designed for landscape only, but browsers happily
   render in portrait. Cover the table with a "please rotate" panel
   whenever the viewport is taller than it is wide. Sits above everything
   on .game-table (max in-game z-index is ~21) so input is blocked too. */
.rotate-device-overlay {
    display: none;
}

@media (orientation: portrait) {
    .rotate-device-overlay {
        position: fixed;
        inset: 0;
        z-index: 2000;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 16px;
        padding: 24px;
        text-align: center;
        background: radial-gradient(ellipse at center, #1e7a3a 0%, #145a28 60%, #0d3d1a 100%);
        color: #f4f4f4;
        font-family: var(--te-font-body);
    }

    .rotate-device-overlay-icon {
        font-size: 4rem;
        line-height: 1;
        color: var(--te-accent-gold, #f0c850);
        animation: rotate-device-hint 2.4s ease-in-out infinite;
    }

    .rotate-device-overlay-title {
        font-size: 1.4rem;
        font-weight: 700;
        letter-spacing: 0.02em;
    }

    .rotate-device-overlay-body {
        font-size: 0.95rem;
        color: #c4d6cb;
        max-width: 28ch;
    }
}

@keyframes rotate-device-hint {
    0%, 100% { transform: rotate(0deg); }
    50%      { transform: rotate(90deg); }
}

/* === Spectator Partner Hand ===
   Rendered at the TOP of the table when the local human is out but the
   partner still has cards — visually paired with the partner badge so
   the user looks UP to follow partner, mirroring how their own hand sits
   below their seat. .partner-hand inherits the .player-hand column-by-rank
   layout, but flips the column anchor from flex-end to flex-start so each
   rank stack grows DOWNWARD from the top instead of upward from the
   bottom (the user's "stacked from top to bottom"). Non-interactive. */
.partner-hand {
    /* Span the band between the partner badge (which sits at env + 6px
       and is ~46px tall + padding ≈ 56px to its bottom edge) and roughly
       the vertical middle of the table. The container's vertical extent
       reserves a comfortable area for short stacks; tall stacks (5+ of a
       rank, e.g. a six-card bomb) overflow past the container's bottom
       edge — see overflow / contain overrides below. */
    top: calc(env(safe-area-inset-top) + 56px);
    bottom: 55%;
    /* Override .player-hand's flex-end so columns top-anchor and the cards
       in each column hang DOWN from the partner-hand's top edge. */
    align-items: flex-start;
    /* Allow tall rank stacks to spill below the partner-hand container so
       all of partner's cards are visible — the spillover slides UNDER the
       play positions because partner-hand inherits z-index: 1 from
       .player-hand and .play-position is z-index: 10. Override
       .player-hand's overflow: hidden + contain: paint, both of which
       would clip the spillover. `contain: layout` keeps the layout-scope
       optimisation from the parent rule without forcing the paint clip. */
    overflow: visible;
    contain: layout;
    /* Partner-hand is non-interactive; absorb pointer events at the
       container so taps on the spectator strip route through the game
       table click handler (clears selections / hint) instead of
       surfacing card-level interactions. */
    pointer-events: none;
}

/* When the spectator partner-hand is on, push the played-card landings
   DOWN toward the lower half of the table so they don't overlap the new
   top-of-table partner-hand band, and drop the SKIP-only action-dock to
   match. Each rule overrides the matching base rule above; selectors are
   scoped to `.game-table.with-partner-hand` so the normal layout is
   unaffected. East/west landings stay at the sides where they don't
   horizontally overlap the centered partner-hand, so they keep their
   visual pairing with the side opponents' badges. */
.game-table.with-partner-hand .play-position.pos-south {
    /* Pos-south rarely receives plays in this mode (human is out) but
       may still hold a previously-winning slot until the trick ends.
       Push below mid-screen so it doesn't stack with the partner's
       played cards, which now sit just below the partner badge inside
       .opponent-area.pos-north's flex column. */
    top: 64%;
}

.game-table.with-partner-hand .action-dock.skip-only {
    /* Was top: 38% (just above mid-screen). Drop to the lower part of
       the table, well below the played-card landings, so the SKIP
       button never overlaps the played cards. */
    top: 78%;
}

/* <=650px hand padding follow-through. The narrow-viewport media block
   higher up hides the TEAM chip and the CARDS label on every badge —
   the you-hud then shrinks, and the hand can pad less on the left
   without tucking under it. Side-seat play-positions are now flex
   children of their badges so they shrink with the badge automatically
   — no media-query override needed for them. */
@media (max-width: 650px) {
    /* You-hud at <=650px is ~82px wide (avatar 24 + gap 8 + "You" ~30
       + 2*10 padding) → right edge at env+92 → hand pads to 100 so the
       leftmost card column sits ~8px past the hud's right edge. */
    .player-hand {
        padding:
            8px
            max(0px, calc(100px + env(safe-area-inset-right) - 14vw))
            4px
            max(0px, calc(100px + env(safe-area-inset-left) - 14vw));
    }
}
