/* === Blef Web App — Android-matched Theme ===
 *
 * dp / sp → CSS px mapping
 * ------------------------
 * Android's `dp` (density-independent pixel) and `sp` (scale-independent
 * pixel) both equal 1 px at the 160 DPI baseline (mdpi). CSS `px` is
 * already a device-independent unit (1/96 inch nominally, scales with
 * the user's browser zoom). For a desktop browser at 100 % zoom this
 * maps roughly 1 dp → 1 CSS px.
 *
 * So when the Android source hardcodes a size in dp/sp we use the same
 * number of CSS px. Examples already wired:
 *   .bet-btn { height: 70px; }                   // Android list_item_bet 70dp
 *   .hand-slot { width: 44px; height: 60px; }    // Android player card slot
 *   .history-card-wrap { width set inline; }     // 26 / 30 / 34 dp by count
 * ============================================= */


@import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700;900&display=swap');

:root {
    /* === Android Material 3 Color Tokens === */
    --md-primary: #8F4A4E;
    --md-on-primary: #FFFFFF;
    --md-primary-container: #FFDADA;
    --md-on-primary-container: #733337;

    --md-secondary: #765657;
    --md-on-secondary: #FFFFFF;
    --md-secondary-container: #FFDADA;
    --md-on-secondary-container: #5D3F40;

    --md-tertiary: #76592F;
    --md-on-tertiary: #FFFFFF;

    --md-background: #FFFAF4;
    --md-on-background: #221919;
    --md-surface: #FFF8F7;
    --md-on-surface: #221919;
    --md-surface-variant: #F4DDDD;
    --md-on-surface-variant: #524343;
    --md-surface-container: #F2E6D8;
    --md-surface-container-low: #F6EEE4;
    --md-surface-container-lowest: #F8F1E9;

    --md-outline: #857373;
    --md-outline-variant: #D7C1C1;

    --md-error: #BA1A1A;
    --md-on-error: #FFFFFF;

    --md-inverse-surface: #382E2E;
    --md-inverse-on-surface: #FFEDEC;

    /* Functional aliases */
    --primary-color: var(--md-primary);
    --primary-text: var(--md-on-primary);
    --surface-color: var(--md-surface-container);
    --surface-card: var(--md-surface-container-low);
    --background-color: var(--md-background);
    --text-color: var(--md-on-surface);
    --text-muted: var(--md-on-surface-variant);
    --border-color: var(--md-outline-variant);
    --danger-color: var(--md-error);
    --success-color: #34A853;

    --button-disabled-bg: #E4DFDA;
    --button-disabled-text: #ABA5A1;

    /* Team colors */
    --team-1: #4285F4;
    --team-2: #34A853;
    --team-3: #9C27B0;
    --team-4: #FF9800;

    --card-radius: 12px;
    --panel-radius: 16px;

    /* Speech bubble — light beige (Android md_theme_surfaceContainerLowest) */
    --bubble-color: #F8F1E9;
    --bubble-text:  var(--md-on-surface);

    /* Team colors (Android-matched) */
    --team-flag-1: #4285F4;
    --team-flag-2: #34A853;
    --team-flag-3: #9C27B0;
    --team-flag-4: #FF9800;

    /* Animation scale — mirrors Android's Constants.animationScale.
       Off (default) = 1.0, Fast Animations = 0.5 — every gameplay
       duration below is multiplied by this. */
    --anim-scale: 1;
    --anim-extra-short: calc(120ms * var(--anim-scale));   /* BLEF_ANIM_DURATION_EXTRA_SHORT */
    --anim-short:       calc(240ms * var(--anim-scale));   /* BLEF_ANIM_DURATION_SHORT */
    --anim-default:     calc(350ms * var(--anim-scale));   /* BLEF_ANIM_DURATION */
    --anim-long:        calc(700ms * var(--anim-scale));   /* BLEF_ANIM_DURATION_LONG */
    --anim-extra-long:  calc(1400ms * var(--anim-scale));  /* BLEF_ANIM_DURATION_EXTRA_LONG */

    /* Easing curves — port of Android's standard interpolators.
       --easing-standard   = AccelerateDecelerateInterpolator (the default
                             when an animation doesn't call setInterpolator).
                             Used for hand deal-in, flip reveal, history
                             selection / fade-in, bubble bg transitions,
                             highlight strokeWidth.
       --easing-decelerate = DecelerateInterpolator. Used explicitly by
                             slide_in_bottom.xml (finished standings),
                             reaction emoji travel (Game.kt:789),
                             card sort/move (PlayerHandAdapter:399),
                             placeholder→Next (PlayerHandAdapter:234).
       --easing-accelerate = AccelerateInterpolator. Not used by any
                             current Blef animation but provided for
                             completeness. */
    --easing-standard:   cubic-bezier(0.4, 0, 0.2, 1);
    --easing-decelerate: cubic-bezier(0, 0, 0.2, 1);
    --easing-accelerate: cubic-bezier(0.4, 0, 1, 1);

    /* Dealt-card sizing — responsive to viewport width (mirroring
       Android's "fraction of screen width" approach for hand cards in
       res/values-w*dp/dimens.xml). Bounded so the cards don't get tiny
       on small phones or oversized on tablets. Aspect ratio 0.8 matches
       the source .webp (240×300). The horizontal overlap is the fixed
       18% of card width (was 8/44 in the old hard-coded values). */
    --hand-card-w: clamp(34px, calc(5vw + 18px), 48px);
    --hand-card-h: calc(var(--hand-card-w) / 0.8);
    --hand-card-overlap: calc(var(--hand-card-w) * 0.18);
}
html.fast-animations { --anim-scale: 0.5; }

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
    /* Default body weight is 500 — Android Material 3's
       textAppearanceBodyLarge uses Roboto Regular, which optically reads
       as slightly heavier than the web stack's 400 weight. Bumping to 500
       brings the perceived weight in line with the Android app. */
    font-family: 'Lato', 'Inter', sans-serif;
    font-weight: 500;
    color: var(--text-color);
    min-height: 100vh;
    line-height: 1.5;
    /* Niedzica wallpaper at 8% — Android stacks it as an ImageView with
       alpha=0.08 behind every activity (activity_game.xml etc.). The
       layered linear-gradient is a 92% opaque overlay of the surface
       colour over the image; visually equivalent to setting the image to
       opacity 0.08 over the surface colour. */
    background-color: var(--background-color);
    background-image:
        linear-gradient(rgba(255, 250, 244, 0.92), rgba(255, 250, 244, 0.92)),
        url('assets/niedzica.webp');
    background-size: auto, cover;
    background-position: center, center;
    background-repeat: no-repeat, no-repeat;
    background-attachment: fixed, fixed;
}

#app { min-height: 100vh; position: relative; }

/* === Views === */
.view { display: none; min-height: 100vh; }
.view.active-view { display: flex; justify-content: center; align-items: center; }

/* === Glass/Surface Card === */
.glass-card {
    background: var(--surface-card);
    border-radius: var(--panel-radius);
    border: 1px solid var(--border-color);
    padding: 1.5rem;
}

/* === Buttons ===
   Default to pill-shaped (Android M3 MaterialButton style). Bet-menu and
   toggle-group buttons override with smaller radii where needed. */
.btn {
    font-family: 'Lato', 'Inter', sans-serif;
    border: 2px solid transparent;
    border-radius: 999px;
    padding: 0.6rem 1.2rem;
    font-size: 0.95rem;
    font-weight: 700;
    cursor: pointer;
    transition: all 0.15s ease;
    text-align: center;
    width: 100%;
}
.btn-primary {
    background: var(--primary-color);
    color: var(--primary-text);
    border-color: var(--primary-color);
}
.btn-primary:hover:not(:disabled) {
    background: var(--md-on-primary-container);
    border-color: var(--md-on-primary-container);
}
.btn-primary:disabled {
    background: var(--button-disabled-bg);
    color: var(--button-disabled-text);
    border-color: var(--button-disabled-bg);
    cursor: not-allowed;
}
/* Quick Play is permanently disabled in the rules-refinement build until
   the AI lambdas catch up. Use Material 3 disabled tokens (filled-button
   variant: on-surface @ 12% container, @ 38% label) so the button reads as
   clearly non-interactive on mobile, where the desktop `title` tooltip
   doesn't surface. */
#btn-quick-play:disabled {
    background: rgba(34, 25, 25, 0.12);
    color: rgba(34, 25, 25, 0.38);
    border-color: transparent;
}
.btn-secondary {
    background: var(--md-surface);
    color: var(--md-on-surface-variant);
    border-color: var(--md-outline-variant);
}
.btn-secondary:hover:not(:disabled) { background: var(--md-surface-variant); }
.btn-secondary:disabled {
    background: var(--button-disabled-bg);
    color: var(--button-disabled-text);
    border-color: var(--button-disabled-bg);
    cursor: not-allowed;
}
.btn-large { padding: 1rem 2rem; font-size: 1.1rem; }
.btn-small { padding: 0.3rem 0.8rem; font-size: 0.85rem; width: auto; }

/* Ready-active button shows check icon inline */
.btn.ready-active {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
}
.btn.ready-active .player-ready-icon {
    width: 20px;
    height: 20px;
    fill: var(--primary-color);
}

