/* Dalmuti — mobile-first.
   Layout follows .claude/commands/card-game-UI-render.md: cards are absolutely
   positioned in #game and hands sit around a centred board, bottom / left / top
   / right by slot. Reference implementation: games/Haggis/. */

/* ── Old London (self-hosted) ────────────────────────────────────────────────
   Blackletter display face, used for all text by request. It no longer sets the
   card numerals: those are printed on the scanned faces in assets/cards/, so the
   old .card-num / .back-mark rules are gone with the text nodes that wore them.

   woff2 first (20 KB vs 49 KB for the ttf); the ttf stays as the fallback. */
@font-face {
    font-family: 'Augusta';
    src: url('assets/fonts/Augusta.woff2') format('woff2'),
         url('assets/fonts/Augusta.ttf') format('truetype');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

@property --card-w { syntax: '<length>'; inherits: true; initial-value: 64px; }

:root {
    /* --card-scale shrinks every card when six fans have to share the table */
    --card-scale: 1;
    --opp-scale: 0.72;
    /* Sizes relative to --card-w. JS reads all three back out of the computed
       style (oppScale/playScale/handScale) so the fan geometry and the play
       zones are laid out at the size the cards are actually drawn — change one
       here and the layout follows on its own. */
    --play-scale: 0.874;
    --hand-scale: 1.2;
    --card-w: calc(clamp(44px, 10.5vw, 74px) * var(--card-scale));
    /* The scans' own aspect (336x542), so no face is ever stretched. cardDims()
       in game.js repeats this number for the seat geometry — change both. */
    --card-ratio: 1.613;
    --header-h: 52px;
    --gold: #c9a94f;
    --ink: #f2ecdf;
    --ink-dim: rgba(242, 236, 223, 0.62);
    --felt: #1d4636;
    --panel: rgba(10, 22, 18, 0.55);
    --line: rgba(201, 169, 79, 0.28);
    --radius: 10px;
    /* Parchment, sampled off the middle of assets/cover.png: the field's median
       is #ec9260, its light patches reach #faae78 and its mottle #b65f3b, and
       the cover inks its lettering in a dark brown. Dialogs are the one surface
       that carries the cover's colour — the table stays felt-green, so a panel
       reads as paper laid on the table rather than as part of it. */
    --parch-hi: #dd9265;
    --parch-lo: #bd6837;
    --parch-ink: #40210f;
    --parch-ink-dim: rgba(64, 33, 15, 0.72);
    --parch-line: rgba(74, 32, 12, 0.30);
    --parch-accent: #8a3d12;
    /* Medieval display face. Cinzel is inscriptional-Roman rather than
       blackletter: it reads as period without becoming unreadable at 12px, and
       it ships LINING numerals — the trap Cormorant Garamond fell into, where
       card faces rendered "11" as "II". Card numerals still use Inter. */
    /* Old London everywhere, by request. Inter stays only as the metric
       fallback while the webfont loads. */
    --display: 'Augusta', 'Cinzel', Georgia, serif;
    --display-old: 'Augusta', 'Cinzel', Georgia, serif;
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    background: #0d1a15;
    color: var(--ink);
    font-family: var(--display);
    overscroll-behavior: none;
    -webkit-tap-highlight-color: transparent;
}

body { touch-action: none; }

button, select, input, a {
    font-family: inherit;
    touch-action: manipulation;
    -webkit-user-select: none;
    user-select: none;
}

#app {
    display: flex;
    flex-direction: column;
    height: 100dvh;
    /* Shared table photo used across Deckades (games/table.avif), tinted so the
       cards stay the brightest thing on screen. */
    background:
        radial-gradient(ellipse at 50% 40%, rgba(112, 74, 168, 0.26), transparent 68%),
        linear-gradient(rgba(16, 12, 30, 0.70), rgba(11, 9, 22, 0.84)),
        url('../table.avif') center/cover no-repeat fixed;
}

/* ── Header bar ─────────────────────────────────────────────────────────── */
#header-bar {
    position: relative;
    flex: 0 0 var(--header-h);
    height: var(--header-h);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 0 12px;
    /* a touch of the table's purple, so the bar belongs to the same room */
    background: linear-gradient(180deg, #2a1f3e, #17102a);
    border-bottom: 1px solid rgba(150, 110, 205, 0.30);
    z-index: 300;
    pointer-events: none;
}

#header-bar > * { pointer-events: auto; }

#header-left, #header-right { display: flex; align-items: center; gap: 6px; }

.deckades-logo {
    font-family: var(--display);
    font-weight: 700;
    font-size: 21px;
    letter-spacing: 0.04em;
    color: rgba(242, 236, 223, 0.62);
    text-decoration: none;
    white-space: nowrap;
}

.deckades-spade { color: var(--gold); font-size: 1.1em; vertical-align: middle; }

.game-wordmark {
    font-family: var(--display-old);
    font-size: 15px;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--ink-dim);
    margin-left: 10px;
    white-space: nowrap;
}

#header-center {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    max-width: min(46vw, 520px);
    pointer-events: none;
    text-align: center;
}

.game-wordmark {
    font-family: var(--display-old);
    font-size: 26px;
    letter-spacing: 0.08em;
    color: var(--gold);
    white-space: nowrap;
    margin: 0;
}

/* Status line: absolutely placed by renderActions in the same pass as the hand
   and the buttons, so it can never drift into either. */
#status-line {
    position: absolute;
    transform: translateX(-50%);
    max-width: 92vw;
    text-align: center;
    pointer-events: none;
    z-index: 120;
}

#header-msg {
    display: block;
    font-family: var(--display);
    font-size: 19px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--ink-dim);
    text-shadow: 0 2px 8px rgba(0, 0, 0, 0.75);
    transition: color 0.2s;
}

