/* ============================================================
   tokens.css — the tokens of style A, shared by the app and the front door

   Round C13a (docs/ontwerp/11-voordeur.md §7) moved these three blocks here
   from public/style.css without changing one value: `:root` (the type,
   space, corner, control and motion ladders and the light colours) and the
   two theme blocks, `:root[data-thema="donker"]` and `:root[data-thema="sepia"]`.
   The values are REDESIGN.md §17.2, set in round B1.

   Why a file of its own: /welkom carried its own copy of the old tokens and
   drifted out of step with the app — dark whenever the system was dark,
   whatever the visitor chose in the app, and in the old palette. Now both
   pages load this one file, as a <link> BEFORE their own stylesheet (not an
   @import: that is a second request that can only start once the first one
   has arrived).

   ⚠️ LOAD ORDER IS PART OF THE CONTRACT. style.css comes after this file, so
   its own `:root` rules further down (the phone header height, the three
   BD media blocks for reduced motion, transparency and more contrast) still
   win on order, exactly as they did when everything stood in one file.

   checks/style.mjs reads this file and style.css as one source, so every
   rule it holds (the ladders, the colour tokens, the comments) still applies
   here. /tokens.css is in SCHIL in public/sw.js: the app opens offline only
   if every stylesheet it links is in the cache.
   ============================================================ */

