/*
  Design tokens.

  Two independent axes, so a palette choice never forces a brightness:
    [data-mode="light"|"dark"]  -> surfaces, text, borders
    [data-theme="…"]            -> accent hue, set for BOTH modes

  Every colour used anywhere in the app resolves through a token here. No component
  hardcodes a hex value, and nothing about presentation is ever written into task or
  workspace data — a task does not know what colour it is.

  The mode attribute is written by app/js/ui/theme.js. It is always present on <html>
  (index.html ships with a default) so there is no unstyled first paint.
*/

:root {
  /* spacing — a 4px rhythm */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-8: 3rem;

  --radius-sm: 4px;
  --radius: 8px;
  --radius-lg: 14px;
  --radius-pill: 999px;

  /* System font stack: no webfont request, no layout shift, and font-src stays 'self'. */
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  /* RF-20260827-10 — the third stack, added when the Font control shipped. A document stores the
     NAME `serif`, never a family list, so the stack can be retuned here without rewriting a single
     stored document. Same rule as the other two: system faces only, so no webfont is requested and
     `font-src 'self'` is untouched. */
  --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;

  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.5rem;
  --text-2xl: 2rem;

  --weight-normal: 400;
  --weight-medium: 500;
  --weight-bold: 650;

  --leading-tight: 1.25;
  --leading-normal: 1.55;

  --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.08);
  --shadow: 0 4px 16px rgb(0 0 0 / 0.12);

  --transition: 140ms ease;

  /* THE DECLARED MINIMUM HEIGHTS. `min-height` on the elements themselves, never their actual
     rendered height — see --chrome-header below for why that distinction turned out to matter. */
  --header-height: 3.5rem;
  /* THE VIEW NAVIGATION'S HEIGHT, DECLARED so something below it can stick to it.
     `.app__nav` is sticky at --chrome-header, and Settings' own tab bar has to sit under
     both — which is arithmetic it cannot do without a number. Enforced with min-height on
     the nav itself, so the declaration and the rendered height cannot drift apart. */
  --nav-height: 3.25rem;

  /* RF-20260826-07 — THE CHROME'S *MEASURED* HEIGHT. THE ONLY THING ANY OFFSET MAY USE.
     `app/js/ui/shell.js` overwrites these two on the root element from a ResizeObserver on the
     real header and nav. These declarations are the fallback used before the first measurement
     and anywhere the observer cannot run.

     WHY THIS EXISTS. The comment above says min-height keeps declaration and rendered height
     from drifting. That is TRUE OF THE NAV, whose content is shorter than its minimum, and
     FALSE OF THE HEADER, whose content is taller: `.app__header` is padded and `flex-wrap`,
     so it renders 61px at desktop widths against a declared 56px, and wraps to 116px when the
     workspace picker and account control stop fitting on one line.

     Five offsets were computed from the declared number and were therefore wrong by 5px at
     desktop and by 60px once the header wrapped: the view nav's own sticky top, the left
     rail's fixed top, the Settings tab bar, and two scroll-margins. Measured in Edge: nav
     61-113 against a rail pinned at 108.

     A CONSTANT CANNOT DESCRIBE A BOX THAT WRAPS. So the offsets read the measurement and the
     minimums stay minimums — which is also why these are separate properties rather than an
     override of --header-height: assigning a measured height back onto the min-height that
     produced it is a ratchet, and the header could never shrink again. */
  --chrome-header: var(--header-height);
  --chrome-nav: var(--nav-height);

  /* THE CONTROL LANGUAGE — one set, read by every control in the task form.
     The form modernized field by field between beta.43 and beta.55, and the fields that were
     not converted kept the old language: a heavier border, a bigger radius, a larger type size
     and more padding than the palettes beside them. Assignees, Urgent, Repeat, Tags and
     Progress sat next to Status and Due looking like they came from a different form.
     WHAT IS SHARED IS THE LANGUAGE, NOT THE COMPONENT. A people picker, a recurrence rule, a
     tag box and a percentage are four different things and each keeps its own markup and its
     own behaviour; they simply agree on how tall they are, what they sit on, how heavy their
     border is and what they do on hover, on focus and when disabled.
     NO COLOUR HERE, deliberately. Colour comes from --surface / --border / --accent, which
     every palette already answers for, so this set is declared ONCE and never per theme. */
  --control-height: 2.25rem;
  --control-padding-y: var(--space-2);
  --control-padding-x: var(--space-3);
  --control-radius: var(--radius-sm);
  --control-font-size: var(--text-sm);
  --control-surface: var(--surface);
  --control-border: var(--border);
  --control-border-hover: var(--border-strong);
  --control-border-active: var(--accent);
  --control-disabled-opacity: 0.6;
  --content-max: 80rem;
  /* RF-20260827-21 — the CAP a `wide` route's content may grow to. Not a percentage alone:
     `min(90vw, --content-wide-max)` in `.app__main--wide` (layout.css) is what keeps a truly
     enormous monitor from turning Kanban's columns into a single unreadable sprawl. */
  --content-wide-max: 110rem;

  /* Semantic colours that stay constant across themes. */
  --color-danger: #ef4444;
  /* IMPORTANCE, as its own colour. Not the danger colour: a task being important is not a
     task being wrong, and reusing the error colour would make a well-run board look alarming. */
  --important: #b45309;
  --important-ink: #92400e;
  --color-warning: #f59e0b;
  --color-success: #10b981;
  --color-info: #3b82f6;

  /* RF-20260827-18 — CHART SERIES COLOURS.
     Eight hues for distinguishing slices of one chart from each other, and nothing else. They
     are deliberately NOT the category palette in `app/js/core/palette.js`: that library exists
     so a PERSON can choose what colour a category is, it is user data, and any of its
     seventeen families may be in force for any reader. A chart's series colours are the
     opposite kind of thing — nobody chooses them, they must stay distinguishable from each
     other in both modes, and they must not change when somebody switches category palette.
     Two different concerns, so two different sets rather than one set doing both jobs badly.
     DECLARED ONCE AND NEVER PER THEME, like the semantic colours above: they sit on a surface
     rather than carrying text, so they do not need a per-mode contrast pair. Order is fixed —
     a chart whose slice colours moved between reads could not be compared to yesterday's. */
  --chart-1: #3b82f6;
  --chart-2: #10b981;
  --chart-3: #f59e0b;
  --chart-4: #8b5cf6;
  --chart-5: #ec4899;
  --chart-6: #14b8a6;
  --chart-7: #f97316;
  --chart-8: #64748b;
}

