/* ============================================================================
   THE PALETTE. ONE DECLARATION, EVERY PAGE.

   Until 25 August 2026 every page carried its own copy of this block, so each
   colour existed in up to seventeen places and a change meant editing thirteen
   files and writing a test to stop the copies drifting apart. Issue #210 moved
   the copies here so that a dark theme has ONE set of values to swap rather
   than seventeen.

   HOW IT REACHES A PAGE: a plain <link> in the head, ahead of that page's own
   <style>, so the page can still override a token after it (only two do, and
   both are named below). It is NOT a serve-time partial, because web/_nav.html
   and web/_jargon.html are injected by servePage() and three of the pages that
   need this palette never go through servePage: landing.html is served raw on
   purpose (src/routes.js), and so are issue-admin.html and admin.html. A link
   reaches all of them without changing how any of them is served.

   THE COST OF THAT CHOICE, STATED PLAINLY: every page used to be one file that
   could not fail to have its colours. Now a page whose palette request fails
   renders with no tokens at all, and text falls back to the initial values,
   which on the paper background is close to unreadable. It is same-origin and
   render-blocking, so this is remote rather than likely, but it is a new way
   for a page to be wrong and it did not exist before. Note also that send() in
   src/routes.js puts `cache-control: no-store` on EVERYTHING, this file
   included, so it is re-fetched on every page load rather than cached. That is
   one small same-origin request per navigation, and it is the price of the
   single declaration until somebody decides static assets may be cached.

   WHAT IS NOT HERE, AND MUST NOT BE MOVED HERE:
   - web/_nav.html keeps --gn-tall, --gn-short and --gn-height. The bar measures
     its own height and publishes it; a partial that measures itself owns those.
   - web/admin.html keeps its own twelve tokens. It is a SECOND DESIGN SYSTEM,
     not a drifted copy of this one: different ink, different accent, a system
     font stack, white cards on grey. Folding it in here would be a redesign of
     the back office. See docs/decisions.md, 25 August 2026.
   - web/_nav.html and web/_jargon.html spell their colours out as literals and
     say in their own headers that this is deliberate, so that a token change
     cannot break the masthead's skip link or the glossary popover. Nothing here
     reaches them and nothing here should. Their literals are hand copies of the
     values below; a change here is a change there in the same commit.

   THE TWO BARS A CHART COLOUR CLEARS, still: 3:1 as a fill against the ground
   and 4.5:1 for a word sitting on that fill. They are different questions with
   different answers, and tests/palette-dark.test.js computes both.
   ========================================================================== */
/* TEXT IS THE SIZE THE PAGE SAYS IT IS, ON A PHONE TOO (owner report, Rob, 1
   September 2026: "my phone seemed slightly zoomed into the page"). Every page
   here declares width=device-width and initial-scale=1, so nothing asks the
   browser to zoom; WebKit's own text auto-sizing does it anyway, inflating type
   inside wider blocks so it stays readable at a zoomed-out width, and a page
   that was never zoomed out comes out looking as though it had been. The guard
   is one declaration and it belongs here rather than on one page, because the
   pages all share this stylesheet and none of them wants inflated type.

   IT DOES NOT STOP A MEMBER ZOOMING. Pinch, double-tap and the aA menu are
   untouched: user-scalable is never set anywhere in this project, and it must
   stay that way. This only stops the browser resizing type nobody asked it
   to. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

/* ============================================================================
   ONE DARK THEME, THE SPECTRUM SYSTEM (owner decision, 4 September 2026).

   Light mode is withdrawn. Every token below carries the value the design
   brief on design/spectrum-prototype gives it, or the nearest one where the
   brief has no word for a token (the calendar levels, the washes, the series
   lines): the ground #14120d, raised tiles #1e1b14, the sunk surface #17150f
   and the deepest block #0e0d0b; cream #f6f2e6 for headings, #cfc7b4 for body,
   #a19887 muted and #8d8471 faint; rules #2e2a21, #3a352a and #4c463a; rust
   #d97258 with #e08a74 as its hover and #14120d as the ink that sits on it;
   #7fc29b positive and #e08a74 negative.

   THE TOKEN NAMES ARE THE OLD ONES ON PURPOSE. Seventeen pages read --paper,
   --ink, --rule and the rest, so the names stay and only the values move:
   nothing on any page had to be edited for its colours. Two names keep their
   meaning rather than their direction: --accent-dark is the accent's hover form
   (on paper it darkened, on ink it lightens or it would vanish) and
   --paper-sunk names depth, not lightness. Note that in this system a sunk
   surface sits BETWEEN the ground and a tile (the brief's chips inside a tile
   are #17150f on #1e1b14), so it is a shade lighter than the ground and darker
   than a tile; the test that orders the surfaces says so.

   THE CHART COLOURS each clear TWO bars: 3:1 as a fill against the ground and
   4.5:1 under a word set in what --accent-on carries, which is near-black. The
   one deliberate exception is --cat-other, held BELOW the word bar so the
   treemap refuses its inside label and the drawing's key names it instead
   (#229). Every ratio is checked by test with the same arithmetic the tracker
   draws by; none of them is a number somebody once measured and wrote down.

   THE SECOND BLOCK, :root[data-theme="dark"], SAYS THE SAME VALUES AGAIN.
   web/theme.js puts data-theme="dark" on the root element before first paint
   and never takes it off, and a page-local declaration at bare :root (the
   home page's --ink-soft, kept while that page is redrawn) would beat this
   file's :root by order; the attribute block outranks it by specificity. So
   the two blocks are identical, held identical by test, and nothing flips. */
