/* MCH — the authored stylesheet. One file, on top of vendored Bootstrap 5.3.
   (Forgejo issue #32, ADR 0003. Renamed from style.css; there is deliberately only
   one authored stylesheet, so "where does this rule live" has one answer.)

   How it is layered, outermost intent first:

     1. @font-face for the two self-hosted OFL families (static/fonts/README.md).
     2. A named token block — the only place a colour literal is allowed below the
        font declarations. Everything else refers to a token.
     3. The same tokens again for dark, written TWICE on purpose (see below).
     4. A Bootstrap bridge: re-point Bootstrap's own `--bs-*` variables at the tokens.
        Bootstrap is built out of custom properties, so this is the supported way to
        recolour it. Overriding its rules by specificity is not: every `!important`
        arms race in the old stylesheet started that way, and a Bootstrap upgrade then
        silently changes which side wins.
     5. Base typography, then the handful of app-specific pieces.

   Gone from the old stylesheet and not coming back (ADR 0003): the purple gradient
   text, the gradient button with its glow, and the `backdrop-filter` navbar. They cost
   a compositor layer, they break in forced-colors mode, and none of them said anything
   the type and the tokens do not say more calmly. */

/* --------------------------------------------------------------------------
   1. Fonts — vendored, OFL, self-hosted. No CDN, no Google Fonts (`font-src 'self'`).
      Lexend = headings/nav/labels. Atkinson Hyperlegible Next = body and data.
      Latin / Latin-Extended split by unicode-range: the DE switch needs Latin-Ext, and
      an English page must not pay for it. `?v=<sha8>` is the file's own content hash
      (static/fonts/README.md), not the shared asset_version.
   -------------------------------------------------------------------------- */
@font-face {
    font-family: "Lexend";
    src: url("/static/fonts/Lexend-Variable.woff2?v=1ec8f6ee") format("woff2");
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
    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+20AC, U+2122, U+2191, U+2193, U+2212,
        U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Lexend";
    src: url("/static/fonts/Lexend-Variable-LatinExt.woff2?v=368c86e9") format("woff2");
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
        U+1D00-1DBF, 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: "Atkinson Hyperlegible Next";
    src: url("/static/fonts/AtkinsonHyperlegibleNext-Variable.woff2?v=1e4cea71") format("woff2");
    font-weight: 200 800;
    font-style: normal;
    font-display: swap;
    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+20AC, U+2122, U+2191, U+2193, U+2212,
        U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Atkinson Hyperlegible Next";
    src: url("/static/fonts/AtkinsonHyperlegibleNext-Variable-LatinExt.woff2?v=ca1969af")
        format("woff2");
    font-weight: 200 800;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
        U+1D00-1DBF, 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: "Atkinson Hyperlegible Next";
    src: url("/static/fonts/AtkinsonHyperlegibleNext-Italic-Variable.woff2?v=6e221fe2")
        format("woff2");
    font-weight: 200 800;
    font-style: italic;
    font-display: swap;
    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+20AC, U+2122, U+2191, U+2193, U+2212,
        U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Atkinson Hyperlegible Next";
    src: url("/static/fonts/AtkinsonHyperlegibleNext-Italic-Variable-LatinExt.woff2?v=871436be")
        format("woff2");
    font-weight: 200 800;
    font-style: italic;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
        U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113,
        U+2C60-2C7F, U+A720-A7FF;
}

/* --------------------------------------------------------------------------
   2. Tokens — light.

   Every value below is an app-level name, never a Bootstrap name and never a raw
   colour used twice. The `-rgb` twins exist because Bootstrap's utilities compose
   alpha themselves (`rgba(var(--bs-primary-rgb), var(--bs-text-opacity))`), so a hex
   token alone cannot reach `.text-primary` or `.bg-primary-subtle`.

   The palette is the Birnchen brand ("Farben" v10, the reference PDF), adopted from
   the sister project Rutilquarz so the two apps look like one company: a green-tinted
   daylight page in light, the Birnchenblau night in dark. docs/design/colours.md is
   the token-by-token comparison — PDF name, PDF hex, Rutilquarz token, MCH token — and
   records which values are brand literals and which are derived. Read it before
   changing a literal here; a colour with no entry there is a colour nobody signed off.

   Note the one departure from the brand hierarchy: the PDF calls Birnchengrün the
   Primärfarbe, but MCH uses Birnchenlila as --accent and the green as --ok, because
   this app's primary action is not the brand's primary gesture. Swapping them back is
   a two-token change.

   Contrast was checked numerically against --bg, --surface, --surface-2, --field-bg
   and each `-soft` ground; the weakest text pair is --ok on --ok-soft at 5.2:1 in
   light and --danger on --surface-2 at 6.7:1 in dark, i.e. every text-carrying token
   clears WCAG AA for normal text with room to spare. Keep it that way when changing a
   value: colour here is never the only carrier of meaning, but it still has to be
   readable.
   -------------------------------------------------------------------------- */
