/* style.css — Mogler. THIS GAME ONLY.
 *
 * The header, AI badge, tool buttons, tool panels, start screen, dialog shell, house fonts
 * and the table felt all come from ../core/deckades.css, which index.html links FIRST, and
 * what is PRINTED on a playing card comes from ../core/cards/cards.css, linked AFTER this
 * sheet. Nothing here may restyle any of those selectors. What is left is this game's own
 * surface: the table, the card BOXES, the STOP cards, the trick piles, the seat plates and
 * the stars panel.
 *
 * -- DIE BANDEROLE ------------------------------------------------------------
 *
 * The world is the printed matter that seals a deck of cards before anyone may open it: an
 * engraved plate, guilloche cut into it, and a paper band across the seal that has to be
 * cancelled before play can end. It was chosen because this game's three hard questions are
 * already that world's three marks.
 *
 *   A locked suit is a STAMP.     Winning a Trumpfstich inks your record with the suit it
 *                                 shows, and a stamp cannot be taken back. That is what
 *                                 "you may no longer play this" looks like on paper.
 *   The STOP pile is the SEAL.    Nine cards spelling a word, turned over one per trick and
 *                                 laid out beside the deck. A complete word voids the seal
 *                                 and the round closes. They are cards, not a printed band:
 *                                 the letters are read off the stock they came on.
 *   The trick is the CARTOUCHE.   An engraved plaque, top left, naming what is being played
 *                                 for right now and in which ink.
 *
 * ONE LAW ABOUT COLOUR: a hue may only ever mean a suit. The suit inks are the rulebook's own
 * red and black, and nothing else on this table may borrow one -- so cancellation, refusal and
 * emphasis are carried by RULES, STRIKES and WEIGHT, never by a second red. Everything that is
 * not a suit is plate metal or paper. Which of a red pair a glyph is, or which of a black
 * pair, is a BORDER and not a hue: see "Suit ink" below.
 *
 * ONE ENGRAVED WEIGHT: every hairline on this table is 1px of the same metal. No element
 * invents its own rule.
 *
 * Where the colours come from:
 *   --dk-accent   the burnished metal of the plate itself. Low chroma on purpose: it is a
 *                 MATERIAL, not a third colour, so it cannot be mistaken for a suit. Hue 42
 *                 at 30% saturation, clear of both suit inks (red 355, black neutral) and
 *                 pewter rather than Haggis's brass.
 *   --dk-tint     a cold steel wash over the shared felt, so the table reads as plate.
 *   --band-*      the paper stock the round-end record is printed on, and its ink.
 *   --stop-ink    the blue of the STOP letters in the author's prototype photo (page 1 of
 *                 mogler_v066_im_test.pdf). INK ON PAPER inside the game's own art, kept.
 *   --suit-*      the deck's two inks. The pairs are told apart by an outline on the second
 *                 of each -- diamonds and spades. See "Suit ink" below.
 */