#header-msg.msg-good { color: #7fd6a0; }
#header-msg.msg-alert { color: #f0b45e; }

#ai-indicator {
    position: relative;
    font-size: 11px;
    letter-spacing: 0.04em;
    padding: 4px 9px 4px 18px;
    border-radius: 999px;
    border: 1px solid transparent;
    white-space: nowrap;
    cursor: default;
}

#ai-indicator::before {
    content: '';
    position: absolute;
    left: 7px;
    top: 50%;
    width: 6px;
    height: 6px;
    margin-top: -3px;
    border-radius: 50%;
    background: currentColor;
}

.ai-pending   { color: #e0a952; border-color: rgba(224, 169, 82, 0.35); }
.ai-onnx      { color: #6fd18f; border-color: rgba(111, 209, 143, 0.35); }
.ai-ready     { color: #a58ce0; border-color: rgba(165, 140, 224, 0.35); }
.ai-heuristic { color: #e0a952; border-color: rgba(224, 169, 82, 0.35); }

#ai-indicator-detail {
    position: absolute;
    right: 10px;
    top: calc(var(--header-h) + 6px);
    width: min(300px, 88vw);
    padding: 12px 14px;
    background: #16302a;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--ink-dim);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
    display: none;
    z-index: 320;
}

#ai-indicator-detail p { margin: 0 0 6px; }
#ai-indicator-detail dl { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 2px 10px; }
#ai-indicator-detail dt { color: rgba(242, 236, 223, 0.45); }
#ai-indicator-detail dd { margin: 0; }
/* The badge itself, not the whole header group: hovering the 🤖 / 🏆 / 📜 / ?
   buttons on the way to somewhere else used to pop the model card open. */
#ai-indicator:hover + #ai-indicator-detail,
#ai-indicator-detail.open { display: block; }

#ai-btn,
#log-btn,
#ach-btn,
#rules-btn {
    width: 44px;
    height: 44px;
    min-width: 44px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    border: 1px solid var(--line);
    background: transparent;
    color: var(--ink-dim);
    font-size: 17px;
    cursor: pointer;
}

#ai-btn:hover, #log-btn:hover, #ach-btn:hover, #rules-btn:hover { filter: brightness(1.3); }

/* ── AI evaluation panel ────────────────────────────────────────────────── */
#ai-eval-panel {
    position: fixed;
    right: 12px;
    top: 58px;
    width: min(290px, 90vw);
    max-height: 68dvh;
    overflow-y: auto;
    padding: 11px 13px;
    background: rgba(10, 22, 18, 0.95);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: 0 12px 34px rgba(0, 0, 0, 0.55);
    z-index: 501;
}

#ai-eval-panel .log-title { font-family: var(--display); font-weight: 700;
                            color: var(--gold); margin-bottom: 7px; }
#ai-eval-panel .log-empty { color: rgba(242, 236, 223, 0.4); font-size: 12px; }
#ai-eval-panel .eval-entry { padding: 6px 0; border-top: 1px solid rgba(201, 169, 79, 0.16); }
#ai-eval-panel .eval-entry:first-of-type { border-top: 0; }
#ai-eval-panel .eval-head { font-family: var(--display); font-size: 12.5px;
                            color: var(--ink); margin-bottom: 3px; }
#ai-eval-panel .eval-row { display: flex; align-items: center; gap: 6px;
                           font-size: 11.5px; color: var(--ink-dim); padding: 1px 0; }
#ai-eval-panel .eval-row.picked { color: #7fd6a0; font-weight: 600; }
#ai-eval-panel .eval-label { flex: 0 0 88px; white-space: nowrap; overflow: hidden;
                             text-overflow: ellipsis; }
#ai-eval-panel .eval-bar { flex: 1; height: 6px; border-radius: 3px;
                           background: rgba(255, 255, 255, 0.08); overflow: hidden; }
#ai-eval-panel .eval-bar i { display: block; height: 100%; background: var(--gold); }
#ai-eval-panel .eval-row.picked .eval-bar i { background: #7fd6a0; }
#ai-eval-panel .eval-pct { flex: 0 0 40px; text-align: right;
                           font-variant-numeric: tabular-nums; }

/* ── Move log (Sevens-style toggled panel) ──────────────────────────────── */
#move-log-panel {
    position: fixed;
    right: 12px;
    top: 58px;
    width: min(272px, 88vw);
    max-height: 68dvh;
    overflow-y: auto;
    padding: 11px 13px;
    background: rgba(10, 22, 18, 0.95);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: 0 12px 34px rgba(0, 0, 0, 0.55);
    z-index: 501;
}

#move-log-panel .log-title {
    font-family: var(--display);
    font-weight: 700;
    letter-spacing: 0.05em;
    color: var(--gold);
    margin-bottom: 7px;
}

#move-log-panel .log-entry {
    display: flex;
    align-items: baseline;
    gap: 7px;
    padding: 2px 0;
    font-size: 12.5px;
    color: var(--ink-dim);
}

#move-log-panel .log-entry b { color: var(--ink); font-weight: 600; font-family: var(--display); }
#move-log-panel .log-entry.big { color: #f0b45e; }
#move-log-panel .log-entry.good { color: #7fd6a0; }
#move-log-panel .log-divider { border-top: 1px solid rgba(201, 169, 79, 0.22); margin: 6px 0; }
#move-log-panel .log-empty { color: rgba(242, 236, 223, 0.4); font-size: 12px; }

/* ── Table ──────────────────────────────────────────────────────────────────
   Per card-game-UI-render: cards are position:absolute inside #game, never in a
   flex/grid. Hands sit AROUND the centred board — bottom / left / top / right by
   slot, each opponent showing a real fan of face-down backs, because a text
   panel does not read as a card table. The ONE relaxation is the board's own
   grid, which hosts a play zone per seat. */
