/* Dominoes web client — felt-green theme matching the Android app. */

/* v9.2 design tokens. Palettes.apply() sets the live values on documentElement at boot
   and on every palette/texture change; these :root defaults only cover the moment before
   palettes.js runs. --accent is the FIXED accent (v8.4 discipline: outside the palette,
   never user-configurable). Chrome/board consumers migrate onto these tokens in the
   v9.2 restyle stages; --felt is retained (= --felt-mid) for pre-9.2 consumers. */
:root {
  --accent: #e8a33d;
  --felt-hi: #4d8151;
  --felt-mid: #1B5E20;
  --felt-lo: #103813;
  --board-tex: none;

  /* Dark ink for text/glyphs sitting ON an accent fill (primary buttons, chips). */
  --accent-ink: #3a2410;
  /* Chrome surfaces (seat pills, boneyard pill, header buttons, banners). These are
     PALETTE-DERIVED at runtime by Palettes.chromeShades(): the text on them follows
     --game-text (the v8.4 contract), so a fixed dark panel would make the scoreboard
     unreadable under a light palette. The values here are only the pre-JS fallback. */
  --panel: #0f3412;
  --panel-deep: #0a2309;
  --hairline: rgba(255,255,255,0.18);
  --wood-hi: #5a3620;
  --wood-lo: #39220f;
  --rail-hi: #7d4c27;
  --rail-lo: #4b2c15;
  /* v9.3: dialog-sheet deep stop, derived (railLo x0.60) by Palettes.chromeShades()
     so it follows the walnut/oak flip; this is only the pre-JS fallback. */
  --rail-deep: #2d1a0d;
  --ink-soft: rgba(255,255,255,0.45);
  --ink-faint: rgba(255,255,255,0.09);
  --wood-ink: #fdf3e2;

  --font-display: 'Bricolage Grotesque Variable', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-ui: 'DM Sans Variable', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: 'DM Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* v9.2 typography. Bundled locally (public/fonts/, latin subsets, ~104KB total) rather
   than fetched from a web font CDN at runtime: the client has no build step and must
   work offline, and the Android side has to bundle regardless - so both clients ship
   the same faces from their own package. font-display:swap keeps text visible while the
   face loads; every stack falls back to system-ui so nothing depends on the download. */
@font-face {
  font-family: 'Bricolage Grotesque Variable';
  font-style: normal; font-display: swap; font-weight: 200 800;
  src: url(/fonts/bricolage-grotesque-latin-wght-normal.woff2) format('woff2');
}
@font-face {
  font-family: 'DM Sans Variable';
  font-style: normal; font-display: swap; font-weight: 100 1000;
  src: url(/fonts/dm-sans-latin-wght-normal.woff2) format('woff2');
}
@font-face {
  font-family: 'DM Mono';
  font-style: normal; font-display: swap; font-weight: 500;
  src: url(/fonts/dm-mono-latin-500-normal.woff2) format('woff2');
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
/* v9.2: ALL screens share the game's chrome - wood surround, palette-derived panels
   and pills. This intentionally supersedes the v8.4 rule that only the game surface
   recolored (landing/lobby/settings used to stay fixed felt green); the whole app now
   follows the player's chosen palette, defaulting to Felt Green + Smooth. */
body {
  background: linear-gradient(180deg, var(--wood-hi, #5a3620), var(--wood-lo, #39220f));
  background-attachment: fixed;
  color: var(--wood-ink, #fdf3e2);
  font-family: var(--font-ui);
  overscroll-behavior: none;
}

/* v9.2: OUTSIDE the game surface the chrome sits on wood, so buttons/cards derive from
   the WOOD tones, never the board colour - the board colour is the felt, and letting it
   tint the menus made every screen green. The marigold accent carries primary actions. */
button {
  background: var(--rail-hi, #7d4c27);
  color: var(--wood-ink, #fdf3e2);
  border: 1px solid rgba(255,225,190,0.22);
  border-radius: 999px;
  padding: 11px 16px;
  min-height: 44px;
  font-family: var(--font-ui);
  font-size: 15px;
  font-weight: 500;
  cursor: pointer;
}
button:hover { filter: brightness(1.14); }
button:disabled { opacity: 0.45; cursor: default; }
button.small { padding: 6px 10px; font-size: 14px; min-width: 44px; min-height: 44px; }
button.primary { background: var(--accent); color: var(--accent-ink); font-weight: 700; border-color: var(--accent); }
button.primary:hover { filter: brightness(1.1); }

/* v9.2 round icon button (header chrome). 44x44 minimum tap target per the design kit. */
button.icon-btn {
  width: 44px; height: 44px; min-width: 44px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px; background: var(--panel-deep); border: 1px solid var(--hairline);
  color: var(--game-text, #f7ece0); font-size: 17px; line-height: 1; flex-shrink: 0;
}
/* v9.2: the header glyphs are inline SVG using currentColor, so they follow the palette
   text color and carry no plate of their own (the pill IS the button). */
button.icon-btn svg { display: block; width: 21px; height: 21px; }
button.icon-btn:hover { background: rgba(20,12,6,0.72); }

input[type="text"], input[type="number"], input[type="password"] {
  width: 100%;
  /* v9.3: translucent black, not fixed dark brown - self-adapts so the dark wood-ink
     of the pale-oak variant still reads (the old fill was ~2:1 against oak ink). */
  background: rgba(0,0,0,0.18);
  border: 1px solid rgba(255,225,190,0.22);
  border-radius: 14px;
  color: var(--wood-ink, #fdf3e2);
  padding: 11px 12px;
  font-family: var(--font-ui);
  font-size: 15px;
  min-height: 44px; /* v9.3: tap-target floor */
}
label.row {
  display: flex; align-items: center; justify-content: space-between;
  padding: 8px 0; gap: 12px; font-size: 15px;
  min-height: 44px; /* v9.3: toggle rows meet the tap floor (mock drew 40px; 44 wins) */
}
.field-label { display: block; margin: 12px 0 4px; font-size: 14px; opacity: 0.9; }

/* v9.2: native checkboxes/radios default to the browser's own accent (blue on
   Chrome, and the platform accent on Android) - pin them to our fixed accent so no
   stray purple/blue appears anywhere in the UI. */
input[type="checkbox"], input[type="radio"] {
  accent-color: var(--accent);
  width: 20px; height: 20px; cursor: pointer;
}

/* v8.4 color customization controls (Settings) */
select.color-select {
  width: 100%; background: rgba(0,0,0,0.18); color: var(--wood-ink, #fdf3e2);
  border: 1px solid rgba(255,225,190,0.22); border-radius: 14px;
  padding: 11px 12px; font-family: var(--font-ui); font-size: 15px; min-height: 44px;
}
select.color-select option { color: #000; }
.color-custom { margin-top: 8px; }
.color-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: 6px 0; font-size: 15px; min-height: 44px; /* v9.3: row meets the tap floor */
}
.color-row input[type="color"] {
  width: 52px; height: 32px; padding: 0; background: none; cursor: pointer;
  border: 1px solid rgba(255,255,255,0.5); border-radius: 6px;
}

.radio-row { display: flex; gap: 8px; margin: 4px 0 8px; }
.radio-row label {
  flex: 1; text-align: center; padding: 9px 4px;
  border: 1px solid rgba(255,225,190,0.22); border-radius: 999px; cursor: pointer;
  background: var(--rail-hi, #7d4c27); color: var(--wood-ink, #fdf3e2); font-size: 15px; min-height: 44px;
}
.radio-row input { display: none; }
.radio-row input:checked + span { font-weight: bold; }
.radio-row label:has(input:checked) { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

/* Screens */
.screen { display: none; height: 100%; }
.screen.active { display: flex; flex-direction: column; }
.center-card {
  margin: auto; width: min(420px, 92vw); padding: 24px;
}
.center-card h1 {
  text-align: center; font-family: var(--font-display); font-weight: 800;
  font-size: 30px; letter-spacing: 0.10em; margin-bottom: 24px;
}
.center-card h2 {
  font-family: var(--font-display); font-weight: 700; font-size: 24px; margin-bottom: 16px;
}

.corner-btn { position: absolute; top: 10px; z-index: 5; }
.corner-btn.left { left: 10px; }
.corner-btn.right { right: 10px; }

/* v8.1: the host's shareable 4-digit room code, front and center in the lobby */
#lobbyCodeBanner {
  text-align: center; margin: 8px 0 12px; padding: 12px 10px;
  border: 1px solid var(--accent); border-radius: 14px; background: rgba(30,17,7,0.55);
  font-size: 17px;
}
#lobbyCodeBanner .room-code {
  color: var(--accent); font-family: var(--font-mono); font-weight: 500; font-size: 30px; letter-spacing: 6px;
}
#lobbyCodeBanner small { opacity: 0.85; font-size: 13px; }
/* v9.4 (Update 1): the banner is a tappable invite control - it must read as
   interactive (cursor, focus ring) and visibly respond to a press. Transform +
   opacity only, so the press never forces layout or repaints the backdrop. */
#lobbyCodeBanner[role="button"] {
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition: transform 90ms ease-out, opacity 90ms ease-out;
}
#lobbyCodeBanner[role="button"]:active {
  transform: scale(0.97);
  opacity: 0.85;
}
#lobbyCodeBanner[role="button"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* ── v9.4 (Update 2): launch splash ──────────────────────────────────────────
   Animates transform + opacity ONLY (compositor-friendly; never width/height/
   top/left). Backdrop fade overlaps the wordmark fade so the screen underneath
   resolves without a hard cut. Timings mirrored on Android (LandingActivity
   splash constants) - change both together. */
#splash {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(180deg, var(--wood-hi, #5a3620), var(--wood-lo, #39220f));
  animation: splashBackdropOut 400ms ease-out 350ms forwards;
}
#splashMark {
  font-size: 26px; font-weight: 600; letter-spacing: 0.24em; text-transform: uppercase;
  color: var(--accent); text-align: center; padding: 0 20px;
  animation: splashMarkOut 750ms ease-out forwards;
}
@keyframes splashMarkOut {
  from { transform: scale(1); opacity: 1; }
  to   { transform: scale(1.6); opacity: 0; }
}
@keyframes splashBackdropOut {
  from { opacity: 1; }
  to   { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  /* Show the wordmark briefly, then cut - no motion at all. */
  #splashMark { animation: none; }
  #splash { animation: splashBackdropOut 1ms linear 250ms forwards; }
}

/* Game screen */
/* v8.4: the felt and in-game text follow the local color palette (CSS vars set by
   palettes.js apply()). Only the game surface recolors - landing/lobby stay felt green.
   Defaults reproduce the original look. The gold accent (drop slots, spinner, score
   flash, pause banner) is deliberately kept fixed. */
/* v9.2: the screen is the WOOD surround; the palette's felt moved into #boardInset.
   Wood tones are palette-derived (Palettes.chromeShades) so in-game text painted on
   this surface stays legible under dark-text palettes - see palettes.js WOOD. */
#screen-game {
  position: relative;
  background: linear-gradient(180deg, var(--wood-hi, #5a3620), var(--wood-lo, #39220f));
}
#screen-game #turnText,
#screen-game #scoreboard,
#screen-game #bonesLabel,
#screen-game #bonesPlayers .bones-entry { color: var(--game-text, #fff); }
/* v9.2 header: icon button / title stack / icon button. The title stack keeps the
   turn text (it is the accessible, always-readable statement of whose turn it is);
   the design's seat-pill glow is an additional cue, not a replacement. */
.game-header {
  display: flex; align-items: center; gap: 8px; padding: 8px 12px;
}
.title-stack { flex: 1; min-width: 0; text-align: center; }
.game-title {
  font-family: var(--font-display); font-weight: 800; font-size: 15px;
  letter-spacing: 0.14em; color: var(--game-text, #fff);
  white-space: nowrap; overflow: hidden;
}
#turnText {
  font-weight: 500; font-size: 12.5px; margin-top: 1px;
  /* 0.85, not lower: this must stay above the 4.5:1 small-text contrast floor under
     every palette - it is the accessible statement of whose turn it is. */
  color: var(--game-text, #fff); opacity: 0.92;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* v8: one evenly-distributed column per player (name over score), 2-4 players.
   v9.2: each column is a seat pill; the current player's pill gets the accent border
   and glow from the design kit. */
#scoreboard { display: flex; gap: 6px; padding: 0 12px 6px; font-size: 14px; }
/* position: relative anchors the v8.5 +N score-delta indicator. */
#scoreboard .score-col {
  flex: 1 1 0; text-align: center; min-width: 0; position: relative;
  padding: 6px 8px; border-radius: 999px;
  background: var(--panel); border: 1px solid var(--hairline);
}
#scoreboard .score-col.seat-turn {
  border-color: rgba(232,163,61,0.45);
  box-shadow: 0 0 18px rgba(232,163,61,0.30);
}
#scoreboard .score-name {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 700; font-size: 11.5px;
}
#scoreboard .score-value {
  margin-top: 1px; display: flex; justify-content: center; align-items: center;
  min-height: 18px; border-radius: 4px; min-width: 0; overflow: hidden;
  font-family: var(--font-mono); font-weight: 500; font-size: 14px; white-space: nowrap;
}
/* v8.3 item 4: flash a score yellow then fade back (~1s) when that player scores.
   Background (not text color) so it reads the same in numbers and tally modes.
   v8.5 item 1: app.js sets a NEGATIVE animation-delay equal to the elapsed time of
   the scoring event, so a re-render mid-flash resumes the animation at the right
   offset instead of restarting it from 0%. Keep 1s in sync with FLASH_MS in app.js. */
#scoreboard .score-value.score-flash { animation: scoreFlash 1s ease-out; }
@keyframes scoreFlash {
  0%   { background: var(--accent); color: var(--accent-ink); }
  100% { background: transparent; color: inherit; }
}
/* v8.5 item 2: the points scored, floating up and fading beneath the score. Absolutely
   positioned (and pointer-events: none) so it never reflows the column or the board -
   in portrait it rises under the score; in landscape the media query below re-anchors
   it to the end of the sidebar row so it can't reach the Remaining Bones bar. Uses the
   fixed accent to match the flash, and like the flash it is palette-independent. */
#scoreboard .score-delta {
  position: absolute; left: 0; right: 0; top: 100%;
  font-size: 13px; font-weight: 700; color: var(--accent);
  font-family: var(--font-mono);
  pointer-events: none; white-space: nowrap;
  /* forwards: without a fill mode the element would snap back to opacity 1 and stay
     visible once the animation ended (nothing guarantees another render to remove it). */
  animation: scoreDelta 1s ease-out forwards;
}
@keyframes scoreDelta {
  0%   { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-14px); }
}

#pauseBanner {
  text-align: center; background: var(--panel-deep);
  border: 1px solid var(--accent); color: var(--accent); margin: 0 12px 6px;
  padding: 8px 12px; border-radius: 14px; font-size: 13px; font-weight: 500;
}
#pauseBanner button { margin-left: 10px; }

/* v9.2 felt inset (design kit 1c): three-shade radial felt + inner shadow, with the
   texture overlay clipped to the same radius. overflow:hidden does the clipping, so the
   scroller and its tiles can never paint outside the rounded corners. */
#boardInset {
  flex: 1; min-height: 0; position: relative;
  margin: 0 12px 8px; border-radius: 20px; overflow: hidden;
  background: radial-gradient(ellipse 80% 55% at 50% 45%,
    var(--felt-hi, #4d8151), var(--felt-mid, #1B5E20) 60%, var(--felt-lo, #103813));
  box-shadow: inset 0 0 60px rgba(0,0,0,0.55), 0 2px 0 rgba(255,225,190,0.14);
  border: 1px solid rgba(0,0,0,0.35);
}
/* Texture layer: above the felt, below the scroller (DOM order decides - both are
   positioned at auto z-index). Never intercepts pointer events. */
#boardTexture {
  position: absolute; inset: 0; pointer-events: none;
  background-image: var(--board-tex, none);
}
#boardScroll {
  position: absolute; inset: 0; overflow: auto;
  background: transparent;
  touch-action: pan-x pan-y;
}
#boardCanvas { position: relative; }
#boardCanvas .tile { pointer-events: none; } /* board tiles must not block drop targeting */
#boardCanvas .tile svg { filter: drop-shadow(0 5px 11px rgba(0,0,0,0.45)); }
/* Boneyard pill rides the inset's bottom-right corner, as in the design. Display only -
   pointer-events off so it can never swallow a drop near the board edge. */
#boneyardPill {
  position: absolute; right: 12px; bottom: 10px; z-index: 2; pointer-events: none;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border-radius: 999px;
  background: var(--panel); border: 1px solid var(--hairline);
}
.slot {
  display: flex; align-items: center; justify-content: center;
  border: 1.5px dashed var(--ink-soft, rgba(255,240,220,0.45)); border-radius: 7px;
  background: var(--ink-faint, rgba(255,255,255,0.06));
  color: var(--ink-soft, rgba(255,240,220,0.55));
  font-family: var(--font-mono); font-size: 14px; pointer-events: none;
}
.slot.active {
  border-color: var(--accent); border-style: dashed;
  background: rgba(232,163,61,0.22); color: var(--accent);
}

.info-line { padding: 3px 10px; font-size: 13px; }
/* v8 bottom bar: per-player remaining tile counts (+ optional zoom buttons).
   v8.1 item 3: entries distributed evenly across the full width; long names
   ellipsize rather than colliding. */
/* v9.2: the bar now also carries the boneyard pill, so it WRAPS instead of squeezing.
   #bonesPlayers has a flex-basis wide enough to hold 2-4 "Name(n)" entries legibly; when
   the label + pill leave less than that on the first line it drops to its own full-width
   row rather than ellipsising every name down to a letter. */
#bonesLine { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; row-gap: 3px; padding: 3px 12px; }
/* NOTE: the two accent colors below are overridden on the game screen by the
   #screen-game rules above (which bind these to --game-text, the v8.4 contract).
   They only apply if the bar is ever rendered outside the game screen. */
#bonesLabel {
  color: var(--accent); min-height: 20px; white-space: nowrap; flex-shrink: 0;
  display: inline-flex; align-items: center;
  font-weight: 600; font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase;
  opacity: 0.9;
}
#bonesPlayers { display: flex; flex: 1 1 200px; min-width: 0; gap: 4px; }
#bonesPlayers .bones-entry {
  color: var(--accent); flex: 1 1 auto; min-width: 0; text-align: center;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--font-mono); font-size: 11.5px;
}
/* v9.2: the boneyard pill itself is positioned on the board inset (above); these are
   just its text styles. The count is server-pushed state - the client renders
   g.boneyard.length, it never computes it. */
