/*
 * Stepping Stones — the game's own skin. Everything else (header, start screen, AI badge,
 * tool panels, dialog shell, fonts, the table felt) is inherited from ../core/deckades.css
 * and is deliberately NOT restated here.
 *
 * ── Where the colours come from ──────────────────────────────────────────────
 * The art is Michelle Campbell's mosaic of a Japanese garden: grey stepping stones over a
 * black pond, a red bridge, pink blossom, orange koi, moss-green trees. Every suit already
 * owns one of those hues — cherry pink, koi orange, lantern grey, bridge red — so the ONE
 * accent had to come from the part of the picture that is not a suit: the trees.
 *
 *   --dk-accent  #7fa36b  the tree canopy, sampled at #95a088 / #7a826d off cover.webp and
 *                         lifted + saturated for a 12px label on a near-black panel. Reads
 *                         as "lit" without ever reading as "that suit". Nearest neighbour
 *                         on the portal is Zauberberg's #5c9a48 — a leaf green; this is a
 *                         greyer sage, and the two are never on one screen.
 *   --dk-tint    pond     the black water under the stones — a cool blue-black wash over
 *                         the shared felt. The environment, not the accent's hue.
 *   suit inks             the printed suits' own colours, used ONLY for suit labels (the
 *                         trump badge, the chips, the log). Never for chrome.
 */

:root {
    --dk-accent:      #7fa36b;
    --dk-accent-soft: rgba(127, 163, 107, 0.35);
    --dk-tint:        rgba(18, 40, 58, 0.34);

    /* ── The chrome, re-tinted (the documented palette in deckades.css, by token only) ──
       The house chrome is violet; this game's world is the POND — black water, grey stone,
       moss at the edge — and the violet card and header sat on the cover like a sticker
       (user, 2026-09-19: "change the colors of the start menu and the header, so they
       better match the game's colors"). Every value below is the pond ink (#121c22 /
       #0a1015) or the moss accent at an alpha; the greys are the stones' own (#bcc4c6).
       Nothing here is a suit colour — the suits stay the only pink, orange and red on the
       table. Same shape as Schadenfreude's re-tint (games/Schadenfreude/style.css). */
    --dk-header-bg:   linear-gradient(180deg,
                          rgba(6, 12, 16, 0.92),
                          rgba(6, 12, 16, 0.65) 80%,
                          rgba(6, 12, 16, 0));
    --dk-header-line: rgba(127, 163, 107, 0.20);
    --dk-chrome-plate:      rgba(10, 18, 22, 0.92);
    --dk-chrome-plate-line: rgba(127, 163, 107, 0.30);
    --dk-logo:        rgba(200, 212, 204, 0.45);
    --dk-logo-hover:  rgba(230, 238, 230, 0.92);
    --dk-spade:       rgba(127, 163, 107, 0.62);
    --dk-spade-hover: #a3c48d;

    --dk-panel:        rgba(10, 18, 22, 0.97);
    --dk-panel-line:   rgba(127, 163, 107, 0.30);
    --dk-panel-text:   rgba(230, 235, 228, 0.92);
    --dk-panel-dim:    rgba(230, 235, 228, 0.55);
    --dk-panel-shadow: 0 10px 34px rgba(0, 0, 0, 0.66);
    --dk-dialog-top:    #16242a;
    --dk-dialog-bottom: #0b1418;
    --dk-dialog-line:   rgba(127, 163, 107, 0.45);
    --dk-scrim:         rgba(5, 9, 12, 0.86);
    --dk-field-bg:      #121c22;
    --dk-field-line:    #33474f;

    --dk-tool-ring:       rgba(127, 163, 107, 0.45);
    --dk-tool-ink:        rgba(190, 208, 182, 0.85);
    --dk-tool-wash:       rgba(127, 163, 107, 0.20);
    --dk-tool-ink-hover:  #e6efe0;
    --dk-tool-ring-hover: #7fa36b;
    --dk-popup-line:      rgba(127, 163, 107, 0.50);
    --dk-scroll-thumb:    rgba(127, 163, 107, 0.34);
    --dk-popup-close:     rgba(170, 196, 160, 0.85);
    --dk-log-big:         #a8d68c;                /* the log's round heading: lit moss, not the platform amber beside the koi */

    /* The AI badge. "Loaded" is a STONE (the slab grey, lamp dark); "the net is driving" is
       the same moss as the accent, lit a shade — so the badge answers in the game's two
       materials rather than the platform's lilac and mint. Heuristic amber is untouched. */
    --dk-ai-bg:           rgba(8, 16, 20, 0.65);
    --dk-ai-ink:          rgba(200, 212, 204, 0.70);
    --dk-ai-ready-ink:    rgba(188, 196, 198, 0.95);
    --dk-ai-ready-ring:   rgba(188, 196, 198, 0.40);
    --dk-ai-ready-dot:    rgba(188, 196, 198, 0.95);
    --dk-ai-ready-glow:   0 0 8px rgba(188, 196, 198, 0.55);
    --dk-ai-onnx-ink:     rgba(168, 214, 140, 0.95);
    --dk-ai-onnx-ring:    rgba(168, 214, 140, 0.45);
    --dk-ai-onnx-dot:     rgba(168, 214, 140, 0.95);
    --dk-ai-onnx-glow:    0 0 8px rgba(168, 214, 140, 0.70);

    /* The start card: the pond, with the moss edge the shared sheet already draws from
       --dk-accent. The inks are the game's own stone-white and its greened dim (6.6:1 on
       the card), never the lilacs the default card was inked for. */
    --dk-start-surface: rgba(12, 20, 24, 0.96);
    --dk-start-ink:      #e6ebe4;
    --dk-start-ink-dim:  #97a69a;
    --dk-start-ink-soft: #cdd8cc;
    --dk-start-ink-fade: #7f8f82;
    --dk-start-secondary-edge: rgba(127, 163, 107, 0.55);
    --dk-start-field:      #121c22;
    --dk-start-field-edge: #33474f;
    --dk-start-field-ink:  #e6ebe4;

    /* Glass button 2 (../core/glassButton.css): the pane is tinted moss, its ink stone-white,
       its shadow pool the pond's night. The filled primary pane (Play again) is MEASURED on
       sampled pixels under this ink (tools/contrast.mjs): with the accent itself (127 163 107)
       it landed 5.9:1 at rest but 4.53:1 lifted — on the line — so the tint is the same moss a
       shade deeper: 6.6:1 at rest, 5.2:1 lifted; the plain pane 11.9:1 / 9.8:1.
       (A Shiny Button pass was tried on 2026-09-17 and reverted the next day: "use the normal
       glass buttons, not the ones with the little dots".) */
    --gb-tint:  104 138 86;
    --gb-ink:   #f4f7ee;
    --gb-shade: 8 18 28;

    /* ── This game's own vocabulary. Namespaced; never a new --dk-* token. ── */
    --ss-ink:        #e6ebe4;                      /* body text: stone-white, greened */
    --ss-dim:        #97a69a;                      /* secondary text, tinted from the accent */
    --ss-stone:      rgba(14, 22, 26, 0.80);       /* the tablet and the plates */
    --ss-stone-hi:   rgba(24, 36, 42, 0.92);
    --ss-line:       rgba(127, 163, 107, 0.22);
    --ss-groove:     rgba(0, 0, 0, 0.35);          /* the cut lines in the tablet */
    --ss-cherry:     #d27a9a;
    --ss-koi:        #e08a2c;
    --ss-lantern:    #a9b4bd;
    --ss-bridge:     #c4553a;
    --ss-danger:     #d9836d;                      /* the refusal, and a lost column */

    /* The scoresheet: a slab of the cover's grey stepping stones, written on in pond ink. */
    --ss-slab:       #bcc4c6;                      /* the stones' tesserae */
    --ss-slab-hi:    #cfd6d7;                      /* a lit tile */
    --ss-slab-mine:  #c6cecf;                      /* the human's own row */
    --ss-pond:       #121c22;                      /* the pond: ink and rules */
    --ss-pond-dim:   #3f4f57;
    --ss-rule:       rgba(18, 28, 34, 0.55);
    --ss-koi-ink:    #a0500c;                      /* koi orange, deepened to read on stone */
    --ss-cherry-ink: #9c2f58;                      /* cherry, deepened — a loss */
    --ss-moss-ink:   #45662f;
    --ss-moss-wash:  rgba(127, 163, 107, 0.42);
    --ss-moss-wash-hi: rgba(127, 163, 107, 0.62);

    /* The trump's pool of light on the felt, one per suit, at watermark strength. */
    --ss-cherry-pool:  rgba(210, 122, 154, 0.17);
    --ss-koi-pool:     rgba(224, 138, 44, 0.17);
    --ss-lantern-pool: rgba(169, 180, 189, 0.15);
    --ss-bridge-pool:  rgba(196, 85, 58, 0.17);

    /* Card geometry is solved in JS and written back here, so one variable resizes every
       card on the table. The printed cards are 500×700 — a 5:7 box. */
    --card-w:  80px;
    --card-h:  112px;
    --card-scale: 1;
    --opp-scale: 0.62;                             /* face-down opponent backs: never read */
    --play-scale: 1;                               /* cards in the trick: as big as the hand's (user, 2026-09-18) */
    --prop-w:  64px;                               /* the reminder cards in the middle */
}