/* === Toast === */
#toast-container {
    position: fixed; top: 1rem; left: 50%; transform: translateX(-50%);
    z-index: 9999; display: flex; flex-direction: column; gap: 0.5rem;
}
.toast {
    background: var(--md-inverse-surface);
    color: var(--md-inverse-on-surface);
    padding: 0.8rem 1.5rem;
    border-radius: var(--card-radius);
    font-size: 0.9rem;
    animation: fadeIn 0.3s ease;
    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.toast.error  { border-left: 4px solid var(--danger-color); }
.toast.success { border-left: 4px solid var(--success-color); }
@keyframes fadeIn  { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
@keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }

/* === Welcome View === */
#welcome-view { background: transparent; }
.main-panel { width: min(420px, 90vw); padding: 2.5rem; text-align: center; }
.title {
    font-family: 'Lato', sans-serif;
    font-size: 3rem;
    font-weight: 900;
    color: var(--primary-color);
    letter-spacing: 0.1em;
}
.subtitle { color: var(--text-muted); font-size: 1rem; margin-bottom: 2rem; }

.input-group { text-align: left; margin-bottom: 1rem; }
.input-group label {
    display: block; font-size: 0.85rem; font-weight: 700; color: var(--text-muted);
    margin-bottom: 0.3rem; text-transform: uppercase; letter-spacing: 0.05em;
}
.input-group input, .input-group select {
    width: 100%; padding: 0.7rem 1rem;
    border: 1.5px solid var(--border-color);
    border-radius: var(--card-radius);
    background: var(--md-surface); color: var(--text-color);
    font-size: 1rem; font-family: 'Lato', 'Inter', sans-serif;
    transition: border-color 0.2s;
}
.input-group input:focus, .input-group select:focus {
    outline: none; border-color: var(--primary-color);
}

.action-divider { display: flex; align-items: center; gap: 1rem; margin: 1.5rem 0; }
.action-divider hr { flex: 1; border: none; border-top: 1px solid var(--border-color); }
.action-divider span { font-size: 0.8rem; font-weight: 700; color: var(--text-muted); letter-spacing: 0.05em; }

.welcome-lang-row {
    display: flex; justify-content: center;
    margin-top: 1.5rem;
}

/* === Lobby View ===
   Column layout: scrollable cards area on top + Ready button anchored at the bottom. */
#lobby-view {
    background: transparent;
    padding: 2rem 2rem 0 2rem;
    flex-direction: column !important;
    align-items: stretch !important;
    justify-content: flex-start !important;
}
.lobby-layout {
    display: grid; grid-template-columns: minmax(0, 480px) minmax(0, 480px);
    justify-content: center;
    /* Each card sizes to its own content height — the rules card and
       players card grow independently rather than being stretched to
       match. Matches Android, where each card uses wrap_content. */
    align-items: start;
    /* Pack rows to the top — without this the implicit rows distribute
       across the flex-stretched grid height and a 200px+ gap appears
       between the two cards even with `gap: 0`. */
    align-content: start;
    gap: 2rem;
    max-width: 1000px; margin: 0 auto; width: 100%;
    flex: 1 1 auto;
    overflow-y: auto;
}
@media (max-width: 900px) {
    .lobby-layout {
        grid-template-columns: 1fr;
        /* Stacked layout: keep both cards as DISTINCT panels (rounded
           corners + outline preserved) but with a small fixed gap so the
           players card sits close to the rules card without dynamic
           whitespace between them. */
        gap: 0.75rem;
    }
    /* When stacked: rules above players above ready (sticky-bottom). The
       order is set here, not globally, because in the wide layout we want
       grid auto-placement to put .lobby-rules-card in column 1 and the
       .lobby-right-column wrapper in column 2 by DOM order — adding
       `order` to rules-card globally would push it past the wrapper. */
    .lobby-rules-card   { order: 1; }
    .lobby-players-card { order: 2; }
}
.lobby-card {
    padding: 1.5rem;
    position: relative;
    /* Match Android BlefCard (strokeWidth=0dp) — no border. */
    border: none;
}
.lobby-info { margin-bottom: 1rem; }
.lobby-info p { font-size: 0.9rem; color: var(--text-muted); margin-bottom: 0.5rem; }
.highlight-text { font-weight: 700; color: var(--text-color); font-size: 0.85rem; word-break: break-all; }

/* Full-width invite buttons — Android-style pill / outlined with icon */
.invite-btn-row { margin-top: 0.5rem; }
.invite-btn-row .btn {
    width: 100%;
    border-radius: 999px;             /* pill shape */
    background: var(--md-surface);
    color: var(--primary-color);
    border: 1.5px solid var(--md-outline-variant);
    font-weight: 700;
    padding: 0.6rem 1.2rem;
    display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem;
}
.invite-btn-row .btn:hover:not(:disabled) {
    background: var(--md-surface-variant);
    color: var(--primary-color);
}
/* Material 3 disabled state: on-surface @ 38% foreground, @ 12% outline, @ 6%
   container fill. The pill keeps its silhouette but desaturates so it reads as
   "not interactive" without needing a tooltip — important on mobile where
   `title` doesn't surface. */
.invite-btn-row .btn:disabled {
    background: rgba(34, 25, 25, 0.06);
    color: rgba(34, 25, 25, 0.38);
    border-color: rgba(34, 25, 25, 0.12);
    cursor: not-allowed;
}
.invite-btn-row .btn:disabled::before {
    /* Desaturate the inline icon so it joins the disabled state instead of
       fighting it — the SVG has a baked-in primary-colour fill. */
    filter: grayscale(1) opacity(0.38);
}
.invite-btn-row .btn::before {
    content: '';
    width: 16px; height: 16px;
    background-repeat: no-repeat; background-position: center;
    background-size: contain;
    flex-shrink: 0;
}
#btn-copy-invite::before {
    /* < (Send / share) icon */
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='%238F4A4E' d='M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92s2.92-1.31 2.92-2.92-1.31-2.92-2.92-2.92z'/></svg>");
}
#btn-invite-ai::before {
    /* + (add) icon */
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='%238F4A4E' d='M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z'/></svg>");
}

/* Players list "well" — Android: lighter than the surrounding lobby card. */
.players-list {
    list-style: none;
    background: var(--md-surface-container-lowest);
    border-radius: 12px;
    padding: 0.25rem 0.5rem;
    margin: 0.5rem 0 0;
    /* Same trick as .history-list: clip during the WAAPI height animation
       so transient overflow doesn't trigger a parent scrollbar. */
    overflow: hidden;
}
.players-list li {
    position: relative;
    overflow: hidden;
    user-select: none;
    touch-action: pan-y;
}

/* The translatable row content sits above the swipe-bg */
.player-row-inner {
    display: flex; justify-content: space-between; align-items: center;
    padding: 0.6rem 0.8rem;
    background: var(--md-surface-container-lowest);
    position: relative; z-index: 1;
}

/* Swipe-to-kick visual feedback */
.players-list li.swiping { cursor: grabbing; }
.players-list li.swipe-kicked .player-row-inner { transition: transform 0.25s ease; }
.players-list li.swipe-kicked { transition: opacity 0.25s ease, max-height 0.25s ease; opacity: 0; max-height: 0; }
.player-row-inner.swipe-snap-back { transition: transform 0.2s ease; }

.swipe-bg {
    position: absolute; inset: 0;
    background: var(--md-error);
    pointer-events: none;
    z-index: 0;
    display: none;
}
.players-list li.swiping .swipe-bg,
.players-list li.swipe-kicked .swipe-bg { display: block; }

.player-name {
    font-weight: 400; color: var(--text-color);
    display: flex; align-items: center; gap: 0.45rem;
    flex: 1; min-width: 0;
}
.player-name.is-me { font-weight: 900; }
.player-admin-icon { width: 16px; height: 16px; fill: var(--md-on-surface-variant); flex-shrink: 0; }

/* Team flag indicator (matches Android list_item_player_pregame.xml) */
.team-flag {
    width: 18px; height: 18px; flex-shrink: 0;
    display: inline-flex; align-items: center; justify-content: center;
}
.team-flag svg { width: 16px; height: 16px; }
.team-flag-1 svg { fill: var(--team-flag-1); }
.team-flag-2 svg { fill: var(--team-flag-2); }
.team-flag-3 svg { fill: var(--team-flag-3); }
.team-flag-4 svg { fill: var(--team-flag-4); }
.team-flag-none svg { fill: var(--md-on-surface-variant); opacity: 0.45; }
.player-ready-icon  { width: 18px; height: 18px; fill: var(--primary-color); flex-shrink: 0; }

.player-status-indicator {
    font-size: 1rem; color: var(--text-muted); line-height: 1;
    display: flex; align-items: center;
}
.player-status-indicator.ready .player-ready-icon { fill: var(--primary-color); }

/* Lobby emoji button on each player row */
.player-emoji-btn {
    background: transparent; border: none; cursor: pointer;
    font-size: 1.05rem; padding: 0.1rem 0.35rem;
    border-radius: 6px; transition: background 0.12s;
}
.player-emoji-btn:hover { background: var(--md-surface-variant); }

/* Lobby Ready button — sticky at the bottom of the view (phones).
   Background is transparent so the niedzica wallpaper shows through, the
   same way the running view's actions area is transparent. */
.lobby-controls {
    display: flex; align-items: center; gap: 1rem;
    max-width: 1000px;
    width: 100%;
    margin: 0 auto;
    padding: 1rem 0 1.5rem;
    background: transparent;
    position: sticky; bottom: 0;
}
.lobby-controls > #btn-ready-toggle { flex: 1 1 auto; }
@media (min-width: 900px) {
    /* Wide screens: .lobby-layout is a 2-column grid whose two cells are
       the rules card (left) and the right-column wrapper (right). Each
       cell sizes to its own content height — column heights are fully
       independent. Inside the wrapper, the Ready button stays at its
       fixed natural height and the players card expands as more players
       join. Sticky-bottom is dropped because there's nothing to scroll
       past on wide layouts. */
    .lobby-right-column {
        display: flex;
        flex-direction: column;
        gap: 1rem;
        min-width: 0;
    }
    .lobby-controls {
        position: static;
        padding: 0;
        margin: 0;
    }
}
@media (max-width: 900px) {
    /* Narrow screens: flatten the right-column wrapper so the rules card,
       players card and Ready button participate directly in .lobby-layout's
       single-column flow. The Ready button keeps its sticky-bottom
       behaviour from the base .lobby-controls rule. Order ensures the
       Ready button stays at the bottom of the visual stack regardless of
       its DOM position inside the wrapper. */
    .lobby-right-column { display: contents; }
    .lobby-controls     { order: 3; }
}