/* ---------- mode: light ---------- */

:root[data-mode="light"] {
  --bg: #f6f7f9;
  --surface: #ffffff;
  --surface-raised: #ffffff;
  --surface-sunken: #eef0f4;

  --text: #11161d;
  --text-muted: #59616e;
  --text-subtle: #7d8794;
  --text-inverse: #ffffff;

  --border: #dfe3ea;
  --border-strong: #c3cad5;

  --overlay: rgb(17 22 29 / 0.45);

  /* Dark enough to hold 4.5:1 against a light card. */
  --important: #b45309;
  --important-ink: #92400e;
}

/* ---------- mode: dark ---------- */

:root[data-mode="dark"] {
  --bg: #0b0f16;
  --surface: #141a24;
  --surface-raised: #1b222e;
  --surface-sunken: #0d1219;

  --text: #e8edf5;
  --text-muted: #9aa6b8;
  --text-subtle: #74809a;
  --text-inverse: #0b0f16;

  /* LIGHTER IN DARK MODE. The light-mode amber is invisible as a 2px outline on a dark card,
     which would make importance a signal only half the users could see. */
  --important: #f59e0b;
  --important-ink: #fbbf24;

  --border: #232c3a;
  --border-strong: #33405a;

  --overlay: rgb(0 0 0 / 0.6);

  --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.4);
  --shadow: 0 6px 24px rgb(0 0 0 / 0.45);
}

/* ---------- themes: accent only, defined for both modes ---------- */

:root[data-theme="midnight"] {
  --accent: #3b82f6;
  --accent-hover: #2f6fdc;
  --accent-soft: rgb(59 130 246 / 0.14);
  --accent-contrast: #ffffff;
}

:root[data-theme="slate"] {
  --accent: #64748b;
  --accent-hover: #52607a;
  --accent-soft: rgb(100 116 139 / 0.16);
  --accent-contrast: #ffffff;
}

