/* Layout and UI components come from Bootstrap 5 (dark theme); this file themes Bootstrap to the
   game's colours and styles the game itself (table, cards, zones, hands, effects). */

:root {
  --bg: #14181d;
  --panel: #1d232a;
  --panel-2: #262e37;
  --text: #e8ecf0;
  --muted: #8d99a6;
  --accent: #4fb3a1;
  --danger: #d0645c;
  --felt: #1f5c45;
  --felt-dark: #184a37;
  --grid-line-strong: rgba(255, 255, 255, 0.22);
  --card-red: #c0392b;
  --card-black: #1b1f24;
}

/* ---------- Bootstrap theme: the game's palette, teal as the primary colour ---------- */
[data-bs-theme=dark] {
  --bs-body-bg: #14181d; --bs-body-bg-rgb: 20, 24, 29;
  --bs-body-color: #e8ecf0; --bs-body-color-rgb: 232, 236, 240;
  --bs-body-font-size: .875rem;
  --bs-secondary-color: #8d99a6; --bs-secondary-color-rgb: 141, 153, 166;
  --bs-tertiary-bg: #1d232a; --bs-tertiary-bg-rgb: 29, 35, 42;
  --bs-secondary-bg: #262e37; --bs-secondary-bg-rgb: 38, 46, 55;
  --bs-border-color: #2c343d;
  --bs-primary: #4fb3a1; --bs-primary-rgb: 79, 179, 161;
  --bs-primary-text-emphasis: #8fd6c9;
  --bs-link-color: #4fb3a1; --bs-link-color-rgb: 79, 179, 161;
  --bs-link-hover-color: #8fd6c9; --bs-link-hover-color-rgb: 143, 214, 201;
  --bs-danger: #d0645c; --bs-danger-rgb: 208, 100, 92;
}
.btn-primary {
  --bs-btn-color: #0d1f1b; --bs-btn-bg: #4fb3a1; --bs-btn-border-color: #4fb3a1;
  --bs-btn-hover-color: #0d1f1b; --bs-btn-hover-bg: #65c2b1; --bs-btn-hover-border-color: #65c2b1;
  --bs-btn-active-color: #0d1f1b; --bs-btn-active-bg: #3f9a8a; --bs-btn-active-border-color: #3f9a8a;
  --bs-btn-disabled-color: #0d1f1b; --bs-btn-disabled-bg: #4fb3a1; --bs-btn-disabled-border-color: #4fb3a1;
  --bs-btn-focus-shadow-rgb: 79, 179, 161;
  font-weight: 600;
}
.btn-outline-primary {
  --bs-btn-color: #4fb3a1; --bs-btn-border-color: #4fb3a1;
  --bs-btn-hover-color: #0d1f1b; --bs-btn-hover-bg: #4fb3a1; --bs-btn-hover-border-color: #4fb3a1;
  --bs-btn-active-color: #0d1f1b; --bs-btn-active-bg: #4fb3a1; --bs-btn-active-border-color: #4fb3a1;
  --bs-btn-disabled-color: #4fb3a1; --bs-btn-disabled-border-color: #4fb3a1;
  --bs-btn-focus-shadow-rgb: 79, 179, 161;
}
.form-control:focus, .form-check-input:focus {
  border-color: #4fb3a1; box-shadow: 0 0 0 .25rem rgba(79, 179, 161, .25);
}
.form-check-input:checked { background-color: #4fb3a1; border-color: #4fb3a1; }
.form-range::-webkit-slider-thumb { background-color: #4fb3a1; }
.form-range::-moz-range-thumb { background-color: #4fb3a1; }
.form-range:focus::-webkit-slider-thumb { box-shadow: 0 0 0 1px #14181d, 0 0 0 .25rem rgba(79, 179, 161, .25); }
.form-range:focus::-moz-range-thumb { box-shadow: 0 0 0 1px #14181d, 0 0 0 .25rem rgba(79, 179, 161, .25); }

/* ---------- page layout (the rest is Bootstrap utility classes in index.html) ---------- */
html, body { height: 100%; }
body { overflow: hidden; }
/* Bootstrap's display utilities (d-flex etc.) are !important and come after its own [hidden] rule,
   so an element with both would stay visible. Loaded after Bootstrap, this makes `hidden` win. */
[hidden] { display: none !important; }
.muted { color: var(--bs-secondary-color); }
.min-h-0 { min-height: 0; }
.min-w-0 { min-width: 0; }
.justify-self-start { justify-self: start; }

.lobby-card { width: min(400px, 100%); }

/* "safe center": centred when it fits, start-aligned (never clipped on the left) when it doesn't. */
.players { min-width: 0; overflow: hidden; justify-content: safe center !important; }
/* Bootstrap has no w-md-auto: on md+ screens the player list shares the navbar row. */
@media (min-width: 768px) { .players.w-md-auto { width: auto !important; } }
.player { display: inline-flex; align-items: center; gap: 6px; min-width: 0; color: var(--bs-secondary-color); }
.player .dot { flex: none; }
.player.you { color: var(--bs-body-color); }
.player .dot { width: 8px; height: 8px; border-radius: 50%; background: #555; }
.player.online .dot { background: var(--accent); }

/* Settings: a Bootstrap card dropping down under its button (toggled by client.js). */
.settings-panel { position: absolute; top: calc(100% + 10px); right: 0; z-index: 1800; width: 300px; }

/* Right-click menu: Bootstrap dropdown styling, positioned at the pointer by client.js. */
.ctx-menu { position: fixed; z-index: 2000; min-width: 210px; }
.ctx-menu kbd { font-size: .75em; }

/* Split popover: a Bootstrap card positioned at the pointer by client.js. */
.split-popover { position: fixed; z-index: 2100; width: 270px; }

/* ---------- table ---------- */
/* The green felt fills the whole table area. The play surface (.game-table: a fixed size shared by
   both players, scaled to fit) sits centred on it with no visible edge; drops on the extra felt
   land on the nearest spot of the play surface (see viewPointAt in client.js). */
.table-wrap {
  min-height: 0; display: grid; place-items: center;
  background-color: var(--felt);
  background-image: radial-gradient(ellipse at center, var(--felt) 45%, var(--felt-dark) 100%);
}
.game-table {
  position: absolute;
  transform-origin: 0 0;
}
/* Grid preview, only while the settings menu is open. --grid-w and --grid-h are set by client.js. */
.game-table.show-grid {
  background-image:
    linear-gradient(var(--grid-line-strong) var(--grid-line-w, 1px), transparent var(--grid-line-w, 1px)),
    linear-gradient(90deg, var(--grid-line-strong) var(--grid-line-w, 1px), transparent var(--grid-line-w, 1px));
  background-size: var(--grid-w) var(--grid-h);
}
/* The divider between the two sides runs across the whole felt (the table area clips it). */
.game-table .midline {
  position: absolute; left: -100vw; right: -100vw; height: 0;
  border-top: 2px dashed rgba(255,255,255,.12);
  pointer-events: none;
}
/* Pings (hold Tab). Drawn in table coordinates above the cards. */
.ping-layer {
  position: absolute; inset: 0; width: 100%; height: 100%;
  overflow: visible; pointer-events: none; z-index: 5000;
}
.ping-layer g { transition: opacity .3s ease; filter: drop-shadow(0 2px 3px rgba(0,0,0,.5)); }
.ping-layer g.fading { opacity: 0; }
.ping-ring { transform-box: fill-box; transform-origin: center; animation: ping-pulse 1s ease-out infinite; }
@keyframes ping-pulse {
  from { transform: scale(.5); opacity: 1; }
  to { transform: scale(1.6); opacity: 0; }
}

/* Pinged objects glow in the pinging player's colour (--ping-color, set by client.js). */
.pile.pinged .rotor { animation: ping-glow .8s ease-in-out infinite alternate; }
.game-table .pile.pinged .pcard {
  outline: calc(3px * var(--inv-scale, 1)) solid var(--ping-color);
  outline-offset: calc(3px * var(--inv-scale, 1));
}
@keyframes ping-glow {
  from { filter: drop-shadow(0 0 calc(2px * var(--inv-scale, 1)) var(--ping-color)); }
  to { filter: drop-shadow(0 0 calc(14px * var(--inv-scale, 1)) var(--ping-color)); }
}
.hand-bar.pinged { box-shadow: inset 0 0 0 3px var(--ping-color); animation: hand-ping .8s ease-in-out infinite alternate; }
@keyframes hand-ping {
  from { background-color: var(--panel); }
  to { background-color: color-mix(in srgb, var(--ping-color) 22%, var(--panel)); }
}
/* A single pinged card in a hand: lifted and outlined in the pinger's colour. */
.pcard.pinged {
  outline: 3px solid var(--ping-color); outline-offset: 2px; z-index: 2;
  transform: translateY(-5px); animation: ping-glow .8s ease-in-out infinite alternate;
}
#opp-hand .pcard.pinged { outline-width: 2px; transform: translateY(3px); }
.pcard.pinged.ping-fading { animation: none; outline-color: transparent; transform: none; transition: outline-color .3s ease, transform .3s ease; }
.pile.pinged.ping-fading .rotor, .hand-bar.pinged.ping-fading { animation: none; }
.game-table .pile.pinged.ping-fading .pcard { outline-color: transparent; transition: outline-color .3s ease; }
.hand-bar.pinged.ping-fading { box-shadow: none; transition: box-shadow .3s ease; }

@media (prefers-reduced-motion: reduce) {
  .ping-ring, .pile.pinged .rotor, .hand-bar.pinged { animation: none; }
}

/* Where a dragged card will land (centred on its snapped point). */
.drop-cell {
  position: absolute; width: 80px; height: 112px;
  border: 2px dashed rgba(255,255,255,.55); border-radius: 7px;
  background: rgba(255,255,255,.06);
  pointer-events: none;
}

/* ---------- stacks ---------- */
/* A stack is a zero-size anchor at the centre of its first card. It holds a column of piles;
   each pile is also a zero-size anchor (offset down by client.js) with its card centred on it. */
.stack {
  position: absolute; width: 0; height: 0;
  transition: left .18s ease, top .18s ease;
  touch-action: none;
}
.pile { position: absolute; left: 0; width: 0; height: 0; transition: top .18s ease, left .18s ease; }
.pile.carried { visibility: hidden; } /* being dragged away as part of a line */
.stack .rotor {
  position: absolute; left: -40px; top: -56px; width: 80px; height: 112px;
  transition: transform .18s ease;
}
.stack.dragging-src { opacity: .35; }
.pile:hover .pcard { outline: 2px solid rgba(255,255,255,.6); outline-offset: 1px; }
.pile.drop-target .pcard { outline: 3px solid var(--accent); outline-offset: 2px; }
/* Held long enough to grab the whole column: lift it and outline it. */
.game-table .stack.held .pcard {
  outline: calc(3px * var(--inv-scale, 1)) solid #fff;
  outline-offset: calc(2px * var(--inv-scale, 1));
}
.stack.held .rotor { translate: 0 -6px; transition: transform .18s ease, translate .12s ease; }
/* Deck size, on the top-right corner so it stays visible when a column overlaps the card. */
.stack .count {
  position: absolute; left: 22px; top: -64px; z-index: 2;
  min-width: 22px; padding: 1px 6px; border-radius: 10px;
  background: rgba(0,0,0,.7); color: #fff; font-size: 12px; font-weight: 600; text-align: center;
  pointer-events: none;
}
/* In a row the next card covers the right side, so show the count on the visible left strip. */
.stack.line-row .count { left: -48px; top: 32px; min-width: 0; padding: 1px 5px; }

/* ---------- cards ---------- */
.pcard {
  font-size: 16px; /* card is 5em x 7em, so everything scales with font-size */
  width: 5em; height: 7em;
  border-radius: .4em;
  position: relative;
  user-select: none;
  cursor: grab;
  flex: none;
}
.pcard.face {
  background: #fbfaf6;
  color: var(--card-black);
  box-shadow: 0 1px 2px rgba(0,0,0,.4);
  border: 1px solid #d5d2c8;
}
.pcard.face.red { color: var(--card-red); }
.pcard .corner {
  position: absolute; font-weight: 700; line-height: 1; text-align: center;
  font-size: .95em; width: 1.2em;
}
.pcard .corner span { display: block; font-size: .85em; }
.pcard .corner.tl { top: .3em; left: .25em; }
.pcard .corner.br { bottom: .3em; right: .25em; transform: rotate(180deg); }
.pcard .center {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-size: 2.2em; line-height: 1;
}
.pcard .center.court { font-size: 1.6em; font-weight: 700; font-family: Georgia, serif; }
.pcard .center.court small { display: block; font-size: .6em; text-align: center; }
/* One back design per player's deck, matching their player colour, with a different pattern
   too so the decks are told apart without relying on colour: diamonds (player 1), dots (player 2). */
.pcard.back {
  background:
    repeating-linear-gradient(45deg, rgba(255,255,255,.14) 0 .25em, transparent .25em .5em),
    repeating-linear-gradient(-45deg, rgba(255,255,255,.14) 0 .25em, transparent .25em .5em),
    #1d5a73;
  border: .3em solid #f2eee6;
  box-shadow: 0 1px 2px rgba(0,0,0,.4);
}
.pcard.back.back-1 {
  background:
    radial-gradient(circle at center, rgba(255,255,255,.2) 0 .12em, transparent .14em) 0 0 / .5em .5em,
    #8a3b1c;
}

/* ---------- hands ---------- */
.hand-bar { flex: none; }
.hand-bar.opponent { min-height: 50px; }
.hand-bar.opponent .pcard { font-size: 7px; cursor: default; margin-left: -12px; }
.hand-bar.opponent .pcard:first-of-type { margin-left: 0; }
/* Your hand: compact cards (65 x 91; hover still shows the full-size preview) and only the
   padding the hover lift needs. Keep in step with HAND_CARD_W in client.js. */
.hand-bar.mine { padding-top: 2px; padding-bottom: 2px; transition: background .15s; }
.hand-bar.mine.drop-hover { background: #25343a !important; }
.hand {
  flex: 1; min-width: 0; display: flex; align-items: center; overflow: hidden;
  min-height: 99px; padding: 7px 4px 1px; /* 91px cards + room to lift 6px on hover */
}
/* --hand-step is set by client.js: horizontal distance between card left edges */
.hand .pcard {
  font-size: 13px; /* cards are 5em x 7em: 65 x 91 */
  margin-right: calc(var(--hand-step, 50px) - 5em);
  transition: transform .12s ease, margin .12s ease; touch-action: none;
}
.hand .pcard:last-of-type { margin-right: 0; }
.hand .pcard:hover { transform: translateY(-6px); z-index: 1; }
.hand .pcard.dragging-src { opacity: .3; }
.hand .insert-marker { width: 4px; height: 84px; border-radius: 2px; background: var(--accent); margin: 0 16px 0 2px; flex: none; }
.hand-empty { color: var(--muted); font-size: 13px; }

/* ---------- side panel: card preview + game log ---------- */
.zoom-panel { flex: none; width: 280px; min-height: 0; }
/* Game log: fills the rest of the side panel under the card preview and scrolls. */
.log-panel { min-height: 120px; }
.game-log { flex: 1; min-height: 0; overflow-y: auto; line-height: 1.35; }
.game-log li { display: flex; gap: 8px; padding: 3px 0; }
.game-log li + li { border-top: 1px solid rgba(255,255,255,.04); }
.game-log time { flex: none; color: var(--muted); font-size: 11px; padding-top: 1px; font-variant-numeric: tabular-nums; }
/* flex: none so the preview never shrinks: in a short side panel an empty slot would otherwise
   collapse to its placeholder text and jump back when a card appears, moving the game log. */
.zoom-slot { flex: none; width: 240px; height: 336px; display: grid; place-items: center; text-align: center; }
.zoom-slot .pcard { font-size: 48px; cursor: default; }
/* Short windows: a smaller (still fixed-size) preview so the log keeps some room. */
@media (max-height: 700px) {
  .zoom-slot { width: 180px; height: 252px; }
  .zoom-slot .pcard { font-size: 36px; }
  .log-panel { min-height: 80px; }
}
/* Fixed two-line height so the game log below doesn't jump as the caption appears/changes. */
.zoom-info { flex: none; height: 4.2em; overflow: hidden; } /* 3 lines: card name + details */

/* ---------- drag ghost ---------- */
/* Sized to the first card; later cards of a carried column hang below it (.ghost-pile). */
.ghost {
  position: fixed; top: 0; left: 0; z-index: 1000; pointer-events: none;
  width: 80px; height: 112px;
  transform-origin: 0 0;
}
.ghost .ghost-pile { position: absolute; left: 0; }
.ghost .pcard { box-shadow: 0 12px 24px rgba(0,0,0,.5); cursor: grabbing; }
.ghost .ghost-count {
  position: absolute; right: -8px; top: -8px; background: var(--accent); color: #0d1f1b;
  border-radius: 10px; padding: 1px 7px; font-weight: 700; font-size: 12px;
}
body.dragging, body.dragging * { cursor: grabbing !important; }

.draw-prompt {
  position: fixed; z-index: 1500; pointer-events: none;
  background: var(--accent); color: #0d1f1b; font-weight: 700;
  padding: 3px 9px; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,.4);
}

/* ---------- deck search ---------- */
/* Floats over the table (not the hand). While a card is dragged out it fades and lets the
   pointer through, so the card can be dropped anywhere on the table beneath it. */
.search-panel {
  position: absolute; top: 12px; left: 50%; transform: translateX(-50%); z-index: 900;
  width: min(760px, calc(100% - 24px)); max-height: calc(100% - 24px);
  transition: opacity .15s ease;
}
.search-panel.dragging-out { opacity: .12; pointer-events: none; }
.search-cards {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(88px, 1fr)); gap: 10px;
  overflow-y: auto; min-height: 0; padding: 6px 2px 2px;
}
.search-slot { display: grid; justify-items: center; gap: 3px; font-size: 11px; color: var(--muted); }
.search-slot .pcard { touch-action: none; transition: transform .12s ease; }
.search-slot .pcard:hover { transform: translateY(-4px); }
.search-slot .pcard.dragging-src { opacity: .3; }

/* Deck being searched: dashed outline and a label in the searcher's colour. */
.game-table .pile.searched .pcard {
  outline: calc(3px * var(--inv-scale, 1)) dashed var(--search-color);
  outline-offset: calc(3px * var(--inv-scale, 1));
}
.search-badge {
  display: none; position: absolute; left: 50%; top: -58px; transform: translate(-50%, -100%);
  white-space: nowrap; pointer-events: none; z-index: 3;
  font-size: calc(12px * var(--inv-scale, 1)); font-weight: 700; color: #0d1f1b;
  background: var(--search-color); border-radius: 999px;
  padding: calc(2px * var(--inv-scale, 1)) calc(9px * var(--inv-scale, 1));
}
.pile.searched .search-badge { display: block; }

/* ---------- peek (hold Alt over a face-down card) ---------- */
/* Everyone sees an eye badge under the card in the looker's colour; the face stays private. */
.game-table .pile.peeked .pcard { box-shadow: 0 0 0 calc(2px * var(--inv-scale, 1)) var(--peek-color), 0 0 calc(14px * var(--inv-scale, 1)) var(--peek-color) !important; }
.peek-badge {
  display: none; position: absolute; left: 50%; top: 60px; transform: translateX(-50%);
  align-items: center; gap: calc(4px * var(--inv-scale, 1));
  white-space: nowrap; pointer-events: none; z-index: 3;
  font-size: calc(12px * var(--inv-scale, 1)); font-weight: 700; color: #0d1f1b;
  background: var(--peek-color); border-radius: 999px;
  padding: calc(2px * var(--inv-scale, 1)) calc(9px * var(--inv-scale, 1));
}
.peek-badge svg { width: 1.25em; height: 1.25em; flex: none; }
.pile.peeked .peek-badge { display: flex; }
/* The preview is showing a private peek. */
.zoom-slot.peeking .pcard { outline: 3px dashed var(--accent); outline-offset: 5px; }

/* ---------- draw / discard zones ---------- */
/* Fixed slots under the cards (first in the table, so every stack paints over them). */
.zone {
  position: absolute; border-radius: 12px; pointer-events: none;
  border: 2px dashed rgba(255,255,255,.28); background: rgba(0,0,0,.14);
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  padding-bottom: 6px; transition: border-color .12s, background-color .12s;
}
.zone-label {
  position: absolute; bottom: 100%; margin-bottom: 5px; left: 50%; transform: translateX(-50%);
  font-size: 12px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: rgba(255,255,255,.75); white-space: nowrap;
  /* Zones sit close together: keep each label within its own zone's width. */
  max-width: 104px; overflow: hidden; text-overflow: ellipsis; text-align: center;
}
.zone-hint { font-size: 10px; color: rgba(255,255,255,.4); }
.zone-discard { border-color: rgba(255,214,140,.35); }
.zone.drop-zone { border-color: var(--accent); border-style: solid; background: rgba(79,179,161,.14); }
.zone-theirs { opacity: .7; }

/* ---------- tokens ---------- */
/* A token is a zero-size anchor at its centre (like a stack); the 26 x 26 art is centred on it
   (keep in step with TOKEN_SIZE in client.js). Tokens sit above the cards so they can mark them.
   Sizes are in table units, so tokens and their incrementor scale with the table like cards. */
.token { position: absolute; width: 0; height: 0; z-index: 3000; transition: left .18s ease, top .18s ease; touch-action: none; }
.token-face { position: absolute; left: -13px; top: -13px; width: 26px; height: 26px; cursor: grab; }
.token-face svg { width: 100%; height: 100%; display: block; filter: drop-shadow(0 1px 2px rgba(0,0,0,.45)); }
.token:hover .token-face { filter: brightness(1.08); }
.token.dragging-src { opacity: .35; }
.token.held .token-face { translate: 0 -3px; outline: calc(2px * var(--inv-scale, 1)) solid #fff; outline-offset: 1px; border-radius: 50%; }
.token.drop-target .token-face { outline: calc(3px * var(--inv-scale, 1)) solid var(--accent); outline-offset: 2px; border-radius: 50%; }
/* Count badge on the token's top-right; scales with the token but never below ~8px on screen. */
.token-count {
  display: none; position: absolute; left: 6px; top: -21px; z-index: 1; pointer-events: none;
  font-size: max(9px, calc(8px * var(--inv-scale, 1))); line-height: 1.3; padding: 0 .35em; min-width: 1.4em;
  font-weight: 700; text-align: center; color: #fff; background: rgba(0,0,0,.78); border-radius: 999px;
}
/* Incrementor: "−" and "+" as two rectangles joined into one bar, a little wider than the token,
   directly under it. Transparent with a gray outline. */
.token-btns {
  display: none; position: absolute; left: -16px; top: 15px; width: 32px; height: 12px;
  border: 1px solid #9aa4ae; border-radius: 3px; overflow: hidden; background: transparent;
}
.token.stacked .token-count { display: block; }
.token.stacked .token-btns { display: flex; }
.token-btn {
  flex: 1; height: 100%; padding: 0; margin: 0; border: 0; border-radius: 0;
  font-size: 11px; line-height: 1; font-weight: 700;
  color: #e8ecf0; background: transparent;
  display: grid; place-items: center; cursor: pointer;
}
.token-btn + .token-btn { border-left: 1px solid #9aa4ae; }
.token-btn:hover { background: rgba(255,255,255,.15); }
/* Token strips on a card's edges: − [token count] +, a thin bar just outside the edge. The .strips
   layer covers the card and turns with it (client.js); side strips run top to bottom, + at the top. */
.stack .strips { position: absolute; left: -40px; top: -56px; width: 80px; height: 112px; transition: transform .18s ease; pointer-events: none; }
.stack.held .strips { translate: 0 -6px; }
/* Styled like a table token and its incrementor: a transparent bar with a gray outline (the − and
   + halves), the token art in the middle at full token size, and the same dark count badge. */
.token-strip {
  position: absolute; display: flex; align-items: stretch; pointer-events: auto; touch-action: none;
  border: 1px solid #9aa4ae; border-radius: 3px; background: transparent;
}
.token-strip.side { flex-direction: column-reverse; }
.token-strip .token-btn { flex: 1; height: auto; }
.token-strip .token-btn + .token-btn { border-left: 0; }
.token-strip:not(.side) .token-btn:first-child { border-right: 1px solid #9aa4ae; }
.token-strip:not(.side) .token-btn:last-child { border-left: 1px solid #9aa4ae; }
.token-strip.side .token-btn:first-child { border-top: 1px solid #9aa4ae; }
.token-strip.side .token-btn:last-child { border-bottom: 1px solid #9aa4ae; }
/* The picture fits inside the strip (20px thick, so 16px with its outline and a little room). */
.strip-token { position: relative; flex: none; width: 22px; display: grid; place-items: center; cursor: grab; overflow: visible; }
.token-strip.side .strip-token { width: auto; height: 22px; }
.strip-token svg { width: 16px; height: 16px; display: block; filter: drop-shadow(0 1px 2px rgba(0,0,0,.45)); }
.token-strip:hover .strip-token svg { filter: drop-shadow(0 1px 2px rgba(0,0,0,.45)) brightness(1.08); }
.strip-count {
  display: none; position: absolute; left: calc(50% + 3px); top: calc(50% - 17px); z-index: 1; pointer-events: none;
  font-size: max(9px, calc(8px * var(--inv-scale, 1))); line-height: 1.3; padding: 0 .35em; min-width: 1.4em;
  font-weight: 700; text-align: center; color: #fff; background: rgba(0,0,0,.78); border-radius: 999px;
}
.token-strip.stacked .strip-count { display: block; }
.token-strip.held .strip-token svg { translate: 0 -1px; outline: calc(2px * var(--inv-scale, 1)) solid #fff; outline-offset: 1px; border-radius: 50%; }
.token-strip.dragging-src { opacity: .35; }
.token-strip.drop-target .strip-token svg { outline: calc(3px * var(--inv-scale, 1)) solid var(--accent); outline-offset: 2px; border-radius: 50%; }
.strip-preview { position: absolute; border: 2px dashed var(--accent); border-radius: 4px; background: rgba(255,255,255,.08); }

/* Drop outline for a token: round, token-sized. */
.drop-cell.token-spot { width: 26px; height: 26px; border-radius: 50%; border-width: 1.5px; }
/* Drag ghost and supply tray */
.token-ghost { width: 26px; height: 26px; }
.token-ghost .token-face { left: 0; top: 0; }
.token-tray { padding: 4px 0 4px 12px; border-left: 1px solid var(--bs-border-color); }
/* A table token dragged over the tray will be removed. */
.token-tray { align-self: stretch; margin: 4px 0; border-radius: 6px; transition: background .12s, box-shadow .12s; }
.token-tray.drop-remove { background: rgba(220, 53, 69, .18); box-shadow: inset 0 0 0 2px rgba(220, 53, 69, .7); }
.token-supply { width: 26px; height: 26px; cursor: grab; touch-action: none; }
.token-supply svg { width: 100%; height: 100%; display: block; filter: drop-shadow(0 2px 3px rgba(0,0,0,.45)); transition: transform .12s ease; }
.token-supply:hover svg { transform: translateY(-3px) scale(1.06); }
.token-zoom { width: 160px; height: 160px; }
/* Tokens snapped to the previewed card: one chip per type, over the bottom of the zoomed card. */
.zoom-slot { position: relative; }
.zoom-tokens {
  position: absolute; left: 8px; right: 8px; bottom: 10px;
  display: flex; flex-wrap: wrap; justify-content: center; gap: 4px; pointer-events: none;
}
.zoom-token {
  display: flex; align-items: center; gap: 5px; padding: 2px 9px 2px 3px; border-radius: 999px;
  background: rgba(0,0,0,.78); color: #fff; font-size: 13px; font-weight: 600; line-height: 1;
}
.zoom-token svg { width: 24px; height: 24px; filter: drop-shadow(0 1px 2px rgba(0,0,0,.45)); }
/* Objects menu: small icons beside each entry. */
.objects-menu { min-width: 240px; }
.objects-menu .dropdown-item:disabled .menu-token { opacity: .4; }
.menu-token { width: 20px; height: 20px; flex: none; }
.menu-token svg { width: 100%; height: 100%; display: block; }
.menu-deck {
  width: 14px; height: 20px; flex: none; border-radius: 2px; border: 2px solid #f2eee6;
  background: repeating-linear-gradient(45deg, rgba(255,255,255,.2) 0 2px, transparent 2px 4px), #1d5a73;
}
.token-zoom svg { width: 100%; height: 100%; filter: drop-shadow(0 6px 12px rgba(0,0,0,.5)); }

/* ---------- effects ---------- */
/* Shuffle riffle: two card backs laid over the deck, animated by client.js. */
.fx-riffle {
  position: absolute; left: -40px; top: -56px; width: 80px; height: 112px;
  pointer-events: none; z-index: 4;
}
.fx-riffle .pcard { position: absolute; inset: 0; box-shadow: 0 4px 10px rgba(0,0,0,.35); }
/* Discard flight: a face-up card moving from its old spot to the discard pile. */
.fx-fly {
  position: fixed; top: 0; left: 0; z-index: 1200; pointer-events: none;
  transform-origin: 0 0;
}
.fx-fly .pcard { box-shadow: 0 12px 24px rgba(0,0,0,.5); }
/* A drawn card waits invisibly in the hand until its flying card lands. */
.pcard.incoming { visibility: hidden; }

/* ---------- touch screens & small screens ---------- */
/* The table handles its own touch gestures (pinch-zoom, pan: client.js), so the browser mustn't
   scroll or zoom the page from it, or pop up callouts / text selection on a long press. */
.table-wrap { touch-action: none; }
.table-wrap, .hand-bar { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }

/* Zoom buttons: touch screens only, bottom-left of the table (the zones are on the right). */
.zoom-controls { display: none !important; position: absolute; left: 8px; bottom: 8px; z-index: 800; opacity: .85; }
.touch-ui .zoom-controls { display: inline-flex !important; }
.zoom-controls .btn { width: 34px; height: 34px; padding: 0; font-size: 18px; line-height: 1; }

/* Portrait phones: the table is wide, so suggest turning sideways. */
.rotate-hint { display: none; }
@media (orientation: portrait) and (max-width: 575.98px) {
  .touch-ui .rotate-hint {
    display: block; position: absolute; left: 50%; top: 8px; transform: translateX(-50%); z-index: 800;
    padding: 4px 10px; border-radius: 999px; background: rgba(0,0,0,.45); color: #cfd6dd;
    white-space: nowrap; pointer-events: none;
  }
}

/* Action sheet: the tapped thing's preview and every action for it, docked at the bottom. */
.action-sheet {
  position: fixed; left: 50%; bottom: calc(8px + env(safe-area-inset-bottom)); transform: translateX(-50%);
  z-index: 1500; width: min(640px, calc(100% - 16px)); max-height: 70vh; overflow-y: auto;
  background: rgba(29, 35, 42, .97);
}
.sheet-preview { position: relative; flex: none; width: 90px; }
.sheet-preview .pcard { font-size: 18px; cursor: default; } /* 90 x 126 */
.sheet-preview.peeking .pcard { outline: 2px dashed var(--accent); outline-offset: 3px; }
.sheet-preview .token-zoom { width: 90px; height: 90px; }
.sheet-preview .zoom-tokens { left: 2px; right: 2px; bottom: 4px; gap: 2px; }
.sheet-preview .zoom-token { font-size: 10px; padding: 1px 5px 1px 1px; gap: 2px; }
.sheet-preview .zoom-token svg { width: 14px; height: 14px; }
.sheet-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.sheet-actions .btn { min-height: 36px; padding-left: .7rem; padding-right: .7rem; }
.sheet-draw { width: auto; }
.sheet-draw .input-group-text { min-width: 2.4em; justify-content: center; }
/* Short (landscape) phones: a smaller preview so the sheet covers less of the table. */
@media (max-height: 500px) {
  .action-sheet { max-height: 62vh; }
  .sheet-preview { width: 60px; }
  .sheet-preview .pcard { font-size: 12px; }
  .sheet-preview .token-zoom { width: 60px; height: 60px; }
  .sheet-preview .zoom-tokens { display: none; }
  .sheet-actions .btn { min-height: 32px; }
}

/* What the sheet is for: outlined in the accent colour. */
.game-table .pile.sheet-selected .pcard {
  outline: calc(3px * var(--inv-scale, 1)) solid var(--accent); outline-offset: calc(2px * var(--inv-scale, 1));
}
.token.sheet-selected .token-face { outline: calc(3px * var(--inv-scale, 1)) solid var(--accent); outline-offset: 2px; border-radius: 50%; }
.token-strip.sheet-selected { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent); }
.hand .pcard.sheet-selected, .search-slot .pcard.sheet-selected { outline: 3px solid var(--accent); outline-offset: 1px; transform: translateY(-6px); z-index: 1; }

/* Below lg the side panel (preview + game log) is a drawer, opened with the navbar's Log button.
   The preview itself is the action sheet's job there. */
@media (max-width: 991.98px) {
  .zoom-panel {
    position: fixed; top: 0; right: 0; bottom: 0; z-index: 1700; width: min(320px, 88vw);
    transform: translateX(100%); visibility: hidden; transition: transform .2s ease, visibility .2s;
    box-shadow: -8px 0 24px rgba(0,0,0,.45);
  }
  .zoom-panel.open { transform: none; visibility: visible; }
  .zoom-panel .zoom-slot, .zoom-panel .zoom-info { display: none; }
  .log-panel { border-top: 0 !important; }
}

/* Short screens (phones sideways): slimmer bars and a smaller hand, so the table gets the room. */
@media (max-height: 500px) {
  #game > .navbar { padding-top: 3px !important; padding-bottom: 3px !important; }
  .hand-bar.opponent { min-height: 30px; padding-top: 2px !important; padding-bottom: 2px !important; }
  .hand-bar.opponent .pcard { font-size: 5px; }
  .hand { min-height: 76px; padding-top: 5px; }
  .hand .pcard { font-size: 10px; } /* 50 x 70 */
  .hand .insert-marker { height: 64px; }
}

/* ---------- custom tokens ---------- */
.objects-tokens { max-height: 50vh; overflow-y: auto; }
.token-delete { font-size: 18px; line-height: 1; text-decoration: none; opacity: .7; }
.token-delete:hover { opacity: 1; }
.cube-swatches { display: flex; flex-wrap: wrap; gap: 6px; }
.cube-swatch {
  width: 42px; height: 42px; padding: 4px; border-radius: 8px; background: transparent;
  border: 2px solid transparent; cursor: pointer;
}
.cube-swatch svg { width: 100%; height: 100%; display: block; filter: drop-shadow(0 1px 2px rgba(0,0,0,.45)); }
.cube-swatch:hover { background: rgba(255,255,255,.06); }
.cube-swatch.active { border-color: var(--accent); background: rgba(79,179,161,.12); }
.custom-token-preview { width: 64px; height: 64px; flex: none; border-radius: 10px; background: var(--felt); display: grid; place-items: center; }
.custom-token-preview:empty::after { content: '?'; color: rgba(255,255,255,.4); font-size: 22px; }
.custom-token-preview svg { width: 44px; height: 44px; filter: drop-shadow(0 1px 2px rgba(0,0,0,.45)); }

/* ---------- horizontal cards ---------- */
/* Shown sideways in the preview (side panel and touch action sheet), scaled to keep its width. */
.zoom-slot .pcard.horizontal, .sheet-preview .pcard.horizontal { transform: rotate(90deg) scale(.714); }
.sheet-preview:has(.pcard.horizontal) { height: 90px; display: grid; place-items: center; }
/* A small ⟷ mark, so a horizontal card is recognisable in a hand or search too. */
.pcard.horizontal::after {
  content: '⟷'; position: absolute; top: .15em; left: 50%; transform: translateX(-50%);
  font-size: .8em; line-height: 1; color: #6b7682; font-weight: 700;
}

/* ---------- custom decks ---------- */
/* Cards drawn from a contact sheet (background-image/size/position set inline by client.js). */
.pcard.custom.face, .pcard.custom.back {
  background-color: #2a2f35; background-repeat: no-repeat; border: 1px solid rgba(0,0,0,.35);
}
.menu-deck.custom { background-repeat: no-repeat; border-width: 1px; }
.sheet-canvas { max-width: 100%; border-radius: 4px; background: #000; }
.sheet-samples .pcard { font-size: 16px; cursor: default; }
.deck-sheet[hidden] { display: none !important; }
/* Landscape card cells in the deck dialog's samples and orientation grid. */
.pcard.custom.landscape { width: 7em; height: 5em; }
/* Orientation check: every card on the sheet; horizontal ones outlined and marked ⟷. */
.orient-grid { display: flex; flex-wrap: wrap; gap: 5px; max-height: 190px; overflow-y: auto; padding: 3px; }
.orient-cell {
  position: relative; width: 48px; height: 48px; padding: 0; display: grid; place-items: center;
  border: 1px solid transparent; border-radius: 6px; background: transparent; cursor: pointer;
}
.orient-cell:hover { background: rgba(255,255,255,.06); }
.orient-card {
  width: 30px; height: 42px; border: 1px solid rgba(255,255,255,.25); border-radius: 3px;
  background-color: #2a2f35; background-repeat: no-repeat; transition: transform .15s ease;
}
.orient-card.landscape { width: 42px; height: 30px; }
.orient-cell.on { border-color: var(--accent); background: rgba(79,179,161,.1); }
.orient-cell.on::after {
  content: '⟷'; position: absolute; left: 50%; bottom: -2px; transform: translateX(-50%);
  font-size: 10px; line-height: 1; padding: 1px 3px; border-radius: 4px; background: var(--accent); color: #0d1f1b; font-weight: 700;
}
/* Custom cards show their orientation in the art itself; no ⟷ badge over it. */
.pcard.custom.horizontal::after { display: none; }
/* The import dialog's sheet is built from the CSV's images: no file picker of its own. */
.deck-sheet.composed input[type=file] { display: none; }

/* ---------- drop zones (file pickers in the token, card, deck and import dialogs) ---------- */
.drop-zone {
  display: grid; gap: 4px; padding: 18px 12px; text-align: center; cursor: pointer;
  border: 2px dashed rgba(255,255,255,.25); border-radius: 10px; color: var(--bs-secondary-color);
  transition: border-color .12s, background .12s, color .12s;
}
.drop-zone:hover, .drop-zone:focus-visible { border-color: rgba(255,255,255,.45); color: var(--bs-body-color); outline: none; }
.drop-zone.over { border-color: var(--accent); background: rgba(79,179,161,.12); color: var(--bs-body-color); }
.drop-zone.has-file { padding: 10px 12px; border-style: solid; border-color: rgba(79,179,161,.55); }
.drop-zone.has-file .drop-zone-prompt { font-size: .8em; }
.drop-zone-file { color: var(--bs-body-color); word-break: break-all; }
.drop-zone-file:empty { display: none; }

/* ---------- the zoom (hover + Z), beside the pointer ---------- */
.cursor-zoom {
  position: fixed; left: 0; top: 0; z-index: 2500; pointer-events: none;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  filter: drop-shadow(0 12px 28px rgba(0,0,0,.6));
}
.cz-card { position: relative; width: 5em; height: 7em; font-size: 48px; }
.cz-card.landscape { width: 7em; height: 5em; }
.cz-card .pcard { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); cursor: default; font-size: 1em; }
.cz-card.landscape .pcard { transform: translate(-50%, -50%) rotate(90deg); }
.cz-card.peeking .pcard { outline: .06em dashed var(--accent); outline-offset: .1em; }
.cz-card.token { width: 3.5em; height: 3.5em; }
.cz-card .token-zoom { width: 100%; height: 100%; }
.cz-info {
  max-width: 100%; padding: 3px 10px; border-radius: 8px; text-align: center;
  background: rgba(20, 24, 29, .92); color: var(--bs-body-color); font-size: 12px; line-height: 1.35;
}
.cz-info:empty { display: none; }
/* Horizontal cards that enter play turned the other way (270°) are previewed that way too. */
.sheet-preview .pcard.horizontal.h270 { transform: rotate(-90deg) scale(.714); }
.cz-card.landscape.h270 .pcard { transform: translate(-50%, -50%) rotate(-90deg); }