#boneyardPill .by-label {
  font-weight: 600; font-size: 9.5px; letter-spacing: 0.12em;
  color: var(--game-text, #fff); opacity: 0.6;
}
#boneyardPill .by-count {
  font-family: var(--font-mono); font-weight: 500; font-size: 14px;
  color: var(--game-text, #fff);
}
#zoomControls { display: inline-flex; gap: 6px; margin-left: 8px; flex-shrink: 0; }

/* v9.2 rack tray (design kit 1c): a wood bottom sheet carrying the rack label, the
   turn-status pill, the knock action, and the hand itself. */
#rackTray {
  position: relative; flex-shrink: 0;
  padding: 8px 0 6px; border-radius: 20px 20px 0 0;
  background: linear-gradient(180deg, var(--rail-hi, #7d4c27), var(--rail-lo, #4b2c15));
  border-top: 2px solid rgba(255,225,190,0.22);
  box-shadow: 0 -14px 34px rgba(0,0,0,0.5);
}
#rackTray::before {
  content: ''; position: absolute; inset: 0; border-radius: 20px 20px 0 0;
  background: repeating-linear-gradient(90deg, rgba(0,0,0,0.14) 0 3px, rgba(255,255,255,0.035) 3px 8px);
  pointer-events: none;
}
.rack-head {
  position: relative; z-index: 1;
  display: flex; align-items: center; gap: 8px; padding: 0 12px 6px;
}
#rackLabel {
  flex: 1; min-width: 0; font-size: 10px; font-weight: 600; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--game-text, #fff); opacity: 0.93;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#rackStatus {
  flex-shrink: 0; padding: 5px 11px; border-radius: 999px;
  background: rgba(255,255,255,0.10); color: var(--game-text, #fff); opacity: 0.95;
  font-size: 10.5px; font-weight: 600; white-space: nowrap;
}
/* Your turn: the design's solid amber pill. Fixed accent, so it reads under any palette. */
#rackStatus.your-turn { background: var(--accent); color: var(--accent-ink); opacity: 1; }
#handScroll {
  position: relative; z-index: 1;
  overflow-x: auto; overflow-y: hidden; white-space: nowrap;
  padding: 10px 8px 4px; touch-action: pan-x;
}
/* v8.3 item 1: user-select/touch-callout off on the tiles only (not the whole page)
   so a swipe/hold picks the domino instead of starting a browser text selection. */
