/* =========================================================================
 * DealsSpy — design tokens (v2 rebuild)
 * =========================================================================
 *
 * The single source of colour, type, spacing, radius, shadow and motion for
 * the whole site. `site.css` (public), `community.css` (the wall) and
 * `admin.css` (admin) are written ENTIRELY against the semantic aliases at the
 * bottom of this file and contain no literal colour of their own.
 *
 * THE FILE HAS THREE PARTS AND THE ORDER MATTERS.
 *
 *   0. The FONT. `@font-face` lives here rather than in `site.css` because
 *      `admin.css` is loaded WITHOUT `site.css` (see `layouts/admin.php`), and
 *      `tokens.css` is the only file all three shells load. Declaring the face
 *      twice is how the two shells end up on two different Inters.
 *
 *   1. The PALETTE — raw ramps. `--ds-navy-600` is a colour, not a meaning.
 *      Nothing outside this file should reference a ramp step directly; a
 *      component that says "navy" cannot be re-themed.
 *
 *   2. The SEMANTIC ALIASES — meanings. `--ds-color-primary` is a role that
 *      happens to be navy today. These are what the stylesheets consume.
 *
 * =========================================================================
 * WHAT THE v2 REBUILD CHANGED, AND WHY (DESIGN-SYSTEM.md §2, §3)
 * =========================================================================
 *
 * THREE JOBS, THREE COLOURS, NO OVERLAP. Navy carries chrome and structure;
 * a warm CTA colour carries "act now"; a heat ramp carries momentum. Gold is
 * repurposed rather than demoted — on Slickdeals `#F4B40B` is the RATING
 * colour, so here gold means "what the community thinks" (heat, ranks, votes)
 * and never "buy". Using gold for a CTA is what made v1 feel unfocused.
 *
 * THE TYPE SCALE TIGHTENED AT THE TOP. 44px and 36px headings are magazine
 * sizes on a feed site. Body drops 16 → 15 and that single step is the density
 * lever the whole redesign turns on.
 *
 * SPACING AND RADIUS ARE UNCHANGED. They were fine, and changing a spacing
 * scale is how every existing view breaks at once.
 *
 * ON THE DARK SCHEME: the light path is complete on its own. Every semantic
 * alias has a light value in `:root`, and `tokens-dark.css` only RE-MAPS
 * those aliases. It defines no new token and no component depends on it
 * existing. Delete that file and the site is a complete light theme; that is
 * the property being protected, because a half-built dark mode that leaks one
 * unreadable panel is worse than none.
 *
 * THE DARK VALUES ARE NO LONGER AT THE BOTTOM OF THIS FILE. They were, inside
 * `@media (prefers-color-scheme: dark)`, until the site grew a three-state
 * theme control. The long version of why is at the bottom of this file and at
 * the top of `tokens-dark.css`; the short version is that a media query can
 * express "the OS asked for dark" and can never express "this reader asked
 * for dark on an OS that asked for light", and writing the palette twice to
 * cover both is the drift this file exists to prevent.
 *
 * (DESIGN-SYSTEM.md §2.3 put the TOGGLE out of scope for the v2 pass and
 * shipped the OS preference alone. §2.3 now records the toggle as built, and
 * the reason the estimate in it was wrong: the expensive half was never the
 * palette, it was applying a stored choice before first paint without an
 * inline script the CSP would refuse.)
 * ========================================================================= */

/* =========================================================================
 * 0. THE TYPEFACE — Inter, self-hosted, subset, `font-display: swap`
 * =========================================================================
 *
 * NOT GOOGLE FONTS, AND THAT IS A CSP DECISION RATHER THAN A TASTE ONE.
 * `Config\ContentSecurityPolicy` sets `default-src 'self'` and declares no
 * `font-src`, so a font-host origin would have to be added to the policy AND
 * would cost a third-party DNS lookup, connection and request on every page.
 * `'self'` already covers `/assets/fonts/`.
 *
 * BOTH REFERENCE SITES SHIP INTER — that is measured, not preferred
 * (DESIGN-SYSTEM.md §2.1 and §2.1b) — and it is drawn for dense UI at small
 * sizes, which is what a 15px body and a 13px meta line need.
 *
 * FOUR WEIGHTS, TWO SUBSETS, EIGHT FILES. 400 body, 500 meta emphasis,
 * 600 titles and buttons, 700 prices and headings. `latin` is fetched on every
 * page; `latin-ext` is only fetched when a page actually contains a character
 * in its range (an accented store or member name), which is the entire point
 * of splitting them.
 *
 * `font-display: swap` on all eight: the fallback stack below is metric-close
 * enough that the reflow is small, and a blank page waiting on a font is worse
 * than a paragraph that re-draws.
 *
 * Files: `public/assets/fonts/`, Inter 4.x (SIL Open Font License 1.1, text in
 * `OFL.txt` beside them). The URLs are RELATIVE TO THIS STYLESHEET, so they
 * resolve to `/assets/fonts/…` wherever the CSS is mounted.
 * ========================================================================= */