:root {
    color-scheme: light;

    /* Page and panels: three steps, flattest first. The page is Background grün
       "hell" rather than white — the owner reads this app for a long time and a white
       slab is the part that hurts. */
    --bg: #f6fae4; /* brand: Background grün "hell" */
    --surface: #ffffff; /* brand: weiß — cards, modals, the navbar */
    --surface-2: #f0f5d8; /* derived: halfway to the green ground; table stripes */
    --border: #dfe5c2; /* derived from the green ground */
    /* One step darker than Rutilquarz's #c4cc9e: this is the form-control and
       outline-button boundary, and at #c4cc9e it is 1.6:1 on --bg, below the
       2.0:1 the old grey managed. #aeb885 restores that without leaving the hue. */
    --border-strong: #aeb885; /* a border that has to be seen, not just felt */

    /* Ink: Birnchenblau carries all of it on light, brand grau the secondary. */
    --fg: #0d1345; /* brand: Birnchenblau — body and headings, 16.5:1 on --bg */
    --muted: #595959; /* brand: grau — 6.6:1 on --bg, not a light grey */

    /* Accent = Birnchenlila. The brand hex #CFB6EB is a pale lilac that carries no
       text on a light ground (1.6:1), so light darkens it on-hue until it clears AA;
       dark uses the brand value as-is. --accent-soft is the brand's own lilac tint. */
    --accent: #6d4f97; /* derived: Birnchenlila darkened to 6.1:1 on --bg */
    --accent-rgb: 109, 79, 151;
    --accent-hover: #563c7c;
    --accent-soft: #f0eaf9; /* tinted ground under the accent (badges, active chips) */
    --accent-ink: #ffffff; /* text ON an --accent fill — 6.5:1 */

    /* The brand names a Linkfarbe of its own (#A9AFFE), so a link is no longer just
       the accent: on light it is that blue darkened on-hue to 7:1. --link-rgb exists
       because Bootstrap composes the link colour's alpha itself. */
    --link: #4046bc; /* derived: brand Linkfarbe darkened to 7.0:1 on --bg */
    --link-rgb: 64, 70, 188;
    --link-hover: #333a9e;

    /* Semantic colours. Always paired with a word or an icon in the markup — a rating,
       a status or an error is never signalled by colour alone. */
    /* --ok is Birnchengrün. Same story as the lilac: the brand greens (#DFFB65,
       #ADC838) carry no text on a light ground, so light uses the logo shadow green
       #748239 darkened to AA, and --ok-soft is Background grün "dunkel". */
    --ok: #4c6e1f; /* derived: Birnchengrün Schatten Blätter, darkened to 5.5:1 */
    --ok-rgb: 76, 110, 31;
    --ok-soft: #ebf5c1; /* brand: Background grün "dunkel" */
    --warn: #8a5a06;
    --warn-rgb: 138, 90, 6;
    --warn-soft: #fbf0dc;
    --danger: #b3261e;
    --danger-rgb: 179, 38, 30;
    --danger-soft: #fbe7e6;
    --info: #2a5cb8;
    --info-rgb: 42, 92, 184;
    --info-soft: #e6edfa;

    /* Form fields sit slightly inset so an input is visible before it is focused. */
    --field-bg: #f1f5de;

    /* Focus. One ring, one token, visible on every surface — the old stylesheet had
       none and left Bootstrap's blue glow, which vanished against the purple. */
    --focus: #6d4f97;
    --focus-ring: 0 0 0 3px rgba(109, 79, 151, 0.35);

    /* Theme-dependent Bootstrap icon assets. Bootstrap ships these as data URIs on the
       component itself and swaps them under `[data-bs-theme=dark]`, which this app does
       not set; a token plus one bridge rule each (section 4) replaces that. */
    --icon-select-arrow: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23595959' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
    --icon-switch-knob: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%2889, 89, 89, 0.5%29'/%3e%3c/svg%3e");
    --icon-navbar-toggler: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2889, 89, 89, 0.85%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
    --icon-close-filter: none;

    /* Spacing and radius. A short scale on purpose: five steps are enough for this app
       and a sixth only invites "roughly 22px". */
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 1rem;
    --space-4: 1.5rem;
    --space-5: 2.5rem;

    --radius-sm: 6px;
    --radius-md: 10px;
    --radius-lg: 16px;

    --shadow-card: 0 1px 2px rgba(13, 19, 69, 0.06), 0 6px 18px -12px rgba(13, 19, 69, 0.18);

    --font-ui: "Lexend", "Segoe UI", system-ui, sans-serif;
    --font-body: "Atkinson Hyperlegible Next", "Segoe UI", system-ui, sans-serif;

    /* Which of the three toggle glyphs the navbar button shows. */
    --icon-auto-display: inline-block;
    --icon-light-display: none;
    --icon-dark-display: none;
}

