/* Global Theme CSS Variables */

/* ────────────────────────────────────────────────────────────────────────
   Every colour in the application is declared here, exactly once. A scoped
   sheet references a token; it never restates a literal, and it never carries
   a `var(--x, #fallback)` fallback — a fallback is a second declaration of the
   same colour in a place review does not look, which is how five different
   values for --accent-color once ended up in the tree.

   Colours that need alpha variants are declared as an `-rgb` channel triple
   and the solid token is derived from it, so `rgba(var(--x-rgb), 0.3)` at a
   call site sets opacity — a per-site design decision — without restating the
   hue. CssColorTokenTests enforces both rules.
   ──────────────────────────────────────────────────────────────────────── */
:root {
    /* Channel triples: the hues that appear at more than one opacity. */
    --shadow-rgb: 0, 0, 0;
    --highlight-rgb: 255, 255, 255;
    --color-gold-rgb: 201, 162, 39;
    --color-danger-rgb: 220, 53, 69;
    --mm-accent-red-rgb: 146, 38, 16;
    --mm-text-header-rgb: 88, 23, 13;
    --mm-bg-parchment-rgb: 253, 241, 220;
    --dh-bg-dark-rgb: 65, 37, 85;
    --dh-bg-light-rgb: 245, 240, 248;
    --dh-accent-gold-rgb: 230, 198, 75;
    --dh-accent-teal-rgb: 132, 205, 194;
    --system-dnd-rgb: 176, 69, 80;
    --system-dh-rgb: 122, 106, 154;
    --color-info-rgb: 0, 123, 255;
    --color-purple-rgb: 111, 66, 193;
    --difficulty-easy-bar-rgb: 40, 167, 69;
    --difficulty-moderate-bar-rgb: 255, 193, 7;
    --brand-kofi-rgb: 255, 187, 0;
    --brand-patreon-rgb: 255, 66, 77;

    /* Neutrals */
    --color-white: #fff;
    --color-black: #000;
    --ink-navy: #1a1a2e;

    /* The pale input surface of light mode, and the pale value the three
       feature-suggestion badges use as their TEXT colour. --bg-input itself is
       mode-dependent (paper block below). */
    --bg-input-light: #faf3e3;

    /* Text Colors */
    --text-primary: #1a1a1a;
    --text-header: #3d2b1f;
    --text-secondary: #5c4a3a;

    /* Accent Colors */
    --color-gold-pale: #e8d59a;
    /* Gold for headings on a light page (both systems' light mode): 6.03:1 on
       the Daggerheart page, 5.40:1 on its card, 6.06:1 / 5.49:1 on D&D parchment and card. */
    --gold-ink: #7a5410;
    --color-danger: #8b2323;
    --color-danger-light: #a52a2a;
    --color-danger-dark: #6b1a1a;
    --color-danger-bg: rgba(var(--color-danger-rgb), 0.1);
    --color-copper: #b87333;
    --color-copper-light: #c98544;
    --color-copper-dark: #9a6528;
    --color-copper-deep: #7a4a1a;

    /* Patreon brand (SubscribeCta, PatreonGate) — not a theme colour. */
    --color-patreon: #f96854;

    /* Navigation */
    --nav-bg: var(--spine);
    --nav-text: rgb(var(--ink-pale-rgb));
    --nav-text-muted: var(--ink-muted-pale);

    /* Borders */
    --border-color: #8b7355;
    --border-light: #a89070;
    /* The boundary of an interactive control, which WCAG 1.4.11 holds to 3:1
       against both sides of the edge — unlike a decorative card border. Follows
       --border-color everywhere it already clears that; Daggerheart's light mode
       overrides it below, where --dh-border-light does not. */
    --input-border: var(--border-color);

    /* Adaptive text colors (overridden per theme + dark/light mode) */
    --text-color-on-light-bg: var(--text-primary);
    --text-muted-on-light-bg: var(--text-secondary);

    /* Button variant colors */
    --color-warning: #e6a623;
    --color-warning-light: #f0b83a;
    --color-warning-dark: #c98d1d;
    --color-success: #3a7d4c;
    --color-success-light: #3d8150;
    --color-success-dark: #2f6b3f;
    --color-secondary: var(--border-light);
    --color-secondary-light: #b9a080;
    --color-secondary-dark: var(--border-color);
    --on-warning-text: var(--text-primary);
    --on-success-text: var(--color-white);
    --on-danger-text: var(--color-white);
    --on-secondary-text: var(--text-primary);

    /* Compare-table stat highlighting. Values are tuned for the dark card
       backgrounds (.light-mode overrides them further down); both clear WCAG AA
       against --card-bg and the --editor-highlight-bg alternating rows in each
       theme. Not --color-success/--color-danger: those are fill colours meant to
       carry --on-*-text on top, these are text colours on the card. */
    --compare-stat-highest: #6ecf78;
    --compare-stat-lowest: #ef8a8a;
    /* Danger as TEXT: --color-danger is a fill for --on-danger-text and is 2:1 on dark paper. */
    --danger-ink: var(--compare-stat-lowest);

    /* Focus & misc */

    /* ────────────────────────────────────────────────────────────────────
       Defaults for the tokens the paper blocks below do not derive. Pages
       without a system (Home, legal, auth, admin) carry no theme class and
       render from :root alone.
       ──────────────────────────────────────────────────────────────────── */
    --card-actions-bg: rgba(var(--shadow-rgb), 0.15);
    --hover-shadow: rgba(var(--shadow-rgb), 0.15);
    --hover-bg: rgba(var(--highlight-rgb), 0.06);
    --section-shadow: rgba(var(--shadow-rgb), 0.15);
    --print-card-shadow: rgba(var(--shadow-rgb), 0.25);
    --modal-backdrop-bg: rgba(var(--shadow-rgb), 0.6);
    --surface-muted: #e5e7eb;
    --remove-btn-gradient: linear-gradient(180deg, var(--color-danger-light) 0%, var(--color-danger) 100%);
    --remove-btn-border: var(--color-danger-dark);
    --remove-btn-hover: linear-gradient(180deg, var(--color-danger) 0%, var(--color-danger-dark) 100%);

    /* System accents on unthemed surfaces (Home cards, builder landing). */
    --system-dnd-accent: #d05560;
    --system-dh-accent: #9a8aba;

    /* Party-role chips (EncounterPartyConfig): fixed role identities, not theme colours. */
    --role-caster: #9b59b6;
    --role-healer: #2ecc71;
    --role-physical: #e67e22;

    /* Damage-type badges (MonsterDefensesSection): vulnerability blue,
       resistance green, immunity red — fixed identities, not theme colours. */
    --defense-badge-start: #2c5aa0;
    --defense-badge-end: #3a6db8;
    --defense-badge-border: #234b85;
    --resist-badge-start: #4a7c59;
    --resist-badge-end: #5a9a6b;
    --resist-badge-border: #3d6b4a;
    --immune-badge-end: #b83020;

    /* Objective-type chips (EncounterScenarioPanel) and assessment verdicts. */
    --color-info: rgb(var(--color-info-rgb));
    --color-purple: rgb(var(--color-purple-rgb));
    --assessment-positive: #1f7a3a;
    --assessment-negative: #b02a37;

    /* Field-validation outline (base.css). */
    --valid-outline: #26b050;

    /* Support-platform brands. Patreon ships two reds: the coral it uses on
       buttons (--color-patreon) and the flat red on promo tiles. */
    --brand-kofi: rgb(var(--brand-kofi-rgb));
    --brand-kofi-light: #ffdd00;
    --brand-kofi-text: var(--ink-navy);
    --brand-patreon: rgb(var(--brand-patreon-rgb));
    --brand-patreon-dark: #ff2a35;

    /* Patron-tier accents (TierGateBadge, Home tier cards). Their own ramp:
       the --tier-badge-* tokens above are the Patreon-palette badges. */
    --tier-gate-insider: #6366f1;
    --tier-gate-curator: #f59e0b;
    --tier-gate-supporter: #10b981;
    --tier-insider-heading: #d97706;

    /* ── Plain-print greys ──────────────────────────────────────────────
       The monochrome print modes (.print-plain cards, .eprint-* sheets) are
       theme-independent by design — they render the same on any surface, so
       they read these rather than the themed tokens. Two weights each,
       because the card sheets and the encounter/environment sheets were
       drawn against different paper.
       ──────────────────────────────────────────────────────────────────── */
    --print-text: #444;
    --print-text-soft: #666;
    --print-text-muted: #555;
    --print-text-strong: #333;
    --print-rule: #aaa;
    --print-rule-strong: #999;
    --print-rule-light: #ddd;
    --mm-print-label: #7a4a35;
    --mm-print-rule-soft: #c4a882;
    --mm-print-row-tint: rgba(var(--mm-text-header-rgb), 0.05);
    --mm-print-panel-bg: rgba(var(--mm-bg-parchment-rgb), 0.8);
    --dh-print-label: #5a3d6b;
    --dh-print-rule-soft: #b8a0c8;
    --dh-print-row-tint: rgba(var(--dh-bg-dark-rgb), 0.05);
    --dh-print-panel-bg: rgba(var(--dh-bg-light-rgb), 0.8);
    --dh-card-header-text: #3d2854;
    --dh-card-text: #3d2b4a;
    --dh-card-accent-text: #5c2a44;

    /* Printed difficulty readout (EncounterContent). Its own ramp: the badge
       tokens below are fills meant to carry text on top, these are ink. */
    --print-difficulty-easy: var(--color-success);
    --print-difficulty-moderate: #c8a227;
    --print-difficulty-hard: #c85c27;
    --print-difficulty-overwhelming: #c82727;

    /* Typography */
    --font-header: 'IM Fell English', serif;
    --font-body: 'EB Garamond', serif;
    --font-label: 'IM Fell English SC', serif;
    /* Torn bottom edge of a grid card (GlassLayer Deckled): solid body above a 12px torn strip. */
    --deckle-mask: linear-gradient(var(--color-black), var(--color-black)) top / 100% calc(100% - 11px) no-repeat, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 12' preserveAspectRatio='none'%3E%3Cpath fill='white' d='M0 0H400V6 L392 9 384 5 375 10 366 6 357 9 348 5 339 10 330 7 321 10 312 5 303 9 294 6 285 10 276 5 267 9 258 6 249 10 240 5 231 9 222 7 213 10 204 5 195 9 186 6 177 10 168 5 159 9 150 6 141 10 132 7 123 9 114 5 105 10 96 6 87 9 78 5 69 10 60 6 51 9 42 5 33 10 24 6 15 9 6 5 0 8Z'/%3E%3C/svg%3E") bottom / 100% 12px no-repeat;
    /* The Monster Manual stat block and its print chrome keep their own face. */
    --mm-font-header: 'Cinzel', serif;
    /* Print sheets keep the system's own heading face (.dh-theme swaps it). */
    --print-font-header: var(--mm-font-header);

    /* Layout */

    /* Difficulty colours (used by encounter display + builder) */
    --difficulty-easy-bg: #d4edda;
    --difficulty-easy-text: #155724;
    --difficulty-moderate-bg: #fff3cd;
    --difficulty-moderate-text: #856404;
    --difficulty-hard-bg: #f8d7da;
    --difficulty-hard-text: #721c24;
    --difficulty-deadly-bg: var(--difficulty-hard-text);
    --difficulty-deadly-text: var(--color-white);
    --difficulty-easy-bar: rgb(var(--difficulty-easy-bar-rgb));
    --difficulty-moderate-bar: rgb(var(--difficulty-moderate-bar-rgb));
    --difficulty-hard-bar: rgb(var(--color-danger-rgb));
    --difficulty-deadly-bar: var(--difficulty-hard-text);

    /* Patreon tier badge colours (TierBadge). Theme-independent by design:
       they mirror the Patreon tier palette, not the parchment/dark themes.
       Values are the ones AuthStatus and CreatorBadge each hardcoded before
       TierBadge was extracted. TierGateBadge deliberately uses a different
       palette and does not read these. */
    --tier-badge-free-bg: #6c757d;
    --tier-badge-free-text: var(--color-white);
    --tier-badge-supporter-bg: #198754;
    --tier-badge-supporter-text: var(--color-white);
    --tier-badge-curator-bg: #0d6efd;
    --tier-badge-curator-text: var(--color-white);
    --tier-badge-insider-end: #ff8c00;
    --tier-badge-insider-bg: linear-gradient(135deg, var(--difficulty-moderate-bar) 0%, var(--tier-badge-insider-end) 100%);
    --tier-badge-insider-text: var(--color-black);
    --tier-badge-creator-bg: linear-gradient(135deg, #9333ea 0%, #ec4899 100%);
    --tier-badge-creator-text: var(--color-white);

    /* Feature suggestion badge colors */
    --badge-role-bg: var(--color-copper-deep);
    --badge-role-text: var(--bg-input-light);
    --badge-type-bg: #86601f;
    --badge-type-text: var(--bg-input-light);
    --badge-property-bg: #3d7a5e;
    --badge-property-text: var(--bg-input-light);

    /* Classic stat block theme tokens */
    --stat-block-bg: var(--mm-bg-parchment);
    --stat-block-text: #5e2a18;
    --stat-block-rule: var(--mm-accent-red-light);

    /* ────────────────────────────────────────────────────────────────────
       Stat-block / print palettes: the D&D Monster Manual (--mm-*) and
       Daggerheart (--dh-*) card colours.

       These live on :root, NOT inside .dnd-theme / .dh-theme, and that is
       deliberate. A stat block is theme-independent by design — the same
       parchment card in dark mode, light mode, on a cross-system preview
       overlay, and in the OG renderer, whose OgRenderLayout has no theme
       class at all. Scoped to a theme wrapper these tokens resolved to
       nothing on exactly those surfaces, which is why every stat-block and
       print sheet hard-coded the hex literals instead of using them.

       They are namespaced by prefix, so both palettes coexist on :root
       without either shadowing the other. The paper blocks below carry the
       app's own colours; these stay fixed.
       ──────────────────────────────────────────────────────────────────── */

    /* D&D Monster Manual — backgrounds */
    --mm-bg-parchment: rgb(var(--mm-bg-parchment-rgb));

    /* D&D Monster Manual — accents */
    --mm-accent-red: rgb(var(--mm-accent-red-rgb));
    --mm-accent-maroon: #7A200D;
    --mm-accent-red-light: #9c2b1b;

    /* D&D Monster Manual — text */
    --mm-text-header: rgb(var(--mm-text-header-rgb));
    --mm-text-primary: var(--text-primary);
    --mm-text-secondary: var(--text-secondary);

    /* Daggerheart — backgrounds */
    --dh-bg-dark: rgb(var(--dh-bg-dark-rgb));
    --dh-bg-medium: #5a3a6e;
    --dh-bg-light: rgb(var(--dh-bg-light-rgb));

    /* Daggerheart — accents */
    --dh-accent-gold: rgb(var(--dh-accent-gold-rgb));
    --dh-accent-teal: rgb(var(--dh-accent-teal-rgb));
    --dh-accent-teal-dark: #5fb5a8;

    /* Daggerheart — text */
    --dh-text-header: #2d1a38;
    --dh-text-primary: var(--text-primary);

    /* Daggerheart — borders */
    --dh-border-purple: #6b4a7d;
    --dh-border-light: #a89ab0;

    /* Daggerheart — fonts */
    --dh-font-header: 'Cormorant Garamond', 'Cinzel', serif;

    /* ── Stat-block cover touches (screen only) ──
       The antique gold of the on-screen stat blocks: Daggerheart's rule lines,
       section underlines and adversary-type ornaments, and D&D's top and bottom
       bars. On :root with the palettes because the stat blocks are
       theme-independent. Darker than --dh-accent-gold (1.6:1 there) so a rule or
       ornament stays a visible graphic: 3.79:1 on --dh-bg-light, 3.81:1 on
       --mm-bg-parchment (WCAG 1.4.11 asks 3:1). Print never reads it. */
    --sb-rule-gold: #9a7518;

    /* ── Paper & ink palette constants ──
       Mode-independent values, each declared once. The paper blocks below pick
       from them per mode. */
    --paper-vellum: #efe3c8;
    --paper-night: #1d1610;
    --ink-pale-rgb: 234, 220, 188;
    --ink-dark-rgb: 43, 28, 16;
    --ink-muted-pale: #b39d7b;
    --ink-muted-dark: #6a5238;
    --line-pale-rgb: 179, 157, 123;
    --line-dark-rgb: 90, 64, 38;
    --umber-deep: #7a4a22;
    --umber-glow: #c99a62;
    --dnd-glow: #e0674f;
    --dh-violet: #5b3a8c;
    --dh-violet-glow: #a585d6;
    /* The ink spine (top bar, footer): dark in both modes. */
    --spine: #17110b;

    /* The page ground: a fine grain and a low-frequency mottle (feTurbulence),
       laid over --page-bg by .main-content and blended per mode. */
    --paper-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='260'%3E%3Cfilter id='paper-grain'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .25 0 0 0 0 .18 0 0 0 0 .1 0 0 0 .22 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23paper-grain)'/%3E%3C/svg%3E");
    --paper-mottle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='600'%3E%3Cfilter id='paper-stain'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.012' numOctaves='4' seed='7'/%3E%3CfeColorMatrix values='0 0 0 0 .35 0 0 0 0 .24 0 0 0 0 .1 0 0 0 .16 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23paper-stain)'/%3E%3C/svg%3E");

    /* ── Paper ──
       The page the app prints on, in one place. These are the only physical
       units in the tree (CssPhysicalUnitTests enforces it) because they are the
       only measurements that answer to something outside the browser, and
       because they are related: the printable width IS the sheet less its
       margins, and a card measured against one number while the page frame uses
       another is the defect this block exists to prevent. Three files used to
       spell 194mm and 210mm out separately, kept in step by a comment.

       Swapping A4 for US Letter (215.9 x 279.4mm) is a change to these five
       declarations and nothing else. */
    --print-page-width: 210mm;
    --print-page-height: 297mm;
    --print-page-margin: 8mm;
    --print-printable-width: calc(var(--print-page-width) - 2 * var(--print-page-margin));

    /* The adventure session sheet is a full, read-through handout, not a stat-block
       card framed by its own per-page padding, so its pages carry a true document
       margin on all four edges (applied through a named @page box). */
    --adv-print-page-margin: 16mm;

    /* Print typography and placeholders, in paper units for the same reason:
       what they size is measured on the sheet, not on a display. */
    --print-body-size: 12pt;
    --print-placeholder-height: 60mm;
}