html { background: #0a1015; }
html, body { height: 100%; }
body {
    margin: 0;
    color: var(--ss-ink);
    font-family: var(--dk-font-ui);
    overflow: hidden;
    touch-action: none;
    -webkit-user-select: none; user-select: none;
}
::selection { background: var(--dk-accent-soft); color: var(--ss-ink); }

/* ── The play surface: the SHARED FELT, with the garden baked onto it ──────────
   The mosaic is blended soft-light into the table photo, so the stones and the pond come
   through the felt's grain as a pattern in the cloth rather than a picture behind the
   cards. The wash is a cool near-black — the pond's own hue — and heavy enough that a hand
   of printed cards is still the brightest thing on screen. */
#game {
    position: absolute; inset: 0;
    overflow: hidden;
    touch-action: none;
    background:
        linear-gradient(rgba(8, 12, 14, 0.70), rgba(6, 9, 12, 0.84)),
        url('assets/cover.webp') center 30% / cover no-repeat fixed,
        var(--dk-table);
    background-blend-mode: normal, soft-light, normal;
}

/* Behind the start screen the garden is shown whole and brighter — the one place a cover is
   a cover. */
#start-screen {
    background:
        linear-gradient(rgba(6, 9, 12, 0.66), rgba(6, 9, 12, 0.84)),
        url('assets/cover.webp') center 30% / cover no-repeat fixed,
        #0a1015;
}
/* The launch column's glass buttons: full width, the shared column's rhythm. */
.start-glass { width: 100%; }
.start-glass .gb { width: 100%; }

/* ── The pond: the ellipse the seats sit around ─────────────────────────────── */
#board {
    position: absolute; left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    border-radius: 50%;
    border: 1px solid rgba(255, 255, 255, 0.05);
    box-shadow: inset 0 0 70px rgba(0, 0, 0, 0.50), 0 18px 60px rgba(0, 0, 0, 0.35);
    background:
        radial-gradient(ellipse at 50% 46%, var(--felt-trump, transparent) 0%, rgba(0, 0, 0, 0) 60%),
        radial-gradient(ellipse at 50% 45%, rgba(18, 40, 58, 0.20), rgba(4, 8, 12, 0.28) 70%);
    transition: background 420ms ease;
    pointer-events: none;
}