#game {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
    touch-action: none;
}

#board {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    padding: 10px;
    border-radius: 18px;
    /* No felt fill: the shared table photo behind it IS the table. The board
       only needs to say where the play area ends, so it is a border and a soft
       inner shadow, nothing more. */
    background: transparent;
    border: 1px solid rgba(201, 169, 79, 0.28);
    box-shadow: inset 0 0 40px rgba(0, 0, 0, 0.30);
    z-index: 1;
    isolation: isolate;
}

/* Cover art printed on the table, 80% transparent. On a ::before rather than a
   background layer so the opacity applies to the ART ONLY — putting it on
   #board would fade every card sitting on it too. */
#board::before {
    content: '';
    position: absolute;
    inset: 0;
    background: url('assets/cover.webp') center/contain no-repeat;
    /* fade the art's own rectangular edge out, or the board shows a hard seam
       where the parchment ends */
    -webkit-mask-image: radial-gradient(ellipse at 50% 50%, #000 52%, transparent 82%);
    mask-image: radial-gradient(ellipse at 50% 50%, #000 52%, transparent 82%);
    opacity: 0.11;
    /* luminosity, not soft-light: it borrows only the art's LIGHTNESS and keeps
       the wood's own hue, so the play area stays a faint watermark inside its
       border instead of becoming a coloured slab. */
    mix-blend-mode: luminosity;
    border-radius: inherit;
    pointer-events: none;
    z-index: 0;
}

#board > * { position: relative; z-index: 1; }

/* Play zones are ZERO-SIZE ANCHORS placed by JS on an ellipse inside the board,
   one per seat, matching the seat ring but at a smaller radius. As grid cells
   they landed in the board's corners — exactly where the diagonal fans reach —
   and the played cards ended up on top of their owner's own hand.

   NO `position` here on purpose. `#board > *` above is an ID selector and beats
   this one, so it is what makes these `relative` — declaring `absolute` here
   only ever LOOKED like it won. That matters when reading sizeBoard(): the
   left/top it computes read like board coordinates but land as offsets from
   flow position. Every zone is a zero-height block, so they all share one flow
   origin and the whole ellipse is simply shifted by the board's padding —
   uniform, which is why it looks right. Anything measuring a played card's
   position must read its rect, not add up these numbers. */
.play-zone {
    width: 0;
    height: 0;
    overflow: visible;
}

#center-area {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    min-height: 0;
    pointer-events: none;
}



/* 10% opacity: the badge still says "passed" but the cards underneath read. */
/* Only while the confirm pill is actually up: that is the moment the table is
   being held still to be READ, and an opaque badge covers the cards it is
   asking you to look at. The rest of the time the badge stays legible. */
body.confirming .pass-mark { opacity: 0.1; }

.pass-mark {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%) rotate(-7deg);
    padding: 5px 18px;
    border-radius: 12px;
    background: rgba(24, 12, 0, 0.62);
    border: 1px solid rgba(240, 180, 94, 0.5);
    font-size: 22px;
    font-weight: 800;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: #f0b45e;
    white-space: nowrap;
    pointer-events: none;
    z-index: 60;
}

/* ── Prompt banner ──────────────────────────────────────────────────────────
   For instructions the player must ACT on — handing a card over during
   taxation. The header's centre message is too easy to miss for something that
   is blocking the game, so this sits on the board where the eye already is.
   pointer-events:none is load-bearing: the cards underneath must stay
   clickable. */
#prompt-banner {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    max-width: min(560px, 88vw);
    padding: 14px 26px;
    border-radius: 12px;
    background: rgba(24, 14, 4, 0.88);
    border: 1px solid var(--gold);
    color: #f6dd97;
    font-family: var(--display);
    font-size: 24px;
    line-height: 1.35;
    text-align: center;
    text-shadow: 0 2px 10px rgba(0, 0, 0, 0.8);
    box-shadow: 0 10px 34px rgba(0, 0, 0, 0.55);
    pointer-events: none;
    z-index: 140;
    animation: prompt-in 220ms ease-out;
}

#prompt-banner.hidden { display: none; }
#prompt-banner .prompt-sub { display: block; margin-top: 5px; font-size: 17px; color: var(--ink-dim); }

