/* ==========================================================================
   Theme Gallery · base.css
   --------------------------------------------------------------------------
   1. The token contract every theme overrides (neutral defaults).
   2. Every component, written against tokens only.
   Themes live in css/themes/<id>.css as one [data-theme="<id>"] block each:
   first the tokens, then rules for whatever tokens cannot express (layout,
   casing, pseudo-element decoration). See docs/README.md.
   ========================================================================== */

/* ---------- fonts (all SIL OFL, bundled so every theme works offline) ---------- */
@font-face { font-family: "Fraunces"; src: url("../fonts/fraunces.woff2") format("woff2"); font-weight: 300 900; font-display: swap; }
@font-face { font-family: "Jost"; src: url("../fonts/jost.woff2") format("woff2"); font-weight: 300 800; font-display: swap; }
@font-face { font-family: "Space Grotesk"; src: url("../fonts/space-grotesk.woff2") format("woff2"); font-weight: 300 700; font-display: swap; }
@font-face { font-family: "IBM Plex Mono"; src: url("../fonts/plex-mono-400.woff2") format("woff2"); font-weight: 400; font-display: swap; }
@font-face { font-family: "IBM Plex Mono"; src: url("../fonts/plex-mono-600.woff2") format("woff2"); font-weight: 600; font-display: swap; }
@font-face { font-family: "Silkscreen"; src: url("../fonts/silkscreen-400.woff2") format("woff2"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Silkscreen"; src: url("../fonts/silkscreen-700.woff2") format("woff2"); font-weight: 700; font-display: swap; }
@font-face { font-family: "Zen Kaku Gothic New"; src: url("../fonts/zen-kaku-400.woff2") format("woff2"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Zen Kaku Gothic New"; src: url("../fonts/zen-kaku-700.woff2") format("woff2"); font-weight: 700; font-display: swap; }
@font-face { font-family: "Shippori Mincho"; src: url("../fonts/shippori-mincho-700.woff2") format("woff2"); font-weight: 400 800; font-display: swap; }
@font-face { font-family: "Bricolage Grotesque"; src: url("../fonts/bricolage-grotesque.woff2") format("woff2"); font-weight: 400 800; font-stretch: 75% 100%; font-display: swap; }

/* ---------- 1. tokens (defaults) ---------- */
:root {
  color-scheme: light;
  /* surfaces and ink */
  --bg: #f5f6fb;
  --surface: #ffffff;
  --surface-2: #eceef7;
  --text: #1b1d2e;
  --muted: #6a6f8e;
  --faint: #8d91ab;
  --border: #dfe2ef;
  --rule: var(--border);             /* structural lines; editorial themes make them ink */
  /* the one accent, and the button colours derived from it */
  --accent: #5b5bd6;
  --primary: #5b5bd6;
  --primary-strong: #3f3fc0;
  --primary-contrast: #ffffff;
  --primary-soft: #e6e6fc;
  --danger: #dc3e42;
  --danger-soft: #fde8e8;
  --success: #2b9a66;
  /* shape: every corner in the app reads one of these */
  --radius: 16px;                    /* cards, panels, banners */
  --r-ctl: 12px;                     /* buttons, inputs, segmented */
  --r-ctl-in: 9px;                   /* the pressed segment inside a control */
  --r-dialog: 22px;
  --r-menu: 16px;
  --r-board: 10px;
  --r-cell: 8px;
  --r-pill: 999px;                   /* badges, switches, counts */
  --bw: 1px;                         /* default border width */
  /* the content column. .view and the footer's inner both read it, so a theme
     sets its width once and the page and its footer stay in one column. */
  --content-w: 1040px;
  /* motion: one curve and two durations, so everything that moves agrees.
     --t-fast is a colour change under the pointer, --t-slow a panel arriving.
     --fade is the property list every hover uses; a component that also moves
     writes its own transform ahead of it (see .btn). */
  --ease: cubic-bezier(.2, .7, .3, 1);
  --t-fast: .13s;
  --t-slow: .26s;
  --fade: background-color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease),
          color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease),
          outline-color var(--t-fast) var(--ease);
  /* depth */
  --shadow: 0 1px 2px rgba(20, 22, 50, .05), 0 4px 16px rgba(20, 22, 50, .06);
  --menu-shadow: 0 16px 48px rgba(10, 12, 30, .22);
  --dialog-shadow: 0 20px 60px rgba(0, 0, 0, .3);
  --overlay: rgba(10, 12, 30, .45);
  /* scrollbars: mixed from --muted, so a theme that sets its ink gets a matching
     bar without writing a rule. Override either one to change it on purpose. */
  --scroll-thumb: color-mix(in srgb, var(--muted) 32%, transparent);
  --scroll-thumb-hover: color-mix(in srgb, var(--muted) 58%, transparent);
  /* type */
  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-display: var(--font);
  --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  /* toast & record badge */
  --toast-bg: var(--text);
  --toast-fg: var(--bg);
  --toast-action: var(--primary-soft);
  --record-bg: #fff3c4;
  --record-fg: #8a5a00;
  /* game pieces: the 2048 tile board */
  --g-board: #bbada0;
  --g-slot: rgba(238, 228, 218, .35);
  --t2: #eee4da; --t4: #ede0c8; --t8: #f2b179; --t16: #f59563; --t32: #f67c5f; --t64: #f65e3b;
  --t128: #edcf72; --t256: #edcc61; --t512: #edc850; --t1024: #edc53f; --t2048: #edc22e;
  --t-ink: #776e65; --t-paper: #f9f6f2;
  /* game pieces: the memory grid and number pad */
  --flash: var(--primary);
  --tile: var(--surface-2);
  /* game pieces: the card table and playing cards */
  --table-bg: radial-gradient(130% 100% at 50% 0%, #2a5f48, #1f4a38 50%, #143226);
  --table-frame: none;
  --slot-line: rgba(255, 255, 255, .16);
  --card-paper: #fbf8f1;
  --card-r: .085;                    /* corner radius as a fraction of the card width */
  --card-shadow: 0 0 0 1px rgba(20, 16, 8, .28), 0 1px 2px rgba(0, 0, 0, .28);
  --court-panel: color-mix(in srgb, #d6ad5c 16%, var(--card-paper));
  --court-rule: inset 0 0 0 1px color-mix(in srgb, currentColor 35%, transparent);
  --suit-red: #b4231c;
  --suit-black: #1a1d1b;
  --card-back-art:
    repeating-linear-gradient(45deg, rgba(255, 255, 255, .09) 0 1px, transparent 1px 5px),
    repeating-linear-gradient(-45deg, rgba(255, 255, 255, .09) 0 1px, transparent 1px 5px),
    radial-gradient(circle at 50% 40%, #93303f, #7b2432);
  --back-edge: var(--card-paper);
  --back-edge-w: .045;               /* multiplied by --cw in the rule, see .back */
  --back-shadow: 0 0 0 1px rgba(0, 0, 0, .35), 0 1px 2px rgba(0, 0, 0, .3);
  --font-card: var(--font-display);
}

/* ---------- 2. base ---------- */
*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; scroll-padding-top: 90px; }
body {
  margin: 0;
  min-height: 100dvh;
  display: flex; flex-direction: column;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  -webkit-tap-highlight-color: transparent;
}
button, input, select { font: inherit; color: inherit; }
a { color: inherit; }
button:focus-visible, a:focus-visible, input:focus-visible, [tabindex]:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
/* Hover and focus are colour changes, so they are eased rather than cut: every control
   below carries `transition: var(--fade)` in its own rule, on the same 130 ms, whatever
   a theme paints it with. Nothing there moves - the size rule (§4.11) still holds; a
   control that does move writes its own transform ahead of it, as .btn does.
   This is deliberately not one shared :is() list: the most specific argument would set
   the whole list's specificity (docs/README.md §5) and silently beat the per-component
   rules that follow. */
/* A scrollbar is chrome, not content: a thin bar in the theme's own ink, on no
   track at all. The standard properties are what Chrome and Firefox read; the
   -webkit- block below is for Safari, which still has only the old ones. */
* { scrollbar-width: thin; scrollbar-color: var(--scroll-thumb) transparent; }
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track, ::-webkit-scrollbar-corner { background: transparent; }
::-webkit-scrollbar-button { display: none; }
::-webkit-scrollbar-thumb {
  /* The transparent border plus padding-box clip is what makes the thumb float
     in the middle of the track instead of filling it edge to edge. */
  border: 3px solid transparent; border-radius: var(--r-pill);
  background: var(--scroll-thumb); background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--scroll-thumb-hover); background-clip: padding-box; }

.ico { width: 22px; height: 22px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
/* Line symbols carry a heavier Horizon redraw next to the normal drawing; Horizon shows its own. */
:root:not([data-theme="horizon"]) .sym-hz, [data-theme="horizon"] .sym-app { display: none; }

/* ---------- top bar ---------- */
.topbar {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; gap: 6px;
  min-height: 56px;
  padding: calc(env(safe-area-inset-top) + 8px) max(12px, env(safe-area-inset-right)) 8px max(12px, env(safe-area-inset-left));
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border);
}
.brand {
  display: flex; align-items: center; gap: 10px;
  min-width: 0; margin: 0 4px;
  color: var(--text); text-decoration: none;
  font-family: var(--font-display); font-size: 1.15rem; font-weight: 750; letter-spacing: -.01em;
  white-space: nowrap;
}
.brand:hover { color: var(--primary-strong); }
.brand-text { overflow: hidden; text-overflow: ellipsis; }
/* the four-square brand mark; only the themes that draw it switch it on */
.brand-mark { display: none; flex: none; width: 30px; height: 30px; padding: 6px; place-items: center; }
.brand-mark svg { width: 100%; height: 100%; fill: currentColor; }
.icon-btn {
  flex: none;
  width: 40px; height: 40px;
  display: grid; place-items: center;
  padding: 0;
  border: 0; border-radius: var(--r-ctl);
  background: transparent; color: var(--text);
  cursor: pointer;
  transition: var(--fade);
}
.icon-btn:hover { background: var(--surface-2); }
.topbar-end { display: flex; align-items: center; gap: 6px; margin-left: auto; }
/* The way to the documentation, from every page of the gallery. */
.docs-link { height: 40px; padding: 0 12px; text-decoration: none; font-size: .88rem; }
.docs-link .ico { width: 18px; height: 18px; }
@media (max-width: 680px) { .docs-link span { display: none; } .docs-link { padding: 0 10px; } }

/* navigation */
.main-nav { display: flex; gap: 2px; margin-left: 12px; }
.main-nav a {
  padding: 8px 12px;
  border-radius: var(--r-ctl);
  color: var(--muted); text-decoration: none;
  font-size: .9rem; font-weight: 600;
  white-space: nowrap;
  transition: var(--fade);
}
.main-nav a:hover { background: var(--surface-2); color: var(--text); }
.main-nav a[aria-current="true"] { background: var(--primary-soft); color: var(--primary-strong); }
@media (max-width: 900px) {
  .topbar { flex-wrap: wrap; row-gap: 4px; }
  .main-nav { order: 5; flex-basis: 100%; margin: 0; overflow-x: auto; scrollbar-width: none; }
}

/* language toggle = segmented control in the bar */
.topbar .lang-toggle { height: 40px; }
.topbar .lang-toggle button { padding: 0 9px; letter-spacing: .04em; }

/* theme picker */
.theme-picker { position: relative; }
.theme-btn {
  display: inline-flex; align-items: center; gap: 8px;
  height: 40px; padding: 0 8px 0 6px;
  border: 1px solid var(--border); border-radius: var(--r-ctl);
  background: var(--surface);
  font-size: .88rem; font-weight: 600;
  cursor: pointer;
  transition: var(--fade);
}
.theme-btn:hover { background: var(--surface-2); }
.theme-btn .theme-swatch { width: 28px; height: 28px; border-radius: 999px; }
.theme-btn .theme-swatch b { font-size: 11px; }
.theme-btn .theme-swatch i { display: none; }
.theme-btn .chev { width: 16px; height: 16px; color: var(--muted); transition: transform var(--t-fast) var(--ease); }
.theme-btn[aria-expanded="true"] .chev { transform: rotate(180deg); }
@media (max-width: 560px) { .theme-btn .theme-name { display: none; } }
/* The swatch is a small sample of its theme, painted in that theme's colours, typeface
   and signature mark whatever theme is active, so the menu reads as samples.
   Values per theme: docs/README.md §2.6. */
.theme-swatch {
  position: relative;
  display: grid;
  place-items: center;
  flex: none;
  width: 48px;
  height: 36px;
  overflow: hidden;
  border-radius: 9px;
  border: 1px solid rgba(128, 128, 128, .35);
  font-weight: 700;
  font-style: normal;
  line-height: 1;
  letter-spacing: 0;
  text-transform: none;
}
.theme-swatch b { font-size: 16px; font-weight: inherit; line-height: 1; }
.theme-swatch i { position: absolute; left: 6px; right: 6px; bottom: 5px; height: 3px; border-radius: 2px; }
.theme-swatch[data-swatch="cyberpunk"] {
  background: radial-gradient(120% 90% at 50% 110%, #3a0f47, #0a0614 70%);
  color: #fff4fb;
  border-color: #ff2e88;
  border-radius: 0;
  font-family: "Bricolage Grotesque", "Arial Narrow", sans-serif;
  font-stretch: 75%;
  font-weight: 800;
  text-shadow: -1px 0 #ff2e88, 1px 0 #2de2ff;
}
.theme-swatch[data-swatch="cyberpunk"] i { height: 2px; border-radius: 0; background: linear-gradient(90deg, #ff2e88 0 50%, #2de2ff 0); box-shadow: 0 0 5px #ff2e88; }
.theme-swatch[data-swatch="washi"] {
  background: #f1ece1;
  color: #262421;
  border-color: #d8cfbf;
  border-radius: 3px;
  font-family: "Shippori Mincho", "Hiragino Mincho ProN", Georgia, serif;
}
.theme-swatch[data-swatch="washi"] i { left: auto; right: 5px; bottom: auto; top: 5px; width: 7px; height: 7px; border-radius: 1px; background: #c6412a; transform: rotate(-3deg); }
.theme-swatch[data-swatch="broadsheet"] {
  background: #f2ede2;
  color: #161513;
  border-color: #161513;
  border-radius: 0;
  font-family: "Fraunces", Georgia, serif;
  font-weight: 900;
  letter-spacing: -.03em;
}
.theme-swatch[data-swatch="broadsheet"] i { left: 0; right: 0; bottom: 0; height: 22%; border-radius: 0; background: #b3261e; }
.theme-swatch[data-swatch="bauhaus"] {
  background: #efe8d8;
  color: #121212;
  border: 2px solid #121212;
  border-radius: 0;
  font-family: "Jost", Futura, "Century Gothic", sans-serif;
  font-weight: 800;
  letter-spacing: -.04em;
}
.theme-swatch[data-swatch="bauhaus"] b { text-transform: lowercase; }
.theme-swatch[data-swatch="bauhaus"] i { left: 0; right: 0; bottom: 0; height: 5px; border-radius: 0; background: linear-gradient(90deg, #d7261e 0 33.3%, #f4b400 0 66.6%, #1e4fa3 0); }
.theme-swatch[data-swatch="pop"] {
  background: #ffd23f;
  color: #111;
  border: 2px solid #111;
  border-radius: 10px;
  font-family: "Bricolage Grotesque", system-ui, sans-serif;
  font-weight: 800;
}
.theme-swatch[data-swatch="pop"] i { background: #ff5c8a; height: 5px; border: 1.5px solid #111; }
.theme-swatch[data-swatch="horizon"] {
  background: linear-gradient(180deg, #fff 55%, #e9eef4);
  color: #17181c;
  border-color: #b9c3cf;
  border-radius: 4px;
  font-family: Frutiger, "Frutiger Linotype", "Segoe UI", "Noto Sans", "Helvetica Neue", Arial, sans-serif;
}
.theme-swatch[data-swatch="horizon"] i { left: 0; right: 0; bottom: 0; height: 4px; border-radius: 0; background: #1f5fb4; }
.theme-swatch[data-swatch="blueprint"] {
  background:
    linear-gradient(rgba(207, 224, 255, .2) 1px, transparent 1px),
    linear-gradient(90deg, rgba(207, 224, 255, .2) 1px, transparent 1px),
    #0d3466;
  background-size: 8px 8px;
  background-position: -1px -1px;
  color: #eaf2ff;
  border-color: #cfe0ff;
  border-radius: 2px;
  font-family: "Space Grotesk", "Helvetica Neue", Arial, sans-serif;
  letter-spacing: -.03em;
}
.theme-swatch[data-swatch="blueprint"] i { height: 2px; border-radius: 0; background: #ffd23f; }
.theme-swatch[data-swatch="arcade"] {
  background: #1d2b53;
  color: #fff1e8;
  border: 2px solid #000;
  border-radius: 0;
  font-family: "Silkscreen", monospace;
  font-weight: 400;
}
.theme-swatch[data-swatch="arcade"] b { font-size: 14px; }
.theme-swatch[data-swatch="arcade"] i { height: 4px; border-radius: 0; background: repeating-linear-gradient(to right, #ffec27 0 4px, transparent 4px 8px); }
.theme-menu {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 40;
  width: min(340px, calc(100vw - 24px));
  max-height: calc(100dvh - 90px); overflow: auto;
  margin: 0; padding: 6px;
  list-style: none;
  border: 1px solid var(--border); border-radius: var(--r-menu);
  background: var(--surface); color: var(--text);
  box-shadow: var(--menu-shadow);
  animation: menu-in var(--t-fast) var(--ease);
}
.theme-opt {
  display: grid; grid-template-columns: auto 1fr 18px; align-items: center; gap: 12px;
  width: 100%; padding: 8px;
  border: 0; border-radius: var(--r-ctl-in);
  background: none; text-align: left;
  cursor: pointer;
  transition: var(--fade);
}
.theme-opt:hover, .theme-opt:focus-visible { background: var(--surface-2); outline: none; }
.theme-opt:focus-visible { box-shadow: inset 0 0 0 2px var(--primary); }
.theme-opt strong { display: block; font-family: var(--opt-font); font-size: .98rem; font-weight: 700; line-height: 1.2; }
.theme-opt small { display: block; margin-top: 2px; font-size: .78rem; line-height: 1.3; color: var(--muted); }
.theme-opt .ico { width: 18px; height: 18px; color: var(--primary-strong); visibility: hidden; }
.theme-opt[aria-checked="true"] .ico { visibility: visible; }
/* The menu is the gallery's index: the rows arrive one after another, 18 ms apart,
   so it reads as a list of samples being laid out rather than a block appearing.
   --i is the row's index, set in js/app.js; the whole cascade is under 200 ms. */
.theme-menu .theme-opt { animation: opt-in var(--t-slow) var(--ease) backwards; animation-delay: calc(var(--i, 0) * 18ms); }
@keyframes menu-in { from { opacity: 0; } }
@keyframes opt-in { from { opacity: 0; transform: translateY(-5px); } }

/* ---------- buttons & controls ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  height: 42px; padding: 0 16px;
  border: 1px solid var(--border); border-radius: var(--r-ctl);
  background: var(--surface); color: var(--text);
  font-weight: 650; text-decoration: none;
  cursor: pointer; white-space: nowrap;
  transition: transform .08s var(--ease), var(--fade);
}
.btn:hover:not(:disabled) { background: var(--surface-2); }
.btn:active { transform: scale(.96); }
.btn:disabled { opacity: .45; cursor: default; transform: none; }
.btn .ico { width: 18px; height: 18px; }
.btn-primary { background: var(--primary); border-color: transparent; color: var(--primary-contrast); }
.btn-primary:hover:not(:disabled) { background: var(--primary-strong); }
.btn-danger { color: var(--danger); }
.btn-danger:hover:not(:disabled) { background: var(--danger-soft); }

.segmented {
  display: inline-flex;
  padding: 3px;
  background: var(--surface-2);
  border-radius: var(--r-ctl);
}
.segmented button {
  border: 0; background: transparent;
  padding: 7px 12px; border-radius: var(--r-ctl-in);
  font-size: .9rem; font-weight: 600; color: var(--muted);
  white-space: nowrap;
  cursor: pointer;
  transition: var(--fade);
}
.segmented button[aria-pressed="true"] { background: var(--surface); color: var(--text); box-shadow: var(--shadow); }
/* the unpressed options have to look pressable; the :not() also lifts this over a theme's resting rule */
.segmented button:hover:not([aria-pressed="true"]) { background: color-mix(in srgb, var(--text) 8%, transparent); color: var(--text); }

.stats { display: flex; gap: 8px; flex-wrap: wrap; }
.stat {
  display: flex; flex-direction: column; align-items: center;
  min-width: 62px; padding: 3px 10px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-ctl);
}
.stat-label { font-size: .62rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
.stat-value { font-family: var(--font-display); font-size: 1.05rem; font-weight: 750; font-variant-numeric: tabular-nums; }

.tools { display: flex; justify-content: center; gap: 4px; }
.tool {
  position: relative;
  flex: 1; max-width: 92px;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 7px 4px;
  border: 0; border-radius: var(--r-ctl);
  background: transparent; color: var(--muted);
  font-size: .75rem; font-weight: 650;
  cursor: pointer;
  transition: var(--fade);
}
.tool .ico { width: 24px; height: 24px; }
.tool:hover:not(:disabled) { background: var(--surface-2); }
.tool:active:not(:disabled) { transform: scale(.95); }
.tool[aria-pressed="true"] { background: var(--primary-soft); color: var(--primary-strong); }
.tool:disabled { opacity: .4; cursor: default; }
.tool .badge {
  position: absolute; top: 2px; right: calc(50% - 22px);
  min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 8px; background: var(--primary); color: var(--primary-contrast);
  font-size: .65rem; line-height: 16px;
}

.record { display: inline-block; padding: 4px 10px; border-radius: var(--r-pill); background: var(--record-bg); color: var(--record-fg); font-weight: 700; font-size: .85rem; }

/* form controls */
.field { display: grid; gap: 6px; }
.field label { font-size: .82rem; font-weight: 650; color: var(--muted); }
.field input {
  height: 42px; padding: 0 12px;
  border: 1px solid var(--border); border-radius: var(--r-ctl);
  background: var(--surface); color: var(--text);
  transition: var(--fade);
}
.field input::placeholder { color: var(--faint); }
.field input:hover:not(:focus-visible) { border-color: var(--muted); }
.field input:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }

.switch {
  position: relative; flex: none;
  width: 46px; height: 28px; padding: 0;
  border: 0; border-radius: var(--r-pill);
  background: var(--surface-2);
  box-shadow: inset 0 0 0 1px var(--border);
  cursor: pointer;
  transition: var(--fade);
}
.switch::before {
  content: ""; position: absolute; top: 3px; left: 3px;
  width: 22px; height: 22px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .25);
  transition: transform var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease);
}
.switch[aria-checked="true"] { background: var(--primary); box-shadow: none; }
.switch[aria-checked="true"]::before { transform: translateX(18px); }
/* hover halo for the small form controls: themes draw their tracks with box-shadow, so the
   halo uses outline, which none of them set (keyboard focus replaces it with the solid ring) */
.switch:hover:not(:disabled, :focus-visible), .check:hover input:not(:focus-visible), .range:hover:not(:focus-visible) { outline: 2px solid color-mix(in srgb, var(--primary) 45%, transparent); outline-offset: 2px; }

.check { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; font-size: .95rem; }
.check input { width: 20px; height: 20px; margin: 0; accent-color: var(--primary); }

.range { width: 100%; accent-color: var(--primary); cursor: pointer; }

/* ---------- surfaces ---------- */
/* A box that groups content: the gallery's specimen panels, the settings panel. */
.box {
  padding: 20px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
}
.box > h3 { margin: 0 0 14px; font-family: var(--font-display); font-size: 1rem; letter-spacing: -.01em; }

.install-banner {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-bottom: 14px; padding: 14px 16px;
  border-radius: var(--radius);
  background: var(--primary-soft);
}
@media (max-width: 560px) { .install-banner { flex-wrap: wrap; } }
.install-banner strong { display: block; }
.install-banner span { color: var(--muted); font-size: .88rem; }

.offline-badge { display: inline-flex; align-items: center; gap: 8px; font-size: .82rem; color: var(--muted); }
.offline-badge i { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }
.offline-badge.ready i { background: var(--success); box-shadow: 0 0 0 3px color-mix(in srgb, var(--success) 25%, transparent); }

/* ---------- dialog & toast ---------- */
.overlay {
  position: fixed; inset: 0; z-index: 50;
  display: grid; place-items: center;
  padding: 20px;
  background: var(--overlay);
  animation: fade var(--t-fast) var(--ease);
}
.overlay.closing { animation: fade var(--t-fast) var(--ease) reverse forwards; }
.dialog {
  width: min(100%, 400px);
  max-height: calc(100dvh - 40px); overflow: auto;
  padding: 22px;
  border-radius: var(--r-dialog);
  background: var(--surface); color: var(--text);
  box-shadow: var(--dialog-shadow);
  animation: dialog-in .22s cubic-bezier(.2, .9, .3, 1.2);
}
/* A dialog that only faded out would leave its box hanging; it leaves the way it came. */
.overlay.closing .dialog { animation: dialog-in var(--t-fast) var(--ease) reverse forwards; }
.dialog h2 { margin: 0 0 10px; font-family: var(--font-display); font-size: 1.3rem; letter-spacing: -.01em; }
.dialog p { margin: 0; color: var(--muted); line-height: 1.5; }
.dialog .stats { margin-top: 14px; }
.dialog .stats .stat { flex: 1; padding: 8px 10px; }
.dialog .record { margin-top: 12px; }
.dialog-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px; }
.rules { margin: 0; padding-left: 1.2em; color: var(--muted); line-height: 1.5; }
.rules li + li { margin-top: 6px; }
@keyframes fade { from { opacity: 0; } }
@keyframes dialog-in { from { opacity: 0; transform: scale(.92); } }

#toast {
  position: fixed; left: 50%; bottom: calc(20px + env(safe-area-inset-bottom)); z-index: 70;
  display: flex; align-items: center; gap: 14px;
  max-width: calc(100% - 32px);
  padding: 11px 16px;
  border-radius: var(--r-ctl);
  background: var(--toast-bg); color: var(--toast-fg);
  box-shadow: var(--shadow);
  font-size: .92rem;
  opacity: 0; pointer-events: none;
  transform: translate(-50%, 20px);
  transition: opacity var(--t-slow) var(--ease), transform var(--t-slow) var(--ease);
}
#toast.show { opacity: 1; pointer-events: auto; transform: translate(-50%, 0); }
.toast-action { border: 0; padding: 0; background: none; color: var(--toast-action); font-weight: 800; font-size: .82rem; text-transform: uppercase; letter-spacing: .04em; cursor: pointer; transition: var(--fade); }
.toast-action:hover { text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 3px; }

/* The backdrop: js/backdrop.js creates this canvas and paints each theme's scene on it
   (js/backdrops/<theme>.js). It sits behind the page and never takes a pointer event.
   --veil is how much of the scene shows through the content column (1 = all of it);
   the margins show the full scene and the mask fades to --veil over 80px at each edge
   of the column, so text stays readable while the margins stay alive. */
.backdrop {
  --veil: 1;
  --veil-mask: linear-gradient(90deg, #000 calc(50% - var(--content-w) / 2 - 40px), rgb(0 0 0 / var(--veil)) calc(50% - var(--content-w) / 2 + 40px), rgb(0 0 0 / var(--veil)) calc(50% + var(--content-w) / 2 - 40px), #000 calc(50% + var(--content-w) / 2 + 40px));
  position: fixed; inset: 0; z-index: -1; width: 100%; height: 100%; pointer-events: none;
  -webkit-mask-image: var(--veil-mask); mask-image: var(--veil-mask);
}
/* the reader is for long text: the scene stays, but well behind it */
body[data-route="docs"] .backdrop { opacity: .5; }

/* ---------- page ---------- */
.view { width: 100%; max-width: var(--content-w); margin: 0 auto; padding: 16px max(16px, env(safe-area-inset-right)) 28px max(16px, env(safe-area-inset-left)); }
.page { scroll-margin-top: 80px; }
.page + .page { margin-top: 64px; }
/* the gallery's own section heads reuse .section-head, so they follow each theme */

/* ---------- home: hero ---------- */
.home-hero { margin-bottom: 16px; }
/* the masthead title, dateline and artwork are only switched on by themes that use them */
.hero-title, .hero-meta, .hero-art, .game-num { display: none; }
.intro { margin: 4px 2px 0; color: var(--muted); line-height: 1.5; }

/* ---------- home: sections and game cards ---------- */
.home-section + .home-section { margin-top: 28px; }
.section-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; margin: 0 2px 12px; }
.section-title { margin: 0; font-family: var(--font-display); font-size: 1.15rem; font-weight: 750; letter-spacing: -.01em; }
.section-count { display: none; }
.section-note { margin: 0; color: var(--muted); font-size: .88rem; }
.home-section.untitled .section-head { display: none; }
/* guide only, not part of a product screen: names the layout a home section demonstrates */
.layout-tag { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; margin: 0 2px 14px; padding-bottom: 8px; border-bottom: 1px dashed var(--border); color: var(--muted); font-size: .8rem; line-height: 1.4; }
.layout-tag strong { color: var(--text); }
.layout-tag strong::before { content: ""; display: inline-block; width: 7px; height: 7px; margin-right: 8px; border-radius: var(--r-pill); background: var(--accent); vertical-align: .08em; }

.game-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: 12px; }
.game-card {
  --c: var(--gc);
  display: flex; align-items: center; gap: 14px;
  padding: 14px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); color: inherit;
  box-shadow: var(--shadow);
  text-decoration: none;
  transition: transform .12s var(--ease), var(--fade);
}
.game-card:hover { transform: translateY(-2px); border-color: color-mix(in srgb, var(--c) 45%, var(--border)); }
.game-card:active { transform: scale(.98); }
.game-icon {
  flex: none;
  display: grid; place-items: center;
  width: 58px; height: 58px;
  border-radius: 16px;
  background: var(--c); color: var(--c);
  box-shadow: inset 0 -3px 0 rgba(0, 0, 0, .12);
}
.game-icon svg { width: 42px; height: 42px; }
.game-meta { min-width: 0; }
.game-meta h3 { margin: 0; font-family: var(--font-display); font-size: 1.05rem; letter-spacing: -.01em; }
.game-meta p { margin: 2px 0 0; color: var(--muted); font-size: .87rem; line-height: 1.35; }
.game-status { margin-top: 5px; font-size: .75rem; font-weight: 700; color: var(--primary-strong); }

/* Three list layouts, set by a class on .game-list (see docs/README.md §1.1):
   .kind-glyph  filled pictograms with #fff details on the game colour (the default rules above)
   .kind-line   stroke-drawn symbols in currentColor
   .kind-art    a card-art panel above the text */
.kind-line .game-icon svg, .ico-line svg { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.kind-line .game-icon { color: #fff; }
.kind-line .game-icon svg { width: 30px; height: 30px; }

/* art panels: the icon box shows a small card layout instead (see .tile-art-inner) */
.kind-art .game-card { align-items: stretch; flex-direction: column; gap: 10px; padding: 0 0 14px; overflow: hidden; }
.kind-art .game-card .game-meta { padding: 0 14px; }
.kind-art .game-icon {
  position: relative; overflow: hidden;
  width: auto; height: auto; aspect-ratio: 16 / 10;
  border-radius: 0;
  background: var(--table-bg);
  box-shadow: none;
  --cw: 40px;
}
.tile-art-inner { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%) scale(var(--art-scale, 1)); }

.home-footer { margin-top: 22px; display: flex; justify-content: center; }

/* ---------- specimen layout ---------- */
/* align-items: start, so a short specimen is a short box: stretching them all to the
   tallest in the row leaves voids that read as a mistake rather than as rhythm. */
.spec-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); align-items: start; gap: 16px; }
.spec-grid .wide { grid-column: 1 / -1; }
.row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.row + .row { margin-top: 12px; }
.stack { display: grid; gap: 14px; }
.caption { margin: 10px 0 0; color: var(--muted); font-size: .8rem; line-height: 1.45; }

.swatches { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
.swatch-item { display: grid; grid-template-columns: 34px 1fr; gap: 10px; align-items: center; font-size: .78rem; }
.swatch-item i { width: 34px; height: 34px; border-radius: min(var(--r-ctl), 8px); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text) 18%, transparent); background: var(--sw); }
.swatch-item b { display: block; font-family: var(--font-mono); font-weight: 600; font-size: .74rem; }
.swatch-item span { font-family: var(--font-mono); color: var(--muted); font-size: .72rem; }

.type-specimen { display: grid; gap: 12px; }
.type-specimen .t-display { margin: 0; font-family: var(--font-display); font-size: 1.8rem; font-weight: 750; line-height: 1.05; letter-spacing: -.02em; }
.type-specimen .t-body { margin: 0; line-height: 1.55; }
.type-specimen .t-label { margin: 0; font-size: .72rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.type-specimen .t-mono { margin: 0; font-family: var(--font-mono); font-size: .9rem; }

.icon-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); gap: 10px; }
.icon-tile {
  display: grid; justify-items: center; gap: 8px;
  padding: 14px 6px 10px;
  border: 1px solid var(--border); border-radius: var(--r-ctl);
  background: var(--surface);
  font-size: .72rem; color: var(--muted);
  transition: var(--fade);
}
.icon-tile .ico { width: 26px; height: 26px; color: var(--text); }

/* ---------- settings panel ---------- */
.settings { display: grid; }
.setting { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 14px 0; border-top: 1px solid var(--border); }
.setting:first-of-type { border-top: 0; padding-top: 4px; }
.setting-text strong { display: block; font-weight: 650; }
.setting-text small { display: block; margin-top: 2px; color: var(--muted); font-size: .82rem; line-height: 1.35; }
.setting .range { max-width: 200px; }
.settings-title { margin: 0 0 6px; font-family: var(--font-display); font-size: 1.3rem; letter-spacing: -.01em; }

/* the same panel as a drawer from the right */
.drawer-scrim { position: fixed; inset: 0; z-index: 45; background: var(--overlay); animation: fade var(--t-fast) var(--ease); }
.drawer-scrim.closing { animation: fade var(--t-fast) var(--ease) reverse forwards; }
.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 46;
  width: min(420px, 100vw); overflow: auto;
  padding: 22px;
  background: var(--surface); color: var(--text);
  box-shadow: var(--dialog-shadow);
  animation: drawer-in var(--t-slow) var(--ease);
}
/* A drawer comes in off the edge it hangs from, and goes back out the same way.
   It is re-rendered in place when the theme or language changes, so it never replays. */
.drawer.closing { animation: drawer-in var(--t-fast) var(--ease) reverse forwards; }
.drawer-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
@keyframes drawer-in { from { transform: translateX(100%); } }

/* ---------- footer ---------- */
/* body is a min-height: 100dvh flex column; the auto top margin takes the slack, so on a
   short page the footer rests on the bottom of the screen instead of floating under the
   content. On a long page there is no slack and it simply follows the content. */
.site-footer {
  margin-top: auto; flex-shrink: 0;
  border-top: 1px solid var(--border);
  color: var(--muted); font-size: .85rem;
}
/* The footer sits in the same column as the page: both read --content-w, so a theme
   that narrows .view does not leave its footer hanging wider than everything above it.
   The links take the slack in the middle; below 700px the three groups stack, left
   aligned, instead of the last one wrapping under the first. */
.site-footer .inner {
  max-width: var(--content-w); margin: 0 auto;
  padding: 22px max(16px, env(safe-area-inset-right)) 32px max(16px, env(safe-area-inset-left));
  display: flex; flex-wrap: wrap; align-items: center; gap: 14px 24px;
}
.footer-brand { color: var(--text); font-family: var(--font-display); font-weight: 750; }
.footer-links { flex: 1 1 auto; display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 18px; margin: 0; padding: 0; list-style: none; }
.footer-links a { color: inherit; text-decoration: none; transition: var(--fade); }
.footer-links a:hover { color: var(--text); text-decoration: underline; }
/* flex: 1 1 auto on both of these is what makes the wrap deliberate: where the three
   groups do not fit on one line the meta takes a line of its own and stays right
   aligned under the links, instead of landing back under the brand. */
.footer-meta { flex: 1 1 auto; display: flex; align-items: center; justify-content: flex-end; gap: 16px; }
.version { font-family: var(--font-mono); font-size: .78rem; }
@media (max-width: 700px) {
  .site-footer .inner { flex-direction: column; align-items: flex-start; }
  .footer-links, .footer-meta { justify-content: flex-start; }
}

/* ---------- game screen specimens ---------- */
.game-demo { display: grid; gap: 14px; }
.game-demo .bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; }
.hint-text { margin: 0; text-align: center; color: var(--muted); font-size: .85rem; }

/* the 2048 tile board */
.g2048-board {
  --gap: 10px;
  display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--gap);
  width: min(100%, 340px); aspect-ratio: 1; margin: 0 auto;
  padding: var(--gap);
  border-radius: var(--r-board);
  background: var(--g-board);
}
.g2048-board > div {
  display: grid; place-items: center;
  border-radius: var(--r-cell);
  background: var(--tb, var(--g-slot)); color: var(--tf);
  font-family: var(--font-display); font-weight: 800; font-size: 1.6rem;
}
.g2048-board .d3 { font-size: 1.3rem; } .g2048-board .d4 { font-size: 1.05rem; }
.v2 { --tb: var(--t2); --tf: var(--t2-fg, var(--t-ink)); }
.v4 { --tb: var(--t4); --tf: var(--t4-fg, var(--t-ink)); }
.v8 { --tb: var(--t8); --tf: var(--t8-fg, var(--t-paper)); }
.v16 { --tb: var(--t16); --tf: var(--t16-fg, var(--t-paper)); }
.v32 { --tb: var(--t32); --tf: var(--t32-fg, var(--t-paper)); }
.v64 { --tb: var(--t64); --tf: var(--t64-fg, var(--t-paper)); }
.v128 { --tb: var(--t128); --tf: var(--t128-fg, var(--t-paper)); }
.v256 { --tb: var(--t256); --tf: var(--t256-fg, var(--t-paper)); }
.v512 { --tb: var(--t512); --tf: var(--t512-fg, var(--t-paper)); }
.v1024 { --tb: var(--t1024); --tf: var(--t1024-fg, var(--t-paper)); }
.v2048 { --tb: var(--t2048); --tf: var(--t2048-fg, var(--t-paper)); }

/* the memory grid and number pad */
.mem-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; width: min(100%, 300px); aspect-ratio: 1; margin: 0 auto; }
.mem-grid i { border-radius: calc(var(--r-cell) * .9); background: var(--tile); }
.mem-grid i.lit { background: var(--flash); }
.mem-grid i.bad { background: var(--danger); }
.keypad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; width: min(100%, 300px); margin: 0 auto; }
.keypad button {
  height: 52px;
  border: 1px solid var(--border); border-radius: var(--r-ctl);
  background: var(--surface); color: var(--text);
  font-family: var(--font-display); font-size: 1.3rem; font-weight: 650;
  cursor: pointer;
  transition: var(--fade);
}
.keypad button:hover { background: var(--surface-2); }
.keypad button.accent { background: var(--primary); border-color: transparent; color: var(--primary-contrast); }
.keypad button.accent:hover:not(:disabled) { background: var(--primary-strong); }

/* the card table and playing cards */
.table {
  position: relative;
  height: 230px;
  border-radius: var(--r-board);
  background: var(--table-bg);
  box-shadow: var(--table-frame);
  overflow: hidden;
  --cw: 70px; --ch: 98px;
}
.card {
  position: absolute; left: 0; top: 0;
  width: var(--cw); height: var(--ch);
  border-radius: calc(var(--cw) * var(--card-r));
}
.face, .back { position: absolute; inset: 0; border-radius: inherit; }
.face { display: none; overflow: hidden; background: var(--card-paper); box-shadow: var(--card-shadow); color: var(--suit-black); }
.card.up .face { display: block; }
.card.up .back { display: none; }
.face.red { color: var(--suit-red); }
.pip { display: block; background: currentColor; -webkit-mask: var(--suit) center / contain no-repeat; mask: var(--suit) center / contain no-repeat; }
.s-S { --suit: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath d='M50 4C55 17 94 39 94 63c0 13-10 21-21 21-9 0-15-4-19-10 1 10 5 17 12 22H34c7-5 11-12 12-22-4 6-10 10-19 10C16 84 6 76 6 63 6 39 45 17 50 4z'/%3E%3C/svg%3E"); }
.s-H { --suit: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath d='M50 92C46 87 5 60 5 32 5 16 17 6 30 6c9 0 16 5 20 13 4-8 11-13 20-13 13 0 25 10 25 26 0 28-41 55-45 60z'/%3E%3C/svg%3E"); }
.s-D { --suit: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath d='M50 3c10 17 23 32 38 47C73 65 60 80 50 97 40 80 27 65 12 50 27 35 40 20 50 3z'/%3E%3C/svg%3E"); }
.s-C { --suit: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='27' r='20'/%3E%3Ccircle cx='27' cy='58' r='20'/%3E%3Ccircle cx='73' cy='58' r='20'/%3E%3Ccircle cx='50' cy='52' r='12'/%3E%3Cpath d='M46 50h8l2 22c1 12 4 19 11 24H33c7-5 10-12 11-24z'/%3E%3C/svg%3E"); }
.idx { position: absolute; top: calc(var(--cw) * .025); display: flex; align-items: center; }
.idx.tl { left: calc(var(--cw) * .07); }
.idx b { font: 650 calc(var(--cw) * .3)/1 var(--font-card); letter-spacing: -0.03em; }
.ten .idx b { letter-spacing: -0.09em; margin-left: -0.04em; }
.idx.tl .pip { display: none; }
.idx.br { top: calc(var(--cw) * .075); right: calc(var(--cw) * .08); }
.idx.br b { display: none; }
.idx.br .pip { width: calc(var(--cw) * .22); height: calc(var(--cw) * .22); }
.mid { position: absolute; left: 0; right: 0; bottom: 0; height: calc(var(--ch, calc(var(--cw) * 1.4)) - var(--cw) * .38); display: grid; place-items: center; }
.mid .pip { width: calc(var(--cw) * .52); height: calc(var(--cw) * .52); margin-top: calc(var(--cw) * -.06); }
.court .mid {
  left: calc(var(--cw) * .12); right: calc(var(--cw) * .12); bottom: calc(var(--cw) * .12); top: calc(var(--cw) * .44); height: auto;
  border-radius: calc(var(--cw) * .04);
  background: var(--court-panel);
  box-shadow: var(--court-rule);
  /* the rank and its suit are stacked, not layered: one grid cell put the pip over the
     letter's tail (Q, J) */
  display: flex; flex-direction: column; justify-content: center; align-items: center;
  gap: calc(var(--cw) * .1);
}
.court .mid b { font: 600 calc(var(--cw) * .44)/1 var(--font-card); }
.court .mid .pip { width: calc(var(--cw) * .19); height: calc(var(--cw) * .19); flex: none; }
.ace .mid .pip { width: calc(var(--cw) * .6); height: calc(var(--cw) * .6); }
.back { background: var(--card-back-art); box-shadow: inset 0 0 0 calc(var(--cw) * var(--back-edge-w)) var(--back-edge), var(--back-shadow); }
.game-icon .card { --ch: 56px; height: 56px; }
.slot {
  position: absolute;
  width: var(--cw); height: var(--ch);
  border-radius: calc(var(--cw) * var(--card-r));
  box-shadow: inset 0 0 0 1.5px var(--slot-line);
  display: grid; place-items: center;
  color: var(--slot-line); font: 600 calc(var(--cw) * .36)/1 var(--font-card);
}
.card.sel .face { box-shadow: 0 0 0 2px var(--accent), 0 0 0 5px color-mix(in srgb, var(--accent) 35%, transparent), 0 6px 14px rgba(0, 0, 0, .4); }

/* ---------- the theme switch ---------- */
/* Switching the theme repaints the whole surface at once, and that is the one change
   on this page worth animating: it is what the page is for. js/app.js wraps the
   re-render in a view transition where the browser has one, so the old and the new
   theme cross-fade instead of cutting. Everything else on the page stays still.
   The switch is skipped entirely under prefers-reduced-motion (see js/app.js). */
::view-transition-old(root), ::view-transition-new(root) {
  animation-duration: var(--t-slow);
  animation-timing-function: var(--ease);
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
  .theme-menu .theme-opt { animation-delay: 0s !important; }
}