/* --------------------------------------------------------------------------
   3. Tokens — dark.

   Two selectors, and the two blocks must stay byte-identical apart from the selector:

     - the media query covers "the OS says dark and the user has not overruled it"
       (`:root:not([data-theme="light"])`), which is also what `data-theme="auto"` means;
     - `:root[data-theme="dark"]` covers "the user chose dark", including on a machine
       whose OS is light.

   One selector cannot do both. A media query alone cannot be overridden to dark on a
   light OS; an attribute alone cannot follow the OS. Nesting the attribute inside the
   media query loses the explicit choice in the other direction.
   -------------------------------------------------------------------------- */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        color-scheme: dark;

        --bg: #080b28; /* brand: Background-Blau */
        --surface: #0d1345; /* brand: Birnchenblau */
        --surface-2: #181f5a; /* derived: one lifted step above Birnchenblau */
        --border: rgba(207, 214, 255, 0.14);
        --border-strong: rgba(207, 214, 255, 0.3);

        --fg: #e4e8f7; /* blue-tinted off-white, deliberately not #fff */
        --muted: #a8afd6;

        --accent: #cfb6eb; /* brand: Birnchenlila, as-is — 9.7:1 on --surface */
        --accent-rgb: 207, 182, 235;
        --accent-hover: #e2d1f4;
        --accent-soft: #2b2358;
        --accent-ink: #0d1345; /* Birnchenblau ink on the lilac fill */

        --link: #a9affe; /* brand: Linkfarbe, as-is — 8.6:1 on --surface */
        --link-rgb: 169, 175, 254;
        --link-hover: #c3c7ff;

        --ok: #c9e35e; /* brand: Design-Akzent-Grün */
        --ok-rgb: 201, 227, 94;
        --ok-soft: #252f12;
        --warn: #e3b35c;
        --warn-rgb: 227, 179, 92;
        --warn-soft: #322a1a;
        --danger: #f0938c;
        --danger-rgb: 240, 147, 140;
        --danger-soft: #34201e;
        --info: #8fb4f2;
        --info-rgb: 143, 180, 242;
        --info-soft: #1a2448;

        --field-bg: #0a0e33; /* inset: between page and surface */

        --focus: #cfb6eb;
        --focus-ring: 0 0 0 3px rgba(207, 182, 235, 0.4);

        --icon-select-arrow: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23a8afd6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
        --icon-switch-knob: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28168, 175, 214, 0.5%29'/%3e%3c/svg%3e");
        --icon-navbar-toggler: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28168, 175, 214, 0.85%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
        --icon-close-filter: invert(1) grayscale(100%) brightness(190%);

        --shadow-card: 0 0 0 1px rgba(207, 214, 255, 0.1);
    }
}

:root[data-theme="dark"] {
    color-scheme: dark;

    --bg: #080b28; /* brand: Background-Blau */
    --surface: #0d1345; /* brand: Birnchenblau */
    --surface-2: #181f5a; /* derived: one lifted step above Birnchenblau */
    --border: rgba(207, 214, 255, 0.14);
    --border-strong: rgba(207, 214, 255, 0.3);

    --fg: #e4e8f7; /* blue-tinted off-white, deliberately not #fff */
    --muted: #a8afd6;

    --accent: #cfb6eb; /* brand: Birnchenlila, as-is — 9.7:1 on --surface */
    --accent-rgb: 207, 182, 235;
    --accent-hover: #e2d1f4;
    --accent-soft: #2b2358;
    --accent-ink: #0d1345; /* Birnchenblau ink on the lilac fill */

    --link: #a9affe; /* brand: Linkfarbe, as-is — 8.6:1 on --surface */
    --link-rgb: 169, 175, 254;
    --link-hover: #c3c7ff;

    --ok: #c9e35e; /* brand: Design-Akzent-Grün */
    --ok-rgb: 201, 227, 94;
    --ok-soft: #252f12;
    --warn: #e3b35c;
    --warn-rgb: 227, 179, 92;
    --warn-soft: #322a1a;
    --danger: #f0938c;
    --danger-rgb: 240, 147, 140;
    --danger-soft: #34201e;
    --info: #8fb4f2;
    --info-rgb: 143, 180, 242;
    --info-soft: #1a2448;

    --field-bg: #0a0e33; /* inset: between page and surface */

    --focus: #cfb6eb;
    --focus-ring: 0 0 0 3px rgba(207, 182, 235, 0.4);

    --icon-select-arrow: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23a8afd6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
    --icon-switch-knob: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28168, 175, 214, 0.5%29'/%3e%3c/svg%3e");
    --icon-navbar-toggler: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28168, 175, 214, 0.85%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
    --icon-close-filter: invert(1) grayscale(100%) brightness(190%);

    --shadow-card: 0 0 0 1px rgba(207, 214, 255, 0.1);
}

/* Which glyph the theme button shows: the CURRENT state, not the destination. The
   three rules are outside the token blocks because they key on the attribute alone —
   "auto" is a real, visible state here, and it is the default. */
:root[data-theme="light"] {
    --icon-auto-display: none;
    --icon-light-display: inline-block;
    --icon-dark-display: none;
}

:root[data-theme="dark"] {
    --icon-auto-display: none;
    --icon-light-display: none;
    --icon-dark-display: inline-block;
}