@keyframes prompt-in {
    from { opacity: 0; transform: translate(-50%, -50%) scale(0.94); }
    to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

@media (max-height: 460px) {
    #prompt-banner { font-size: 18px; padding: 9px 16px; }
    #prompt-banner .prompt-sub { font-size: 14px; }
}

/* ── Player name tags ───────────────────────────────────────────────────── */
.player-tag {
    font-family: var(--display);

    position: absolute;
    transform: translate(-50%, -50%);
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 18px;
    border-radius: 11px;
    background: rgba(6, 16, 12, 0.72);
    border: 1px solid rgba(255, 255, 255, 0.09);
    font-size: 24px;
    white-space: nowrap;
    pointer-events: none;
    z-index: 80;
    transition: border-color 0.2s, background 0.2s;
}

/* The human's tag rides IN the action bar, where the Clear button used to be,
   so flex places it and the absolute positioning above has to be undone. Height
   matched to .act-btn so the row reads as one strip rather than a pill next to
   some buttons. */
.player-tag.in-bar {
    position: static;
    transform: none;
    min-height: 44px;   /* same as .act-btn, so the row lines up */
    padding: 0 16px;
    font-size: 20px;
    gap: 8px;
}

.player-tag.active { border-color: var(--gold); background: rgba(52, 42, 10, 0.85); }
.player-tag.done { opacity: 0.55; }
.player-tag b { font-weight: 600; }
.player-tag .tag-out { color: #7fd6a0; font-weight: 700; }
.player-tag .tag-score { color: var(--gold); font-weight: 700; }

.tag-rank {
    font-size: 19px;
    font-weight: 700;
    letter-spacing: 0.05em;
    padding: 2px 8px;
    border-radius: 6px;
    background: rgba(201, 169, 79, 0.2);
    color: var(--gold);
}

.tag-rank.r0 { background: rgba(232, 196, 90, 0.3); color: #f6dd97; }
.tag-rank.r3 { background: rgba(140, 150, 160, 0.22); color: #b9c3cc; }

/* ── Live standings (Rainbow-style) ─────────────────────────────────────── */
#round-counter {
    position: fixed;
    top: 60px;
    right: 12px;
    width: 156px;
    text-align: center;
    font-family: var(--display);
    font-size: 15px;
    letter-spacing: 0.04em;
    color: var(--gold);
    text-shadow: 0 2px 6px rgba(0, 0, 0, 0.7);
    z-index: 200;
    pointer-events: none;
    user-select: none;
}

#ranking-table {
    position: fixed;
    top: 84px;
    right: 12px;
    width: 156px;
    z-index: 200;
    pointer-events: none;
    user-select: none;
}


#ranking-table .rank-row {
    position: absolute;
    left: 0;
    right: 0;
    height: 30px;
    display: flex;
    align-items: center;
    gap: 5px;
    padding: 0 10px;
    border-radius: 8px;
    background: rgba(6, 16, 12, 0.55);
    border: 1px solid rgba(201, 169, 79, 0.18);
    color: rgba(242, 236, 223, 0.85);
    backdrop-filter: blur(4px);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
    /* rows slide to their new position as scores change */
    transition: top 380ms cubic-bezier(.45, .1, .3, 1), border-color 0.2s, background 0.2s;
}

#ranking-table .rank-row.active-player {
    border-color: rgba(201, 169, 79, 0.85);
    background: rgba(52, 42, 10, 0.8);
}

#ranking-table .rank-place { font-size: 10.5px; font-weight: 600; min-width: 14px;
                             color: rgba(201, 169, 79, 0.75); }
#ranking-table .rank-name {
    font-family: var(--display);
 flex: 1; font-size: 12.5px; font-weight: 600; overflow: hidden;
                            text-overflow: ellipsis; white-space: nowrap; }
#ranking-table .rank-score { font-size: 13px; font-weight: 700; color: var(--gold); }

/* ── Cards ──────────────────────────────────────────────────────────────── */
/* The face is the scanned AMIGO card, set per element as --card-face by
   cardEl() in game.js. The gradient is the layer UNDERNEATH: it is never seen
   while the art loads clean, and leaves something card-shaped if a face 404s.
   background-size:100% 100% (not `cover`) because the art is already cut to
   --card-ratio, so it fills the box exactly with nothing cropped away. */
.card {
    position: absolute;
    width: var(--card-w);
    height: calc(var(--card-w) * var(--card-ratio));
    /* Matches the radius baked into the art: 6% of the card's WIDTH, which the
       second value restates against the height (6 / 1.613) so the corner stays
       circular instead of going oval. Percentages, not px, because .small and
       .played re-scale the box and a fixed radius would then be wrong on them. */
    border-radius: 6% / 3.72%;
    background:
        var(--card-face, none) center / 100% 100% no-repeat,
        linear-gradient(160deg, var(--c-lo), var(--c-hi));
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
    overflow: hidden;
}

.card.back {
    /* One shared back for every face-down card, so this one is in CSS rather
       than set per element. */
    --card-face: url('assets/cards/back.webp');
    --c-lo: #58317f;
    --c-hi: #24123a;
}

/* Opponent fans are drawn smaller: six of them at full size overlap each other
   and the board. Only the BACKS shrink — played cards stay readable. */
.card.small {
    width: calc(var(--card-w) * var(--opp-scale));
    height: calc(var(--card-w) * var(--opp-scale) * var(--card-ratio));
}

/* Cards on the table are what the whole turn is about — what has to be beaten —
   so they are drawn LARGE: above --card-w, and only a little under the human's
   own hand. Still clearly under it, so the hand stays the thing you act on. */
.card.played {
    width: calc(var(--card-w) * var(--play-scale));
    height: calc(var(--card-w) * var(--play-scale) * var(--card-ratio));
}

/* One played set. Zero-size and pinned to its zone's origin so the cards inside
   keep the offsets JS gave them; it exists to be hovered and scaled as a unit.
   transform-origin is set per group in renderPlayZones() to the set's middle. */
.play-group {
    position: absolute;
    left: 0;
    top: 0;
    width: 0;
    height: 0;
    /* scale(1) not `none`: a group that already has a transform keeps the same
       stacking context whether hovered or not, so cards do not re-layer on the
       way in. Also gives the transition something to interpolate FROM. */
    transform: scale(1);
    transition: transform 170ms cubic-bezier(0.34, 1.4, 0.64, 1);
}

/* Set for a beat around every rebuild by settlePlayZoom(): a set the pointer is
   already resting on snaps to its zoomed size instead of replaying the inflate
   from scratch on every turn. */
body.zoom-settling .play-group { transition: none; }

/* Invisible, unscaled stand-in for the set's rest footprint — sized in JS. It is
   what the pointer actually hits, because a target that grows with the cards
   slides out from under a still cursor and the hover flickers. */
.play-hit {
    position: absolute;
    background: transparent;
}

/* The cards never take the pointer, so they cannot steal the hover back off the
   hit box as they swell across it. */
.play-group .card.played { pointer-events: none; }

