/* ===========================================================================
   design.css — the design system: tokens, base layer and shared components.

   Loaded FIRST, before app.css and every other sheet, so anything here can be
   overridden by a view's own sheet. Two themes live side by side: dark is the
   default (`:root`), light is the same system inverted onto warm paper
   (`:root[data-theme="light"]`).

   Direction — "Holo Gallery": deep near-black surfaces with a faint violet-blue
   cast, glass panels with hairline light borders, generous depth, and
   iridescent foil reserved for cards and moments of delight. Cards are the only
   saturated colour on screen; the chrome stays quiet so the art pops.

   Every token name the app already used still works — their values changed,
   nothing disappeared.
   =========================================================================== */

/* ------------------------------------------------------------------ tokens */
:root {
  /* structure ----------------------------------------------------------- */
  --card-ratio: 0.714;           /* 2.5in / 3.5in — real trading-card proportion */
  --radius-sm: 8px;
  --radius: 12px;
  --radius-lg: 18px;
  --radius-pill: 999px;
  --pad: 14px;

  /* space scale: 4 / 8 / 12 / 16 / 24 / 32 / 48 / 64 */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --sp-7: 48px;
  --sp-8: 64px;

  /* type ----------------------------------------------------------------- */
  --font: "Segoe UI Variable Display", "SF Pro Display", system-ui, -apple-system,
    "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --fs-display: clamp(32px, 3.2vw + 14px, 44px);
  --fs-display-2: clamp(26px, 2.2vw + 13px, 36px);
  --fs-h1: 24px;
  --fs-h2: 18px;
  --fs-h3: 15px;
  --fs-body: 14px;
  --fs-sm: 12.5px;
  --fs-micro: 11px;

  /* colour --------------------------------------------------------------- */
  --bg: #0a0b10;
  --bg-2: #101218;
  --bg-3: #171a22;
  --surface: #13161d;
  --line: #252a36;
  --line-soft: #1c202a;
  --hairline: rgba(255,255,255,.07);      /* the 1px top-light edge on every raised thing */

  --ink: #eef0f6;
  --ink-2: #a7afc2;
  /* DESIGN.md §1 asks for #6f7889, which reads at 4.2:1 on --bg-2 — just under the
     4.5:1 §9 holds every piece of text to. Lifted by one step, same hue: 5.0:1. */
  --ink-3: #7b8496;

  /* Sky blue, unless the person picked their own (Preferences → Accent colour):
     accent.js then sets the --user-accent family, worked out for contrast on
     both themes, and everything below follows it. */
  --accent: var(--user-accent, #38bdf8);  /* sky */
  --accent-2: #a78bfa;                    /* violet, secondary */
  --accent-ink: var(--user-accent-ink, #04121c);
  --gold: #e3b665;                        /* money */
  --danger: #e2686b;
  /* The accent and the gold are pitched for a near-black ground. On paper-white
     they would read at about 3.5:1, under the 4.5:1 the app holds itself to, so
     the light theme redefines these three for *text* and *filled controls*
     only — borders, rings, glows and bars keep --accent and --gold as they are.
     Use them wherever the colour carries words. */
  --accent-text: var(--user-accent-text, var(--accent));
  --accent-fill: var(--accent);
  --accent-hover: color-mix(in srgb, var(--accent) 86%, #fff);
  --gold-text: var(--gold);

  --pocket: #0b0d12;
  --pocket-line: #262c39;

  /* glass ---------------------------------------------------------------- */
  --glass: color-mix(in srgb, var(--bg-2) 72%, transparent);
  --glass-blur: 14px;
  --glass-edge: inset 0 1px 0 var(--hairline);

  /* elevation ------------------------------------------------------------ */
  --shadow-1: 0 1px 2px rgba(0,0,0,.5);
  --shadow-2: 0 12px 40px rgba(0,0,0,.5), 0 2px 8px rgba(0,0,0,.4);
  --shadow-card: 0 6px 18px rgba(0,0,0,.55);
  --glow-accent:
    0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent),
    0 8px 30px color-mix(in srgb, var(--accent) 18%, transparent);

  /* the iridescent sheen, used by the foil effect and nowhere else */
  --foil: linear-gradient(115deg,
    #ff6ec7 0%, #ffd36e 18%, #7dffb2 38%, #6ec7ff 58%, #c98bff 78%, #ff6ec7 100%);

  /* motion --------------------------------------------------------------- */
  --t-1: 120ms;    /* hover / press */
  --t-2: 180ms;    /* small entrances */
  --t-3: 280ms;    /* panels, dialogs */
  --t-4: 600ms;    /* page turn, hero */
  --ease-out: cubic-bezier(.22,.9,.28,1);
  --ease-in-out: cubic-bezier(.6,.04,.3,1);
  --ease-spring: cubic-bezier(.34,1.46,.5,1);    /* small UI only */
}

:root[data-theme="light"] {
  --bg: #f6f5f2;
  --bg-2: #ffffff;
  --bg-3: #eceae5;
  --surface: #ffffff;
  --line: #dcd9d2;
  --line-soft: #e8e5df;
  --hairline: rgba(20,24,32,.06);

  --ink: #141820;
  --ink-2: #555e70;
  /* §1 asks for #7d8697 (3.7:1 on white); darkened one step for §9. 4.7:1. */
  --ink-3: #656e7e;

  --accent: var(--user-accent-light, #0284c7);
  --accent-2: #6d28d9;
  --accent-ink: #ffffff;
  --gold: #9a7320;
  --danger: #b8453f;
  /* 5.9:1 as text on white, and white on it; the hue is the same sky. */
  --accent-text: var(--user-accent-deep, #0369a1);
  --accent-fill: var(--user-accent-deep, #0369a1);
  --accent-hover: var(--user-accent-deeper, #075985);
  --gold-text: #8a6519;    /* 5.3:1 on white */

  --pocket: #e4e2db;
  --pocket-line: #cfccc3;

  --glass: color-mix(in srgb, var(--bg-2) 76%, transparent);

  --shadow-1: 0 1px 2px rgba(20,24,32,.10);
  --shadow-2: 0 10px 30px rgba(20,24,32,.12), 0 1px 3px rgba(20,24,32,.08);
  --shadow-card: 0 4px 14px rgba(20,24,32,.18);
  --glow-accent:
    0 0 0 1px color-mix(in srgb, var(--accent) 40%, transparent),
    0 8px 26px color-mix(in srgb, var(--accent) 16%, transparent);
}

/* -------------------------------------------------------------------- base */
* { box-sizing: border-box; }

/* An explicit `display` would otherwise beat the `hidden` attribute and leave
   invisible overlays intercepting clicks. */
[hidden] { display: none !important; }

html, body {
  height: 100%;
  margin: 0;
}
/* The ground behind the page, including any strip iOS leaves past the layout
   viewport. The installed app sizes itself to the screen in pwa.css. */
html { background: var(--bg); }

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Each view scrolls inside itself, so the page never scrolls sideways. */
  overflow: hidden;
}

h1, h2, h3, h4 { font-weight: 650; letter-spacing: -0.02em; line-height: 1.2; }

button, input, select, textarea { font: inherit; color: inherit; }

/* Anything that shows a number, an amount or a count lines up column by column. */
.tabular, .num, [data-count] { font-variant-numeric: tabular-nums; }

::selection { background: color-mix(in srgb, var(--accent) 34%, transparent); }

/* One focus ring for the whole app. `:where()` keeps it at zero specificity, so
   a component with a ring of its own still wins. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* On cover art there is no contrast for an accent ring, so it turns white. */
:where(.on-art, .slot-card, .bcard-cover, .pocket) :where(a, button):focus-visible,
:where(a, button).on-art:focus-visible {
  outline-color: rgba(255,255,255,.92);
}

/* scrollbars: 10px, quiet thumb, no track */
* { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb {
  background: var(--line); border-radius: var(--radius-pill);
  border: 2px solid transparent; background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--ink-3); background-clip: padding-box; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-corner { background: transparent; }

/* ============================================================ motion levels
   motion.js writes `data-motion` on <html>: full · reduced · off. Everything
   decorative is gated on it, in JS and here. */

/* Off: instant state changes, nothing animates anywhere. */
:root[data-motion="off"] *,
:root[data-motion="off"] *::before,
:root[data-motion="off"] *::after {
  animation-duration: 1ms !important;
  animation-delay: 0ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 1ms !important;
  transition-delay: 0ms !important;
  scroll-behavior: auto !important;
}
:root[data-motion="off"] .turn-layer { display: none !important; }

/* Reduced: entrances become short fades; nothing loops, drifts, tilts or shimmers. */
:root[data-motion="reduced"] {
  --t-2: 120ms;
  --t-3: 120ms;
  --t-4: 120ms;
  --ease-spring: var(--ease-out);
}
:root[data-motion="reduced"] [data-ambient],
:root[data-motion="reduced"] .mo-sparkles { display: none !important; }
:root[data-motion="reduced"] [data-foil]::after,
:root[data-motion="reduced"] [data-foil]::before,
:root[data-motion="reduced"] .mo-glare { opacity: 0 !important; }
:root[data-motion="reduced"] [data-tilt] { transform: none !important; }
/* The rail still shows its content — it just stops moving. */
:root[data-motion="reduced"] .mo-marquee-shell,
:root[data-motion="off"] .mo-marquee-shell { transform: none !important; }
:root[data-motion="reduced"] [data-marquee] { overflow-x: auto; }
/* Entrances become a short fade, with no travel. */
:root[data-motion="reduced"] [data-reveal] { transform: none; }

/* The page turn (pageturn.js) becomes a cross-fade: the leaf lies flat and the
   whole layer — copies of the pages you were on — fades off the new ones. */
:root[data-motion="reduced"] .turn-leaf { transform: none !important; }
:root[data-motion="reduced"] .turn-shade,
:root[data-motion="reduced"] .turn-gloss { opacity: 0 !important; }
:root[data-motion="reduced"] .turn-layer { animation: mo-view-out 140ms var(--ease-out) forwards; }

/* Someone who has never touched the preference and asks the system for less
   motion gets the same, before motion.js has even run. */
@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion="full"]) .turn-leaf { transform: none !important; }
}

/* ---------------------------------------------------------------- buttons */
.btn {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  height: 32px; padding: 0 13px;
  background: var(--bg-3); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  box-shadow: var(--glass-edge);
  font-size: 13.5px; font-weight: 550; white-space: nowrap;
  cursor: pointer;
  transition: background var(--t-1) var(--ease-out), border-color var(--t-1) var(--ease-out),
    color var(--t-1) var(--ease-out), transform var(--t-1) var(--ease-out),
    box-shadow var(--t-1) var(--ease-out);
}
.btn:hover {
  background: color-mix(in srgb, var(--ink) 7%, var(--bg-3));
  border-color: color-mix(in srgb, var(--ink) 18%, var(--line));
  transform: translateY(-1px);
  box-shadow: var(--glass-edge), var(--shadow-1);
}
.btn:active { transform: translateY(1px); box-shadow: var(--glass-edge); }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .45; pointer-events: none; }

.btn-icon { width: 32px; padding: 0; }

/* Touch: pointers are fingers, so every button clears the 36px target size,
   and the small ones that sit in lists and rows reach it too: a small button,
   a menu's rows, a segmented option, a chip. min-height, so a component that
   sets a smaller height still grows to it. */
@media (hover: none) {
  .btn { min-height: 36px; }
  .btn-sm { min-height: 36px; }
  .pmenu-item { min-height: 42px; }
  .seg-opt { min-height: 36px; }
  .chip { min-height: 34px; }
}
.btn-sm { height: 27px; padding: 0 10px; font-size: 12.5px; }
.btn-sm.btn-icon { width: 27px; padding: 0; }
.btn-lg { height: 40px; padding: 0 20px; font-size: 14.5px; border-radius: var(--radius-pill); }
a.btn { text-decoration: none; }

.btn-ghost { background: transparent; border-color: transparent; box-shadow: none; font-weight: 550; }
.btn-ghost:hover { background: var(--bg-3); border-color: var(--line); }

/* the one saturated control: accent fill with a soft accent glow */
.btn-primary {
  background: var(--accent-fill); color: var(--accent-ink);
  border-color: transparent; border-radius: var(--radius-pill);
  padding: 0 16px; font-weight: 650;
  box-shadow: 0 1px 0 rgba(255,255,255,.22) inset,
    0 6px 20px color-mix(in srgb, var(--accent) 22%, transparent);
}
.btn-primary:hover {
  background: var(--accent-hover);
  border-color: transparent;
  box-shadow: 0 1px 0 rgba(255,255,255,.28) inset,
    0 10px 28px color-mix(in srgb, var(--accent) 30%, transparent);
}
.btn-primary.btn-icon { padding: 0; }

.btn-danger { color: var(--danger); }
.btn-danger:hover {
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 14%, var(--bg-3));
  border-color: color-mix(in srgb, var(--danger) 45%, var(--line));
}

.btn-toggle.is-on {
  background: var(--accent-fill); color: var(--accent-ink); border-color: transparent;
  box-shadow: var(--glow-accent);
}

/* ----------------------------------------------------------- inputs, selects */
.input {
  height: 34px; width: 100%; padding: 0 10px;
  background: var(--bg); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  min-width: 0;
  transition: border-color var(--t-1) var(--ease-out), box-shadow var(--t-1) var(--ease-out);
}
.input::placeholder { color: var(--ink-3); }
.input:hover { border-color: color-mix(in srgb, var(--ink) 18%, var(--line)); }
.input:focus, .input:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}
.input-sm { height: 28px; width: auto; font-size: 13px; padding: 0 8px; }
select.input { cursor: pointer; }
textarea.input { height: auto; padding: 8px 10px; line-height: 1.45; resize: vertical; }

.check {
  display: flex; align-items: center; gap: 7px;
  font-size: var(--fs-sm); color: var(--ink-2); cursor: pointer; padding: 2px 0;
}
.check input { accent-color: var(--accent); }

/* ------------------------------------------------------------------- chips */
/* Rarity, type and facet chips: a pill that fills with accent when it is on. */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  height: 26px; padding: 0 11px;
  background: var(--bg-3); color: var(--ink-2);
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  box-shadow: var(--glass-edge);
  font-size: 12px; font-weight: 600; white-space: nowrap;
  cursor: pointer;
  transition: background var(--t-1) var(--ease-out), border-color var(--t-1) var(--ease-out),
    color var(--t-1) var(--ease-out);
}
.chip:hover { color: var(--ink); border-color: color-mix(in srgb, var(--ink) 20%, var(--line)); }
.chip[aria-pressed="true"], .chip.is-on {
  color: var(--ink);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
}
.chip[aria-pressed="true"]::before, .chip.is-on::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 6px color-mix(in srgb, var(--accent) 70%, transparent);
}
.chip-count { color: var(--ink-3); font-variant-numeric: tabular-nums; font-weight: 550; }
/* A row of chips that turn kinds on and off (set-products.js). */
.kind-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.kind-chips .chip { height: 32px; padding: 0 13px; font-size: 13px; }

