/*
	The browser app's whole stylesheet — item `#597`.

	**Colour marks exceptions and never encodes a scale, and nothing is said in colour alone**
	(decision `#102`). That rule was taken for the terminal and it is a product decision rather
	than a terminal one, so it holds here: overdue is red *and* says "overdue" under a heading
	that says so; blocked is marked *and* labelled. A reader who cannot distinguish the hues
	loses nothing.

	**Three families, one per role** (`#2865`, `#2877`). Lexend for headings, the wordmark and an
	item's title wherever it is drawn; Inter for reading text; JetBrains Mono for code. The
	heading face is the one `projects/websites/subsystem-co` uses, and **below it the two part
	company on purpose**: the site sets text in Roboto Serif, and so did this app for a day
	(`#2870`), until Simon had seen three pairings against real content and chose this one.

	**Measured at the sizes this app actually works at**, which are small - `--text-small` is
	fifty of eighty-one declarations and a fenced block is `--text-fine`. Inter has a tall
	x-height, and at its usual 400 it carries as much ink as the serif that read heavy, which is
	why the body is set at 300 (on `body`, below). Its known cost is `l` against `I`, drawn as
	nearly one stroke - and **the places that cost would bite are not set in Inter**, because the
	item number, the priority and a token's prefix all wear the mono.

	**The mono is not only for markdown**: `.ref` wears it, so it draws the item number on every
	row.

	**Served by this instance, never fetched from anywhere** — which is what kept the old rule's
	reason true rather than trading it away: there is no network to fetch a font from on an
	instance behind a VPN, so every face is vendored in `web/vendor` like everything else here
	we did not write, and arrives from `/app/` with the rest of the app. Seven faces, 140 KB,
	fetched once by a reader and then cached.

	**Both themes live in one declaration per colour**, through `light-dark()`, so there is no
	second block that has to agree with the first and no token can be defined in one theme and
	missing from the other. A viewer who has expressed no preference gets whatever their system
	says; `[data-theme]` is the reader saying otherwise (`#908`).

	**Every size names a step** — five for type, nine for spacing (`#906`). Nothing outside them,
	and `tests/test_web.py` refuses a literal rather than this paragraph asking nicely.
*/

/*
	**Every face, in the weights its role actually asks for** (`#2865`, `#2871`, `#2873`,
	`#2877`). Fontsource's Latin subsets, each vendored with its licence and recorded in
	`web/vendored.py`. Lexend 400, 500 and 600; Inter 300, 400, 500 and 600; JetBrains Mono 400
	and 600, the second because `**`code`**` renders `strong > code` and a `th` is 600, so one
	weight alone would be synthesised into a smear.

	**Inter 400 stays although almost nothing declares it** (`#2873`). The body reads at 300 and
	the explicit 400s all sit inside contexts that declare 600, where they are a reduction rather
	than a value. Dropping the file would make a future `font-weight: 400` render 300 or 500 with
	nothing to say so - which is the trap `#2871` was written about, and 23 KB is a cheap price
	for not laying it again.

	**Lexend 500 stays although a row's title no longer asks for it** (`#2871`, `#2879`). It was
	vendored for that title, after CDP showed that with only 400 and 600 present `font-weight:
	500` draws the *400* file, silently. The title is 400 now, but `strong` is 500 by its own
	rule and a writer's bold inside a heading in a description asks for Lexend 500 - so the file
	stays, on the rule that kept Inter 400: a weight something can ask for is shipped, and one
	nothing asks for is never fetched.

	**Relative, so it resolves against this stylesheet** — the app's files are served flat at
	`/app/<name>`, and a bare name cannot be wrong if the app is ever mounted somewhere else.
	The mask below is written the same way for the same reason.

	**`swap`, so the page is readable before a face arrives.** Each stack falls back to the
	machine's own sans or mono, so what changes when one lands is mostly the shape of the letters
	rather than the layout of the page.
*/
@font-face {
	font-family: "Lexend";
	src: url("lexend-latin-400-normal.woff2") format("woff2");
	font-weight: 400;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "Lexend";
	src: url("lexend-latin-500-normal.woff2") format("woff2");
	font-weight: 500;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "Lexend";
	src: url("lexend-latin-600-normal.woff2") format("woff2");
	font-weight: 600;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "Inter";
	src: url("inter-latin-300-normal.woff2") format("woff2");
	font-weight: 300;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "Inter";
	src: url("inter-latin-400-normal.woff2") format("woff2");
	font-weight: 400;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "Inter";
	src: url("inter-latin-500-normal.woff2") format("woff2");
	font-weight: 500;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "Inter";
	src: url("inter-latin-600-normal.woff2") format("woff2");
	font-weight: 600;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "JetBrains Mono";
	src: url("jetbrains-mono-latin-400-normal.woff2") format("woff2");
	font-weight: 400;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: "JetBrains Mono";
	src: url("jetbrains-mono-latin-600-normal.woff2") format("woff2");
	font-weight: 600;
	font-style: normal;
	font-display: swap;
}

