/* === 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;
}
.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;
}
.lobby-rules-card   { order: 1; }
.lobby-players-card { order: 2; }
@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 */
    .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);
}
.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: the Ready button sits ABOVE the rules + players cards
       rather than at the bottom of a tall, mostly-empty viewport. Flip
       the order via flex `order: -1` and drop the sticky-bottom behaviour
       — there's nothing to scroll past on wide layouts. */
    .lobby-controls {
        order: -1;
        position: static;
        padding: 0 0 1rem;
    }
}

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

.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);
}
.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%; min-height: 100vh;
}
/* 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-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; }

.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 {
    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( 1px 0 0 transparent)
        drop-shadow(-1px 0 0 transparent)
        drop-shadow(0  1px 0 transparent)
        drop-shadow(0 -1px 0 transparent);
    transition: filter var(--anim-short) var(--easing-standard);
}
/* 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( 1px 0 0 var(--md-outline))
        drop-shadow(-1px 0 0 var(--md-outline))
        drop-shadow(0  1px 0 var(--md-outline))
        drop-shadow(0 -1px 0 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( 1px 0 0 #FFBF00)
        drop-shadow(-1px 0 0 #FFBF00)
        drop-shadow(0  1px 0 #FFBF00)
        drop-shadow(0 -1px 0 #FFBF00);
}
/* (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;
}
@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;
}

.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. */
.history-bubble.history-bubble-narration {
    background: transparent; padding: 5px 0;
    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; }
}
.history-item-dimmed { 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; }

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