/* ------------------------------------------------------------------ badges */
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--fs-micro); font-weight: 600; letter-spacing: .01em;
  padding: 3px 10px; border-radius: var(--radius-pill);
  background: var(--bg-3); color: var(--ink-2);
  border: 1px solid var(--line); box-shadow: var(--glass-edge);
  white-space: nowrap;
}
.badge.is-demo { color: var(--gold-text); border-color: color-mix(in srgb, var(--gold) 40%, transparent); background: color-mix(in srgb, var(--gold) 10%, transparent); }
.badge.is-live { color: var(--accent-text); border-color: color-mix(in srgb, var(--accent) 40%, transparent); background: color-mix(in srgb, var(--accent) 10%, transparent); }

/* ------------------------------------------------------- surfaces and glass */
.surface {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--glass-edge), var(--shadow-1);
}
.surface-raise { transition: box-shadow var(--t-2) var(--ease-out), transform var(--t-2) var(--ease-out); }
.surface-raise:hover { box-shadow: var(--glass-edge), var(--shadow-2); transform: translateY(-2px); }

/* Top nav, dialogs, menus, toasts. A hairline border and a 1px inner top light. */
.glass {
  background: var(--glass);
  backdrop-filter: blur(var(--glass-blur)) saturate(1.2);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.2);
  border: 1px solid var(--line);
  box-shadow: var(--glass-edge), var(--shadow-2);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass, .topbar, .modal, .pmenu, .toast { background: var(--bg-2); }
}