:root {
    /* The accent is the engraved metal AT REST; the lighter rgba(185,167,127,...) literals
       throughout this sheet are that same metal CATCHING THE LIGHT, which is why the two
       differ. This value was picked by measuring both jobs the token has to do, because the
       shared sheet fills .primary-btn with it and puts #fff on top:
           white on it   4.07:1   (the outgoing indigo scored 3.50)
           on the panel  4.61:1   (the outgoing indigo scored 5.36)
       The first light pewter read 7.95 on the panel and 2.36 under white -- beautiful on the
       plate and unreadable on every Play button in the game. This one beats the incumbent on
       the button and still clears 4.5:1 on the panel. */
    --dk-accent:      #8f7c4e;
    --dk-accent-soft: rgba(143, 124, 78, 0.34);
    --dk-tint:        rgba(32, 40, 52, 0.52);

    /* ── THE HEADER IS THE TOP EDGE OF THE SAME PLATE ──────────────────────────────────
       Everything below re-points the shared sheet's documented header skin set. None of it
       existed before, so the strip kept the platform's violet-black ground, its lavender
       logo, its violet tool rings and its GREEN "net is driving" badge -- on a table whose
       one law is that a hue may only ever mean a suit, and whose clubs are green. The game's
       own name was the only mark up there already speaking this world's language.

       Two things are true of every value here: it is plate metal or paper, never a hue; and
       it was measured, because a metal at 30% saturation goes unreadable long before a violet
       does. Ratios are against the strip as it actually composites over the table (#121419).

       Hierarchy on the strip, by value: the GAME'S NAME (6.8:1) reads first, then the tools
       (5.2:1), then the site's own mark (4.6:1), which is a maker's stamp and not a control. */

    /* The plate. Same three-stop shape as the house header so the strip still dissolves into
       the table rather than ending on a line. --dk-chrome-plate follows it, as the shared
       sheet says in as many words that a game re-tinting one re-points the other two. */
    --dk-header-bg:   linear-gradient(180deg,
                          rgba(18, 21, 26, 0.94),
                          rgba(18, 21, 26, 0.66) 80%,
                          rgba(18, 21, 26, 0));
    --dk-chrome-plate:      rgba(22, 26, 32, 0.94);
    --dk-chrome-plate-line: rgba(185, 167, 127, 0.30);
    /* ONE ENGRAVED WEIGHT: the felt's rim is 1px of this and so is the strip's bottom edge. */
    --dk-header-line: rgba(185, 167, 127, 0.22);

    /* The game's name, and the only mark on the strip allowed to be the metal catching full
       light. It had never been declared, so it fell through to --dk-accent at 0.9 opacity and
       measured about 4.0:1 -- the title of the game, below AA on its own header. */
    --dk-wordmark:    #c3ad7d;

    /* The site's mark: an engraved maker's stamp on the plate, one step below the game's own
       name. It is a LINK, so it clears 4.5:1 rather than sitting at the platform's decorative
       0.45; the step down is carried by value and by the face, not by making it unreadable. */
    --dk-logo:        rgba(185, 167, 127, 0.72);
    --dk-logo-hover:  rgba(214, 196, 154, 0.98);
    --dk-spade:       rgba(185, 167, 127, 0.85);
    --dk-spade-hover: #d6c49a;

    /* The three tool circles: a quiet engraved ring, the metal for the icon inside it, and a
       thin wash of the same metal when one is open. The ring is deliberately faint -- what
       identifies these controls is the drawn mark inside them, which measures 5.2:1. */
    --dk-tool-ring:       rgba(185, 167, 127, 0.38);
    --dk-tool-ink:        rgba(185, 167, 127, 0.78);
    --dk-tool-wash:       rgba(185, 167, 127, 0.14);
    --dk-tool-ink-hover:  #d6c49a;
    --dk-tool-ring-hover: rgba(185, 167, 127, 0.72);

    /* THE AI BADGE IS A LAMP ON A PLAQUE, not a coloured pill.
       The platform carries its four states in four hues -- green, violet, amber, grey. Green
       is CLUBS here and amber sits at the accent's own hue with three times its chroma, so it
       reads as the metal shouting. Neither is available. The states are carried the way this
       table carries every other distinction: by WEIGHT and by whether the lamp is LIT.
           onnx       6.0:1  lamp LIT and glowing, in the metal's full light
           ready       5.2:1  lamp on standby, a faint halo
           heuristic   4.6:1  lamp DARK -- there is no net driving
       Three signals separate the two that matter, and not one of them is a hue: the word, the
       ink's value, and the lamp. It also survives a narrow header, where the shared sheet
       drops the badge's text and the lamp is all that is left.

       The readout deliberately does NOT take the metal's full light -- only the lit lamp
       does. At 10.7:1 the words AI: ONNX were the brightest thing on the strip, brighter than
       the game's own name, which is a status line out-ranking a title. The badge is a lamp on
       a plaque: the lamp is the bright part. */
    --dk-ai-bg:           rgba(18, 21, 26, 0.72);
    --dk-ai-ink:          rgba(185, 167, 127, 0.55);
    --dk-ai-dot:          rgba(185, 167, 127, 0.30);
    --dk-ai-dot-glow:     none;
    --dk-ai-pending-dot:  rgba(185, 167, 127, 0.45);
    --dk-ai-pending-glow: 0 0 6px rgba(185, 167, 127, 0.25);
    --dk-ai-onnx-ink:     rgba(185, 167, 127, 0.85);
    --dk-ai-onnx-ring:    rgba(185, 167, 127, 0.42);
    --dk-ai-onnx-dot:     #d6c49a;
    --dk-ai-onnx-glow:    0 0 8px rgba(214, 196, 154, 0.55);
    --dk-ai-ready-ink:    rgba(185, 167, 127, 0.78);
    --dk-ai-ready-ring:   rgba(185, 167, 127, 0.30);
    --dk-ai-ready-dot:    rgba(185, 167, 127, 0.62);
    --dk-ai-ready-glow:   0 0 6px rgba(185, 167, 127, 0.30);
    --dk-ai-heur-ink:     rgba(185, 167, 127, 0.72);
    --dk-ai-heur-ring:    rgba(185, 167, 127, 0.20);
    --dk-ai-heur-dot:     rgba(185, 167, 127, 0.24);
    --dk-ai-heur-glow:    none;

    /* What the three tool buttons OPEN. --dk-panel paints exactly the move log, the AI panel,
       the ? popup and the badge's detail card in this game -- the stars panel is local and the
       start screen has its own group -- so these are the header's own surfaces and not a
       fifth skin. Left violet they would have been a seam of the old world hanging off a
       strip that no longer had one. Ink measures 12.8:1 on the panel, dim ink 5.7:1. */
    --dk-panel:        rgba(16, 19, 24, 0.97);
    --dk-panel-line:   rgba(185, 167, 127, 0.26);
    --dk-panel-text:   rgba(238, 231, 214, 0.92);
    --dk-panel-dim:    rgba(238, 231, 214, 0.58);
    --dk-panel-shadow: 0 10px 34px rgba(0, 0, 0, 0.70);
    --dk-popup-line:   rgba(185, 167, 127, 0.34);
    --dk-scroll-thumb: rgba(185, 167, 127, 0.28);
    --dk-popup-close:       rgba(185, 167, 127, 0.72);
    --dk-popup-close-hover: #d6c49a;
    /* The log's round heading takes the table's OWN star gold, which is the thing a round is
       counted in, rather than the platform's amber -- two golds a shade apart inside one
       panel read as one of them being wrong. */
    --dk-log-big:           var(--star);

    /* THE START SCREEN IS THE BANDEROLE ITSELF -- the sealed band across the deck, before it
       is broken. Declared through the sanctioned --dk-start-* group, which exists for exactly
       this: a game whose material is not a dark panel. No .start-* selector is touched.
       Ink on this stock measures 12.5:1. */
    --dk-start-scrim:   rgba(9, 11, 15, 0.88);
    --dk-start-surface: #ece3cf;
    --dk-start-edge:    rgba(120, 100, 62, 0.6);
    --dk-start-radius:  2px;
    /* Offset and blur, never a coloured halo: paper casts a shadow, it does not glow. */
    --dk-start-glow:    0 18px 54px rgba(0, 0, 0, 0.72), 0 2px 0 rgba(120, 100, 62, 0.35);
    --dk-start-ink:      #262119;
    --dk-start-ink-dim:  rgba(38, 33, 25, 0.68);
    --dk-start-ink-soft: #3a3227;
    --dk-start-ink-fade: rgba(38, 33, 25, 0.52);
    --dk-start-title-shadow: none;
    /* The one control on the card. Left at the dark-panel default it was a black hole punched
       through the paper; on this stock it is a ruled field with ink in it. */
    --dk-start-field:      rgba(255, 255, 255, 0.5);
    --dk-start-field-ink:  #262119;
    --dk-start-field-edge: rgba(120, 100, 62, 0.55);
    /* And the keyboard ring, which the shared sheet leaves at the UA default precisely so a
       game that skins the card light does not draw a white halo on paper. */
    --dk-start-focus:        2px solid #262119;
    --dk-start-focus-offset: 2px;

    /* Game-local tokens (not --dk-*, so outside the shared contract). */
    --card-w: 78px;             /* JS writes these from updateCardDims() on load and resize */
    --card-h: 111px;

    --face:      #f7f3ea;       /* the printed card stock */
    --face-edge: rgba(30, 24, 16, 0.22);
    --stop-ink:  #3d6db3;
    --ink:       #1c1c22;

    /* What the shared deck prints in: this game's ink and index face. Two inks, which is what
       the shared sheet knows and what the author's own deck prints, so nothing has to fall
       through to anything. How the pairs stay apart is under "Suit ink" in Cards. */
    --dk-card-red:   #b3242e;
    --dk-card-black: #1c1c22;
    --dk-card-font:  var(--dk-font-display);

    /* Suit ink: the rulebook's two. Which of a pair a glyph is comes from its BORDER, not
       from a hue — see the block under Cards. */
    --suit-red:   var(--dk-card-red);
    --suit-black: var(--dk-card-black);

    /* The same two inks LIFTED for the near-black panels — the move log and the seat plate's
       locked-suit chips. Both sit on the same ground, so they share one definition. Black
       cannot be printed on near-black, so it comes back as the paper it was printed on. */
    --log-red:   #ef8a8a;
    --log-black: #dfe3ee;

    --cream: rgba(238, 233, 222, 0.92);

    /* The plate: the metal everything on this table is cut from. */
    --steel:      #12151a;
    --steel-hi:   #1b2028;
    --engrave:    rgba(185, 167, 127, 0.055);   /* a cut line catching the light */
    --engrave-lo: rgba(0, 0, 0, 0.42);          /* the shadow in the same cut */

    /* Paper stock and ink. It carried the banderole first; the round-end record is printed
       on the same sheet and is the only thing left cut from it. */
    --band-paper:    #ece3cf;
    --band-paper-lo: #d8ccb0;
    --band-ink:      #262119;
    --band-ink-soft: rgba(38, 33, 25, 0.62);

    --plate: rgba(15, 18, 23, 0.80);
    --plate-line: rgba(185, 167, 127, 0.24);
    --star: #f2c94c;
    --alert: #e8735a;
    --good: #8ce0a0;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body { height: 100%; }

body {
    background-color: #0a0c10;
    width: 100vw; height: 100vh;
    overflow: hidden;
    position: relative;
    font-family: var(--dk-font-ui);
    color: var(--cream);
    -webkit-user-select: none; user-select: none;
}

/* ── The table: an engraved plate ───────────────────
 * Guilloche is CUT, not photographed: rules at engraver's angles, each a pale metal highlight
 * against its own shadow, over the shared felt reduced to a cold wash. The alphas are tiny on
 * purpose -- the pattern must be there when you look for it and gone when you are reading a
 * card. The light rakes from the upper left, which is where the cartouche sits, and the plate
 * falls into shadow at the edges so the play area is the lit part of the table. */
#game {
    position: absolute; inset: 0;
    z-index: 1;
    overflow: hidden;
    touch-action: none;                 /* stop OS pan/scroll from stealing card taps */
    background:
        radial-gradient(120% 95% at 26% 6%, rgba(185, 167, 127, 0.085), transparent 58%),
        radial-gradient(118% 108% at 50% 52%, transparent 40%, rgba(0, 0, 0, 0.66)),
        repeating-linear-gradient(24deg, var(--engrave-lo) 0 1px, transparent 1px 7px),
        repeating-linear-gradient(24deg, var(--engrave) 1px 2px, transparent 2px 8px),
        repeating-linear-gradient(-24deg, var(--engrave-lo) 0 1px, transparent 1px 7px),
        repeating-linear-gradient(-24deg, var(--engrave) 1px 2px, transparent 2px 8px),
        repeating-linear-gradient(90deg, var(--engrave-lo) 0 1px, transparent 1px 29px),
        repeating-linear-gradient(0deg, var(--engrave-lo) 0 1px, transparent 1px 29px),
        linear-gradient(160deg, var(--steel-hi), var(--steel) 62%),
        var(--dk-table);
}