/* --------------------------------------------------------------------------
   4. Bootstrap bridge.

   Bootstrap 5.3 is assembled from `--bs-*` custom properties, so recolouring it is a
   matter of assignment, not of out-specifying it. Everything here is one of:
     (a) a root-level `--bs-*` that Bootstrap itself reads everywhere, or
     (b) a component's own variable block (`.btn-primary { --bs-btn-bg: ... }`), which
         is the documented extension point.
   The three literal-colour rules at the end are the exceptions Bootstrap hard-codes;
   each says so.
   -------------------------------------------------------------------------- */
:root {
    /* Core surfaces and ink. */
    --bs-body-bg: var(--bg);
    --bs-body-bg-rgb: 246, 250, 228;
    --bs-body-color: var(--fg);
    --bs-body-color-rgb: 13, 19, 69;
    --bs-emphasis-color: var(--fg);
    --bs-emphasis-color-rgb: 13, 19, 69;
    --bs-secondary-color: var(--muted);
    --bs-secondary-bg: var(--surface-2);
    --bs-secondary-bg-rgb: 240, 245, 216;
    --bs-tertiary-color: var(--muted);
    --bs-tertiary-bg: var(--surface-2);
    /* `.bg-body-tertiary` (the footer) and `.bg-body-secondary` (the modifier badges)
       compose their own alpha, so they read this triple and NOT --bs-tertiary-bg. That
       is why the footer stayed a white slab in dark mode until this line existed. */
    --bs-tertiary-bg-rgb: 240, 245, 216;
    --bs-heading-color: var(--fg);

    --bs-border-color: var(--border);
    --bs-border-color-translucent: var(--border);
    --bs-border-radius: var(--radius-md);
    --bs-border-radius-sm: var(--radius-sm);
    --bs-border-radius-lg: var(--radius-lg);
    --bs-border-radius-xl: var(--radius-lg);

    /* Type. 15px/1.6 with the body face; Lexend is applied to headings in section 5. */
    --bs-body-font-family: var(--font-body);
    --bs-body-font-size: 0.9375rem;
    --bs-body-line-height: 1.6;

    --bs-link-color: var(--link);
    --bs-link-color-rgb: var(--link-rgb);
    --bs-link-hover-color: var(--link-hover);

    /* `primary` is the accent; `secondary` is the muted ink. The subtle/emphasis trio
       is what `.alert-*`, `.badge`, `.bg-*-subtle` and `.text-*-emphasis` are built
       from, so bridging it recolours all of them at once. */
    --bs-primary: var(--accent);
    --bs-primary-rgb: var(--accent-rgb);
    --bs-primary-bg-subtle: var(--accent-soft);
    --bs-primary-border-subtle: var(--accent);
    --bs-primary-text-emphasis: var(--accent);

    --bs-secondary: var(--muted);
    --bs-secondary-rgb: 89, 89, 89;
    --bs-secondary-bg-subtle: var(--surface-2);
    --bs-secondary-border-subtle: var(--border);
    --bs-secondary-text-emphasis: var(--muted);

    --bs-success: var(--ok);
    --bs-success-rgb: var(--ok-rgb);
    --bs-success-bg-subtle: var(--ok-soft);
    --bs-success-border-subtle: var(--ok);
    --bs-success-text-emphasis: var(--ok);

    --bs-warning: var(--warn);
    --bs-warning-rgb: var(--warn-rgb);
    --bs-warning-bg-subtle: var(--warn-soft);
    --bs-warning-border-subtle: var(--warn);
    --bs-warning-text-emphasis: var(--warn);

    --bs-danger: var(--danger);
    --bs-danger-rgb: var(--danger-rgb);
    --bs-danger-bg-subtle: var(--danger-soft);
    --bs-danger-border-subtle: var(--danger);
    --bs-danger-text-emphasis: var(--danger);

    --bs-info: var(--info);
    --bs-info-rgb: var(--info-rgb);
    --bs-info-bg-subtle: var(--info-soft);
    --bs-info-border-subtle: var(--info);
    --bs-info-text-emphasis: var(--info);

    /* `.text-dark` / `.btn-dark` are used as "the strong neutral", which in a dark
       theme must not be near-black on near-black. */
    --bs-dark: var(--fg);
    --bs-dark-rgb: 13, 19, 69;
    --bs-light: var(--surface-2);
    --bs-light-rgb: 240, 245, 216;

    --bs-focus-ring-color: rgba(var(--accent-rgb), 0.35);
    --bs-focus-ring-width: 3px;
    --bs-code-color: var(--accent);
    --bs-highlight-bg: var(--accent-soft);
}

/* The `-rgb` twins are literals (a custom property cannot be decomposed into channels),
   so they are the one thing the dark blocks above cannot express and have to be
   restated here — same two-selector rule as section 3. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --bs-body-bg-rgb: 8, 11, 40;
        --bs-body-color-rgb: 228, 232, 247;
        --bs-emphasis-color-rgb: 228, 232, 247;
        --bs-secondary-rgb: 168, 175, 214;
        --bs-secondary-bg-rgb: 24, 31, 90;
        --bs-tertiary-bg-rgb: 24, 31, 90;
        --bs-dark-rgb: 228, 232, 247;
        --bs-light-rgb: 24, 31, 90;
    }
}

:root[data-theme="dark"] {
    --bs-body-bg-rgb: 8, 11, 40;
    --bs-body-color-rgb: 228, 232, 247;
    --bs-emphasis-color-rgb: 228, 232, 247;
    --bs-secondary-rgb: 168, 175, 214;
    --bs-secondary-bg-rgb: 24, 31, 90;
    --bs-tertiary-bg-rgb: 24, 31, 90;
    --bs-dark-rgb: 228, 232, 247;
    --bs-light-rgb: 24, 31, 90;
}

/* Components whose variables live on the component, not on :root. */
.card {
    --bs-card-bg: var(--surface);
    --bs-card-color: var(--fg);
    --bs-card-border-color: var(--border);
    --bs-card-cap-bg: transparent;
    --bs-card-cap-color: var(--fg);
    --bs-card-box-shadow: var(--shadow-card);
}