:root[data-theme="forest"] {
  --accent: #10b981;
  --accent-hover: #0d9d6d;
  --accent-soft: rgb(16 185 129 / 0.15);
  --accent-contrast: #04231a;
}

:root[data-theme="solar"] {
  --accent: #f59e0b;
  --accent-hover: #d98708;
  --accent-soft: rgb(245 158 11 / 0.16);
  --accent-contrast: #241802;
}

/*
  Solar and Forest accents are light; in light mode they need a darker variant to keep
  text on accent-coloured surfaces above the 4.5:1 contrast threshold.
*/
:root[data-mode="light"][data-theme="forest"] {
  --accent: #0a8a60;
  --accent-hover: #077352;
  --accent-contrast: #ffffff;
}

:root[data-mode="light"][data-theme="solar"] {
  --accent: #b45309;
  --accent-hover: #92400e;
  --accent-contrast: #ffffff;
}

/* ---------- RF-20260824-19: seventeen more, same mechanism ----------
   ACCENT ONLY, THROUGH THE CANONICAL TOKENS. Not one per-component override exists here, which
   is what the requirement asks for and is also why seventeen palettes cost four declarations
   each instead of a stylesheet each: every surface, border, focus ring, link, chart, board
   column, chat bubble, dialog and administration screen already derives from these four.

   EVERY RATIO BELOW IS COMPUTED, NOT EYEBALLED. `app/test/palettes.test.js` parses this file
   and calculates WCAG contrast for two pairs per palette: `--accent-contrast` on `--accent`
   (text sitting ON the colour) and `--accent` on the page (the colour used AS text — links,
   counts, active states). Both must clear 4.5:1, and the test fails the build otherwise.

   THE LIGHT-MODE VARIANTS EXIST FOR THE SECOND PAIR. A light accent is perfectly readable as a
   fill in dark mode and fails badly as a link on near-white, which is exactly why Forest and
   Solar already had one. Each darkened variant is the original hue walked down until it clears
   the threshold, so the palette stays recognisably itself rather than becoming a different
   colour in light mode. */

:root[data-theme="pink"] {
  --accent: #ec4899;
  --accent-hover: #c93d82;
  --accent-soft: rgb(236 72 153 / 0.15);
  --accent-contrast: #0b1220;
}

:root[data-theme="purple"] {
  --accent: #ab5cf7;
  --accent-hover: #914ed2;
  --accent-soft: rgb(171 92 247 / 0.15);
  --accent-contrast: #0b1220;
}

:root[data-theme="rose"] {
  --accent: #f43f5e;
  --accent-hover: #cf3650;
  --accent-soft: rgb(244 63 94 / 0.15);
  --accent-contrast: #0b1220;
}

:root[data-theme="magenta"] {
  --accent: #d946ef;
  --accent-hover: #b83ccb;
  --accent-soft: rgb(217 70 239 / 0.15);
  --accent-contrast: #0b1220;
}

:root[data-theme="lavender"] {
  --accent: #8b8cf7;
  --accent-hover: #7677d2;
  --accent-soft: rgb(139 140 247 / 0.15);
  --accent-contrast: #0b1220;
}

:root[data-theme="indigo"] {
  --accent: #7678f3;
  --accent-hover: #6466cf;
  --accent-soft: rgb(118 120 243 / 0.15);
  --accent-contrast: #0b1220;
}

:root[data-theme="cobalt"] {
  --accent: #5182ef;
  --accent-hover: #456ecb;
  --accent-soft: rgb(81 130 239 / 0.15);
  --accent-contrast: #0b1220;
}

:root[data-theme="azure"] {
  --accent: #0ea5e9;
  --accent-hover: #0c8cc6;
  --accent-soft: rgb(14 165 233 / 0.15);
  --accent-contrast: #0b1220;
}

:root[data-theme="cyan"] {
  --accent: #06b6d4;
  --accent-hover: #059bb4;
  --accent-soft: rgb(6 182 212 / 0.15);
  --accent-contrast: #0b1220;
}

:root[data-theme="teal"] {
  --accent: #14b8a6;
  --accent-hover: #119c8d;
  --accent-soft: rgb(20 184 166 / 0.15);
  --accent-contrast: #0b1220;
}