/* Observer Join row (Android observerLayout) — nickname input + 🎲 + Join. */
.observer-join-row {
    display: flex; align-items: stretch; gap: 0.5rem;
    width: 100%;
    flex-wrap: wrap;
}
.observer-nickname-input {
    flex: 2 1 200px;
    min-height: 56px;
    padding: 0 1rem;
    border-radius: 999px;
    border: 1px solid var(--md-outline);
    background: var(--md-surface);
    color: var(--text-color);
    font-size: 1rem;
    box-sizing: border-box;
}
.observer-generate-btn {
    flex: 0 0 auto;
    min-width: 56px;
    font-size: 1.4rem;
    padding: 0 0.9rem;
}
.observer-join-btn {
    flex: 1 1 100%;
    margin: 0;
}

/* === Standard / Pro toggle === */
.std-pro-toggle-wrapper {
    display: flex; justify-content: center;
    margin-bottom: 1rem;
}
.std-pro-toggle {
    width: 100%;
    max-width: 260px;
}

/* Std/Pro is a value preset only — never hide rule rows */

/* === Rules admin panel — toggle groups + sliders === */
.rules-admin-grid { display: flex; flex-direction: column; gap: 0.85rem; }

/* Sectioning: Passive cards · Common hand · Active cards · Tempo */
.rules-section + .rules-section { margin-top: 1rem; }
.rules-section-header {
    font-size: 0.7rem; font-weight: 700; color: var(--primary-color);
    text-transform: uppercase; letter-spacing: 0.08em;
    margin-bottom: 0.5rem; padding-bottom: 0.3rem;
    border-bottom: 1px solid var(--md-outline-variant);
}

.rule-row {
    display: flex; align-items: center; gap: 0.75rem;
    min-height: 36px;          /* identical height regardless of control inside */
}
.rule-row-label {
    font-size: 0.78rem; font-weight: 700; color: var(--text-muted);
    text-transform: uppercase; letter-spacing: 0.05em;
    min-width: 52px; flex-shrink: 0;
}

.toggle-group {
    display: flex; border-radius: 8px; overflow: hidden;
    border: 1.5px solid var(--md-outline-variant); flex: 1;
}
.toggle-btn {
    flex: 1; padding: 0.35rem 0.4rem;
    background: var(--md-surface); color: var(--text-muted);
    border: none; border-right: 1px solid var(--md-outline-variant);
    cursor: pointer; font-size: 0.82rem; font-family: inherit; font-weight: 700;
    transition: background 0.12s, color 0.12s; white-space: nowrap;
}
.toggle-btn:last-child { border-right: none; }
.toggle-btn.active { background: var(--primary-color); color: white; }
.toggle-btn:hover:not(.active) { background: var(--md-surface-variant); color: var(--text-color); }

.slider-rule { display: flex; align-items: center; gap: 0.5rem; flex: 1; }
.slider-rule input[type=range] { flex: 1; accent-color: var(--primary-color); }
.slider-value { font-size: 0.82rem; font-weight: 700; color: var(--text-color); min-width: 64px; text-align: right; }

/* Clickable rule row (Android-style "tap to open modal").
   Padding left/right is 0 so the label aligns with the non-clickable rows.
   Vertical padding gives the row some height; the hover background still
   spans full width thanks to negative margin. */
.rule-row-clickable {
    cursor: pointer;
    padding: 0.4rem 0;
    border-radius: 8px;
    transition: background 0.12s;
}
.rule-row-clickable:hover { background: var(--md-surface-variant); }
.rule-row-value {
    margin-left: auto;
    font-size: 0.82rem; font-weight: 700; color: var(--text-color);
}
.rule-row-chevron {
    color: var(--text-muted); font-size: 1.1rem; padding-left: 0.5rem;
}

/* Rule value modal (Android-style: title, big value display, slider, Back/OK) */
.rule-value-modal {
    position: fixed; inset: 0; background: rgba(0,0,0,0.4);
    display: none; align-items: center; justify-content: center; z-index: 8400;
}
.rule-value-modal.open { display: flex; }
.rule-value-card {
    padding: 1.5rem;
    min-width: 320px; max-width: 400px; width: 90vw;
    text-align: center;
}
.rule-value-title {
    color: var(--text-color); font-size: 0.85rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 1rem;
}
.rule-value-display {
    color: var(--primary-color);
    font-size: 1.8rem; font-weight: 700;
    margin: 0.5rem 0;
}
.rule-value-display-sub {
    color: var(--text-muted); font-size: 0.85rem; min-height: 1.2rem;
    margin-bottom: 0.6rem;
}
.rule-value-slider {
    width: 100%; accent-color: var(--primary-color);
    margin: 0.6rem 0 1.4rem;
}
.rule-value-actions {
    display: flex; justify-content: flex-end; gap: 1rem;
}
.rule-value-text-btn {
    background: transparent;
    border: none;
    color: var(--primary-color);
    font-weight: 700; font-size: 0.95rem; font-family: inherit;
    padding: 0.4rem 0.8rem;
    cursor: pointer;
    border-radius: 8px;
    transition: background 0.12s;
}
.rule-value-text-btn:hover { background: var(--md-surface-variant); }

/* Rules saving indicator: spinner only, no background flash */
.rules-saving-indicator {
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    background: transparent;
    pointer-events: none;
    z-index: 10;
}
.loading-spinner-small {
    width: 28px; height: 28px;
    border: 3px solid var(--md-outline-variant);
    border-top-color: var(--primary-color);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

/* Rules viewer (non-admin read-only) */
.rules-viewer-list { list-style: none; padding: 0; }
.rules-viewer-list li {
    display: flex; justify-content: space-between;
    padding: 0.4rem 0; font-size: 0.9rem;
    border-bottom: 1px solid var(--md-outline-variant);
}
.rules-viewer-list li:last-child { border-bottom: none; }
.rules-viewer-list .rule-label { color: var(--text-muted); }
.rules-viewer-list .rule-value { font-weight: 700; color: var(--text-color); }

/* Loading Overlay (welcome poster + spinner below) */
.loading-overlay {
    position: fixed; inset: 0; background: rgba(0,0,0,0.55);
    display: flex; align-items: center; justify-content: center; z-index: 9999;
}
.loading-overlay-content {
    display: flex; flex-direction: column; align-items: center; gap: 1.5rem;
    padding: 1.5rem;
    background: var(--surface-card);
    border-radius: var(--panel-radius);
    border: 1px solid var(--border-color);
    max-width: 360px;
    box-shadow: 0 8px 32px rgba(0,0,0,0.25);
}
.loading-poster {
    width: 280px; max-width: 80vw; height: auto;
    border-radius: var(--card-radius);
    box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
/* Spinner-only mode: used when joining an existing game where the
   welcome poster would briefly flash the wrong screen. The card chrome
   shrinks down to just the spinner. */
.loading-overlay.spinner-only .loading-poster { display: none; }
.loading-overlay.spinner-only .loading-overlay-content {
    background: transparent;
    border: none;
    box-shadow: none;
    padding: 0;
}
.loading-spinner {
    width: 36px; height: 36px;
    border: 4px solid var(--md-outline-variant);
    border-top-color: var(--primary-color);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Emoji Picker Modal */
.emoji-picker-modal {
    position: fixed; inset: 0; background: rgba(0,0,0,0.4);
    display: flex; align-items: center; justify-content: center; z-index: 8000;
}
.emoji-picker-card { padding: 1.5rem; min-width: 280px; max-width: 350px; }
.emoji-picker-header {
    display: flex; justify-content: space-between; align-items: center;
    margin-bottom: 1rem; font-weight: 700; color: var(--primary-color); font-size: 1rem;
}
.emoji-picker-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 0.3rem; }
.emoji-picker-grid .emoji-btn {
    font-size: 1.6rem; text-align: center; padding: 0.4rem;
    border-radius: 8px; cursor: pointer; transition: background 0.15s, transform 0.15s;
}
.emoji-picker-grid .emoji-btn:hover {
    background: var(--md-surface-variant); transform: scale(1.15);
}

/* === Player Action Modal (lobby) === */
.player-action-modal {
    position: fixed; inset: 0; background: rgba(0,0,0,0.4);
    display: none; align-items: center; justify-content: center; z-index: 8200;
}
.player-action-modal.open { display: flex; }
.player-action-card {
    padding: 1.25rem;
    min-width: 300px; max-width: 380px; width: 90vw;
    display: flex; flex-direction: column; align-items: stretch;
}
.player-action-header {
    display: flex; justify-content: center; align-items: center;
    margin-bottom: 0.5rem; position: relative;
}
.player-action-title {
    color: var(--text-color); font-size: 1.05rem; font-weight: 700;
    text-align: center; flex: 1;
}
#player-action-close {
    position: absolute; right: 0;
}
.player-action-divider {
    height: 1px;
    background: var(--md-outline-variant);
    margin: 0.6rem 0 0.5rem;
}
.player-action-section {
    font-size: 0.8rem; color: var(--text-muted);
    margin: 0 0 0.5rem; text-transform: uppercase; letter-spacing: 0.05em;
    text-align: center;
}
.player-action-team-row {
    display: flex; align-items: center;
    width: 100%;
    justify-content: space-between;
    margin-bottom: 0.2rem;
}
.player-action-team-btn {
    background: transparent; border: 2px solid transparent; border-radius: 8px;
    cursor: pointer; padding: 4px;
    display: inline-flex; align-items: center; justify-content: center;
    transition: transform 0.12s, border-color 0.12s;
    flex: 1;
}
.player-action-team-btn.active { border-color: var(--text-color); transform: scale(1.05); }
.player-action-team-btn .team-flag svg { width: 24px; height: 24px; }

/* Text-only action button (Remove / Leave Game) — outlined-style with red text */
.player-action-text-btn {
    background: transparent;
    border: none;
    color: var(--md-error);
    font-weight: 700;
    font-size: 0.95rem;
    font-family: inherit;
    padding: 0.6rem 1rem;
    cursor: pointer;
    border-radius: 8px;
    width: 100%;
    text-align: center;
    transition: background 0.12s;
}
.player-action-text-btn:hover { background: rgba(186, 26, 26, 0.08); }
.player-action-danger { color: var(--md-error); }

/* Emoji traveler (positioned by JS, animated via WAAPI) */
.reaction-traveler {
    position: fixed; z-index: 9000;
    font-size: 2rem; line-height: 1;
    pointer-events: none;
    filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
}

/* === AI Invite Modal === */
.ai-invite-modal {
    position: fixed; inset: 0; background: rgba(0,0,0,0.4);
    display: flex; align-items: center; justify-content: center; z-index: 8500;
}
.ai-invite-card { padding: 1.5rem; min-width: 240px; max-width: 340px; }
.ai-invite-header {
    display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;
}
.ai-invite-header h3 { color: var(--primary-color); font-size: 1.1rem; }
.ai-invite-list { display: flex; flex-direction: column; gap: 0.5rem; }

/* Floating reactions: see .reaction-traveler — sender→target Bezier travel
   is implemented via WAAPI keyframes in app.js; no static animation here. */

/* === Game View === */
#game-view { display: none; padding: 0; min-height: 100vh; }
#game-view.active-view {
    display: flex; flex-direction: column; align-items: stretch;
    width: 100%;
    /* Exact viewport height (not min-height): the body never scrolls, so
       the history list stays strictly above the bet menu instead of gliding
       underneath when content is tall. Mirrors Android's ConstraintLayout
       behaviour where the history's bottom edge is anchored to the bet
       menu's top — overlap is structurally impossible.
       100dvh tracks the dynamic viewport on mobile (handles browser chrome
       collapsing). 100vh is the fallback for browsers without dvh. */
    height: 100vh;
    height: 100dvh;
    overflow: hidden;
}
/* The board-pane and controls-pane must hug the top and bottom of the
   viewport respectively; the layout is the flex child that grows. */
