/* ===== ROYAL — the LIGHT base, and the defaults every theme falls back to (moved out of styles.css 2026-09-14) =====
   This is the :root block: every token the app reads is declared here, with the value the
   Royal (light) theme renders. Shadow redefines the colours for [data-theme="dark"]; skins
   (Gilded, Glass) override what they like on top of one of those two. Custom properties
   resolve at computed time, so this sheet coming after styles.css changes nothing.
   Loaded from index.html after styles.css; see public/themes/README.md for the whole
   arrangement and _template.css for adding a theme. */
/* --- Royal Minimalist Theme --- */
:root {
    --bg-body: #fdfdfd;       /* White Wall */
    --bg-panel: #ffffff;      /* Pure White Cards — the SOLID panel colour; see OVERLAY OPACITY below */
    --nav-color: #ffffff;     /* White Nav — solid; --bg-nav is derived from it below */
    
    --text-main: #333333;     /* Dark Grey for readability */
    --text-muted: #888888;    /* Soft Grey */
    /* Muted text small enough to need WCAG AA at 4.5:1. --text-muted is #888 in
       BOTH themes, which is 5.58:1 on the dark background but only 3.48:1 on the
       light one, so small print needs its own darker value here. */
    --text-hint: #666666;     /* 5.64:1 on --bg-body */
    
    --gold: #C5A028;          /* Antique Gold */
    --gold-light: #fcf8e3;    /* Very faint gold background */
    /* The accent used as TEXT, which is a different job from the accent used as a fill.
       A fill only has to be seen; text has to be READ, and luminance is what decides that
       — so a deep, saturated accent that looks right as a button background is far too
       dark to read as small text. Every `color:` that meant the accent points here, while
       backgrounds/borders keep --gold, so the identity is unchanged.
       LIGHT VALUE, measured: #C5A028 as text was 2.32-2.49:1 across every light surface
       (--bg-body, --bg-panel, --bg-card, --bg-alt, --bg-nav and the --gold-light pill), so
       for a long time each new component worked around it individually — the attribution
       footer, the guild leader plate and the verification target all moved the accent into a
       border and took --text-main for their letters. Three workarounds is the palette asking
       to be fixed, so this is the darkened gold that clears AA everywhere: worst case
       4.91:1, on --bg-alt. It is the LIGHTEST value that still leaves margin — 4.58:1 was
       available but a hair over the line, and a surface tweak later would silently spend it.
       --gold itself is UNCHANGED, so every fill, border and glow keeps the bright antique
       gold; only text moves. That is the whole reason the two tokens are separate. */
    --gold-text: #856812;
    
    --border: #cccccc;        
    --border-gold: #e6c860;   /* Soft Gold border */
    
    --success: #28a745;
    --danger: #dc3545;
    --info: #17a2b8;
    /* An outcome that half-worked: the account exists, but its confirmation email did not
       send. Green tells the reader to sit and wait for a mail that is not coming; red says
       the sign-up failed, which it did not. Amber is the only honest one. */
    --warn: #8a5300;
    /* Same fill/text split as --gold / --gold-text below: an error colour is used both as
       a button BACKGROUND (which wants depth) and as small TEXT (which wants luminance),
       and one value cannot serve both on a dark surface. Light keeps one value — #dc3545
       is 4.53:1 on white, so there is nothing to fix here. */
    --danger-text: var(--danger);
    /* Positive/"done" accent. Distinct from --success, which is the button green. */
    --ok: #2f9e63;

    /* Raised surfaces INSIDE a card — list rows, inset panels, form wells. These were
       referenced by a dozen rules before they existed, so every profile/verification
       surface rendered with no background at all; keep all three defined in both themes. */
    --bg-alt: #f7f7f5;        /* Faint warm grey, one step up from the card */
    --bg-card: #ffffff;       /* Alias of the panel, for rules that mean "a card" */
    --bg-input: #ffffff;      /* Field interiors */

    /* The fixed header's height. Anything anchored below the header reads this rather
       than repeating the number (the drawer and the dropdown panels both do). */
    --nav-h: 70px;

    /* Corner radii, so components stop hard-coding 4-11px each */
    --r-sm: 6px;
    --r-md: 9px;
    --r-lg: 14px;

    /* --- SHAPE & TYPE TOKENS (2026-08-22) ---
       These exist so a THEME can change the shape of the app, not just its colours. Every
       value here is the value the Royal/Shadow themes already rendered, so adding them
       changed nothing: the components below now read the token instead of hard-coding the
       number, and light and dark keep the squared, uppercase look they were designed with.
       The Gilded theme (bottom of this block) is the one that moves them.
       Anything that ADDS a property rather than swapping a value — backdrop blur, gradients,
       button fills — is not a token; it lives in a `[data-theme="dark"][data-skin="gilded"]` rule instead, so
       the other two themes do not pay for a feature they do not use. */
    --r-btn: 0;                       /* .action-btn — squared, deliberately */
    --r-panel: 0;                     /* .card and the panels built on it */
    --r-field: 0;                     /* inputs, selects, textareas */
    /* --- OVERLAY OPACITY (2026-09-14) ---
       Every big surface that sits over the artwork is a SOLID colour a theme names, times an
       alpha the reader may change: Appearance → Overlay opacity has one slider per group,
       saved per theme in the browser (js/opacity.js writes the --op-* tokens and the paper
       alphas inline on <html>, which outranks every theme rule). A theme sets the colours and
       its own default alphas; the derived tokens are what components read, so a theme never
       writes a translucent rgba() for these itself.
         cards & panels   --card-color  × --op-panel  → --panel-bg   (.card and everything built on it)
         header (& rail)  --nav-color   × --op-nav    → --bg-nav
         chat & drawers   --float-color × --op-float  → --bg-float   (chat windows, the friends drawer, the bell/message panels)
         page veil        --paper-centre/mid/edge     (the wash over the artwork, above)
       --bg-panel stays the theme's own solid colour for the small things (chips, rows, menus),
       and dialogs and dropdown lists keep a solid floor whatever the sliders say. */
    --card-color: var(--bg-panel);
    --float-color: var(--card-color);
    --op-panel: 100%;
    --op-nav: 100%;
    --op-float: 100%;
    --panel-bg: color-mix(in srgb, var(--card-color) var(--op-panel), transparent);   /* what a card is filled with */
    --bg-nav: color-mix(in srgb, var(--nav-color) var(--op-nav), transparent);
    --bg-float: color-mix(in srgb, var(--float-color) var(--op-float), transparent);
    --panel-border: var(--border);
    --h1-transform: uppercase;
    --h1-tracking: 2px;
    --h1-weight: 300;
    --h1-color: var(--gold-text);
    --h2-rule: 1px solid var(--gold); /* the gold underline beneath every h2 */
    --h2-rule-gap: 15px;              /* its padding-bottom */
    --h2-tracking: 1px;
    --btn-weight: 400;
    /* What to write ON a solid --gold fill. A dozen rules hard-coded `color: #fff` there,
       which is only readable while --gold is dark enough for it — true of the Shadow theme's
       blood red (7.2:1) and NOT of the Royal theme's antique gold, where white on gold has
       always measured 2.49:1. Gilded's gold is brighter still, so it needs the opposite ink.
       Default is #fff, so light and dark render exactly what they did before; only the theme
       that needs dark ink asks for it. New accent-filled controls should use this rather than
       picking a literal — that is the whole reason it exists.
       NOTE: this does not FIX the Royal theme's 2.49:1; it makes it fixable in one place. */
    --on-gold: #fff;

    --shadow-card: 0 4px 20px rgba(0,0,0,0.03);
    --shadow-hover: 0 8px 30px rgba(197, 160, 40, 0.15); /* Golden glow */

    /* --- THE BACKDROP (2026-08-28) ---
       An illustration behind the whole app, veiled by a wash so the page still reads as a
       page. Drop the artwork at the path below; until a file exists there the url() simply
       fails to load and everything renders exactly as it did before, so this is safe to ship
       ahead of the image.
       Swap the file, or point this at a different one per theme, and nothing else has to move.

       A BOOTSTRAP, NOT THE ANSWER. js/appearance.js overrides this token with the viewer's
       own choice (or, for a signed-out visitor, the operator's configured default) as soon as
       it runs. This value exists only so the first paint is the artwork rather than a bare
       colour, and it is the one place a filename is repeated outside the appearance module —
       kept deliberately, because the alternative is a visible flash on every page load.
       
       ONE url now. It used to carry a second, `backdrop-placeholder.svg`, as a fallback while
       no real art existed; the art exists, so the placeholder was deleted (2026-08-29). The
       Gilded body rule still lists five `background-size` values against four layers, which is
       harmless — CSS ignores size values with no layer to apply them to. */
    /* ROOT-ABSOLUTE, and it must stay so (2026-09-14): a url() inside a custom property is
       resolved against the stylesheet that SUBSTITUTES it, not the one that declares it. With
       the theme sheets under themes/, a relative path here became themes/assets/… for every
       body rule that lives in a theme file, and the artwork vanished from Gilded and Glass. */
    /* It must name a file that EXISTS in assets/bgs (2026-09-21): this is painted on every load,
       guests included, before appearance.js repaints — so when the art it named was deleted,
       every page view requested a 404 first. Deleting a background means updating this line. */
    --backdrop: url('/assets/bgs/lone_silver_haired_knight.webp');

    /* The wash is a VIGNETTE, and the direction is the unusual part: opaque at the edges,
       thinnest in the middle. That is what makes it read as a sheet of paper laid over the
       picture rather than as a photo with a dark border.
       Three alphas, so the strength is tunable without rewriting the gradient:
         --paper-centre  the thinnest point — how much artwork shows through at all
         --paper-mid     the shoulder
         --paper-edge    the rim, which is fully opaque
       CENTRE IS THE ONE THAT MATTERS, and it is a READABILITY setting, not only a taste one:
       body text sits on it. See the measured numbers beside the dark values below — lowering
       it shows more artwork and costs contrast, in that order. */
    --paper-ink: 255, 255, 255;          /* the wash colour, as r,g,b */
    /* MEASURED, and measured TWICE by two different methods — the difference between them is
       the whole story of this value.
       Every text node the backdrop can actually reach (25 across Home, Party, Database,
       Profile and Community; the rest sit on opaque cards and are unaffected) is checked using
       the wash's own alpha at that exact point on screen.
         Method 1, no image available: assume the worst thing ANY artwork could put under each
         word — pure black and pure white. That put the floor at 0.90, and 0.90 is visually
         nothing: the page looked untouched. Correct, and useless.
         Method 2, with an image in place: draw the backdrop to a canvas at the same cover-fit
         geometry CSS uses, and sample the ACTUAL pixels under each word. Against a bright
         illustration the floor drops to 0.60 — only one element regresses at 0.55.
       0.72 is set here: comfortably inside the measured-clean range, with room for artwork
       darker than the current placeholder, and visibly a paper wash rather than a blank page.
       RE-MEASURE WITH METHOD 2 WHEN THE REAL ARTWORK LANDS. A much darker illustration would
       move this floor up. Do not lower it by eye — the failures are small muted text, which is
       exactly the text nobody notices going bad. Six elements fail at EVERY value including
       1.0; those are the pre-existing --text-muted cases documented in the light palette
       above, not something the backdrop caused. */
    --paper-centre: 0.72;
    --paper-mid: 0.84;
    --paper-edge: 1;
    /* The footer is deliberately much thinner, so the picture is genuinely LOOKED at in the
       one strip where nothing competes with it. It can afford to be, because the footer's text
       was moved off --text-hint onto --text-main for exactly this reason — see .site-footer.
       0.68 is again a measured floor, and the DARK themes are what set it: there the worst case
       inverts (light text over a white patch of artwork under a dark wash), which failed at
       0.62 (4.28:1). 0.68 clears it at ~4.7:1 in dark and ~5.5:1 in light. */
    --paper-footer: 0.68;

    --paper-wash: radial-gradient(ellipse 82% 76% at 50% 46%,
        rgba(var(--paper-ink), var(--paper-centre)) 0%,
        rgba(var(--paper-ink), var(--paper-mid)) 46%,
        rgba(var(--paper-ink), var(--paper-edge)) 84%);
}