/* Behind the start screen the author's own prototype photo is shown whole -- the one place a
   cover is a cover, and the one place this world admits a photograph. It sits under the same
   cold wash as the plate, so entering the game is a continuous surface rather than a cut. */
#start-screen {
    background:
        linear-gradient(rgba(9, 11, 15, 0.74), rgba(7, 9, 13, 0.88)),
        url('assets/cover.jpg') center / cover no-repeat fixed,
        var(--steel);
}

/* ── The board: where the play area ends ─────────────────────────────────────
   No felt fill — the table behind it IS the table. JS sizes and places it around the trick
   zones. Tinted by the trick's state: neutral, or the trump colour once one is named. */
#board {
    position: absolute;
    /* ROUND. A card table is round, and the rectangle was also answering a question it could
       not answer -- see boardBox() in game.js. The rim is where the light stops, not a drawn
       line: an ellipse with a 1px border reads as a plate someone cut out and laid down. */
    border-radius: 50%;
    /* A RECESS, not an outline. The first cut drew a bright rectangle around a mostly empty
       felt, which read as a panel someone had forgotten to fill. The field is now sunk into
       the plate: dark at the top edge where the light does not reach, one thin metal line
       along the bottom where it does, and no drawn border at all. */
    /* Two layers: the trump's pool of light in the middle of the felt (JS writes the ink into
       --felt-trump, transparent until a trump is named), over the table's own lamp. */
    background:
        radial-gradient(ellipse at 50% 46%, var(--felt-trump, transparent) 0%,
                        rgba(0, 0, 0, 0) 58%),
        radial-gradient(ellipse at 50% 32%,
                    rgba(255, 248, 228, 0.075) 0%,
                    rgba(0, 0, 0, 0.16) 62%,
                    rgba(0, 0, 0, 0.42) 100%);
    transition: background 260ms ease;
    /* A 50% radius turns a border into an elliptical RING, which is the one place on this
       table where a drawn line is the honest answer: a rim is a real edge, not a change of
       light. It is the same hairline weight every other cut surface here uses. */
    border: 1px solid rgba(185, 167, 127, 0.20);
    /* The light rakes from the upper left, as it does on every other cut surface here: the rim
       catches it along the top-left arc, the felt is sunk under its own edge, and the table
       casts onto the plate rather than floating on it. */
    box-shadow: inset 3px 4px 10px rgba(0, 0, 0, 0.55),
                inset 1px 1px 0 rgba(210, 194, 158, 0.22),
                inset 0 0 80px rgba(0, 0, 0, 0.34),
                0 10px 34px rgba(0, 0, 0, 0.45);
    pointer-events: none;
    z-index: 1;
}

/* THE TRUMP'S OWN SYMBOL, watermarked into the cloth under the trick it decides. JS writes
   the ink, the size and the glyph, because all three come from the trump and the felt's own
   radius. Faint on purpose: the trick is what is being read, and this is the answer to a
   question about it lying on the table underneath, not a second thing to look at. */
.felt-suit {
    position: absolute;
    transform: translate(-50%, -50%);
    line-height: 1;
    pointer-events: none; user-select: none;
    /* Over the felt's own light (#board is z-index 1) and under every card played on it. */
    z-index: 2;
    /* IT FADES. A trump is declared in the middle of a trick and gone when the trick is swept,
       and a mark that snapped in and out on those two frames read as a glitch rather than as
       the table answering. Slower than the card that declares it, so it arrives after the play
       it explains -- see the ambience rule: the ground moves slower than the signal on it. */
    opacity: 0;
    transition: opacity 420ms ease, color 300ms ease;
}
.felt-suit.on { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .felt-suit { transition: none; } }
/* Diamonds and spades carry the same border here that they carry on the cards -- without it
   the watermark is the one place on this table where a red suit does not say WHICH red suit.
   The ink is the table's own dark, so the mark stays a shadow in the cloth rather than a
   drawn object with a second colour in it. */
.felt-suit[data-suit="d"], .felt-suit[data-suit="s"] {
    -webkit-text-stroke: 0.045em rgba(8, 10, 6, 0.85);
    paint-order: stroke fill;
}

/* ── Cards ────────────────────────────────────────────────────────────────────
 * The BOX is ours: position, size, radius, stock and every state class. The FACE — corner
 * index, pips, court letters, the back — is printed by ../core/cards/ onto the inner .cf,
 * which is what lets one card carry COMPACT_CLASS on its own box and switch its face to the
 * pip-less compact form below ~46px. `--card-w` is written per box by sizeCard(). */
.card {
    position: absolute;
    width: var(--card-w); height: var(--card-h);
    border-radius: calc(var(--card-w) * 0.085);
    background: var(--face);
    box-shadow: 0 2px 7px rgba(0, 0, 0, 0.55), inset 0 0 0 1px var(--face-edge);
    color: var(--ink);
    user-select: none;
    touch-action: none;
    transition: filter 0.12s, box-shadow 0.14s, opacity 0.18s;
    will-change: transform;
}
.card .cf {
    position: absolute; inset: 0;
    border-radius: inherit;
    overflow: hidden;
}

/* ── Suit ink: THE RULEBOOK'S TWO, with a BORDER on the second of each pair ───
 * The author's note: "Die Spielfarben (insbesondere Pik und Kreuz) lassen sich nur schwer
 * unterscheiden" — with the standard two inks, ♠ and ♣ are one colour and one silhouette
 * apart, and in Mogler the suit is the whole rule set (what is trump, what is locked, what
 * a Mogelstich pays), so it has to be readable at a glance and at 40px.
 *
 * The answer is his own deck's, not a new palette. His prototype prints a plain two-colour
 * deck (mogler_v066_im_test.pdf p.1 fan, p.5/p.6 diagrams: red pips at #822c28 / #6f3532,
 * one dark slate at #2c2529 / #1b141b for both black suits) and separates the pairs by
 * DRAWING them differently: hearts and clubs are printed solid, diamonds and spades are
 * printed solid AND given a second outline around them, a ring of ink with the card stock
 * showing between. So the ink says red or black, and the border says which of the two — which
 * is the pair the eye actually confuses.
 *
 * That is two cues, not one: colour AND border. ♠ against ♣ no longer depends on telling two
 * silhouettes apart at 40px, and it survives protanopia and deuteranopia unchanged, because
 * neither of them touches whether a shape has a ring round it.
 *
 * A game that tried four inks instead lives in this file's history. It read well and it was
 * not his deck; the border is. So did a version that drew the pip hollow — one ring instead
 * of two — which is a different mark: it makes a spade PALER than a club rather than more
 * drawn, and on his cards the symbol itself is solid.
 *
 * SPECIFICITY: cards.css is linked AFTER this sheet and paints `.dk-face.dk-red` /
 * `.dk-face.dk-black` (0,2,0). Three class/attribute terms here make (0,3,0), so these win
 * on specificity and source order does not matter. Scope is `.card`, this game's own card
 * BOX, in a sheet only Mogler links — the shared deck is untouched, and TheGang and Tablić
 * keep the red/black they had. The ghost that flies a card is appended to <body>, not to
 * #game, so this must not be anchored on the board. */