/* ────────────────────────────────────────────────────────────────────────
   Shape, spacing, elevation and motion scales.

   Colour has one declaration each (above); geometry did not. `border-radius`
   alone was hand-typed as 4/6/8/3/2/5/10/12/16px across the scoped sheets, so
   no two cards rounded alike. These scales give the same discipline to the
   other four axes — a rule references a step, never a raw px, and the app
   snaps to one rhythm.

   They live in their own :root blocks, one axis each, on purpose: radius and
   spacing share literal values (both step through 4/8/12/16px) and
   CssColorTokenTests forbids two tokens with the same literal *inside one
   block* — an alias that drifts. Split by axis, --radius-sm and --space-1 are
   4px in different blocks: two intents that coincide, not one aliased twice.
   Migrate incrementally; a raw-radius guard can follow once the tree is clean.
   ──────────────────────────────────────────────────────────────────────── */
:root {
    /* Corner radius — a paper-sharp 2/3/4px scale; the steps stay distinct. */
    --radius-sm: 2px;      /* chips, inputs, small controls */
    --radius-md: 3px;      /* cards, buttons, panels */
    --radius-lg: 4px;      /* feature tiles, hero cards */
    --radius-pill: 999px;  /* fully rounded (tags, toggles) */
}

:root {
    /* Spacing — a 4px rhythm for padding, margins and gaps. */
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 24px;
    --space-6: 32px;
}