#handScroll .hand-tile {
  display: inline-block; position: relative; margin: 0 3px; touch-action: none;
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
  transition: transform 90ms ease-out;
}
#handScroll .hand-tile svg { filter: drop-shadow(0 6px 13px rgba(0,0,0,0.5)); }
#handScroll .hand-tile.dim { opacity: 0.55; }
/* v8.3 item 2: instant acknowledgment on pointerdown, before the hold-to-lift fires.
   v9.2 gives it the design's treatment: lifted 10px with a 3px accent ring. Purely
   visual - the hold-to-lift arbitration and drag hit-testing are unchanged. */
#handScroll .hand-tile.picked {
  outline: 3px solid var(--accent); outline-offset: -3px; border-radius: 7px;
  transform: translateY(-10px);
}
/* The lift must NOT move the hit box out from under the pointer. A mouse pointer gets
   no implicit capture, and capture is only taken when hold-to-lift fires at 150ms - so
   if the press landed in the bottom 10px, translating the tile up would drop the
   subsequent pointerup on #handScroll instead of the tile, stranding the drag state
   machine (and later replaying it as a spurious play). This strip re-covers exactly the
   vacated 10px, so the original press point stays inside the element. */
#handScroll .hand-tile.picked::after {
  content: ''; position: absolute; left: 0; right: 0; top: 100%; height: 10px;
}
/* Knock is the design's action pill, living in the rack head. */
#knockButton {
  flex-shrink: 0; margin: 0; padding: 8px 14px; min-height: 44px;
  border-radius: 999px; font-size: 13px; font-weight: 700;
  background: var(--accent); color: var(--accent-ink); border-color: var(--accent);
}
#knockButton:hover { filter: brightness(1.1); background: var(--accent); }