/* ---------------------------------------------------------- dialog shell */
.modal-root {
  position: fixed; inset: 0; z-index: 60;
  display: grid; place-items: center; padding: 20px;
  background: color-mix(in srgb, #05060a 62%, transparent);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  animation: mo-backdrop-in var(--t-2) var(--ease-out);
}
:root[data-theme="light"] .modal-root { background: color-mix(in srgb, #1a1d24 38%, transparent); }

/* Every dialog: glass, with the accent washed softly into its top corners (the
   card dialog washes its own card's colours there instead: dialogs.css). The
   wash is the box's own background, so it stays put while the body scrolls. */
.modal {
  background:
    radial-gradient(85% 55% at 0% 0%, color-mix(in srgb, var(--accent) 11%, transparent), transparent 70%),
    radial-gradient(65% 45% at 100% 0%, color-mix(in srgb, var(--accent-2) 8%, transparent), transparent 70%),
    var(--glass);
  backdrop-filter: blur(var(--glass-blur)) saturate(1.2);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.2);
  border: 1px solid color-mix(in srgb, var(--ink) 9%, var(--line));
  border-radius: 20px;
  box-shadow: var(--glass-edge), var(--shadow-2), 0 34px 80px -34px rgba(0, 0, 0, .7);
  width: min(520px, 100%); max-height: 86vh; overflow: auto;
  animation: mo-dialog-in var(--t-2) var(--ease-out);
}
:root[data-theme="light"] .modal { box-shadow: var(--glass-edge), var(--shadow-2), 0 30px 70px -34px rgba(20, 24, 32, .35); }
@keyframes mo-backdrop-in { from { opacity: 0; } }
@keyframes mo-dialog-in {
  from { opacity: 0; transform: translateY(8px) scale(.98); }
}

/* ------------------------------------------------------------------ toasts */
/* A toast never stands in the way of a click (on a dialog's Save, say); only one
   with a button takes the pointer. */
.toast-stack {
  position: fixed; right: 16px; bottom: 16px; z-index: 80;
  display: flex; flex-direction: column; gap: 8px; align-items: flex-end;
  pointer-events: none;
}
/* While a dialog is open over the corner, ui.js (placeToasts) lifts the stack to
   just above the dialog's foot and centres it there, so its buttons stay in view. */
/* Its own width (up to the max-width ui.js gives it): placed by its left edge
   at the dialog's centre, it would otherwise only have the half of the screen
   right of that to wrap in — a toast over a phone's full-screen dialog came
   out half as wide as the screen. */
.toast-stack.is-over-dialog { align-items: center; transform: translateX(-50%); width: max-content; }
.toast {
  max-width: 340px; padding: 9px 14px;
  font-size: 13px;
  background: var(--glass);
  backdrop-filter: blur(var(--glass-blur)) saturate(1.2);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.2);
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-pill);
  box-shadow: var(--glass-edge), var(--shadow-2);
  animation: mo-toast-in var(--t-2) var(--ease-out);
}
.toast.is-error { border-left-color: var(--danger); }
.toast.has-action {
  pointer-events: auto;
  display: flex; align-items: center; gap: 12px;
  padding: 6px 6px 6px 14px;
}
.toast-t { flex: 1; min-width: 0; }
.toast-btn {
  flex: none; height: 28px; padding: 0 12px;
  font-size: 12.5px; font-weight: 650; color: var(--accent-text);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background var(--t-1) var(--ease-out), border-color var(--t-1) var(--ease-out);
}
.toast-btn:hover { background: color-mix(in srgb, var(--accent) 22%, transparent); border-color: var(--accent); }
@keyframes mo-toast-in { from { opacity: 0; transform: translate(8px, 10px); } }