:root {
  color-scheme: dark;

  /* Ground and tiles */
  --paper: #14120d; --paper-sunk: #17150f; --paper-raised: #1e1b14;
  /* The pressed state of a product row on the phone door (home.html). */
  --paper-pressed: #2e2a21;
  --ink-block: #0e0d0b; --ink: #f6f2e6; --ink-body: #cfc7b4; --ink-muted: #b3ac9f;
  --ink-soft: #a19887; --ink-faint: #8d8471; --label: #a19887; --label-faint: #8d8471;

  /* Rules. --sand-rule is the hairline ON the sand fill (the home page's edition
     hero, the Today band): a rule between two lines of near-black text on sand,
     never a word and never anything a member has to see to understand a page. */
  --rule: #3a352a; --rule-soft: #2e2a21; --rule-strong: #4c463a; --rule-yours: #2e2a21;
  --sand-rule: #857c6b;

  /* The rust, lifted to read on ink, and the one green that is not a category */
  --accent: #d97258; --accent-dark: #e08a74; --accent-on: #14120d; --positive: #7fc29b;
  /* The lighter rust, and it means one thing: the part of a debt payment that
     reaches the balance, beside the interest that does not. A fill at 3:1 or
     better on the ground and it never carries a word. */
  --accent-soft: #c06b52;
  /* Two washes behind a pill: the state is also written in the word, never in the
     colour alone (issue-admin.html). */
  --positive-wash: #18231c; --accent-wash: #3b221c;
  /* The markets strip on Today: a fund's whole cell takes the wash of its move, and a
     move of 2.5% or more either way takes the deeper one (owner decision, 9 September
     2026). The move is written in the cell as well, sign first, so the colour is never
     the only carrier; --ink and --ink-body clear 4.5:1 on all four. */
  --up-wash: #1d3327; --up-wash-strong: #2b5038; --down-wash: #3d211b; --down-wash-strong: #602c22;

  /* On the deepest block */
  --on-ink: #f6f2e6; --on-ink-body: #cfc7b4; --on-ink-faint: #8d8471;
  --on-ink-link: #ece6d6; --on-ink-hint: #9d9482;
  --accent-on-ink: #d4674f; --divider-on-ink: #3b362a; --composer-rule: #6c6553;

  /* The economic calendar's two levels, on the calendar and on Today. The word
     colours clear 4.5:1 on the tile and on their own soft rows; the rows are
     tints of the same hue. Neither is ever the only thing carrying the meaning:
     every row says the word as well. */
  --high: #dd635a; --high-soft: #301815; --high-line: #dd635a;
  --medium: #c3780d; --medium-soft: #271e0a; --medium-line: #e68a00;

  /* The earnings calendar's two kinds of date */
  --confirmed: #7fc29b; --expected: #c87718;

  /* THE ECONOMIC CALENDAR'S OWN ACCENT. The two calendars are drawn to one
     layout and told apart by one colour each: the earnings page takes the rust
     the whole site takes, and the economic page takes this plum. It fills one
     block per page, the day a member is standing in, and it carries --accent-on
     as the ink on it exactly as the rust does; --plum-dark is its hover form,
     lighter rather than darker for the reason --accent-dark is.
     /today borrows the same plum for the same group, as LIGHT rather than fill:
     the kicker, the times and the 3px rule down each event row, with high and
     medium still told apart by the word. That makes it a reading colour there,
     so it clears 4.5:1 on the ground, the sunk surface and a tile as well. */
  --plum: #b58ac2; --plum-dark: #c4a0cf;

  /* ECONOMIC DATA'S OWN ACCENT. The same idea as the plum above, for the other
     market page: /dashboards/macro is drawn in teal, and the page repoints
     --accent to it rather than spelling a hex of its own, so every rule already
     written as var(--accent) turns teal at once. It carries --accent-on as the
     ink on the one filled block it fills, and --teal-dark is its hover form,
     lighter rather than darker for the reason --accent-dark is. */
  --teal: #6cc3c9; --teal-dark: #93d6da;

  /* THE THREE DOORS. web/index.html is ONE shell behind three paths, and each path is a
     product the home page already draws in its own colour (--sp-green, --sp-sky,
     --sp-coral). The page sets --door from the path at boot and every piece of its chrome
     reads that: links, buttons, hovers, focus rings, the composer's send, the kickers and
     the rules that carry the page's identity.

     WHY THESE ARE THEIR OWN TOKENS AND NOT BORROWED ONES. --door-ask holds the same value
     as --positive, and that is the whole point of naming it separately: on a finance page
     --positive means a figure went UP, and a link is not a figure going up. The chat page
     used to paint its links, its send button and its focus ring in --positive, so one green
     said two things on one screen and the market pages read as though every link were a
     gain (docs/decisions.md, 4 September 2026). --positive and its negative twin are for
     DIRECTION ON A FIGURE and nothing else now; chrome takes the door. --door-earnings
     likewise shares the rust's hover value, and means the door, not a hover.

     Each is a reading colour: it clears 4.5:1 on the ground, on the sunk surface and on a
     tile, and it carries --accent-on as the ink on the one block it fills, the send. */
  --door-ask: #7fc29b; --door-company: #6fb3d2; --door-earnings: #e08a74;

  /* The macro chart's comparison lines. The first line a member draws is the accent;
     these are the second, third and fourth, and they are only ever lines and swatches. */
  --series-b: #3b7297; --series-c: #a19887; --series-d: #82608f;
  /* The eight profiles' shades for the company page's one-box tile (5 September 2026): the two loss-making shapes share one */
  --shade-fortress: #2e7d6b; --shade-compounder: #6a8a63; --shade-steady: #668594; --shade-turnaround: #9f7b2b;
  --shade-speculative: #82608f; --shade-distressed: #d97258; --shade-loss: #8a8160;

  /* One colour per asset category, the same on every chart on the tracker, and
     the five the brief names for Net Worth (cash, shares, pensions, property,
     owed) are these. The rust family stays reserved for debt, green for net
     worth and the liquid pot; each of these reads against the ground at 3:1 or
     better. --cat-other is a FILL and never sits under a word; the tracker's
     treemap refuses to label it for exactly that reason (docs/decisions.md,
     25 August 2026). */
  --steel: #4987b1;
  --cat-cash: #558d69; --cat-equities: #4987b1; --cat-bonds: #7f7da9; --cat-pension: #9f7b2b;
  --cat-property: #a47660; --cat-crypto: #af6a9c; --cat-commodities: #b0751a;
  --cat-collectibles: #4c8a8f; --cat-insurance: #668594; --cat-vc: #368f73;
  --cat-vehicles: #8a8160; --cat-other: #807768;
  /* Two more from the WorthLens redesign handoff (#341): car finance's rust
     between --accent and --accent-soft, and the shopping tan. */
  --cat-car: #c9705a; --cat-shopping: #c99a4a;

  /* Type: Bricolage Grotesque for display (the site loads 700 and 800, so a
     heading that asks for less gets 700), Instrument Sans for reading, IBM Plex
     Mono for labels and figures. The families arrive through the head every
     served page gets (withIdentity in src/routes.js). --serif keeps its name
     for the same reason --paper does: seventeen pages say it. */
  --serif: 'Bricolage Grotesque', 'Instrument Sans', Helvetica, Arial, sans-serif;
  --body: 'Instrument Sans', Helvetica, Arial, sans-serif;
  --mono: 'IBM Plex Mono', Menlo, monospace;

  /* The legibility floor: nothing a member reads goes below these */
  --fs-label: 12px;    /* small caps section labels: SOURCES, THIS SESSION */
  --fs-micro: 13px;    /* mono lines that are read as sentences */
  --fs-read: 16.5px;   /* body text outside the report itself */

  /* The widest a page lays out */
  --page: 1360px;

  /* THE MEASURE. The answer column is as wide as the screen allows; a sentence in it is not.
     68 characters is the number the rest of the site already reads at (issue #98 held
     `.note-line`, `.explain` and `.honest` to it), and it is stated in `ch` rather than pixels
     so it means 68 characters in whatever font and size the block using it is set in. */
  --measure: 68ch;
}