:root {
    /* Foe search row columns from 769px: name · CR/tier · type/role · "+".
       FoeSearchResultItem's rows and FoePickerLayout's sort header both read
       it, so the header labels sit over their column. */
    --foe-result-columns: minmax(0, 3fr) 4.5rem minmax(0, 2fr) 2rem;
}

:root {
    /* Elevation — three layers of depth on the neutral --shadow-rgb triple
       (declared above), so they carry no colour literal and read the same on
       any theme; the warmer per-theme --hover-shadow / --section-shadow stay
       where a surface wants its own system's warmth.
         e1 resting card · e2 hover / popover · e3 modal / lifted */
    --elevation-1: 0 1px 2px rgba(var(--shadow-rgb), 0.35);
    --elevation-2: 0 4px 12px rgba(var(--shadow-rgb), 0.45);
    --elevation-3: 0 12px 28px rgba(var(--shadow-rgb), 0.55);

    /* One keyboard focus ring, themed through --focus-shadow so it is the
       accent of whatever theme is worn. */
    --focus-ring: 0 0 0 3px var(--focus-shadow);

    /* Motion — two durations. Name the properties that move rather than
       `transition: all`, which also animates layout and is a jank source. */
    --transition-fast: 120ms ease;
    --transition-base: 200ms cubic-bezier(0.2, 0.7, 0.3, 1);
}