.card > .cf[data-suit="h"], .card > .cf[data-suit="d"] { color: var(--suit-red); }
.card > .cf[data-suit="c"], .card > .cf[data-suit="s"] { color: var(--suit-black); }
/* THE BORDER, on diamonds and spades, wherever the suit is drawn: the pips, the corner suits,
   the court suit, the ace and the compact single glyph — miss one and a card changes suit
   between two sizes.

   It is a border AROUND the symbol, not a symbol drawn as a border. On the author's own cards
   (p.1 of the PDF, the fan: the A♦ and the 10♠ are the clearest) the pip is a SOLID diamond,
   then white, then a second diamond outline round it. Three rings of one shape, which is why
   it still reads at 40px where a hollow glyph reads as ink that has gone pale.

   Drawn in two passes, because a glyph is text and text takes one stroke:
     the COLLAR -- a stroke in the card stock, painted UNDER the fill (paint-order), so the
                   symbol keeps its full size and gains a white gap rather than being eaten;
     the RING   -- four chained drop-shadows, which dilate what is already drawn, COLLAR AND
                   ALL, and so land outside the gap instead of under it.

   Not text-shadow, which is the obvious tool and the wrong one: it is painted from the glyph
   behind everything, the collar never occludes it, and all it produces is a fatter symbol.
   The filter goes on the smallest box that holds ONLY suit glyphs -- the pip layer as a whole,
   each corner's suit, the ace, the court suit, the compact glyph -- so a ten costs one filter
   instead of ten. */
.card > .cf[data-suit="d"] .dk-pip,
.card > .cf[data-suit="d"] .dk-suit,
.card > .cf[data-suit="d"] .dk-ace,
.card > .cf[data-suit="d"] .dk-court-suit,
.card > .cf[data-suit="d"] .dk-glyph,
.card > .cf[data-suit="s"] .dk-pip,
.card > .cf[data-suit="s"] .dk-suit,
.card > .cf[data-suit="s"] .dk-ace,
.card > .cf[data-suit="s"] .dk-court-suit,
.card > .cf[data-suit="s"] .dk-glyph {
    -webkit-text-stroke: var(--pip-collar) var(--face);
    paint-order: stroke fill;
}
.card > .cf[data-suit="d"] .dk-pips,
.card > .cf[data-suit="d"] .dk-suit,
.card > .cf[data-suit="d"] .dk-ace,
.card > .cf[data-suit="d"] .dk-court-suit,
.card > .cf[data-suit="d"] .dk-glyph,
.card > .cf[data-suit="s"] .dk-pips,
.card > .cf[data-suit="s"] .dk-suit,
.card > .cf[data-suit="s"] .dk-ace,
.card > .cf[data-suit="s"] .dk-court-suit,
.card > .cf[data-suit="s"] .dk-glyph {
    /* currentColor is the suit's own ink -- the block above sets no colour, so the symbol
       stays solid and the ring round it is drawn in the same ink it is. */
    filter: drop-shadow(var(--pip-ring) 0 0 currentColor)
            drop-shadow(calc(var(--pip-ring) * -1) 0 0 currentColor)
            drop-shadow(0 var(--pip-ring) 0 currentColor)
            drop-shadow(0 calc(var(--pip-ring) * -1) 0 currentColor);
}
/* Both are a fraction of the card, never a pixel: the same border has to survive a full-size
   hand card and the 44px of the out-of-the-game tray. A stroke is centred on the path, so the
   gap that SHOWS is half the width set here — and both have a 1px floor, because below that
   they stop being drawn at all and the two black suits collapse back into one. */
.card {
    --pip-collar: max(2px, calc(var(--card-w) * 0.024));
    --pip-ring:   max(1px, calc(var(--card-w) * 0.011));
}
/* The ace and compact mode's single glyph are drawn three to four times the size of a pip; at
   one weight for all of them their gap and ring are hairlines. */
.card > .cf .dk-ace, .card > .cf .dk-glyph {
    --pip-collar: max(3px, calc(var(--card-w) * 0.062));
    --pip-ring:   max(1.5px, calc(var(--card-w) * 0.028));
}
/* Face down: the shared mandala back paints the inner face; the box goes transparent so the
   back's own rounded alpha corners are the card's corners. */
.card.back { background: transparent; box-shadow: 0 2px 7px rgba(0, 0, 0, 0.55); }

/* ── STOP cards: a letter on white, drawn in the same stock as the deck ──────
   Nine letters S S S T T O O P P, and a back with a double question mark, as the author's
   prototype prints them. */
.card.stop .cf {
    display: flex; align-items: center; justify-content: center;
    font-family: var(--dk-font-display);
    font-weight: 700;
    font-size: calc(var(--card-w) * 0.78);
    line-height: 1;
    color: var(--stop-ink);
}
.card.stop .cf.qq {
    font-size: calc(var(--card-w) * 0.52);
    letter-spacing: -0.06em;
    opacity: 0.85;
}
.card.stop.dim { opacity: 0.45; }

/* A stars pill on a collected trick: 3 on a face-down Farbstich, 2 on a Trumpfstich. */
.card .star-pill {
    position: absolute; right: -6px; top: -6px;
    min-width: 22px; height: 18px; padding: 0 5px;
    border-radius: 9px;
    background: rgba(12, 14, 10, 0.92);
    border: 1px solid rgba(242, 201, 76, 0.55);
    color: var(--star);
    font-family: var(--dk-font-ui); font-weight: 700; font-size: 11px; line-height: 16px;
    text-align: center; white-space: nowrap;
    z-index: 3; pointer-events: none;
}
.card.trumpf-pile .star-pill { border-color: rgba(143, 124, 78, 0.6); }

/* Interaction states on the human's own hand. Hover must NOT move the card — a translateY
   fires just before the click and makes the card jump out from under the finger. */
.card.playable { cursor: pointer; box-shadow: 0 0 0 2px var(--dk-accent), 0 3px 10px rgba(0,0,0,0.6); }
.card.playable:hover { filter: brightness(1.06); z-index: 200 !important; }
@keyframes mg-lead-in {
    from { box-shadow: 0 0 0 0 rgba(0, 0, 0, 0), 0 3px 10px rgba(0, 0, 0, 0.6); }
    to   { box-shadow: 0 0 0 2px var(--dk-accent), 0 3px 10px rgba(0, 0, 0, 0.6); }
}
.card.playable.lead-in { animation: mg-lead-in 300ms ease-out both; }
@media (prefers-reduced-motion: reduce) { .card.playable.lead-in { animation: none; } }
/* A suit showing on your own Trumpfstich. It is OFFERED like any other card -- mogeln is a
   move, and the app does not get to decide the player did not mean it -- and it is ringed in
   the alert ink so the price is on the card before the tap, not in a dialog after it. */