#game-view .game-layout { min-height: 0; }
.board-pane { flex: 0 0 auto; }
.controls-pane { flex: 1 1 auto; }

.game-layout {
    /* Phones: stacked top-to-bottom — board (hands), then a flexible history
       row that absorbs leftover space, then the actions card pinned at the
       bottom with a fixed height. The 1fr middle row is what keeps the
       actions card hugging the viewport bottom (not the history's bottom). */
    display: grid;
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr auto;
    grid-template-areas:
        "board"
        "history"
        "actions";
    gap: 0; width: 100%;
    max-width: 720px;
    margin: 0 auto;
    flex: 1 1 auto;
}
@media (min-width: 900px) {
    /* Wide screens: hands + history stacked on the left, the bet-menu fills
       the right column at full height. The history's changing height never
       moves the actions. A column-gap separates the two halves visually. */
    .game-layout {
        grid-template-columns: 1fr 1fr;
        grid-template-rows: auto 1fr;
        grid-template-areas:
            "board   actions"
            "history actions";
        max-width: 880px;
        column-gap: 1.25rem;
        padding: 0 1rem;
    }
}
.game-layout > .board-pane    { grid-area: board; }
.game-layout > .controls-pane { grid-area: history; }
.game-layout > .actions-card  { grid-area: actions; }

/* === Top bar (Android menu_game.xml: back, round indicator, rules, cog) === */
.game-top-bar {
    position: relative;
    display: flex; align-items: center; gap: 6px;
    width: 100%; max-width: 720px;
    margin: 0 auto;
    padding: 4px 8px;
    min-height: 48px;
    background: var(--background-color);
    /* Hand-slot cards reach up to ~140; keep the bar's stacking context
       below that so its dropdown (200) wins over the cards. */
    z-index: 150;
}
@media (min-width: 900px) {
    /* Match the game-layout's wide-screen max-width so the top bar lines
       up with the rest of the columns. */
    .game-top-bar { max-width: 880px; padding-left: 1rem; padding-right: 1rem; }
}
.game-top-bar .round-indicator {
    flex: 1 1 auto;
    text-align: left;
    font-size: 1rem; font-weight: 700; color: var(--text-color);
    padding: 0 8px;
}
.game-top-actions { display: flex; align-items: center; gap: 2px; }
.game-top-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 40px; height: 40px;
    background: transparent; border: none; border-radius: 999px;
    cursor: pointer; color: var(--md-on-surface-variant);
    transition: background-color 120ms;
}
.game-top-btn:hover { background: var(--md-surface-variant); }
.game-top-btn svg { fill: currentColor; }
/* Settings dropdown — positioned beneath the cog. */
.game-settings-menu {
    position: absolute; top: 100%; right: 8px;
    background: var(--md-surface);
    border-radius: 12px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.16);
    padding: 4px 0;
    min-width: 220px;
    /* Above the hand-slot cards which use z-index up to ~140. */
    z-index: 200;
}
.game-settings-menu[hidden] { display: none; }
.game-settings-item {
    display: flex; align-items: center; gap: 10px;
    width: 100%; padding: 8px 14px;
    background: transparent; border: none;
    cursor: pointer; font: inherit;
    color: var(--text-color);
    text-align: left;
}
.game-settings-item:hover { background: var(--md-surface-variant); }
.game-settings-item svg { fill: currentColor; flex: 0 0 auto; }
.game-settings-item.game-settings-danger { color: var(--md-error); }
.game-settings-divider {
    height: 1px; background: var(--md-outline-variant);
    margin: 4px 0;
}
/* Toggle indicator — empty box / filled with checkmark when on. */
.game-settings-check {
    width: 18px; height: 18px;
    border: 1.5px solid var(--md-outline);
    border-radius: 3px;
    flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    color: transparent;
}
.game-settings-item.is-checked .game-settings-check {
    background: var(--md-primary);
    border-color: var(--md-primary);
    color: white;
}
.game-settings-item.is-checked .game-settings-check::before {
    content: '✓'; font-size: 13px; font-weight: 700; line-height: 1;
}
/* Rules modal reuses the rule-value-modal style; just override the list. */
#game-rules-modal .rule-value-card { padding: 1.25rem 1.25rem 0.75rem; }
#game-rules-modal .rule-value-title { margin-bottom: 0.75rem; }
#game-rules-modal .rules-viewer-list { margin-bottom: 1rem; }

/* Fast Animations toggle: handled via the --anim-scale custom property
   above. Each keyframed animation / transition uses calc(... * var(--anim-scale))
   so toggling .fast-animations halves every duration in lockstep with
   Android's Constants.animationScale = 0.5. */

/* Board pane */
.board-pane {
    display: flex; flex-direction: column;
    padding: 0 1.5rem 0;           /* no top/bottom padding — hands-area hugs the top bar AND the history below */
    border-right: 1px solid var(--border-color);
    background: transparent;
    position: relative;
}
.round-indicator { font-size: 1rem; font-weight: 700; color: var(--text-color); padding: 0.5rem 0; }

/* === Hands area: all players (incl. self + common) in one grid ===
   Android: surfaceContainer bg with horizontal-padding 6dp / top 8dp / bottom 12dp. */
.hands-area {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem 1.5rem;
    padding: 8px 6px 12px;
    background: var(--md-surface-container);
    border-radius: 0;
    margin: 0;
}
.hand {
    display: flex; flex-direction: column;
    align-items: center;
    padding: 0.4rem 0.5rem;
}
.hand-fullwidth { grid-column: 1 / -1; }

/* Header — Android list_item_player_native: a packed chain of
   [team-indicator, nickname, ready-check]. The whole pack is centered in
   the .hand container; indicators hug the nickname directly (6dp gap on
   each side). The nickname uses flex shrink + ellipsis so long names
   truncate without pushing indicators off-screen. */
.hand-header {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    margin-bottom: 0.4rem;
    /* 1rem = 16px = Android's bodyLarge (16sp) used in
       list_item_player_native for the player nickname. */
    font-size: 1rem;
    width: 100%;
    max-width: 240px;
}
.hand-team-indicator,
.hand-ready-indicator {
    width: 16px; height: 16px;
    display: inline-flex; align-items: center; justify-content: center;
    flex: 0 0 16px;
}
.hand-team-indicator svg,
.hand-ready-indicator svg { width: 16px; height: 16px; }
.hand-team-empty,
.hand-ready-empty { visibility: hidden; }
/* Team flag colours — match the lobby team-flag tints. Independents get
   a muted dash glyph so the slot is never empty. */
