/* Zantillion Hub — the design tokens.
   Mirrors the prototype palette: warm neutral greys, IBM Plex, slate-blue accent.

   Split out of app.css in Phase 28 so a second file can override the palette
   without app.css holding two :root blocks — the contrast guards and the
   island's token tests all parse :root by regex, and one per file is what
   keeps that honest. One cut at the end of the block, nothing rearranged:
   every comment below is where it has always been, and app.css keeps every
   line it had from `*{box-sizing:border-box}` onward.

   Linked before app.css on every page, including the auth sheet, which has
   its own inline <style> and no app.css at all. */
:root{
  --bg:#edece8; --panel:#faf9f6; --card:#fff; --border:#e3e2dd; --line:#f0eee7;
  --text:#1b1c1e; --muted:#8a8b8f; --faint:#a4a5a0;
  --accent:#3a6598; --accent-tint:#3a659814; --accent-ring:#3a659855; --next-bg:#3a65980e;
  --done:#3f7049; --amber:#b9883e; --error:#a34430;
  /* --amber is the warning *mark* — a dot, a border, a bar. As body
     text on the pale warning band it measures 2.93:1, under the 4.5
     AA floor, so text uses this instead (5.07:1). Measured, not
     eyeballed. */
  --amber-ink:#8a6224;
  /* Same story for --muted, which is the *mark* — a rule, a chart axis, an
     icon. As body text it measures 2.88:1 on --bg, 3.23 on --panel and 3.40
     on --card, so secondary text uses this instead (4.99 / 5.60 / 5.90).
     Darker than the arithmetic minimum on purpose: --amber-ink can sit at
     4.61 because its band (#fbf6ee) is lighter than --bg, while this one is
     read directly on --bg in half its sites. Measured, not eyeballed. */
  --muted-ink:#63646a;
  /* And the same one token over (Phase 25 B4): --faint is the *quietest
     mark* — 2.1:1 as text. Meta lines, counts and help text read in this
     instead: 4.62 / 5.19 / 5.46 on --bg/--panel/--card, deliberately a
     step lighter than --muted-ink (4.99) so the faint-under-muted
     hierarchy survives the fix. Measured, not eyeballed. */
  --faint-ink:#696a64;
  /* Five sites painted text in a hardcoded #9a9b9f — 2.35/2.64/2.78, a
     literal twin of the mark greys — and both ink sweeps walked past them
     because the guards look for `var(--muted)`, not for a hex (2026-09-11:
     a year-button sub-heading, a donut label, the stat caption, every form
     label, and the "coming soon" card name). They read in the ink now.
     This changes how the hub looks on desktop, deliberately, the same call
     U9 was. */
  /* --- folded from literals (Phase 28) ------------------------------------
     Every value below is the hex that was already in the stylesheets, moved
     here unchanged — the rendering is identical and was proved so, not
     assumed. They are named for the *role* they play, because that is what a
     second palette has to be able to answer: a dark theme needs to know
     whether a colour was a surface or the ink on top of one.

     --on-accent is the one that earns its name twice over. As a background,
     #fff is the raised surface (--card); as a `color` it is text sitting on a
     filled accent or a filled --done. The two are the same hex today and must
     not be in a dark theme, where white text on the lifted accent measures
     1.9:1. Getting this wrong is invisible in light. */
  --on-accent:#fff; --on-done:#fff;
  /* And a third #fff that is neither: the initials on an avatar, whose
     background is `User.avatar_color` — six saturated hexes from the model,
     not from this palette, and therefore the same in both themes. It rode on
     --on-accent until session 5, where that became dark ink and would have put
     #1a1917 on a mid-blue circle (3.6:1, worse than the 6.0 it replaced). */
  --on-avatar:#fff;
  --accent-press:#376191;
  /* The hub's input hairline (8 sites). The auth sheet had its own #d8d6ce
     for the same job — one step darker, one fork — and takes this now. */
  --border-input:#e2e0d8;
  --text-soft:#44454a;
  --error-band:#fbeae5;
  --amber-band:#fbf6ee; --amber-band-border:#e9dec8;
  /* The island's one shadow (the dashboard's drag preview). Here rather
     than in the CSS module because a theme has to be able to reach it, and
     a module that defines no colour of its own is the rule a test keeps. */
  --shadow-island:0 8px 24px rgba(27,28,30,.12);
  /* --- folded from app.css's own literals (Phase 28, ledger 4.2) -----------
     The other half of the job session 4 started. app.css held 129 colour
     literals across 54 distinct values, and a literal is a colour **no theme
     can reach**: it renders light whatever theme is active, and no test, no
     type checker and no review of a dark palette can see it — you find it by
     opening the page in dark mode and noticing a white box.

     Named for the ROLE, never for the value, which is the rule the block
     above already states and the reason this was not a find-and-replace:
     `#fff` alone appears 26 times wearing four different hats — a card, the
     face of a text field, the face of a button, and the ink on a filled
     accent or a filled --done. Several pairs below therefore share a value
     today and must not in a dark theme, where a field is darker than the card
     it sits on and white on the lifted accent measures 1.9:1.

     What is deliberately NOT here: a smaller palette. The warm greys form an
     almost continuous ramp — nearly every value sits within 6/255 of another
     — so collapsing them is a design judgement about how many steps the
     system wants, not a refactor. That goes to the design pass with 4.3, for
     the same reason type and spacing did. Eight sites where one *role* had
     two values were unified here and every one was measured; they are listed
     in the phase-28 ledger. */

  /* Surfaces. --card is above; these are the ones under and beside it. */
  --surface-quiet:#fcfbf8; --surface-quiet-hover:#fbfaf5;
  --field-bg:#fff; --control-bg:#fff; --control-bg-hover:#f7f6f1;
  --chips-bg:#f0efe9; --track:#eceadf;
  /* Hover reads differently by what it sits on: the rail and the sidebar are
     --panel, a menu and the year list are --card, so the fill has to be a
     step darker on the lighter of the two to land the same. Two tokens, not
     drift. */
  --hover-panel:#f2f1ed; --hover-card:#f4f3ee; --press-fill:#eceae4;
  /* Same value as --hover-card today, and a different thing: a disabled
     control is not a hovered one, and in dark one lifts while the other does
     not. */
  --disabled-fill:#f4f3ee;

  /* Hairlines. --border (chrome) and --border-input (fields) are above. */
  --border-card:#e9e7e0; --border-quiet:#ece9e2; --border-chip:#e6e4dc;
  --border-control:#e0ded6; --border-control-hover:#d6d4ca;
  /* Two dashed hairlines, and they stay two. They look like the same job and
     are 10/255 apart, which is well past the ~6 where these warm greys are
     indistinguishable — the harness caught the unification as a real change
     and it was reverted. A drop target invites a file onto it; an empty state
     and a not-yet-built card are quietly absent. */
  --border-hover:#e8e6df; --border-dashed:#ddd9cc; --border-placeholder:#e0ded6;
  --border-disabled:#e2e0d8; --border-locked:#ece6da;
  --barline:#e6e3da; --scrollbar-thumb:#d4d3ce;

  /* The quiet inks. Ledger 4.12 recorded these as mark greys being used as
     text — the exact defect the two ink sweeps fixed for --muted, --faint and
     the hardcoded #9a9b9f, invisible to both because the guards look for
     `var(--muted)` and cannot see a hex. Andreas decided it in session 5, and
     the line he drew is the one WCAG draws:

       * --label-ink paints REAL TEXT (the uppercase micro-labels on five
         surfaces, a table header, a data-label on the phone), so it clears
         4.5:1 — 4.60 / 5.17 / 5.44, up from 2.00 / 2.24 / 2.36. It lands on
         the same value as --faint-ink rather than a hair beside it: the
         quietest *legible* tier is one step, and two tokens sharing a value is
         fine where two values sharing a role was the bug (4.7). It keeps its
         own name because a theme and the design pass must be able to move
         micro-labels without moving meta text — and in dark they differ.
       * --chip-ink is real text too — the unselected filter chips, read on
         --chips-bg rather than on a surface, where it measured 3.67. It is
         4.68 there now and keeps its cool cast, which is what distinguishes a
         chip from the warm greys around it.
       * --caret-ink and the four below it are NOT text. A caret is a glyph, a
         strikethrough is a line, and a disabled control is exempt from the
         contrast floor precisely because it must read as unavailable. They
         keep their values, measured and recorded rather than left unexamined:
         1.84 / 2.05 / 1.61 / 1.72 / 1.97 on --bg. */
  --label-ink:#696a64; --caret-ink:#b0b0ab; --chip-ink:#696a70;
  --done-text-ink:#a6a7a3; --done-strike:#cfcfc8;
  --disabled-ink:#cdccc5; --disabled-text-ink:#b6b6b0;
  --tag-blocked-ink:#b0a99c; --tag-blocked-bg:#f3f1ea;

  /* Semantic marks and the bands they sit in. */
  --done-tint:#3f70491a; --done-band:#3f704912; --done-band-border:#3f704933;
  --accent-tint-hover:#3a659824;
  /* Three more inks measured on the band they actually sit on rather than on a
     surface, which is where they had been getting away with it (session 5,
     the same decision as --label-ink above):
       --danger-ink        3.94 -> 4.88 on its own tint, 4.33 -> 5.41 on --card
                           ("Log out", the .tag.off, the danger button);
       --amber-badge-ink   3.01 -> 4.63 on --amber-badge-tint (ARCHIVED, SOON);
       --warn-band-ink     4.20 -> 4.59 on --warn-band (the read-only banner).
     10px uppercase at 600 is harder to read than body text, not easier, which
     is why a badge scraping the floor is the worst place to scrape it. */
  --danger-ink:#945c46; --danger-tint:#945c4614; --danger-border:#e8ddd7;
  --danger-bg-hover:#fdf6f2; --danger-border-hover:#dcc9be;
  --error-band-border:#eed6cd;
  --amber-badge-ink:#806820; --amber-badge-tint:#f4eede;
  --warn-band:#f4eee2; --warn-band-ink:#836737;
  /* Left where it was, and measured so the next reader does not have to: 2.41
     on --surface-quiet. It is a glyph rather than text — the mark against a
     document the folder rules expected and did not find, beside a row that
     also says so in --muted-ink — but it is under the 3:1 floor a meaningful
     graphical object carries, so it is recorded in the phase-28 ledger for the
     design pass rather than quietly left. Its dark twin, being a new value, is
     chosen above that floor. */
  --tick-missing:#c0a05a;

  /* --- the checklist's phase scale (session 5) -----------------------------
     Six categorical hues for the six kinds of phase, and the last colours in
     the hub that no theme could reach: they were a literal map in app.js,
     written into `style=` attributes, where every guard in the suite is blind
     to them — a stylesheet scan cannot see a JS string.

     Three tokens per hue, not one, because the same colour was doing two jobs
     at two different sizes: an 8px dot and a 5px bar (marks), and a 10px
     uppercase badge *label* (text). Five of the six failed AA as that label —
     `kommentare` at 2.08 on its own band — while being perfectly good dots.
     That is the mark/ink split this palette already makes for --amber and
     --muted, and making it here leaves every dot and bar exactly the colour it
     was: only the badge text moved.

     The tint is the mark at 10%, which is what app.js used to build by
     concatenating "1a" onto the hex — a trick that needs a literal and is the
     reason these could not simply become var()s. */
  --phase-doku:#7a8aa8;       --phase-doku-ink:#5b6b8b;       --phase-doku-tint:#7a8aa81a;
  --phase-daten:#3f6ea5;      --phase-daten-ink:#3e6ca2;      --phase-daten-tint:#3f6ea51a;
  --phase-kontrolle:#9a7fa8;  --phase-kontrolle-ink:#7f618e;  --phase-kontrolle-tint:#9a7fa81a;
  --phase-reporting:#a88a6a;  --phase-reporting-ink:#82684c;  --phase-reporting-tint:#a88a6a1a;
  --phase-kommentare:#aeaea6; --phase-kommentare-ink:#6e6e64; --phase-kommentare-tint:#aeaea61a;
  --phase-other:#9a9b96;      --phase-other-ink:#6c6d68;      --phase-other-tint:#9a9b961a;

  /* --- the dashboard's categorical scale (session 5) ------------------------
     The island's six-hue series palette, validated in 2026-08-19 against the
     *light* card with the dataviz six-checks script. Three of the six measure
     2.5-2.9 on a dark card, which is under the 3:1 a meaningful graphical
     object carries, so the dark theme lifts them — and they have to be tokens
     for it to be able to. `--cat-other` is not a seventh hue: it is what
     nobody has named yet, and it wears the remainder grey.

     Hues are assigned in FIXED order and never cycled, so a group keeps its
     colour when a value changes; the order below is the order palette.ts
     reads them in. */
  --cat-1:#3a6598; --cat-2:#c99a4e; --cat-3:#1f7a44;
  --cat-4:#8a5a9e; --cat-5:#a34430; --cat-6:#0b96a6;
  --cat-other:#9a9b9e;

  /* Overlays and shadows. A shadow is one token, offsets included, not just
     its colour: a dark surface needs a deeper and wider shadow to read as
     lifted at all, so a theme has to be able to change the geometry with the
     alpha. --shadow-island is above, defined for the same reason. */
  --scrim:rgba(28,29,30,.34); --overlay:rgba(28,29,30,.32);
  --shadow-menu:0 10px 30px rgba(0,0,0,.13);
  --shadow-modal:0 20px 60px rgba(0,0,0,.25);
  --shadow-drawer:0 14px 40px rgba(0,0,0,.18);
  --shadow-movebar:0 -6px 24px rgba(0,0,0,.10);
  --shadow-card-hover:0 6px 20px rgba(0,0,0,.06);
  /* The sign-in card's lift. It was the one rgba() the no-literal guard still
     excused in base_auth.html; a theme has to be able to reach it like any
     other, so the exception is gone with it (session 5). */
  --shadow-auth:0 10px 40px rgba(0,0,0,.07);
  --shadow-chip:0 1px 2px rgba(0,0,0,.06);
  --shadow-pill:0 2px 8px rgba(0,0,0,.08);
  /* The holdings table's pinned column once rows scroll under it (Phase 40):
     a shadow cast to the right only, so it reads as an edge, not a lift. */
  --shadow-pinned-edge:6px 0 8px -6px rgba(27,28,30,.22);
  /* The same box's *right* edge (Phase 40 E5): the overlay scrollbar takes
     no layout space, so nothing else says a column sits past it. A fade, not
     a shadow — caught in review: a 1px strip carrying a box-shadow with a
     negative spread draws nothing at all (the spread eats the shape before
     it reaches the strip), and the sticky header cells would have painted
     over it besides. The strip this now paints on has real width, so a
     gradient works where a shadow's shorthand could not. */
  --fade-more-right:rgba(27,28,30,.16);
  /* The holdings map and the dashboard's monthly-returns heatmap now speak
     one colour language (Phase 40 E2): green for a gain, red for a loss,
     through the neutral --line. Not --done/--error: as a pair those fail
     colour-vision separation (dataviz validator, ΔE 4.9), and status colours
     are for state besides. This pair — Andreas's choice, teal-leaning rather
     than a pure green so it still reads next to --done — passes at ΔE 9.6
     deutan light (8.9 dark). Cost figures are not a result and never share
     this pair: --map-scale is the one-way blue the map used to diverge
     through for everything unsigned, kept for exactly that (`isPerformance`
     in treemap-data.ts is what tells the two apart). --map-band and
     --map-account are the grouped map's depot-band and account-tile
     fills (Phase 40 E3; R4 later made --map-band the band's whole body, not
     just a header) — neutrals, not part of the gain/loss language, but
     tokens for the same reason every map colour is. Validated in both
     themes. */
  --map-gain:#138a72; --map-loss:#c0392b; --map-scale:#2f63b8;
  --map-band:#e7e4dc; --map-account:#dedad0;

  /* Not a colour, and still a theme's business. The press rules at the end of
     app.css exist because iOS fires :hover *after* the tap, so a tap otherwise
     looked like nothing had happened; dimming by 4% is invisible on a dark
     surface, so the dark palette brightens instead of dimming. A token rather
     than a literal for exactly the reason every colour became one. */
  --press-dim:brightness(.96);

  --mono:'IBM Plex Mono',monospace;
  /* This is a light design and stays one in a dark-mode OS. Without this,
     Windows dark mode paints native <select> text light — white on the #fff
     backgrounds below. */
  color-scheme:light;
}