:root[data-theme="mint"] {
  --accent: #34d399;
  --accent-hover: #2cb382;
  --accent-soft: rgb(52 211 153 / 0.15);
  --accent-contrast: #0b1220;
}

:root[data-theme="lime"] {
  --accent: #84cc16;
  --accent-hover: #70ad13;
  --accent-soft: rgb(132 204 22 / 0.15);
  --accent-contrast: #0b1220;
}

:root[data-theme="amber"] {
  --accent: #eab308;
  --accent-hover: #c79807;
  --accent-soft: rgb(234 179 8 / 0.15);
  --accent-contrast: #0b1220;
}

:root[data-theme="orange"] {
  --accent: #f97316;
  --accent-hover: #d46213;
  --accent-soft: rgb(249 115 22 / 0.15);
  --accent-contrast: #0b1220;
}

:root[data-theme="coral"] {
  --accent: #fb7185;
  --accent-hover: #d56071;
  --accent-soft: rgb(251 113 133 / 0.15);
  --accent-contrast: #0b1220;
}

:root[data-theme="crimson"] {
  --accent: #e35151;
  --accent-hover: #c14545;
  --accent-soft: rgb(227 81 81 / 0.15);
  --accent-contrast: #0b1220;
}

:root[data-theme="sand"] {
  --accent: #c2a878;
  --accent-hover: #a58f66;
  --accent-soft: rgb(194 168 120 / 0.15);
  --accent-contrast: #0b1220;
}

:root[data-mode="light"][data-theme="pink"] {
  --accent: #c63c81;
  --accent-hover: #a8336e;
  --accent-contrast: #ffffff;
}

:root[data-mode="light"][data-theme="purple"] {
  --accent: #9b4ee3;
  --accent-hover: #8442c1;
  --accent-contrast: #ffffff;
}

:root[data-mode="light"][data-theme="rose"] {
  --accent: #d73753;
  --accent-hover: #b72f47;
  --accent-contrast: #ffffff;
}

:root[data-mode="light"][data-theme="magenta"] {
  --accent: #b63bc9;
  --accent-hover: #9b32ab;
  --accent-contrast: #ffffff;
}

:root[data-mode="light"][data-theme="lavender"] {
  --accent: #6a6abc;
  --accent-hover: #5a5aa0;
  --accent-contrast: #ffffff;
}

:root[data-mode="light"][data-theme="indigo"] {
  --accent: #5f62e7;
  --accent-hover: #5153c4;
  --accent-contrast: #ffffff;
}

:root[data-mode="light"][data-theme="cobalt"] {
  --accent: #2563eb;
  --accent-hover: #1f54c8;
  --accent-contrast: #ffffff;
}

:root[data-mode="light"][data-theme="azure"] {
  --accent: #0b7db1;
  --accent-hover: #096a96;
  --accent-contrast: #ffffff;
}

:root[data-mode="light"][data-theme="cyan"] {
  --accent: #047c90;
  --accent-hover: #03697a;
  --accent-contrast: #ffffff;
}

:root[data-mode="light"][data-theme="teal"] {
  --accent: #0e8478;
  --accent-hover: #0c7066;
  --accent-contrast: #ffffff;
}

:root[data-mode="light"][data-theme="mint"] {
  --accent: #1f7f5c;
  --accent-hover: #1a6c4e;
  --accent-contrast: #ffffff;
}

:root[data-mode="light"][data-theme="lime"] {
  --accent: #54830e;
  --accent-hover: #476f0c;
  --accent-contrast: #ffffff;
}

:root[data-mode="light"][data-theme="amber"] {
  --accent: #8c6b05;
  --accent-hover: #775b04;
  --accent-contrast: #ffffff;
}

:root[data-mode="light"][data-theme="orange"] {
  --accent: #bd5711;
  --accent-hover: #a14a0e;
  --accent-contrast: #ffffff;
}

:root[data-mode="light"][data-theme="coral"] {
  --accent: #b55160;
  --accent-hover: #9a4552;
  --accent-contrast: #ffffff;
}

:root[data-mode="light"][data-theme="crimson"] {
  --accent: #dc2626;
  --accent-hover: #bb2020;
  --accent-contrast: #ffffff;
}

:root[data-mode="light"][data-theme="sand"] {
  --accent: #847252;
  --accent-hover: #706146;
  --accent-contrast: #ffffff;
}