.hand-team-indicator.team-1 svg { fill: var(--team-1, #B9342B); }
.hand-team-indicator.team-2 svg { fill: var(--team-2, #2E7D32); }
.hand-team-indicator.team-3 svg { fill: var(--team-3, #1565C0); }
.hand-team-indicator.team-4 svg { fill: var(--team-4, #C2185B); }
.hand-team-indicator.team-indep svg { fill: var(--md-on-surface-variant); }
.hand-ready-indicator svg { fill: var(--md-primary); }

.hand-name {
    font-weight: 400; color: var(--text-color);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    flex: 0 1 auto;     /* shrinkable, ellipsis-safe */
    min-width: 0;
}
.hand-name.is-me { font-weight: 900; }

/* Half-speed loss counter (e.g. "1/2" while a player has lost a round but
   not yet earned a card). Sits in the same header strip as the team / ready
   indicators. */
.hand-loss-counter {
    font-size: 0.7rem; font-weight: 700;
    color: var(--md-on-surface-variant);
    background: var(--md-surface-variant);
    border-radius: 9999px;
    padding: 2px 6px;
    flex: 0 0 auto;
}
/* Leave-queued indicator: small door glyph in the ready slot. */
.hand-ready-indicator.hand-leave-queued {
    font-size: 0.95rem;
    line-height: 1;
}

.hand-cards { display: flex; flex-direction: column; align-items: center; }
.hand-row {
    display: flex; align-items: flex-start; justify-content: center;
}
/* Vertical overlap between rows. */
.hand-row.second-row { margin-top: -8px; }

/* Card slot — sizes from --hand-card-w / --hand-card-h, which clamp
   responsively to viewport. */
.hand-slot {
    width: var(--hand-card-w);
    height: var(--hand-card-h);
    position: relative;
    flex-shrink: 0;
    box-sizing: border-box;
    margin-left: calc(var(--hand-card-overlap) * -1);
}
.hand-row .hand-slot:first-child { margin-left: 0; }
/* Empty + Next placeholder slots — Android PlayerHandAdapter
   createSolidPlaceholder: rounded rectangle with surfaceContainer blended
   ~35% with white as the fill, and a thick dashed light-gray stroke. The
   Empty placeholder is dimmed (DISABLED_ALPHA = 0.38) and slightly scaled
   down (0.95); the Next slot stays at full alpha and scale, plus shows a
   "+" glyph (alpha 0.7) in the centre. */
.hand-slot.is-empty,
.hand-slot.is-next {
    /* Android `createSolidPlaceholder`: rounded rect with `setStroke(4, LTGRAY, 15, 15)`
       — width 4dp, dash 15dp, gap 15dp, plus a generous corner radius.
       CSS `border: dashed` only gives ~2× border-width dashes (so 6px-ish
       segments), which reads as a tight checkerboard rather than the
       relaxed long-dash look Android has. Drawing the stroke as an inline
       SVG background lets us pin both the dash pattern and corner radius
       to match Android exactly. */
    border-radius: 8px;
    background-color: #F6EEE3;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='44' height='60'><rect x='1.6' y='1.6' width='40.8' height='56.8' rx='6.4' ry='6.4' fill='none' stroke='%23C8BFB4' stroke-width='3' stroke-dasharray='10 6'/></svg>");
    background-size: 100% 100%;
    background-repeat: no-repeat;
    border: none;
    box-sizing: border-box;
}
.hand-slot.is-empty {
    /* Android `placeholderAlpha = DISABLED_ALPHA = 0.38` — the Empty
       placeholder reads as "absent". Scale is applied via inline transform
       in JS (renderHandsArea) so it composes with the arc rotation. */
    opacity: 0.38;
}
.hand-slot.is-next {
    /* Next placeholder is at FULL alpha + scale (Android line 339:
       `cardBase.alpha = 1f, scaleX/Y = 1f` for non-Placeholder models).
       Was previously 0.7 here, which made it look dim and unhighlighted —
       Android shows it bright to draw the eye to the slot that fills next. */
    display: flex; align-items: center; justify-content: center;
    color: var(--md-on-surface-variant);
    font-weight: 600; font-size: 1.6rem; line-height: 1;
    opacity: 1;
}
/* Hand cards — Android getParchmentCardDrawable: white card pixels are
   replaced with a parchment TEXTURE (parchment_card_bg.webp at ~25% over a
   solid white base), then the card ink is multiplied on top, and the result
   is clipped to the card's alpha mask. Elevation is drawn via drop-shadow
   so the shadow follows the rounded alpha shape (matching Android's
   cardElevation=4dp on a transparent-bg MaterialCardView). */
.hand-slot.has-card .hand-card-img {
    position: relative; display: block;
    width: 100%; height: 100%;
    /* Solid white base + parchment texture at ~25% — emulates the two
       layers Android stacks before drawing the card ink. */
    background:
        linear-gradient(rgba(255,255,255,0.75), rgba(255,255,255,0.75)),
        url("assets/parchment_card_bg.webp") center / cover no-repeat,
        white;
    -webkit-mask-image: var(--card-mask);
    mask-image: var(--card-mask);
    -webkit-mask-mode: alpha; mask-mode: alpha;
    -webkit-mask-size: 100% 100%; mask-size: 100% 100%;
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    isolation: isolate;
}
.hand-slot.has-card .hand-card-img > img {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    display: block;
    mix-blend-mode: multiply;
}
/* Elevation — drop-shadow follows the masked alpha shape (won't leak around
   the rounded corners). 4dp ≈ Android cardElevation=4dp.
   The four cardinal halo drop-shadows are always present but transparent
   by default; the highlight variants override them with the halo colour.
   CSS interpolates the colours so the halo softly fades in/out over
   --anim-short (240ms = BLEF_ANIM_DURATION_SHORT), matching Android's
   updateHighlightBackground strokeWidth ValueAnimator. */
.hand-slot.has-card {
    /* Four drop-shadows: two for elevation, two stacked transparent halo
       placeholders that the highlight rules animate to coloured shadows.
       Stacking the same halo shadow twice compounds the alpha, producing a
       visibly darker outline than a single shadow at the same blur — the
       original four-direction 1px-offset trick gave a crisp outline, this
       gives a slightly softer one but at a fraction of the GPU cost
       (4 shadows vs 6 originally). Important on mobile, where late-game
       boards with 40+ cards × 6 drop-shadow ops × 60fps was killing fps. */
    filter:
        drop-shadow(0 1px 2px rgba(0,0,0,0.18))
        drop-shadow(0 2px 4px rgba(0,0,0,0.10))
        drop-shadow(0 0 0 transparent)
        drop-shadow(0 0 0 transparent);
    transition: filter var(--anim-short) var(--easing-standard);
    /* Hint compositor for transform only. We deliberately do NOT include
       'filter' in will-change — that hint tells the browser "the filter is
       about to change, don't cache the filtered raster", which is the
       opposite of what we want. The filter is mostly stable; the animated
       property is transform (deal-in, highlight pulse). */
    will-change: transform;
}
/* History-set highlight — Android PlayerHandAdapter.updateHighlightBackground
   draws a 2dp stroke around contributing cards in the chosen colour:
     NATURAL → md_theme_outline (slate)
     JOKER   → color_joker (#FFBF00)
   Drop-shadow rather than CSS border so the outline hugs the card's natural
   rounded shape (matches the alpha mask). */
.hand-slot.has-card.hand-card-highlight-natural {
    filter:
        drop-shadow(0 1px 2px rgba(0,0,0,0.18))
        drop-shadow(0 2px 4px rgba(0,0,0,0.10))
        drop-shadow(0 0 1.4px var(--md-outline))
        drop-shadow(0 0 1.4px var(--md-outline));
}
.hand-slot.has-card.hand-card-highlight-joker {
    filter:
        drop-shadow(0 1px 2px rgba(0,0,0,0.18))
        drop-shadow(0 2px 4px rgba(0,0,0,0.10))
        drop-shadow(0 0 1.4px #FFBF00)
        drop-shadow(0 0 1.4px #FFBF00);
}

/* === Phase 11 per-class card decorations ===
   Card artwork now carries the per-class symbol (amber ?, ↷, 0, …); CSS
   only paints outlines/tints that art can't carry per-instance. */

/* Activated joker keeps the joker-yellow outline persistently (not just on
   history-click highlight) so it never visually merges with a real card. */
.hand-slot.has-card.hand-slot-activated-joker {
    filter:
        drop-shadow(0 1px 2px rgba(0,0,0,0.18))
        drop-shadow(0 2px 4px rgba(0,0,0,0.10))
        drop-shadow(0 0 1.2px #FFBF00)
        drop-shadow(0 0 1.2px #FFBF00);
}
/* Used-blank top stripe — placed via a ::before inside .hand-card-img so the
   stripe is clipped to the card's alpha mask (respects rounded corners).
   Colour follows the actual suit (red for ♦/♥, black for ♣/♠) so the signal
   is consistent for both the owner (whose face shows the suit) and opponents
   (whose face is a hidden back, so the stripe is the only suit hint). The
   3px height keeps it above the rank glyph and the top-left suit symbol. */
.hand-slot.has-card.hand-slot-blank-stripe-red .hand-card-img::before,
.hand-slot.has-card.hand-slot-blank-stripe-black .hand-card-img::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 3px;
    z-index: 4;
    pointer-events: none;
}
/* Pure red / pure black to match the card-art rendering exactly (sampled
   from the .webp suit symbols). */
.hand-slot.has-card.hand-slot-blank-stripe-red .hand-card-img::before   { background: #FF0000; }
.hand-slot.has-card.hand-slot-blank-stripe-black .hand-card-img::before { background: #000000; }
/* (Placeholder corners are now fully rounded on all sides; the SVG-drawn
   stroke renders the same regardless of overlap position. Matches the
   Android look where each placeholder is a self-contained rounded
   rectangle and the slight overlap reads as a soft tuck rather than the
   half-flat-half-rounded shape the previous CSS produced.) */

/* Common hand: marker glyph; the "Common" text is set in JS for i18n. */
.hand-common .hand-name { color: var(--md-tertiary); font-style: italic; }

/* Center area removed — status (your turn / waiting / round ended) lives
   in the history bubbles and below the bet menu, matching Android. */
.center-area { display: none; }

/* (My-area / opponents-area removed — replaced by .hands-area unified layout.) */

/* Card animations — Android equivalents:
   - hand-slot-deal-in: alpha 0→1, scale 0.8→1, staggered by card index.
     Mirrors PlayerHandAdapter.animateNewDealSwap (350ms duration with
     cardIndex × 120ms delay).
   - hand-slot-flip-reveal: rotateY 0→180° about the card's vertical axis.
     The image (the new face) is swapped in instantly when the slot
     re-renders, but at the 90° midpoint the card is edge-on so visually it
     reads as "flipping over". 700ms duration. */
/* Deal-in runs on the inner .hand-card-img + the empty/next slot
   placeholders so the slot's inline arc transform isn't overridden mid-
   animation (which would snap the card upright + un-translated).
   Standard easing — Android animateNewDealSwap doesn't call
   setInterpolator, defaulting to AccelerateDecelerateInterpolator. */
.hand-slot-deal-in .hand-card-img,
.hand-slot.is-empty.hand-slot-deal-in,
.hand-slot.is-next.hand-slot-deal-in {
    animation: handDealIn var(--anim-default) var(--easing-standard) backwards;
    will-change: transform, opacity;
}
@keyframes handDealIn {
    from { opacity: 0; transform: scale(0.8); }
    to   { opacity: 1; transform: scale(1); }
}
/* The flip animation runs on the INNER .hand-card-img (the surface
   showing the image). Putting it on the slot would clobber the slot's
   inline arc transform (translate + rotate around bottom-centre); the
   inner element is independent of that. */
.hand-slot-flip-reveal .hand-card-img {
    /* Standard easing — Android animateFlipReveal doesn't call
       setInterpolator, defaulting to AccelerateDecelerateInterpolator. */
    animation: handFlipReveal var(--anim-long) var(--easing-standard);
    transform-style: preserve-3d;
    backface-visibility: hidden;
}
@keyframes handFlipReveal {
    0%   { transform: perspective(800px) rotateY(0deg); }
    50%  { transform: perspective(800px) rotateY(90deg); }
    100% { transform: perspective(800px) rotateY(0deg); }
}

/* === Controls Pane — right side ===
   History grows to fill, actions sticky at the bottom. */
.controls-pane {
    display: flex; flex-direction: column;
    background: transparent;
    /* Top padding 0 so the history-card hugs the hands-area's bottom edge —
       only the history-card's own margin separates them. */
    padding: 0 0.75rem 0;
    gap: 0;
    overflow: hidden;        /* the history-card scrolls internally */
}
.controls-pane > .history-card { margin-top: 0.75rem; }

/* History — Android BlefCard style: surfaceContainer bg, 28dp radius, no border.
   The card sizes to its content (does NOT grow to fill remaining height) so the
   inside top/bottom padding stays symmetric. It still scrolls when content is
   taller than available space. */
.history-card {
    border-radius: 28px; border: none;
    flex: 0 1 auto; overflow-y: auto;
    background: var(--md-surface-container);
    padding: 16px;
    /* Mobile Chrome: when the history reaches its top/bottom edge, scroll
       chaining used to engage the page (which here is locked at 100dvh, so
       there's nothing to actually scroll — but the gesture still triggers
       Chrome's URL-bar show/hide animation, which then reflows the layout
       under the user's finger and yanks the history scroll position). The
       Android app doesn't have this problem because its NestedScrollView is
       inside a Coordinator — same idea: contain the scroll. `contain` stops
       the gesture at the list edge, preventing the URL bar reflow without
       affecting normal in-list scrolling.  Also disables the rubber-band
       "bounce" past the edges (Chrome) that re-triggered the same problem
       when the user flicked hard. */
    overscroll-behavior: contain;
}

.history-list {
    list-style: none;
    /* While the WAAPI height animation is interpolating from oldHeight →
       newHeight (after a new bubble is appended), the list's set height
       is briefly LESS than its content's natural height. Without
       overflow:hidden the items below the set-height visually leaked out
       and the parent .history-card briefly registered overflow → a
       scrollbar would flash for ~350ms each time a row arrived. Clipping
       at the list level keeps the card's overflow detection seeing only
       the animated height, no scroll thrash. */
    overflow: hidden;
}

/* --- Speech bubble history items: name and bubble meet at center ---
   Android uses 4dp paddingVertical on the row + 4dp marginVertical on the
   bubble, which together produce 8dp of breathing room between bubbles. */
.history-item { padding: 4px 0; }

.history-bubble-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: center;
    column-gap: 14px;       /* visible gap between nickname and bubble tail */
}
/* History nickname matches the in-game hand-name proportions (0.9rem). */
.history-player-name {
    /* 1rem = 16sp — Android list_item_history.xml uses bodyLarge (16sp)
       for the nickname and 16sp for the bubble text. */
    font-size: 1rem; font-weight: 400; color: var(--md-on-surface-variant);
    justify-self: end;
    text-align: right;
    line-height: 1.2;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    max-width: 100%;
}
.history-player-name.is-me { font-weight: 900; color: var(--text-color); }
/* History bubble — single colour regardless of content type. Padding
   matches Android list_item_history.xml: 8dp horizontal, 4dp vertical.
   Font size matches the nickname on the left (Android uses 16sp on both).
   The bg-color transition powers the selection-brightening animation. */
.history-bubble {
    background: var(--bubble-color);
    border-radius: 16px;
    padding: 4px 8px; position: relative;
    color: var(--bubble-text);
    /* bodyLarge — Android list_item_history.xml uses 16sp for the bubble. */
    font-size: 1rem; line-height: 1.3;
    max-width: 100%;
    justify-self: start;
    /* Standard easing — Android HistoryAdapter selection animator has no
       setInterpolator, so it defaults to AccelerateDecelerateInterpolator. */
    transition: background-color var(--anim-short) var(--easing-standard);
}
.history-bubble::before { transition: border-right-color var(--anim-short) var(--easing-standard); }
.history-bubble::before {
    content: '';
    position: absolute; left: -6px; top: 50%;
    transform: translateY(-50%);
    width: 0; height: 0;
    border-top: 6px solid transparent;
    border-bottom: 6px solid transparent;
    border-right: 8px solid var(--bubble-color);
}
/* "Lost the round" is narration, not a claim/action — no bubble bg, no
   tail, standard (non-italic) text colour.
   Horizontal padding stays 8px so the text aligns with regular bubbles
   (whose 8px padding-left puts "Check!" / the cards 8px in from the
   bubble's bg edge). The tail being `content: none` doesn't move the
   bubble's text — the tail is `position: absolute` and never contributed
   to the bubble's box — but DROPPING the padding does, and a previous
   `padding: 5px 0` left narration text 8px to the left of Check!.
   Android handles this with a Space placeholder of the tail's width when
   the tail is gone (list_item_history.xml: `history_space` Space view that
   becomes visible when `speech_tail` becomes GONE) — same idea, different
   mechanism. */
.history-bubble.history-bubble-narration {
    background: transparent; padding: 5px 8px;
    color: var(--text-color);
}
.history-bubble.history-bubble-narration::before { content: none; }
/* Pulsating ellipsis (only the "…" inside the bubble — the bubble bg stays
   steady). Matches Android HistoryAdapter.startPulseAnimation: 2000ms,
   alpha 1 → 0 → 1, linear, infinite. */
.pending-pulse { animation: pendingPulse 2s linear infinite; display: inline-block; }
@keyframes pendingPulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0; }
}
/* Dimming for non-most-recent bets — mirrors Android HistoryAdapter:
   `playerTextView.alpha = 0.5` and `actionCardsContainer.alpha = 0.5`
   ONLY. The bubble background is intentionally NOT dimmed, so the
   surfaceBright "selected" highlight stays at full alpha when you tap a
   previous-bet bubble to inspect it. The previous web rule put opacity 0.5
   on the whole <li>, which dimmed the brightening too — the selection
   highlight then looked washed out and you couldn't tell which past bet
   you'd picked.
   For text-only bubbles ("Checked"), the bubble bg also has to stay
   undimmed, so we drop the text alpha via color rather than view opacity
   (Android does the same: `actionTextView.setTextColor` with alpha-modulated
   colour, not `actionTextView.alpha`). */
.history-item-dimmed .history-player-name,
.history-item-dimmed .history-cards-container { opacity: 0.5; }
.history-item-dimmed .history-bubble:not(.history-bubble-narration) {
    color: color-mix(in srgb, var(--bubble-text) 50%, transparent);
}
/* Narration ("Lost the round") has no bubble bg — plain opacity is fine. */
.history-item-dimmed .history-bubble.history-bubble-narration { opacity: 0.5; }
.history-item-clickable { cursor: pointer; }
/* Selected history bubble — Android animates the bubble's bg from
   surfaceContainerLowest (#F8F1E9) to surfaceBright (#FFF8F7) — the surface
   gets BRIGHTER, no outline. The transition on .history-bubble +
   .history-bubble::before makes the change ease in/out smoothly. */
.history-item-selected .history-bubble { background: #FFF8F7; }
.history-item-selected .history-bubble::before { border-right-color: #FFF8F7; }
/* New history items fade in IN PLACE while existing items animate down via FLIP. */
.history-item.history-item-new {
    /* New row fade-in matches Android RecyclerView ItemAnimator
       addDuration = BLEF_ANIM_DURATION (350ms). Standard easing —
       DefaultItemAnimator uses the framework default. */
    animation: historyFadeIn var(--anim-default) var(--easing-standard);
}
@keyframes historyFadeIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* Cards inside history bubbles — per-card offsets are set inline via JS to
   match Android's HISTORY_H_OVERLAP_FACTOR / BET_STAIRCASE_STEP / BET_MULTIROW_STEP. */
.history-cards-container { display: flex; align-items: flex-start; }
/* MultiRow uses absolute positioning, so we don't want flex layout to interfere. */
.history-cards-container.layout-multirow { display: block; position: relative; }

/* Exchange (joker / blank → target): no overlap, arrow between cards. Reads
   as a transformation rather than a 2-card set. */
.history-cards-container.layout-exchange { gap: 2px; align-items: center; }
.history-exchange-arrow {
    color: var(--text-muted);
    font-size: 1rem; line-height: 1;
    padding: 0 2px;
    user-select: none;
}

/* History card: just an outlined card. State is conveyed via outline colour
   (and a subtle tint overlay on joker / missing only — natural cards are
   shown at full readability).
   The right-side rounded corners are flattened on cards that are NOT the
   last in the row, so the rounded corner of the next (overlapping) card
   doesn't leave a visible "diamond" peeking through. */
/* Outer wrap — Android equivalent of the GradientDrawable rectangle behind
   the card image: rectangular, rounded, in the state colour, with 1.6px of
   padding so the colour peeks out around the card as a visible "outline".
   No CSS border (Android sets strokeWidth=0). */
.history-card-wrap {
    position: relative; display: inline-block; flex-shrink: 0;
    border-radius: 4px;
    padding: 1.6px;
    box-sizing: border-box;
    background: var(--md-outline-variant);
}
/* (Card outlines follow the image's natural rounded corners — no corner
   flattening, even on overlapping cards.) */
/* State colour — Android sets the GradientDrawable's color to one of these.
   The wrap's bg is the visible "outline" that frames the masked card. */
.history-card-wrap.tint-unknown { background: var(--md-outline-variant); }
.history-card-wrap.tint-natural { background: var(--md-outline); }
.history-card-wrap.tint-joker   { background: #FFBF00; }
.history-card-wrap.tint-missing { background: var(--md-error); }
/* JOKER / MISSING also get a CARD_TINT_ALPHA (=16/255 ≈ 6%) overlay on top
   of the card via SRC_ATOP. The overlay is clipped to the card's alpha mask
   so the transparent corners stay transparent. */
.history-card-wrap.tint-joker > .history-card-img::after,
.history-card-wrap.tint-missing > .history-card-img::after {
    content: ''; position: absolute; inset: 0; pointer-events: none;
    -webkit-mask-image: var(--card-mask);
    mask-image: var(--card-mask);
    -webkit-mask-mode: alpha; mask-mode: alpha;
    -webkit-mask-size: 100% 100%; mask-size: 100% 100%;
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
.history-card-wrap.tint-joker   > .history-card-img::after { background: rgba(255, 191, 0, 0.063); }
.history-card-wrap.tint-missing > .history-card-img::after { background: rgba(186, 26, 26, 0.063); }
/* (Don't drop opacity on individual cards — overlap with siblings stacks
   outlines and looks ugly. Use the row-level .history-item-dimmed instead.) */

/* Inner card — the masked surface that holds the image. Width/height are
   set inline by JS so the parent (history-card-wrap) sizes itself + the
   1.6px outline padding. The white background of the .webp is "replaced"
   by #FFFDFC via mix-blend-mode multiply against the wrap's #FFFDFC bg.
   The transparent rounded corners of the .webp survive the mask. */
.history-card-img {
    position: relative; display: block;
    width: 100%; height: 100%;
    background: #FFFDFC;        /* the parchment tint that replaces white */
    -webkit-mask-image: var(--card-mask);
    mask-image: var(--card-mask);
    -webkit-mask-mode: alpha; mask-mode: alpha;
    -webkit-mask-size: 100% 100%; mask-size: 100% 100%;
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    border-radius: 3px;          /* slight rounding to match card shape */
    isolation: isolate;
}
.history-card-img > img {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    display: block;
    mix-blend-mode: multiply;
}

/* === Bet Menu (Actions card) — its own grid area in the .game-layout.
   On phones it has a FIXED height (drag-handle-controlled) and sits in
   the auto-row at the bottom of the stacked layout. On wide screens it
   fills the right column at full height (no drag handle needed).
   --bet-menu-height controls the INNER GRID height (matching Android's
   max_bet_menu_height = 322dp). The card auto-sizes around it.
   Horizontal padding 12px = Android's betContainer paddingHorizontal=12dp.
   Background transparent so the body's niedzica wallpaper shows through;
   the inner grid then layers a 50% surfaceContainerLowest on top
   (matching Android's @color/surface_glass = 50% alpha). */
.actions-card {
    --bet-menu-height: 322px;
    border-radius: 0; border: none;
    /* The grid inside has `padding: 6px 0 4px` (4px breathing-room above
       its bottom edge), so the visible gap below the last button is
       (card padding-bottom) + 4. We pick 8px on the card so that the
       total reads as 12px — matching the 12px left/right padding on the
       card itself. */
    padding: 0 12px 8px;
    /* Transparent: the inner .actions-grid layers a 50% surface_glass on
       top and the body's niedzica wallpaper shows through. Overlap with
       the history is prevented by #game-view being constrained to exact
       viewport height — see the comment there for the layout reasoning. */
    background: transparent;
    flex-shrink: 0;
    display: flex; flex-direction: column;
    position: sticky; bottom: 0;
}
@media (min-width: 900px) {
    /* On wide screens the menu fills its grid cell and the drag handle is
       hidden — there's already plenty of vertical room. The OK/Ready
       single-action button is anchored to the TOP of the menu on this
       layout (handled by the .is-single-action override below). */
    .actions-card { height: 100%; position: static; }
    .actions-card .actions-drag-handle { display: none; }
    .actions-card .actions-grid { height: 100%; }
    .actions-card.is-single-action .actions-grid { align-content: start; }
}
.actions-card[hidden],
.actions-card.hidden { display: none; }
/* The drag handle only makes sense when the bet menu (multiple buttons +
   header) is displayed. Round-over OK / observer / waiting-for-ready states
   don't need a resizable area, so the handle is hidden in those cases. */
.actions-card.is-single-action .actions-drag-handle { display: none; }
/* Drag handle — Android uses a small grip bar that sits ON a 2dp primary
   horizontal line at the top of the bet container (recyclerview_top_border).
   The line spans the full width; the bar is centered on it. The line +
   handle live OUTSIDE the bet-menu's horizontal padding so the line spans
   edge-to-edge.
   Height 12px (was 18px): trims the empty space above the bar — Android's
   handle has only 2dp of paddingTop above the bar, no large vertical gap. */
.actions-drag-handle {
    flex: 0 0 auto;
    position: relative;
    height: 12px;
    margin: 0 -12px;                /* span past the .actions-card horizontal padding */
    cursor: ns-resize;
    touch-action: none;
    user-select: none;
}
/* The 2dp full-width primary line at the bottom edge of the drag handle
   (= top edge of the bet menu grid). Matches Android's
   recyclerview_top_border drawable. */
.actions-drag-handle::after {
    content: '';
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 2px;
    background: var(--md-primary);
}
/* The 50×4 dp grip bar centered ON the line — same primary colour as the
   line, so the bar reads as a thicker section in the middle. */
.actions-drag-handle::before {
    content: '';
    position: absolute;
    left: 50%; bottom: 0;
    transform: translate(-50%, 1px);   /* center on the 2px line */
    width: 50px; height: 4px; border-radius: 2px;
    background: var(--md-primary);
}
.actions-drag-handle:hover::before { background: var(--md-on-primary-container); }
.actions-card.is-dragging .actions-drag-handle::before { background: var(--md-on-primary-container); }
/* The header sits at the top of the menu; buttons stack below it from top
   to bottom. Empty space at the bottom (when fewer rows than the menu can
   fit) is intentional — the header position is a fixed anchor.
   The grid's height matches Android's controlPanelRecyclerView (the
   --bet-menu-height variable) so the drag handle resizes the visible
   button area, not the surrounding chrome. */
.actions-grid {
    flex: 0 0 auto;
    height: var(--bet-menu-height);
    /* In a flex column, the default `min-height: auto` would force the
       grid to be at least as tall as its content (min-content), preventing
       the user from shrinking the menu below ~4 rows. Override to 0 so
       the explicit height wins all the way down to MIN_PX. */
    min-height: 0;
    /* Android's controlPanelRecyclerView paddingVertical=4dp + the
       betContainer paddingVertical=2dp create a small breathing space
       between the 2dp horizontal line and the first bet row. */
    padding: 6px 0 4px;
    /* Mirrors Android's @color/surface_glass: surfaceContainerLowest at
       50% alpha. The body's niedzica wallpaper shows through faintly. */
    background: rgba(248, 241, 233, 0.5);
    display: grid; grid-template-columns: repeat(12, 1fr); gap: 0.35rem;
    grid-auto-rows: max-content;
    align-content: start;
    overflow-y: auto;
}
/* Single-action mode (round-over OK / waiting-for-ready / observing)
   collapses the grid to its content height — no need to reserve the full
   4-row height when only one button is visible. The history card reclaims
   the freed space.
   The grid also gets 4px extra horizontal padding so the full-width OK /
   Ready button sits 16px (= Android's screen_edge_margin) from the screen
   edge: 12px on the .actions-card + 4px here. Padding (rather than margin
   on the button) keeps the grid item bounded by the grid track and avoids
   the horizontal overflow that `gridColumn: span 12` + `margin` produced.
   The 50% surface_glass background is suppressed in this mode because
   there's no bet menu to frame — Android doesn't paint behind a lone
   button either, and the niedzica wallpaper should remain visible. */
.actions-card.is-single-action .actions-grid {
    height: auto;
    padding-left: 4px;
    padding-right: 4px;
    background: transparent;
}
/* Round-over OK button anchoring depends on viewport:
   - On phones, it's at the bottom (thumb-reachable).
   - On wide screens, it's at the TOP of the right column (no need to
     reach across the wide menu).
   The wide-screen override lives in the matching @media block above. */
@media (max-width: 899px) {
    .actions-card.is-single-action .actions-grid { align-content: end; }
}
/* Header h4 — the wrap (.bet-header-wrap) handles vertical centring, so
   the h4 itself just needs default margins reset. The previous rule set
   `height: 100%` + flex centring, which made the h4 grab the wrap's full
   height and pushed the subtitle below the wrap's bottom padding,
   producing a visible gap above it. */
.actions-grid h4 {
    color: var(--text-color);
    margin: 0;
}

/* Bet buttons — image-based; all bet buttons share a fixed height
   so rows align (matches Android's bet_menu_card_height). Rounded like
   Android's MaterialCardView default. */
.bet-btn {
    background: var(--md-surface-container);
    border: 2px solid var(--md-outline-variant);
    border-radius: 24px;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    padding: 4px 3px;
    height: 70px;
    overflow: hidden; cursor: pointer;
    transition: opacity 0.12s;
    width: 100%;
    /* Bet menu buttons (Back / Check / Browse / Action) are all non-bold —
       overrides the .btn font-weight: 700 default. */
    font-weight: 400;
}
.bet-btn:hover:not(:disabled) { opacity: 0.8; }
.bet-btn-action { border-color: var(--md-primary); }
/* Check! is a FinalBet — same primary outline as other action buttons. */
.bet-btn-check  { border-color: var(--md-primary); font-size: 1rem; font-weight: 400; }

/* === Phase 10 active-card staging === */
.card-picker-card { min-width: 360px; max-width: 480px; }
.card-picker-grid {
    display: grid; grid-template-columns: repeat(8, 1fr); gap: 4px;
    margin: 0.6rem 0 1rem; max-height: 60vh; overflow-y: auto;
}
.card-picker-card-btn {
    background: white; border: 1.5px solid var(--md-outline-variant);
    border-radius: 4px; padding: 0; cursor: pointer;
    width: 100%; aspect-ratio: 0.8;
    display: flex; align-items: center; justify-content: center;
    transition: border-color 0.12s, transform 0.12s;
    font-family: inherit; font-weight: 700; font-size: 0.8rem;
}
.card-picker-card-btn:hover { border-color: var(--md-primary); transform: scale(1.05); }
.card-picker-card-btn img { width: 100%; height: 100%; object-fit: contain; }

/* Staged sub-actions strip — small chips shown above the bet menu while the
   user has queued an exchange or draw but not yet submitted a final action. */
.staged-actions {
    display: flex; flex-wrap: wrap; gap: 0.4rem;
    grid-column: span 12;
    padding: 0.4rem 0;
}
.staged-action-chip {
    display: inline-flex; align-items: center; gap: 0.3rem;
    background: #FFBF00; color: #2a1f00;
    padding: 0.2rem 0.5rem; border-radius: 9999px;
    font-size: 0.78rem; font-weight: 700;
    border: none; cursor: pointer; font-family: inherit;
}
.staged-action-chip-blank { background: #b73d2b; color: white; }
.staged-action-chip:hover { opacity: 0.85; }
.staged-action-chip .x { font-size: 0.85rem; opacity: 0.7; }

/* Hand-slot click target indicator: when the slot represents an unused
   joker / blank and it's the player's turn, give it a subtle interactive cue. */
.hand-slot.is-stageable { cursor: pointer; }
.hand-slot.is-stageable:hover { filter: brightness(1.1); }
.bet-btn-browse { border-color: var(--md-outline-variant); }
/* Back button — same surfaceContainer fill as the bet category buttons; only
   the muted outline / smaller text distinguish it from action buttons.
   1rem = titleMedium (16sp) matching the bet menu title. */
.bet-btn-back   { background: var(--md-surface-container); border-color: var(--md-outline-variant); font-size: 1rem; font-weight: 400; }
.bet-btn-disabled {
    background: var(--md-surface-container-lowest);
    border-color: transparent; opacity: 0.42; cursor: not-allowed;
}

/* Card image containers inside bet buttons */
.bet-visual-wrapper {
    display: flex; flex-direction: column; align-items: center;
    gap: 1px; pointer-events: none;
}
.bet-visual-single,
.bet-visual-staircase,
.bet-visual-row {
    display: flex; align-items: flex-start;
}
/* Bet menu card — mirrors Android's bg_mini_card drawable: a rounded white
   rectangle (3dp corner radius, 2dp outlineVariant stroke) with 1dp padding
   inside, holding the tinted card image. The outer rectangle gives every
   card a clear, non-blending outline; the inner image is the masked card
   with white pixels replaced by #FFFDFC. */
.bet-card-img {
    position: relative; display: block; flex-shrink: 0;
    background: #FFFFFF;
    border: 2px solid var(--md-outline-variant);
    border-radius: 3px;
    padding: 1px;
    box-sizing: border-box;
}
.bet-card-img > .bet-card-img-inner {
    position: relative; display: block;
    width: 100%; height: 100%;
    background: #FFFDFC;        /* md_theme_almostBasicallyWhite */
    -webkit-mask-image: var(--card-mask);
    mask-image: var(--card-mask);
    -webkit-mask-mode: alpha; mask-mode: alpha;
    -webkit-mask-size: 100% 100%; mask-size: 100% 100%;
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    isolation: isolate;
}
.bet-card-img-inner > img {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    display: block;
    mix-blend-mode: multiply;
}
.bet-subtitle {
    /* bodySmall — Android list_item_bet.xml uses
       textAppearanceBodySmall (12sp) with autoSize 8-12sp. */
    font-size: 0.75rem; color: var(--md-outline);
    text-align: center; margin-top: 2px; line-height: 1.2;
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Header slot — title + optional explanation subtitle stacked in the 8
   columns reserved for the header. Matches Android, where both labels sit
   inside the same header area (not as full-width entities). */
.bet-header-wrap {
    display: flex; flex-direction: column; align-items: center;
    justify-content: center;
    padding: 0.2rem 0.5rem 0.4rem;
    gap: 1px;
}
.bet-header-title {
    /* titleMedium — Android list_item_bet.xml uses 16sp. */
    font-size: 1rem; line-height: 1.2;
    text-align: center;
    color: var(--text-color);
    margin: 0;
}
.bet-header-subtitle {
    /* bodySmall (12sp) — same as the bet button subtitle below the title. */
    font-size: 0.75rem;
    color: var(--md-outline);
    text-align: center;
    line-height: 1.2;
}

/* === Finished View (full-screen, mirrors Android layout_game_finished) ===
   Layout: top bar (exit + title) + scrollable standings list + a Rematch
   button anchored at the bottom. */
#finished-view { background: transparent; padding: 0; min-height: 100vh; }
#finished-view.active-view {
    display: flex; flex-direction: column; align-items: stretch;
    width: 100%; min-height: 100vh;
}
.finished-layout {
    flex: 1 1 auto;
    display: flex; flex-direction: column; align-items: stretch;
    width: 100%; max-width: 720px;
    margin: 0 auto;
    padding: 16px 16px 24px;
    gap: 16px;
}
@media (min-width: 900px) {
    .finished-layout { max-width: 880px; }
}
.finished-standings-list {
    list-style: none;
    flex: 1 1 auto;
    padding: 32px 0 0;        /* Android paddingTop=32dp */
    margin: 0;
    overflow-y: auto;
}
/* Each standing row matches Android list_item_final_standing:
   centered horizontal LinearLayout with padding 8dp containing the team
   indicator (12sp×12sp, marginEnd 6dp) + the player nickname (24sp). */
.finished-standings-list li {
    display: flex; align-items: center; justify-content: center;
    gap: 6px;
    padding: 8px;
    text-align: center;
    /* slide-in-bottom + fade-in (Android slide_in_bottom.xml @ BLEF_ANIM_DURATION_LONG, decelerate). */
    animation: finishedSlideIn var(--anim-long) var(--easing-decelerate) backwards;
}
.finished-standings-list li.is-clickable { cursor: pointer; border-radius: 12px; transition: background 0.12s; }
.finished-standings-list li.is-clickable:hover { background: var(--md-surface-variant); }
@keyframes finishedSlideIn {
    from { opacity: 0; transform: translateY(50%); }
    to   { opacity: 1; transform: translateY(0); }
}
.finished-standings-list .standing-team {
    width: 14px; height: 14px;
    display: inline-flex; align-items: center; justify-content: center;
    flex: 0 0 auto;
}
.finished-standings-list .standing-team svg { width: 14px; height: 14px; }
.finished-standings-list .standing-team.team-1 svg { fill: var(--team-1); }
.finished-standings-list .standing-team.team-2 svg { fill: var(--team-2); }
.finished-standings-list .standing-team.team-3 svg { fill: var(--team-3); }
.finished-standings-list .standing-team.team-4 svg { fill: var(--team-4); }
.finished-standings-list .standing-team.team-indep svg { fill: var(--md-on-surface-variant); opacity: 0.6; }
.finished-standings-list .standing-name {
    /* 1.5rem = 24sp — Android list_item_final_standing.xml sets
       textSize="24sp" explicitly. */
    font-size: 1.5rem;
    color: var(--text-color);
}
.finished-standings-list .standing-name.is-me { font-weight: 900; }
.finished-standings-list li.is-winner .standing-name { color: var(--md-tertiary); font-weight: 700; }
.finished-rematch-btn {
    margin: 0 auto;
    width: calc(100% - 32px);    /* Android screen_edge_margin=16dp on each side */
    max-width: 480px;
}

/* The game layout is always single-column (Android stacks hands above the
   history pane). board-pane and round-indicator behave the same on every
   width — no extra responsive rules needed. */
.controls-pane { max-height: none; }
.board-pane {
    border-right: none; min-height: auto;
    /* Strip horizontal padding so the hands-area runs edge-to-edge —
       no parallel "frame" of background-color colour either side. */
    padding-left: 0; padding-right: 0;
}
.round-indicator { padding-left: 1.5rem; padding-right: 1.5rem; }