.modal {
    --bs-modal-bg: var(--surface);
    --bs-modal-color: var(--fg);
    --bs-modal-border-color: var(--border);
    --bs-modal-header-border-color: var(--border);
    --bs-modal-footer-border-color: var(--border);
}

.dropdown-menu {
    --bs-dropdown-bg: var(--surface);
    --bs-dropdown-color: var(--fg);
    --bs-dropdown-border-color: var(--border);
    --bs-dropdown-link-color: var(--fg);
    --bs-dropdown-link-hover-bg: var(--surface-2);
    --bs-dropdown-link-active-bg: var(--accent);
    --bs-dropdown-link-active-color: var(--accent-ink);
}

.list-group {
    --bs-list-group-bg: var(--surface);
    --bs-list-group-color: var(--fg);
    --bs-list-group-border-color: var(--border);
    --bs-list-group-action-hover-bg: var(--surface-2);
    --bs-list-group-active-bg: var(--accent);
    --bs-list-group-active-border-color: var(--accent);
    --bs-list-group-active-color: var(--accent-ink);
}

.table {
    --bs-table-color: var(--fg);
    --bs-table-bg: transparent;
    --bs-table-border-color: var(--border);
    --bs-table-striped-bg: var(--surface-2);
    --bs-table-striped-color: var(--fg);
    --bs-table-hover-bg: var(--surface-2);
    --bs-table-hover-color: var(--fg);
}

/* `.table-light` (used for every table head here) hard-codes #f8f9fa / #000, so the
   head stayed a white slab in dark mode. Same for `.btn-outline-success`. */
.table-light {
    --bs-table-color: var(--fg);
    --bs-table-bg: var(--surface-2);
    --bs-table-border-color: var(--border);
    --bs-table-striped-color: var(--fg);
    --bs-table-active-color: var(--fg);
    --bs-table-hover-color: var(--fg);
}

.btn-outline-success {
    --bs-btn-color: var(--ok);
    --bs-btn-border-color: var(--ok);
    --bs-btn-hover-color: var(--bg);
    --bs-btn-hover-bg: var(--ok);
    --bs-btn-hover-border-color: var(--ok);
    --bs-btn-active-color: var(--bg);
    --bs-btn-active-bg: var(--ok);
    --bs-btn-active-border-color: var(--ok);
    --bs-btn-focus-shadow-rgb: var(--ok-rgb);
}

.navbar {
    --bs-navbar-color: var(--muted);
    --bs-navbar-hover-color: var(--fg);
    --bs-navbar-active-color: var(--accent);
    --bs-navbar-brand-color: var(--fg);
    --bs-navbar-brand-hover-color: var(--fg);
    --bs-navbar-disabled-color: var(--muted);
    --bs-navbar-toggler-border-color: var(--border);
    --bs-navbar-toggler-focus-width: 3px;
}

.btn-primary {
    --bs-btn-color: var(--accent-ink);
    --bs-btn-bg: var(--accent);
    --bs-btn-border-color: var(--accent);
    --bs-btn-hover-color: var(--accent-ink);
    --bs-btn-hover-bg: var(--accent-hover);
    --bs-btn-hover-border-color: var(--accent-hover);
    --bs-btn-active-color: var(--accent-ink);
    --bs-btn-active-bg: var(--accent-hover);
    --bs-btn-active-border-color: var(--accent-hover);
    --bs-btn-disabled-color: var(--accent-ink);
    --bs-btn-disabled-bg: var(--accent);
    --bs-btn-disabled-border-color: var(--accent);
    --bs-btn-focus-shadow-rgb: var(--accent-rgb);
}

.btn-outline-primary {
    --bs-btn-color: var(--accent);
    --bs-btn-border-color: var(--border-strong);
    --bs-btn-hover-color: var(--accent-ink);
    --bs-btn-hover-bg: var(--accent);
    --bs-btn-hover-border-color: var(--accent);
    --bs-btn-active-color: var(--accent-ink);
    --bs-btn-active-bg: var(--accent);
    --bs-btn-active-border-color: var(--accent);
    --bs-btn-disabled-color: var(--muted);
    --bs-btn-disabled-border-color: var(--border);
    --bs-btn-focus-shadow-rgb: var(--accent-rgb);
}