/* THE TRUMP'S OWN EMBLEM, watermarked into the cloth under the trick it decides: the suit's
   mosaic cut from the printed art, used as a mask over the suit's ink. Over the felt's light
   and under every card. It fades slower than the card that declares it, and keeps the old
   suit while it fades out (JS never clears the mask while it is on the way out). */
.felt-suit {
    position: absolute;
    transform: translate(-50%, -50%);
    background: var(--ss-ink);
    mask-repeat: no-repeat; -webkit-mask-repeat: no-repeat;
    mask-position: center; -webkit-mask-position: center;
    mask-size: contain; -webkit-mask-size: contain;
    opacity: 0;
    pointer-events: none;
    z-index: 1;
    transition: opacity 560ms ease, background-color 300ms ease;
}
.felt-suit.on { opacity: 0.11; }
.felt-suit[data-suit="cherry"]  { background: var(--ss-cherry); }
.felt-suit[data-suit="koi"]     { background: var(--ss-koi); }
.felt-suit[data-suit="lantern"] { background: var(--ss-lantern); }
.felt-suit[data-suit="bridge"]  { background: var(--ss-bridge); }

#trick-zone, #seats, #piles, #hand { position: absolute; inset: 0; pointer-events: none; }
#trick-zone { z-index: 2; }
#hand > *, #seats > *, #piles > * { pointer-events: auto; }
.play-zone { position: absolute; width: 0; height: 0; pointer-events: none; }

/* ── Cards ──────────────────────────────────────────────────────────────────
   The printed art IS the card: a 500×700 mosaic face and a mosaic back. The box carries a
   thin stone-white edge so a card reads as a card against the dark pond even where the
   art's border is dark. */
.card {
    position: absolute;
    width: calc(var(--card-w) * var(--card-scale));
    height: calc(var(--card-h) * var(--card-scale));
    border-radius: calc(var(--card-w) * 0.07);
    background: #e9e6e2;
    box-shadow: 0 3px 9px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(0, 0, 0, 0.35);
    overflow: hidden;
    transition: transform 180ms cubic-bezier(.22,.75,.3,1), box-shadow 140ms, filter 140ms;
    will-change: transform;
    touch-action: none;
}
.card img {
    display: block; width: 100%; height: 100%;
    object-fit: cover; pointer-events: none;
    -webkit-user-drag: none;
}
.card .card-face, .card .card-back { position: absolute; inset: 0; }
.card.face-down .card-face { display: none; }
.card:not(.face-down) .card-back { display: none; }

/* Opponent backs and trick cards scale through their own tokens; every JS geometry helper
   reads the same numbers back, so layout and paint cannot disagree. */
.card.opp  { --card-scale: var(--opp-scale); }
.card.play { --card-scale: var(--play-scale); }

/* A card the human may legally play right now. Following is restrictive — often one suit,
   under Must Beat often one card — so showing it is most of the interface. */