.card.cheat { cursor: pointer; box-shadow: 0 0 0 2px var(--alert), 0 0 16px rgba(232, 115, 90, 0.6); }
/* A hand card that is already earning a Mogelstich bonus. */
.card.bonus::after {
    content: '★';
    position: absolute; left: 50%; bottom: 4%; transform: translateX(-50%);
    font-size: calc(var(--card-w) * 0.18); color: var(--star);
    text-shadow: 0 0 6px rgba(0, 0, 0, 0.5);
    pointer-events: none;
}

/* SCORING: the hands are turned face up and the cards that actually paid a Mogelstich are
   ringed. This is the author's check -- a Trumpfstich is only a Mogelstich if its winner is
   still HOLDING cards of the suit it shows -- so the ring marks the engine's own scored list,
   not "cards of a locked suit", which is the same set only when nothing was spent. */
.card.bonus-card {
    box-shadow: 0 0 0 2px var(--star), 0 0 14px rgba(242, 201, 76, 0.55);
    z-index: 60;
}
.card.bonus-card::after {
    content: '★';
    position: absolute; left: 50%; bottom: 4%; transform: translateX(-50%);
    font-size: calc(var(--card-w) * 0.18); color: var(--star);
    text-shadow: 0 0 6px rgba(0, 0, 0, 0.5);
    pointer-events: none;
}
/* A revealed opponent hand is information, not a live control. */
.opp-card.revealed { cursor: default; }

/* The card that took the trick, and the ones it beat. */
.card.won-glow {
    box-shadow: 0 0 0 3px var(--star), 0 0 22px rgba(242, 201, 76, 0.85);
    z-index: 80;
}
.card.lost-dim { filter: brightness(0.52) saturate(0.6); }
.card.flying { z-index: 500; pointer-events: none; }

/* The shared card BACK is a red-and-gold engine-turned pattern -- itself a guilloche, which
   is why it belongs here at all, but at full saturation it was the brightest object on a dark
   plate and pulled the eye off the faces that carry the game. Toned, not replaced: this is a
   local treatment of the card BOX, and games/core/cards is untouched. */
.card.back { filter: saturate(0.55) brightness(0.72) contrast(1.05); }
.card.back.won-glow, .card.back.cheat { filter: none; }

/* ── Trick zones — one per seat, on that seat's own ray ─────────────────────── */
/* A played card is smaller than a held one -- JS writes the box (see renderTrickZones); these
   are only the fallback for a zone rendered before the first layout pass. */
.trick-zone {
    position: absolute;
    width: var(--card-w); height: var(--card-h);
    pointer-events: none;
    z-index: 20;
}

/* ── The top band: round badge · the removed tray ───────────────────────────── */
/* THE CARTOUCHE — an engraved plaque cut into the plate's lit corner. It answers the one
   question the felt cannot: what is being played for at this instant, in which ink, and which
   suit the winner will be locked out of. It re-prints whenever the trick changes character,
   which under Ruling 20 can happen twice in one trick. */
#round-badge {
    position: absolute; left: 12px;
    top: calc(var(--dk-header-h) + 8px);
    display: flex; flex-direction: column; gap: 3px;
    padding: 7px 12px 8px;
    background: linear-gradient(168deg, rgba(28, 33, 41, 0.92), rgba(15, 18, 23, 0.94));
    border: 1px solid var(--plate-line);
    border-radius: 3px;
    box-shadow: 0 3px 14px rgba(0, 0, 0, 0.55),
                inset 0 1px 0 rgba(185, 167, 127, 0.16);
    pointer-events: none; z-index: 30; white-space: nowrap;
}
#round-badge .ct-rule {
    display: flex; gap: 12px;
    font-family: var(--dk-font-display);
    font-size: 13px; letter-spacing: 2px; text-transform: uppercase;
    color: rgba(238, 233, 222, 0.5);
}
#round-badge .ct-kind {
    display: flex; align-items: baseline; gap: 7px;
    padding-top: 4px;
    border-top: 1px solid rgba(185, 167, 127, 0.18);
}
#round-badge .ct-name {
    font-family: var(--dk-font-display);
    font-size: 19px; line-height: 1; letter-spacing: 0.5px;
    color: var(--cream);
}
#round-badge .ct-worth {
    font-size: 13px; font-variant-numeric: tabular-nums;
    color: var(--star);
}
#round-badge .ct-suits { display: flex; gap: 12px; }
#round-badge .ct-mark { display: inline-flex; align-items: baseline; gap: 4px; }
#round-badge .ct-lab {
    font-size: 9.5px; letter-spacing: 1.2px; text-transform: uppercase;
    color: rgba(238, 233, 222, 0.42);
}
#round-badge .ct-glyph { font-size: 15px; line-height: 1; }
#round-badge .ct-glyph.s-h, #round-badge .ct-glyph.s-d { color: var(--log-red); }
#round-badge .ct-glyph.s-c, #round-badge .ct-glyph.s-s { color: var(--log-black); }

/* The STOP row: the draw pile, the face-down STOP pile and every flipped letter, centred
   under the header. JS sizes and places the cards; this is only the labels. */
/* THE TALON on the felt: the cards nobody has dealt yet, standing where the dealer left them.
   JS places it on the table's top-left (talonBox()); this only says how the pair sits. */
#talon {
    position: absolute;
    display: flex; align-items: flex-end;
    /* The pile labels hang BELOW their boxes on `bottom: -13px`, which puts them outside this
       element's own rect -- so everything that avoids the talon was avoiding a box 15px
       shorter than what is drawn, and a round-end record landed on the word STOP. */
    padding-bottom: 15px;
    pointer-events: none; z-index: 30;
}
.pile { position: relative; flex: none; }
.pile .card { position: absolute; left: 0; top: 0; }
.pile-label {
    position: absolute; left: 50%; transform: translateX(-50%);
    bottom: -13px;
    font-size: 9.5px; letter-spacing: 1.2px; text-transform: uppercase;
    color: rgba(243, 238, 226, 0.42); white-space: nowrap;
}
.pile-count {
    position: absolute; right: -5px; top: -5px;
    min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px;
    background: rgba(12, 14, 10, 0.92); border: 1px solid var(--plate-line);
    color: var(--cream); font-size: 10px; font-weight: 700; line-height: 14px; text-align: center;
    z-index: 3;
}
/* THE STOP CARDS, up beside the standing. Both are the round's record; the talon is material
   and stays on the cloth. Right-aligned to the same gutter as #ranking so their two right
   edges make one line down the corner.
   The face-down pile, then one column per letter of the word in the order the word spells.
   JS sizes and places every card, so all this has to say is how the row and its columns sit.
   Nothing is printed, framed or tallied any more -- these ARE the cards. */
#stop-cards {
    position: fixed;
    top: calc(var(--dk-header-h) + 8px); right: 190px;
    display: flex; align-items: flex-start; gap: 6px;
    /* The pile's label hangs below its box on `bottom: -13px`, outside this element's rect --
       so anything that avoids the row must be avoiding the label too. */
    padding-bottom: 14px;
    pointer-events: none; z-index: 200;
}
.stop-col { position: relative; flex: none; }
.stop-col .card { position: absolute; left: 0; }