/* ========================================
   Paper & ink
   ======================================== */
/* Dark is the default; .light-mode flips it. Declaring color-scheme makes the
   browser paint its OWN widgets to match (the <option> popup, date picker,
   autofill wash, scrollbars). Everything here is derived from the same
   element's primitives, so each mode block restates the derived tokens: a
   custom property resolves var() where it is declared, and a value inherited
   from :root would stay dark inside .light-mode. */
:root {
    color-scheme: dark;
    --ink-rgb: var(--ink-pale-rgb);
    --line-rgb: var(--line-pale-rgb);
    --page-bg: var(--paper-night);
    --text-color: rgb(var(--ink-rgb));
    --text-muted: var(--ink-muted-pale);
    --card-bg: rgb(34, 26, 18);
    --bg-input: rgb(44, 33, 23);
    --card-border: rgba(var(--line-rgb), 0.55);
    --sidebar-bg: rgba(20, 15, 10, 0.7);
    --side-ink: var(--text-color);
    --side-border: rgba(var(--line-rgb), 0.3);
    --side-active-bg: rgba(var(--ink-rgb), 0.08);
    --ground-blend: normal;
    --art-filter: sepia(0.7) brightness(0.85);
    --wash-rgb: 214, 190, 150;
    --bg-main: var(--page-bg);
    --bg-card: var(--card-bg);
    --text-header: var(--text-color);
    --dropdown-bg: var(--card-bg);
    --dropdown-border: var(--card-border);
    --dropdown-text: var(--text-color);
    --on-primary-text: var(--page-bg);
    --on-accent-text: var(--page-bg);
    --on-info-text: var(--page-bg);
    --control-glass-bg: color-mix(in srgb, var(--bg-input) 45%, transparent);
    /* Glass cards (GlassLayer): see-through paper over the art behind, blurred,
       darkened and warmed; the border is the glass line, the shadow a ring.
       Opacity and --card-glass-muted are set so text and muted text hold 4.5:1 over the
       brightest (dark) / darkest (light) local art tone; links (--rubric) only over typical art. */
    --card-glass-bg: rgba(34, 26, 18, 0.4);
    --card-glass-blur: blur(6px) brightness(0.55) sepia(0.4);
    --card-glass-muted: color-mix(in srgb, var(--text-color) 80%, var(--text-muted));
    --card-glass-sheen: none;
    --card-glass-rim: 0 0 transparent;
    --card-glass-border: rgba(var(--line-rgb), 0.4);
    --card-glass-shadow: 0 0 0 4px rgba(20, 15, 10, 0.25), 0 0 0 5px rgba(var(--line-rgb), 0.18), 0 8px 22px rgba(var(--shadow-rgb), 0.4);
}