.btn-outline-secondary {
    --bs-btn-color: var(--fg);
    --bs-btn-border-color: var(--border-strong);
    --bs-btn-hover-color: var(--fg);
    --bs-btn-hover-bg: var(--surface-2);
    --bs-btn-hover-border-color: var(--border-strong);
    --bs-btn-active-color: var(--fg);
    --bs-btn-active-bg: var(--surface-2);
    --bs-btn-active-border-color: var(--border-strong);
    --bs-btn-disabled-color: var(--muted);
    --bs-btn-disabled-border-color: var(--border);
}

.btn-outline-dark {
    --bs-btn-color: var(--fg);
    --bs-btn-border-color: var(--fg);
    --bs-btn-hover-color: var(--bg);
    --bs-btn-hover-bg: var(--fg);
    --bs-btn-hover-border-color: var(--fg);
    --bs-btn-active-color: var(--bg);
    --bs-btn-active-bg: var(--fg);
    --bs-btn-active-border-color: var(--fg);
}

.btn-dark {
    --bs-btn-color: var(--bg);
    --bs-btn-bg: var(--fg);
    --bs-btn-border-color: var(--fg);
    --bs-btn-hover-color: var(--bg);
    --bs-btn-hover-bg: var(--muted);
    --bs-btn-hover-border-color: var(--muted);
    --bs-btn-active-color: var(--bg);
    --bs-btn-active-bg: var(--muted);
    --bs-btn-active-border-color: var(--muted);
}

.btn-success {
    --bs-btn-color: var(--bg);
    --bs-btn-bg: var(--ok);
    --bs-btn-border-color: var(--ok);
    --bs-btn-hover-color: var(--bg);
    --bs-btn-hover-bg: var(--ok);
    --bs-btn-hover-border-color: var(--ok);
    --bs-btn-active-color: var(--bg);
    --bs-btn-active-bg: var(--ok);
    --bs-btn-active-border-color: var(--ok);
    --bs-btn-disabled-color: var(--bg);
    --bs-btn-disabled-bg: var(--ok);
    --bs-btn-disabled-border-color: var(--ok);
    --bs-btn-focus-shadow-rgb: var(--ok-rgb);
}

.btn-outline-danger {
    --bs-btn-color: var(--danger);
    --bs-btn-border-color: var(--danger);
    --bs-btn-hover-color: var(--bg);
    --bs-btn-hover-bg: var(--danger);
    --bs-btn-hover-border-color: var(--danger);
    --bs-btn-active-color: var(--bg);
    --bs-btn-active-bg: var(--danger);
    --bs-btn-active-border-color: var(--danger);
    --bs-btn-focus-shadow-rgb: var(--danger-rgb);
}

/* Fields read as inset rather than as page: `.form-control` takes --bs-body-bg, which
   is now the page colour, so a field would otherwise be invisible until focused. */
.form-control,
.form-select {
    background-color: var(--field-bg);
    color: var(--fg);
    border-color: var(--border-strong);
}

.form-control::placeholder {
    color: var(--muted);
    opacity: 1;
}

/* The three places Bootstrap 5.3 hard-codes its blue instead of reading a variable
   (`.form-control:focus`, `.form-check-input:checked`, `.form-select:focus`). There is
   no variable to bridge, so these are assignments over a literal — the only rules in
   this file that override Bootstrap by selector, and the reason each is spelled out. */
.form-control:focus,
.form-select:focus {
    background-color: var(--field-bg);
    color: var(--fg);
    border-color: var(--accent);
    box-shadow: var(--focus-ring);
}

.form-check-input:checked {
    background-color: var(--accent);
    border-color: var(--accent);
}

.form-check-input:focus {
    border-color: var(--accent);
    box-shadow: var(--focus-ring);
}

/* Bootstrap's own theme-swapped assets, driven by the tokens instead of
   `[data-bs-theme=dark]` (which this app deliberately does not set — `data-theme` is
   the single source of truth for the theme). */
.form-select {
    --bs-form-select-bg-img: var(--icon-select-arrow);
}

.form-switch .form-check-input:not(:checked):not(:focus) {
    --bs-form-switch-bg: var(--icon-switch-knob);
}

.navbar-toggler-icon {
    --bs-navbar-toggler-icon-bg: var(--icon-navbar-toggler);
}

.btn-close {
    filter: var(--icon-close-filter);
}

/* --------------------------------------------------------------------------
   5. Base typography and layout.
   -------------------------------------------------------------------------- */
html {
    -webkit-text-size-adjust: 100%;
}

body {
    background-color: var(--bg);
    color: var(--fg);
    font-family: var(--font-body);
    font-size: 0.9375rem; /* 15px */
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
}

h1,
h2,
h3,
h4,
h5,
h6,
.navbar-brand,
.card-header,
.form-label,
.btn {
    font-family: var(--font-ui);
}

h1,
h2,
h3,
h4,
h5,
h6 {
    color: var(--fg);
    font-weight: 600;
    letter-spacing: -0.01em;
    /* A heading that wraps should break into even lines rather than leave one orphan
       word; `pretty` below does the paragraph equivalent. */
    text-wrap: balance;
}

p {
    text-wrap: pretty;
}