:root {
  /* ------------------------------------------------------------------
     Type ladder — seven steps, and a page title that leads
     (G67, REDESIGN section 2.6)

       11  chips, meta, small labels                 --fs-xs
       12  captions, help texts                      --fs-sm
       13  secondary text, buttons                   --fs-md
       15  ordinary text                             --fs-base
       17  card titles, section and window headings  --fs-lg
       20  the welcome window, and glyphs sized by   --fs-xl
           font-size
       30  a page title                              --fs-title

     E2a (G164, REDESIGN section 19, 3 October 2026) — WHOLE PIXELS. Until
     this round the steps were 11.52 / 12.48 / 13.76 / 15.2 / 17.28: tenths of
     a rem picked by eye, which land between pixels and render a little soft
     and a little "not quite" — the noise the UX check of 1 October measured
     (KL-56). Every reference app uses whole steps. They stay in rem, so a
     reader's own text size still scales the app, but each is a sixteenth
     that lands on a whole pixel at the default 16px (11/16 = 0.6875rem …).
     Weights: 400 text, 500 card and row titles, 600 headings — no 700
     (`checks/style.mjs` rules 11d and 11e hold both).

     NEVER a loose rem or px value; that is exactly what makes an interface
     look messy. `checks/style.mjs` rule 11 holds this, and it holds two
     other things that only a scanner finds -- see the header there.

     WHY THE PAGE TITLE GOT A STEP OF ITS OWN instead of --fs-xl being
     raised. Measured on Capture at 1280x900 before this round: the page
     title was 20/700 and the three block headings under it ("Talk", "Type
     or paste", "Ask") were 17.28/700 -- 2.72px apart at the same weight,
     with #logo a fourth thing at 17.28/700 as well. Four things competing
     to be the biggest is the same as none, so the title carried no more
     weight than the blocks it was introducing. But --fs-xl had three users
     and only one of them was a page title: #welkomModal h2 (a hero moment,
     fine at 20) and .welkomIcoon, which is not text at all but a glyph
     sized by font-size. Raising the token would have moved all three. So
     the role that was borrowing gets its own step, exactly as
     --radius-card and --radius-window did in G64.

     30px AND WEIGHT 600, WHICH IS THE REVIEW'S OWN NUMBER -- and the first
     one in REDESIGN to survive being measured unchanged. Ratio to a block
     heading goes from 1.16 to 1.74. The weight comes DOWN as the size goes
     up, because 700 at 30px is a shout where 700 at 20px was an attempt to
     be heard. Checked on a phone at 375x812, where .paginaKop is a centred
     column: "Library" is 93px wide on one line, the counter beside it still
     fits, document.scrollWidth stays 375, and the header grows 64 -> 76.
     ------------------------------------------------------------------ */
  --fs-xs: 0.6875rem;  /* 11px: chips, meta, small labels */
  --fs-sm: 0.75rem;    /* 12px: captions, help texts */
  --fs-md: 0.8125rem;  /* 13px: secondary text, buttons */
  --fs-base: 0.9375rem; /* 15px: ordinary text */
  --fs-lg: 1.0625rem;  /* 17px: card titles, section and window headings */
  --fs-xl: 1.25rem;    /* the welcome window, and glyphs sized by font-size */
  --fs-title: 1.875rem; /* a page title, a note's title (C6), the sign-in title (C13a), nothing else */

  /* ------------------------------------------------------------------
     J7 (G197, VISUAL-SYSTEM-01; review 2026-11-02 §5.0) — EVERY STEP HAS ITS
     LINE HEIGHT, IN PIXELS. E2a put the sizes on whole pixels and stopped
     there: 46 line heights stayed a bare factor, so the line box was the
     factor times the size — the 11 px meta drew 15.4 px tall, a phone row
     52.39, and the rows of one list stood on ten different sub-pixel tops.
     A factor also inherits as a factor, so one 1.5 on <body> gave every 11,
     12 and 13 px line a box nobody chose. Now each step is a pair, and
     checks/style.mjs holds both halves: no line-height without a unit, and
     a rule that sets --fs-x sets --lh-x beside it (or --lh-prose-x, below).

       11 / 14   12 / 16   13 / 18   15 / 20   17 / 22   20 / 25   30 / 36

     The pairs are Apple's text styles (11/13 · 12/16 · 13/18 · 15/20 ·
     17/22 · 20/25); the review set 14 for the 11 rather than Apple's 13, so
     a meta line keeps a little of the air its 15.4 had. In rem, like the
     sizes they pair with (J7 review 1): at the default 16 px root a
     sixteenth-based rem is the same whole pixel (0.875rem = 14), so a row's
     top stays on a whole pixel; and when the reader sets a larger default
     text size the line grows with its text. A px line box did not: at a
     24 px root the 30 px title became 45 px inside a 36 px line and wrapped
     lines drew over each other. checks/style.mjs holds the pairing: a twin
     of a rem size is rem.

     RUNNING PROSE KEEPS ITS AIR (a choice of this round, not in the review
     table): a note's body, an action's description, Ask's answer and a
     transcript are paragraphs you read, and WCAG 1.4.8 (AAA — the review
     that asked for this round is called the AAA check) wants 1.5 between
     their lines. 15/20 is 1.33. So prose has two pairs of its own on the
     same whole pixels: 13 / 20, 15 / 24 and 17 / 26.
     ------------------------------------------------------------------ */
  --lh-xs: 0.875rem; /* 14px */
  --lh-sm: 1rem; /* 16px */
  --lh-md: 1.125rem; /* 18px */
  --lh-base: 1.25rem; /* 20px */
  --lh-lg: 1.375rem; /* 22px */
  --lh-xl: 1.5625rem; /* 25px */
  --lh-title: 2.25rem; /* 36px */
  --lh-prose-md: 1.25rem; /* 20px */
  --lh-prose-base: 1.5rem; /* 24px */
  --lh-prose-lg: 1.625rem; /* 26px; J7 — 17 px prose on the static pages (/privacy, /telefoon): 1.53, WCAG 1.4.8's 1.5 */

  /* ------------------------------------------------------------------
     J7 (G197, VISUAL-SYSTEM-07) — THE PHONE ONE STEP UP. The phone ran the
     desktop ladder: measured at 375, 68 % of the visible text was 11 or 12 px,
     a row title 15 and its meta 11, where Apple Notes, Things and Bear set
     17 / 15 / 13. These roles carry the step; the `(pointer: coarse)` block
     at the end of this file moves them, so a mouse keeps every size it had.

       role            desktop        finger
       row title       15 / 20        17 / 22   an action's row, a note's row, a board card
       meta            11 / 14        13 / 18   date, category, board under a row title
       preview         12 / 16        13 / 18   a note's second line, a card's description
     ------------------------------------------------------------------ */
  --fs-row-title: var(--fs-base);
  --lh-row-title: var(--lh-base);
  --fs-meta: var(--fs-xs);
  --lh-meta: var(--lh-xs);
  --fs-preview: var(--fs-sm);
  --lh-preview: var(--lh-sm);

  /* ------------------------------------------------------------------
     Spacing — the scale (round AM)

     Measured on 6 August: 502 loose spacing values spread over 28 different
     numbers. 84% of them already sat on a grid of 2, but the rest did not:
     5, 7, 9, 11, 13, 18, 22 and 26 appeared side by side. That is what makes
     a screen restless — not that it is too roomy or too tight, but that the
     distances bear no relation to one another.

     106 values were moved to the nearest step, and everything now runs
     through these ten names. Anyone typing 13px after this sees straight
     away that they are off the scale, because there is no name for it.

     The steps were NOT invented but read off from what the app already did:
     2-4-6-8-10 for the insides of buttons and chips, 12-16 for cards and
     columns, 20-24-32 for the space between blocks.

     G63 -- THE TWO SECTION STEPS, AND THE RULE THAT SAYS WHICH STEP IS WHICH.
     Ten steps say how far apart two things may be; they never said what a
     distance MEANS. That is the grouping rule, and it is the whole point of
     having a spacing scale (REDESIGN sections 1.4 and 2.5):

       within a group    --space-4 .. --space-6    (8-12)
       between groups    --space-9 .. --space-10   (24-32)
       between sections  --space-11 .. --space-12  (48-64)

     Measured before this round: the app had NO section tier at all. Its
     largest distance anywhere was 32, on `#borden` -- the gap between two
     whole boards -- and 24, one step below it, was already the distance from
     the toolbar to those boards. The biggest boundary on the page and one of
     the smallest sat one step apart, and with nothing above 32 to reach for,
     grouping fell back on lines: `.paginaKop` and `.vandaagGroep h2` both
     drew a border because distance was not doing the work.

     A PHONE COMPRESSES ONE STEP, and that is not an exception to these tiers
     but the same move `.scherm` already makes: G50 took the between-blocks
     distance from 24 to 16 on Barend's own report. So a phone's section step
     is --space-10. What carries the meaning is the ratio -- a section is
     twice a block -- and that is 2:1 at both widths.

     --space-1 (2px) and --space-3 (6px) are for hairlines and optical
     correction, not for laying blocks out.

     --space-12 (64px) HAS NO USERS YET, and that is deliberate rather than an
     oversight -- the same call G79 made for --motion-slow. A tier declared by
     halves is a tier the next round invents a number for. 48 is the section
     step and 64 is the roomy one; the round that needs the second is the one
     that gives a single screen more than one section (REDESIGN section 8).
     ------------------------------------------------------------------ */
  --space-1: 2px;
  --space-2: 4px;
  --space-3: 6px;
  --space-4: 8px;
  --space-5: 10px;
  --space-6: 12px;
  --space-7: 16px;
  --space-8: 20px;
  --space-9: 24px;
  --space-10: 32px;
  --space-11: 48px;
  --space-12: 64px;

  /* ------------------------------------------------------------------
     Corners — one step per role (G64, REDESIGN section 1.3)

     There were three steps and four roles, so the roles borrowed from each
     other: every card in the app was 12, the same as the panel it sat in, and
     nine windows were an 18 that belonged to no step at all.

       8   a field, a chip, a small thing           --radius-s     (37 uses)
       10  a card inside a panel                   --radius-card  NEW
       12  a panel, and a button                   --radius-m / --radius
       18  a window that floats over the page      --radius-window
       999 a pill                                  --radius-pill

     Note what 12 covers, because REDESIGN section 1.3 calls it "panel" and
     that is only half true: `.knop` is 12 as well. Putting every button on a
     step of its own is a far bigger change to how the app looks than this post
     asks for, so 12 keeps both roles and this comment says so rather than
     describing a ladder the file does not have.

     THE WINDOW STEP IS NOT IN THE REVIEW, and that is the finding rather than
     an oversight on its part. REDESIGN section 1.3 lists four roles — panel,
     card, field, pill — and calls the 18s "loose". They are not loose: all
     of them are windows (#modal, #promptModal, #bordModal, the styles' and
     the categories' windows until C2b, #migratieModal, #voorstelModal,
     #welkomModal, #zoekAllesVenster, plus #slotVak, the sign-in box). A window is
     not a card, exactly as it was not a page in G62 — sweeping them onto
     --radius-card would round every dialog in the app to a card's corner and
     lose a distinction the app was already making. So the ladder gains the
     role it was missing instead.

     Off the ladder there are now three, each carrying its reason as `maat:`
     the way a height does: two 2px and one 4px, all three ornaments a few
     pixels wide rather than surfaces. And nine `50%`, which are circles.
     ------------------------------------------------------------------ */
  --radius-s: 10px;
  --radius-card: 12px;
  --radius-m: 12px;
  /* J7 (G197) — a corner inside a corner: a thumb in its tray, a chip in a
     field, an item in a menu. Concentric with --radius-s around it when the
     inner one stands --space-1 in; it replaces the loose 4, 6 and 8. */
  --radius-inner: calc(var(--radius-s) - var(--space-1));
  --radius-window: 20px;
  --radius-pill: 999px;

  /* ------------------------------------------------------------------
     Control heights — the fourth scale (G59, REDESIGN section 2.1)

     There was a scale for spacing, for depth, for colour and for type, and
     none for the one measurement you see first: how tall a control is. The
     result is what Barend kept describing as "one box is taller than the
     other", and on 2 September it was measured rather than guessed. On one
     desktop screen, in a single row or stacked right above each other:

       header buttons 34, the tabs under them 32, "Put on my Kanban" 40,
       "Ask" 44.8, the gear beside the text field 40x40 and the enlarge
       button beside THAT one 30x30 — the same class, two rectangles.

     In the board toolbar: search field 40, the ... button 34 wide by 40
     tall, the microphone 30, the record block 40, the select inside it 26.

     The steps are not invented. They are what the app already reaches for,
     rounded onto a grid of 4:

       28  a chip, a counter, a button on a card — something that reads along
       32  a compact row, and only where a mouse is doing the pointing
       36  THE standard: button, field, dropdown, icon button
       44  anything a thumb has to hit

     Two rules come with the scale, and both are enforced by
     `checks/style.mjs` rule 5 and 6:

       1. The touch floor always wins. `--control-touch` is what the
          `@media (pointer: coarse)` block at the bottom of this file hands
          out, and a control that a finger must hit is 44 no matter how neat
          36 would look next to it. G34 measured that floor; the ladder does
          not get to argue with it.
       2. A height off this scale needs its reason written down, with the
          `maat:` marker the spacing scale already uses — that word inside
          the comment, exactly as rule 1 reads it. There are a handful in
          this file and every one of them names what it was measured
          against.

     One warning about writing those comments, because it cost half an hour
     on the day this block was added: never put a comment terminator inside a
     comment. The two characters that end one, written out in prose, end it
     there and then, and everything after them is parsed as CSS. That is what
     happened here — the first declaration below was swallowed whole and
     --control-s was simply undefined, while its three neighbours worked. Rule
     4b of `checks/style.mjs` now catches it.
     ------------------------------------------------------------------ */
  --control-s: 28px;
  --control-m: 32px;
  --control-l: 36px;
  --control-touch: 44px;

  /* ------------------------------------------------------------------
     The page grid — the fifth scale (G62, REDESIGN section 2.4)

     Two widths and one margin, and every page reads them instead of
     carrying a number of its own. Before this round there were FIVE
     widths for six screens, and measured at 1280x900 the content of the
     app started on five different left lines:

       40   Kanban, Canvas, Calendar   main is min(96vw, 1600px)
       135  Library list               nothing caps it, so main's 1040
       315  Today                      640
       335  Capture, header and blocks 600
       375  Capture, the ask row       520

     So switching from Capture to Today slid everything 20px left, and on
     to the Library another 200. The two numbers on the SAME screen are
     the sharpest of the five: the page header starts at 335 and the row
     right under it at 375.

     WHY 640 FOR THE COLUMN, AND IT IS A MEASUREMENT AND NOT A TASTE.
     What matters is the line, not the number, so the line was counted at
     the app's own body size (15.2px) in the elements that really hold
     prose:

       a card on Today, inside 640   580px of text   70 characters
       the text field, inside 600    524px of text   63 characters
       a note title in the list      877px of text  104 characters
       its description               877px of text  126 characters

     45 to 75 characters is the readable band and 80 the ceiling that
     WCAG 1.4.8 names. Today at 640 sits at 70, so the screen that was
     already right stays exactly where it is. The Library list, the one
     screen with no cap at all, is the one that was actually broken: 104
     and 126 characters. 520 would have been the other way round, pushing
     the text field down to about 52.

     BOTH WIDTHS ARE THE PAGE BOX and not the column, because that is
     what max-width means on main with box-sizing: border-box. Hence 680
     rather than 640 -- the 640 of text plus its two page margins. The
     arithmetic is written out so that changing the margin finds it.

     WHY --page-work KEEPS 1600 WHERE REDESIGN section 2.4 proposes 1440.
     Nobody asked for a narrower board, and the columns spend every pixel
     they get: `.kolommen` lays out four tracks of at least 260 with a gap
     of 12, so four columns need 1076 and get 381 each at 1600 against 341
     at 1440. The review's number is not wrong, it is just narrower, and
     narrowing the one surface people work on is not what this post is.

     🔵 G122 (round C1c) — 680 BECAME 800, and the reason is the row. Since
     C1b a line on the List is a circle (28), the title, and a hover column of
     64 reserved on every row; at 640 of text that left the title 524 px, about
     72 characters, where the card had 580. 760 of text gives it 628 px
     (measured: 760 − 24 of padding − 28 − 8 − 8 − 64), about 86 characters —
     under the ~90 G122 names, above the 80 WCAG calls a
     ceiling for running prose. A title is not running prose, and it wraps at
     two lines. It is one token, so Capture and the Library's notes list widen
     with it (their text reaches about 88 characters too); the action list no
     longer reads it at all (G132: a grid over the work width).
     ------------------------------------------------------------------ */
  --page-read: 800px; /* a column you read: 760 of text + 2 x --page-pad */
  /* J7 (G197, VISUAL-SYSTEM-02) — A COLUMN ON WHOLE PIXELS. 96vw is a
     fraction of the window, so `main` was centred on a half pixel: x = 7.5 at
     375, 25.6 at 1280, and every row in it stood on n + 0.5. The window less
     two whole margins is centred on a whole pixel at any width. The margin
     is --page-pad (20) as the review has it; on a phone it is 8 (below), so
     the phone's column stays where it was (7.5 → 8) instead of losing 25 px of
     its 360 — J14 gives the phone its gutter of its own.
     100% and not the card's 100vw: html reserves the scrollbar's gutter
     (scrollbar-gutter: stable), 11 px in Chrome with a mouse, and 100vw counts
     it — at 1280 main came out 1240 in a body of 1269 and stood on x = 14.5.
     100% is main's containing block, so the two margins are whole at any
     gutter. Only main reads this token (max-width), so % means the body. */
  --page-work: min(calc(100% - 2 * var(--page-pad)), 1600px); /* a surface you work on */
  --page-pad: var(--space-8); /* the page margin, the same on every page */

  /* ------------------------------------------------------------------
     Motion — the eighth scale (G79, REDESIGN section 12.2)

     There was a scale for spacing, depth, colour, type, control heights,
     the page grid and corners, and none for the property you feel rather
     than see. Measured in this file before the round: 17 `transition`
     declarations holding 26 property/duration pairs, on four durations
     that had nothing to do with each other:

       120ms  x9    150ms  x13    200ms  x2    250ms  x2

     REDESIGN section 12.2 counts 23 declarations and says the curve is
     "virtually always" a bare `ease`. Both numbers are worth correcting,
     and the second one is the more useful correction: it is not virtually
     always, it is ALWAYS. All 26 pairs were a bare `ease`, with no
     exception anywhere in 11,116 lines -- so the rule the review asks for,
     one curve in and one curve out, had exactly zero users to build on.

     TWO SCALES AND NOT ONE, because motion is two questions. How long is
     `--motion-*`; what shape is `--ease-*`. Keeping them apart is what
     lets one line read `var(--motion-fast) var(--ease-state)` and say
     both.

       120ms  a tint, a checkmark, an arrow turning   --motion-fast   (14)
       200ms  a panel, a menu, a bar stepping aside   --motion-base    (2)
       320ms  a whole screen                          --motion-slow    (0)

     THE THIRD STEP HAS NO USERS TODAY AND IS DECLARED ANYWAY. That is
     worth naming rather than hiding, because a token nobody reads is
     normally a liability. Nothing in this app moves a whole screen yet:
     screens swap by toggling `hidden`, which is instant and has no
     duration to give. REDESIGN section 12.3 -- a card that opens into the
     window it belongs to instead of a window dropping on top -- is the
     round that needs it, and 320 is here so that round does not invent a
     fourth number next to these three.

     THREE CURVES AND NOT THE TWO THE REVIEW ASKS FOR, and the third is
     the one that carries almost all the traffic:

       --ease-state  symmetric   a reversible change: hover, focus, toggle
       --ease-enter  decelerate  something arriving
       --ease-exit   accelerate  something leaving

     Section 12.2 asks for enter and exit only, and that is right for
     one-way motion -- but 24 of the 26 pairs measured here are not
     one-way. A hover, a focus ring, a switch, a disclosure arrow: every
     one of them plays backwards as often as forwards, and an asymmetric
     curve on a reversible change is a defect you feel the moment you move
     the mouse away, because the return journey uses the arrival curve.
     Symmetric is not laziness on those; it is the correct answer, and
     naming it is what stops the next person reaching for `--ease-enter`
     on a hover because it was the only token on offer.

     WHY THESE ARE NOT `ease-in` AND `ease-out`, which is the trap this
     ladder is named around. CSS already has both words and they mean the
     opposite of what the roles need: something arriving must DECELERATE,
     which is `ease-out`, so a token called `--ease-in` would have to hold
     `ease-out` to be correct. Naming the roles instead of the shapes
     makes that impossible to get backwards.

     WHAT IS DELIBERATELY NOT ON THIS SCALE: the four `@keyframes`
     animations. `pulseren`, `draai` and `pulsOpnameKnop` run
     700ms to 1.6s and every one of them is `infinite`. Their duration is
     the RHYTHM of a signal that says "recording" or "busy", not the
     length of a change, and a pulse that takes 1.6s per beat has nothing
     to learn from a 120ms tint. Rule 13 of `checks/style.mjs` reads
     `transition` only, and this paragraph is why.

     And the reduced-motion block near the top of this file reaches all of
     this for free: it sets `transition-duration` on every element with
     `!important`, so it overrules a token exactly as it overruled a
     literal. Nothing here needed carving out.
     ------------------------------------------------------------------ */
  --motion-fast: 120ms; /* a tint, a checkmark, an arrow */
  --motion-base: 200ms; /* a panel, a menu */
  --motion-slow: 320ms; /* a whole screen -- see REDESIGN 12.3 */
  --ease-state: ease; /* a reversible change, so symmetric both ways */
  --ease-enter: cubic-bezier(0, 0, 0.2, 1); /* arriving: decelerate */
  --ease-exit: cubic-bezier(0.4, 0, 1, 1); /* leaving: accelerate */

  /* ------------------------------------------------------------------
     E3 (G156, design 14 §5.1): the layers, as names and not as numbers.

     Every z-index from 50 up was picked one at a time against its
     neighbours, and a number alone never said what a layer IS. Twice that
     cost a tap that hit something nobody could see: a question opened
     under the window that asked it (125 under 150 — "Discard this
     proposal?" answered by a tap in the middle of the screen), and a toast
     lay across a menu's lowest row. The order is now one sentence:

        page < window < menu < question < toast < lock screen < hint

     A window (anything openOverlay() opens) counts from --layer-window and
     keeps its place among the other windows with calc(); a menu is above
     every window because a menu is opened FROM one (the detail card's ⋯);
     a question is above the menu that asked it; a toast is above all of
     them and keeps itself off their controls (toast-room.js); the sign-in
     screen after a 401 is above everything that can be pressed, and a hint
     with nothing to press (the tooltip, the live transcript) above that.

     --layer-page-float is the ladder's kind 4 in style.css: a page layer
     that must float high for a reason of its own on the page (the floating
     record button, the phone's bottom bar, so their trays open above the
     page) and steps aside under html.vensterOpen. Its number sits among the
     windows' on purpose — it is never on screen with one.

     checks/style.mjs rule 16 holds every z-index from 50 up to one of
     these, or to a written `maat:` reason; tests/window-layers.test.mjs
     reads the names back to numbers and holds the order.
     ------------------------------------------------------------------ */
  --layer-window: 50;
  --layer-page-float: 95;
  --layer-menu: 300;
  --layer-question: 400;
  --layer-toast: 500;
  --layer-lock: 600;
  --layer-hint: 700;

  /* ------------------------------------------------------------------
     BD (BA-5): the safe area as four named values.

     `env(safe-area-inset-*)` can only be read where it is written, and a
     browser without a notch reports 0. Naming the four here means every
     rule that touches a screen edge reads the same four words instead of
     repeating the env() call — and, more usefully, it makes the whole
     thing measurable. There is no notch on a desktop browser, so the only
     way to see whether this actually works was to overrule these four
     variables in the console; that is how the numbers in
     `docs/ronde-BD-iphone.md` were measured.

     The second argument of env() is the fallback for browsers that do not
     know the keyword at all. Without it the whole declaration is dropped,
     which on an old Safari would mean a calc() with a gap in it.
     ------------------------------------------------------------------ */
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);

  /* (CH's --zweef-vast — "is the record button fixed on this screen?" — went
     in E2b with the drag path that read it: the pill no longer floats or
     moves anywhere. --blad-vorm below is the same one-source arrangement.) */

  /* C3a — is a window a sheet from below here? The fourth flag in this
     arrangement: bottom-sheet.js onPhone() asks it instead of a copy of 640. */
  --blad-vorm: 0;

  /* G54 — is this a screen where the calendar writes "sep 2026" instead of
     "september 2026"? Third one in the same arrangement, and for the same
     reason: the number 640 belongs in exactly one place per feature, and
     tests/fixed-record-bar.test.mjs fails the build on a matchMedia holding it.
     The month name is drawn by app.js (toLocaleDateString would follow the
     BROWSER's language), so the stylesheet cannot shorten it with a rule —
     it can only say whether it should be short. */
  --maand-kort: 0;

  /* ------------------------------------------------------------------
     §10 — HOW MUCH ROOM THE BOTTOM BAR TAKES. 0 on a wide screen, because
     there is no bar there: #onderbalk is `display: contents` above 640px and
     has no box at all.

     Fourth variable in the arrangement above, and the first one that carries a
     LENGTH rather than a flag — for the same reason the other three exist. The
     bar's height was written out as a number in three separate rules, and the
     comment beside `main`'s padding says in as many words "re-measure this if
     the bar's height changes". Two of the three were never re-measured: the
     toast tray and the recording strip both stood at 18px and 140px from the
     bottom of the VIEWPORT, so they landed ON the bar. MEASURED at 375 x 812:
     a toast with an "Ongedaan maken" button sat at y 754..794 while #onderTabs
     — Invoer, Vandaag, Acties, Notities, Meer — runs from y 757 to 806, and
     `document.elementFromPoint` at the toast's centre answered `meldingActie`.
     So the toast covered three of the five destinations AND swallowed the taps
     meant for them, every time the app confirmed anything.

     ⚠️ IT DOES NOT INCLUDE --safe-bottom. The bar adds that itself, and so
     must everything that clears it: `calc(var(--onderbalk) + X +
     var(--safe-bottom))` then reads correctly on BOTH sides of the breakpoint,
     where a variable with the inset baked in would double-count it on a phone
     or lose it on a desktop.

     ⚠️ AND IT IS A NUMBER, SO IT CAN GO STALE — that is the one thing this
     arrangement does not fix. What it does fix is that there is now one place
     to change instead of three, and tests/bottom-bar.test.mjs holds every rule
     that has to clear the bar to reading this variable rather than a number of
     its own. The height itself is checked in a browser; see that file. */
  --onderbalk: 0px;

  /* C8a (03 §3) — THE HEADER'S HEIGHT, ONE TOKEN FOR EVERY READER (registered as
     a <length> by the @property rule above :root, so a script that reads it
     gets "44px" and not the calc() it is written as): the Settings
     panel and its scrim now, and C10's key group, C11a's sticky column strip,
     D1a and D3 later. Above 640 px it is the header as measured —
     modules/sticky-header.js publishes #kop's height as --header-height, at
     start, on resize and whenever it wraps; on a phone the phone block below
     gives the slim row's own sum. Nothing writes it inline: an inline value on
     <html> would beat the phone's rule after the window narrows. */
  --kop-hoogte: var(--header-height, 0px);

  /* 🔴 RONDE HH — AND THE BAR IS NOT THE ONLY THING DOWN THERE WHILE YOU SPEAK.
     The recording strip (the sound bar, or the live transcript) hangs just
     above the button you pressed, and capture-feedback.js goes to real trouble
     to keep it clear of the bar — see watchFurniture() there. Nothing did the
     same for the toast tray, which sits at a fixed distance above the bar and
     therefore lands straight on the strip.

     MEASURED at 375 x 812, on the FIRST recording of a new account — the one
     moment wake-lock.js says its piece, and it says it with `staysLonger`:
     the toast stood at y 556..672 and #liveTekst at y 577..631, so all 54
     pixels of the sound bar were covered, plus 31 of the 44 of "Weggooien"
     beside it. The one thing on screen that shows the microphone is working,
     hidden by the message that arrives because the microphone started.

     capture-feedback.js writes the strip's own top edge here on every
     placement (and clears it when the strip goes), so the tray rises with it
     and comes back down by itself. 0px when nothing is recording, which is
     why `max()` below then reads exactly as it did before. */
  --opnamevoet: 0px;

  /* Tells the browser which way round this theme is, so ITS OWN parts —
     scrollbars, the caret, a date picker, the flash between two pages —
     stop being light while the app is dark. Overridden per theme below. */
  color-scheme: light;

  /* betekenis: B1 (REDESIGN §17.2) — the page is white now, the same white as
     --card, on purpose: a row on the List is on its way to not being a card at all
     (§17.1 point 3), and on the board the card is told apart by the tinted
     --column under it, not by the page. */
  --background: #ffffff;
  /* betekenis: white is the paper here AND the ink on a saturated fill
     (--on-accent, --on-colour). That is one neutral doing two jobs at
     opposite ends of the stack, not one meaning wearing two names -- and a
     card that is not white in a light theme is a different design. */
  --card: #ffffff;
  /* betekenis: B1 — a column and a surface are one tint in style A: a tinted
     area set off from the white page (§17.1 point 1). --column keeps its own
     name because the dark and sepia themes give it its own value. */
  --surface: #f2f2f4;
  --column: #f2f2f4;
  /* B1 (REDESIGN §17.2) — a row in a list is not a card: no border, no fill,
     and this tint under it on hover. Use it only inside @media (hover: hover);
     a touch screen has no hover and would keep the tint after a tap. */
  --row-hover: #f5f5f7;
  /* J7 (G197) — a row under a finger or a pressed mouse: one step deeper than
     --row-hover, the iOS cell highlight. For J9/J14/J15; defined here. */
  --row-pressed: #e8e8ed; /* betekenis: = --surface-hover in light on purpose — a pressed row is one step deeper, the same step a control takes under a pointer */
  --border: #e5e5ea;
  --text: #1d1d1f;
  /* AM-4: four shades darker than it was (#656d6a).
     Measured across all three themes: on the card background this colour
     scored 5.3:1, but on --surface (the grey bars, chips and menus) 4.36:1 —
     just under the 4.5:1 standard. That hit the tabs, the AI text in a note
     and the explanatory lines: exactly the things you need to be able to
     read. On every background the app uses it now scores at least 4.6:1.
     J7 (G197) — one step darker again (#6e6e73 → #65656a): with fields on
     --surface and the phone's meta at 13 px, it reads on --surface at 5.18
     where it had 4.6 (review 2 November, §5.0). */
  --text-light: #65656a;
  --accent: #0e5c48;
  /* J4 — the accent as the colour of a LINK or other small text. Equal to
     --accent in light and sepia, where the accent is dark; the dark theme's
     accent is a light green meant to carry dark text (--on-accent), and as
     text it scored 4.16 : 1 on --surface and 3.71 on --accent-soft. */
  --accent-text: var(--accent);
  /* Text/icons ON the accent colour. Different per theme, so the contrast
     always holds (in the dark theme the accent is light instead). */
  --on-accent: #ffffff;
  /* AM-3: text on a saturated coloured area (record button, note button).
     Deliberately NOT --on-accent: that shifts per theme along with the accent
     colour, and in the dark theme it is almost black. On red or purple it must
     always be white, whatever the theme. */
  --on-colour: #ffffff;
  /* AO-5: the dot on the bell. Deliberately NOT --recording-red: that is the
     surface of a large record button, and white on it scores 4.39:1 — just
     under the standard. That went unnoticed on a 40px button, but the dot
     carries a 10px number, and then every tenth counts. This shade scores
     6.5:1 and is the same in all three themes: it is always white on red. */
  --alert-red: #b3261e;
  /* AQ-1: colours the style check found that stayed the same in all three
     themes until now. Two kinds:

     IDENTITY — belongs to WHAT something is, not to the theme, and is equal
     in every theme, as --note-accent is. Two that stand below are no longer
     among them since J7: --action-light became the accent and --pin is
     themed, so they follow the theme like any THEME colour. */
  /* J7 (G197, VISUAL-SYSTEM-14) — ONE green. --action-light was #1a7a5e in
     every theme beside an --accent of #0e5c48: two greens on each Actions
     screen, six points apart, and a green record half on sepia's brown. It is
     the accent now, per theme; the name stays for its two readers (the record
     pill's Action half and the map's card dot). */
  --action-light: var(--accent);
  --note-line: #6b5fa8;     /* small bar/icon "this is a note" */
  /* J7 (G197, VISUAL-SYSTEM-03) — the pin is THEMED now. #d8b060 in every
     theme was the only "pinned" signal and 2.04 : 1 on white (1.83 on sepia's
     surface): a gold you had to know was there. Each theme gets the gold that
     clears 3 : 1 (non-text, WCAG 1.4.11) on every ground a pin lies on:
     #a2781e 4.01 on white and 3.58 on --surface; sepia #8c6a1a; dark keeps
     #d8b060 (6.83 on the card). The glow follows the pin. */
  --pin: #a2781e;              /* pinned note */
  --pin-glow: color-mix(in srgb, var(--pin) 40%, transparent);
  --accent-on-dark: #7fd6b8; /* accent on the permanently dark toast area */
  /* BD (BA-9): the two ends of the two record gradients. These are IDENTITY
     as well: a record button that turned pale in the dark theme would no
     longer be the same button. They were loose values in five rules.
       (--action-deep and --note-deep, the closed ends of the green and the
                        purple gradient, went in E2a (KL-52): the pill's halves
                        are flat, and nothing read them any more. So did
                        --danger-border, since the control recipe (G165).)
       --recording-light   the open end of the red gradient. Measured: white on
                        #e05a52 scores 3.65:1, on this value 4.54:1. The big
                        record button already used the deeper red for exactly
                        that reason; the floating button and the mini
                        microphone did not, while the word "Stop" and the
                        running counter stand on precisely that red. One
                        token, so that can no longer drift apart. */
  /* 🔵 G105 (ronde FS) — `--action-gradient-light: #147a5f` STOOD HERE and is
     gone. It meant "the light end of the action green" and so does
     `--action-light: #1a7a5e` twenty lines up, six points away in red and two
     in green: one idea under two names, which is the case `checks/style.mjs`
     rule 12 exists to tell apart from two ideas on one value. It survived
     because it was the only token sepia overrode, and sepia only overrode it
     because the OTHER end of that gradient was the theme accent. Take the theme
     colour out and the reason for the second name goes with it. */
  --recording-light: #ce4840;
  /* A surface that stays dark in EVERY theme: the toast strip at the bottom
     and the tooltip. Deliberately not --card — these two lie over the page
     and have to be legible above a light one as well. Sepia does shift them,
     because a cold grey-black on warm paper is the one place where it looks
     like a foreign object. */
  --dark-surface: #24282b;
  --on-dark-surface: #f2f4f3;
  /* J7 (G197, VISUAL-SYSTEM-06) — the edge of that dark surface, for the one
     theme where it is not dark against the page. None in light and sepia. */
  --dark-surface-edge: transparent;
  /* SURFACE — does belong to the theme. */
  /* ------------------------------------------------------------------
     --control-border — the edge of something you can TYPE IN or PRESS
     (doorlichting §9, round GO)
     ------------------------------------------------------------------
     🔎 MEASURED, and the number is worse than the screening's. It reported
     1,15 : 1; against the five surfaces a control actually stands on,
     `--border` runs from 1,03 (light, on --surface) to 1,45 (sepia, on
     --card). WCAG 1.4.11 asks 3 : 1 of the visual information that says
     "there is a control here", and an empty text field with a placeholder is
     that rule's own worked example: take the border away and there is
     nothing left to see.
     ⚠️ AND `--border-strong` DOES NOT RESCUE IT EITHER — 1,61 to 1,89 — so
     the `prefers-contrast: more` block, which swaps one for the other, did
     not reach 3 : 1 for anybody who asked for more contrast. That is the
     half of this finding nobody had counted.

     WHY A SECOND TOKEN RATHER THAN DARKENING `--border`. They are two
     different jobs wearing one name. `--border` draws cards, columns,
     menus, the calendar grid and every divider — grouping, on surfaces that
     already carry their own fill, and REDESIGN §1.3 is explicit that a
     border AND a fill for one edge is two signals for one boundary. Push
     that to 3 : 1 and every card on the page grows a hard outline; the app
     reads as a spreadsheet and the thing it was asked to be is "modern,
     minimalist, fluid". So the structural border stays soft and only the
     controls get the strong one.

     WHERE THE VALUE COMES FROM. The same method as round GJ one layer up:
     walk from `--border-strong` toward `--text` and stop at the FIRST step
     that clears 3 : 1 on every surface a control can stand on — background,
     card, surface, column and chip. The lowest rung, not a comfortable one,
     because a border that is darker than it has to be is the spreadsheet
     again. Light lands on 3,04 at its worst (on --surface, --column and
     --chip-surface, one tint in style A), dark on 3,04 (on --card and
     --surface), sepia on 3,00 (on --surface). 🔵 Recomputed in B1: REDESIGN
     §17.2 proposed #aeaeb2 / #6e6e73 / #a4957c, which measure 1,98 / 2,75 /
     2,34 on the new surfaces; the values here are this method's answer
     instead, approved by Barend on 25 September 2026. `tests/control-border.test.mjs`
     recomputes all fifteen and names them. */
  --control-border: #8b8b8f;
  --border-strong: #c7c7cc;       /* border of a button you hover over */
  --accent-deep: #0c503f;      /* primary button you hover over */
  /* AP-2: the arrow of a select, in the muted text colour of the theme.
     Drawn as a background image, so set per theme separately — a data URI
     cannot contain a CSS variable. */
  --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23616966' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  /* E2b (G167) — the line set's chevron pointing right, for a place CSS draws
     an icon (a submenu row's ::after) instead of icons.js. A MASK, so its
     colour is the rule's background: one value for all three themes. */
  --icoon-chevron-rechts: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 6l6 6-6 6'/%3E%3C/svg%3E");
  --icoon-prullenbak: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13M10 11v5M14 11v5'/%3E%3C/svg%3E");
  --icoon-chevron-links: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 6l-6 6 6 6'/%3E%3C/svg%3E");
  /* The purple note colour appeared seven times as a loose value in this
     file, while everywhere it means "note" — next to the green for actions. */
  /* AM-4: from #6f6fc4 to #6d6dc2. White text on the light side of the
     purple gradient scored 4.42:1 — just under the standard. Two shades
     deeper cannot be seen but can be measured. */
  --note-accent: #6d6dc2;
  /* The haze behind an open window. Existed in three variants. */
  --scrim: rgba(0, 0, 0, 0.28);
  --accent-soft: #e6f2ec;
  --note-soft: #efedf7;
  --recording-red: #d6453f;
  /* U-11: the tint under a destructive button you are pointing at, beside
     --accent-soft and --note-soft. It exists because those two were the only
     soft tints in the file, so a trash button lit up ACCENT GREEN when you
     hovered it. Measured with tests/helpers/contrast.mjs, --danger-text on it:
     4.59 light, 3.80 dark, 4.94 sepia. Dark is the lowest of the three because
     its tint has to be a step LIGHTER than the --surface it replaces (E2a); it
     is still above the 3:1 a graphical control needs. Every destructive
     .knop / .ikoonKnop / .kaartKnop takes it under a pointer (E2a review). */
  --danger-soft: #fbeeed;
  /* AM-4: red as TEXT needs a different value than red as a SURFACE.
     --recording-red is the surface of the record button, with white text on it.
     That same colour was also used in front of text (the Delete button), and
     there it scored 4.39:1 on white — just under the standard. Two jobs, two
     values. */
  --danger-text: #c83731;
  --shadow: 0 1px 2px rgba(23, 27, 25, 0.05), 0 4px 16px rgba(23, 27, 25, 0.06);
  /* AM-2: three levels instead of 25.
     Measured: 43 shadows, with 25 different values — nearly every shadow
     occurred once. Depth is supposed to mean something, and with 25 variants
     it means nothing any more.
       --shadow            at rest: cards and notes, lying on the page
       --shadow-raised   hovering just above: menus, tooltips, strips
       --shadow-floating    lying over the page: windows and panels */
  --shadow-raised: 0 4px 20px rgba(0, 0, 0, 0.10);
  --shadow-floating: 0 16px 44px rgba(23, 27, 25, 0.18), 0 2px 8px rgba(23, 27, 25, 0.08);
  /* E3 (design 14 §2.6): the edge of something raised — a menu. None in the
     light themes, where the shadow draws the edge; a hairline in the dark
     one, where a shadow on near-black does not show. */
  /* E2a (G166, VS-02) — the "thumb" of a segmented control: the chosen tab
     in the header, the chosen view, the chosen board on a phone. It lay on its
     tray with the full card shadow, which made the header the loudest band on
     the page. A thumb gets a shadow of its own, a third as heavy. The header's
     tabs lie on a --surface tray, and in the dark theme --surface is above the
     card now — so there the thumb is a lighter grey than its tray, as iOS
     draws it, with no shadow (--thumb-surface). The view and board switchers
     lie on a --column tray, where the card is above the tray in every theme,
     and kept --card. Since I139 the four segmented controls (style.css, I105)
     all take --thumb-surface inside a 1 px --control-border edge: that edge,
     not the fill, is what tells the chosen side (3 : 1); the board picker on a
     phone still keeps --card. */
  --thumb-surface: var(--card);
  /* E2a (G165, KL-45) — THE CONTROL RECIPE. A button, an icon button, a
     ⋮, Filter, Sort: a --surface fill and NO border. The 1px edge is still
     there (so nothing changes size) but transparent; it comes back under
     Increase Contrast (prefers-contrast: more sets --control-edge to
     --control-border) and with the keyboard focus. Text fields keep their
     3:1 border — nothing else makes an empty field recognisable. A board
     card keeps a border only in the dark theme (--card-edge), where its fill
     is the least apart from the column.
     --surface-hover: one tint deeper than --surface, for a pointer. */
  --control-edge: transparent;
  --card-edge: transparent;
  --surface-hover: #e8e8ed;
  --shadow-thumb: 0 1px 3px rgba(0, 0, 0, 0.10), 0 1px 1px rgba(0, 0, 0, 0.04);
  --header-background: rgba(255, 255, 255, 0.88);
  /* The frosting behind the header, as a value rather than a rule. See the
     reduced-transparency block: an override there has to beat #kop::before,
     and #kop::before comes later in the file, so a rule would silently lose
     on order — which is exactly what happened when it was first written that
     way. A variable is set on :root and does not care about order. */
  --header-frost: blur(12px);
  --radius: var(--radius-m);

  /* ------------------------------------------------------------------
     BD (BA-9): the chips.

     These were the biggest group of loose colours: five pairs written out
     three times over — once as the ordinary rule (which was the light
     theme) and twice more as a `:root[data-thema="…"] .chip.…` line. The
     style check allows that, because a rule with a theme variant beside it
     is not a colour that stands still. It was still eighteen places to keep
     in step, and they had already fallen out of step once: an overdue
     deadline had a dark variant but no sepia one, so on sepia paper it drew
     the light-pink chip from the light theme.

     G68 -- FIVE PAIRS BECAME TWO, because four of the five said something
     colour is not able to say here. Measured on one board of 25 cards, 64
     chips, before anything moved:

       .chip                30   grey, no meaning
       .chip.deadline       24   amber (22 of them overdue, drawn RED)
       .chip.prioriteit-hoog 9   red
       .chip.domein-Werk     1   blue

     Three findings, and each one removes a pair:

     1. THE DOMAIN IS AN OPEN SET, so two magic strings cannot colour it.
        `domein` is the name of a user's BOARD (claude.js: "the user can have
        their own boards such as Klanten or a project name"). Only
        `.domein-Werk` and `.domein-Privé` had a colour, so in this database
        63 Werk and 4 Privé cards were coloured and 2 "Prive" (no accent) and
        1 "Thuis" were not -- the same word spelled two ways, coloured two
        ways. A colour that only works for two of an unbounded set is not a
        code, it is a coincidence.

     2. RED SAID TWO THINGS AND THE SECOND ONE WON. --chip-alert-* was
        "priority: high" AND an overdue deadline, pixel for pixel: both drew
        rgb(56,32,30)/rgb(238,139,132) in dark. Nine urgent chips and 22
        overdue chips on one board, in one red, meaning two things.

     3. THE FILL WAS NEVER DOING THE WORK. Every coloured chip surface sat
        1.01-1.04:1 against the plain grey chip, in ALL THREE themes -- the
        text colour carried the whole signal and the fill carried none. In
        sepia that is fatal, because there the paper is amber too: the plain
        chip (h40) and the Work chip (h42) and the deadline chip (h42) were
        the same hue AND the same lightness, so the sepia chip row said
        nothing at all.

     What is left is two pairs: a plain chip, and amber for a date that has
     PASSED. The amber is re-cut so the fill earns its place -- see the
     three values below and the same pair in the two theme blocks. */
  --chip-surface: var(--surface);
  --chip-text: var(--text-light);
  /* G68: the one coloured chip left. 1.11:1 against the plain chip where it
     used to be 1.04:1, and s86 against the plain chip's s6 -- in this theme
     the hue does most of the work, which is exactly what sepia cannot do. */
  --chip-deadline-surface: #fdf0cc;
  --chip-deadline-text: #8a5b00;
  /* The ring around a category dot. Black at 15% on a light chip; on a dark
     one black on black is no ring at all, hence the theme variant. */
  --dot-border: rgba(0, 0, 0, 0.15);

  /* J7 (G197, VISUAL-SYSTEM-12) — THE FIVE NAMED TINTS. style.css had 30
     color-mix() lines with 14 recipes: a second palette that no test ever
     measured per theme. Five names now, each a recipe on the theme's own
     tokens (so each theme resolves its own), and style-a-tokens.test holds
     them:
       --surface-2     a wash of the ink: hover, an open menu row, a skeleton
       --surface-3     a step deeper: a pressed menu row, a subsection
       --text-faint    ink for what is barely there: a scrollbar, a quote rule
       --tint-accent   the accent as a ground: an open dark control, a selection
       --tint-section  a section's colour as a ground — declared where
                       --sectie-kleur is (style.css, .canvasSectie and its
                       kin), because JS sets that colour per element.
     They are transparent washes: laid on a ground, they darken or tint it,
     so one value serves the card, the column and the page alike. Where the
     result has to be opaque, a rule layers it over its ground
     (linear-gradient(var(--surface-2), var(--surface-2)), var(--card)). */
  --surface-2: color-mix(in srgb, var(--text) 6%, transparent);
  --surface-3: color-mix(in srgb, var(--text) 10%, transparent);
  --text-faint: color-mix(in srgb, var(--text) 28%, transparent);
  --tint-accent: color-mix(in srgb, var(--accent) 22%, transparent);
  /* A count on a section's tint: --text-light is under 4.5 : 1 on a coloured subsection over a coloured
     parent (red on red: 4.09 light, 4.02 sepia); 30 % of the way to --text clears it in every theme
     (tests/canvas-edge.test.mjs). Opaque, unlike the washes above. */
  --text-on-tint: color-mix(in srgb, var(--text) 30%, var(--text-light));

  /* J7 (G197) — for J9/J14/J15, defined here and read by nothing yet:
       --gutter  the one page gutter a row's title starts on (20 on a phone,
                 24 with room: below, at 640);
       --ring    the action circle's 1.5 px ring, ≥ 3.5 : 1 on the card, the
                 page, a hovered and a pressed row in its theme. */
  --gutter: 24px;
  --ring: #77777c;
}