/* --------------------------------------------------------------- skeletons */
/* Only ever on screen while something is loading. */
.skeleton {
  position: relative; overflow: hidden;
  background: var(--bg-3);
  border-radius: var(--radius-sm);
  color: transparent;
}
.skeleton::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(100deg, transparent 30%, var(--hairline) 48%, transparent 62%);
  transform: translateX(-100%);
  animation: mo-sheen 2s linear infinite;
}
@keyframes mo-sheen { to { transform: translateX(100%); } }
:root[data-motion="reduced"] .skeleton::after,
:root[data-motion="off"] .skeleton::after { animation: none; opacity: .4; transform: none; }

/* =========================================================== motion effects
   The CSS half of motion.js. Each effect is opt-in by data attribute and only
   paints once the module has switched it on. */

/* scroll reveal — rises 12px and fades over --t-3, staggered by motion.js */
[data-reveal] {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity var(--t-3) var(--ease-out), transform var(--t-3) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}
[data-reveal].is-revealed { opacity: 1; transform: none; }
/* Nothing may stay invisible because the observer never ran (no JS, no support). */
:root[data-motion="off"] [data-reveal],
.no-motion [data-reveal] { opacity: 1; transform: none; transition: none; }

/* 3D tilt — motion.js writes --rx / --ry; the glare layer is painted here */
[data-tilt] {
  --rx: 0deg; --ry: 0deg; --tilt-scale: 1;
  transform: perspective(900px) rotateX(var(--rx)) rotateY(var(--ry)) scale(var(--tilt-scale));
  transform-style: preserve-3d;
  transition: transform var(--t-3) var(--ease-spring);
}
[data-tilt].is-tilting { transition: none; }
.mo-glare {
  position: absolute; inset: 0; z-index: 4; pointer-events: none;
  border-radius: inherit; opacity: 0;
  background: radial-gradient(circle at var(--mx, 50%) var(--my, 50%),
    rgba(255,255,255,.38), rgba(255,255,255,0) 55%);
  transition: opacity var(--t-2) var(--ease-out);
}
[data-tilt].is-tilting .mo-glare { opacity: 1; }