/* Prose blocks get a measure. Deliberately NOT on every <p>: the app is mostly cards,
   labels and figures, where a 65ch cap would leave odd gaps inside a grid cell. Put
   `.prose` on the container that actually holds running text. */
.prose,
.prose p,
.prose li {
    max-width: 65ch;
}

a {
    color: var(--link);
    text-underline-offset: 0.15em;
}

a:hover {
    color: var(--link-hover);
}

::selection {
    background-color: var(--accent-soft);
    color: var(--fg);
}

/* One focus ring for the whole app, on the focus token. `:focus-visible` rather than
   `:focus` so a mouse click on a card button does not light up, but every keyboard
   path does. */
:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
    box-shadow: none;
}

.btn:focus-visible,
.form-control:focus-visible,
.form-select:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
}

/* Alpine hides its component roots until it has initialised them. */
[x-cloak] {
    display: none !important;
}

/* --------------------------------------------------------------------------
   6. App pieces.
   -------------------------------------------------------------------------- */

/* The navbar is a plain surface with a hairline under it. It used to be a
   `backdrop-filter: blur(10px)` translucent bar, which forced a compositor layer on
   every scroll and turned into a grey slab wherever the filter is unsupported. */
.navbar-app {
    background-color: var(--surface);
    border-bottom: 1px solid var(--border);
}

.navbar-app .nav-link.active {
    color: var(--accent);
}

/* The wordmark. Formerly `.text-gradient`, a background-clipped gradient that made the
   app name unselectable-looking and invisible in forced-colors mode. */
.brand {
    color: var(--accent);
    font-family: var(--font-ui);
    letter-spacing: -0.02em;
}

/* Figures — stats, counts, ratings, anything that changes in place. Tabular figures
   keep the digits on a fixed grid so a counter going 9 -> 10 does not shift the
   layout, and a column of ratings lines up. */
.num,
.display-1,
.display-2,
.display-3,
.display-4,
.display-5,
.display-6,
.stat-figure,
td.num,
th.num {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";
}

.stat-figure {
    font-family: var(--font-ui);
    font-weight: 600;
    letter-spacing: -0.02em;
    line-height: 1.1;
}