/* Dark mode — inspired by modern developer tools */
:root[data-thema="donker"] {
  color-scheme: dark;
  --background: #1c1c1e;
  /* betekenis: B1 (REDESIGN §17.2) — the iOS dark grey: --card is one step
     above the page (#1c1c1e), and on the board the card is told apart by the
     darker --column under it.
     E2a (G166, KL-51) — --surface goes ONE STEP ABOVE --card. With both on
     #2c2c2e, everything that is a surface on a card or a panel — a chip, a
     field, the tray of a segmented control, a note in a canvas section — had
     the card's own colour and vanished: the UX check of 1 October counted
     seven places. #3a3a3c is iOS's next grey: 6.4 points L* above the card
     (0 before). It is also --border's value in this theme, and that is the
     point of the recipe (G165): where a control used a border to be seen,
     it now has this surface instead. */
  --card: #2c2c2e;
  --surface: #3a3a3c; /* betekenis: --surface and --border are both #3a3a3c on purpose — G165: where a control was seen by its border, it is now seen by this surface */
  --column: #232326;
  --row-hover: #242427;
  --row-pressed: #3a3a3c; /* betekenis: = --surface in dark on purpose — the iOS cell highlight is the next grey up, which is the control surface */
  /* J7 (G197) — #3a3a3c → #48484a: --border was --surface's own value, so a
     border on a surface was 1.00 : 1. iOS's next grey: 1.24 on --surface,
     1.53 on the card. It took --raised-border's value, and that token went. */
  --border: #48484a;
  --text: #f2f2f7;
  /* E2a (G166) — #98989d → #a3a3a8. With --surface a step above the card,
     secondary text on it — the inactive tabs in the header's tray, every
     chip (--chip-text follows this token) — scored 3.95:1 where 4.5 is the
     floor (REDESIGN §17.2). #a3a3a8 is the first grey toward --text that
     clears it on every dark ground: 4.52 on --surface, 5.55 on the card,
     6.77 on the page.
     J7 (G197) — one step lighter (#ababb0): 4.96 on --surface, 6.09 on the
     card, 7.44 on the page. */
  --text-light: #ababb0;
  --accent: #27b183;
  /* J4 — a step lighter than --accent, for text: 5.45 : 1 on --surface and
     4.87 on --accent-soft laid on it (the accent itself: 4.16 and 3.71). */
  --accent-text: #3cca98;
  /* The accent is light here — so dark text on it (white scored 2.7:1). */
  --on-accent: #08130f;
  --on-colour: #ffffff;
  --note-accent: #6d6dc2;
  --scrim: rgba(0, 0, 0, 0.6);
  --accent-soft: rgba(39, 177, 131, 0.08);
  --note-soft: rgba(124, 111, 196, 0.18);
  --recording-red: #e05a52;
  /* E2a review — the red follows the lighter --surface (G166). A destructive
     control rests on --surface now and takes --danger-soft under a pointer;
     #e2514b scored 2.98 on the one and 2.87 on the other (floor 3.5,
     tests/danger-without-hover). #f27a72 is the value /beheer already chose for
     the same reason (ontwerp 13 §12): 5.18 on --card, 4.22 on --surface, 3.80
     on --danger-soft. The tint is OPAQUE here, a red-tinged step above
     --surface: a 4 % red over whatever lay behind the button came out darker
     than the button at rest, so a pointer seemed to switch it off. */
  --danger-text: #f27a72;
  --danger-soft: #503c3c;
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 6px 20px rgba(0, 0, 0, 0.35);
  --shadow-raised: 0 4px 14px rgba(0, 0, 0, 0.40), 0 1px 3px rgba(0, 0, 0, 0.30);
  --shadow-floating: 0 16px 44px rgba(0, 0, 0, 0.55), 0 2px 8px rgba(0, 0, 0, 0.35);
  /* J7 (G197, VISUAL-SYSTEM-06) — the tooltip and the toast. #24282b was the
     last greenish grey and 1.15 : 1 against the page: a dark strip on a dark
     page, seen only by its text. #48484a is iOS's raised grey (8.18 for its
     text, 5.31 for --accent-on-dark), and the 1 px edge at #8e8e93 is what
     clears the 3 : 1 of a component against the page (5.22) and the card
     (4.27) — the surface alone stays at 1.86, because any grey that clears
     3 : 1 against #1c1c1e leaves its own green link under 4.5. */
  --dark-surface: #48484a; /* betekenis: = --border in dark on purpose — iOS's raised grey, one value for the edge of a card and the ground of a tooltip */
  --on-dark-surface: #f2f2f7; /* betekenis: = --text in dark on purpose — on a surface that is dark anyway, the ink is the theme's own */
  --dark-surface-edge: #8e8e93;
  --pin: #d8b060;
  --ring: #919196;
  --thumb-surface: #636366;
  --card-edge: var(--border);
  --surface-hover: #444446;
  --shadow-thumb: none;
  --header-background: rgba(28, 28, 30, 0.88);
  /* E2a (G166) — #75757a → #838388. A text field keeps its 3:1 edge (G165),
     and G166 lifted --surface a step (#3a3a3c), where #75757a scored 2.48.
     The first grey from there toward --text that clears 3:1 on all five
     grounds (tests/control-border.test.mjs): 3.01 on --surface, 3.69 on the
     card, 4.51 on the page. */
  --control-border: #838388;
  --border-strong: #5a5a5e;
  --accent-deep: #34c795;
  --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%238d9995' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --chip-surface: var(--surface);
  --chip-text: var(--text-light);
  /* G68 -- AND THIS IS WHERE THE TWO AMBERS WERE LITERALLY ONE COLOUR.
     --chip-deadline-text was #d8b060 here, which is --pin, character for
     character: the only pair of tokens in the whole stylesheet that resolved
     to the same value in the same theme. The deadline pair moves, not --pin:
     --pin is an IDENTITY colour (AQ-1/BA-9, the same in all three themes) and
     the deadline pair is a themed pair, so the themed one is the one allowed
     to shift. 1.34:1 against the plain chip, where it was 1.03:1.
     (Since J7 --pin is themed after all — for contrast in light and sepia; the
     dark theme keeps #d8b060, so this separation stands.) */
  --chip-deadline-surface: #4a3a12;
  --chip-deadline-text: #f5c76a;
  /* Light instead of dark: a black ring on a dark chip is not a ring. */
  --dot-border: rgba(255, 255, 255, 0.22);
}