/* Point at a set to read it: it swells by half again, over everything else on
   the felt. The board does not clip it — .play-zone is overflow:visible.
   !important because renderPlayZones() sets each group's z-index inline. */
.play-hit:hover + .play-group {
    transform: scale(1.5);
    z-index: 600 !important;
}

/* The zone has to come up with it. `#board > *` gives every zone z-index:1,
   which makes each one a stacking context — without this the swollen set is
   trapped in its own zone's layer and slides UNDER a neighbouring seat's cards.
   Selector carries #board so it outranks that rule. :hover matches the zone
   because the pointer is over a descendant of it. */
#board > .play-zone:hover { z-index: 600; }

.card.jester { --c-lo: #7d57b0; --c-hi: #4a2d73; }

/* The human's own cards, in the fan at the bottom. Drawn bigger than every
   other card on the table: this is the only hand whose faces have to be read
   and clicked, and the fan overlaps them so only a sliver of each one shows. */
.hand-card {
    width: calc(var(--card-w) * var(--hand-scale));
    height: calc(var(--card-w) * var(--hand-scale) * var(--card-ratio));
    cursor: pointer;
    transition: filter 0.13s, transform 0.13s;
}
.hand-card:hover:not(.disabled) { filter: brightness(1.2); z-index: 400 !important; }
.hand-card.disabled { opacity: 0.32; cursor: default; }
.hand-card.selected { box-shadow: 0 0 0 2px var(--gold), 0 8px 16px rgba(0, 0, 0, 0.55); }

/* ── Action bar ─────────────────────────────────────────────────────────── */
/* Centred under the human's fan and positioned in the same JS pass as the
   cards — a CSS-only bottom offset drifts into the hand as the card size
   changes. */
#actionbar {
    position: absolute;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: 8px;
    z-index: 120;
}

.act-btn {
    font-family: var(--display);
    letter-spacing: 0.04em;

    min-width: 104px;
    min-height: 44px;
    padding: 0 16px;
    border-radius: 8px;
    border: 1px solid var(--line);
    background: rgba(10, 24, 18, 0.85);
    color: var(--ink);
    font-size: 15px;
    font-weight: 600;
    cursor: pointer;
    transition: filter 0.15s, opacity 0.15s;
}

.act-btn:hover:not(:disabled) { filter: brightness(1.25); }
.act-btn:disabled { opacity: 0.35; cursor: default; }

.act-btn.primary {
    background: linear-gradient(180deg, #3f8f60, #2c6b46);
    border-color: #4fae76;
    color: #fff;
}

/* ── Overlays ───────────────────────────────────────────────────────────── */
.overlay {
    position: fixed;
    inset: 0;
    display: grid;
    place-items: center;
    background: rgba(14, 8, 4, 0.82);
    z-index: 400;
    padding: 12px;
    overflow: auto;
}

.overlay.hidden { display: none; }

.panel {
    width: min(460px, 94vw);
    max-height: 92dvh;
    overflow: auto;
    padding: 22px 24px;
    /* The radial pass is the cover's own vignette: its parchment is lighter in
       the middle and warms toward the edges. Without it a flat fill reads as
       plastic rather than paper. */
    background:
        radial-gradient(120% 85% at 50% 8%, rgba(255, 219, 190, 0.40), rgba(255, 219, 190, 0) 62%),
        linear-gradient(170deg, var(--parch-hi), var(--parch-lo));
    border: 1px solid rgba(74, 32, 12, 0.55);
    border-radius: 14px;
    box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6);
    text-align: center;

    /* Rebind the theme on the panel instead of restating a colour on every
       child: custom properties inherit, so everything inside — the standings,
       the lobby fields, the draw captions — flips to parchment ink in one
       place. Gold is invisible on orange; the cover's terracotta stands in. */
    --ink: var(--parch-ink);
    --ink-dim: var(--parch-ink-dim);
    --line: var(--parch-line);
    --gold: var(--parch-accent);
    color: var(--ink);
}

.panel h2 {
    margin: 0 0 4px;
    font-family: var(--display-old);
    font-size: 30px;
    font-weight: 700;
    letter-spacing: 0.03em;
}

.panel p { margin: 0 0 15px; font-size: 16.5px; color: var(--ink-dim); line-height: 1.55; }