/* foil sheen — the iridescent wash, masked to a soft ellipse under the pointer */
[data-foil] { position: relative; }
[data-foil]::after {
  content: ""; position: absolute; inset: 0; z-index: 3;
  pointer-events: none; border-radius: inherit;
  background: var(--foil);
  background-size: 260% 260%;
  background-position: calc(var(--mx, 50%) * 0.6 + 20%) calc(var(--my, 50%) * 0.6 + 20%);
  mix-blend-mode: color-dodge;
  opacity: 0;
  -webkit-mask-image: radial-gradient(ellipse 62% 48% at var(--mx, 50%) var(--my, 50%), #000 0%, rgba(0,0,0,.35) 48%, transparent 76%);
  mask-image: radial-gradient(ellipse 62% 48% at var(--mx, 50%) var(--my, 50%), #000 0%, rgba(0,0,0,.35) 48%, transparent 76%);
  transition: opacity var(--t-3) var(--ease-out);
}
[data-foil].is-foil::after { opacity: .18; }
/* a thin specular streak that follows the pointer across the art */
[data-foil].is-foil::before {
  content: ""; position: absolute; inset: 0; z-index: 3;
  pointer-events: none; border-radius: inherit;
  background: linear-gradient(105deg, transparent 42%, rgba(255,255,255,.26) 50%, transparent 58%);
  background-size: 220% 100%;
  background-position: calc(100% - var(--mx, 50%)) 0;
  opacity: .8;
}
/* A card the collection is missing never shimmers. */
[data-foil].is-missing::after, [data-foil].is-missing::before,
.slot-card.is-missing[data-foil]::after { content: none; }

/* ambient aurora — a canvas behind content, painted by motion.js */
[data-ambient] {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%;
  pointer-events: none;
  opacity: .35;
}
:root[data-theme="light"] [data-ambient] { opacity: .18; }

/* marquee — motion.js duplicates the track and moves it by transform */
[data-marquee] { overflow: hidden; -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent); mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent); }
.mo-marquee-track { display: flex; width: max-content; will-change: transform; }