.stat-label {
    color: var(--muted);
    font-family: var(--font-ui);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

/* The navbar's two controls. Their sizes used to be `style="..."` attributes, which
   CSP counts as inline styles; the last of those is gone with issue #33 and
   `style-src` no longer carries 'unsafe-inline'. Anything that would have been an
   inline style belongs in this file. */
.locale-select {
    width: 108px;
}

/* The login card is vertically centred in the viewport rather than pinned to the top.
   This was `style="min-height: 75vh"` on the row -- the last inline style in the
   templates, and the reason `style-src` needed 'unsafe-inline' (issue #33). */
.login-viewport {
    min-height: 75vh;
}

.theme-toggle {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    padding: 0;
}

/* The theme toggle's three glyphs. All three ship in the markup; the token blocks in
   sections 2-3 decide which one is displayed, so the button needs no JavaScript to
   render correctly on the very first paint. */
.theme-icon-auto {
    display: var(--icon-auto-display);
}

.theme-icon-light {
    display: var(--icon-light-display);
}

.theme-icon-dark {
    display: var(--icon-dark-display);
}

/* Rating scales — the review modal and the manual-log modal (issues #37-#39).

   The design rule is that the LABEL is the rating and the number is only a handle:
   each step carries the owner's own wording as its accessible name and its tooltip,
   and the picked wording is printed in full under the scale. That is what lets the
   effort scale run 7 = effortless without anyone having to remember which way it goes.

   Colour: three gentle bands from the semantic tokens, and only on the SELECTED step.
   --danger is deliberately absent — a low rating is an honest answer, not an error,
   and ADR 0007 keeps every surface free of anything that reads as a scolding. */
.rating-field + .rating-field {
    margin-top: var(--space-3);
}

.rating-field-label {
    font-family: var(--font-ui);
    font-weight: 600;
}

.rating-hint {
    color: var(--muted);
    font-size: 0.875rem;
}

.rating-optional {
    color: var(--muted);
    font-family: var(--font-ui);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.rating-scale {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    margin-top: var(--space-1);
}

.rating-step {
    flex: 1 1 2.25rem;
    min-width: 2.25rem;
    padding: var(--space-2) 0;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background-color: var(--surface);
    color: var(--muted);
    line-height: 1;
}

.rating-step:hover {
    border-color: var(--accent);
    color: var(--fg);
}

.rating-step:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
}

/* The number is a handle, not the message: small, quiet and tabular. */
.rating-step-number {
    font-size: 0.8125rem;
}

.rating-step-selected {
    border-width: 2px;
    color: var(--fg);
    font-weight: 600;
}

.rating-step-selected.rating-band-low {
    background-color: var(--surface-2);
    border-color: var(--muted);
}

.rating-step-selected.rating-band-mid {
    background-color: var(--warn-soft);
    border-color: var(--warn);
}

.rating-step-selected.rating-band-high {
    background-color: var(--ok-soft);
    border-color: var(--ok);
}

.rating-chosen {
    margin-top: var(--space-1);
    color: var(--fg);
}

.rating-chosen-empty {
    color: var(--muted);
}

.rating-field-compact .rating-chosen,
.rating-field-compact .rating-step-number {
    font-size: 0.8125rem;
}

/* "Rate the parts": a native <details>, collapsed by default per the interview. */
/* Two native `<details>` disclosures share one look: the review modal's "rate the
   parts" and the launcher's "Adjust". Both are collapsed by default and both are
   plain HTML on purpose -- a Bootstrap collapse would need JavaScript, and an inline
   style is refused outright by the CSP. */
.rating-parts,
.launcher-adjust {
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: var(--space-2) var(--space-3);
}

.rating-parts-summary,
.launcher-adjust-summary {
    cursor: pointer;
    font-family: var(--font-ui);
    font-weight: 600;
}

.rating-parts-body,
.launcher-adjust-body {
    margin-top: var(--space-2);
}

/* The quiet relaxation line under a proposal (#55). Muted body text and nothing
   else: it is a note about what the draw set aside, never a warning, so it borrows
   no alert colour and carries no icon. */
.relaxation-note {
    color: var(--muted);
    font-size: 0.875rem;
    margin-bottom: var(--space-3);
}

/* The comparison card (#57): the rating WORDING carries the meaning and the number
   stays small and muted beside it, which is how a 1..7 scale stays readable without
   saying which way it runs in the number itself. */
.comparison-row {
    align-items: baseline;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-bottom: var(--space-1);
}

.comparison-dimension {
    color: var(--muted);
    font-family: var(--font-ui);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    min-width: 5.5rem;
    text-transform: uppercase;
}

.comparison-value {
    font-weight: 600;
}

.comparison-number {
    color: var(--muted);
    font-feature-settings: "tnum";
    font-size: 0.8125rem;
    font-variant-numeric: tabular-nums;
}

.comparison-streak {
    font-family: var(--font-ui);
    font-weight: 600;
}

/* The analysis page's quality strip (#58): the last ten ratings use the same wording
   plus quiet number as every other rating, spaced as a sequence rather than a chart. */
.trend-points {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.trend-point {
    align-items: baseline;
    display: inline-flex;
    font-family: var(--font-ui);
    gap: var(--space-1);
}

/* The strip of the last ten sessions. Three marks, all of them in the muted colour
   and none of them red: a hand-only session is history, not a fault, and the
   counted/protected split is drawn as a difference in weight rather than in hue. */
.session-strip {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
}

.session-dot {
    background-color: var(--muted);
    border-radius: 50%;
    display: inline-block;
    height: 0.7rem;
    width: 0.7rem;
}

.session-dot-hand_only_protected {
    background-color: transparent;
    border: 2px solid var(--muted);
    opacity: 0.75;
}

.session-dot-hand_only_counted {
    background-color: transparent;
    border: 1px dotted var(--muted);
    opacity: 0.6;
}

/* The history table's three rating columns: the wording first, truncated to keep the
   row readable and carried in full by the title attribute, with the number beside it
   as secondary text. */
.rating-cell {
    display: inline-flex;
    align-items: baseline;
    gap: var(--space-1);
}

.rating-cell-label {
    display: inline-block;
    max-width: 13rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.rating-cell-number {
    color: var(--muted);
    font-size: 0.8125rem;
}

.rating-cell-empty {
    color: var(--muted);
}

/* The settings page's one numeric field. Bootstrap's .form-control fills the whole
   card, which makes a 0..1 share look like a free-text field; capping the width says
   "a small number goes here" without adding a second control. */
.settings-number {
    max-width: 10rem;
}

/* The two honest-logging prompts (#54, #60): eight hand-only reasons in two groups,
   six skip reasons in one, each one tap.

   The counted/protected split is real and derived (src/core/models.py), and it is
   deliberately never spelled out here: the only trace of it on screen is the rule
   between the two groups. A heading saying which half breaks the streak would turn
   an honest answer into a confession, and the streak itself is batch 8. */
.reason-block {
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: var(--space-3);
}

.reason-question {
    font-family: var(--font-ui);
    font-weight: 600;
}

.reason-hint {
    color: var(--muted);
    font-size: 0.875rem;
}

.reason-group {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    margin-top: var(--space-2);
}

/* The quiet grouping, and the whole of it: a hairline and a little air. */
.reason-group + .reason-group {
    border-top: 1px solid var(--border);
    margin-top: var(--space-3);
    padding-top: var(--space-3);
}

.reason-chip {
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-lg);
    background-color: var(--surface);
    color: var(--fg);
    font-size: 0.9375rem;
    padding: var(--space-1) var(--space-3);
}

.reason-chip:hover {
    border-color: var(--accent);
}

.reason-chip:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
}

.reason-chip-selected {
    background-color: var(--accent-soft);
    border-color: var(--accent);
    border-width: 2px;
    font-weight: 600;
}

.reason-skip {
    color: var(--muted);
    font-size: 0.875rem;
    margin-top: var(--space-2);
    padding: 0;
}

.reason-note {
    margin-top: var(--space-3);
}

/* Respect a reduced-motion preference: this app has no animation worth keeping. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}