.panel .primary-btn {
    font-family: var(--display);
    letter-spacing: 0.04em;

    width: 100%;
    min-height: 48px;
    margin-top: 8px;
    border-radius: 9px;
    /* The pale mint outline the button carried on the dark panel washes out on
       parchment; the deep edge is what separates it from the paper now. Green
       on orange is the cover's own pairing — its title is teal on this ground. */
    border: 1px solid #1e5436;
    background: linear-gradient(180deg, #3f8f60, #2c6b46);
    color: #fff;
    font-size: 19px;
    font-weight: 600;
    cursor: pointer;
}

.panel .primary-btn.alt {
    background: rgba(74, 32, 12, 0.10);
    border-color: rgba(74, 32, 12, 0.42);
    color: var(--ink);
}

.panel .primary-btn:hover { filter: brightness(1.2); }

.standings { width: 100%; border-collapse: collapse; margin: 6px 0 10px; font-size: 16.5px; }
.standings th {
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: rgba(64, 33, 15, 0.52);
    font-weight: 600;
    padding: 4px 6px;
    text-align: left;
}
.standings td { padding: 6px; border-top: 1px solid rgba(74, 32, 12, 0.18); text-align: left; }
.standings td.num { text-align: right; font-variant-numeric: tabular-nums; }
.standings tr.me td { color: var(--gold); font-weight: 600; }

/* New order: fixed columns, so name and rank each start on their own line down
   the table. table-layout:fixed takes the widths from the header row, which is
   why the <th>s carry the same classes as the cells. */
#ranks-table, #draw-table { table-layout: fixed; }
#ranks-table .rk-pos, #draw-table .rk-pos { width: 30px; }
#ranks-table .rk-name, #draw-table .rk-name {
    width: 40%;
    padding-right: 8px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
#ranks-table .rk-rank, #draw-table .rk-rank {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
#ranks-table .rk-move { width: 46px; text-align: right; }
#ranks-table .rk-move .move { margin-left: 0; }

/* Movement against last round, shown behind the rank name. */
.standings .move {
    margin-left: 7px;
    font-size: 12px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Darkened for parchment: the pastel green/red that read on the old dark panel
   disappear on a light one. */
.standings .move.up { color: #1c6b41; }
.standings .move.down { color: #a4301c; }
.standings .move.flat { color: rgba(64, 33, 15, 0.35); }

/* ── Tutorial coach ─────────────────────────────────────────────────────── */
.tut-box {
    position: fixed;
    left: 50%;
    bottom: 14px;
    transform: translateX(-50%);
    width: min(520px, 94vw);
    max-height: 46dvh;
    overflow: auto;
    padding: 14px 16px;
    background: linear-gradient(170deg, #24422f, #162c22);
    border: 1px solid var(--gold);
    border-radius: 12px;
    box-shadow: 0 14px 40px rgba(0, 0, 0, 0.6);
    z-index: 450;
}

.tut-box p { margin: 0 0 10px; font-size: 14px; line-height: 1.5; color: var(--ink); }

.tut-ok {
    min-height: 44px;
    width: 100%;
    border-radius: 8px;
    border: 1px solid #4fae76;
    background: linear-gradient(180deg, #3f8f60, #2c6b46);
    color: #fff;
    font-size: 15px;
    font-weight: 600;
    cursor: pointer;
}

.tut-spot {
    outline: 2px solid var(--gold);
    outline-offset: 3px;
    border-radius: 10px;
}

/* short screens: the coach must not bury the hand it is talking about */
@media (max-height: 460px) {
    .tut-box { bottom: 6px; max-height: 60dvh; padding: 10px 12px; }
    .tut-box p { font-size: 12.5px; margin-bottom: 7px; }
}

/* ── Multiplayer lobby ──────────────────────────────────────────────────── */
.mp-field { display: flex; flex-direction: column; gap: 4px; margin: 10px 0; text-align: left; }
.mp-field span { font-size: 12px; color: var(--ink-dim); }

.mp-field input {
    height: 44px;
    padding: 0 10px;
    border-radius: 8px;
    border: 1px solid rgba(74, 32, 12, 0.42);
    background: rgba(255, 250, 244, 0.55);
    color: var(--ink);
    font-size: 15px;
}

#mp-code { letter-spacing: 0.22em; text-transform: uppercase; font-weight: 700; }

.mp-seats-note { font-size: 14px; color: var(--gold); margin: 0 0 6px !important; }

.mp-or {
    margin: 14px 0 6px;
    font-size: 12px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: rgba(64, 33, 15, 0.45);
}

.mp-code {
    margin: 10px 0 14px;
    padding: 12px;
    border-radius: 10px;
    background: rgba(255, 250, 244, 0.5);
    border: 1px solid rgba(74, 32, 12, 0.42);
    font-size: 32px;
    font-weight: 700;
    letter-spacing: 0.3em;
    text-indent: 0.3em;
    color: var(--gold);
}

.mp-err { min-height: 18px; font-size: 13px; color: #8c2f0b; margin: 8px 0 !important; }

/* "The Dalmutis are collecting their taxes" — on the felt, centred, never
   interactive: the human has nothing to do while it is up. */
#tax-watch {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    max-width: min(92%, 340px);
    padding: 10px 18px;
    border-radius: 999px;
    background: rgba(14, 8, 4, 0.82);
    border: 1px solid var(--line);
    color: var(--ink);
    font-size: 17px;
    line-height: 1.35;
    text-align: center;
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.5);
    pointer-events: none;
    z-index: 310;
}

.tax-label { margin: 12px 0 4px; font-size: 15px; }
.tax-label.taken { color: #a4301c; }
.tax-label.got { color: #1c6b41; }
.tax-row { display: flex; justify-content: center; gap: 10px; flex-wrap: wrap; }

/* a COLUMN: it stacks label / row / label / row. As a flex row the second
   label lands beside the first row of cards. */
#tax-cards {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    margin: 6px 0 16px;
}

/* ── Rank draw ──────────────────────────────────────────────────────────── */
/* A fixed 3-wide grid: six players wrap to two rows of three rather than one
   long line that squeezes the cards, and four players read as 3 + 1. */
#draw-cards {
    display: grid;
    grid-template-columns: repeat(3, auto);
    justify-content: center;
    gap: 12px 14px;
    margin: 8px 0 16px;
}

.draw-cell { display: flex; flex-direction: column; align-items: center; gap: 5px; }
/* DIRECT child only. The card backs used to carry a <span> of their own that an
   unscoped rule here shrank to a 15px speck; the back is pure art now, but the
   scoping stays — this rule is about the player's NAME under the card. */
.draw-cell > span { font-size: 15px; color: var(--ink-dim); font-family: var(--display); }

.draw-card {
    position: relative;
    width: var(--card-w);
    height: calc(var(--card-w) * var(--card-ratio));
}

.draw-card .card { position: relative; left: 0; top: 0; }

@keyframes draw-flip {
    from { transform: rotateY(90deg) scale(0.9); opacity: 0; }
    to   { transform: rotateY(0) scale(1); opacity: 1; }
}

.draw-reveal { animation: draw-flip 260ms ease-out; }

/* ── Start screen ───────────────────────────────────────────────────────── */
#start-screen {
    position: fixed;
    inset: 0;
    display: grid;
    place-items: center;
    background:
        radial-gradient(ellipse at 50% 38%, rgba(112, 74, 168, 0.28), transparent 66%),
        linear-gradient(rgba(16, 12, 30, 0.68), rgba(11, 9, 22, 0.86)),
        url('../table.avif') center/cover no-repeat fixed;
    z-index: 500;
    padding: 12px;
}

.start-card {
    position: relative;
    width: 600px;
    max-width: 92vw;
    padding: 34px 30px 30px;
    /* Same parchment as the dialogs — the card the game opens with is the first
       thing that should look like the cover. */
    background:
        radial-gradient(120% 85% at 50% 6%, rgba(255, 219, 190, 0.40), rgba(255, 219, 190, 0) 62%),
        linear-gradient(170deg, var(--parch-hi), var(--parch-lo));
    border: 1px solid rgba(74, 32, 12, 0.55);
    border-radius: 16px;

    --ink: var(--parch-ink);
    --ink-dim: var(--parch-ink-dim);
    --line: var(--parch-line);
    --gold: var(--parch-accent);
    color: var(--ink);
    box-shadow: 0 22px 60px rgba(0, 0, 0, 0.6);
    /* TOP centre, not centre centre. When the card is taller than the viewport
       the grid row grows with it and overflows downward only — scaling about the
       middle then leaves the bottom of the card (the Play button) off-screen.
       Scaling about the top edge keeps the whole card inside. */
    transform-origin: top center;
    text-align: center;
}

.start-title {
    margin: 0;
    font-family: var(--display-old);
    font-size: 54px;
    font-weight: 700;
    letter-spacing: 0.06em;
    color: var(--gold);
}

.start-sub {
    margin: 6px 0 22px;
    font-size: 17px;
    letter-spacing: 0.3px;
    color: var(--ink-dim);
    white-space: nowrap;
}

/* One control height for the whole card: the selects and the buttons are the
   same object to the eye, so they are the same size. The label metrics are
   pinned too, because the right column offsets itself by exactly one label to
   put ► PLAY level with the first dropdown rather than with its caption. */
.start-cols {
    display: flex;
    gap: 22px;
    text-align: left;
    --ctl-h: 44px;
    --field-label-h: 20px;
    --field-gap: 6px;
}
.start-col-left { flex: 0 0 48%; display: flex; flex-direction: column; gap: 14px; }
.start-col-right {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 9px;
    padding-top: calc(var(--field-label-h) + var(--field-gap));
}

.start-field { display: flex; flex-direction: column; gap: var(--field-gap); font-size: 16px; color: var(--ink-dim); }
.start-field > span { line-height: var(--field-label-h); }

.start-field select {
    width: 100%;
    height: var(--ctl-h);
    padding: 0 10px;
    border-radius: 8px;
    border: 1px solid rgba(74, 32, 12, 0.42);
    background: rgba(255, 250, 244, 0.55);
    color: var(--ink);
    font-size: 16px;
}

.start-check {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 38px;
    font-size: 16px;
    color: var(--ink-dim);
    cursor: pointer;
}

.start-check.disabled { opacity: 0.45; cursor: default; }
.start-check input { width: 21px; height: 21px; accent-color: var(--gold); }

.start-play {
    font-family: var(--display);
    letter-spacing: 0.05em;

    height: var(--ctl-h, 44px);
    border-radius: 9px;
    border: 1px solid #1e5436;
    background: linear-gradient(180deg, #3f8f60, #2c6b46);
    color: #fff;
    font-size: 19px;
    font-weight: 600;
    cursor: pointer;
}

.start-play.start-secondary {
    background: rgba(74, 32, 12, 0.10);
    border-color: rgba(74, 32, 12, 0.42);
    color: var(--ink);
    font-size: 17px;
}

.start-play:hover:not(:disabled) { filter: brightness(1.2); }
.start-play:disabled { opacity: 0.4; cursor: default; }

.start-corner { position: absolute; right: 16px; top: 14px; }

.start-lang-btn {
    width: 44px;
    height: 32px;
    display: grid;
    place-items: center;
    border-radius: 6px;
    border: 1px solid rgba(74, 32, 12, 0.42);
    background: rgba(255, 250, 244, 0.5);
    cursor: pointer;
}

.start-lang-menu {
    position: absolute;
    right: 0;
    top: 40px;
    min-width: 156px;
    padding: 5px;
    background: #f3ddc9;
    border: 1px solid rgba(74, 32, 12, 0.42);
    border-radius: 9px;
    box-shadow: 0 12px 30px rgba(0, 0, 0, 0.55);
}

.start-lang-menu button {
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    min-height: 40px;
    padding: 0 9px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--ink);
    font-size: 16px;
    cursor: pointer;
    text-align: left;
}

.start-lang-menu button:hover { background: rgba(255, 255, 255, 0.07); }
.start-lang-menu button.active { color: var(--gold); }

/* ── Responsive ─────────────────────────────────────────────────────────── */


/* narrow phones: stack the start-screen columns */
@media (max-width: 560px), (max-height: 520px) {
    .start-card { width: 400px; padding: 22px 20px 52px; }
    .start-cols { flex-direction: column; gap: 14px; }
    .start-col-left { flex: 1 1 auto; }
    /* stacked: there is no dropdown beside the buttons to line up with */
    .start-col-right { padding-top: 0; }
    .start-title { font-size: 38px; }
    .start-sub { white-space: normal; }
}

/* roomy screens: bigger cards, wider table */
@media (min-width: 900px) and (min-height: 620px) {
    :root { --card-w: calc(clamp(58px, 6.2vw, 88px) * var(--card-scale)); }
    #ranking-table { width: 172px; }
}

/* Short landscape phones: four fans, a board and the buttons have to share
   360px of height, so the cards shrink against the HEIGHT, not the width. */
@media (max-height: 460px) {
    :root { --card-w: calc(clamp(30px, 5.6vh, 44px) * var(--card-scale)); }
    #log-line { display: none; }
    /* the standings would sit on top of the right seat's fan at this height —
       every score is already on that player's own tag */
    #ranking-table, #round-counter { display: none; }
    .act-btn { min-width: 84px; padding: 0 10px; font-size: 14px; }
    .player-tag { font-size: 17px; padding: 3px 10px; gap: 6px; }
    .tag-rank { font-size: 13px; padding: 1px 5px; }
}

@media (prefers-reduced-motion: reduce) {
    * { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}

/* ── Confirm-turn button ────────────────────────────────────────────────────
   Opt-in ("Confirm turns"): the finished trick stays on the felt until this is
   clicked. It sits in the middle of the board, above the play zones, so the
   cards being confirmed are all around it. */
#confirm-turn {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    /* A single low row, not a stacked card: the four play zones sit close to the
       middle, and a tall box lands on the cards it is asking you to look at. */
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 12px;
    background: rgba(10, 22, 18, 0.9);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
    z-index: 320;
}

#confirm-turn .confirm-caption {
    font-family: var(--display);
    font-size: 15px;
    color: var(--ink-dim);
    white-space: nowrap;
}

#confirm-turn .confirm-ok {
    min-width: 88px;
    min-height: 44px;
    padding: 0 20px;
    font-family: var(--display);
    font-size: 18px;
    letter-spacing: 0.06em;
    color: #12251c;
    background: var(--gold);
    border: 0;
    border-radius: var(--radius);
    cursor: pointer;
}

#confirm-turn .confirm-ok:hover { filter: brightness(1.12); }

/* ── Achievements ───────────────────────────────────────────────────────────
   Icons are inline SVG (Dalmuti ships no image assets), so they are sized here
   rather than by an <img> width. */
.dm-ach-art {
    width: 46px;
    height: 46px;
    flex: 0 0 46px;
    display: grid;
    place-items: center;
}

.dm-ach-art svg { width: 100%; height: 100%; display: block; }

#dm-ach-toast-stack {
    position: fixed;
    right: 14px;
    bottom: 14px;
    display: flex;
    flex-direction: column-reverse;
    gap: 8px;
    z-index: 900;
    pointer-events: none;
}

.dm-ach-toast {
    display: flex;
    align-items: center;
    gap: 11px;
    max-width: min(330px, 92vw);
    padding: 10px 14px 10px 11px;
    background: rgba(10, 22, 18, 0.96);
    border: 1px solid var(--gold);
    border-radius: var(--radius);
    box-shadow: 0 12px 34px rgba(0, 0, 0, 0.55);
    opacity: 0;
    transform: translateY(14px);
    transition: opacity 260ms ease, transform 260ms ease;
}

.dm-ach-toast.show { opacity: 1; transform: translateY(0); }

.dm-ach-toast-title {
    font-family: var(--display);
    font-size: 16px;
    color: var(--gold);
}

.dm-ach-toast-desc { font-size: 12.5px; color: var(--ink-dim); }

.dm-ach-overlay {
    position: fixed;
    inset: 0;
    display: grid;
    place-items: center;
    padding: 16px;
    background: rgba(12, 7, 3, 0.72);
    z-index: 880;
}

/* Same parchment as .panel — the gallery is a dialog too, and a dark one beside
   the orange round-over box would read as a different game. */
.dm-ach-panel {
    width: min(460px, 94vw);
    max-height: 84dvh;
    overflow-y: auto;
    padding: 18px 20px;
    background:
        radial-gradient(120% 85% at 50% 8%, rgba(255, 219, 190, 0.40), rgba(255, 219, 190, 0) 62%),
        linear-gradient(170deg, var(--parch-hi), var(--parch-lo));
    border: 1px solid rgba(74, 32, 12, 0.55);
    border-radius: var(--radius);
    box-shadow: 0 18px 46px rgba(0, 0, 0, 0.6);

    --ink: var(--parch-ink);
    --ink-dim: var(--parch-ink-dim);
    --line: var(--parch-line);
    --gold: var(--parch-accent);
    color: var(--ink);
}

.dm-ach-panel-title {
    font-family: var(--display-old);
    font-size: 24px;
    color: var(--gold);
    text-align: center;
    margin-bottom: 14px;
}

.dm-ach-grid { display: flex; flex-direction: column; gap: 10px; }

.dm-ach-item {
    display: grid;
    grid-template-columns: 46px 1fr;
    grid-template-rows: auto auto;
    column-gap: 12px;
    align-items: center;
    padding: 10px 12px;
    border: 1px solid rgba(74, 32, 12, 0.28);
    border-radius: var(--radius);
    background: rgba(255, 246, 236, 0.34);
}

.dm-ach-item .dm-ach-art { grid-row: 1 / span 2; }

.dm-ach-name {
    font-family: var(--display);
    font-size: 17px;
    color: var(--gold);
}

.dm-ach-desc { font-size: 12.5px; color: var(--ink-dim); }

/* Locked: the icon greys out and the title is withheld, but the description
   stays — it is the only hint on how to earn the thing. */
.dm-ach-item.locked { opacity: 0.6; }
.dm-ach-item.locked .dm-ach-art svg { filter: grayscale(1) brightness(0.65); }
.dm-ach-item.locked .dm-ach-name { color: var(--ink-dim); }

@media (max-height: 460px) {
    #confirm-turn { padding: 8px 14px; gap: 5px; }
    #confirm-turn .confirm-caption { font-size: 13px; }
}
