/* ============================================================
   Domolytics — the token sheet. THE single source of colour.

   The "Geode" palette: Quartz (light) and Obsidian (dark) as one
   build with a switch between them.

     :root                     Quartz   — light  ← DEFAULT
     :root[data-theme="dark"]  Obsidian — dark

   Light sits on plain :root deliberately: it is what renders with no
   JavaScript and no stored preference. Dark is opt-in, and
   `prefers-color-scheme` is never consulted.

   `main_v2.css` and `components_v2.css` contain NO colour literals and
   NO colour variables of their own — every colour they name resolves
   through a token declared here. Switching theme therefore never
   touches component CSS, and adding a third theme is one more block in
   this file and nothing else. A dark-mode or contrast fix belongs here,
   not in a component.
   ============================================================ */

/* ------------------------------------------------------------
   QUARTZ — light · the default
   ------------------------------------------------------------ */
:root {
  color-scheme: light;

  /* Brand — the deep end carries text and icons on a light ground */
  --brand-400: #3a4cc4;
  --brand-500: #5468e0;
  --brand-600: #7488fa;
  --brand-700: #8fa0fc;
  --teal-400: #0c7d78;
  --teal-600: #5fd8d0;

  /* Ground */
  --bg: #f6f7fb;
  --bg-2: #eef0f7;
  --panel: #ffffff;

  /* Surfaces — raised means lighter in BOTH themes. That is why these
     are two token sets and not one inversion filter. */
  --glass: #ffffff;
  --glass-2: #f2f4fb;
  --row-hover: #f4f6fd;
  --sunken: rgba(19, 26, 46, 0.035);
  --sunken-2: #f7f9fd;
  --sunken-3: rgba(19, 26, 46, 0.04);
  --tint: rgba(19, 26, 46, 0.025);
  --tint-2: rgba(19, 26, 46, 0.03);
  /* `--table-group-bg` (task 0174e) was removed by task 0176e with the People card,
     its only consumer. If a banded group-title row is ever wanted again, it needs its
     own token for the reason the old one had: it must stay distinct from BOTH the rows
     on `--panel` and the column titles on `--sunken`, and `--bg-2` — the obvious
     candidate — collapses onto `--panel` in monochrome. */
  /* The three-second mark on a row that was just saved (task 0175b). Brand-tinted on
     purpose: green and red already mean *bought* and *not found* on the shopping list,
     and a third meaning borrowing either would be a lie for three seconds. `--text`
     measures 13.6:1 on it, `--text-muted` 5.2:1. */
  --row-saved-bg: #dfe4fb;
  /* The "travel companion" mark on a contact (task 0179f). Teal, because every group
     chip is on `--glass-2` and the mark is NOT a group — it is computed from Travel
     and cannot be assigned. #0a5f5b on #dcf1ef measures 6.4:1. */
  --chip-travels-bg: #dcf1ef;
  --chip-travels-text: #0a5f5b;
  --window-bg: linear-gradient(165deg, #ffffff, #fbfcfe);
  --footer-bg: #eef0f7;
  --nav-bg: rgba(246, 247, 251, 0.82);
  --backdrop: rgba(19, 26, 46, 0.45);

  /* Lines */
  --stroke: rgba(19, 26, 46, 0.12);
  --stroke-2: rgba(19, 26, 46, 0.22);
  --hairline: rgba(19, 26, 46, 0.07);
  --grid-line: rgba(19, 26, 46, 0.035);

  /* Controls */
  --input-bg: #ffffff;
  --input-bg-focus: #ffffff;
  --track: rgba(19, 26, 46, 0.12);
  --focus-ring: 0 0 0 3px rgba(92, 114, 245, 0.22);

  /* Text */
  --text: #131a2e;
  --text-muted: #515c7a;
  --text-dim: #5a6379;
  --on-accent: #ffffff;   /* sits on a gradient chip */
  --on-brand: #ffffff;    /* sits on the brand button */
  --on-danger: #ffffff;

  /* Semantic — never the brand hue */
  --success: #0a7048;  --success-bg: rgba(15, 157, 99, 0.11);
  --warning: #8f4207;  --warning-bg: rgba(180, 83, 9, 0.10);
  /* Text sitting ON `--warning-bg`, not on the page ground. `--warning` clears
     4.5:1 against white but only 4.35:1 once the amber wash is behind it, so a
     warning chip needs the darker step. The old `.badge--warn` used this same
     amber-800 for the same reason. */
  --warning-strong: #92400e;
  --danger:  #dc2626;  --danger-bg:  rgba(220, 38, 38, 0.09);
  --danger-hover: #b91c1c;
  --danger-border: rgba(220, 38, 38, 0.40);
  --danger-ring: 0 0 0 3px rgba(220, 38, 38, 0.16);
  --success-border: rgba(15, 157, 99, 0.28);
  --info-bg: rgba(92, 114, 245, 0.11);
  --info-border: rgba(92, 114, 245, 0.30);
  --violet: #6d4aff;

  /* Gradients — brand runs deep→deeper so the white label survives */
  --grad-brand:  linear-gradient(135deg, #5468e0, #4054d8);
  --grad-mark:   linear-gradient(140deg, #4054d8, #0f9e97);
  --grad-text:   linear-gradient(110deg, #4054d8 15%, #0f9e97 85%);
  --grad-rule:   linear-gradient(90deg, #4054d8, #0f9e97);
  --grad-active: linear-gradient(120deg, rgba(92, 114, 245, 0.30), rgba(47, 212, 200, 0.10));
  --grad-active-border: rgba(92, 114, 245, 0.42);
  --grad-cta:    linear-gradient(120deg, rgba(92, 114, 245, 0.14), rgba(47, 212, 200, 0.06));
  --grad-quote:  linear-gradient(140deg, rgba(92, 114, 245, 0.16), rgba(47, 212, 200, 0.06));
  --grad-quote-border: rgba(92, 114, 245, 0.28);

  /* Ambient field — alpha baked in, ~40% of the dark theme's strength.
     A saturated blur at the dark opacity tints body text on paper. */
  --field-1: rgba(92, 114, 245, 0.18);
  --field-2: rgba(95, 216, 208, 0.10);
  --field-3: rgba(143, 160, 252, 0.12);

  /* ---- Companion role badges (request 131 §6) ----
     One hue per role. Each pair is a tint background with a same-hue
     foreground, and every one is measured at 11.5px — normal text, so 4.5:1
     applies to all eight. Light: pale tint, ink-dark text. */
  --role-friend-bg:    #e6eafc;  --role-friend-fg:    #2f41bd;
  --role-family-bg:    #d9f3f0;  --role-family-fg:    #0d5f5a;
  --role-colleague-bg: #fbeed2;  --role-colleague-fg: #7a4e00;
  --role-partner-bg:   #fbe4ed;  --role-partner-fg:   #98204f;
  --role-coach-bg:     #ddf1e0;  --role-coach-fg:     #1d6127;
  --role-guide-bg:     #dcecf9;  --role-guide-fg:     #10556f;
  --role-customer-bg:  #eee3fa;  --role-customer-fg:  #61329b;
  --role-other-bg:     #e8eaf1;  --role-other-fg:     #434a61;
  /* "you are" (request 139). Orange is the one hue the eight roles leave free —
     blue, teal, amber, pink, green, sky, purple and grey are all taken — so the
     badge reads as a different KIND of label, not a ninth role. */
  --role-self-bg:      #ffe1cf;  --role-self-fg:      #8f3410;
  /* "shared with them" (request 195, task 0195b). Deliberately the NEUTRAL pair
     rather than a tenth hue: the nine above are spoken for, and a colour close
     enough to any of them would read as a role the person had been given. Like
     `--role-self-*` this is a different KIND of label, and the word on it is what
     carries the meaning. Named separately so it can be re-picked later without
     hunting through every use of the grey. */
  --badge-shared-bg:   #e8eaf1;  --badge-shared-fg:   #434a61;

  /* Airport codes and flight numbers as badges (request 192, task 0192). Two hues,
     because the story asks for the two to be told apart at a glance; they are the
     sky and purple pairs the role badges already measure for AA at 11.5px, which is
     exactly the size a chip sets. Not the role tokens themselves — an airport is not
     a companion role, and sharing the variable would tie two unrelated palettes. */
  --chip-airport-bg:   #dcecf9;  --chip-airport-fg:   #10556f;
  --chip-flight-bg:    #eee3fa;  --chip-flight-fg:    #61329b;

  /* Elevation — on paper, depth reads through opacity, not black */
  --shadow-sm: 0 1px 2px rgba(19, 26, 46, 0.07);
  --shadow-md: 0 8px 24px rgba(19, 26, 46, 0.09);
  --shadow-lg: 0 24px 56px rgba(19, 26, 46, 0.14);
  --glow: 0 8px 24px rgba(92, 114, 245, 0.28);
  --glow-hover: 0 10px 28px rgba(92, 114, 245, 0.34);

  /* ---- Navigation rail ----
     Inked in BOTH themes. Under Quartz it is a slab that stops the
     white rail and the white content column merging into each other;
     under Obsidian it becomes glass over the page. */
  --rail: #151c33;
  /* Opaque twin of the rail, for the mobile drawer, which floats over content
     where glass is unreadable (bug 0018). */
  --rail-solid: #151c33;
  --rail-2: #1d2645;
  --rail-border: transparent;
  --rail-text: #e6e9f5;
  --rail-muted: #a2abc6;
  --rail-dim: #79829e;
  --rail-stroke: rgba(255, 255, 255, 0.10);
  --rail-stroke-2: rgba(255, 255, 255, 0.18);
  --rail-hover: rgba(255, 255, 255, 0.07);
  --rail-active: linear-gradient(120deg, rgba(92, 114, 245, 0.55), rgba(15, 158, 151, 0.28));
  --rail-active-border: rgba(128, 147, 255, 0.55);
  --rail-active-text: #ffffff;
  --rail-active-icon: #7ee8e0;
  --rail-av: linear-gradient(135deg, #8093ff, #2fd4c8);
  --rail-av-fg: #0d1220;
  --rail-focus: #7ee8e0;

  /* ---- Non-colour: identical in both themes ---- */
  --r-xs: 4px;
  --r-sm: 6px;
  --r-md: 8px;
  --r-lg: 12px;
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
    Arial, "Noto Sans Hebrew", sans-serif;
  --mono: "SF Mono", "Cascadia Mono", Consolas, "Liberation Mono", monospace;
  --container: 1240px;
}

/* ------------------------------------------------------------
   OBSIDIAN — dark · opt-in
   Only colour is redeclared. Radii, type and layout are inherited
   from the block above, which keeps the two modes in step.
   ------------------------------------------------------------ */
:root[data-theme="dark"] {
  color-scheme: dark;

  /* Same hues; ramp reversed so the light end carries text on ink */
  --brand-400: #adb9ff;
  --brand-500: #5c72f5;
  --brand-600: #4356d6;
  --brand-700: #3543b4;
  --teal-400: #2fd4c8;
  --teal-600: #14a8a0;

  --bg: #0a0e1c;
  --bg-2: #0e1428;
  --panel: #121a30;

  --glass: rgba(255, 255, 255, 0.04);
  --glass-2: rgba(255, 255, 255, 0.07);
  --row-hover: rgba(255, 255, 255, 0.04);
  --sunken: rgba(0, 0, 0, 0.18);
  --sunken-2: rgba(0, 0, 0, 0.16);
  --sunken-3: rgba(0, 0, 0, 0.24);
  --tint: rgba(255, 255, 255, 0.02);
  --tint-2: rgba(255, 255, 255, 0.03);
  /* The three-second just-saved mark on a dark ground (task 0175b). Brand-tinted for
     the same reason as light: green and red already mean *bought* and *not found*.
     `--text` measures 7.84:1 on it. */
  --row-saved-bg: #26305a;
  /* Same mark, dark ground: #5fd8d0 on #123834 measures 8.4:1 (task 0179f). */
  --chip-travels-bg: #123834;
  --chip-travels-text: #5fd8d0;
  --window-bg: linear-gradient(165deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.02));
  --footer-bg: rgba(0, 0, 0, 0.3);
  --nav-bg: rgba(10, 14, 28, 0.78);
  --backdrop: rgba(4, 6, 16, 0.74);

  --stroke: rgba(255, 255, 255, 0.09);
  --stroke-2: rgba(255, 255, 255, 0.17);
  --hairline: rgba(255, 255, 255, 0.05);
  --grid-line: rgba(255, 255, 255, 0.025);

  --input-bg: rgba(0, 0, 0, 0.24);
  --input-bg-focus: rgba(0, 0, 0, 0.34);
  --track: rgba(255, 255, 255, 0.1);

  --text: #e8ecf7;
  --text-muted: #c3cbe1;
  --text-dim: #a3aed0;
  --on-accent: #0a0e1c;
  /* Dark flips the brand ramp light, so the label on a brand surface must go
     dark too — white on --brand-400 measures 2.78:1 (task 0161a). */
  --on-brand: #0a0e1c;
  --on-danger: #24070a;

  --success: #34d399;  --success-bg: rgba(52, 211, 153, 0.13);
  --warning: #fbbf24;  --warning-bg: rgba(251, 191, 36, 0.13);
  /* On ink the light amber is already the readable end of the ramp (6.08:1 on
     the chip), so the strong step stays where it is rather than going darker. */
  --warning-strong: #fbbf24;
  --danger:  #f87171;  --danger-bg:  rgba(248, 113, 113, 0.13);
  --danger-hover: #fb8a8a;
  --danger-border: rgba(248, 113, 113, 0.45);
  --danger-ring: 0 0 0 3px rgba(248, 113, 113, 0.18);
  --success-border: rgba(52, 211, 153, 0.28);
  --info-bg: rgba(92, 114, 245, 0.15);
  --info-border: rgba(92, 114, 245, 0.30);
  --violet: #a78bfa;

  --grad-brand:  linear-gradient(135deg, #96a5ff, #8093ff);
  --grad-mark:   linear-gradient(140deg, #8093ff, #2fd4c8);
  --grad-text:   linear-gradient(110deg, #8093ff 15%, #2fd4c8 85%);
  --grad-rule:   linear-gradient(90deg, #8093ff, #2fd4c8);
  --grad-active: linear-gradient(120deg, rgba(92, 114, 245, 0.32), rgba(47, 212, 200, 0.12));
  --grad-active-border: rgba(92, 114, 245, 0.4);
  --grad-cta:    linear-gradient(120deg, rgba(92, 114, 245, 0.18), rgba(47, 212, 200, 0.07));
  --grad-quote:  linear-gradient(140deg, rgba(92, 114, 245, 0.24), rgba(47, 212, 200, 0.08));
  --grad-quote-border: rgba(92, 114, 245, 0.32);

  --field-1: rgba(92, 114, 245, 0.30);
  --field-2: rgba(20, 168, 160, 0.16);
  --field-3: rgba(53, 67, 180, 0.22);

  /* Role badges on ink: the tint goes translucent so it sits on the page
     instead of glowing, and the text lightens to carry the contrast. */
  --role-friend-bg:    rgba(128, 147, 255, 0.18); --role-friend-fg:    #aeb9ff;
  --role-family-bg:    rgba(47, 212, 200, 0.16);  --role-family-fg:    #6fded4;
  --role-colleague-bg: rgba(240, 180, 70, 0.16);  --role-colleague-fg: #f0c473;
  --role-partner-bg:   rgba(244, 114, 160, 0.16); --role-partner-fg:   #f79cba;
  --role-coach-bg:     rgba(90, 200, 110, 0.16);  --role-coach-fg:     #89dd9a;
  --role-guide-bg:     rgba(90, 180, 235, 0.16);  --role-guide-fg:     #8fcdf2;
  --role-customer-bg:  rgba(180, 130, 240, 0.16); --role-customer-fg:  #cba9f7;
  --role-other-bg:     rgba(255, 255, 255, 0.10); --role-other-fg:     #c4ccdf;
  --role-self-bg:      rgba(255, 146, 84, 0.18);  --role-self-fg:      #ffb689;
  --badge-shared-bg:   rgba(255, 255, 255, 0.10); --badge-shared-fg:   #c4ccdf;

  /* Same two hues on a dark ground (request 192). */
  --chip-airport-bg:   rgba(90, 180, 235, 0.16);  --chip-airport-fg:   #8fcdf2;
  --chip-flight-bg:    rgba(180, 130, 240, 0.16); --chip-flight-fg:    #cba9f7;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.4);
  --shadow-lg: 0 24px 56px rgba(0, 0, 0, 0.55);
  --glow: 0 0 32px rgba(92, 114, 245, 0.35);
  --glow-hover: 0 0 44px rgba(92, 114, 245, 0.5);

  /* Glass here rather than a slab: the page is already dark, so the
     rail no longer has to hold its own edge. */
  --rail: rgba(0, 0, 0, 0.28);
  /* The same colour the desktop rail resolves to over the page, made opaque so
     the drawer covers what is behind it (bug 0018). */
  --rail-solid: #070a14;
  --rail-2: rgba(255, 255, 255, 0.04);
  --rail-border: var(--stroke);
  --rail-text: var(--text);
  /* NOT var(--text-muted) (task 0149b). In dark the rail is glass —
     rgba(0,0,0,0.28) over the page — so it composites LIGHTER than the light
     theme's solid #151c33 rail, and the page's muted colour loses the contrast
     it has everywhere else. Sampled from the painted pixels: #9aa5c4 on the
     composited rail is 3.67:1, under AA for the 10.5px group titles that were
     already using it and for the brand suffix added here. #b3bcd4 measures
     4.92:1 and still reads a step below --rail-text. */
  --rail-muted: #b3bcd4;
  --rail-dim: var(--text-dim);
  --rail-stroke: var(--stroke);
  --rail-stroke-2: var(--stroke-2);
  --rail-hover: var(--glass);
  --rail-active: var(--grad-active);
  --rail-active-border: rgba(92, 114, 245, 0.42);
  --rail-active-text: var(--text);
  --rail-active-icon: var(--teal-400);
  --rail-av: var(--grad-mark);
  --rail-av-fg: var(--on-accent);
  --rail-focus: var(--teal-400);
}

/* Cross-fade only after load, so switching animates but the first
   paint does not. theme.js adds .theme-ready on DOMContentLoaded. */
.theme-ready body,
.theme-ready .nav,
.theme-ready .topbar,
.theme-ready .side,
.theme-ready .panel,
.theme-ready .stat,
.theme-ready .trip,
.theme-ready .auth-card,
.theme-ready .window {
  transition: background-color 0.22s ease, border-color 0.22s ease, color 0.22s ease;
}
@media (prefers-reduced-motion: reduce) {
  .theme-ready * { transition: none !important; }
}

/* ============================================================================
   Contrast preferences (task 0162a)

   Two conditional layers *beneath* the product palette — they never run unless
   the user has already told their operating system what they need. There is no
   UI for either, and nothing here changes the default look.

   Dark is expressed only as `:root[data-theme="dark"]`, never via
   `prefers-color-scheme` (a deliberate product decision — see the head of this
   file), so each preference needs exactly two blocks, not three.
   ========================================================================== */

/* ---- prefers-contrast: more ---------------------------------------------
   Legibility outranks hierarchy here: the muted/dim steps are pulled close to
   body text (all >7:1, AAA territory) and the near-invisible hairline borders
   are raised past 3:1 so card and field edges are unmistakable. Measured
   against the surfaces each token actually lands on. */
@media (prefers-contrast: more) {
  :root {
    --text-muted: #2f3847;   /* 11.81:1 on white, 10.38:1 on --bg-2 */
    --text-dim:   #36404f;   /* 10.48:1 on white,  9.21:1 on --bg-2 */

    --stroke:     #7b8496;   /* 3.76:1 on white — was rgba(19,26,46,.12) */
    --stroke-2:   #5d6678;   /* 5.77:1 on white */
    --hairline:   rgba(19, 26, 46, 0.38);
    --grid-line:  rgba(19, 26, 46, 0.20);
    --track:      rgba(19, 26, 46, 0.45);

    --focus-ring: 0 0 0 3px rgba(58, 79, 208, 0.85);
  }

  :root[data-theme="dark"] {
    --text-muted: #e0e5f2;   /* 15.25:1 on --bg, 13.70:1 on --panel */
    --text-dim:   #d7dcec;   /* 14.04:1 on --bg, 12.61:1 on --panel */

    --stroke:     #7d86a0;   /* 5.30:1 on --bg */
    --stroke-2:   #8d96b0;   /* 6.52:1 on --bg */
    --hairline:   rgba(255, 255, 255, 0.34);
    --grid-line:  rgba(255, 255, 255, 0.18);
    --track:      rgba(255, 255, 255, 0.42);

    --focus-ring: 0 0 0 3px rgba(173, 185, 255, 0.9);
  }
}

/* ---- forced-colors: active (Windows High Contrast) ----------------------
   The job is to stop fighting the user's palette, not to restyle for it.
   Backgrounds and text are replaced by the system automatically; three things
   are NOT, and each would otherwise lose meaning:

     1. `background-image` survives forced-colors, so a gradient button keeps
        its fill while its text is forced to a system colour — which is how a
        primary button becomes unreadable. Drop the image, take a border.
     2. `box-shadow` is discarded, so any affordance carried only by a shadow
        (our focus ring included) needs an `outline` to survive.
     3. Purely decorative colour washes have nothing to say once recoloured. */
@media (forced-colors: active) {
  .btn,
  .btn-primary,
  .btn-quiet,
  .btn-outline,
  .chip,
  .pillstat,
  .trip-mark,
  .mark {
    background-image: none;
    border: 1px solid currentColor;
  }

  /* A shadow-only focus ring disappears here; an outline does not. */
  :focus-visible {
    outline: 3px solid Highlight;
    outline-offset: 2px;
  }

  /* Decorative only — recoloured it is noise, not information. */
  .aurora,
  .grid-bg {
    display: none;
  }

  /* The switch reads as two states by knob position; give it an edge so the
     track and knob stay distinguishable once both take system colours. */
  .theme-switch { border: 1px solid currentColor; }
  .theme-switch .ts-knob { border: 1px solid currentColor; }
}

/* ============================================================================
   Monochrome (task 0162c)

   A palette, not a stylesheet. `data-mono` composes with `data-theme`, so one
   set of overrides yields both directions:

       data-mono                       -> black on white
       data-mono + data-theme="dark"   -> white on black

   It also composes with `prefers-contrast: more` above, which only touches
   text/border steps and so simply lands on top.

   (No relation to the `--mono` token, which is the monospace font stack.)

   Every colour-bearing token is neutralised here. Hue that carried meaning —
   success/warning/danger — collapses to ink, so the rules at the end give those
   components a border: what was a colour difference has to become a shape
   difference, or the mode would trade one colour-only signal for another.
   ========================================================================== */
:root[data-mono] {
  color-scheme: light;

  --brand-400: #000000;  --brand-500: #000000;
  --brand-600: #000000;  --brand-700: #000000;
  --teal-400:  #000000;  --teal-600:  #000000;
  --violet:    #000000;

  --bg: #ffffff;  --bg-2: #ffffff;  --panel: #ffffff;
  --glass: #ffffff;  --glass-2: #f2f2f2;
  --row-hover: #e8e8e8;
  --sunken: #f0f0f0;  --sunken-2: #f7f7f7;  --sunken-3: #f0f0f0;
  --tint: #f4f4f4;  --tint-2: #ececec;
  /* Monochrome flattens `--success-bg` and `--danger-bg` onto the panel, so the status
     tints are gone here and a grey mark cannot be mistaken for one (task 0175b). Darker
     than `--row-hover` so the two do not read as the same thing. */
  --row-saved-bg: #d6d6d6;
  /* No hue survives here, so the mark is ink on paper and the border rule below is
     what keeps it a distinct mark rather than a group chip (task 0179f). */
  --chip-travels-bg: #ffffff;
  --chip-travels-text: #000000;
  --window-bg: #ffffff;  --footer-bg: #ffffff;  --nav-bg: #ffffff;
  --backdrop: rgba(0, 0, 0, 0.7);

  --stroke: #000000;  --stroke-2: #000000;
  --hairline: #000000;  --grid-line: #767676;
  --track: #000000;

  --text: #000000;  --text-muted: #000000;  --text-dim: #1f1f1f;
  --on-accent: #ffffff;  --on-brand: #ffffff;  --on-danger: #ffffff;

  --success: #000000;  --success-bg: #ffffff;  --success-border: #000000;
  --warning: #000000;  --warning-strong: #000000;  --warning-bg: #ffffff;
  --danger: #000000;   --danger-bg: #ffffff;  --danger-border: #000000;
  --danger-hover: #000000;  --danger-ring: rgba(0, 0, 0, 0.35);
  --info-bg: #ffffff;  --info-border: #000000;

  --input-bg: #ffffff;  --input-bg-focus: #ffffff;
  --field-1: rgba(0, 0, 0, 0.3);  --field-2: rgba(0, 0, 0, 0.2);  --field-3: rgba(0, 0, 0, 0.1);

  /* Thicker than the themed ring: without hue it has only weight to separate
     itself from an ordinary black border. */
  --focus-ring: 0 0 0 4px #000000;
  --glow: none;  --glow-hover: none;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.25);
  --shadow-md: 0 2px 6px rgba(0, 0, 0, 0.3);
  --shadow-lg: 0 6px 18px rgba(0, 0, 0, 0.35);

  /* A gradient that survives into monochrome is the bug this mode removes. */
  --grad-brand:  linear-gradient(135deg, #000000, #000000);
  --grad-cta:    linear-gradient(135deg, #000000, #000000);
  --grad-mark:   linear-gradient(140deg, #000000, #000000);
  --grad-text:   linear-gradient(110deg, #000000, #000000);
  --grad-rule:   linear-gradient(90deg, #000000, #000000);
  --grad-quote:  linear-gradient(135deg, #ffffff, #ffffff);
  --grad-quote-border: #000000;
  --grad-active: #e8e8e8;  --grad-active-border: #000000;

  --rail: #ffffff;  --rail-2: #ffffff;  --rail-solid: #ffffff;
  --rail-text: #000000;  --rail-muted: #000000;  --rail-dim: #1f1f1f;
  --rail-border: #000000;  --rail-stroke: #000000;  --rail-stroke-2: #000000;
  --rail-hover: #e8e8e8;
  --rail-active: #000000;  --rail-active-text: #ffffff;
  --rail-active-icon: #ffffff;  --rail-active-border: #000000;
  --rail-focus: #000000;
  --rail-av: linear-gradient(135deg, #000000, #000000);  --rail-av-fg: #ffffff;

  --role-self-bg: #ffffff;      --role-self-fg: #000000;
  --role-family-bg: #ffffff;    --role-family-fg: #000000;
  --role-friend-bg: #ffffff;    --role-friend-fg: #000000;
  --role-partner-bg: #ffffff;   --role-partner-fg: #000000;
  --role-colleague-bg: #ffffff; --role-colleague-fg: #000000;
  --role-customer-bg: #ffffff;  --role-customer-fg: #000000;
  --role-coach-bg: #ffffff;     --role-coach-fg: #000000;
  --role-guide-bg: #ffffff;     --role-guide-fg: #000000;
  --role-other-bg: #ffffff;     --role-other-fg: #000000;
  --badge-shared-bg: #ffffff;   --badge-shared-fg: #000000;
  /* Hue is what monochrome removes, so the two badges become one ink treatment
     and the chip border is what still says they are badges — the same trade the
     role badges above make (request 192). */
  --chip-airport-bg: #ffffff;   --chip-airport-fg: #000000;
  --chip-flight-bg: #ffffff;    --chip-flight-fg: #000000;
}

/* Same palette, inverted. */
:root[data-mono][data-theme="dark"] {
  color-scheme: dark;

  --brand-400: #ffffff;  --brand-500: #ffffff;
  --brand-600: #ffffff;  --brand-700: #ffffff;
  --teal-400:  #ffffff;  --teal-600:  #ffffff;
  --violet:    #ffffff;

  --bg: #000000;  --bg-2: #000000;  --panel: #000000;
  --glass: #000000;  --glass-2: #0d0d0d;
  --row-hover: #1c1c1c;
  --sunken: #0f0f0f;  --sunken-2: #080808;  --sunken-3: #0f0f0f;
  --tint: #0b0b0b;  --tint-2: #131313;
  --row-saved-bg: #2b2b2b;
  --chip-travels-bg: #000000;
  --chip-travels-text: #ffffff;
  --window-bg: #000000;  --footer-bg: #000000;  --nav-bg: #000000;
  --backdrop: rgba(0, 0, 0, 0.85);

  --stroke: #ffffff;  --stroke-2: #ffffff;
  --hairline: #ffffff;  --grid-line: #9a9a9a;
  --track: #ffffff;

  --text: #ffffff;  --text-muted: #ffffff;  --text-dim: #e4e4e4;
  --on-accent: #000000;  --on-brand: #000000;  --on-danger: #000000;

  --success: #ffffff;  --success-bg: #000000;  --success-border: #ffffff;
  --warning: #ffffff;  --warning-strong: #ffffff;  --warning-bg: #000000;
  --danger: #ffffff;   --danger-bg: #000000;  --danger-border: #ffffff;
  --danger-hover: #ffffff;  --danger-ring: rgba(255, 255, 255, 0.4);
  --info-bg: #000000;  --info-border: #ffffff;

  --input-bg: #000000;  --input-bg-focus: #000000;
  --field-1: rgba(255, 255, 255, 0.35);  --field-2: rgba(255, 255, 255, 0.25);  --field-3: rgba(255, 255, 255, 0.15);

  --focus-ring: 0 0 0 4px #ffffff;
  --glow: none;  --glow-hover: none;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.8);
  --shadow-md: 0 2px 6px rgba(0, 0, 0, 0.85);
  --shadow-lg: 0 6px 18px rgba(0, 0, 0, 0.9);

  --grad-brand:  linear-gradient(135deg, #ffffff, #ffffff);
  --grad-cta:    linear-gradient(135deg, #ffffff, #ffffff);
  --grad-mark:   linear-gradient(140deg, #ffffff, #ffffff);
  --grad-text:   linear-gradient(110deg, #ffffff, #ffffff);
  --grad-rule:   linear-gradient(90deg, #ffffff, #ffffff);
  --grad-quote:  linear-gradient(135deg, #000000, #000000);
  --grad-quote-border: #ffffff;
  --grad-active: #1c1c1c;  --grad-active-border: #ffffff;

  --rail: #000000;  --rail-2: #000000;  --rail-solid: #000000;
  --rail-text: #ffffff;  --rail-muted: #ffffff;  --rail-dim: #e4e4e4;
  --rail-border: #ffffff;  --rail-stroke: #ffffff;  --rail-stroke-2: #ffffff;
  --rail-hover: #1c1c1c;
  --rail-active: #ffffff;  --rail-active-text: #000000;
  --rail-active-icon: #000000;  --rail-active-border: #ffffff;
  --rail-focus: #ffffff;
  --rail-av: linear-gradient(135deg, #ffffff, #ffffff);  --rail-av-fg: #000000;

  --role-self-bg: #000000;      --role-self-fg: #ffffff;
  --role-family-bg: #000000;    --role-family-fg: #ffffff;
  --role-friend-bg: #000000;    --role-friend-fg: #ffffff;
  --role-partner-bg: #000000;   --role-partner-fg: #ffffff;
  --role-colleague-bg: #000000; --role-colleague-fg: #ffffff;
  --role-customer-bg: #000000;  --role-customer-fg: #ffffff;
  --role-coach-bg: #000000;     --role-coach-fg: #ffffff;
  --role-guide-bg: #000000;     --role-guide-fg: #ffffff;
  --role-other-bg: #000000;     --role-other-fg: #ffffff;
  --badge-shared-bg: #000000;   --badge-shared-fg: #ffffff;
  --chip-airport-bg: #000000;   --chip-airport-fg: #ffffff;
  --chip-flight-bg: #000000;    --chip-flight-fg: #ffffff;
}

/* ---- monochrome: the parts a token cannot reach --------------------------
   Two categories, and they need opposite treatments.

   (a) Inline colour that is DATA. Expense rows are tinted from
       `expense_categories`, written straight into a `style` attribute, so no
       token touches them. They cannot be greyscaled either: greyscale
       preserves lightness, and a user who picked a dark navy would keep a dark
       row with black text on it. The background is replaced outright, with the
       alternation kept so rows still group. Nothing is lost — since request 155
       the group header names the category in text.

   (b) Inline colour that is DECORATION. The dashboard pie is
       `aria-hidden="true"` with a text legend carrying every label and
       percentage, and each status dot is followed by its own word. Those can
       simply be desaturated. */
:root[data-mono] .exp-table tbody tr[style] {
  background: #ffffff !important;
  --cat: #ffffff !important;
  --cat-fg: #000000 !important;
}
:root[data-mono] .exp-table tbody tr[style]:nth-child(even) {
  background: #ededed !important;
  --cat: #ededed !important;
}
:root[data-mono][data-theme="dark"] .exp-table tbody tr[style] {
  background: #000000 !important;
  --cat: #000000 !important;
  --cat-fg: #ffffff !important;
}
:root[data-mono][data-theme="dark"] .exp-table tbody tr[style]:nth-child(even) {
  background: #191919 !important;
  --cat: #191919 !important;
}

:root[data-mono] .dash-pie__chart,
:root[data-mono] .dash-pie__swatch,
:root[data-mono] .dash-total__dot {
  filter: grayscale(1) contrast(1.4);
}
/* Desaturated, two swatches can land on the same grey — an outline keeps them
   readable as distinct marks. */
:root[data-mono] .dash-pie__swatch,
:root[data-mono] .dash-total__dot {
  border: 1px solid var(--text);
}

/* ---- monochrome: hue that carried meaning becomes shape ------------------
   success / warning / danger are all ink here, so a status that read as
   "the green one" now needs an edge. Each of these already carries a text
   label; the border restores the second, non-textual cue. */
:root[data-mono] .pillstat,
:root[data-mono] .chip-travels,
:root[data-mono] .chip,
:root[data-mono] .badge,
:root[data-mono] .role-badge,
:root[data-mono] .btn,
:root[data-mono] .trip-mark,
:root[data-mono] .mark {
  border: 1px solid currentColor;
}
/* The destructive action must not look identical to a neutral one. */
:root[data-mono] .btn--danger,
:root[data-mono] .btn-danger {
  border-width: 3px;
  font-weight: 700;
}

/* Remaining inline colour, each written per element by a view and therefore
   beyond the token layer. Custom properties accept !important, which is what
   lets a stylesheet beat a `style` attribute here.

     --badge      career funnel stages and status badges (career/dashboard.php,
                  listings/_status_badge.php, travel/dashboard.php)
     .pay-status  the paid/due glyphs, coloured from app/config/travel.php
     .plan-day    the day cell, tinted with that day's category colour */
:root[data-mono] [style*="--badge"] { --badge: var(--text) !important; }
:root[data-mono] .pay-status { color: var(--text) !important; }
:root[data-mono] .plan-day[style] {
  background: var(--sunken) !important;
  --cat: var(--sunken) !important;
}
/* The status dots sit beside their own words, so one ink colour loses nothing.
   (The pie keeps `filter: grayscale()` instead: its slices must stay correlated
   with the legend swatches, and a single flat colour would erase that.) */
:root[data-mono] .dash-total__dot { background: var(--text) !important; }