.card.playable { cursor: pointer; }
.card.playable:hover { filter: brightness(1.12); box-shadow: 0 0 0 2px var(--dk-accent), 0 4px 14px rgba(0,0,0,.55); }
/* The trick's third beat: the card that took it lights, the two it beat fall back. */
.card.won-glow { box-shadow: 0 0 0 2px #ffe58a, 0 0 14px rgba(255, 229, 138, 0.95); filter: brightness(1.08); z-index: 80 !important; }
.card.lost-dim { filter: brightness(0.5) saturate(0.65); }
.card.won-glow, .card.lost-dim { transition: filter 220ms ease, box-shadow 220ms ease; }
.card.selected { box-shadow: 0 0 0 2px var(--dk-accent), 0 8px 18px rgba(0,0,0,.55); z-index: 100 !important; }
.card.illegal { filter: brightness(0.58) saturate(0.55); }
.card.illegal::after {
    content: ''; position: absolute; inset: 0;
    border-radius: inherit; background: rgba(6, 10, 14, 0.26);
}
.card.just-played { box-shadow: 0 0 0 2px var(--dk-accent), 0 4px 14px rgba(0,0,0,.55); }

/* The audible refusal: an illegal tap shakes the card and the status line says why. */
/* The shake runs on the FACE, not the card: the card's own transform is its position on the
   table, and a keyframe on it would throw the card to the origin for the duration. */
.card.shake .card-face, .card.shake .card-back { animation: ss-shake 360ms cubic-bezier(.36,.07,.19,.97) both; }
@keyframes ss-shake {
    10%, 90% { transform: translateX(-2px); }
    20%, 80% { transform: translateX(3px); }
    30%, 50%, 70% { transform: translateX(-5px); }
    40%, 60% { transform: translateX(5px); }
}

/* ── Trick piles ────────────────────────────────────────────────────────────
   The declarer's tricks are the only ones that score, so theirs are laid out one by one
   (the count must be readable from across the table); the defenders' go on one heap. In
   Win Last 6 the first six are shown greyed: taken, but not counted. */
.pile {
    position: absolute;
    display: flex; align-items: center; gap: 4px;
    pointer-events: none;
}
.pile-card {
    width: calc(var(--card-w) * 0.30); height: calc(var(--card-h) * 0.30);
    border-radius: 3px;
    background: #3b4a52 url('assets/cards/back.webp') center / cover;
    box-shadow: 0 1px 3px rgba(0,0,0,.6);
    margin-left: calc(var(--card-w) * -0.14);
}
.pile-card:first-child { margin-left: 0; }
.pile-card.dim { filter: brightness(0.45) saturate(0.3); }
.pile-count {
    margin-left: 6px;
    font-family: var(--dk-font-display); font-size: 18px; font-weight: 700;
    color: var(--ss-ink); font-variant-numeric: tabular-nums;
    text-shadow: 0 1px 4px rgba(0,0,0,.8);
}
.pile-count.dim { color: var(--ss-dim); }

/* ── Seats: the name tags ────────────────────────────────────────────────────── */
.seat {
    position: absolute;
    display: flex; flex-direction: column; align-items: center; gap: 2px;
    padding: 5px 10px;
    border-radius: 10px;
    background: var(--ss-stone);
    border: 1px solid rgba(255, 255, 255, 0.07);
    font-size: 12.5px; line-height: 1.25;
    white-space: nowrap;
    transition: border-color 180ms, box-shadow 180ms, background 180ms;
}
.seat.turn {
    border-color: var(--dk-accent);
    box-shadow: 0 0 0 1px var(--dk-accent-soft), 0 6px 18px rgba(0, 0, 0, 0.35);
    background: var(--ss-stone-hi);
}
.seat-name { font-weight: 600; color: var(--ss-ink); }
.seat-name.ai { color: var(--ss-dim); }
.seat-row { display: flex; align-items: center; gap: 7px; color: var(--ss-dim); font-size: 11.5px; }
.seat-badge {
    display: inline-flex; align-items: center; gap: 3px;
    padding: 1px 7px; border-radius: 999px;
    background: var(--dk-accent-soft);
    color: var(--ss-ink);
    font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
}
.seat-badge.defender { background: rgba(255,255,255,.07); color: var(--ss-dim); }

/* ── The reminder cards: live state props in the middle ────────────────────── */
#props { position: absolute; pointer-events: none; }
.prop {
    position: absolute;
    width: var(--prop-w); height: calc(var(--prop-w) * 1.4);
    border-radius: calc(var(--prop-w) * 0.07);
    overflow: hidden;
    background: #e9e6e2;
    box-shadow: 0 3px 9px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(0, 0, 0, 0.35);
    transition: transform 220ms cubic-bezier(.22,.75,.3,1), box-shadow 180ms, opacity 180ms, filter 180ms;
    pointer-events: auto;
}
.prop img { display: block; width: 100%; height: 100%; object-fit: cover; pointer-events: none; -webkit-user-drag: none; }
/* The rank cards (9 HIGH / 1 HIGH) are printed along the long edge: they lie landscape, the
   art turned a quarter clockwise inside, so the words read. game.js's propBox() sizes the
   slot to match. */
.prop.rank { width: calc(var(--prop-w) * 1.4); height: var(--prop-w); }
.prop.rank img {
    position: absolute; left: 50%; top: 50%;
    width: var(--prop-w); height: calc(var(--prop-w) * 1.4);
    transform: translate(-50%, -50%) rotate(90deg);
}
.prop.small { --prop-w: 48px; }
.prop.hidden { display: none; }
.prop.inactive { filter: brightness(0.6) saturate(0.6); }
/* The two rank cards as a CHOICE: both lifted, both taps, ≥44px. */
.prop.choice {
    cursor: pointer;
    transform: translateY(-8px);
    box-shadow: 0 0 0 2px var(--dk-accent), 0 10px 22px rgba(0, 0, 0, 0.5);
}
.prop.choice:hover { filter: brightness(1.1); }
.prop.chosen { box-shadow: 0 0 0 2px var(--dk-accent), 0 3px 9px rgba(0,0,0,.55); }
/* The trump card carries the chosen suit's emblem, cut from the art, on a dark disc. */
.trump-suit {
    position: absolute; left: 50%; bottom: 6%;
    transform: translateX(-50%);
    width: 52%; aspect-ratio: 1;
    border-radius: 50%;
    background: rgba(10, 14, 18, 0.86) center / 78% no-repeat;
    box-shadow: 0 1px 4px rgba(0,0,0,.5);
}
.trump-suit[data-suit="cherry"]  { background-image: url('assets/suits/cherry.webp'); }
.trump-suit[data-suit="koi"]     { background-image: url('assets/suits/koi.webp'); }
.trump-suit[data-suit="lantern"] { background-image: url('assets/suits/lantern.webp'); }
.trump-suit[data-suit="bridge"]  { background-image: url('assets/suits/bridge.webp'); }
/* Declared, the result parks in the row under the sheet at the small size; a prop that was
   on the table when the declaration landed flies there once and is static after. */
.prop.parked { --prop-w: 44px; z-index: 45; }   /* JS sets the width per site */
.prop.moving { transition: transform 480ms cubic-bezier(.22,.75,.3,1), box-shadow 180ms; transform-origin: center center; }
/* Slalom: the rank card turns over every trick. */
.prop.flip { animation: ss-flip 420ms ease-in-out; }
@keyframes ss-flip {
    0%   { transform: rotateY(0deg); }
    50%  { transform: rotateY(90deg); }
    100% { transform: rotateY(0deg); }
}

/* ── The suit chips ─────────────────────────────────────────────────────────
   Four glass buttons (built in game.js) on a stone tray, each carrying its suit's emblem
   cut from the art and its name in the suit's ink. */
#suit-picker {
    position: absolute;
    display: grid; grid-template-columns: 1fr 1fr; gap: 8px 10px;
    padding: 10px 12px;
    background: var(--ss-stone);
    border: 1px solid var(--ss-line);
    border-radius: 14px;
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.5);
    z-index: 60;
}
#suit-picker.hidden { display: none; }
.suit-chip .gb { min-width: 96px; padding: 0 12px 0 8px; }
.suit-chip .gb-text { gap: 8px; }
.suit-chip img { width: 30px; height: 30px; object-fit: contain; pointer-events: none; display: block; }
.suit-chip[data-suit="cherry"] .gb-text span  { color: var(--ss-cherry); }
.suit-chip[data-suit="koi"] .gb-text span     { color: var(--ss-koi); }
.suit-chip[data-suit="lantern"] .gb-text span { color: var(--ss-lantern); }
.suit-chip[data-suit="bridge"] .gb-text span  { color: var(--ss-bridge); }