/* ============================================================================
   THE SAME COLOURS AGAIN, UNDER THE ATTRIBUTE. Colours only: type, the page
   width and the measure are declared once above. This block exists for
   specificity, not for a second look (see the header above): web/theme.js
   always sets the attribute, so this block always applies, and it must say
   exactly what :root says or the two would be a light-versus-dark difference
   nobody decided. tests/palette-dark.test.js holds them identical. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --paper: #14120d; --paper-sunk: #17150f; --paper-raised: #1e1b14;
  --paper-pressed: #2e2a21;
  --ink-block: #0e0d0b; --ink: #f6f2e6; --ink-body: #cfc7b4; --ink-muted: #b3ac9f;
  --ink-soft: #a19887; --ink-faint: #8d8471; --label: #a19887; --label-faint: #8d8471;

  --rule: #3a352a; --rule-soft: #2e2a21; --rule-strong: #4c463a; --rule-yours: #2e2a21;
  --sand-rule: #857c6b;

  --accent: #d97258; --accent-dark: #e08a74; --accent-on: #14120d; --positive: #7fc29b;
  --accent-soft: #c06b52;
  --positive-wash: #18231c; --accent-wash: #3b221c;
  /* The markets strip on Today: a fund's whole cell takes the wash of its move, and a
     move of 2.5% or more either way takes the deeper one (owner decision, 9 September
     2026). The move is written in the cell as well, sign first, so the colour is never
     the only carrier; --ink and --ink-body clear 4.5:1 on all four. */
  --up-wash: #1d3327; --up-wash-strong: #2b5038; --down-wash: #3d211b; --down-wash-strong: #602c22;

  --on-ink: #f6f2e6; --on-ink-body: #cfc7b4; --on-ink-faint: #8d8471;
  --on-ink-link: #ece6d6; --on-ink-hint: #9d9482;
  --accent-on-ink: #d4674f; --divider-on-ink: #3b362a; --composer-rule: #6c6553;

  --high: #dd635a; --high-soft: #301815; --high-line: #dd635a;
  --medium: #c3780d; --medium-soft: #271e0a; --medium-line: #e68a00;

  --confirmed: #7fc29b; --expected: #c87718;

  --plum: #b58ac2; --plum-dark: #c4a0cf;

  --teal: #6cc3c9; --teal-dark: #93d6da;

  --door-ask: #7fc29b; --door-company: #6fb3d2; --door-earnings: #e08a74;

  --series-b: #3b7297; --series-c: #a19887; --series-d: #82608f;
  /* The eight profiles' shades for the company page's one-box tile (5 September 2026): the two loss-making shapes share one */
  --shade-fortress: #2e7d6b; --shade-compounder: #6a8a63; --shade-steady: #668594; --shade-turnaround: #9f7b2b;
  --shade-speculative: #82608f; --shade-distressed: #d97258; --shade-loss: #8a8160;

  --steel: #4987b1;
  --cat-cash: #558d69; --cat-equities: #4987b1; --cat-bonds: #7f7da9; --cat-pension: #9f7b2b;
  --cat-property: #a47660; --cat-crypto: #af6a9c; --cat-commodities: #b0751a;
  --cat-collectibles: #4c8a8f; --cat-insurance: #668594; --cat-vc: #368f73;
  --cat-vehicles: #8a8160; --cat-other: #807768;
  --cat-car: #c9705a; --cat-shopping: #c99a4a;
}