.light-mode {
    color-scheme: light;
    --ink-rgb: var(--ink-dark-rgb);
    --line-rgb: var(--line-dark-rgb);
    --page-bg: var(--paper-vellum);
    --text-color: rgb(var(--ink-rgb));
    --text-muted: var(--ink-muted-dark);
    --card-bg: rgb(244, 234, 210);
    --bg-input: var(--bg-input-light);
    --card-border: rgba(var(--line-rgb), 0.55);
    --sidebar-bg: rgba(230, 214, 180, 0.55);
    --side-ink: var(--text-color);
    --side-border: rgba(var(--line-rgb), 0.3);
    --side-active-bg: rgba(var(--ink-rgb), 0.12);
    --ground-blend: multiply;
    --art-filter: sepia(0.85) contrast(1.05) brightness(1.05);
    --wash-rgb: 59, 38, 20;
    --bg-main: var(--page-bg);
    --bg-card: var(--card-bg);
    --text-header: var(--text-color);
    --dropdown-bg: var(--card-bg);
    --dropdown-border: var(--card-border);
    --dropdown-text: var(--text-color);
    --on-primary-text: var(--page-bg);
    --on-accent-text: var(--page-bg);
    --on-info-text: var(--page-bg);
    --control-glass-bg: color-mix(in srgb, var(--bg-input) 70%, transparent);
    --card-glass-bg: rgba(244, 234, 210, 0.6);
    --card-glass-blur: blur(5px) brightness(1.18) sepia(0.25);
    --card-glass-muted: color-mix(in srgb, var(--text-color) 80%, var(--text-muted));
    --card-glass-sheen: none;
    --card-glass-rim: 0 0 transparent;
    --card-glass-border: rgba(var(--line-rgb), 0.45);
    --card-glass-shadow: 0 0 0 4px rgba(244, 234, 210, 0.25), 0 0 0 5px rgba(var(--line-rgb), 0.25), 0 6px 18px rgba(var(--shadow-rgb), 0.18);
    --compare-stat-highest: #1f5c1f;
    --compare-stat-lowest: var(--color-danger);
    --danger-ink: var(--color-danger);
    /* White-on-copper badges: the pale copper fails against --on-primary-text (vellum). */
    --color-copper: var(--color-copper-deep);
    /* The system accents are tuned for dark surfaces; light ones need their ink. */
    --system-dnd-accent: var(--mm-accent-red);
    --system-dh-accent: var(--dh-border-purple);
    /* Warning amber as text: unreadable on a light surface. */
    --warning-ink: var(--gold-ink);
}