@font-face {
    font-family: "Inter";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("../fonts/inter-latin-400.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
        U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
        U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Inter";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("../fonts/inter-latin-500.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
        U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
        U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Inter";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("../fonts/inter-latin-600.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
        U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
        U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Inter";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("../fonts/inter-latin-700.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
        U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
        U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Inter";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("../fonts/inter-latin-ext-400.woff2") format("woff2");
    unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
        U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: "Inter";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("../fonts/inter-latin-ext-500.woff2") format("woff2");
    unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
        U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: "Inter";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("../fonts/inter-latin-ext-600.woff2") format("woff2");
    unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
        U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: "Inter";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("../fonts/inter-latin-ext-700.woff2") format("woff2");
    unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
        U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
    /* =====================================================================
     * 1. PALETTE
     * ===================================================================== */

    /* --- Navy: the brand's spine. Chrome, headings, admin shell. --------- */
    --ds-navy-50: #eef3f9;
    --ds-navy-100: #d6e2f0;
    --ds-navy-200: #adc3de;
    --ds-navy-300: #7d9dc6;
    --ds-navy-400: #4d75a7;
    --ds-navy-500: #2d5488;
    --ds-navy-600: #1d4a7e;
    --ds-navy-700: #14365e;
    --ds-navy-800: #0f2a4a;
    --ds-navy-900: #0a1d34;
    --ds-navy-950: #061223;

    /* --- Powder blue: quiet surfaces, informational emphasis. ------------ */
    --ds-powder-50: #f4f9fd;
    --ds-powder-100: #e8f1fa;
    --ds-powder-200: #cfe0f2;
    --ds-powder-300: #a8c8e8;
    --ds-powder-400: #7fabd8;
    --ds-powder-500: #5b8dc4;
    --ds-powder-600: #4272a8;

    /* --- Gold: NOT money any more. "What the community thinks."-----------
     * DESIGN-SYSTEM.md §2.2. Slickdeals uses `#F4B40B` for its five-thumb
     * RATING and its rating pill, and inspecting that is what changed this:
     * gold is an opinion colour, not a commerce one. Here it carries the heat
     * score's frame, rank badges and vote state. The outbound CTA is the
     * `--ds-cta-*` ramp below and never this.
     * ------------------------------------------------------------------- */
    --ds-gold-50: #fdf8ec;
    --ds-gold-100: #faeecd;
    --ds-gold-200: #f4dc9b;
    --ds-gold-300: #f0c95c;
    --ds-gold-400: #e0a92e;
    --ds-gold-500: #c9962c;
    --ds-gold-600: #a67a1f;
    --ds-gold-700: #7d5b16;

    /* --- CTA: the warm commerce ramp. NEW in v2. -------------------------
     * DesiDime's `#FE6301` pulled darker until it is legal as text.
     *
     * §2.2 NAMES `#E85D12` AS `--ds-color-cta` AND CLAIMS 4.6:1 ON WHITE.
     * IT MEASURES 3.50:1. That is below AA for normal text in BOTH directions
     * — as a text colour on white and as a button fill under white text — and
     * failing AA is the exact reason §2.2 gives for rejecting DesiDime's
     * orange in the first place. So `#E85D12` is kept as `--ds-cta-400` for
     * decoration that carries no text (a ribbon edge, a focus tint), and the
     * ROLE `--ds-color-cta` resolves to `--ds-cta-500` = `#C74A08`, which
     * measures 4.76:1 on white and 4.4:1 under white text at 600 weight.
     * Same colour family, one step darker, honest at 13px.
     * ------------------------------------------------------------------- */
    --ds-cta-50: #fdf2ea;
    --ds-cta-100: #fadfcc;
    --ds-cta-200: #f5bb95;
    --ds-cta-400: #e85d12;
    --ds-cta-500: #c74a08;
    --ds-cta-600: #a63c05;
    --ds-cta-700: #7f2e04;

    /* --- Heat: momentum. NEW in v2. DESIGN-SYSTEM.md §2.2 and §9.5. ------
     * Four bands, and the PRESENTER decides which one — a view never compares
     * a number.
     *
     * Each band is three values rather than one, because one is not enough:
     * the ramp value is a 3:1-on-white GRAPHIC colour (fine for a border or a
     * fill) and none of the four is legible as small text on white. The
     * `-text` steps are the same hues darkened to 5.5:1+, the `-bg` steps are
     * the same hues at tint strength. A heat score has to be the loudest thing
     * on the row AND readable; a 3.7:1 number is neither.
     * ------------------------------------------------------------------- */
    --ds-heat-cold: #6f86a8;
    --ds-heat-cold-bg: #edf1f6;
    --ds-heat-cold-text: #4f6484;

    --ds-heat-warm: #e8952f;
    --ds-heat-warm-bg: #fdf3e4;
    --ds-heat-warm-text: #8a5209;

    --ds-heat-hot: #e5462c;
    --ds-heat-hot-bg: #fdece8;
    --ds-heat-hot-text: #b33418;

    --ds-heat-blazing: #c81e0e;
    --ds-heat-blazing-bg: #fce7e4;
    --ds-heat-blazing-text: #a81709;

    /* --- The struck was-price. NEW in v2. --------------------------------
     * Slickdeals' `#C64111`, 959 uses, 5.04:1 on white. §4.3: on a deal site
     * the saving is the product, so the was-price gets the loudest treatment
     * on the row and it is NOT grey. This is a role of its own rather than a
     * step of the CTA ramp — it is a different hue doing a different job, and
     * aliasing them would mean a CTA re-tune silently moves every strike.
     * ------------------------------------------------------------------- */
    --ds-was-600: #c64111;

    /* --- Link blue. ADJUSTED in v2 (§2.2): 5.97:1 on white. ------------- */
    --ds-link-600: #2c63b0;
    --ds-link-700: #204a85;
    --ds-link-800: #17375f;

    /* --- Neutrals: text, rules, page background. ------------------------- */
    --ds-neutral-0: #ffffff;
    --ds-neutral-50: #f8fafc;
    --ds-neutral-100: #f1f4f8;
    --ds-neutral-200: #e4e9f0;
    --ds-neutral-300: #cdd5e0;
    --ds-neutral-400: #9aa6b6;

    /* MEASURED, NOT PICKED. `500` is `--ds-color-text-muted`: the lede under
     * every page title, the inactive feed tab, the row meta line. It was
     * `#6B7887`, which is 4.50:1 on WHITE — a pass by 0.00 — and 4.08:1 on
     * `--ds-neutral-100`, which is the actual page canvas that most muted
     * text sits on. So the step was chosen against a background the text is
     * almost never on. `#5F6B7A` is 4.92:1 on the canvas and 5.43:1 on white,
     * and is the darkest value that still reads as "quieter than body". */
    --ds-neutral-500: #5f6b7a;
    --ds-neutral-600: #4d5866;
    --ds-neutral-700: #38414d;
    --ds-neutral-800: #242b34;
    --ds-neutral-900: #151a21;
    --ds-neutral-950: #0b0e13;

    /* --- Status ramps. Four states, three steps each: a tint for a banner
     *     background, a mid for a border, a deep for text on the tint.
     *     Kept separate from the brand so a "success" never drifts into
     *     "gold" and stops meaning success. -------------------------------- */
    --ds-success-100: #ddf3e4;
    --ds-success-300: #86cea1;
    --ds-success-600: #1f7a43;
    --ds-success-700: #176034;

    --ds-warning-100: #fdf0d5;
    --ds-warning-300: #f0c980;
    --ds-warning-600: #96601b;
    --ds-warning-700: #764a13;

    --ds-danger-100: #fbe3e3;
    --ds-danger-300: #e79c9c;
    --ds-danger-600: #b3271f;
    --ds-danger-700: #8e1e18;

    --ds-info-100: var(--ds-powder-100);
    --ds-info-300: var(--ds-powder-300);
    --ds-info-600: var(--ds-navy-600);
    --ds-info-700: var(--ds-navy-700);

    /* =====================================================================
     * 2. TYPE
     * =====================================================================
     * DESIGN-SYSTEM.md §3, tightened. The steps are named by ROLE rather than
     * by pixel size so a heading can be re-tuned in one place.
     *
     * TWO THINGS ABOUT THIS LADDER ARE UNUSUAL AND BOTH ARE DELIBERATE.
     *
     * `--ds-text-lg` IS 15px AND SO IS `--ds-text-base`. §3 proposed 17px for
     * `lg`; measuring Slickdeals' own DOM put their card title at 14px/500, and
     * density is the entire point of this redesign, so `lg` came down to 15px.
     * `lg` and `base` therefore agree on size and differ on WEIGHT — `lg` is
     * "the title step", always set at 600, and `base` is body at 400. That is
     * how both reference sites actually separate a card title from its copy.
     *
     * `--ds-text-md` (17px) IS LARGER THAN `--ds-text-lg` (15px). It has to be:
     * with `lg` at the title size, something still has to be the lead
     * paragraph, the h3 and the row's current price, and 17px is the step §3
     * had originally given to `lg`. The names are historical; the roles in the
     * right-hand column are what to read.
     * ===================================================================== */

    --ds-font-sans:
        "Inter",
        system-ui,
        -apple-system,
        BlinkMacSystemFont,
        "Segoe UI",
        Roboto,
        "Helvetica Neue",
        Arial,
        "Noto Sans",
        sans-serif,
        "Apple Color Emoji",
        "Segoe UI Emoji";
    --ds-font-mono:
        ui-monospace,
        SFMono-Regular,
        "SF Mono",
        Menlo,
        Consolas,
        "Liberation Mono",
        monospace;

    --ds-text-2xs: 0.6875rem;  /* 11px — badge, ribbon, row meta */
    --ds-text-xs: 0.75rem;     /* 12px — timestamps, author, footnotes */
    --ds-text-sm: 0.8125rem;   /* 13px — secondary body, buttons, form help */
    --ds-text-base: 0.9375rem; /* 15px — body. THE DENSITY LEVER. */
    --ds-text-lg: 0.9375rem;   /* 15px — row/card title (set at 600) */
    --ds-text-md: 1.0625rem;   /* 17px — lead, h3, the row's current price */
    --ds-text-xl: 1.3125rem;   /* 21px — h2 */
    --ds-text-2xl: 1.625rem;   /* 26px — h1 */
    --ds-text-3xl: 2rem;       /* 32px — page hero */
    --ds-text-4xl: 2.5rem;     /* 40px — error code only */

    --ds-leading-tight: 1.15;
    --ds-leading-snug: 1.3;
    --ds-leading-normal: 1.55;
    --ds-leading-relaxed: 1.7;

    --ds-weight-regular: 400;
    --ds-weight-medium: 500;
    --ds-weight-semibold: 600;
    --ds-weight-bold: 700;

    --ds-tracking-tight: -0.015em;
    --ds-tracking-normal: 0;
    --ds-tracking-wide: 0.04em;

    /* =====================================================================
     * 3. SPACE — UNCHANGED (§3: "kept as-is")
     * =====================================================================
     * A 4px base. Numbered, not t-shirt-sized, because "between --ds-space-md
     * and --ds-space-lg" has no answer and "between 3 and 4" does.
     * ===================================================================== */

    --ds-space-0: 0;
    --ds-space-1: 0.25rem;   /* 4  */
    --ds-space-2: 0.5rem;    /* 8  */
    --ds-space-3: 0.75rem;   /* 12 */
    --ds-space-4: 1rem;      /* 16 */
    --ds-space-5: 1.25rem;   /* 20 */
    --ds-space-6: 1.5rem;    /* 24 */
    --ds-space-8: 2rem;      /* 32 */
    --ds-space-10: 2.5rem;   /* 40 */
    --ds-space-12: 3rem;     /* 48 */
    --ds-space-16: 4rem;     /* 64 */
    --ds-space-20: 5rem;     /* 80 */

    /* =====================================================================
     * 4. RADIUS, BORDER, SHADOW, MOTION, LAYOUT — radius UNCHANGED
     * ===================================================================== */

    --ds-radius-none: 0;
    --ds-radius-sm: 0.25rem;
    --ds-radius-md: 0.5rem;
    --ds-radius-lg: 0.75rem;
    --ds-radius-xl: 1rem;
    --ds-radius-pill: 999px;
    --ds-radius-circle: 50%;

    --ds-border-width: 1px;
    --ds-border-width-thick: 2px;

    /* Shadows are navy-tinted rather than neutral black: a black shadow over
     * a blue-white surface reads grey and dirty. */
    --ds-shadow-0: none;
    --ds-shadow-1: 0 1px 2px rgba(10, 29, 52, 0.06), 0 1px 1px rgba(10, 29, 52, 0.04);
    --ds-shadow-2: 0 2px 4px rgba(10, 29, 52, 0.07), 0 4px 12px rgba(10, 29, 52, 0.06);
    --ds-shadow-3: 0 6px 16px rgba(10, 29, 52, 0.10), 0 2px 6px rgba(10, 29, 52, 0.06);
    --ds-shadow-4: 0 16px 40px rgba(10, 29, 52, 0.16), 0 4px 12px rgba(10, 29, 52, 0.08);
    --ds-shadow-inset: inset 0 1px 2px rgba(10, 29, 52, 0.08);

    --ds-duration-fast: 90ms;
    --ds-duration-base: 160ms;
    --ds-duration-slow: 280ms;
    --ds-ease: cubic-bezier(0.2, 0, 0.2, 1);

    /* Layout rails. `--ds-container` is the public reading width; `--ds-wide`
     * is the listing grid; `--ds-prose` is the legal/article measure, kept at
     * roughly 68 characters (§9.4).
     *
     * `--ds-rail-width` and `--ds-nav-height` are NEW and belong to the v2
     * shell (§4.1): a 300px right rail beside a 1fr feed, under a 40px
     * secondary nav that scrolls away. `--ds-row-media` is the deal row's
     * fixed square (§9.2) and is a token because the row's whole height budget
     * is derived from it. */
    --ds-container: 1200px;
    --ds-wide: 1440px;
    --ds-prose: 68ch;
    --ds-sidebar-width: 248px;
    --ds-header-height: 56px;
    --ds-nav-height: 40px;
    --ds-rail-width: 300px;
    --ds-row-media: 88px;

    /* Z-index is a short, closed list on purpose. A stylesheet that invents
     * `z-index: 9999` is a stylesheet with no layering model. */
    --ds-z-base: 0;
    --ds-z-sticky: 100;
    --ds-z-header: 200;
    --ds-z-drawer: 300;
    --ds-z-overlay: 400;
    --ds-z-toast: 500;
    --ds-z-skiplink: 600;

    /* =====================================================================
     * 5. SEMANTIC ALIASES — the only tokens a component may use
     * =====================================================================
     * Complete for light on its own. `tokens-dark.css` re-maps this list and
     * adds nothing to it.
     * ===================================================================== */

    /* THE SCHEME THIS BLOCK IS, STATED RATHER THAN INFERRED.
     *
     * It used to be absent, and absent means "whatever `<meta name=
     * color-scheme>` says" — which is `light dark`, which means the OS
     * decides. That was harmless while the OS was the only thing that
     * decided. It stopped being harmless the moment a reader could ask for
     * LIGHT on a dark OS: the page would be light and the form controls, the
     * scrollbars and the canvas behind them would still be painted dark by
     * the UA. `tokens-dark.css` sets `dark` when it applies; this sets the
     * other half so that one of the two is always true. */
    color-scheme: light;

    /* Surfaces */
    --ds-color-bg: var(--ds-neutral-100);
    --ds-color-surface: var(--ds-neutral-0);
    --ds-color-surface-sunken: var(--ds-neutral-50);
    --ds-color-surface-raised: var(--ds-neutral-0);
    --ds-color-surface-quiet: var(--ds-powder-50);
    --ds-color-surface-inverse: var(--ds-navy-800);

    /* Lines */
    --ds-color-border: var(--ds-neutral-200);
    --ds-color-border-strong: var(--ds-neutral-300);
    --ds-color-border-inverse: var(--ds-navy-700);

    /* Text */
    --ds-color-heading: var(--ds-navy-800);
    --ds-color-text: var(--ds-neutral-800);
    --ds-color-text-muted: var(--ds-neutral-500);
    --ds-color-text-faint: var(--ds-neutral-400);
    --ds-color-text-inverse: var(--ds-neutral-0);
    --ds-color-text-on-primary: var(--ds-neutral-0);
    --ds-color-text-on-accent: var(--ds-navy-900);
    --ds-color-text-on-cta: var(--ds-neutral-0);

    /* Brand roles */
    --ds-color-primary: var(--ds-navy-700);
    --ds-color-primary-hover: var(--ds-navy-800);
    --ds-color-primary-soft: var(--ds-powder-100);
    --ds-color-primary-soft-text: var(--ds-navy-700);

    --ds-color-secondary: var(--ds-powder-300);
    --ds-color-secondary-hover: var(--ds-powder-400);

    /* THE CTA ROLE — "act now", and nothing else.
     * `Get deal`, `Reveal code`, `Submit a deal`, the discount percentage.
     * It is the ONLY role that may fill a button that sends a visitor to a
     * merchant, and gold may not. */
    --ds-color-cta: var(--ds-cta-500);
    --ds-color-cta-hover: var(--ds-cta-600);
    --ds-color-cta-active: var(--ds-cta-700);
    --ds-color-cta-soft: var(--ds-cta-50);
    --ds-color-cta-soft-border: var(--ds-cta-200);
    /* ONE STEP DARKER THAN THE FILL, BECAUSE IT IS NEVER USED ON WHITE.
     * `--ds-color-cta-text` colours the discount pill, and the pill's own
     * background is `--ds-color-cta-soft` (`#FDF2EA`). `cta-500` is 4.76:1 on
     * white but only 4.32:1 on that tint — the one surface it actually
     * appears on. `cta-600` is 5.83:1 there. */
    --ds-color-cta-text: var(--ds-cta-600);

    /* Gold is `accent`, and after §2.2 `accent` means WHAT THE COMMUNITY
     * THINKS: the heat frame, rank badges, an active vote. It no longer means
     * money and it must never fill an outbound CTA. */
    --ds-color-accent: var(--ds-gold-400);
    --ds-color-accent-hover: var(--ds-gold-500);
    --ds-color-accent-soft: var(--ds-gold-100);
    --ds-color-accent-text: var(--ds-gold-700);

    /* The was-price. A role, because §4.3 makes it a signature element. */
    --ds-color-price-was: var(--ds-was-600);

    /* Links */
    --ds-color-link: var(--ds-link-600);
    --ds-color-link-hover: var(--ds-link-800);
    --ds-color-link-visited: var(--ds-link-700);

    /* Focus. One ring, everywhere, and it is never removed — only replaced.
     * A focus style that disappears is an accessibility regression that no
     * mouse user will ever notice. */
    --ds-color-focus: var(--ds-powder-600);
    --ds-focus-ring: 0 0 0 3px rgba(66, 114, 168, 0.45);
    --ds-focus-offset: 2px;

    /* Status roles */
    --ds-color-success: var(--ds-success-600);
    --ds-color-success-bg: var(--ds-success-100);
    --ds-color-success-border: var(--ds-success-300);

    --ds-color-warning: var(--ds-warning-600);
    --ds-color-warning-bg: var(--ds-warning-100);
    --ds-color-warning-border: var(--ds-warning-300);

    --ds-color-danger: var(--ds-danger-600);
    --ds-color-danger-bg: var(--ds-danger-100);
    --ds-color-danger-border: var(--ds-danger-300);

    --ds-color-info: var(--ds-info-600);
    --ds-color-info-bg: var(--ds-info-100);
    --ds-color-info-border: var(--ds-info-300);

    /* Chrome roles — used by all three stylesheets so the shells stay related
     * while looking nothing alike.
     *
     * THE BAR IS DARKER IN v2. §0 of DESIGN-SYSTEM.md records that both
     * reference sites run NEAR-BLACK chrome, and a navy-800 bar over a
     * navy-800 nav row is one band, not two. `header` is navy-900 and `nav` is
     * navy-800, so the two dark rows read as a bar and a shelf under it.
     *
     * THERE ARE FIVE REGIONS ON A PUBLIC PAGE AND THEY NEED FIVE VALUES:
     * canvas, rail, bar, nav, footer. A border is an edge here; it is no
     * longer the only thing separating two surfaces. */
    --ds-color-header-bg: var(--ds-navy-900);
    --ds-color-header-text: var(--ds-powder-100);
    --ds-color-header-text-strong: var(--ds-neutral-0);
    --ds-color-nav-bg: var(--ds-navy-800);
    --ds-color-nav-text: var(--ds-powder-200);
    --ds-color-footer-bg: var(--ds-navy-900);
    --ds-color-footer-text: var(--ds-powder-200);
    --ds-color-footer-heading: var(--ds-neutral-0);

    /* THE LEFT RAIL / DRAWER, AND IT IS ITS OWN ROLE RATHER THAN
     * `--ds-color-surface`. It used to borrow the CARD surface, which is a
     * coincidence in light and a collision in dark: cards, the rail and the
     * page were three different things painted two colours. */
    --ds-color-rail-bg: var(--ds-neutral-0);

    /* Overlays for use ON the dark chrome (header, nav, footer, admin
     * sidebar). They are white at low alpha rather than a fixed grey so one
     * set works over every navy step, and they are tokens rather than inline
     * `rgb()` so the stylesheets can hold to "no literal colour". */
    --ds-overlay-hover: rgb(255 255 255 / 0.08);
    --ds-overlay-active: rgb(255 255 255 / 0.14);
    --ds-overlay-panel: rgb(255 255 255 / 0.06);
    --ds-overlay-line: rgb(255 255 255 / 0.12);
    --ds-overlay-line-soft: rgb(255 255 255 / 0.08);
    --ds-overlay-border: rgb(255 255 255 / 0.25);

    /* THE SCRIM — the dim behind a drawer or a modal, and the OPPOSITE JOB from
     * the overlays above. Those are white-at-alpha for lifting a surface out of
     * dark chrome; a scrim pushes the whole page back, so it is black, and
     * using an `--ds-overlay-*` for it LIGHTENS the thing it is meant to
     * subdue. `community.css` records having hit exactly that and left
     * `::backdrop` unstyled rather than reach for the wrong one.
     *
     * IT IS THE SAME VALUE IN BOTH SCHEMES and is not re-mapped below. A scrim
     * is not a colour the theme owns — it is an absence of light, and 40% black
     * reads as "that is behind something" on a white page and on a navy one.
     * The dark theme darkening it further would only make an already-dark page
     * look switched off. */
    --ds-color-scrim: rgb(0 0 0 / 0.4);

    /* Admin-only chrome roles. Declared here rather than in `admin.css` so
     * the two shells cannot drift onto two different navies. */
    --ds-color-admin-sidebar-bg: var(--ds-navy-900);
    --ds-color-admin-sidebar-text: var(--ds-powder-200);
    --ds-color-admin-sidebar-active-bg: var(--ds-navy-700);
    --ds-color-admin-topbar-bg: var(--ds-neutral-0);
    --ds-color-admin-canvas: var(--ds-neutral-100);
}

/* =========================================================================
 * THE DARK SCHEME LIVES IN `tokens-dark.css`, AND THAT IS NOT TIDYING
 * =========================================================================
 * It was a `@media (prefers-color-scheme: dark)` block at the bottom of this
 * file. It still contains exactly the same ninety re-mappings and not one new
 * token — what changed is WHO DECIDES WHETHER IT APPLIES.
 *
 * A three-state control (light / dark / follow the system) cannot be
 * expressed by a media query: `prefers-color-scheme` can say "the OS asked
 * for dark" and can never say "this reader asked for dark on an OS that asked
 * for light". Keeping the media query and adding an attribute selector for
 * the second case would mean the same ninety declarations written twice, in
 * the one file whose whole argument is that a value in two places drifts.
 *
 * So the palette moved to its own file, under one selector that is true in
 * both cases, and the `media` attribute of the `<link>` in
 * `partials/head.php` is what turns it on and off. That attribute is the only
 * thing the pre-paint script touches for the theme.
 *
 * WHAT DID NOT CHANGE: with JavaScript off, `media="(prefers-color-scheme:
 * dark)"` does precisely what the media query did. Delete `tokens-dark.css`
 * and the site is a complete light theme, which is the property the old
 * comment at the top of this file promised and this one still keeps.
 * ========================================================================= */