/* Sepia — soft, warm evening mode (paper-like, easy on the eyes) */
:root[data-thema="sepia"] {
  --background: #f8f2e6;
  /* betekenis: the paper, and the ink on the dark panel (--on-dark-surface).
     An inversion: the lightest colour is the surface at the bottom of the
     stack and the text at the top of it. Deliberate on sepia, where a second
     near-white would read as a smudge. */
  --card: #fefaf2;
  --surface: #eee5d2;
  --column: #efe7d6;
  --row-hover: #f2ebdd;
  --row-pressed: #e6dcc8;
  --border: #e3d8c2;
  /* betekenis: the same inversion the other way up -- the ink, and the dark
     panel it is inverted out of (--dark-surface). One colour used as figure
     in one place and ground in the other, which is what makes the pair read
     as one family rather than as two. */
  --text: #3c3128;
  /* C1b — #7a6b57 until a row's hover surface (--row-hover) put it under it:
     4.35 : 1 there, under the 4.5 of text this small. #746552 is the value the
     chip already needed (B1): 4.75 on --row-hover, 5.06 on the page. */
  /* J7 (G197) — one step darker (#6e5f4b): 4.93 on --surface, 5.54 on the page. */
  --text-light: #6e5f4b;
  /* AM-4: slightly deeper than #a45a2a — that one scored 4.1:1 on the sepia
     background and the standard is 4.5:1. This affected the name "Assistent"
     in the top left.

     DG — AND ONCE MORE, FOR THE SAME REASON, BECAUSE AM-4 MEASURED ONE
     SURFACE. #9c5222 clears 4.5 on `--background` (4.57) and on `--card`
     (5.04), which is what AM-4 looked at. It does NOT clear it on the two
     TINTS this theme also paints TEXT on: 4.13:1 on `--surface` and 4.28:1 on
     `--accent-soft`. Four rules put accent-coloured text on one of those, and
     none of that text is large:

       .uitlegKnop:hover        the `?` beside every heading in Settings, on
       .uitlegKnop.actief       `--surface` and on the tint — 11.5px at 700
       #canvasHulpKnop.actief   the help item on Canvas, 13.8px
       (#kalenderVorige.sleepDoel, #kalenderVolgende.sleepDoel — the ‹ ›
                                you dragged a deadline onto, 15.2px; gone in D1b)

     One more rule colours with the accent on the tint — `.meerKnop:hover` (and
     `.zoekMic:hover` until C7a) — but it holds a bare <svg> and no text at all.
     That is a graphic: WCAG 1.4.11 asks 3:1, and 4.28 was never a problem for
     them. Three further rules (`.verbinding:hover`, `.archiefTeOpenen:hover`,
     `.zoekTreffer.actief`; a fourth, `#notitieZoekAiRegel:hover`, went with the
     Notes field's Ask line in C8b) put an accent BORDER on a tint and leave the
     text alone; 3:1 again, and 4.13 passes.

     ⚠️ THOSE LAST FOUR WERE ON THIS LIST UNTIL A MUTATION RUN THREW THEM OFF.
     The scan that found them looked for `color:\s*var\(--accent\)`, which
     matches the tail of `border-color: var(--accent)`. Eight places became
     four once the pattern stopped answering for a border. Fifth time in five
     rounds that a pattern found the wrong thing and looked satisfied — see
     ROADMAP §0.

     Why the token and not those four rules. The fault is not in a button, it
     is in this value: it is too light for the tints this same block defines,
     and the fifth rule somebody writes would fail again. `.uitlegKnop:hover`
     could not be fixed from the two rules DE named in any case, because it
     fails on `--surface` and not on the tint. The same argument AM-4 made, and
     the same fix. Three lightness points deeper, hue and saturation untouched
     (HSL 23.6°, 64.2%, 37.3% -> 34.3%). That puts every text use of the accent
     in this theme over 4.5 with room to spare — `--surface` 4.71,
     `--accent-soft` 4.88, `--card` 5.75, `--background` 5.22 — and it IMPROVES
     everything else: white on the accent goes from 5.76 to 6.57. Measured
     against every surface in tests/accent-contrast.test.mjs, which fails on
     the value and not on a selector, so a lighter accent cannot come back
     unnoticed. */
  --accent: #8f4b1f;
  /* betekenis: --on-accent and --on-colour both mean "ink on a saturated
     fill" -- one meaning, two names, kept apart only so the accent can move
     per theme without dragging every other coloured fill with it. */
  --on-accent: #ffffff;
  --on-colour: #ffffff;
  /* 🔵 G105 (ronde FS) — TWO OVERRIDES STOOD HERE AND BOTH ARE GONE, because
     what they were compensating for is gone.

     The history is worth keeping, because it is the same bug from both sides.
     Barend reported on 26 August that "the circle round the microphone is an
     oval". It was not the shape — both microphone buttons measure exactly
     square — it was that `--action-gradient-light` and `--glow-action` were
     MISSING from this block, so the record button was painted
     `linear-gradient(160deg, #147a5f, #9c5222)`: GREEN fading into brown, with
     a GREEN shadow under it, offset 10 px down and blurred 28 px. An off-centre
     coloured smudge under a round button is what reads as an oval. The repair
     then was an orange start and an orange glow, so the whole orb turned brown
     in this theme and agreed with itself again.

     G105 is the other half of it: the orb should never have followed the theme
     at all. Then it was `--action-light` to `--action-deep` (gone in E2a) — the
     identity pair `.zweefActie` used — and an orange start HERE would recreate
     the clash of 26 August with the colours the other way round. So these two
     are not tidied away; they are obsolete, and putting one back would be the
     bug. Since C9 the orb is gone: the two round buttons on Capture are flat
     --action-light and --note-accent, and no record button has a glow. */
  --note-accent: #6d6dc2;
  --scrim: rgba(60, 49, 40, 0.35);
  --accent-soft: #f0e0cc;
  --note-soft: #ede0c8;
  --recording-red: #b0463b;
  --danger-text: #a83a30;
  --danger-soft: #f2e0d2;
  --shadow: 0 1px 2px rgba(70, 58, 41, 0.08), 0 4px 16px rgba(70, 58, 41, 0.12);
  --shadow-raised: 0 4px 14px rgba(70, 58, 41, 0.14), 0 1px 3px rgba(70, 58, 41, 0.10);
  --shadow-floating: 0 16px 44px rgba(70, 58, 41, 0.22), 0 2px 8px rgba(70, 58, 41, 0.12);
  --pin: #8c6a1a;
  --ring: #7d6e5a;
  --surface-hover: #e6dcc6;
  --shadow-thumb: 0 1px 3px rgba(70, 58, 41, 0.12), 0 1px 1px rgba(70, 58, 41, 0.05);
  --header-background: rgba(248, 242, 230, 0.9);
  --control-border: #8f826f;
  --border-strong: #c6b89f;
  /* DG: three lightness points deeper as well, so the step between this and
     `--accent` stays the 5.3 points it was. Without that the hover state of
     `.knop.primair` — its only user — would sit 1.4 points under its rest
     state and read as no hover at all. White on it: 8.27:1. */
  --accent-deep: #793f1a;
  --select-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%236b5c3e' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  /* Sepia is the one theme that also moves the always-dark surfaces: a cold
     grey-black toast on warm paper reads as a piece of another app. */
  --dark-surface: #463a29;
  --on-dark-surface: #f8efdc;
  --chip-surface: var(--surface);
  --chip-text: var(--text-light);
  /* G68 -- the theme this round is really for. Sepia paper is itself amber,
     so hue can carry nothing here: the plain chip was h40, Work h42 and the
     deadline chip h42, all within 1.01-1.04:1 of each other in lightness.
     Four meanings, one beige. Amber therefore has to separate by LIGHTNESS in
     this theme and it now does: 1.31:1 against the plain chip, up from
     1.01:1. Deeper than the light theme's amber on purpose -- it is the same
     step away from its own paper. */
  --chip-deadline-surface: #f5dfa8;
  --chip-deadline-text: #7a4a05;
}

/* J7 — see --page-work: on a phone the column keeps the 8 px it had (96vw left
   7.5 at 375), on a whole pixel. 640 is the app's phone line (main's own
   phone block sits under it). */
@media (max-width: 640px) {
  :root {
    --page-work: min(calc(100% - 2 * var(--space-4)), 1600px);
    --gutter: 20px;
  }
}

/* J7 — the phone one step up (the role table at --fs-row-title). A finger and
   not a width: an iPad held in the hand is a finger at 820, a narrow desktop
   window is still a mouse. */

@media (pointer: coarse) {
  :root {
    --fs-row-title: var(--fs-lg);
    --lh-row-title: var(--lh-lg);
    --fs-meta: var(--fs-md);
    --lh-meta: var(--lh-md);
    --fs-preview: var(--fs-md);
    --lh-preview: var(--lh-md);
  }
}