:root {
	/* **Both themes in one declaration each** (`#908`). `light-dark()` reads `color-scheme`,
	   so the reader's choice below switches every token at once and dark is not a second block
	   that has to agree with the first — which is this codebase's signature defect, and the
	   shape the old arrangement had: dark redefined ten of the twelve, so a token added to one
	   was silently identical in the other. */
	color-scheme: light dark;

	--bg: light-dark(#ffffff, #16191d);
	--bg-sunken: light-dark(#f6f7f9, #101316);
	--bg-raised: light-dark(#ffffff, #1c2025);
	/* **Half a step up from where it was** (Simon, 2026-09-17, `#2859`): *"the divider line
	   between items in agenda, lists, and journal needs a touch more contrast from the
	   background - especially in dark mode, but I think we should mirror in light mode also"*.
	   One token draws all three, since a row is `.rows li` and its `border-top` is this.

	   **Measured against `--bg-raised`, which is what a row sits on.** It was `#e3e6ea` at
	   1.25:1 and `#2a3037` at 1.23:1 - a line a reader takes on trust rather than sees. It is
	   1.38:1 and 1.40:1 now, and 1.60:1 against the page behind a row in dark.

	   **Half a step, because the whole one is already taken.** `--line-strong` below is the
	   next value on this ramp, and moving `--line` onto it would leave two tokens the same
	   shade - a step named twice is a step gone. A reader who wants more than this asks their
	   system for it, and `prefers-contrast` below takes both lines to about 3:1. */
	--line: light-dark(#d8dce2, #323941);
	--line-strong: light-dark(#cdd3da, #3a424b);
	--ink: light-dark(#1b1f24, #e6e9ed);
	--ink-soft: light-dark(#5c6570, #a2acb6);

	/* **The faintest ink still clears the contrast minimum** (`#902`). Light was `#868e98`,
	   which is 3.31:1 on `--bg` and 3.09:1 on `--bg-sunken` against WCAG AA's 4.5:1 for small
	   text — and it renders `.row .ref`, the item number on every row, at `--text-small`. Dark was
	   `#7d8791`, which passed on two backgrounds and missed `--bg-raised` by 0.02. */
	--ink-faint: light-dark(#696f77, #828c97);

	--accent: light-dark(#2563eb, #7aa2f7);
	--warn: light-dark(#b42318, #f27a70);
	--radius: 6px;

	/* **One token per role, and the only place a family is named** (`#2865`, `#2877`). The
	   three names match what `projects/websites/subsystem-co` calls its own, so the two
	   products describe type in one vocabulary even where they disagree about the faces - as
	   they do below the headings, by Simon's choice, which the top of this file records.

	   **Every stack still ends where it used to.** A face that fails to arrive falls back to
	   the machine's own, which is what `swap` above is for, and it is why these keep naming
	   the system families rather than resting on the vendored file being there. */
	--font-heading: "Lexend", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
	--font-text: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
	--font-code: "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

	/* **The palette a workspace or a project may be marked with** (`#1027`, design `#1023`).
	   Eight names, chosen from `domain/palette.py`, which stores a *name* and never a value —
	   `#102`'s sixteen-ANSI rule means anything appearing on more than one surface has to be a
	   name, and it is what lets the terminal render one later or not at all.

	   **Red and blue are absent and that is not taste**: `--warn` is red and carries overdue and
	   blocked, `--accent` is blue and carries focus and a live claim. A project wearing either
	   would put two meanings on one hue on the same page.

	   **One token per hue, not four.** `#1023` §5 costed `edge`, `fill`, `ink` and `line`; two
	   of those have no reader, and a declared-and-read-by-nothing token is the defect this
	   arc's own registry exists to end. One value serves the accent bar and the project label,
	   and it is held to the *text* floor, which is the stricter of the two uses.

	   **Generated against the floor and then verified, rather than picked.** Every value is the
	   most saturated colour at its hue that still clears 4.6:1 on the hardest background of its
	   theme — because OKLCH lightness is close to but not the same as WCAG relative luminance,
	   so one lightness across eight hues does *not* give eight equal contrasts. Neighbours are
	   separated on the **lightness** axis as well as the hue axis: there is not 45 degrees of
	   hue room between the reserved red and the reserved blue for eight of anything, so the
	   tight pairs are pulled apart with the other axis instead of pretending the room exists.
	   `tests/test_browser.py` holds both properties. */
	--hue-amber: light-dark(#9d6304, #ffb24f);
	--hue-green: light-dark(#047e39, #04be58);
	--hue-teal: light-dark(#055959, #02f6f6);
	--hue-cyan: light-dark(#0779a0, #0aa6da);
	--hue-indigo: light-dark(#3402c9, #8b9bff);
	--hue-violet: light-dark(#9d00ca, #cd46ff);
	--hue-magenta: light-dark(#980363, #ff71bb);
	--hue-slate: light-dark(#5b646f, #808a95);

	/* **The type scale** (`#906`, decided from `#441`). Six steps, and nothing outside them —
	   which is the whole difference between what colour has here and what type had. There were
	   45 `font-size` declarations across eight values and **39 of them sat in three values
	   spanning 2px**, so this is not eight tiers collapsed, it is one cluster of small text
	   given names. `--text-body` was `body`'s own 15px, unchanged and mostly inherited: only one
	   rule ever declared it, and everything else overrode downward.

	   **Six since every heading has had to be larger than the text under it** (`#2679`, Simon,
	   2026-09-15). Five left one step between the body and the page's title, and a stored `##`
	   and `###` - the two levels people write - each need a step above the body and a visible
	   one between them. So the title and a section heading each moved up, and `--text-subsection`
	   stayed as the heading inside a section.

	   **Two pixels larger at every step since `#2774`** (Simon, 2026-09-16): the page read
	   smaller than the sites he reads beside it, and he was zooming this one. Its working size
	   is `--text-small`, fifty declarations of eighty-one, and that was 13px where a browser's
	   own default is 16px. Every step moved together, so no heading changed its place against
	   the text under it, and spacing stayed in pixels. Rendered at 1440px and 390px before it
	   was chosen: nothing overflowed and nothing was clipped. */
	--text-fine: 14px;
	--text-small: 15px;
	--text-body: 17px;
	--text-subsection: 19px;
	--text-section: 21px;
	--text-page: 24px;

	/* **Relative on purpose.** Code inside a 13px row should be 13px-ish and code inside 15px
	   prose 15px-ish. It was the one relative unit among forty-five, which read as the tell that
	   nothing else scaled — it was that, and it is also right. */
	--text-code: 0.88em;

	/* **The spacing scale** (`#906`). A 2px grid rather than the conventional 4px one, and that
	   reverses the research: measured, `4/8/12/16/24` covers 64 of 149 values, so **57% of the
	   page would have had to round**. The two it deletes are `.row { padding: 10px 14px }` and
	   the 14px block margin — the row rhythm and the block rhythm, which decide how much of a
	   list is visible without scrolling. A 4px grid is right for comfortable-whitespace
	   products; the argument here is that the information is the interface.

	   Nine steps is a weak constraint and that is the accepted cost: what it kills is the odd
	   values and the 18/20/22/28 tail, which is where the accidents were. */
	--space-1: 2px;
	--space-2: 4px;
	--space-3: 6px;
	--space-4: 8px;
	--space-5: 10px;
	--space-6: 12px;
	--space-7: 14px;
	--space-8: 16px;
	--space-section: 24px;

	/* **A control has one of three sizes, and nothing else** (`#763`). Measured before deciding:
	   44 rules targeted a control element and they used **13 distinct paddings** — while
	   `border-radius` was one value across fourteen, because `--radius` existed and nothing
	   named a control's size. `#906`'s argument, one level up.

	   A field and a button share their vertical padding so the two line up when they stand side
	   by side, which they do in every form here; a button is wider because a word needs more
	   room either side of it than a cursor does. `--control-tight` is for a control inside
	   something else — a row's action, a banner's dismiss, a listing's order picker. */
	--control-field: var(--space-5) var(--space-6);
	--control-button: var(--space-5) var(--space-8);
	--control-tight: var(--space-2) var(--space-5);

	/* **One focus ring.** It was two: every control used `--accent` and `.adding button` — the
	   primary button on the page — used `--ink`. Nothing decided that; it is what happens when a
	   value is written out per rule. */
	--focus-ring: 2px solid var(--accent);

	/* **The first motion in this stylesheet** (`#763`), and short enough to read as feedback
	   rather than as animation. `prefers-reduced-motion` zeroes it — which `#441` called not
	   optional and which could not honestly be built until there was something to suppress. */
	--ease: 120ms ease;

	/* **The reading measure**, and the one place it is written down (`#863`). It was a literal
	   on `.app` alone, so anything that widened the frame widened everything the frame held. */
	--measure: 1100px;

	/* What the frame leaves for its content, which is what a form has to match in order to be
	   the same width wherever it is opened. Derived rather than a second number: `.app` pads
	   by `--space-8` on both sides and everything here is `border-box`. */
	--measure-inner: calc(var(--measure) - 2 * var(--space-8));
}

/*
	**The reader's choice sits above the machine's** (`#908`, requirement 8 of `#441`).

	Three states. No attribute, or `system`, leaves `color-scheme: light dark` in force and
	`prefers-color-scheme` decides — which is what the page did before and is still the default.
	The other two pin it, which is the whole of what was missing: somebody whose system is dark
	and who wants *this* page light had no way to say so.

	**Stored in the browser rather than on the account**, and that was decided on the merits
	rather than on cost (`#906`): a theme is a property of the reading device and the room it is
	in, so `system` per-device is a better answer than one that follows you between machines. The
	server never renders, so it has no opinion here to contribute — which is why §6.5's timezone
	chain is the wrong precedent to copy, that chain existing because the server *computes with*
	a zone. `#904` is the stored-preferences mechanism, and this is deliberately not its trigger.

	`index.html` sets the attribute before first paint, so a pinned theme does not flash.
*/
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }

/*
	**More contrast when the reader has asked their system for it** (`#908`). `#441` called this
	not optional and it belongs with the tokens rather than later, because it *is* a token
	question — every value here is a step up from one above, and there is nowhere else to put it.

	Text reaches **AAA (7:1)** on the hardest background of its theme, and the two line colours
	reach the 3:1 that non-text needs — measured, not eyeballed. `--ink` and the backgrounds are
	untouched: `--ink` is already 13:1 or better, and moving a background would change what every
	other ratio was computed against.

	**This is a stronger default, never a different design.** Nothing moves position, nothing
	appears or disappears, and no information arrives that was not already there.
*/
@media (prefers-contrast: more) {
	:root {
		--line: light-dark(#8d8f91, #5d6b7a);
		--line-strong: light-dark(#8b8f94, #5e6b7a);
		--ink-soft: light-dark(#4d555e, #a5afba);
		--ink-faint: light-dark(#50545a, #9fabb8);
		--accent: light-dark(#1d4db7, #81acff);
		--warn: light-dark(#a21f16, #ff897d);
	}
}

* { box-sizing: border-box; }

body {
	margin: 0;
	background: var(--bg-sunken);
	color: var(--ink);
	/* **The base is the token, not a number that happens to match it.** `--text-body` is this
	   value, and almost nothing declares it — every other rule overrides downward, which is why
	   the page's working size is `--text-small` and this is what survives on a title and on
	   prose. Two copies of 15px would be two places to change it and one to forget.

	   **300, and the weight is in the shorthand deliberately** (`#2873`, Simon: *"the body text
	   looks a little heavy"*). `font:` resets weight to `normal`, so a separate declaration
	   after it would be a second place to look and one to forget - the same argument the
	   paragraph above makes about the size.

	   **The weight outlived the face it was tuned for, and that was measured rather than
	   assumed** (`#2873`, `#2877`). 300 was set when Roboto Serif at 400 read heavy and crowded
	   the headings. Inter at 400 carries as much ink as that serif did - 3.24 against 3.16 per
	   pixel of line at 17px, on an identical 1.49px stem - and would stand an `h3` at 1.64x its
	   ink, below the 1.68x that prompted the change. At 300 the heading stands at 2.10x. The
	   size is untouched: 16px would sit 1px from `--text-small`, rebuilding the cluster `#906`
	   exists to prevent. */
	font: 300 var(--text-body)/1.55 var(--font-text);
	-webkit-font-smoothing: antialiased;
}

/* **Bold comes down with the body** (`#2873`, `#2877`). `strong` declares nothing, so it
   takes the browser's `bold` and resolves to the vendored 600 - which against a 300 body is
   1.97x its stem (2.17px over 1.10px, Inter at 17px). 500 puts it at 1.66x, which is about
   the relationship bold had to the body before the text was lightened.

   **Three rules keep their 600 on purpose**: `.you .reveal strong`, `.journal .what strong`
   and `.narrowed strong` each set `color: var(--ink)` against faint surroundings, so their
   weight is picking one word out of dimmed micro-copy rather than emphasising a run of
   prose. Different job, and not the one being tuned.

   **`b` is not here, and `tests/test_browser.py` is why.** It was, and the selector reached
   nothing: `markdown.js` renders emphasis as `strong` and escapes everything a writer types,
   so no `b` can exist on a page this app draws. A rule matching nothing changes nothing and
   never errors, which is how a row separator once went missing for eleven days. */
strong { font-weight: 500; }

/* **Five selectors, and only one of them is a code block** (`#2868`, `#2877`). `.ref` is the
   item number on every row and `.credential .prefix` is the part of a token somebody reads
   back, so the mono is where `l`, `I` and `1` have to stay apart - which is also why Inter's
   weakness there does not reach them. */
code, .ref, .priority, .broke .detail, .credential .prefix {
	font-family: var(--font-code);
}

/* **Bold code is drawn bold** (`#2878`). The mono declares no weight of its own and inherits
   the prose's, so once `strong` came down to 500 (`#2873`) bold code asked for 500 - a weight
   JetBrains Mono does not ship - and was drawn in the regular face, the same as plain code.
   600 is the face the mono does ship, and it keeps code one step heavier than the prose
   around it both ways: regular beside light text, semibold beside medium bold.

   **Only this, deliberately.** Pinning the mono to 400 as well, which was the remedy first
   written down for this, would have flattened code in a table head and in a heading: those
   are 600 and 700 by their own rules and are drawn semibold by inheriting them. Measured
   across every context the mono appears in before choosing. */
strong code { font-weight: 600; }

/*
	**A base look for every element the browser would otherwise decide for us** — `#747`, and
	`#740` is what its absence cost the one time it happened.

	This file styles a link in five contexts — `.views a`, `.prose a`, `.comments .body a`,
	`.foot a`, `.narrowed a.widen` — and that arrangement is right: a link inside prose and a
	link replacing a button want opposite things. **The consequence is that a new anchor
	anywhere falls through to the user agent**: browser blue, underlined, identical in both
	themes. Not a mistake somebody makes once; it is what the stylesheet does to every link
	added without a context, and it did it within a day of the arrangement existing.

	**These change nothing visible today**, which is what makes them safe: every element
	currently rendered has a context rule, and a bare element selector loses to all of them. It
	is purely a floor — an element added without a rule is now *plain* rather than *foreign*.

	**`tests/test_browser.py` is what says so rather than this paragraph.** `#747` argued that a
	guard here would have to be a regex over selectors, which is guarding a spelling; that was
	true until there was a CSS engine in the pipeline. There is one now, and it asks a browser
	whether each rendered element looks like something we decided or like something it decided.
*/
a {
	color: var(--accent);
}

button, input, select, textarea {
	font: inherit;
	color: var(--ink);
	background: var(--bg-raised);
	border: 1px solid var(--line-strong);
	border-radius: var(--radius);

	/* **The first motion in this stylesheet** (`#763`), and it is here rather than per control
	   for the reason the sizes are: a value written out per rule is a value that drifts. Only
	   the two properties a control changes on hover or focus, so nothing else can be caught by
	   it — a transition on `all` animates a layout change nobody asked to see move. */
	transition: border-color var(--ease), color var(--ease);
}

/*
	**Motion is a preference the machine already knows** (`#763`, and `#441` called it not
	optional). Zeroed rather than shortened: somebody who asks for reduced motion is not asking
	for faster motion, and `0.01ms` rather than `0` because a zero-length transition fires no
	`transitionend` and a listener waiting for one would wait for ever.

	`!important` because this has to beat any rule that sets its own duration, which is what a
	preference means — and it is the one place in this file where that is the right instrument.
*/
@media (prefers-reduced-motion: reduce) {
	*, *::before, *::after {
		transition-duration: 0.01ms !important;
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		scroll-behavior: auto !important;
	}
}

button { cursor: pointer; }

/* ---- what a button's look means — design `#1045`, `#1046` ---------------- */

/*
	**A role says what pressing it does; a size says where it is.** `#763` settled the second
	and nothing had ever settled the first — so nine different treatments had grown, and no two
	of them agreed: *More*, *Cancel* and *Search* were one look doing three unrelated things,
	while *Save*, *Complete* and *Remove* were three looks all committing a change. **Complete
	and Show more were byte-identical**, one changing an item and one loading rows.

	Every button carries exactly one of these six classes. Anything that draws its own fill
	instead is what `test_every_button_wears_one_of_the_six_roles` refuses.

	The four rules, and each answers something a reader could not work out:

	1. **Anything that changes an item draws itself a box** — `.action`. *Complete* stopped
	   being grey text that reads as a label saying the item is complete.
	2. **Anything that changes nothing has no box** — `.quiet`. *Cancel* stopped looking like
	   *More*, in the same position, doing the opposite.
	3. **The accent fill is spent only on committing** — `.primary`, one per form. *Preview*
	   wrote nothing and wore *Save*'s fill.
	4. **A reveal says so in a glyph** — `.reveal`. `aria-expanded` was already on both such
	   controls and nothing drew it.
*/

.primary, .action, .quiet, .reveal, .segment {
	padding: var(--control-button);
	border: 1px solid transparent;
	border-radius: var(--radius);
	font: inherit;
	cursor: pointer;
}

/* **Commits the thing this form exists to make**, and there is one per form. Creating nothing
   is what keeps a submit out of the accent — Simon's answer of 2026-08-20 about `Search`,
   which is a form's submit and an `.action`. */
.primary {
	background: var(--accent);
	border-color: transparent;
	color: var(--bg);
	font-weight: 500;
}

/* **Changes an item, without being what this form is for.** A box, because that is the whole
   of *is this a button*; no fill, because the fill is spent. */
.action {
	background: transparent;
	border-color: var(--line-strong);
	color: var(--ink-soft);
}

.action:hover:not(:disabled) { color: var(--ink); border-color: var(--ink-soft); }

/* **A glyph beside the word, never instead of it** (`#102`). `Complete` was the control Simon
   read as a label saying the item *is* complete; a box says it is pressable and a tick says
   what pressing it does. */
.action .icon { width: 1em; height: 1em; margin-inline-end: var(--space-3); }
.action { display: inline-flex; align-items: center; }

/* **Changes nothing** — abandons, dismisses, goes back. No box at all, which is what says so
   before the word is read. */
.quiet {
	background: transparent;
	border-color: transparent;
	color: var(--ink-faint);
}

.quiet:hover:not(:disabled) { color: var(--ink); }

/* **Shows or hides more of what is already here.** Quiet, plus a caret that turns — the state
   is in `aria-expanded` either way, and this is what draws it. */
.reveal {
	display: inline-flex;
	align-items: center;
	gap: var(--space-3);
	background: transparent;
	border-color: transparent;
	color: var(--ink-faint);
}

.reveal:hover:not(:disabled) { color: var(--ink); }
.reveal .icon { width: 1em; height: 1em; }
.reveal[aria-expanded="true"] .icon { transform: rotate(180deg); }

/*
	**`.toggle` is the group and `.segment` is the control**, which is why a segment carries
	the role rather than the wrapper: a role is what pressing *a button* does, and a `<div>` is
	not pressed.

	**Chooses between named states, and both are always visible** — Simon, 2026-08-20. It was
	one button whose label was the *other* state, which is ambiguous twice over: a button
	reading *Preview* cannot say whether that is what you are looking at or what pressing it
	gives you, and it wore the accent fill of a control that writes.

	**Marked with a fill *and* the word** (`#102`): both labels are readable at every moment and
	the mark is never the only thing carrying which is which.

	**Grey rather than the ink, for now** — his answer, and provisional in his own words. It is
	one token read in one place so that changing it is one line.
*/
.toggle {
	display: inline-flex;
	border: 1px solid var(--line-strong);
	border-radius: var(--radius);
	overflow: hidden;
}

.segment {
	padding: var(--control-tight);
	border: 0;
	border-radius: 0;
	background: transparent;
	color: var(--ink-faint);
	font-size: var(--text-small);
}

.segment[aria-pressed="true"] {
	background: var(--bg-sunken);
	color: var(--ink);
	font-weight: 500;
}

.segment:hover:not([aria-pressed="true"]) { color: var(--ink); }

/* **Lives in prose or a nav bar and must never become a box** — signing out, and a row with no
   address to be an anchor. It reads as the link it stands in for (`#927`'s M-26). */
.inline {
	padding: 0;
	background: transparent;
	border: 0;
	color: var(--accent);
	font: inherit;
	cursor: pointer;
}

.inline:hover { text-decoration: underline; }

.primary:disabled, .action:disabled, .quiet:disabled,
.reveal:disabled, .segment:disabled { opacity: 0.5; cursor: default; }

.primary:focus-visible, .action:focus-visible, .quiet:focus-visible,
.reveal:focus-visible, .segment:focus-visible, .inline:focus-visible {
	outline: var(--focus-ring);
	outline-offset: 1px;
}

/* **Tight where the control sits on a row or inside a card**, and `.doing` is deliberately
   absent: an open item's Complete and Edit are *standing* actions with nothing competing for
   the space, which `#763` chose at the button size and this does not reopen. That is `#763`'s
   size
   vocabulary rather than a role: a role says what pressing it does and a size says where it
   is, and the two are orthogonal. */
.rows .action, .rows .quiet, .card .action, .card .quiet,
.meta .action, .meta .quiet, .note .action, .note .quiet,
.narrowed .action, .cut .action, .linked .action, .place .action {
	padding: var(--control-tight);
	font-size: var(--text-small);
}


button:disabled, input:disabled, select:disabled, textarea:disabled { opacity: 0.5; }

/* **The border goes and the padding with it**, which is the one place a base rule is a
   removal rather than a look. A fieldset's user-agent frame is a groove nobody in this design
   asked for, and both of ours already draw their own boundary — so this makes the element
   neutral rather than restyling it. */
fieldset {
	margin: 0;
	padding: 0;
	border: 0;
}

/* ---- frame ------------------------------------------------------------- */

.app {
	max-width: var(--measure);
	margin: 0 auto;
	padding: var(--space-8);
}

/* **The board is the one view that wants the screen** (`#846`). 1100px is a reading measure —
   right for a list, a document and a form, and wrong for a board, where the useful thing is how
   many columns you can see at once. On a wide display it was hiding three columns of seven
   behind a scrollbar at the bottom of a three-thousand-pixel page.

   No second cap here, deliberately. A number would be somebody's screen written into the
   stylesheet; the column's own `max-width` is what stops the columns spreading, and past that
   the space is simply left.

   **This is on the frame, so it widens everything the frame holds** — which the sentence above
   says is wrong for a form, and which is what `#863` was. Anything standing beside the board
   that wants the measure has to say so; `.adding` does. Read that as the cost of putting the
   class here rather than on the board itself: the alternative needs the board to break out of
   a centred container, which is a larger change and a fiddlier one. */
.app.wide {
	max-width: none;
}

/*
	**The masthead: where you are, what a place offers, and who you are, in three zones that stay
	put** - `#2599`, Simon's decision of 2026-09-14.

	**A grid, so each zone keeps its track whether or not the others are drawn.** It was one flex
	row spreading up to four groups apart, so everything moved when a group came or went: the
	reader's name and *Sign out* sat at the right-hand edge on `/` and 706px further left on every
	workspace, at 1440px wide.

	**Outside the frame, and as wide as the screen on every page**, so a board's width moves
	nothing in it. Its side margins are the frame's own padding, which keeps the rule beneath it
	level with a board's columns and a list's rows alike, and puts the menu the same distance from
	the edge everywhere.
*/
.top {
	/* **The height of one line of the masthead, which is the search box's**: a field's line, its
	   padding and its border. The wordmark and the menu are centred in a line this tall on every
	   page, whether or not the page has a search box, so neither moves between `/` and a place. */
	--masthead-line: calc(var(--text-body) * 1.55 + 2 * var(--space-5) + 2px);

	display: grid;
	grid-template-columns: auto minmax(0, 1fr) auto;
	align-items: start;
	gap: var(--space-4) var(--space-8);
	margin: 0 var(--space-8);
	padding: var(--space-8) 0 var(--space-6);
	border-bottom: 1px solid var(--line);
}

/* Where you are: the wordmark, which goes home, and the dropdown naming the place. */
.where {
	grid-column: 1;
	grid-row: 1;
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-2) var(--space-8);
	min-height: var(--masthead-line);
	min-width: 0;
}

/* **Capped, so the search and the tabs keep the first row** - `#3734`. A select is as wide as
   its longest option, and a place's is a project title indented under its parent. With this
   instance's names and a development build in the wordmark, a workspace's tabs took a second
   row below 1350 pixels; with this cap and the search box free to shrink (`.seeking`), one row
   holds down to about 1150. The closed control shows the place, which the heading under the
   masthead names in full, and the open list shows every option whole. */
.where select {
	max-width: 16em;
	font-size: var(--text-small);
}

/* What only a place has: its search and its views. Absent at `/`, which is nowhere in
   particular, and its track stays empty rather than letting the menu slide left. */
.within {
	grid-column: 2;
	grid-row: 1;
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-4) var(--space-8);
	min-width: 0;
}

/* Who you are, at the right-hand end whatever else is drawn. */
.you {
	grid-column: 3;
	grid-row: 1;
	justify-self: end;
	display: flex;
	align-items: center;
	min-height: var(--masthead-line);
}

/*
	**Narrower than a tablet held sideways, a place's controls take a row of their own** and the
	menu stays on the first, beside the wordmark. Measured on 2026-09-14 in Chromium against the
	browser suite's fixture: the search box, the four views, the wordmark with its dropdown and
	the menu stop fitting on one line between 941 and 950px. The dropdown is as wide as the
	longest place name it offers, so a real instance needs more than the fixture did, and 1024px
	leaves room for that. A name longer still wraps the controls inside their own zone, which
	leaves the wordmark and the menu where they are.

	**The second row is placed rather than declared**, so a page with no place's controls has no
	row and no gap for one.
*/
@media (max-width: 1023px) {
	.top { grid-template-columns: minmax(0, 1fr) auto; }

	.you { grid-column: 2; }

	.within {
		grid-column: 1 / -1;
		grid-row: 2;
	}
}

/* **Every heading, and the wordmark with them** (`#2865`). One rule rather than a family on
   each: a heading added later is in the branding's font without anybody remembering, which is
   what a token is for. Nothing else moves - a row's title, a chip and a button are text. */
h1, h2, h3, h4, h5, h6 { font-family: var(--font-heading); }

/* **The product's name kept its step** when section headings moved up one (`#2679`). It names
   what the page belongs to rather than heading a part of it. */
.top h1 {
	font-size: var(--text-subsection);
	font-weight: 600;
	margin: 0;
	letter-spacing: -0.01em;
	display: flex;
	align-items: baseline;
	gap: var(--space-3);
}

/*
	**The mark, painted rather than loaded as a picture** (`#1536`).

	`mask` takes the shape out of the exported file and `background: currentColor` fills it
	with whatever colour the heading resolved to — so it is right in all three theme states
	with no theme rule of its own. That matters more than it sounds: `#908` gives the reader
	`system`, pinned light and pinned dark, and anything keyed on `prefers-color-scheme`
	alone gets the two pinned states wrong, which is where the obvious approach fails.

	**One asset, and it is drawn from the vendored mark** (`#2864`). `favicon.svg` is the mark
	in black on transparent and `favicon-inverted.svg` is its white twin; both are in the tree
	and neither is referenced here, because painting one is what makes the other unnecessary.
	Inlining the path data in `app.js` was the alternative and is refused: it would put a second
	copy of the drawing in the repository, where `web/vendor/waypoints.svg` is the one place the
	shape lives and `scripts/marks.py` is what makes every file from it.

	**Relative, so it resolves against this stylesheet** rather than against a mount point.
	Assets are served at `/app/<name>` today and this is the first `url()` in the file, so
	there was no precedent to follow; a bare name cannot be wrong if the app is ever mounted
	somewhere else.

	**Decorative, and it must stay that way.** `#102` forbids anything being said in a shape
	alone — the word *Subroutine* is beside it and carries the whole meaning, which is what
	lets this be a `::before` with no text alternative at all.
*/
.top h1::before {
	content: "";
	flex: none;
	align-self: center;
	width: 1.05em;
	height: 1.05em;
	background: currentColor;
	-webkit-mask: url("favicon.svg") center / contain no-repeat;
	mask: url("favicon.svg") center / contain no-repeat;
}

/*
	The build, beside the name rather than under the fold (`#1536`). Quieter than the wordmark
	because it is what you look up rather than what you read: `#102`'s family, where the
	exception is what is drawn and the ordinary case recedes.
*/
.top h1 .build {
	font-size: var(--text-small);
	font-weight: 400;
	letter-spacing: 0;
	color: var(--ink-soft);
}

/*
	**The reader's menu** - `#2599`. A `.reveal`, because pressing it shows more of what is already
	here and its caret turns with `aria-expanded`; tight, because it sits in the masthead; and the
	name in the reader's ink, because it is the one word on it.
*/
.you .reveal {
	padding: var(--control-tight);
	font-size: var(--text-small);
}

.you .reveal strong { font-weight: 600; color: var(--ink); }

/*
	**The browser's own popover, placed under the name that opened it.** Anchored where the
	browser can, which puts it under the button however tall the masthead has grown; at the top
	right of the page where it cannot, which is where that button always is now that the masthead
	spans the screen.

	**`display` is set only while it is open**, because a rule of ours beats the user agent's
	however specific theirs is: a bare `display: flex` here would draw the menu for good.
*/
.you-menu {
	inset: auto;
	margin: 0;
	padding: var(--space-3) 0;
	min-width: 12rem;
	background: var(--bg-raised);
	color: var(--ink);
	border: 1px solid var(--line-strong);
	border-radius: var(--radius);
	font-size: var(--text-small);
}

.you-menu:popover-open {
	display: flex;
	flex-direction: column;
}

@supports (anchor-name: --you) {
	.you .reveal { anchor-name: --you; }

	.you-menu {
		position-anchor: --you;
		top: anchor(bottom);
		right: anchor(right);
		margin-top: var(--space-2);
	}
}

@supports not (anchor-name: --you) {
	.you-menu {
		position: absolute;
		top: 3.5rem;
		right: var(--space-8);
	}
}

/* Each entry is a row of the menu, and the one button reads as the links beside it do. */
.you-menu > a,
.you-menu > .inline {
	display: block;
	padding: var(--space-3) var(--space-7);
	color: var(--ink);
	text-align: start;
	text-decoration: none;
}

.you-menu > a:hover,
.you-menu > .inline:hover {
	background: var(--bg-sunken);
	text-decoration: none;
}

.you-menu > a:focus-visible,
.you-menu > .inline:focus-visible {
	outline: var(--focus-ring);
	outline-offset: -2px;
}

/* The theme, which is a choice rather than a destination, ruled off from the entries either
   side of it. */
.you-menu .theme {
	justify-content: space-between;
	gap: var(--space-6);
	margin: var(--space-3) 0;
	padding: var(--space-3) var(--space-7);
	border-block: 1px solid var(--line);
	color: var(--ink-soft);
}

/*
	**Where a page is, named above its content** - `#2599`. A section heading's step rather than
	a page's, for `#1818`'s rule and the settings area's reason: the masthead is the page's name,
	and this says which part of it you are in.

	**The steps above this one are quieter than this one**, which is what a trail is for: the eye
	lands on where you are, and what is above it is there to be followed.
*/
.place {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--space-4) var(--space-8);
	margin-bottom: var(--space-7);
}

/* **The place's own controls: the saved views, Prioritise and its settings** - `#3734`. Beside
   the trail when they fit; when the trail is long they move below it as one group at the right,
   rather than squeezing into whatever width it leaves. */
.place .here {
	margin-left: auto;
	display: flex;
	flex-wrap: wrap;
	justify-content: flex-end;
	align-items: baseline;
	gap: var(--space-4) var(--space-6);
}

.place .trail {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--space-2) var(--space-4);
	margin: 0;
	font-size: var(--text-section);
	font-weight: 600;
}

.place .trail a { color: var(--ink-soft); text-decoration: none; }
.place .trail a:hover { color: var(--ink); text-decoration: underline; }
.place .trail a:focus-visible { outline: var(--focus-ring); outline-offset: 1px; }
.place .step { color: var(--ink-faint); font-weight: 400; }

/* **The way to a place's settings, an anchor drawn as the control it is** - `.narrowed a.widen`'s
   treatment, since both are a way somewhere offered beside a statement about the page. */
.place-settings {
	display: inline-flex;
	align-items: center;
	gap: var(--space-3);
	padding: var(--control-tight);
	border: 1px solid var(--line-strong);
	border-radius: var(--radius);
	color: var(--ink-soft);
	font-size: var(--text-small);
	text-decoration: none;
}

.place-settings:hover { color: var(--ink); border-color: var(--ink-soft); }
.place-settings:focus-visible { outline: var(--focus-ring); outline-offset: 1px; }

/*
	**The views saved here, a menu in the place's heading** - `#3096`, `#3734` for where and in what
	shape, and `#3751` for the menu: a row of buttons grew with every view saved, and a select
	applied a view on every arrow key. The heading's gap spaces it from the other controls, so it
	carries no margin.
*/
.saved-views {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--space-3) var(--space-4);
	margin: 0;
	font-size: var(--text-small);
}

.saved-views-label {
	color: var(--ink-faint);
	font-weight: 600;
}

/* **The button naming the view that is showing** - `#3751`. Capped as the select before it was
   (`#3734`), since a view's name is the reader's own words and a long one would take the heading
   onto a second line; the name gives way, and the caret stays. */
.saved-views-pick {
	display: inline-flex;
	align-items: center;
	gap: var(--space-2);
	max-width: 16em;
}

.saved-views-current {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.saved-view-asking {
	display: inline-flex;
	align-items: baseline;
	gap: var(--space-2);
	color: var(--ink-soft);
}

.saved-views-form {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--space-3) var(--space-4);
}

.saved-views-form label {
	display: inline-flex;
	align-items: baseline;
	gap: var(--space-2);
	color: var(--ink-soft);
}

.saved-views-shared { white-space: nowrap; }

/*
	**Save this view's form, in a panel under its button** - `#3734`. The browser's own popover,
	as the menu under the reader's name is (`#1848`), so opening it moves nothing on the page.
	Where anchoring is not supported the browser centres it, which is the popover's own default.
*/
.saved-views-panel {
	max-width: min(32rem, calc(100vw - 2rem));
	padding: var(--space-6) var(--space-7);
	background: var(--bg-raised);
	color: var(--ink);
	border: 1px solid var(--line-strong);
	border-radius: var(--radius);
	font-size: var(--text-small);
}

@supports (anchor-name: --saving) {
	.saved-views-save { anchor-name: --saving; }

	.saved-views-panel {
		inset: auto;
		margin: var(--space-2) 0 0;
		position-anchor: --saving;
		top: anchor(bottom);
		right: anchor(right);
		position-try-fallbacks: --saving-within-the-screen;
	}

	/*
		**Inside the screen, where under the button would leave it** (`#3752`). With its right edge
		under the button's, the panel opened 110 px off the left of every screen narrower than
		384 px, so the browser is given this place to try when that one does not fit.
	*/
	@position-try --saving-within-the-screen {
		right: auto;
		left: 1rem;
	}
}

/*
	**The views as a menu of links, in a panel under the button naming the one showing** -
	`#3751`. The browser's own popover, laid out as the menu under the reader's name is (`#1848`):
	a row per entry, each a link. **`display` is set only while it is open**, for that menu's
	reason: a rule of ours beats the user agent's, and a bare `display: flex` would draw it for good.
*/
.saved-views-menu {
	max-width: min(24rem, calc(100vw - 2rem));
	min-width: 12rem;
	/* Held to the space under its button as it opens (`#3942`, `fitBelow`), so it scrolls. */
	overflow-y: auto;
	padding: var(--space-3) 0;
	background: var(--bg-raised);
	color: var(--ink);
	border: 1px solid var(--line-strong);
	border-radius: var(--radius);
	font-size: var(--text-small);
}

.saved-views-menu:popover-open {
	display: flex;
	flex-direction: column;
}

.saved-views-menu a {
	display: block;
	padding: var(--space-3) var(--space-7);
	color: var(--ink);
	text-decoration: none;
}

.saved-views-menu a:hover {
	background: var(--bg-sunken);
	text-decoration: none;
}

.saved-views-menu a:focus-visible {
	outline: var(--focus-ring);
	outline-offset: -2px;
}

/* **The view that is showing, in weight as well as in `aria-current`** - never a colour alone
   (`#102`). */
.saved-views-menu a[aria-current="true"] { font-weight: 600; }

/* *None* is the way out rather than a view, so it is ruled off from the views below it. */
.saved-views-none { border-block-end: 1px solid var(--line); }

/* An entry is its link, with the view's *Forget* or the question it asks beside it. */
.saved-view {
	display: flex;
	align-items: baseline;
	gap: var(--space-3);
	padding-inline-end: var(--space-4);
}

.saved-view a { flex: 1; }

@supports (anchor-name: --picking) {
	.saved-views-pick { anchor-name: --picking; }

	.saved-views-menu {
		inset: auto;
		margin: var(--space-2) 0 0;
		position-anchor: --picking;
		top: anchor(bottom);
		right: anchor(right);
		position-try-fallbacks: --picking-within-the-screen;
	}

	/* **Inside the screen, where under the button would leave it**, for `#3752`'s reason. */
	@position-try --picking-within-the-screen {
		right: auto;
		left: 1rem;
	}
}

/*
	**What a view cannot keep, on a line of its own and in the flow** - `#3096`.

	Never a colour alone and never a modal: it is a statement about what saving will do, read
	before the reader commits to it, so it sits inside the form it qualifies.
*/
.saved-views-unkept {
	flex-basis: 100%;
	margin: 0;
	color: var(--ink-faint);
}


/* ---- adding, and what just happened ------------------------------------ */

/*
	**The one line is the row; everything else is disclosed below it** (`#756`, §1.4). The box
	keeps its size, its place and its primacy whether or not the rest is open — a form that
	pushed it aside would be the replacement §1.4 forbids rather than the disclosure it is.
*/
/*
	**One appearance for every control a reader types into** — `#1855`, the first of `#1848`'s
	primitives and the one the measurement asked for.

	Four screens each wrote this out: `.adding`, `.seeking`, `.linking` and `.saying`, and the
	same four declarations appeared in all of them — the border and padding, the focus ring, the
	placeholder colour and the disabled treatment. `#1445`'s three settings scopes would have
	made it seven. Four idioms for four screens is one per screen, which is the definition of
	not scaling.

	**And they had already drifted, which is what makes this a defect rather than tidiness.**
	`.adding :disabled` said `opacity: 0.5` where `.seeking` and `.linking` said
	`opacity: 0.5; cursor: default`, so a disabled control in the capture box showed a text
	cursor and the identical control in the search box showed an arrow. Nobody chose that; it is
	what four copies do.

	**`font: inherit` on all three** because a browser's defaults for an input, a select and a
	textarea are three different fonts at three different sizes.

	**A class rather than an element selector**, so a control that wants to look like something
	else — a bare checkbox, a range — is not fighting a rule it never asked for. `#906`'s tokens
	are unchanged: this composes them rather than replacing any.
*/
.field {
	min-width: 0;
	padding: var(--control-field);
	background: var(--bg-raised);
	border: 1px solid var(--line-strong);
	border-radius: var(--radius);
	color: var(--ink);
	font: inherit;
}

textarea.field { resize: vertical; }

.field:focus-visible { outline: var(--focus-ring); outline-offset: -1px; }

.field::placeholder { color: var(--ink-faint); }

/* **`cursor` only**, because `button:disabled, input:disabled, …` above already dims every
   control on the page — the four copies of `opacity: 0.5` this replaces were all restating a
   rule they had inherited. */
.field:disabled { cursor: default; }

/* **A form keeps the reading measure wherever it is opened** (`#863`). `#846`'s own comment
   says 1100px is "right for a list, a document and a form, and wrong for a board" — and then
   put `wide` on the *frame*, so the board's width reached the form standing above it. The
   fields were laid out with `auto-fit`, so the column count was derived from that width and a
   field landed somewhere different depending on which view you had opened the box from. A
   date and its time are siblings in one cell, so they wrapped differently too. **The count is
   declared now** (`#800`), which fixes the half this could not: the cap is a *maximum*, so it
   never bound inside `.detail`, where the edit form was already narrower.

   Capped here rather than by re-capping the frame, because the board genuinely wants the
   screen and that is the one thing `#846` got right. */
.adding {
	max-width: var(--measure-inner);
	margin-bottom: var(--space-7);
}

.adding .line {
	display: flex;
	gap: var(--space-4);
}

.adding .line input { flex: 1 1 auto; }

/* Layout only; the look is `.primary` (design `#1045`). */
.adding button { flex: 0 0 auto; }




/* **The column count is declared, not derived from the width available** (`#800`). `auto-fit`
   asks the *container*, and the two forms have different containers: the add form is the frame's
   own inner width, and the edit form sits inside `.detail`, which spends 24px of padding and a
   1px border on each side. 50px, which at `minmax(190px, 1fr)` is a whole column — measured at
   five against four, which is exactly the table Simon put on `#800`.

   **The sentence above this used to say the count was stable because `.adding` carries its own
   measure.** True of the add form, false of the edit form, and it is the very shape `#863`
   warned about one line further down: a description that stops the next reader checking.
   `--measure-inner` is a *maximum*, so it binds where there is room to spare and does nothing
   where the container is already narrower.

   **Four is derived rather than chosen.** A column holds 190px, gaps are 12px, and the narrower
   of the two containers is the viewport less 110px, so N columns need `202N - 12 <= width - 110`
   — 906px for four and 1108px for five, against a frame that caps at 1100. Four is the most the
   two forms can ever share, and it is also the better grouping: at five, importance and urgency
   fall on different rows, and they are the one pair that has to be read together. */
.adding {
	--form-columns: 1;
}

@media (min-width: 502px) { .adding { --form-columns: 2; } }
@media (min-width: 704px) { .adding { --form-columns: 3; } }
@media (min-width: 906px) { .adding { --form-columns: 4; } }

.adding .details {
	display: grid;
	grid-template-columns: repeat(var(--form-columns), minmax(0, 1fr));
	gap: var(--space-5) var(--space-6);
	margin-top: var(--space-6);
	padding: var(--space-7);
	background: var(--bg-raised);
	border: 1px solid var(--line);
	border-radius: var(--radius);
}

.adding .details legend {
	padding: 0 var(--space-3);
	color: var(--ink-soft);
	font-size: var(--text-small);
}

.adding .details label {
	display: flex;
	flex-direction: column;
	gap: var(--space-2);
	min-width: 0;
}

.adding .details label span {
	color: var(--ink-soft);
	font-size: var(--text-small);
}

/* The three dates need a sentence each and the rest do not — `SR#769`. Quieter than the label,
   because it is read once and then never again. */
.adding .details small {
	color: var(--ink-faint);
	font-size: var(--text-fine);
	line-height: 1.35;
}

.adding .details .wide { grid-column: 1 / -1; }

/* How often something comes round — `SR#94`, and a disclosure inside a disclosure.

   **It carries the field grid rather than inheriting it.** `.details` is the grid and a
   `<details>` inside it is one cell, so its own controls would stack in a single column
   however wide the page was. Repeating the declaration here is what makes the phrase, the
   anchor and the preview lay out like every other field beside them.

   `list-style: none` and the marker rule are what let the summary read as a control rather
   than as a bullet — Chromium draws a triangle from `::-webkit-details-marker` and every other
   engine from `::marker`, so both are named or it is styled in one browser. */
.adding .details .repeats {
	border: 1px solid var(--line);
	border-radius: var(--radius);
	padding: var(--space-5) var(--space-6);
	background: var(--bg);
}

/* **The grid is on a wrapper inside, not on the `<details>` itself**, and that was measured
   rather than chosen. Chromium lays a `<details>`'s content out in an anonymous slot box, so
   `display: grid` here made grid items of the summary and that box alone — every field came
   out 219px wide inside a 938px row, stacked down the left, with the select clipped and the
   preview wrapped. `::details-content` addresses it directly and is too new to rely on. */
/* The same declared count as the grid it sits inside, and for the same reason: this fieldset
   spans `.details`, so its own width is the container's too, and `auto-fit` here would put the
   repeat fields on a different rhythm from the fields above them in one of the two forms. */
.adding .details .repeats .fields {
	display: grid;
	grid-template-columns: repeat(var(--form-columns), minmax(0, 1fr));
	gap: var(--space-5) var(--space-6);
	margin-top: var(--space-5);
}

.adding .details .repeats summary {
	color: var(--ink-soft);
	font-size: var(--text-small);
	cursor: pointer;
	list-style: none;
}

.adding .details .repeats summary::-webkit-details-marker { display: none; }
.adding .details .repeats summary::marker { content: ""; }

/* The affordance the marker rule takes away, put back as a word rather than as a glyph — it
   has to say which way it will go, and `Repeats ▾` reads as a state where a bare triangle
   reads as decoration. */
.adding .details .repeats summary::after {
	content: " ▸";
	color: var(--ink-faint);
}

.adding .details .repeats[open] summary::after { content: " ▾"; }

/* What the server made of the phrase — read while typing, so it is quiet by default and only
   the refusal takes a colour. `SR#102`: the colour marks an exception and the sentence carries
   the information, so nothing here exists only as a hue. */
.adding .details .reading {
	grid-column: 1 / -1;
	margin: 0;
	color: var(--ink-soft);
	font-size: var(--text-small);
	line-height: 1.4;
}

.adding .details .reading.bad { color: var(--warn); }

.adding .details .reading .next {
	display: block;
	color: var(--ink-faint);
	font-size: var(--text-fine);
}

/* Editing an item is the same form with the title in place of the capture line — `SR#757`. */
.adding.editing { margin: 0 0 var(--space-7); }

/* Layout only; the look and the size are `.action` inside `.doing`'s sibling — a fourth
   padding was what this had, and `#763` allows three. */
.edit { margin-bottom: var(--space-6); }

.edit:hover { color: var(--ink); border-color: var(--ink-soft); }
.edit:disabled { opacity: 0.5; cursor: default; }

/* Finding something — `SR#775`. In the header beside the switcher, because it is about which
   rows there are rather than about the item under the cursor. */
.seeking {
	display: flex;
	gap: var(--space-4);
	flex: 1 1 220px;
	/* **Free to come down to its own basis** - `#3735`. A flex item is never narrower than its
	   content unless it is told it may be, and this one's content is an input and a button about
	   330 pixels wide together: so in the masthead's wrapping row the box never came down to the
	   220 above, and the tabs beside it took a second row first. */
	min-width: 0;
	max-width: 420px;
}

/* Layout only; the appearance is `.field` (`#1855`). */
.seeking input { flex: 1 1 auto; }

/* Layout only; the look is `.action`. */
.seeking button { flex: 0 0 auto; }

.seeking :disabled { opacity: 0.5; cursor: default; }

/* Which kind the disclosed form is about to write — `SR#761`. Above the fields it changes,
   because it decides what they are. */
.adding .kind {
	margin-top: var(--space-6);
}

.adding .kind label {
	display: flex;
	align-items: center;
	gap: var(--space-4);
}

.adding .kind span {
	color: var(--ink-soft);
	font-size: var(--text-small);
}

/* Joining two items — `SR#760`. A row, because a type and a ref are one thought. */
.linking {
	display: flex;
	gap: var(--space-4);
	margin-top: var(--space-5);
	flex-wrap: wrap;
}

/* Layout only; the appearance is `.field` (`#1855`). A ref is short, so this one control is
   sized to what it holds rather than to the row. */
.linking input { width: 7em; }


.linking :disabled { opacity: 0.5; cursor: default; }

/*
	A closed item, struck through — Simon, 2026-08-17 (`SR#970`).

	**This was a `done` chip beside the title** (`SR#658`), which was the whole answer while a
	link line said nothing else. It is not any more: a closed item's status is not its default,
	so `marks` draws a `Done` or a `Cancelled` chip on the same line — and that chip is what
	keeps `SR#102` satisfied, because nothing here is said in styling alone. It also says which
	of the two, where the chip said `done` about both.

	**Both decorations on hover, not one instead of the other.** `.linked a:hover` underlines,
	and a shorthand would replace the line-through — so an item would stop reading as closed for
	exactly as long as the pointer was over it.
*/
.linked .over { color: var(--ink-faint); text-decoration: line-through; }
.linked a.over:hover, .linked button.over:hover { text-decoration: underline line-through; }

/* Which relationship this is, before the item it points at. Quiet, because it is the same word
   down most of the column and the item is what a reader is scanning for. */
.linked .label { color: var(--ink-soft); }

/* Layout only; the look is `.action`. **It was underlined text with no box**, which is
   what a control that changes nothing wears — and unlinking changes the item. */
.linked .unlink { margin-left: var(--space-4); }

/* **Warns on hover and never at rest** (`#102`): the colour is a reaction to the pointer
   rather than information only a sighted reader gets. */
.linked .unlink:hover:not(:disabled) { color: var(--warn); border-color: var(--warn); }

/* Writing down what happened — `SR#759`. Wider than the add box because prose is what goes in
   it, and stacked because the button has nothing to sit beside. */
.saying {
	display: flex;
	flex-direction: column;
	gap: var(--space-4);
	align-items: flex-start;
	margin-top: var(--space-6);
}

/* Layout only; the appearance is `.field` (`#1855`). */
.saying textarea { width: 100%; }



/* **A 409 is news, not a failure** — nothing was written and nothing typed was lost, so this
   is marked the way an exception is marked and says the whole of it in words (`SR#102`). */
.conflict {
	margin: var(--space-6) 0;
	padding: var(--space-5) var(--space-6);
	border: 1px solid var(--warn);
	border-radius: var(--radius);
	line-height: 1.5;
}

/* **A question wears the same look as news, because it is the same kind of interruption**
   (`SR#1253`, decision `SR#1249` §6). What it adds is somewhere to answer: two buttons on a
   line of their own, so neither is read as a caption on the sentence above them. */
.asking .line {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-5);
	margin-top: var(--space-5);
}

/*
	What just happened. **The tone is a colour and says nothing on its own** (`#102`): the
	sentence carries the outcome, so the border is a way to spot it rather than to read it.
*/
/* The total beneath a parent - `#1356`. A line under the heading rather than inside it,
   because the count in the heading is of the direct children and this is of the whole
   subtree: two denominators in one parenthesis read as one number. Soft and small, because
   it is context for the list below it and not a heading of its own. */
.beneath {
	margin: 0 0 var(--space-6);
	color: var(--ink-soft);
	font-size: var(--text-small);
}

.note {
	display: flex;
	align-items: baseline;
	gap: var(--space-5);
	margin-bottom: var(--space-7);
	padding: var(--space-5) var(--space-6);
	background: var(--bg-raised);
	border: 1px solid var(--line-strong);
	border-left-width: 3px;
	border-radius: var(--radius);
	font-size: var(--text-body);
}

.note.bad { border-left-color: var(--warn); }
.note.good { border-left-color: var(--accent); }
.note .said { flex: 1 1 auto; }

/* Layout only; the look is `.action` or `.quiet`. */
.note button { flex: 0 0 auto; }

.note .dismiss { border-color: transparent; font-size: var(--text-body); line-height: 1; }

/* ---- the board (`#653`) ------------------------------------------------- */

/* Columns scroll sideways rather than squeezing: four columns at a phone's width would make
   every title a vertical stack of single words, and a board whose cards cannot be read is not
   a board. */
/*
	**Columns are the same height, and that is what makes a drop work** (`#796`).

	This was `align-items: flex-start`, so each column was exactly as tall as its contents —
	measured with six cards in *To do* and none in *In progress*: 307px against 78px. The drop
	handler is on the column, so dragging a card sideways from anywhere below those 78 pixels
	put the pointer over nothing at all. **The commonest drag on a board is a full column to an
	empty one**, and it worked in the top quarter of the screen.

	`stretch` is the default and is all that was needed. `min-height` is for the other case the
	first version could not do either: a board where *every* column is empty has no target
	anywhere, and a person moving their first card is exactly who cannot afford that.

	**No test could see it**, and that is worth knowing before trusting the next one:
	`page.drag_and_drop` moves to the *computed centre* of the target, so it hits however small
	the element is. `tests/test_browser.py` measures the heights now, because geometry is the
	half a synthetic gesture cannot check.
*/
/* **An edge that says there is more, without needing the bottom of the page** (`#846`).

   The columns scroll horizontally, and the only thing announcing that was the scrollbar — which
   sits at the bottom of this element, and this element is as tall as its tallest column. On
   this project's own board that is ninety-odd cards, so the announcement was several thousand
   pixels below the fold and the reader who found it had gone looking for the footer.

   Two gradients per side and the pairing is what makes it work: the *cover* scrolls with the
   content (`local`), the *shadow* is fixed to the scroll port (`scroll`), so each shadow is
   hidden by its cover exactly when that end is reached. No JavaScript and no resize listener,
   which matters because the condition changes with the window as well as with the scrolling.

   `color-mix` against `--ink` rather than a literal black: the shadow has to read on both
   themes, and a hard-coded `rgba(0, 0, 0, …)` is invisible on the dark one — §22 and decision
   `#102`'s reasoning about hues belonging to the reader's theme. */
.board .columns {
	display: flex;
	gap: var(--space-7);
	align-items: stretch;
	overflow-x: auto;
	padding-bottom: var(--space-3);
	background:
		linear-gradient(to right, var(--bg-sunken), transparent) 0 0 / 28px 100% no-repeat local,
		linear-gradient(to left, var(--bg-sunken), transparent) 100% 0 / 28px 100% no-repeat local,
		linear-gradient(
			to right, color-mix(in srgb, var(--ink) 22%, transparent), transparent
		) 0 0 / 16px 100% no-repeat scroll,
		linear-gradient(
			to left, color-mix(in srgb, var(--ink) 22%, transparent), transparent
		) 100% 0 / 16px 100% no-repeat scroll;
}

/* **Grows to fill, and stops.** Without the maximum, seven columns on a display wide enough for
   them became seven columns of five hundred pixels — a card is a title and a few marks, and a
   long line of one is harder to scan than a short one, which is the whole reason a board is
   columns rather than a table. Past the maximum the space is left empty. */
.board .column {
	flex: 1 0 260px;
	min-width: 260px;
	max-width: 340px;
	min-height: 180px;
}

/* **The section-heading role, declared once for the three surfaces that use it** (`SR#1818`).

   Every structural heading in this app was `--text-small` — the board's columns, the agenda's
   buckets and the item page's sections — and every one of them sits above content that
   inherits `body`'s `--text-body`. So each was **two steps below the thing it headed**, where `#906`'s
   own rule is *"a heading takes the step above its content, except where it is deep enough to
   read as a label"*. A page's top-level section is not deep.

   **The sharpest form of it was on the item page**: `markdown.js` offsets a stored `#` to an
   `h3`, so a heading somebody typed in a description rendered at 17px directly beneath the
   app's own 13px *Comments* — the author's structure outranking the product's.

   **Why `#906` did not catch it.** Its migration table is twelve rules and eleven of them were
   14px; it moved what was *off* the scale. These were already on a legal token, so the sweep
   checked conformance to the vocabulary and never checked correctness of role.

   **The uppercase eyebrow goes with the size.** At 13px it read as a label, which is what made
   it defensible; at 17px, muted and letterspaced, it would shout. A section heading is a
   heading here now — the same treatment `.prose h3` already gives the author's own, so the two
   structures speak one language instead of competing. */
.detail > h3, .board .column h2, .agenda h2 {
	font-size: var(--text-section);
	font-weight: 600;
	text-transform: none;
	letter-spacing: normal;
	color: var(--ink);
}

.board .column h2 {
	display: flex;
	align-items: baseline;
	gap: var(--space-3);
	margin: 0 0 var(--space-4);
}

/* **A column that is over, folded to the width of its own name** (`#1008`).

   `writing-mode: vertical-rl` rather than a `transform`, and the difference is the whole
   point: the element's box genuinely becomes narrow, where a rotation would turn the heading
   visually and leave it occupying the width it had. Nothing else has to be told about it —
   the column simply takes less room, and the flex line reflows.

   **`flex: 0 0 auto` and no maximum**, so a shut column neither grows into the space it just
   gave up nor is stretched by `flex: 1`. The height comes from `align-items: stretch` on the
   row, which is what keeps it a drop target for its whole height (`#796`). */
.board .column.collapsed {
	position: relative;
	flex: 0 0 auto;
	min-width: 0;
	max-width: none;
	width: 38px;
	overflow: hidden;
}

/* **Out of flow, and that is load-bearing rather than tidy.** A turned heading is laid out
   along the vertical axis, so its *intrinsic* size is the length of the words — and with
   `height: 100%` inside a column whose own height is auto, the two resolve against each other
   and the column demands the whole string. Measured: a folded *Cancelled* asked for 718px and
   made the board 209px taller than it had been, which pushed the drop target below the fold
   and broke a drag test three files away. Taking it out of flow means a folded column
   contributes nothing to the row's height and simply takes what stretching gives it. */
.board .column.collapsed h2 {
	position: absolute;
	inset: 0;
	margin: 0;
}

/* **The whole column is the button.** A character-wide strip is a hard thing to hit, and a
   shut column holds nothing else worth clicking, so the control fills it rather than sitting
   in a corner of it. */
.board .column.collapsed h2 button {
	writing-mode: vertical-rl;
	width: 100%;
	height: 100%;
	padding: var(--control-tight);
	gap: var(--space-4);
	display: flex;
	align-items: center;
	justify-content: flex-start;
	border-color: transparent;
	background: transparent;
	font: inherit;
	letter-spacing: inherit;
	text-transform: inherit;
	color: var(--ink-soft);
	cursor: pointer;
}

.board .column.collapsed h2 button:hover { color: var(--ink); }

/* The collapse control on an open column, at the same height as the title — the position a
   reader already knows from a window's minimise button. `margin-left: auto` rather than
   `justify-content: space-between`, so the title and its tally stay together on the left
   however many of them there are. */
.board .column h2 .shut {
	margin-left: auto;
	padding: var(--control-tight);
	border-color: transparent;
	background: transparent;
	color: var(--ink-faint);
	font-size: var(--text-body);
	line-height: 1;
	cursor: pointer;
}

.board .column h2 .shut:hover { color: var(--ink); }

/* A count of what is on the page, which is **not** a total. This comment said the count was
   exact because a board is not paged; that was wrong and was corrected in `app.js` for `#718`
   and left standing here — the same retracted claim in a second file, which is the shape this
   project keeps finding. The board renders the page `load` fetched, capped at 100. */
.board .tally {
	color: var(--ink-faint);
	font-weight: 400;
	letter-spacing: 0;
}

/* An empty column is deliberate — the categories are the structure — so it reads as a place
   rather than as a gap.

   **It says one of two different things and this styles both** (`#742`). *Nothing* is the column
   being empty; *Not shown* is finished work not having been asked for, which is a selection now
   (`#738`) and so a coherent thing for a board to be without. The second carries a link, because
   a statement that something is missing with no way to have it is worse than either. */
.board .column .empty {
	margin: 0;
	padding: var(--space-7);
	border: 1px dashed var(--line);
	border-radius: var(--radius);
	background: transparent;
	color: var(--ink-faint);
	font-size: var(--text-small);
}

/* What one column had to leave out (`#1790`). The board's own `.cut` is a bordered, raised bar
   spanning the width of the page; inside a column that box would compete with the cards above
   it for a sentence that is a footnote to them. So it keeps the words and drops the furniture.

   The comment stays above the whole rule rather than between the selector and its body, which
   is `#1722`: a comment in that position is stripped, the selector merges with the next one,
   and CSS reports nothing at all. */
.board .column .cut {
	display: block;
	margin: var(--space-5) 0 0;
	padding: 0;
	border: 0;
	background: transparent;
	color: var(--ink-faint);
	font-size: var(--text-small);
}

/* A card is a row without the horizontal grid: in a 260px column a 4.5rem address column
   leaves a title about 150px wide, which wraps into nonsense. The ref sits on the title's line
   instead and the title takes what is left — which since `#911` is the whole card, because the
   properties and the action moved beneath it rather than standing alongside. */
.board .row {
	display: block;
	padding: var(--space-5) var(--space-6) 0;
}

/* **The ref is in the strip now** (`#1148`), so the board no longer has to lay it inline
   ahead of the title — which is what put the title at a different x depending on whether it
   wrapped. What is left is the block display, which is right: a card is one column. */
.board .meta { padding: var(--space-3) var(--space-6) var(--space-5); }

/* How the rows are arranged (`#651`). Two words, so it reads as a choice rather than as a
   toolbar. */
.views {
	display: flex;
	gap: var(--space-2);
}

/* Anchors since `#722`, so each arrangement can be opened in its own tab. `inline-block` is what
   makes the padding hold — an inline anchor's vertical padding does not affect line height. */
.views a {
	display: inline-block;
	padding: var(--control-tight);
	background: transparent;
	border: 1px solid transparent;
	border-radius: var(--radius);
	color: var(--ink-faint);
	font: inherit;
	font-size: var(--text-small);
	text-decoration: none;
	cursor: pointer;
}

.views a:hover { color: var(--ink-soft); }
.views a:focus-visible { outline: var(--focus-ring); outline-offset: 1px; }

/* **The chosen one is named as well as coloured** — `aria-current` in the markup, and here a
   border rather than a hue alone (decision `#102`: no information exists only in a colour). */
.views a.chosen {
	border-color: var(--line-strong);
	color: var(--ink);
}

/* ---- the agenda (`#652`) ------------------------------------------------ */

/* Buckets are stacked headings over ordinary rows, not a new kind of list. The agenda answers
   *when*, and the row is the same row it is everywhere else — a reader who has learned one has
   learned both. */
.agenda .bucket + .bucket { margin-top: var(--space-section); }

.agenda h2 {
	margin: 0 0 var(--space-4);
}

/* **Colour marks the exception and says nothing on its own** (decision `#102`). Overdue is
   already under a heading that says the word, so the hue is a second signal rather than the
   only one — and it is a basic ANSI-equivalent token from the palette, never a hex value of
   its own. */
.agenda h2.overdue { color: var(--warn); }

/* Where an add lands, said only where it is ambiguous — the agenda spans workspaces and a
   listing does not. */
.adding .lands {
	display: block;
	margin-top: var(--space-3);
	color: var(--ink-faint);
	font-size: var(--text-small);
}

/* ---- listing ----------------------------------------------------------- */

.rows {
	list-style: none;
	margin: 0;
	padding: 0;
	background: var(--bg-raised);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	overflow: hidden;
}

/*
	**A row is an anchor** (`#722`), so ctrl-click opens a tab, middle-click opens a tab, the
	context menu offers *copy link address*, and a screen reader announces a link. It was a
	button, which has no address for any of those to act on.

	Everything below already applied to a button and still has to: `display: grid` because an
	anchor is inline, `color: inherit` and `text-decoration: none` because an anchor is neither.
	The `button` rules stay because one case still renders one — an agenda row whose workspace
	nobody can name has no address, and a hollow anchor cannot be tabbed to.
*/
/* **The address gets the room it needs, and 4.5rem is its floor rather than its width**
   (`#965`). A fixed track was right while every ref was `#94`: nine monospace characters at
   `--text-small` fit 72px with room to spare, and every row on a page then started its title at
   the same x. The agenda at `/` spans workspaces, so a row says `projects/#94` — twelve
   characters, which overflows the track and eats the gap, because **a grid item does not wrap a
   track, it overflows it**. There is no width at which that degrades gracefully.

   **The cost is stated rather than discovered**: where addresses on one page differ in length,
   the titles differ by that much too. That is `/` alone — inside a workspace every ref is short
   and sits on the floor, so every other page is unchanged to the pixel. `Columns.measured` at
   the terminal takes the widest address across the whole output for exactly this reason and is
   the upgrade if the raggedness reads worse than the overlap; it needs the page measured in the
   component, because the row's own font is not the monospace the ref is set in and `ch` here
   would be the wrong character. */
/* **One column, and the strip spans it** — `#1148`. This was `minmax(4.5rem, max-content) 1fr`,
   a track for the ref beside the title, which aligned a *list* correctly and did nothing for a
   board: `.board .row` is `display: block`, so there the ref flowed inline ahead of the title
   and the title began at a different x depending on whether it wrapped. Measured on a 327px
   card: x=57 after a short ref, x=12 once it wrapped.

   With the ref in the strip the title has the line to itself on both surfaces, from the card's
   own edge, and the 4.5rem the ref used to reserve goes back to it. */
.row {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--space-2) var(--space-6);
	align-items: baseline;
	width: 100%;
	padding: var(--space-5) var(--space-7) 0;
	background: transparent;
	border: 0;
	color: inherit;
	font: inherit;
	text-align: left;
	text-decoration: none;
	cursor: pointer;
}

/*
	**The properties, then the actions, under a title that had the whole width** (`#911`).

	These were laid out beside the row: the button was a flex sibling, so it took width down the
	card's entire height and the title wrapped against it while the space alongside stood empty.
	It is a sibling because a button cannot be nested in a button — so the only place it can go
	without narrowing the title is a line of its own, and the chips join it there rather than
	leaving two half-empty lines.

	**Order is fixed and is the point**: every property, then every action, on the list, the
	board and the agenda alike. A reader scanning a column should not have to find where each
	card put its control.
*/
.meta {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-3);
	padding: var(--space-3) var(--space-7) var(--space-5);
}

/*
	The row, its properties and its actions are one item, and the `li` is what says so.

	**The separator was dead for eleven days and nothing could see it** (`#1722`). A comment sat
	between `.meta` and its body, so CSS read the pair as the descendant selector `.meta .rows
	li` — which matches nothing, because no listing sits inside a `.meta`. Measured in a real
	browser rather than read: `borderTopWidth` was `0px` on every row of every list, board and
	agenda. **CSS never errors**, and the neighbouring `:first-child` and `:hover` rules are
	separate and kept working, so a row still lit under the cursor and the first row still
	correctly had no line above it. The only missing thing was the line between every other
	pair — which is what made Simon's *Waiting on somebody else* rows run together.

	The rule that lost its body was the one below, restored on Simon's instruction the same day.
*/
/*
	**Pushed to the far end, so the actions are one group however many chips precede them.**

	Without it *Complete* sits wherever the last chip left off, so it lands at a different x on
	every row and a reader hunting for it reads the chips first. `#911` gives the actions their
	own end of the line for that reason; this is what puts them there.

	**Restored eleven days after it was lost** (`#1722`), on Simon's instruction: it went with
	the separator when a declaration was deleted off the end of a selector line, and it was
	held back from that fix rather than slipped in with it, because moving a control on every
	card, row and agenda line is a thing to look at rather than a tidy-up.

	`.finish` is last in `.meta`, so this pushes it and nothing else — and a row with no
	*Complete* is untouched, which is every finished one and every occasion.
*/
.meta .finish { margin-left: auto; }

.rows li {
	display: block;
	border-top: 1px solid var(--line);
}

.rows li:first-child { border-top: 0; }
.rows li:hover { background: var(--bg-sunken); }

/*
	What is holding one row up (`#1287`, `#1383`). A third line of the row, under the chips,
	sharing their left edge — so a reader follows the same column down from the title.

	**It was its own `li` and had to stop being one.** Simon, 2026-08-31: it *"appears between
	two items… there is no way to determine whether it relates to the item above, or the one
	below."* An `li` here is an item, and this is part of one — so it took neither neighbour's
	colour bar and neither's separator, and the ambiguity was structural rather than a matter
	of spacing. Inside the row it inherits `li[data-colour]`'s bar for nothing, and the next
	row's `border-top` goes back to separating whole items.

	**The gap between blockers is wider than the gap inside one**, which is what says the name
	belongs to the ref beside it rather than to the pair. They were joined with a separator and
	a blocker with nobody assigned made that unreadable.

	**It was `.waiting` and could not stay** (`SR#1826`). The agenda draws its bucket headings
	as `<h2 class=${bucket.key}>`, one bucket's key is `waiting`, and a bare `.waiting` rule is
	`(0,1,0)` against `.agenda h2`'s `(0,1,1)` — so *Waiting on you* kept its size and its
	margin and silently took this rule's `padding` and `display`, because nothing else declared
	either. Measured: 14px of indent and 18px of gap where every other heading had 0 and 8, from
	the day the row line shipped. **CSS never errors**, so a rule reaching an element it was
	never written for looks exactly like one that meant to — `SR#1722` in the same file.

	`holding` is what the value is called at the site that renders it, and it says what the line
	is rather than which bucket a row is in.
*/
.holding {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--space-3) var(--space-8);
	padding: 0 var(--space-7) var(--space-5);
	font-size: var(--text-small);
}

.holding .held {
	display: inline-flex;
	align-items: baseline;
	gap: var(--space-2);
}

/*
	**A row wears the colour of the project it belongs to** — `#1027`, decision `#1023`, and
	`#102` as Simon amended it on 2026-08-19: colour may carry identity where the identity is a
	bounded unordered category, is redundant with a word already on the row, and is stored as a
	name. All three hold, and the third is what the palette above is.

	**Which field, decided by measuring.** Eleven item types are in live use here against a
	categorical limit of eight to ten — and every one already carries a distinct glyph *and* its
	word. A project carried nothing. Colour goes where there is no other channel.

	**A left edge rather than a tinted card.** A tint would turn sixteen contrast pairs into
	eighty — five ink tokens against eight backgrounds in two themes, with `.row .ref` at 13px
	as the worst case — and would fight `--bg-raised` and `--bg-sunken`, which already carry
	elevation. Two meanings on one property is the duplicated-rule defect in a stylesheet.

	**An inset shadow rather than a border, and the browser is what corrected that.** A border
	takes width: `#911` holds the identity line to the card's *whole* width, and a 4px edge made
	every row 4px narrower than the card containing it — so the guard for a defect Simon reported
	from a real board failed on this change. An inset shadow paints inside the padding the row
	already has and moves nothing, which is what the sentence above claimed before it was true.

	A row with no colour in force carries no edge at all rather than a transparent one, because
	`no colour` is a real answer — nothing up this project's tree has chosen one.
*/
li[data-colour] { box-shadow: inset var(--space-2) 0 0 0 var(--row-hue); }

/* **Any element, not only a row** (`#1447`): a colour control draws each choice as a swatch
   coloured by this same rule, so the palette is named once here and a choice looks like the edge
   it will give a row. Only the edge itself stays a row's, above. */
[data-colour="amber"] { --row-hue: var(--hue-amber); }
[data-colour="green"] { --row-hue: var(--hue-green); }
[data-colour="teal"] { --row-hue: var(--hue-teal); }
[data-colour="cyan"] { --row-hue: var(--hue-cyan); }
[data-colour="indigo"] { --row-hue: var(--hue-indigo); }
[data-colour="violet"] { --row-hue: var(--hue-violet); }
[data-colour="magenta"] { --row-hue: var(--hue-magenta); }
[data-colour="slate"] { --row-hue: var(--hue-slate); }

/*
	**The label is deliberately *not* tinted, and a guard is what decided that.** Tinting the
	project's own mark was written first, on the reasoning that the bar should be redundant with
	a word and this is the word. `test_a_link_says_what_is_closed_and_draws_a_mark_as_a_row_does`
	refused it: the same project chip is an `<a>`, so tinting it in a listing and not in an
	item's link list made one mark two colours depending which page it was on — and the link
	colour is what says it can be clicked.

	**`#102` is satisfied without it.** Nothing here is said in a colour alone: the row already
	carries the project's *name*, in words, and the bar is redundant with that. The tint would
	have been a second encoding of a fact that already had two.
*/

.row:focus-visible { outline: var(--focus-ring); outline-offset: -2px; }


/* Layout only; the look is `.action` (design `#1045`). **It was a sunken fill with a
   border — byte-identical to `Show more`, which loads rows, and is why Simon read it as a
   label saying the item *is* complete rather than a button that completes it.** */
.finish { white-space: nowrap; }
.finish:disabled { opacity: 0.5; cursor: default; }

.row .ref { color: var(--ink-faint); font-size: var(--text-small); }

/* **A glyph, the number, and what kind of thing it is** — `#1148`.

   The glyph is a fixed width, so the ref starts at one x on every card whatever its number.
   The kind is not padded to align in turn, and that was decided rather than overlooked: the
   picture is the scanning instrument and it is already fixed, so a ragged word costs nothing,
   where reserving five digits of width would spend the card's scarcest dimension and leave
   `#42` sitting a long way from its own icon on an instance that never reaches four figures.

   `baseline` rather than `center` so the number and the word sit on the row's own baseline
   with the icon, which is the alignment every other line of the card uses. */
.stamp {
	display: flex;
	align-items: baseline;
	gap: var(--space-3);
	min-width: 0;
	/* **Wraps rather than overflowing**, because a board card is as narrow as a quarter of the
	   content width and the strip grew a second half (`#2026`). A flex item's `min-width` is
	   `auto`, so without this a long type word on a narrow column pushes past the card's edge
	   instead of moving to a line of its own. The ref is the second child either way, so what
	   wraps is the type — the fixed position `#1148` bought is not what gives. */
	flex-wrap: wrap;
}

.stamp .icon { align-self: center; color: var(--ink-soft); }

/* **The type half is one flex item, so wrapping moves both parts together.** Measured on a
   260px column with a workspace prefix: the strip broke between the glyph and its own word,
   leaving the picture stranded at the end of one line and `FEATURE` alone on the next. That is
   worse here than it was under `#2026`'s middot, because the glyph *is* the separator — a line
   ending in one promises a fact the line does not carry, and the next line opens with a word
   nothing introduces. */
.stamp-type {
	display: inline-flex;
	align-items: baseline;
	gap: var(--space-3);
	white-space: nowrap;
}

/* Uppercase and one step down, because it labels the card rather than being read as part of
   it — the same treatment `#906` gives every other structural label. Both halves take it:
   the kind and the type are the same *sort* of fact, so a reader meets one label pair rather
   than a label and a value. */
.stamp-kind {
	font-size: var(--text-fine);
	font-weight: 600;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--ink-soft);
}
/* **An item's title is a heading wherever it is drawn** (`#2871`, Simon: *"I'd like item
   title to always be in Lexend"*). On its own page the title is an `<h2>` and was already
   caught by the heading rule; everywhere else it is a `<span>` and inherited the reading
   face, so the same words changed shape when you clicked them. That difference was a
   consequence of the element each surface happened to use rather than a decision.

   **One rule reaches all four**: the list's `Listing`, `Agenda`, `Board` and the journal's
   `JournalRow` all draw `rows.js`'s `Row` - and the journal wraps its own fallback for a
   deleted item in `class="row"` as well. The item page draws no `Row`, so nothing here
   reaches its title.

   **400, a step lighter than the heading it is** (`#2879`, Simon's). 500 was chosen when the
   text around it was set at 400; `#2873` took that text to 300 and left the title where it
   was, so its stroke went from 1.42x the surrounding text's to 1.93x without the title
   changing. At 400 it is 1.58x - still plainly the title, being larger and in ink while the
   stamp and the meta beside it are small and grey. */
.row .title { font-family: var(--font-heading); font-weight: 400; }

/*
	**Who has the work, aligned into a column** (`#1424`, design `#1422`).

	The browser had this fact and could not be *scanned* for it: a chip sits in a flow after
	however many marks precede it, so down fifty rows it begins at fifty different
	x-positions. The terminal never had the problem because `_assignee_cell` is a column.

	**A fixed track, because every row is its own grid.** `max-content` is resolved per
	element, so a content-sized third column would sit wherever each row's own title ended —
	which is the defect this replaces, rebuilt one layer down.

	**Wide enough for the longest thing it can say**, which is not a username: `#1414` puts
	the agent marker and the accountable person in the same string, so the cell has to hold
	`@claude-super (agent, @laurence)`. It wraps rather than truncating — a name cut mid-word is a
	worse answer than a two-line cell, and `align-items: baseline` keeps the first line on
	the title's line either way.
*/
.row.with-assignee { grid-template-columns: 1fr 11rem; }

/* **The strip spans whatever tracks the row has**, so who-has-this stays beside the title
   rather than beside the number. */
.stamp { grid-column: 1 / -1; }

.assignee {
	display: inline-flex;
	align-items: baseline;
	gap: var(--space-2);
	color: var(--ink-soft);
	font-size: var(--text-small);
	overflow-wrap: break-word;
}
.meta .when { color: var(--ink-soft); font-size: var(--text-small); white-space: nowrap; }

/* **Unscoped from `.meta` because there are two callers now** (`SR#970`). The `Marks` component
   draws the same chips on a listing row and on an item's links, and a rule that only reached one
   of them would be the two surfaces looking different while sharing the code that decided what
   they say — which is the half of the drift a shared component does not fix by itself. */
.marks {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-3);
	font-size: var(--text-fine);
	color: var(--ink-soft);
}

.mark {
	display: inline-flex;
	align-items: center;
	gap: var(--space-1);
	border: 1px solid var(--line-strong);
	border-radius: 999px;
	padding: 0 var(--space-4);
	line-height: 18px;
	white-space: nowrap;
}

/*
	A glyph beside the word it illustrates (`#764`).

	**Sized in `em` and coloured `currentColor`**, so it follows the text it sits with rather
	than needing a rule per place it appears — which matters here because a mark's colour is
	itself conditional: `.mark.late` and `.mark.blocked` set `color`, and an icon in a fixed
	colour would be the one part of the chip that did not respond.

	**Slightly under a full em on purpose.** Phosphor's glyphs fill their box where a lowercase
	letter does not fill its line, so matching the two by number leaves the picture looking
	larger than the word beside it.
*/
.icon {
	width: 0.9em;
	height: 0.9em;
	flex: 0 0 auto;
}

/*
	**Three families, so a mark says which kind of fact it is** — `#1019`, Simon: *"it's hard
	to tell what kind of label each is, since they look similar."* Eleven chips differed only
	by tone, so the *category* of a mark was carried by nothing at all.

	**`#102` is what shapes this rather than taste.** Nothing may be said in colour alone, and
	nothing may be said in a shape alone either — so the family is *reinforcement* and the word
	is what carries it. A reader in monochrome loses the grouping and no information.

	* **identity** — what it *is*: the type, then the status. Exactly one of each, so a solid
	  fill is affordable and is what makes the left edge of every row scannable.
	* **state** — what is true *now*: blocked, blocker, overdue, deferred, claimed, repeats.
	  An outline, and colour only for the two that are problems.
	* **address** — where it lives and whose it is. No box at all: `#ops` and `@laurence` carry
	  quick capture's own sigils, and a project is the bare word beside them.
*/
.mark.identity {
	background: var(--ink-soft);
	border-color: var(--ink-soft);
	/* **`--bg`, and this said `--page`, which is not a token** (`#1021`). An undefined custom
	   property is not an error — the declaration is dropped — so the colour fell back to what
	   `.marks` sets on the line, which is `--ink-soft`: text the colour of its own fill, on
	   the served instance, in both themes. `#923` is the guard and was closed without one.

	   The pairing is measured rather than assumed: 5.91:1 in light and 7.65:1 in dark, both
	   clear of `#902`'s 4.5 minimum. */
	color: var(--bg);
}

/*
	**The outline is the family and a glyph is a bonus**, which is a compromise rather than the
	design: only `lock-simple`, `key` and `repeat` are vendored, so *deferred*, *overdue* and
	*claimed* have no picture. Drawing one from memory is what `#925` warns against — a glyph
	name with no path data draws nothing and says nothing — so three states go without until
	somebody vendors them from Phosphor's own source.
*/
.mark.state { border-color: var(--line-strong); }

/*
	**No border, because a sigil is the marker.** A boxed `#ops` beside a boxed status reads as
	the same kind of thing, which is the confusion this whole change is about; unboxed, the
	three addresses group by eye and stay out of the way of the two that decide what a row is.
*/
.mark.address {
	border-color: transparent;
	padding-left: 0;
	padding-right: var(--space-3);
}

.mark.late { color: var(--warn); border-color: currentColor; }
.mark.blocked { color: var(--warn); border-color: currentColor; }

/*
	Who is holding an item (`#726`). **An expired lease is drawn no longer** (`#1019`), so the
	dashed `stale` rule went with it — see `marks` for the argument it reverses.

	**Accent, and the tension in that is recorded rather than implicit.** `#102` reserves
	colour for exceptions, and a claim is not a *problem* the way overdue and blocked are — it
	is ordinary work in progress. Keeping it stands on rarity instead: one live claim in 172
	open tasks here, and rarity is what an exception is.

	**What would make it wrong**: claiming becoming routine. If a listing ever shows this on a
	third of its rows, colour has started encoding a scale, and this is the rule to come back
	to. Simon's decision of 2026-08-19, taken with that written down.
*/
.mark.claimed { color: var(--accent); border-color: currentColor; }

/*
	The value the page is sorted on (`#661`) — the `context` family, and the only mark that is a
	fact about the *page* rather than about the item.

	**No border**, because it is not a label on this item the way *Blocked* or a project key is
	— it is the same field repeated down the whole column, and giving it a chip's outline would
	make a reader read it row by row instead of scanning it. Decision `#102` is untouched:
	nothing here is said in a colour, and this one is quieter because it is *more* repetitive
	rather than less important.
*/
.mark.context {
	border-color: transparent;
	padding-left: 0;
	color: var(--ink-faint);
	font-variant-numeric: tabular-nums;
}

/*
	The four controls above a listing share one row — `SR#2698`. Simon, 2026-09-15: *"a lot of
	empty space wasted near the top of the view, because the filter form section gives a full
	row to each item"*.

	**The gap is between the controls rather than a margin under each**, so a narrow frame that
	wraps them spaces the lines evenly, and the margin under the whole row is the one each
	control used to carry.
*/
.controls {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-5) var(--space-8);
	margin-bottom: var(--space-5);
}

/*
	How the list is ordered, above the rows (`#661`).

	Deliberately lighter than `.narrowed` beside it: that one describes a *filter somebody
	chose* and offers the way out, so it is a notice. This describes the state a list is always
	in, so on the ordinary page it must read as a caption rather than as news.
*/
/*
	Two controls, one appearance (`#1284`). The selectors are grouped rather than the class
	reused: `.ordered` names what it does, and a control that narrows by person is not an
	ordering — but nothing about how either looks should differ, which is `#1855`'s direction
	and the reason `#2057` exists.
*/
.ordered,
/* **The narrowing controls share a row's worth of styling** — `#2173`. `.narrowing` is the
   top-level collapse and `.whose` is the person; they are two answers to *which rows* and
   differ only in shape, so a second block would be the same rule written twice. */
.narrowing,
/*
	How a task is prioritised, as a narrowing — `SR#2270`, and the rule it shipped without
	(`SR#2275`).

	**`.whose`'s own arrangement**, deliberately rather than approximately: they are two selects
	in one control row asking two questions of the same page, and a second set of numbers here
	would be `#907`'s complaint — every size comes from a named step, and two controls a line
	apart drifting is how a row stops reading as one.

	It shipped with **no rule at all**, so it landed on the user agent's defaults: the label
	against the select with no gap, and the row against the first result. `#747` is the item
	about browser chrome being blind to both themes, and this was that with nothing at all in
	front of it.

	**`judged` rather than `priority`, and that is not a preference.** `.priority` is already a
	selector here — it is in the monospace family with `code` and `.ref` at the top of this
	file, waiting for a `!4/2` cell the browser has never drawn, which is why it sits in
	`NOTHING_RENDERS`. A control row wearing it would have rendered its label and its select in
	monospace. `SR#2275` is about a word that is also an identifier and this is the same fault
	one namespace along, met while fixing it: the reader's word is *Priority*, and the class is
	the one the third option and the narrowed sentence both say.
*/
.judged,
.whose {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: var(--space-5);
	padding: 0 var(--space-1);
	color: var(--ink-faint);
	font-size: var(--text-small);
}

.narrowing label,
.ordered label,
.judged label,
.whose label {
	display: flex;
	align-items: center;
	gap: var(--space-3);
}

/*
	The control (`#782`). Styled as `.assign select` is, one size smaller: both are a single
	choice made in place rather than a form being filled in, and a new element landing on the
	user agent's default is what `#747` is about — browser chrome, blind to both themes.
*/
.ordered select,
.judged select,
.whose select {
	padding: var(--control-tight);
	background: var(--bg-raised);
	border: 1px solid var(--line-strong);
	border-radius: var(--radius);
	color: var(--ink);
	font: inherit;
	font-size: var(--text-small);
}

.ordered select:disabled,
.judged select:disabled,
.whose select:disabled { opacity: 0.5; }

/* **Whose work is capped, so the filters keep one line** - `#3734`. A select is as wide as its
   longest option, and with agents that is a name, whom it answers to *and their agents*: about
   400 pixels with this instance's names, which put *Top level only* on a line of its own at
   every width, since a list is a reading measure wide. The closed control shows the choice, and
   the open list shows every name whole. */
.whose select { max-width: 16em; }

/* What choosing an order does to the page, beside the control that chose it (`SR#2698`) —
   which for a ranked page is that it holds no documents. The choice itself is the select's to
   show, and this used to say it a second time. */
.ordered .says { color: var(--ink-faint); }

/*
	Dragging a card between columns (`#711`).

	**The affordance is the cursor and the target is a border**, and both say what they mean
	without colour carrying it (decision `#102`): the accent marks the column a drop would land
	in, and the dashed edge says *here* whether or not a reader can separate the hues.

	`grab` only where a card can be lifted — a row on a list is not draggable, so it must not
	claim to be.
*/
/* On the `li` rather than on `.row`, because since `#911` the card is two elements and a
   grab cursor on one of them says the other cannot be picked up. `.finish` sets its own. */
.board .rows li[draggable="true"] { cursor: grab; }
.board .rows li[draggable="true"]:active { cursor: grabbing; }

.column.over {
	border: 1px dashed var(--accent);
	border-radius: var(--radius);
	background: var(--bg-raised);
}

/*
	A day and a time side by side (`#798`). The day takes the room it needs and the time takes
	what is left, so a narrow column drops the time under it rather than squeezing both.
*/
.adding .when {
	display: flex;
	gap: var(--space-3);
	min-width: 0;
	flex-wrap: wrap;
}

.adding .when input { flex: 1 1 8em; min-width: 0; }

/* ---- detail ------------------------------------------------------------ */

.detail {
	background: var(--bg-raised);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	padding: var(--space-section) var(--space-section);
}

.detail h2 {
	margin: var(--space-2) 0 var(--space-1);
	font-size: var(--text-page);
	line-height: 1.3;
	letter-spacing: -0.01em;
}

.back {
	background: transparent;
	border: 0;
	padding: 0;
	color: var(--accent);
	font: inherit;
	text-decoration: none;
	cursor: pointer;
}

.back:hover { text-decoration: underline; }

/* **An item's way to its journal** (`#1428`), across the line from the way back to the list it
   came from: both leave the page, and neither is about what is on it. */
.item-journal {
	float: right;
	color: var(--accent);
	text-decoration: none;
}

.item-journal:hover { text-decoration: underline; }

/* ---- the journal ---------------------------------------------------------------------------
   `#2731`, `#1428` and `#2826`. **A day heading, then the list's rows**, each with a line under
   it for every thing that happened, the time in a narrow column of its own so a reader runs an
   eye down it the way a log is read. */

.journal .about {
	margin: 0 0 var(--space-5);
	color: var(--ink-soft);
}

.journal h3.day {
	margin: var(--space-8) 0 var(--space-3);
	font-size: var(--text-body);
	color: var(--ink-soft);
}

/* **Inside the row's `li`, in the row's own inset**, so a line takes its item's colour bar and
   separator. `.rows li` reaches these too, being a descendant rule, and would draw a
   separator and a hover under every line of one item: both are put back here. */
.journal ul.happened {
	margin: 0;
	padding: 0 var(--space-7) var(--space-5);
	list-style: none;
	color: var(--ink-soft);
	font-size: var(--text-small);
}

.journal ul.happened li {
	display: grid;
	grid-template-columns: 4rem 1fr;
	gap: var(--space-5);
	align-items: baseline;
	padding: var(--space-1) 0;
	border-top: 0;
}

.journal ul.happened li:hover { background: none; }

.journal .what strong { color: var(--ink); font-weight: 600; }

.journal .clock {
	color: var(--ink-faint);
	font-size: var(--text-small);
	font-variant-numeric: tabular-nums;
}

/* The door is how it arrived, which is context rather than the news - so it is quieter than who
   and what. */
.journal .door {
	color: var(--ink-faint);
	font-size: var(--text-small);
}

/* **The opening of what somebody wrote, on one line** (`#2852`) - its line breaks arrive as
   spaces, so nothing here keeps them. */
.journal .said {
	color: var(--ink);
}

.journal .rest { font-size: var(--text-small); }

.journal .older { margin: var(--space-6) 0 0; }

.facts {
	display: grid;
	grid-template-columns: max-content 1fr;
	gap: var(--space-2) var(--space-8);
	margin: var(--space-8) 0 0;
	font-size: var(--text-small);
}

.facts dt { color: var(--ink-soft); }
.facts dd { margin: 0; }

/* What can be done to the open item. Wraps rather than scrolls, so a narrow screen stacks
   the two controls instead of hiding one. */
.doing {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-6);
	margin-top: var(--space-8);
	padding-top: var(--space-8);
	border-top: 1px solid var(--line);
}

/* **A button rather than the tight size `.finish` carries elsewhere** (`#763`). The same
   element is a row's action on a listing and a standing one here, and this is the second —
   *what can be done to the open item*, with nothing around it competing for the space.

   **Found by the guard rather than by the sweep**, on its first run: it is addressed by class,
   so the measurement that counted control *elements* never saw it and it was a fourteenth
   padding hiding behind a list of thirteen. */

.assign {
	display: flex;
	align-items: center;
	gap: var(--space-4);
	font-size: var(--text-small);
	color: var(--ink-soft);
}

.assign select {
	padding: var(--control-tight);
	background: var(--bg-raised);
	border: 1px solid var(--line-strong);
	border-radius: var(--radius);
	color: var(--ink);
	font: inherit;
	font-size: var(--text-small);
}

.assign select:disabled { opacity: 0.5; }

.prose {
	margin-top: var(--space-8);
	padding-top: var(--space-8);
	border-top: 1px solid var(--line);
	overflow-wrap: anywhere;
}

/* **A rule where a section begins, and a direct child so the author's own headings never get
   one** (`SR#1818`). The body already drew a line above itself and nothing after it did, so an
   item's description had a boundary and its comments ran on from whatever preceded them —
   which is what Simon read as the comments not being separated from the body.

   `.detail > h3` rather than `.detail h3`: the latter also matches every heading inside
   `.prose`, so a `##` in somebody's description would have been given a horizontal rule of the
   app's own. That descendant match is also why `.prose h3` has always had to restate its case,
   tracking and colour — it was undoing this rule rather than declaring its own. */
.detail > h3 {
	margin: var(--space-section) 0 var(--space-4);
	padding-top: var(--space-6);
	border-top: 1px solid var(--line);
}

.linked, .comments { list-style: none; margin: 0; padding: 0; }

/* **Split by what they hold** (`#906`). These shared a rule and a size, and the type scale is
   what made the difference say itself: a link row is a reference and takes the smaller step,
   where a comment is prose somebody wrote and takes body size. They still share their frame. */
.linked li, .comments li {
	padding: var(--space-4) 0;
	border-top: 1px solid var(--line);
}

/* **One line that wraps rather than one that overflows** (`SR#970`). A link row is a label, an
   item and now the same chips a listing row carries, and `SR#965` is what a fixed track does to
   content that outgrows it: a grid item does not wrap a track, it overflows it. */
.linked li {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--space-2) var(--space-3);
	font-size: var(--text-small);
}

/* **A comment gets the air, a link row does not** (`SR#1819`). They share a frame and `#906`
   already split them on size — *"a link row is a reference and takes the smaller step, where a
   comment is prose somebody wrote"*. The frame is the half it did not split: a comment is the
   largest object on this page, holding rendered Markdown with its own paragraphs and lists,
   and it was separated by exactly the hairline and 8px a one-line reference gets. Four pixels
   inside a group against sixteen between them is what makes two things read as neighbours
   rather than as one thing (`#1722`, one level down from this). */
.comments li { font-size: var(--text-body); padding: var(--space-8) 0; }

/* **Who spoke, as a heading rather than as fine print** (`SR#1819`). This was `--text-fine`
   directly above `--text-body` — the smallest step in the system heading the largest text on
   the page, which is the widest inversion the scale can express.

   **A label rather than the step above**, which is `#906`'s stated exception used the way it
   was written: the step above 15px is 17px, and that is what a *section* takes here, so an
   attribution at 17px would say a comment's byline ranks with *Comments* itself. Deep enough
   to read as a label — so body size, and the weight carries it. The name was already
   `<strong>`, which is the tell that it had reached for heading weight without heading size. */
.comments .said { color: var(--ink); font-size: var(--text-body); margin-bottom: var(--space-3); }

/* The moment stays quiet: *who* is what a reader scans for, *when* is what they check. */
.comments .said .when { color: var(--ink-soft); font-size: var(--text-small); }
.comments .body { overflow-wrap: anywhere; }

/* ---- rendered Markdown (`#637`) ---------------------------------------- */

/*
	Selectors on the container rather than a class on every element: the renderer emits plain
	tags, so the styling hangs off whatever contains them. That keeps `markdown.js` about meaning
	and this file about appearance, and it means a construct added there is styled without
	touching it.

	**Three containers, and the third is the preview** (`#2680`). `Written` draws what is being
	typed through `Prose` as `.rendered`, and it had none of these rules, so a heading, a list or
	a table previewed as the browser's own rather than as it would be read - which is the one
	thing *Preview* promises (`#776`).
*/

.prose > :first-child, .comments .body > :first-child, .written > .rendered > :first-child { margin-top: 0; }
.prose > :last-child, .comments .body > :last-child, .written > .rendered > :last-child { margin-bottom: 0; }

.prose p, .comments .body p, .written > .rendered p { margin: 0 0 var(--space-6); }

.prose h3, .prose h4, .prose h5, .prose h6,
.comments .body h3, .comments .body h4, .comments .body h5, .comments .body h6,
.written > .rendered h3, .written > .rendered h4, .written > .rendered h5, .written > .rendered h6 {
	margin: var(--space-section) 0 var(--space-4);
	line-height: 1.3;
	/* Chosen, not inherited: the browser's bold is 700, and no vendored Lexend has it (`#2874`). */
	font-weight: 600;
	text-transform: none;
	letter-spacing: normal;
	color: var(--ink);
}

/*
	**Every heading somebody writes is larger than the text under it** (`#2679`). `markdown.js`
	pushes a stored heading down two levels, so `#` is an `h3`, `##` an `h4` and `###` an `h5` -
	and `##` was drawn at the body's own size, and `###` below it and muted. Those are the two
	levels people write: across this instance's documents `##` opens 173 of the 193 that have
	headings, and `###` is on 741 lines.

	`#` takes the title's step, because it is usually a document repeating its own title. `##`
	takes a section heading's, the same as the page's own *Links* and *Comments*, so the
	author's structure and the app's are one size - `#1818`'s rule. `###` and deeper take the
	step below, and a deep heading is no longer muted: colour marks exceptions and never
	encodes a scale (`#102`).

	**A comment is prose too**, and had no heading rules at all, so its `###` fell back to the
	browser's own 0.83em.
*/
.prose h3, .comments .body h3, .written > .rendered h3 { font-size: var(--text-page); }
.prose h4, .comments .body h4, .written > .rendered h4 { font-size: var(--text-section); }
.prose h5, .prose h6, .comments .body h5, .comments .body h6,
.written > .rendered h5, .written > .rendered h6 { font-size: var(--text-subsection); }

.prose ul, .prose ol, .comments .body ul, .comments .body ol,
.written > .rendered ul, .written > .rendered ol { margin: 0 0 var(--space-6); padding-left: var(--space-section); }
.prose li, .comments .body li, .written > .rendered li { margin: var(--space-1) 0; }

/* A nested list makes its parent item hold a paragraph, which would otherwise open a gap
   the writer did not put there. */
.prose li > p, .comments .body li > p, .written > .rendered li > p { margin: 0 0 var(--space-2); }

.prose blockquote, .comments .body blockquote, .written > .rendered blockquote {
	margin: 0 0 var(--space-6);
	padding: var(--space-1) 0 var(--space-1) var(--space-7);
	border-left: 3px solid var(--line-strong);
	color: var(--ink-soft);
}

.prose code, .comments .body code, .written > .rendered code {
	padding: var(--space-1) var(--space-2);
	background: var(--bg-sunken);
	border: 1px solid var(--line);
	border-radius: 3px;
	font-size: var(--text-code);
}

.prose pre, .comments .body pre, .written > .rendered pre {
	margin: 0 0 var(--space-6);
	padding: var(--space-5) var(--space-6);
	background: var(--bg-sunken);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	/* Code is the one thing that must not be re-wrapped, so it scrolls on its own rather
	   than widening the page — which on a phone would take the whole layout with it. */
	overflow-x: auto;
}

.prose pre code, .comments .body pre code, .written > .rendered pre code {
	padding: 0;
	background: transparent;
	border: 0;
	font-size: var(--text-fine);
	white-space: pre;
}

.prose hr, .written > .rendered hr { margin: var(--space-8) 0; border: 0; border-top: 1px solid var(--line); }

/*
	A table is the one construct that cannot be made narrow, and 20% of the prose here has
	one. It scrolls inside its own box; the wrapper is the table itself because the renderer
	emits no container, and `display: block` is what lets it scroll at all.
*/
.prose table, .comments .body table, .written > .rendered table {
	display: block;
	overflow-x: auto;
	max-width: 100%;
	margin: 0 0 var(--space-6);
	border-collapse: collapse;
	font-size: var(--text-small);
}

.prose th, .prose td, .comments .body th, .comments .body td, .written > .rendered th, .written > .rendered td {
	padding: var(--space-3) var(--space-5);
	border: 1px solid var(--line);
	text-align: left;
	vertical-align: top;
}

.prose th, .comments .body th, .written > .rendered th { background: var(--bg-sunken); font-weight: 600; }

/*
	A Markdown table says how each column is aligned, and the renderer used to say so with an
	inline style — which `#805`'s policy blocks, because the policy was written on a measurement
	that the app emits none. Three classes cost three rules and keep both true.
*/
.prose .align-left, .comments .body .align-left, .written > .rendered .align-left { text-align: left; }
.prose .align-center, .comments .body .align-center, .written > .rendered .align-center { text-align: center; }
.prose .align-right, .comments .body .align-right, .written > .rendered .align-right { text-align: right; }

.prose a, .comments .body a, .written > .rendered a { color: var(--accent); }

.prose del, .comments .body del, .written > .rendered del { color: var(--ink-faint); }

/* Anchors since `#722` — the other end of a link is the thing a reader on this page is most
   likely to want in a tab. The `button` rules stay for the one case that has no address.

   **Direct children only, since `SR#970`.** A project chip inside `.marks` is an anchor too, and
   this rule is one class more specific than `.mark` — so without the `>` every mark on this
   page would take the accent colour and lose its border, and `.mark.late` and `.mark.claimed`
   would stop saying anything. A chip must look the same here as it does on a row. */
.linked li > a, .linked li > button:not(.action) {
	background: transparent;
	border: 0;
	padding: 0;
	color: var(--accent);
	font: inherit;
	text-decoration: none;
	cursor: pointer;
	text-align: left;
}

.linked li > a:hover, .linked li > button:not(.action):hover { text-decoration: underline; }

/* ---- states ------------------------------------------------------------ */

.empty, .failed {
	padding: var(--space-section) var(--space-section);
	background: var(--bg-raised);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	color: var(--ink-soft);
}

.failed { border-color: var(--warn); color: var(--ink); }
.failed p { margin: 0 0 var(--space-4); }
.failed .why { color: var(--ink-soft); font-size: var(--text-body); }

.foot {
	margin-top: var(--space-8);
	color: var(--ink-faint);
	font-size: var(--text-fine);
	display: flex;
	gap: var(--space-6);
	flex-wrap: wrap;
}

.foot a { color: inherit; }

/* The theme control (`#908`), in the menu under the reader's name since `#2599`. Deliberately
   plain: `#763` is what gives this project's controls a look. */
.theme {
	display: flex;
	align-items: center;
	gap: var(--space-2);
}

.theme select {
	font-size: var(--text-fine);
	padding: var(--control-tight);
}

@media (max-width: 620px) {
	/* **One column, since the stamp took a line of its own** (`#2829`). This was `3.5rem 1fr`,
	   from when a row's number sat in the first column; once the stamp spanned the row, the
	   title fell into the 3.5rem track on the line below and wrapped a word to a line on
	   every phone, in the list and the journal alike. */
	.row { grid-template-columns: 1fr; }

	/* **Under the title rather than beside it.** An 11rem column on a 320px screen leaves
	   the title about 90px, which wraps into nonsense — the measurement `.board .row` made
	   for the same reason one screen size up. One column puts the cell on a line of its own,
	   at the x every title starts at, so the sweep survives. */
	.row.with-assignee { grid-template-columns: 1fr; }
	.facts { grid-template-columns: 1fr; gap: var(--space-1); }
	.facts dd { margin-bottom: var(--space-4); }
}

/* A mention of another item, inside rendered prose (`#638`). Underlined rather than only
   coloured, because `#102` says a colour may not be the only thing carrying a meaning — and
   "this is a link" is a meaning. */
.prose .mention, .comments .body .mention, .written > .rendered .mention {
	color: var(--accent);
	text-decoration: underline;
	text-underline-offset: 2px;
	white-space: nowrap;
}

/* What the listing had to leave out (`#646`). Deliberately plain: it is a statement about the
   list rather than a warning, so it does not borrow the colour that marks an exception. */
.cut {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-6);
	margin-top: var(--space-6);
	padding: var(--space-5) var(--space-7);
	background: var(--bg-raised);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	color: var(--ink-soft);
	font-size: var(--text-body);
}



/* What the address narrowed the list to (`#647`). It says so in words rather than by looking
   different, because a filter a reader did not apply is one they cannot be expected to
   remember — and a short list with no explanation reads as an empty backlog. */
/*
	What is prioritised, said once above the rows it affects — `#986`, decision `#982`.

	**Quieter than `.narrowed` and still deliberately so**, though the reason has narrowed since
	`SR#2265`. That one describes a filter the reader applied to *this page*; this describes an
	ordering they set once, somewhere else, that acts on every page in the workspace. So it keeps
	the softer ink and no border — every token here exists, which `#923`'s guard is what keeps
	true — while no longer being context *rather than* an action: the sentence now carries the
	one act available to it, because announcing a setting and not offering to undo it sent a
	reader through two pages to find the button.

	**A row rather than a paragraph, laid out the way `.narrowed` below is** and for the reason
	written there: `margin-right: auto` on the sentence is identical to `space-between` while
	there is one control, and groups any number of them rather than pushing a second against the
	middle of the row.
*/
.focus {
	display: flex;
	align-items: center;
	gap: var(--space-6);
	margin-bottom: var(--space-6);
	padding: var(--space-3) var(--space-7);
	color: var(--ink-soft);
	font-size: var(--text-small);
}

.focus > span:first-child { margin-right: auto; }

/*
	**The controls sit together at the right rather than being spread apart** (`#986`). This was
	`space-between`, which is the same arrangement while the bar holds one control and pulls two
	to opposite ends the moment it holds two — *Prioritise* against the middle of the row and
	*Show everything* against the edge, reading as two unrelated things. `margin-right: auto` on
	the sentence gives the identical result for one control and groups any number of them.
*/
/*
	Which of the words typed could not be read as a filter — `SR#2268`.

	**`.narrowed`'s bar rather than `.broke`'s warning**, because the search was answered: part
	of the line was read as words instead of as a filter, which is a fact about what this page
	is rather than something that failed. It belongs to the family that explains the page, so
	it looks like the rest of that family.
*/
.unread {
	margin-bottom: var(--space-7);
	padding: var(--space-4) var(--space-7);
	background: var(--bg-raised);
	border: 1px solid var(--line-strong);
	border-radius: var(--radius);
	color: var(--ink-soft);
	font-size: var(--text-body);
}

.unread p { margin: 0; }

.unread p + p { margin-top: var(--space-4); }


.narrowed {
	display: flex;
	align-items: center;
	gap: var(--space-6);
	margin-bottom: var(--space-7);
	padding: var(--space-4) var(--space-7);
	background: var(--bg-raised);
	border: 1px solid var(--line-strong);
	border-radius: var(--radius);
	color: var(--ink-soft);
	font-size: var(--text-body);
}

.narrowed > span:first-child { margin-right: auto; }

.narrowed strong { color: var(--ink); font-weight: 600; }



/*
	Something that would not render, said in words — `#679`, `#680`.

	**Marked the same way a refused write is**, because it is the same kind of news: something
	you were looking at is not there, and the sentence rather than the colour is what carries
	it (decision `#102`). The left border is how you spot it; the text is how you read it.

	It has to survive being the *only* thing on screen, since the boundary at the mount renders
	this with nothing around it — so it carries its own spacing rather than inheriting a
	container's.
*/
.broke {
	margin: var(--space-7) 0;
	padding: var(--space-5) var(--space-6);
	background: var(--bg-raised);
	border: 1px solid var(--line-strong);
	border-left: 3px solid var(--warn);
	border-radius: var(--radius);
	font-size: var(--text-body);
}

/* The message verbatim, which is for whoever is told about it rather than for the reader.
   Its monospace comes from the rule at the top that every other fixed-width thing uses, rather
   than from a second copy of the stack. */
.broke .detail { color: var(--ink-soft); }

/* *Show everything* leaves a project for its workspace, which is an address — so it is a link
   (`#722`), styled as the button it replaced rather than as prose. */
.narrowed a.widen {
	display: inline-block;
	padding: var(--control-tight);
	background: transparent;
	border: 1px solid var(--line-strong);
	border-radius: var(--radius);
	color: var(--ink-soft);
	font: inherit;
	font-size: var(--text-small);
	text-decoration: none;
	cursor: pointer;
}

.narrowed a.widen:hover { color: var(--ink); border-color: var(--ink-soft); }

/* *Show finished work*, in a column that was not asked for it (`#740`, `#738`).

   **There is no global anchor rule in this file and that is deliberate** — `.views a`,
   `.prose a`, `.comments .body a`, `.foot a` and `.narrowed a.widen` each style their own
   context, because a link inside prose and a link that replaces a button want opposite things.
   The cost is that a new one lands on the user agent's default: browser blue, underlined, and
   **blind to both themes**, which is what this arrived as.

   Styled as `.narrowed a.widen` is, because it is the same thing one place smaller — an action
   offered inside a notice — and the accent is what says it is the way out of the state the
   sentence above it describes. */
.board .column .empty a {
	display: inline-block;
	margin-top: var(--space-4);
	padding: var(--space-1) var(--space-4);
	border: 1px solid currentColor;
	border-radius: var(--radius);
	color: var(--accent);
	font-size: var(--text-fine);
	text-decoration: none;
}

.board .column .empty a:hover { color: var(--ink); }

.board .column .empty a:focus-visible {
	outline: var(--focus-ring);
	outline-offset: 1px;
}

/* Prose being written, and a way to see it as it will read — `SR#776`.

   The label is already a column, so the button and the preview stack under the box without
   any layout of their own. The button sits at the end of the label's own line rather than
   above the box, which is where a reader looks for a control that is *about* the field. */
.written {
	position: relative;
}

.written > .preview {
	position: absolute;
	inset-block-start: 0;
	inset-inline-end: 0;
	padding: var(--control-tight);
	font-size: var(--text-fine);
}

/* **The same height as the box it replaces**, or toggling would move everything under it —
   which on a twelve-row document body is the whole page jumping under the cursor. A minimum
   rather than a fixed height: prose that renders taller than the box is worth seeing whole,
   and the box is what somebody scrolls. */
.written > .rendered {
	min-block-size: 6em;
	padding: var(--space-3);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: var(--bg-sunken);
	overflow-x: auto;
}

/* An empty box previews as nothing, and nothing looks like a broken control. */
.written > .rendered:empty::before {
	content: "Nothing written yet.";
	color: var(--ink-faint);
}

/* ---------------------------------------------------------------------------------------
   Who is on this instance — `#1397`, the browser's administrative area.

   **Two aligned columns and nothing else**, which is `#1424`'s finding applied before the
   defect rather than after it: that item's whole subject was an assignee rendered as a chip
   in a flow, unscannable *because it was not aligned*. A directory is read by running an eye
   down one column, so who-they-are and what-they-may-do each get a track.
   --------------------------------------------------------------------------------------- */

.people,
.settings,
.journal {
	padding: var(--space-5) var(--space-7);
}

/* The area's own heading takes the step a section takes, not the step a page takes: this sits
   under the masthead, which is the page's name. `#1818`'s rule — a structural heading is one
   step above the content it heads, and no more. */
.people h2.area,
.settings h2.area,
.journal h2.area {
	margin: 0 0 var(--space-5);
	font-size: var(--text-section);
}

/* **Said when a column is assembled from several answers and one did not arrive** (`#1305`).
   Faint because it is a caveat rather than a warning: the page is still true, it is narrower
   than it should be. */
.people .partial {
	margin: 0 0 var(--space-5);
	color: var(--ink-faint);
	font-size: var(--text-small);
}

.principal {
	display: grid;
	grid-template-columns: 1fr 16rem;
	gap: var(--space-2) var(--space-6);
	align-items: baseline;
	padding: var(--space-3) 0;
	border-block-end: 1px solid var(--line);
}

/* The name, its glyph and — for an agent — who answers for it, as one cell. `named` composes
   the words; this only keeps them on one line with the picture.

   **`account` rather than `who`, and the stylesheet guard is what caught it.** The masthead
   owns a *bare* `.who` — `--ink-soft` at `--text-small` — so a second element wearing that
   class inherited it, and every principal's name on this page rendered at 13px in soft ink
   while being the row's primary content. That is `#1826` reproduced: a class written for one
   place reaching a second, looking nearly right, and reported by nothing a markup test can see.

   **Renamed rather than scoped**, which is `#1826`'s own decision: scoping fixes today and
   leaves one word meaning two things, which breaks again the moment either is drawn somewhere
   new. */
.principal .account {
	display: inline-flex;
	align-items: baseline;
	gap: var(--space-2);
}

/* **Somebody who has left is dimmed and also says so** (`#102`): nothing may be information
   only in how it looks, so the word carries it and the tone is reinforcement. */
.principal.gone {
	color: var(--ink-faint);
}

.principal .left {
	color: var(--ink-faint);
	font-size: var(--text-small);
}

.principal .roles {
	font-size: var(--text-small);
}

/* *admin* **in** *Projects* — the preposition is quieter than either half it joins, because
   the two nouns are what a reader is scanning for. */
.principal .roles .in,
.principal .roles .between {
	color: var(--ink-faint);
}

/* An em dash where somebody holds no role anywhere, rather than an empty cell: on a page that
   fetches its roles a workspace at a time, blank reads as *not loaded*. */
.principal .norole {
	color: var(--ink-faint);
	font-size: var(--text-small);
}

/* ---------------------------------------------------------------------------------------
   Granting, narrowing and revoking authority — `#1396`.

   **The look is the administrative area's, not a form's**, because these controls are read
   rarely and carefully rather than typed into all day. Nothing here is louder than the row it
   belongs to: `#1046`'s six roles decide every button, and the fill is spent on the one act
   that commits.
   --------------------------------------------------------------------------------------- */

/* How many credentials one principal holds, on their row. A reveal, so the caret turns. */
.principal .holdscount {
	font-size: var(--text-small);
}

.holdings {
	margin: var(--space-5) 0;
	padding: var(--space-5);
	border: 1px solid var(--line);
	border-radius: var(--radius);
	background: var(--bg-sunken);
}

.holdings h3 {
	margin: 0 0 var(--space-4);
	font-size: var(--text-body);
}

.credential {
	display: grid;
	grid-template-columns: 1fr 2fr auto;
	gap: var(--space-2) var(--space-6);
	align-items: baseline;
	padding: var(--space-3) 0;
}

/* **Revoked or expired is dimmed and also says which** (`#102`): the tone is reinforcement and
   the word beside it is what carries the fact. It is still listed, because *would this be
   accepted right now* is the question `usable` answers, and an audit needs the ones that would
   not be. */
.credential.spent {
	color: var(--ink-faint);
}

.credential .spentword {
	font-size: var(--text-small);
	color: var(--ink-faint);
}

.credential .prefix {
	margin-inline-start: var(--space-3);
	font-size: var(--text-small);
	color: var(--ink-faint);
}

/* What it may do and where, which is the cell a reader is actually scanning. */
.credential .reach {
	font-size: var(--text-small);
	color: var(--ink-soft);
}

/* **Asked before it happens, and it looks like a question rather than a warning.** The colour
   is spent on the act, not on the asking — a panel that shouts before anybody has decided
   anything is how a reader learns to dismiss it. */
.stopping,
.issuing,
.secret {
	margin: var(--space-5) 0;
	padding: var(--space-6);
	border: 1px solid var(--line-strong);
	border-radius: var(--radius);
	background: var(--bg-raised);
}

.stopping p,
.issuing p {
	margin: 0 0 var(--space-4);
}

.stopping .lastused {
	color: var(--ink-soft);
	font-size: var(--text-small);
}

/* Cancel first and the act second, so the reading order is the safe order. */
.acts {
	display: flex;
	gap: var(--space-4);
	margin-block-start: var(--space-6);
}

.issuing h3,
.secret p {
	margin: 0 0 var(--space-4);
}

.issuing .scopes {
	margin: var(--space-5) 0;
	padding: var(--space-5);
	border: 1px solid var(--line);
	border-radius: var(--radius);
}

.issuing .scope {
	display: flex;
	align-items: baseline;
	gap: var(--space-3);
	padding: var(--space-1) 0;
	font-size: var(--text-small);
}

/* **Says what an untouched control means**, because an empty set of tick boxes reads as *none*
   and means *not narrowed* — the opposite. */
.issuing .hint {
	color: var(--ink-soft);
	font-size: var(--text-small);
}

/* **The warning is before the act.** Told afterwards it is an apology. */
.issuing .warn {
	color: var(--warn);
	font-size: var(--text-small);
}

/* The one moment the secret exists in readable form. Selectable, and offered by nothing else —
   no copy button, which would be a promise about a permission this page cannot check. */
.secret .credentialvalue {
	display: block;
	padding: var(--space-5);
	border: 1px solid var(--line-strong);
	border-radius: var(--radius);
	background: var(--bg-sunken);
	overflow-wrap: anywhere;
	user-select: all;
}

.people .adding {
	margin-block-start: var(--space-5);
}

/* ---------------------------------------------------------------------------------------
   The settings area — `#1446`, design `#2110` §3. One page per scope, all drawn alike, under
   the people page's frame and heading above, so the administrative area reads as one place.
   --------------------------------------------------------------------------------------- */

.settings .timezone h3,
.settings .timezone p {
	margin: 0 0 var(--space-4);
}

/* **The label above its control**, because a zone's name is long and a label beside it would
   squeeze the list to nothing on a phone. */
.settings .timezone label {
	display: grid;
	justify-items: start;
	gap: var(--space-2);
	margin: var(--space-5) 0 var(--space-4);
}

/* **Says where the zone in force came from**, which is the one fact here a reader cannot see in
   the control itself. */
.settings .hint {
	color: var(--ink-soft);
	font-size: var(--text-small);
}

/* The area's own pages, one link each: the account's, then each workspace the reader can
   reach (`#1447`). The one showing is marked in weight as well as colour (`#102`). */
.setting-pages {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-3) var(--space-6);
	margin: 0 0 var(--space-6);
	font-size: var(--text-small);
}

.setting-pages a.chosen {
	color: var(--ink);
	font-weight: 600;
}

.setting-page h3 {
	margin: 0 0 var(--space-4);
}

/* One setting: what it is, where its value came from, and its control. A rule between them
   rather than a card each, because a page of two settings is read top to bottom. */
.setting-row {
	margin: var(--space-6) 0;
	padding-block-start: var(--space-5);
	border-block-start: 1px solid var(--line);
}

.setting-row h4 {
	margin: 0 0 var(--space-3);
	font-size: var(--text-body);
}

.setting-row p {
	margin: 0 0 var(--space-3);
}

.setting-choice fieldset {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-2) var(--space-6);
	margin: var(--space-4) 0;
	padding: var(--space-5);
	border: 1px solid var(--line);
	border-radius: var(--radius);
}

.setting-option {
	display: flex;
	align-items: center;
	gap: var(--space-3);
	font-size: var(--text-small);
}

/* **Coloured by the palette's own rule** — `[data-colour]` below — rather than by a second
   table here, which is the whole reason that rule no longer asks for a row. */
.setting-swatch {
	inline-size: var(--space-6);
	block-size: var(--space-6);
	border-radius: var(--radius);
	background: var(--row-hue);
}

/* A workspace's projects, each leading to its own page (`#1448`). A column rather than a row of
   links, because it follows the tree, and the path beside each title is what tells two
   projects with one title apart. */
.setting-projects ul {
	display: grid;
	gap: var(--space-2);
	margin: 0;
	padding: 0;
	list-style: none;
	font-size: var(--text-small);
}

/* **A section most people will never need, set apart at the foot of its page** (`#2722`,
   Simon: *perhaps even with a visual separator to distinguish*). A double rule in the stronger
   line, because every setting above it already has a single rule of its own, and a gap to match. */
.setting-apart {
	margin: var(--space-8) 0 var(--space-6);
	border: 0;
	border-block-start: 3px double var(--line-strong);
}

.setting-section h3 {
	margin: 0 0 var(--space-3);
	font-size: var(--text-body);
}

/* What the section is for, read before anything in it - kept to a reading measure. */
.setting-explains {
	max-inline-size: var(--measure);
	margin: 0 0 var(--space-3);
}

/* A destination's box, with its label above it, for the same reason as the fields below. */
.setting-field {
	display: grid;
	justify-items: start;
	gap: var(--space-2);
	margin: var(--space-4) 0 var(--space-2);
	font-size: var(--text-small);
}

/* **The label above its control** for the installation's two fields (`#2103`), the timezone
   page's rule and for its reason: a zone's name is long, and a label beside it squeezes the
   list to nothing on a phone. */
.setting-fields label {
	display: grid;
	justify-items: start;
	gap: var(--space-2);
	margin: 0 0 var(--space-4);
}