/* ── The header's glass tools ───────────────────────────────────────────────────
   The material carries hover, press, focus and disabled; these add only the glyph size and
   the "its panel is open" state (header.js toggles .active on the button). */
.hdr-tool { touch-action: manipulation; }
.hdr-tool .gb-text svg { width: 18px; height: 18px; display: block; }
/* iOS: a tap landing on the SVG can miss the button — the icon is non-interactive. */
.hdr-tool svg, .hdr-tool svg * { pointer-events: none; }
.hdr-tool.active {
    background: linear-gradient(-75deg, rgba(255,255,255,.22), rgba(255,255,255,.04) 45%, rgba(255,255,255,.12)), rgba(var(--gb-tint) / 0.34);
    box-shadow: inset 0 1px 0 rgba(255,255,255,.55), inset 0 -1px 1px rgba(var(--gb-shade) / .6), inset 0 0 0 1px rgba(var(--gb-tint) / .7), 0 0 22px -6px rgba(var(--gb-tint) / .6);
}
#header-right { gap: 8px; }

/* ── Status line and the one bid button ─────────────────────────────────────── */
#status-msg.alert { color: var(--ss-danger); }
#pass-host { position: absolute; z-index: 55; }
#pass-host.hidden { display: none; }

/* ── Voice chat ────────────────────────────────────────────────────────────────
   The header's mic button (core/voice.js) is hidden outside a room; `.active` is the glass
   "on" state the other tools use. THE SEAT TAG IS THE SPEAKING INDICATOR: while its player
   talks the pill dissolves and the VOICE ORB (core/voiceOrb.js, the 21st.dev bubbling
   circle) is drawn in its place, centred on the tag and sized by game.js; the name and the
   badge stay on top. It wakes when that seat's level rises and fades 600 ms after it drops. */
#voice-wrap.hidden { display: none; }
.ico-spin { transform-origin: 50% 50%; animation: ss-spin 0.9s linear infinite; }
@keyframes ss-spin { to { transform: rotate(360deg); } }
#voice-orbs { position: absolute; inset: 0; pointer-events: none; }   /* under #seats in the DOM */
.seat-orb {
    position: absolute;
    opacity: 0; transform: scale(.7);
    transition: opacity 200ms ease, transform 200ms ease;
    will-change: opacity, transform;
}
.seat-orb.speaking { opacity: 1; transform: scale(1); }
canvas.voice-orb { display: block; width: 100%; height: 100%; }
/* The pill goes: the orb behind it is the tag now. Text keeps its place and gains a shadow. */
.seat.speaking {
    background: transparent;
    border-color: transparent;
    box-shadow: none;
    transition: background 200ms ease, border-color 200ms ease, box-shadow 200ms ease;
}
.seat.speaking .seat-name, .seat.speaking .seat-name.ai { color: #fff; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85), 0 0 8px rgba(0, 0, 0, 0.6); }
.seat.speaking .seat-badge { text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8); }
@media (prefers-reduced-motion: reduce) { .ico-spin { animation: none; } .seat-orb { transition: none; } }
.table-btn {
    position: absolute;
    min-width: 88px; min-height: 44px;
    padding: 8px 18px;
    border: 1px solid var(--ss-line); border-radius: 9px;
    background: var(--ss-stone);
    color: var(--ss-ink);
    font-family: var(--dk-font-ui); font-size: 14px; font-weight: 600; letter-spacing: .04em;
    cursor: pointer; touch-action: manipulation;
    z-index: 55;
    transition: background 140ms, border-color 140ms;
}
.table-btn:hover { background: var(--ss-stone-hi); border-color: var(--dk-accent); }
.table-btn.hidden { display: none; }
.modal-actions .table-btn { position: static; margin-top: 8px; }

/* ── THE SCORESHEET ─────────────────────────────────────────────────────────
   The printed scoresheet (rulebook p.10) cut into the game's own stone and turned on its
   side to fit the top band: one ROW per player, one COLUMN per contract with "Tricks" and
   "Score" under each heading, the totals down the right. The slab is the grey tesserae of
   the stepping stones on the cover; the rules and the figures are the pond's ink; moss for
   what can be tapped (the human's bid), koi for a score, cherry for a loss. JS builds the
   grid from the contract set and sets #sheet's outer box; the rules below are its material. */
#sheet {
    position: absolute;
    z-index: 40;
    display: flex; flex-direction: column;
    padding: 6px 10px 8px;
    background:
        linear-gradient(165deg, rgba(255,255,255,0.16), rgba(255,255,255,0) 40%, rgba(0,0,0,0.06)),
        var(--ss-slab);
    border: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: 10px;
    box-shadow: 0 14px 40px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255,255,255,0.35);
    color: var(--ss-pond);
    overflow: hidden;
}
.sheet-title {
    display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
    font-family: var(--dk-font-display); font-size: 16px; font-weight: 700; letter-spacing: .08em;
    color: var(--ss-pond);
    padding: 0 2px 4px;
}
.sheet-title small { font-family: var(--dk-font-ui); font-size: 11.5px; font-weight: 600; letter-spacing: .03em; color: var(--ss-pond-dim); }

/* The grid is one table: thin pond-ink rules drawn as gaps over an ink ground. */
.sheet-grid {
    display: grid;
    /* the columns are set inline by JS — they depend on how many contracts are in play */
    grid-template-rows: minmax(44px, auto) 18px repeat(3, minmax(44px, 1fr));
    gap: 1px;
    flex: 1; min-height: 0;
    background: var(--ss-rule);
    border: 1px solid var(--ss-rule);
    border-radius: 4px;
    overflow: hidden;
}
.sheet-grid[hidden] { display: none; }
.sheet-grid > * > * { background: var(--ss-slab); }
.sheet-row { display: contents; }