/* Cards out of the game, face up: a compact overlapping strip. JS places the cards. */
#removed-tray {
    position: absolute; left: 12px;
    top: calc(var(--dk-header-h) + 6px);
    pointer-events: none; z-index: 30;
}
#removed-tray .tray-label {
    position: absolute; left: 0; bottom: -13px;
    font-size: 9.5px; letter-spacing: 1.2px; text-transform: uppercase;
    color: rgba(243, 238, 226, 0.42); white-space: nowrap;
}
.tray-card { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); }

/* ── Seat plates ─────────────────────────────────────────────────────────────
   Name, this round's stars from the tricks in front of them, the running total, and the
   suits that are LOCKED for that seat — the one fact every play reads off the table. */
.seat {
    position: absolute;
    display: flex; flex-direction: column; align-items: center; gap: 2px;
    padding: 5px 12px;
    max-width: 176px;
    background: linear-gradient(168deg, rgba(28, 33, 41, 0.90), rgba(15, 18, 23, 0.92));
    border: 1px solid var(--plate-line);
    border-radius: 3px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(185, 167, 127, 0.14);
    white-space: nowrap; pointer-events: none;
    z-index: 30;
    transition: border-color 0.2s, box-shadow 0.2s, background 0.2s, opacity 0.2s;
}
.seat.active {
    box-shadow: 0 0 15px var(--dk-accent-soft);
    border-color: var(--dk-accent);
}
.seat.out { opacity: 0.5; }
.seat-head { display: flex; align-items: baseline; gap: 7px; }
.seat-name { font-size: 13px; font-weight: 600; color: rgba(243, 238, 226, 0.92); }
.seat-out {
    /* Struck, not reddened. Red is the hearts ink here, and a seat being out is the same
       event as a letter being spent or a record being cancelled -- one mark, one meaning. */
    font-size: 9px; letter-spacing: 1px; padding: 1px 4px; border-radius: 4px;
    background: rgba(238, 233, 222, 0.10); color: rgba(238, 233, 222, 0.75);
    text-decoration: line-through; text-decoration-thickness: 1px;
}
.seat-score {
    font-family: var(--dk-font-display); font-weight: 700; font-size: 16px; line-height: 1;
    color: var(--star); font-variant-numeric: tabular-nums;
}
.seat-round {
    font-family: var(--dk-font-display); font-size: 12px; line-height: 1;
    font-variant-numeric: tabular-nums;
    color: rgba(243, 238, 226, 0.55);
}
.seat-round.gain { color: var(--good); }
.seat-locks { display: flex; gap: 5px; align-items: center; }

/* THE STAMP — a suit you inked onto your own record by winning its trick, and may not play
   again. A ruled ring, the glyph in that suit's own lifted ink, pressed slightly off square.
   The ring is the same 1px metal every rule on this table is cut at. */
.stamp {
    display: inline-flex; align-items: center; justify-content: center;
    width: 19px; height: 19px;
    border: 1px solid currentColor; border-radius: 50%;
    font-size: 11px; line-height: 1;
    opacity: 0.92;
    transform: rotate(var(--tilt, 0deg));
}
/* On the near-black plate, and on the record's cream stock where the card's own inks are
   readable. A stamp's ring is drawn in currentColor, so the ink has to be set on the stamp
   itself and the OUTLINE below has to name its colour again. */
.stamp.s-h, .stamp.s-d { color: var(--log-red); }
.stamp.s-c, .stamp.s-s { color: var(--log-black); }
.rp-badge .stamp.s-h, .rp-badge .stamp.s-d { color: var(--suit-red); }
.rp-badge .stamp.s-c, .rp-badge .stamp.s-s { color: var(--suit-black); }

/* ── The collected tricks — one card per trick, behind each seat's hand ─────
   Public information, every seat shows it: a back with 3★ for a Farbstich, the led card
   with 2★ for a Trumpfstich. Never overlapping — they shrink to fit instead. */
.card.collected { pointer-events: none; }

/* ── The stars panel — the running score, ranked, with a bar to 20 ─────────── */
#ranking {
    position: fixed;
    top: calc(var(--dk-header-h) + 8px); right: 12px;
    width: 168px; padding: 10px 12px 11px;
    background: rgba(12, 16, 9, 0.84);
    border: 1px solid var(--plate-line);
    border-radius: var(--dk-radius);
    backdrop-filter: blur(4px);
    z-index: 200; pointer-events: none;
    font-family: var(--dk-font-ui);
}
#ranking.hidden { display: none; }
.rank-head {
    display: flex; flex-direction: column; gap: 2px;
    padding-bottom: 7px; margin-bottom: 8px;
    border-bottom: 1px solid rgba(185, 167, 127, 0.18);
}
/* Two labels, two lines. Side by side in a panel this narrow they wrapped into each other and
   read as one run-on phrase. */
.rank-head .rh-title {
    font-family: var(--dk-font-display);
    font-size: 13px; letter-spacing: 1.6px; text-transform: uppercase;
    color: var(--cream);
}
/* Absolutely positioned rows at a fixed pitch, so a re-sort slides instead of snapping. */
.rank-body { position: relative; }
.rank-row {
    position: absolute; left: 0; right: 0;
    padding: 3px 0 5px 9px;
    /* A 1px rule at the table's one hairline weight. It was a 3px seat-coloured bar: a second
       colour language beside the suit inks, and a banned coloured side border. */
    border-left: 1px solid rgba(185, 167, 127, 0.22);
    border-radius: 2px;
    transition: top 420ms cubic-bezier(.4,.1,.3,1);
}
.rank-row.me { background: rgba(185, 167, 127, 0.09); }
.rank-row.me { border-left-color: var(--dk-accent); }
.rank-line { display: flex; align-items: baseline; gap: 6px; margin-bottom: 4px; }
.rank-place {
    flex: 0 0 auto; min-width: 13px;
    font-family: var(--dk-font-display); font-weight: 700; font-size: 11px;
    color: rgba(243, 238, 226, 0.40); font-variant-numeric: tabular-nums;
}
.rank-name {
    flex: 1; font-size: 12px; color: rgba(243, 238, 226, 0.82);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rank-row.me .rank-name { color: var(--dk-accent); font-weight: 600; }
.rank-val {
    flex: 0 0 auto; text-align: right;
    font-family: var(--dk-font-display); font-weight: 700; font-size: 15px;
    color: var(--star); font-variant-numeric: tabular-nums;
}
.rank-bar {
    display: block; position: relative; height: 6px; border-radius: 3px;
    background: rgba(255, 255, 255, 0.07); overflow: hidden;
}
/* The fill is ALWAYS full width and is revealed by a clip, never grown by one.
   Animating `width` would squeeze the gradient itself as the bar fills — at two stars the
   whole metal ramp is crushed into 17px and at twenty it is spread over 168,
   so the bar's colours shift with the score it is reporting. A clip leaves the ramp where it
   is and uncovers it left to right, which is what a meter does; it is also a composited
   property rather than a layout one, so nothing reflows. `--fill` is written by JS. */
.rank-bar i {
    position: absolute; inset: 0; border-radius: 3px;
    background: linear-gradient(90deg, rgba(185, 167, 127, 0.5), var(--dk-accent));
    clip-path: inset(0 calc(100% - var(--fill, 0%)) 0 0);
    transition: clip-path 420ms cubic-bezier(.4,.1,.3,1);
}

/* ── The action bar: the S decision, centred where the status line sits ─────── */
#action-bar {
    position: fixed; transform: translateX(-50%);
    display: none; gap: 10px; align-items: center;
    z-index: 130;
}
/* The lead question (Ruling 22) rides on the bar with its buttons: the bar hides the status
   line while it is up, so a prompt left there would not be read. */