/* sparkle / celebrate — a canvas that cleans itself up */
.mo-sparkles { position: fixed; z-index: 85; pointer-events: none; }

/* View transition — the router fades the incoming view up. Opacity only: a
   transform on a view would make it the containing block for the page-turn
   layer and the drag ghost, which are `position: fixed`. */
.mo-view-in { animation: mo-view-in var(--t-2) var(--ease-out); }
@keyframes mo-view-in { from { opacity: 0; } }
@keyframes mo-view-out { to { opacity: 0; } }

/* count-up: a rolling number must not make its row jump about */
[data-count] { font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------------- print */
@media print {
  /* No glass, no shadow, no ambient: paper is paper. */
  .glass, .modal, .toast, .topbar { background: #fff !important; backdrop-filter: none !important; box-shadow: none !important; }
  [data-ambient], .mo-sparkles, .mo-glare { display: none !important; }
  [data-reveal] { opacity: 1 !important; transform: none !important; }
  [data-foil]::after, [data-foil]::before { content: none !important; }
  .skeleton::after { display: none !important; }
}

/* Native controls (a tick box, a select's list, a scrollbar, a date field) in
   the theme's own colours: in dark dialogs an unticked box was bright white. */
:root { color-scheme: dark; }
:root[data-theme="light"] { color-scheme: light; }