/* Header row: "Player", then each contract's name over its conditions. */
.sheet-corner, .sub-corner, .total-label, .sub-total {
    display: flex; align-items: center; padding: 2px 8px;
    font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
    color: var(--ss-pond-dim);
}
.sheet-corner { align-items: flex-end; padding-bottom: 5px; }
.total-label { justify-content: center; align-items: flex-end; padding-bottom: 5px; }
.sheet-label {
    grid-column: span 2;
    display: flex; flex-direction: column; justify-content: flex-end; align-items: center;
    gap: 1px;
    padding: 4px 6px 4px; min-height: 44px;
    text-align: center; line-height: 1.15;
    color: var(--ss-pond);
    transition: background 160ms, box-shadow 160ms;
}
.sheet-label b { font-family: var(--dk-font-display); font-size: 15px; font-weight: 700; letter-spacing: .02em; }
.sheet-label .cond { font-size: 10px; color: var(--ss-pond-dim); letter-spacing: .01em; white-space: normal; text-align: center; max-width: 100%; line-height: 1.2; }
/* THE CONTRACT IN PLAY: its whole column is lit — the heading fills with moss under a heavy
   accent bar and breathes, slowly (a signal, so slower than anything a card does), the
   sub-labels and every cell beneath take the wash, and the declarer's own cell keeps its
   ring on top. One column reads as "this hand" from across the table. */
.sheet-label.active {
    background: var(--ss-moss-wash-hi);
    color: var(--ss-pond);
    box-shadow: 0 0 0 0 rgba(127, 163, 107, 0);
    animation: ss-inplay 2.8s ease-in-out infinite;
}
.sheet-label.active .cond { color: var(--ss-pond); font-weight: 600; }
.sheet-label.active b { color: #15301a; }
@keyframes ss-inplay {
    0%, 100% { box-shadow: 0 0 0 0 rgba(127, 163, 107, 0.0); }
    50%      { box-shadow: 0 0 0 3px rgba(127, 163, 107, 0.55); }
}
@media (prefers-reduced-motion: reduce) { .sheet-label.active { animation: none; } }
/* The wash covers every seat's cells in the column, the human's lighter row included — with
   only the opponents' cells washed the column read as "two players highlighted" (4 cells). */
.sheet-grid > .sheet-row-seat > .inplay,
.sheet-grid > .sheet-row-seat.mine > .cell.inplay,
.sheet-grid > .sheet-row-seat.mine > .score.inplay { background: var(--ss-moss-wash); }
.sheet-grid > * > .cell.inplay.active { box-shadow: inset 0 0 0 2px var(--dk-accent), inset 0 0 0 4px var(--ss-slab-hi); }
/* The sub-columns. */
.sub-label {
    display: flex; align-items: center; justify-content: center;
    font-size: 9.5px; letter-spacing: .06em; text-transform: uppercase; color: var(--ss-pond-dim);
}
.sub-label.active { background: var(--ss-moss-wash-hi); color: var(--ss-pond); font-weight: 700; }

/* Row heads: the players. */
.sheet-name {
    display: flex; align-items: center; gap: 6px;
    padding: 2px 8px; min-height: 40px;
    font-size: 13px; font-weight: 600; color: var(--ss-pond-dim);
    white-space: nowrap; overflow: hidden;
}
.sheet-name-text { overflow: hidden; text-overflow: ellipsis; }
.sheet-name.mine { color: var(--ss-pond); }
.sheet-name.turn { color: var(--ss-moss-ink); }
.sheet-name.turn::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: var(--dk-accent); flex: none; }

/* The figures. Tricks in the pen's ink; the score in koi, a loss in cherry. */
.cell, .score, .gp {
    position: relative;
    display: flex; align-items: center; justify-content: center;
    min-height: 44px; min-width: 44px;
    font-family: var(--dk-font-display); font-size: 21px; font-weight: 700; font-style: italic;
    font-variant-numeric: tabular-nums;
    color: var(--ss-pond);
    transition: background 160ms, box-shadow 160ms, color 160ms;
}
.cell.blank::before { content: '·'; color: var(--ss-pond-dim); opacity: .45; font-style: normal; }
.cell.active { box-shadow: inset 0 0 0 2px var(--dk-accent); }
.score { color: var(--ss-koi-ink); font-size: 19px; }
.score.negative { color: var(--ss-cherry-ink); }
.gp { color: var(--ss-koi-ink); font-size: 22px; font-style: normal; background: var(--ss-slab-hi) !important; }
.gp.negative { color: var(--ss-cherry-ink); }
.sheet-row-seat.mine .cell, .sheet-row-seat.mine .score { background: var(--ss-slab-mine); }
.sheet-row-seat.mine .sheet-name { background: var(--ss-slab-mine); }

/* THE BID: the human's cells under every open contract light up with the heading, and a
   tap on any of them declares. ≥44px tall, and a Tricks|Score pair is ≥88px wide. */