#action-bar .act-label {
    align-self: center; margin-right: 10px;
    font-size: 13px; color: var(--cream); opacity: 0.85;
}
/* It WRAPS on a narrow window rather than disappearing. Hiding the question to save a row
   puts the player back in front of two unexplained names, which is the thing being fixed. */
#action-bar { flex-wrap: wrap; justify-content: center; max-width: 92vw; }
/* Its OWN row, always: inline it made the bar as wide as a sentence, which reached over a
   side seat's cards on a laptop window. A predictable two-row bar is what the status band
   can reserve for. */
#action-bar .act-label { flex-basis: 100%; text-align: center; margin: 0 0 2px; }

#action-bar.show { display: flex; }
/* THE LEAD QUESTION stands in the middle of the felt (Ruling 22 -- it is a question about the
   table, not about a card in one hand), so it needs a ground of its own. The same plate the
   seats are cut from, for the same reason: cream text on dark cloth is legible, but a sentence
   and four buttons floating on the felt read as something that fell on the table. */
#action-bar.lead {
    padding: 9px 14px 11px;
    background: linear-gradient(168deg, rgba(28, 33, 41, 0.94), rgba(15, 18, 23, 0.96));
    border: 1px solid var(--plate-line);
    border-radius: 3px;
    box-shadow: 0 6px 22px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(185, 167, 127, 0.14);
}
#action-bar.lead .act-label { font-size: 14px; opacity: 0.95; margin-bottom: 6px; }
.act-btn {
    min-height: 44px; min-width: 44px; padding: 8px 18px;
    border-radius: var(--dk-radius);
    border: 1px solid var(--dk-accent);
    background: var(--dk-accent); color: #fff;
    font-family: var(--dk-font-ui); font-weight: 700; font-size: 14px;
    cursor: pointer; touch-action: manipulation;
    white-space: nowrap;
    transition: filter 0.15s;
}
.act-btn.alt { background: rgba(12, 16, 9, 0.8); color: var(--cream); border-color: var(--dk-accent-soft); }
.act-btn:hover { filter: brightness(1.12); }

/* ── Banner ───────────────────────────────────────────────────────────────────
   Lives on document.body, not in #game, so a render pass cannot wipe it mid-flight. */
.flash-banner {
    position: fixed; top: 44%; left: 50%;
    transform: translate(-50%, -50%);
    font-family: var(--dk-font-display);
    /* display (42) down to wordmark (22); was a bespoke 44 max. */
    font-weight: 700; font-size: clamp(22px, 4.4vw, 42px); letter-spacing: 4px;
    text-align: center; white-space: nowrap;
    pointer-events: none; z-index: 1400;
    color: var(--dk-accent);
    text-shadow: 0 0 26px currentColor, 0 3px 10px rgba(0, 0, 0, 0.85);
    animation: banner-pop 1400ms ease-out forwards;
}
.flash-banner.good  { color: var(--good); }
.flash-banner.alert { color: var(--alert); }
.flash-banner.star  { color: var(--star); }
@keyframes banner-pop {
    0%   { opacity: 0; transform: translate(-50%,-50%) scale(0.6); }
    14%  { opacity: 1; transform: translate(-50%,-50%) scale(1.08); }
    30%  { transform: translate(-50%,-50%) scale(1); }
    78%  { opacity: 1; }
    100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .flash-banner { animation-duration: 800ms; } }

/* ── Round-scored overview: a badge per seat + Next round ───────────────────── */
.round-points {
    position: fixed; inset: 0; z-index: 210;
    /* Barely a scrim. It exists to separate the overlay from the table, but the table is now
       the thing being read at this moment -- hands face up beside the tricks they explain --
       so it must not dim them. The badges carry their own dark ground for contrast. */
    background: rgba(6, 10, 4, 0.14);
    animation: rp-fade 200ms ease-out;
}
@keyframes rp-fade { from { opacity: 0; } to { opacity: 1; } }
/* THE RECORD — the round's result, printed on the band's own stock. */
.rp-badge {
    position: fixed; transform: translate(-50%, -50%);
    display: flex; flex-direction: column; align-items: center; gap: 2px;
    padding: 8px 16px 7px; border-radius: 2px;
    background: linear-gradient(178deg, var(--band-paper), var(--band-paper-lo));
    border: 1px solid rgba(120, 100, 62, 0.55);
    box-shadow: 0 6px 22px rgba(0, 0, 0, 0.6),
                inset 0 1px 0 rgba(255, 255, 255, 0.55),
                inset 0 -1px 0 rgba(120, 100, 62, 0.28);
    pointer-events: none; text-align: center; line-height: 1.06;
    animation: rp-pop 260ms cubic-bezier(.2, 1.2, .4, 1) both;
    max-width: 210px;
    color: var(--band-ink);
}
.rp-badge .rp-name {
    font-size: 11px; letter-spacing: 1.4px; text-transform: uppercase;
    color: var(--band-ink-soft);
    padding-bottom: 3px; margin-bottom: 2px;
    border-bottom: 1px solid rgba(120, 100, 62, 0.4); align-self: stretch;
}
/* display-compact (32): the biggest number on the record, in ink rather than gold, because
   on this stock gold cannot be read and gold already means a star. */
.rp-badge .rp-delta {
    font-family: var(--dk-font-display);
    font-size: 32px; font-weight: 700; line-height: 1;
    font-variant-numeric: tabular-nums; color: var(--band-ink);
}
.rp-badge .rp-break {
    font-size: 10.5px; line-height: 1.35; color: var(--band-ink-soft); white-space: normal;
}
.rp-badge .rp-locks { display: flex; gap: 5px; margin-top: 2px; }
.rp-badge .rp-total {
    font-size: 12.5px; font-variant-numeric: tabular-nums; color: var(--band-ink);
    margin-top: 2px;
}
/* CAUGHT: the record is cancelled, with the same struck rule the spent letters carry. Not a
   red -- red is the hearts ink and means one thing only on this table. */