/* Connection-lost banner. Stays red (an error state, not the accent) but adopts the
   design's pill shape and hairline so it reads as part of the same chrome family. */
#banner {
  display: none; position: absolute; top: 56px; left: 50%; transform: translateX(-50%);
  background: #B71C1C; padding: 7px 16px; border-radius: 999px; z-index: 6;
  font-size: 12.5px; font-weight: 500; border: 1px solid rgba(255,238,214,0.20);
  box-shadow: 0 6px 18px rgba(0,0,0,0.4);
}

/* Ghost tile while dragging */
#dragGhost svg { filter: drop-shadow(0 8px 16px rgba(0,0,0,0.55)); }
#dragGhost {
  position: fixed; z-index: 50; pointer-events: none; opacity: 0.9;
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
}

/* Modals / dialogs.
   v9.3 restyle (design 2a/2b vocabulary): ONE shared shell that every dialog inherits
   (settings, log, leaderboard, saves, confirms, dev). Surface stays token-driven so the
   walnut/oak flip survives; the old hardcoded #2e1a0b bottom stop is now --rail-deep.
   Translucent BLACK overlays (rgba(0,0,0,.x)) are the one non-token color family used
   for section cards: they darken whichever wood is under them, so they self-adapt. */
.overlay {
  position: fixed; inset: 0; background: rgba(0,0,0,0.55);
  display: flex; align-items: center; justify-content: center; z-index: 20;
}
.dialog {
  background: linear-gradient(180deg, var(--rail-lo, #4b2c15), var(--rail-deep, #2e1a0b));
  border: 1px solid rgba(255,225,190,0.22); border-radius: 20px;
  width: min(440px, 92vw); max-height: 86vh; overflow-y: auto; padding: 20px;
  color: var(--wood-ink, #fdf3e2);
  box-shadow: 0 18px 44px rgba(0,0,0,0.45);
}
.dialog h3 {
  margin-bottom: 10px; font-family: var(--font-display); font-weight: 800; font-size: 19px;
}
.dialog .msg { white-space: pre-wrap; font-size: 15px; margin-bottom: 12px; }
.dialog .btns { display: flex; gap: 9px; margin-top: 16px; flex-wrap: wrap; }
.dialog .btns button { flex: 1 1 auto; }
/* Primary commit CTA: the design's red gradient. Like --accent this is a FIXED chrome
   constant (never palette-driven): #fff7ea on this red measures >4.5:1 and the gradient
   reads on both walnut and oak. Amber stays reserved for selected/turn/flag states. */
.dialog .btns button.primary, button.cta {
  background: linear-gradient(180deg, #c9542f, #a63f22);
  color: #fff7ea; font-weight: 700; border-color: rgba(255,225,190,0.22);
}
.dialog .btns button.primary:hover, button.cta:hover { filter: brightness(1.1); }

/* v9.3 bottom-sheet variant of the SAME shell (design 2b): the Game Log uses it.
   The column layout (list scrolls INSIDE; header/footer pinned) applies everywhere;
   the bottom-anchored sheet dress applies only on portrait phones - landscape and
   desktop windows fall back to the centered dialog per the plan. */
.dialog.sheet {
  display: flex; flex-direction: column; overflow-y: hidden; max-height: 80vh;
}
.dialog.sheet .sheet-head, .dialog.sheet .seg, .dialog.sheet .log-foot { flex-shrink: 0; }
@media (max-width: 600px) and (orientation: portrait) {
  .overlay.sheet { align-items: flex-end; }
  .dialog.sheet {
    width: min(560px, 100vw);
    border-radius: 26px 26px 0 0; border-bottom: none;
    padding: 12px 16px 20px;
    box-shadow: 0 -18px 44px rgba(0,0,0,0.6);
  }
  /* Decorative grabber (close is the 44px X and the scrim tap - no drag gesture).
     currentColor (= wood-ink) at low opacity so it survives the walnut/oak flip. */
  .dialog.sheet::before {
    content: ''; display: block; width: 38px; height: 4px; border-radius: 9px;
    background: currentColor; opacity: 0.25; margin: 0 auto 12px; flex-shrink: 0;
  }
}

/* v9.3 card-stack section (design 2a): a darkening overlay card on the dialog wood,
   with an eyebrow label. Section order + bucket labels live in app.js openSettings(). */
.set-card {
  border-radius: 16px; background: rgba(0,0,0,0.16);
  border: 1px solid rgba(255,225,190,0.14);
  padding: 12px 14px; margin-top: 10px;
}
.set-head {
  display: block; font-size: 10px; font-weight: 600; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--accent); margin-bottom: 4px;
}
.set-sub { font-size: 11.5px; opacity: 0.6; margin: -2px 0 6px; }
/* v9.4.1 house-rule copy. .rule-head titles the lobby's house-rule block (accent,
   but NOT uppercase-transformed like .set-head: the string must stay byte-identical
   to Android's, and Android has no text-transform). .rule-hint is the per-toggle
   explanation and deliberately reuses .set-sub's metrics so both read as the same
   species of secondary text. Both inherit the themed foreground, so they follow the
   wood variants with no per-palette rule. */
.rule-head {
  display: block; margin: 4px 0 2px; font-size: 13px; font-weight: 700;
  letter-spacing: 0.04em; color: var(--accent);
}
.rule-hint { display: block; font-size: 11.5px; opacity: 0.6; margin: -2px 0 8px; }
/* Read-only room rules (non-host): real value rows, not a text blob. */
.ro-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 7px 0; font-size: 15px; min-height: 32px;
}
.ro-row .ro-val { font-family: var(--font-mono); font-weight: 500; opacity: 0.95; }
.ro-chip {
  display: inline-block; padding: 1px 8px; border: 1px solid var(--accent);
  border-radius: 999px; font-size: 10px; font-weight: 700; color: var(--accent);
  letter-spacing: 0.08em; text-transform: uppercase; white-space: nowrap;
}
/* Full-width stacked action button (replaces the old mkBtn inline styles). */
.btn-block { width: 100%; margin-top: 8px; }

/* v9.3 44px round X (log close). Same family as .icon-btn but on dialog wood. */
button.dialog-x {
  width: 44px; height: 44px; min-width: 44px; min-height: 44px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px; background: rgba(0,0,0,0.16);
  border: 1px solid rgba(255,225,190,0.16);
  color: var(--wood-ink, #fdf3e2); font-size: 17px; line-height: 1; flex-shrink: 0;
}
/* Sheet header row: title + trailing controls (hand chip in a future pass, close X). */
.sheet-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.sheet-head h3 { flex: 1; margin-bottom: 0; }
/* Segmented filter (design 2b): amber-solid active segment. Segments meet the 44px
   tap floor (the mock drew 38px; the tap-target rule wins). */
.seg {
  display: flex; padding: 3px; border-radius: 12px;
  background: rgba(0,0,0,0.18); border: 1px solid rgba(255,225,190,0.10);
  margin-bottom: 8px;
}
.seg button {
  flex: 1; min-height: 44px; padding: 8px 4px; border: none; border-radius: 10px;
  background: transparent; color: var(--wood-ink, #fdf3e2);
  font-size: 12.5px; font-weight: 600; opacity: 0.8;
}
.seg button.active {
  background: var(--accent); color: var(--accent-ink); font-weight: 700; opacity: 1;
}
.log-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  border-top: 1px solid rgba(255,225,190,0.10); margin-top: 10px; padding-top: 10px;
}
.log-foot label.row { padding: 0; flex: 0 0 auto; font-size: 13.5px; }
/* v8.1 How to Play screen (embedded instructions) */
#screen-howto .howto-scroll { flex: 1; overflow-y: auto; }
.howto-content { max-width: 640px; margin: 0 auto; padding: 20px 18px; line-height: 1.5; font-size: 15px; }
.howto-content h1 {
  font-family: var(--font-display); font-weight: 800; font-size: 26px; margin: 8px 0 14px;
}
.howto-content h2 { font-size: 21px; margin: 22px 0 10px; color: var(--accent); font-family: var(--font-display); font-weight: 700; }
.howto-content h3 { font-size: 17px; margin: 16px 0 6px; }
.howto-content p { margin: 8px 0; }
.howto-content ul, .howto-content ol { margin: 8px 0; padding-left: 24px; }
.howto-content li { margin: 4px 0; }
.howto-table { width: 100%; border-collapse: collapse; margin: 10px 0; }
.howto-table th, .howto-table td {
  border: 1px solid rgba(255,255,255,0.35); padding: 6px 10px; text-align: left;
}
.howto-table th { color: var(--accent); }

/* v8.1 leaderboard */
.dialog .leaderboard-list { font-size: 15px; min-height: 40px; }
.leaderboard-table { width: 100%; border-collapse: collapse; }
.leaderboard-table th {
  text-align: left; color: var(--accent); font-size: 13px; padding: 4px 6px;
  border-bottom: 1px solid rgba(255,255,255,0.35);
}
.leaderboard-table td {
  padding: 6px; border-bottom: 1px solid rgba(255,255,255,0.15);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 160px;
}

/* v9.3 log list (design 2b): timestamp column + entry text, oldest first with
   auto-scroll (the ordering flip is a v9.3 decision superseding the v8.1 newest-first
   rule). Entry ink moved from --game-text to --wood-ink: the rows sit on the wood
   sheet, and wood-ink is the token designed for wood surfaces (the brief endorses
   this move); the badge stays on the fixed accent. */
.dialog .log-list {
  font-size: 12.5px; line-height: 1.45; color: var(--wood-ink, #fdf3e2);
  overflow-y: auto; flex: 1; min-height: 60px;
}
.log-row {
  display: flex; gap: 10px; align-items: baseline;
  padding: 6px 8px; border-radius: 8px;
}
.log-row + .log-row { margin-top: 1px; }
.log-ts {
  flex: 0 0 36px; font-family: var(--font-mono); font-weight: 500;
  font-size: 10.5px; opacity: 0.5; white-space: nowrap;
}
.log-tx { flex: 1; min-width: 0; }
/* v8.5.1 open-end flag, v9.3 dress: the design's amber row fill + the accent-outlined
   badge pill. --accent is fixed - deliberately NOT palette-driven, same rule as the
   spinner border and the score flash, so the flag stays legible under every preset. */
.log-row.open-end { font-weight: bold; background: rgba(232,163,61,0.10); }
.dialog .log-list .open-end-tag {
  display: inline-block; margin-left: 8px; padding: 1px 7px;
  border: 1px solid var(--accent); border-radius: 999px;
  font-size: 11px; font-weight: 700; color: var(--accent); white-space: nowrap;
}

#toast {
  display: none; position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%);
  background: rgba(0,0,0,0.85); color: #fff; padding: 9px 18px; border-radius: 999px;
  font-size: 13.5px; font-weight: 500; z-index: 60; max-width: 88vw;
  border: 1px solid rgba(255,255,255,0.18); box-shadow: 0 8px 22px rgba(0,0,0,0.45);
}

/* Lobby */
#lobbyPlayers { font-size: 15px; line-height: 1.7; padding: 8px 0 14px; }
#lobbyStatus { color: var(--accent); padding: 6px 0; }
.divider { border: none; border-top: 1px solid rgba(255,255,255,0.4); margin: 14px 0; }

/* Save slots (Settings) */
.slot-list { margin-top: 6px; }
.slot-row {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.18);
}
.slot-name { font-size: 15px; }
.slot-meta { font-size: 12px; opacity: 0.75; margin-top: 2px; }
.slot-btns { display: flex; gap: 4px; flex-shrink: 0; }

/* Version footer (Settings) - display only, low contrast; taps unlock dev settings */
.version-line {
  text-align: center; font-size: 12px; opacity: 0.45;
  margin-top: 14px; user-select: none; cursor: default;
}

/* Developer Settings environment banner */
.dev-banner {
  text-align: center; font-weight: bold; font-size: 16px; letter-spacing: 1px;
  padding: 10px; border-radius: 6px; margin-bottom: 12px;
}
.dev-banner.ops { background: rgba(30,17,7,0.55); border: 2px solid var(--accent); color: var(--accent); }
.dev-banner.dev { background: #B71C1C; border: 2px solid #fff; color: #fff; }

/* ── v9.3 Games Hub landing (design 3a) ─────────────────────────────────────────
   The app root. Dominoes card is live; Spades and Bid Whist are darkened ~50% with a
   "Coming Soon" caption (future updates). Wood page background comes from body. */
#screen-hub { position: relative; overflow-y: auto; }
.hub-wrap {
  margin: auto; width: min(440px, 92vw); padding: 24px 0 70px;
  display: flex; flex-direction: column;
}
.hub-top { display: flex; align-items: center; justify-content: flex-end; min-height: 44px; }
.hub-eyebrow {
  font-size: 10px; font-weight: 600; letter-spacing: 0.24em; text-transform: uppercase;
  color: var(--accent); margin: 26px 0 6px;
}
.hub-title {
  font-family: var(--font-display); font-weight: 800; font-size: 34px;
  line-height: 1.1; letter-spacing: -0.01em; margin-bottom: 26px;
}
.game-card {
  display: flex; align-items: center; gap: 14px; width: 100%; text-align: left;
  padding: 14px; border-radius: 18px; margin-top: 12px; min-height: 44px;
  background: rgba(0,0,0,0.16); border: 1px solid rgba(255,225,190,0.12);
  color: var(--wood-ink, #fdf3e2); font-family: var(--font-ui); cursor: pointer;
}
.game-card.live {
  border-color: rgba(232,163,61,0.40);
  box-shadow: 0 0 22px rgba(232,163,61,0.12);
}
.game-card.live:hover { filter: brightness(1.08); }
/* Coming Soon: darkened ~50%, not interactive. */
.game-card.soon { opacity: 0.5; cursor: default; }
.game-card.soon:hover { filter: none; }
.game-art {
  width: 92px; height: 92px; flex-shrink: 0; border-radius: 14px;
  display: flex; align-items: center; justify-content: center;
  font-size: 34px; color: rgba(255,253,246,0.9);
  box-shadow: inset 0 0 26px rgba(0,0,0,0.4);
}
/* The live dominoes art previews the player's own felt; the future games use the
   design's fixed felt swatches (they have no palette of their own yet). */
.game-art.dominoes {
  background: radial-gradient(circle at 40% 30%, var(--felt-hi, #4d8151), var(--felt-lo, #103813));
  /* The glyph sits on the player's own felt - use the palette text color (the v8.4
     text-on-felt pairing) so it never vanishes on a light felt like "Soft". */
  color: var(--game-text, rgba(255,253,246,0.9));
}
.game-art.spades { background: radial-gradient(circle at 40% 30%, #478266, #1e4133); }
.game-art.bidwhist { background: radial-gradient(circle at 40% 30%, #a95d43, #5f2c1f); }
.game-card .gc-body { flex: 1; min-width: 0; }
.game-card .gc-name {
  font-family: var(--font-display); font-weight: 800; font-size: 18px;
  line-height: 1.1; letter-spacing: 0.04em;
}
.game-card .gc-sub { font-size: 11.5px; line-height: 1.4; opacity: 0.6; margin-top: 3px; }
.game-card .gc-chevron { font-size: 20px; opacity: 0.55; flex-shrink: 0; }
.soon-chip {
  display: inline-block; margin-top: 6px; padding: 2px 9px;
  border: 1px solid rgba(255,225,190,0.35); border-radius: 999px;
  font-size: 10px; font-weight: 700; letter-spacing: 0.10em; text-transform: uppercase;
}
/* v9.5: flex-wrap added so .hub-legal can be forced onto its OWN line via
   flex-basis:100%. Without wrap it becomes a row item and sits inline to the right
   of the status text, clipping at ~320px once the string reads
   "Server connected - v9.5". .hub-wrap already reserves 70px of bottom padding and
   one row uses ~13px, so a second row (~13px + gap) fits with room to spare. */
.hub-foot {
  position: absolute; left: 0; right: 0; bottom: 18px;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 7px;
  font-size: 11px; font-weight: 500;
  /* v9.5.x: opacity moved OFF this container so the .hub-legal links render at
     full contrast (WCAG AA). The prior 0.6 is re-applied to #hubStatusText and
     folded into .status-dot's own opacity so the status area looks unchanged. */
}
/* v9.5: privacy/terms links. These are the FIRST <a> elements in the app - style.css
   had no anchor rules at all before this, so an unstyled link would render browser
   default #0000EE / purple-visited on dark wood. Inherit the wood-ink colour and set
   :visited explicitly so a visited link does not turn purple. */
.hub-legal { flex-basis: 100%; text-align: center; margin-top: 5px; }
/* v9.5.x: was inheriting .hub-foot's 0.6; now carries it directly so the status
   text stays dimmed while the legal links (no opacity of their own) go full-contrast. */
#hubStatusText { opacity: 0.6; }
.hub-legal a,
.hub-legal a:visited {
  color: inherit; text-decoration: underline; text-underline-offset: 2px;
  text-decoration-thickness: 1px; text-decoration-color: currentColor;
}
.hub-legal a:hover, .hub-legal a:focus-visible { text-decoration-thickness: 2px; }
.status-dot {
  width: 7px; height: 7px; border-radius: 999px; flex-shrink: 0;
  /* Off state rides currentColor (wood-ink) so it reads on both wood variants. */
  background: transparent; border: 1.5px solid currentColor; opacity: 0.36;
}
.status-dot.on {
  background: #7fae62; border-color: #7fae62; opacity: 0.6;
  box-shadow: 0 0 8px rgba(127,174,98,0.6);
}

/* ── v9.3 dominoes entry screen header (design 3b): back + centered title stack.
   The screen body keeps the existing landing controls (.center-card). */
.page-head {
  display: flex; align-items: center; gap: 8px; padding: 10px 12px 0;
  flex-shrink: 0;
}
.page-head .title-stack { flex: 1; min-width: 0; text-align: center; }
.page-head .page-title {
  font-family: var(--font-display); font-weight: 800; font-size: 15px;
  letter-spacing: 0.14em;
}
.page-head .page-sub { font-size: 11.5px; opacity: 0.6; margin-top: 1px; }
.page-head .head-spacer { width: 44px; flex-shrink: 0; }

/* v9.3 leaderboard restyle: DM Mono numerics, accent header, soft hairlines. */
.leaderboard-table th {
  font-family: var(--font-ui); font-weight: 600; font-size: 11px;
  letter-spacing: 0.12em; text-transform: uppercase;
  border-bottom: 1px solid rgba(255,225,190,0.22);
}
.leaderboard-table td { border-bottom: 1px solid rgba(255,225,190,0.10); font-size: 14px; }
.leaderboard-table td:first-child, .leaderboard-table td:nth-child(3) {
  font-family: var(--font-mono); font-weight: 500;
}

/* Landscape phones / short landscape windows: sidebar layout so the board always
   stays visible and dominant (mirrors the Android v4 landscape fix). The flat game
   children are re-placed with CSS grid: controls in a left column, board + hand right. */
@media (orientation: landscape) and (max-height: 540px) {
  #screen-game.active {
    display: grid;
    grid-template-columns: minmax(200px, 250px) 1fr;
    grid-template-rows: auto auto auto 1fr auto auto;
    grid-template-areas:
      "header  board"
      "scores  board"
      "pause   board"
      "filler  board"
      "bones   board"
      "rack    rack";
    column-gap: 8px;
  }
  .game-header { grid-area: header; }
  /* Sidebar is too narrow for 2-4 columns: stack the player columns vertically */
  #scoreboard { grid-area: scores; flex-direction: column; }
  #scoreboard .score-col { display: flex; align-items: center; gap: 8px; text-align: left; flex: none; }
  #scoreboard .score-value { margin-top: 0; }
  /* v8.5 item 2: the player columns are stacked ROWS here, so the portrait anchoring
     (below the score) would land on top of the next player. Re-anchor to the free space
     at the end of the row and shorten the rise so the indicator stays inside its own
     row - no collision with the neighbouring seat or the Remaining Bones bar. */
  #scoreboard .score-delta {
    left: auto; right: 2px; top: 0; bottom: 0;
    display: flex; align-items: center;
    animation-name: scoreDeltaSide;
  }
  @keyframes scoreDeltaSide {
    0%   { opacity: 1; transform: translateY(0); }
    100% { opacity: 0; transform: translateY(-8px); }
  }
  #pauseBanner { grid-area: pause; }
  /* v9.2: knock moved into the rack head, so the sidebar's knock row is retired and the
     rack tray spans the full width at the bottom. */
  #boardInset { grid-area: board; margin: 0 8px 6px 0; }
  /* Give the board more of a short landscape window: trim the tray's own padding. */
  #rackTray { grid-area: rack; border-radius: 16px 16px 0 0; padding: 4px 0 2px; }
  .rack-head { padding: 0 12px 3px; }
  /* v9.2 stage 7 landscape tuning: 40px-wide rack tiles (height:auto keeps the SVG
     aspect) hand ~13px back to the board. Top padding stays 10px - it is the
     headroom for the picked-tile lift under overflow-y:hidden; trimming it clips
     the lifted tile. */
  #handScroll { padding: 10px 8px 2px; }
  #handScroll .hand-tile svg { width: 40px; height: auto; }
  #bonesLine { grid-area: bones; }
  /* v9.2: the sidebar is ~200-250px, so the header title and the bones bar (which now
     also carries the boneyard pill) have to give. Shrink the wordmark and let the bar
     wrap rather than letting either overflow the column. */
  .game-header { padding: 6px 8px; gap: 6px; }
  .game-title { font-size: 12px; letter-spacing: 0.10em; }
  #turnText { font-size: 10px; }
  #boneyardPill { padding: 4px 9px; }
  /* The sidebar is far too narrow for 4 entries on one line (they would ellipsize to a
     letter or two), and the grid has spare vertical room - so let them flow onto a
     second line instead. */
  #bonesPlayers { flex-basis: 150px; flex-wrap: wrap; row-gap: 2px; }
  #bonesPlayers .bones-entry { flex: 1 1 auto; min-width: 68px; font-size: 11px; }
}

/* ── v9.3.1: dominoes entry screen (design 3b) ─────────────────────────────────
   Resume banner, "How do you want to play?" option cards, and the inline join
   card. All chrome tokens: fixed accent for the banner/selection, rail/wood for
   card fills - palette-agnostic like the rest of the menu chrome. */
.resume-banner {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px; margin-bottom: 14px; border-radius: 16px;
  background: linear-gradient(180deg, rgba(232,163,61,0.22), rgba(232,163,61,0.12));
  border: 1px solid rgba(232,163,61,0.5);
}
.rb-art { font-size: 20px; }
.rb-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.rb-title { font-weight: 700; font-size: 13px; }
.rb-sub { font-size: 11px; opacity: 0.65; }
.rb-btn {
  padding: 10px 15px; min-height: 44px; border-radius: 11px; border: none;
  background: var(--accent); color: var(--accent-ink);
  font-weight: 700; font-size: 12.5px; white-space: nowrap; cursor: pointer;
}
.group-label { letter-spacing: 0.2em; text-transform: uppercase; font-size: 10px; opacity: 0.75; }
.mode-stack { display: flex; flex-direction: column; gap: 10px; margin: 4px 0 10px; }
.mode-card {
  display: flex; align-items: center; gap: 12px; padding: 14px;
  border-radius: 16px; cursor: pointer;
  background: rgba(0,0,0,0.16);
  border: 1px solid rgba(255,225,190,0.14);
}
.mode-card:has(input:checked) {
  border-color: rgba(232,163,61,0.55);
  background: rgba(0,0,0,0.28);
}
.mode-card input { display: none; }
.mode-dot {
  width: 22px; height: 22px; border-radius: 50%; flex: none; box-sizing: border-box;
  border: 2px solid rgba(255,225,190,0.35);
}
.mode-card:has(input:checked) .mode-dot {
  border-color: var(--accent);
  background: radial-gradient(circle, var(--accent) 0 5px, transparent 6px);
}
.mode-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.mode-title { font-weight: 700; font-size: 13.5px; }
.mode-sub { font-size: 11px; opacity: 0.65; }
.join-card {
  border-radius: 16px; background: rgba(0,0,0,0.16);
  border: 1px solid rgba(255,225,190,0.14);
  padding: 12px 14px; margin-bottom: 12px;
}
.jc-title { font-weight: 700; font-size: 12.5px; margin-bottom: 8px; }
.jc-row { display: flex; gap: 8px; }
.jc-row input {
  flex: 1; min-width: 0; margin: 0; letter-spacing: 0.3em;
  font-family: var(--font-mono, monospace);
}
.jc-btn { width: 84px; flex: none; margin: 0; }
.jc-row input::placeholder { color: inherit; opacity: 0.45; letter-spacing: 0.1em; }

/* -- v9.5 static legal pages (privacy.html, terms.html) ------------------------
   Standalone pages, not app screens - they carry no .screen class, so the
   display:none rule never applies and they scroll as ordinary documents on the
   shared wood background. Kept in this stylesheet rather than per-page CSS, per the
   single-stylesheet convention. Palette vars fall back to their literals here since
   palettes.js does not run on these pages. */
.legal-wrap {
  width: min(680px, 90vw); margin: 0 auto; padding: 32px 0 56px;
  line-height: 1.6; font-size: 15px;
}
.legal-wrap h1 {
  font-family: var(--font-display); font-weight: 800; font-size: 30px;
  margin: 8px 0 4px;
}
.legal-wrap h2 {
  font-family: var(--font-display); font-weight: 700; font-size: 19px;
  margin: 30px 0 8px; color: var(--accent, #e8a33d);
}
.legal-wrap h3 { font-weight: 700; font-size: 15px; margin: 18px 0 4px; }
.legal-wrap p { margin: 0 0 12px; }
.legal-wrap ul { margin: 0 0 12px; padding-left: 22px; }
.legal-wrap li { margin: 0 0 6px; }
.legal-date { font-size: 13px; opacity: 0.65; margin-bottom: 22px; }
.legal-back { font-size: 13px; opacity: 0.8; }
.legal-wrap a,
.legal-wrap a:visited {
  color: var(--accent, #e8a33d); text-decoration: underline;
  text-underline-offset: 2px;
}