.sheet-grid .sheet-label.open, .sheet-grid .sheet-label.redo,
.sheet-grid .cell.open, .sheet-grid .cell.redo, .sheet-grid .score.open, .sheet-grid .score.redo {
    cursor: pointer;
    background: var(--ss-moss-wash);
    box-shadow: inset 0 0 0 1px var(--dk-accent);
}
.sheet-grid .sheet-label.open:hover, .sheet-grid .sheet-label.redo:hover,
.sheet-grid .cell.open:hover, .sheet-grid .cell.redo:hover, .sheet-grid .score.open:hover, .sheet-grid .score.redo:hover { background: var(--ss-moss-wash-hi); }
.sheet-grid .sheet-label.picked, .sheet-grid .cell.picked, .sheet-grid .score.picked { background: var(--dk-accent); color: #fff; }
.sheet-label.picked .cond { color: rgba(255,255,255,.85); }
.sheet-label.redo::after, .cell.redo::after {
    content: 'redo'; position: absolute; right: 3px; top: 2px;
    font-family: var(--dk-font-ui); font-style: normal; font-size: 8.5px; font-weight: 700;
    letter-spacing: .08em; text-transform: uppercase; color: var(--ss-moss-ink);
}
/* An AI declaring, and a recorded count landing: the cell pulses. */
.cell.pulse { animation: ss-pulse 900ms ease-out 2; z-index: 1; }
@keyframes ss-pulse {
    0%   { box-shadow: 0 0 0 0 var(--dk-accent); }
    100% { box-shadow: 0 0 0 10px rgba(127,163,107,0); }
}
/* The re-rank: a column just completed and the figures re-sort — the game's surprise. */
.gp.rerank, .cell.rerank, .score.rerank { animation: ss-rerank 720ms ease-out; }
@keyframes ss-rerank {
    0%   { transform: scale(1);   filter: brightness(1); }
    30%  { transform: scale(1.22); filter: brightness(1.25); }
    100% { transform: scale(1);   filter: brightness(1); }
}
/* The redo token beside a name: a stone; spent, it is hollow. */
.token {
    flex: none;
    width: 11px; height: 11px; border-radius: 50%;
    background: var(--dk-accent);
    box-shadow: 0 1px 2px rgba(0,0,0,.4);
}
.token.spent { background: transparent; border: 1.5px solid var(--ss-pond-dim); box-shadow: none; }

/* The strip treatment: the same slab, one line (hand · GP totals) until tapped; opened,
   the grid unfolds downward over the board. */
body.sheet-strip #sheet { padding: 6px 10px; cursor: pointer; }
body.sheet-strip #sheet .sheet-title { padding-bottom: 2px; font-size: 14px; }
body.sheet-strip #sheet .sheet-title small { font-size: 11.5px; color: var(--ss-pond); }
body.sheet-strip #sheet .sheet-title { padding-right: 104px; }
body.sheet-strip #sheet.has-park:not(.expanded) .sheet-title { padding-right: 84px; }
body.sheet-strip #sheet.expanded .sheet-title { padding-bottom: 6px; }
body.sheet-strip #sheet.expanded { cursor: default; }
body.sheet-strip #sheet .sheet-grid { flex: none; }

/* ── Round / game end ───────────────────────────────────────────────────────── */
#game-over-line { font-size: 14px; color: var(--ss-dim); margin: 2px 0 12px; }
.go-row {
    display: flex; align-items: center; gap: 10px;
    padding: 7px 12px; border-radius: 8px; margin-bottom: 4px;
    font-size: 14px; background: rgba(255,255,255,0.04);
}
.go-row.winner { background: var(--dk-accent-soft); box-shadow: inset 0 0 0 1px var(--dk-accent); }
.go-place { color: var(--ss-dim); font-size: 11px; width: 18px; }
.go-name  { flex: 1; text-align: left; }
.go-firsts { font-size: 11px; color: var(--ss-dim); }
.go-score { font-family: var(--dk-font-display); font-size: 20px; font-weight: 700; color: var(--dk-accent); min-width: 34px; text-align: right; font-variant-numeric: tabular-nums; }
.go-score.negative { color: var(--ss-danger); }
.modal-actions { display: flex; flex-direction: column; gap: 10px; margin-top: 16px; }

/* Card names in the log, the status line and the AI panel carry their suit's ink. */
.suit-ink { font-weight: 600; white-space: nowrap; }
.suit-ink[data-suit="cherry"]  { color: var(--ss-cherry); }
.suit-ink[data-suit="koi"]     { color: var(--ss-koi); }
.suit-ink[data-suit="lantern"] { color: var(--ss-lantern); }
.suit-ink[data-suit="bridge"]  { color: var(--ss-bridge); }

/* ── Animation ghosts ───────────────────────────────────────────────────────── */
#anim-layer { position: absolute; inset: 0; pointer-events: none; z-index: 900; }
.ghost { position: absolute; z-index: 900; pointer-events: none; will-change: transform; }
.ghost .card { position: absolute; left: 0; top: 0; transition: none; }
/* The recording count, flying from the pile to the cell. */
.ghost-count {
    position: absolute; z-index: 910; pointer-events: none;
    font-family: var(--dk-font-display); font-size: 26px; font-weight: 700;
    color: var(--ss-ink); text-shadow: 0 2px 8px rgba(0,0,0,.8);
    font-variant-numeric: tabular-nums;
}
/* The publisher's mark, bottom-left, at half strength — furniture, never a control. Hidden
   where the human's fan needs the whole width. */
#publisher-logo {
    position: absolute; left: 14px; bottom: 10px;
    width: 112px; height: auto;
    opacity: 0.5; pointer-events: none; -webkit-user-drag: none;
    z-index: 1;
}
@media (max-width: 900px) { #publisher-logo { display: none; } }
/* The welcome (game.js welcome()): a glass plate in the middle of the table for three
   seconds, the page inert beneath it — the layer takes every pointer and dims nothing. */
#ss-welcome {
    position: fixed; inset: 0; z-index: 1190;
    display: flex; align-items: center; justify-content: center;
    opacity: 0; transition: opacity 320ms ease;
}
#ss-welcome.show { opacity: 1; }
#ss-welcome .ss-welcome-plate { transform: translateY(10px); transition: transform 420ms cubic-bezier(.22,.75,.3,1); }
#ss-welcome.show .ss-welcome-plate { transform: none; }
#ss-welcome .gb-plate {
    flex-direction: column; gap: 6px; padding: 18px 34px 20px; border-radius: 16px;
    max-width: min(92vw, 520px); text-align: center;
    background:
        linear-gradient(-75deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0.18) 50%, rgba(255, 255, 255, 0.06) 100%),
        rgba(var(--gb-tint) / 0.10),
        rgba(var(--gb-shade) / 0.58);
    backdrop-filter: blur(10px) saturate(1.15); -webkit-backdrop-filter: blur(10px) saturate(1.15);
}
.ss-welcome-title { font-family: var(--dk-font-display); font-size: 28px; font-weight: 700; letter-spacing: 0.04em; color: #fff; text-shadow: 0 0 14px rgba(127, 163, 107, 0.55), 0 1px 2px rgba(2, 6, 20, 0.7); }
.ss-welcome-line { font-family: var(--dk-font-ui); font-size: 15px; color: rgba(238, 244, 240, 0.86); }
@media (max-width: 620px) { .ss-welcome-title { font-size: 23px; } #ss-welcome .gb-plate { padding: 14px 22px 16px; } }
@media (prefers-reduced-motion: reduce) { #ss-welcome, #ss-welcome .ss-welcome-plate { transition: none; } }
/* The declaration toast: a stone plate over the top of the board, 3 s, one at a time. */
.ss-toast {
    position: absolute; z-index: 820;
    padding: 9px 18px; border-radius: 10px;
    background: var(--ss-slab);
    border: 1px solid var(--dk-accent-soft);
    box-shadow: 0 10px 26px rgba(0, 0, 0, 0.55);
    color: var(--ss-pond);
    font-family: var(--dk-font-display); font-size: 17px; font-weight: 600; letter-spacing: .02em;
    white-space: nowrap; max-width: calc(100% - 24px); overflow: hidden; text-overflow: ellipsis;
    pointer-events: none;
    animation: ss-toast-in 260ms cubic-bezier(.22,.75,.3,1) both;
}
.ss-toast b { font-weight: 800; }
.ss-toast.out { animation: ss-toast-out 400ms ease-in forwards; }
@keyframes ss-toast-in  { from { opacity: 0; transform: translateY(8px) scale(.96); } to { opacity: 1; transform: none; } }
@keyframes ss-toast-out { to { opacity: 0; transform: translateY(-12px); } }
@media (prefers-reduced-motion: reduce) { .ss-toast, .ss-toast.out { animation: none; } .ss-toast.out { opacity: 0; } }
@media (max-width: 700px) { .ss-toast { font-size: 14px; padding: 7px 12px; } }
/* Bid and trick call-outs, floated beside the seat that made them. */
.callout {
    position: absolute; z-index: 800;
    padding: 5px 12px; border-radius: 999px;
    background: var(--ss-stone-hi);
    border: 1px solid var(--dk-accent-soft);
    color: var(--dk-accent);
    font-family: var(--dk-font-display); font-size: 14px; font-weight: 700;
    letter-spacing: .03em; white-space: normal; max-width: 240px; text-align: center; line-height: 1.25;
    pointer-events: none;
    animation: ss-callout 1900ms ease-out forwards;
}
@keyframes ss-callout {
    0%   { opacity: 0; transform: translateY(6px) scale(.92); }
    14%  { opacity: 1; transform: translateY(0) scale(1); }
    76%  { opacity: 1; }
    100% { opacity: 0; transform: translateY(-16px); }
}

/* "Contract to skip" while the Shorter Game is off: present, greyed, not a tap. This game's
   own class on the row; the shared .start-field rule is untouched. */
.start-field-off { opacity: var(--dk-start-disabled, 0.35); }
.start-field-off select { cursor: not-allowed; }

/* The Rules link in the start screen's launch column — sized to its own text. */
.rules-link {
    align-self: center;
    display: inline-block;
    width: auto;
    margin-top: 4px;
    padding: 7px 16px;
    border: 1px solid var(--dk-accent-soft);
    border-radius: var(--dk-radius);
    background: transparent;
    color: var(--ss-ink);
    font-family: var(--dk-font-ui); font-size: 13px; font-weight: 600;
    text-align: center; text-decoration: none; white-space: nowrap;
    opacity: .85;
    transition: background .15s, opacity .15s;
}
.rules-link:hover { background: var(--dk-accent-soft); opacity: 1; }

/* ── The crawlable block ────────────────────────────────────────────────────── */
/* Crawlable copy (#intro in index.html): below the fold, never display:none. The table is
   JS-positioned from the viewport and body cannot scroll, so the block is never reached. */
#intro {
    margin: 100vh auto 0; max-width: 720px; padding: 4px 20px 8px; text-align: center;
    color: var(--dk-text-muted, #b8c4d0); font-family: var(--dk-font-ui); font-size: 14px;
    line-height: 1.55;
}
#intro h1 { color: var(--dk-text, #eef4ff); font-family: var(--dk-font-display); font-weight: 700;
            font-size: 24px; letter-spacing: 0.03em; margin: 0 0 8px; }
#intro p { margin: 0 0 9px; }
#intro a { color: var(--dk-accent); }

/* ── Mobile ─────────────────────────────────────────────────────────────────
   Layout is JS-positioned from the viewport; CSS only drops what it owns. */
@media (max-width: 700px), (max-height: 460px) {
    .pile-count { font-size: 15px; }
    .seat { font-size: 11.5px; padding: 4px 8px; }
    .cell, .score { font-size: 17px; }
    .gp { font-size: 19px; }
    .sheet-label b { font-size: 13px; }
    .suit-chip img { width: 24px; height: 24px; }
    .suit-chip .gb { min-width: 0; }
}

@media (prefers-reduced-motion: reduce) {
    .card, .prop, .cell, .score, .seat, .gp, .felt-suit, #board { transition: none; animation: none; }
    .card.shake .card-face, .card.shake .card-back, .prop.flip, .cell.pulse, .gp.rerank, .cell.rerank, .score.rerank { animation: none; }
    .callout { animation: none; opacity: 1; }
}