/* ========================================
   System accent family
   ======================================== */
/* --sys is the system colour for light surfaces, --sys-soft its tone for dark
   ones. Pages without a system keep the umber ink. */
:root {
    --sys: var(--umber-deep);
    --sys-soft: var(--umber-glow);
}

.dnd-theme {
    --sys: var(--mm-accent-red);
    --sys-soft: var(--dnd-glow);
}

.dh-theme {
    --sys: var(--dh-violet);
    --sys-soft: var(--dh-violet-glow);
    --print-font-header: var(--dh-font-header);
}

/* --rubric is the accent of the page: the light tone on dark paper, the deep
   tone on light paper. The dark group comes first so the light group wins in
   every nesting order (.light-mode on the themed element, on an ancestor, and
   a theme nested under a light ancestor). */
:root,
.dnd-theme,
.dh-theme {
    --rubric: var(--sys-soft);
}

.light-mode,
.dnd-theme.light-mode,
.dh-theme.light-mode,
.light-mode .dnd-theme,
.light-mode .dh-theme {
    --rubric: var(--sys);
}

/* Everything derived from --rubric or --sys-soft, restated wherever either can
   change so it resolves against that element's own value. */
:root,
.light-mode,
.dnd-theme,
.dh-theme,
.dnd-theme.light-mode,
.dh-theme.light-mode,
.light-mode .dnd-theme,
.light-mode .dh-theme {
    --spine-accent: var(--sys-soft);
    --nav-border: var(--spine-accent);
    --accent-color: var(--rubric);
    --heading-accent: var(--rubric);
    --focus-color: var(--rubric);
    --radio-accent: var(--rubric);
    --accent-stripe: var(--rubric);
    --focus-shadow: color-mix(in srgb, var(--rubric) 25%, transparent);
    --privacy-toggle-bg: color-mix(in srgb, var(--rubric) 8%, transparent);
    --editor-highlight-bg: color-mix(in srgb, var(--rubric) 10%, transparent);
    --accent-tint: var(--editor-highlight-bg);
    --filter-tag-bg: var(--rubric);
    --filter-tag-color: var(--on-primary-text);
    --drop-zone-active-bg: var(--editor-highlight-bg);
    --drop-zone-active-border: var(--rubric);
    --selectable-hover-shadow: color-mix(in srgb, var(--rubric) 20%, transparent);
    --feature-editor-bg: var(--editor-highlight-bg);
    --feature-editor-border: var(--card-border);
    --divider-accent: color-mix(in srgb, var(--heading-accent) 50%, var(--card-border));
    --color-gold: var(--rubric);
    --color-gold-light: color-mix(in srgb, var(--rubric) 75%, var(--color-white));
    /* Dark paper carries dark ink on --rubric, so the hover tone barely darkens
       (4.5:1 for --on-primary-text); light paper carries pale ink and darkens more. */
    --color-gold-dark: color-mix(in srgb, var(--rubric) 95%, var(--color-black));
}

.light-mode,
.dnd-theme.light-mode,
.dh-theme.light-mode,
.light-mode .dnd-theme,
.light-mode .dh-theme {
    --color-gold-dark: color-mix(in srgb, var(--rubric) 75%, var(--color-black));
}