.rp-badge.caught { border-color: rgba(38, 33, 25, 0.75); }
.rp-badge.caught .rp-delta { opacity: 0.4; }
.rp-badge.caught .rp-delta::after {
    content: ''; position: absolute; left: 14%; right: 14%; top: 50%;
    height: 2px; background: var(--band-ink); transform: rotate(-4deg);
}
.rp-badge.caught .rp-delta { position: relative; display: inline-block; }
.rp-next {
    position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
    /* title (19), one documented step above the shared button (16), because this is the
       only control on the overlay and it should read as the way forward. */
    z-index: 212; font-size: 19px; padding: 12px 30px;
    width: auto;            /* .primary-btn is width:100%, which spans the viewport when fixed */
}
@keyframes rp-pop {
    from { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
    to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
    .round-points { animation: none; }
    .rp-badge { animation: none; }
}

/* ── Final standings (inside the shared .panel) ─────────────────────────────── */
.score-rows { display: flex; flex-direction: column; gap: 4px; margin-top: 4px; }
.score-row {
    display: flex; align-items: center; gap: 9px;
    padding: 7px 11px; border-radius: var(--dk-radius);
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid transparent;
    font-size: 13px;
}
.score-row.winner { background: var(--dk-accent-soft); border-color: var(--dk-accent); }
.score-place { flex: 0 0 20px; text-align: left; font-size: 11px; color: rgba(243,238,226,0.4); }
.score-name  { flex: 1; text-align: left; overflow: hidden; text-overflow: ellipsis;
               white-space: nowrap; color: rgba(243, 238, 226, 0.9); }
.score-total {
    flex: 0 0 48px; text-align: right;
    font-family: var(--dk-font-display); font-weight: 700; font-size: 17px;
    color: var(--star); font-variant-numeric: tabular-nums;
}
.panel-sub { font-size: 12px; color: rgba(243, 238, 226, 0.55); margin-bottom: 8px; }

/* ── AI panel body (the shell itself is shared) ─────────────────────────────── */
.ai-head-line { font-size: 11px; color: var(--dk-panel-dim); margin-bottom: 6px; }
.ai-note {
    margin-top: 10px; padding-top: 8px;
    border-top: 1px solid var(--dk-panel-line);
    font-size: 10.5px; line-height: 1.5; color: var(--dk-panel-dim);
}
.eval-why { font-size: 10px; color: var(--dk-panel-dim); opacity: 0.8; padding-left: 2px; }

/* ── Move log body (the shell itself is shared) ─────────────────────────────── */
.log-entry .log-card {
    display: inline-block; padding: 0 4px; border-radius: 3px;
    font-weight: 700; font-variant-numeric: tabular-nums;
    background: rgba(255, 255, 255, 0.07);
}
/* The card stock's two inks, lifted for the near-black panel — the log is the one place the
   suits are read as a LIST, so a colour that only worked on paper would fail here. */
.log-card.c-h, .log-card.c-d { color: var(--log-red); }
.log-card.c-c, .log-card.c-s { color: var(--log-black); }
.log-card.c-stop { color: var(--dk-accent); letter-spacing: 1px; }
/* THE SAME BORDER THE CARD CARRIES, everywhere the suit is drawn away from the card: the
   second of each pair is outlined. In the log the rank is set beside the glyph and must NOT
   be, so the glyph is its own span; a stamp and a cartouche mark are nothing but the glyph.
   The fill is transparent rather than a ground colour, because these four sit on three
   different grounds (plate, cream record, log panel) and an outline shows whichever it is. */
.stamp.s-d, .stamp.s-s,
#round-badge .ct-glyph.s-d, #round-badge .ct-glyph.s-s,
.log-card.c-d .lc-g, .log-card.c-s .lc-g {
    color: transparent;
    -webkit-text-stroke: 1px currentColor;
    paint-order: stroke fill;
}
/* currentColor has just been zeroed by the fill, so each one names its ink again. A stamp
   also draws its RING in currentColor, which must stay inked — hence the ring's own colour
   is set on .stamp above and only the stroke is re-pointed here. */
.stamp.s-d, .log-card.c-d .lc-g { -webkit-text-stroke-color: var(--log-red); }
.stamp.s-s, .log-card.c-s .lc-g { -webkit-text-stroke-color: var(--log-black); }
/* The ring is `1px solid currentColor` and currentColor is now transparent, so an outlined
   stamp would lose the ring that makes it a stamp. It gets its ink named too. */
.stamp.s-d { border-color: var(--log-red); }
.stamp.s-s { border-color: var(--log-black); }
.rp-badge .stamp.s-d { border-color: var(--suit-red); }
.rp-badge .stamp.s-s { border-color: var(--suit-black); }
#round-badge .ct-glyph.s-d { -webkit-text-stroke-color: var(--log-red); }
#round-badge .ct-glyph.s-s { -webkit-text-stroke-color: var(--log-black); }
.rp-badge .stamp.s-d { -webkit-text-stroke-color: var(--suit-red); }
.rp-badge .stamp.s-s { -webkit-text-stroke-color: var(--suit-black); }
.log-divider { border-top: 1px dashed var(--dk-panel-line); }

/* ── Multiplayer lobby ────────────────────────────────────────────────────────
 * Reuses the shared .overlay / .panel / .primary-btn chrome; only the pieces the lobby adds
 * are defined here. Controls are >=44px so the code input and the seat list stay usable on
 * a phone. */
.mp-panel { max-width: 420px; }
.mp-blurb { font-size: 13.5px; line-height: 1.55; color: rgba(243, 238, 226, 0.72); margin-bottom: 10px; }
.mp-seats-note { font-size: 12.5px; color: rgba(243, 238, 226, 0.5); margin-bottom: 12px; }
.mp-field { display: flex; flex-direction: column; gap: 4px; text-align: left;
            margin: 10px 0; font-size: 13px; color: rgba(243, 238, 226, 0.75); }
.mp-field input {
    min-height: var(--dk-tap); padding: 8px 12px; border-radius: var(--dk-radius);
    border: 1px solid var(--plate-line); background: rgba(10, 14, 8, 0.85);
    color: var(--cream); font: 400 16px var(--dk-font-ui);   /* 16px: iOS zooms below it */
}
.mp-field input:focus { outline: none; border-color: var(--dk-accent); }
#mp-code { letter-spacing: 0.22em; text-transform: uppercase; text-align: center; }
.mp-code {
    /* display-compact (32). The code is read aloud off the screen, so it is the largest
       thing in the lobby panel and nothing else competes with it. */
    font-family: var(--dk-font-display); font-size: 32px; font-weight: 700;
    letter-spacing: 0.28em; text-indent: 0.28em;
    color: var(--dk-accent); margin: 10px 0 6px;
    text-shadow: 0 0 20px var(--dk-accent-soft);
}
.mp-copy { margin-bottom: 12px; }
.mp-seats { width: 100%; margin: 8px 0 4px; border-collapse: collapse; }
.mp-seats td { padding: 7px 10px; font-size: 14px; text-align: left; border-bottom: 1px solid var(--plate-line); }
.mp-seats td:first-child { width: 2em; opacity: 0.5; font-variant-numeric: tabular-nums; }
.mp-seats tr.me td { color: var(--dk-accent); }
.mp-ai { opacity: 0.6; font-style: italic; }
.mp-wait { opacity: 0.55; font-style: italic; font-size: 12.5px; }
.mp-list-title { margin: 14px 0 2px; font-size: 12px; letter-spacing: 0.12em;
    text-transform: uppercase; color: rgba(243, 238, 226, 0.45); }
.mp-err { min-height: 20px; font-size: 13px; color: var(--dk-msg-alert); margin: 8px 0; }
.primary-btn.alt { background: transparent; color: var(--cream); border-color: var(--dk-accent-soft); }

/* The rules link on the start card: sized to its text, centred, a footnote under the
   launch buttons rather than a fourth button. */
.rules-link {
    align-self: center; display: inline-block; width: auto;
    padding: 7px 16px; text-align: center; white-space: nowrap;
    border: 1px solid var(--dk-accent-soft); border-radius: var(--dk-radius);
    color: var(--dk-start-ink-soft); text-decoration: none; font-size: 13px; font-weight: 600;
    min-height: 44px; line-height: 28px;
    touch-action: manipulation;
}
.rules-link:hover { background: var(--dk-accent-soft); }

/* ── Responsive ──────────────────────────────────────────────────────────────
   Below this the stars panel buries the seat plates, which already carry every score. */
@media (max-width: 780px) {
    #ranking { display: none; }
}
@media (max-height: 560px) {
    #round-badge { font-size: 11px; }
    .seat { padding: 4px 8px; }
    .seat-name { font-size: 11px; }
    .seat-score { font-size: 13px; }
    .seat-round { font-size: 11px; }
    .rp-badge .rp-delta { font-size: 24px; }        /* headline, one step down */
    .rp-badge .rp-break { display: none; }
}
@media (max-width: 640px) {
    #removed-tray .tray-label { display: none; }
}
