/* OAR dashboard — the finding aid, set in the OOPS theme.
 *
 * An archive's FINDING AID is hierarchical and referenced: fixed-width identifiers running down a
 * margin, descriptive prose beside them. This dashboard is that document, written DURING the
 * residency rather than after it — so the layout is a gutter and a column, and the gutter does real
 * work. Direction and reasoning: docs/oops-design.md.
 *
 * THE THEME IS THE OPEN CALL, DEVELOPED AS A NEGATIVE — `docs/style-book.html` is the specification
 * and `tokens.css` is the source. The poster's navy ground becomes this interface's ink; its
 * luminous accents are re-cut for a light ground; the cream is its own marigold at four percent. One
 * mono at every structural size, a system sans for prose, FLAT — separation is drawn with 1px edges,
 * never cast with blur, and nothing is rounded. Acid green and hot pink survive only on the spine.
 *
 * MOBILE FIRST, because the operator asked for it and because the bar proved it: nine links in a row
 * cannot sit in a phone-width header. Every layout here is written narrow and widened at a
 * breakpoint, and the one thing that must never happen is a body that scrolls sideways — wide
 * content scrolls inside its own container instead.
 *
 * Every colour and size comes from tokens.css. A literal here is a token that escaped. Siblings are
 * laid out with flex or grid and `gap`; a margin on one element to push its neighbour is the thing
 * that makes two rules fight over the same pixels.
 */

*, *::before, *::after { box-sizing: border-box; }

/* LIGHT ONLY, said to the browser too: without this a dark-mode host paints its own form
 * controls and scrollbars dark against a white page. One theme has to be declared, not
 * merely implied by the palette. */
html { color-scheme: light; }

/* **THE PAGE IS A COLUMN: SPINE, DOCUMENT, DECK — and the deck takes its own space.**
 *
 * Checklist item 54, operator 2026-09-20: *"instead of an overlay you can make it a bottom section —
 * that way it does not hide the items on the top section."*
 *
 * The deck was `position: fixed` at the foot with `padding-bottom: 4rem` reserved under `<main>`,
 * which held while the deck was a 36px bar somebody chose to open. **Item 49 is what breaks it**:
 * the deck now opens ITSELF the moment anything stages, and the thing it opens over is whatever the
 * person was just working on. An overlay that appears over your work without being asked is worse
 * than one you opened, and no fixed reservation can be right — the panel's height depends on how
 * many rows are in it.
 *
 * So `<body>` is the column. The spine and the deck are its two fixed ends and `<main>` is the
 * elastic middle with its own scrollbar: the deck opening SHORTENS the document rather than covering
 * it, and everything the person had on screen is still reachable by scrolling the same container
 * they were already scrolling.
 *
 * **`min-height` RATHER THAN `height`, AND `min-height: 0` ON THE MIDDLE.** A flex item's default
 * `min-height: auto` refuses to shrink below its content, so `overflow-y: auto` on `<main>` would
 * never engage and the deck would be pushed off the bottom of the screen by a long page — the exact
 * failure this replaces, wearing a different mechanism.
 *
 * **`_deck`'s "outside `<main>`" ARGUMENT IS UNCHANGED BY THIS.** That argument is about the reading
 * measure: `.aid` is a two-column grid and
 * `test_EVERY_GRID_ITEM_OF_EVERY_REAL_PAGE_LANDS_IN_A_COLUMN` measures everything that falls out of
 * it, so a bar inside would be a new grid item on all nine pages. It still sits beside `<main>`.
 * What changed is only that it is in FLOW there instead of floating over it. */
body {
  margin: 0;
  /* **`height`, NOT `min-height`, AND THE DIFFERENCE IS THE WHOLE MECHANISM.** With `min-height` the
   * body's used height is still AUTO, so nothing ever forces `<main>` to shrink: it grew to its
   * content, the body grew with it, and the deck was pushed 76px below the fold — measured on the
   * live workstation at 1920×1080, deck top at y=1156. A definite height is what gives the flex
   * line something to distribute, which is what makes the middle the scroller and the deck an end. */
  height: 100vh;
  display: flex; flex-direction: column;
  background: var(--board);          /* explicit: the host paints its own ground behind this */
  color: var(--ink);
  font: 400 var(--t-body)/var(--lh-body) var(--body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
/* **`width: 100%` IS LOAD-BEARING AND LOOKED REDUNDANT.** `.aid` carries `margin: 0 auto` to centre
 * itself, which as a BLOCK meant "full width, then centre within the max-width". As a FLEX ITEM an
 * auto cross-axis margin cancels `stretch`, so the same two declarations shrink-wrapped it to its
 * content: measured on the live workstation at 1920, `main` came back 1353px wide at x=284 — a
 * surface that had just been given the screen and did not take it. The width restores the block
 * behaviour and `max-width` still caps the eight pages that want a measure. */
body > main.aid { flex: 1 1 auto; min-height: 0; width: 100%; overflow-y: auto; }

/* `hidden` must always win. The flex and grid display rules below would otherwise out-cascade the
 * UA's [hidden]{display:none} — equal specificity, later source order — and a `hidden` marker the
 * script has not yet placed would render as a visible blank where a real value belongs. */
[hidden] { display: none !important; }

/* ---- focus: visible everywhere, drawn in the key ------------------------------------------------
 * One rule for every focusable thing, so a control added later cannot arrive without one. On the
 * spine it switches to `--signal-true`, because magenta on that navy is not enough. */
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--key); outline-offset: 2px; }
::selection { background: var(--select); color: var(--ink); }

/* A LINK IS UNDERLINED. The open call emphasises with magenta — dates, names, key terms — so this
 * interface does too, and on a cream ground colour alone is not a signal a reader can rely on. The
 * exceptions below are the links that are not words in a sentence but whole ROWS and controls; an
 * underline drawn the width of a table row reads as a rule, not as a link. */
a { color: var(--key); text-decoration: underline; text-underline-offset: 2px;
    text-decoration-thickness: 1px; }
a:hover { color: var(--key-hover); }
.shelf__row a:hover, .month__step:hover { text-decoration: underline; }

/* ---- the spine: a strip of the open call, left in place ---------------------------------------
 * The one dark ground in the interface, and it is the poster's own — `--spine` IS `--ink`, the navy
 * every page of the open call is printed on. It never leaves the screen: "where are we" should cost
 * no navigation on a residency where nobody shares a room. Acid green and hot pink appear ONLY here,
 * because only here is the ground dark enough for them. */
.strip {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--s2) var(--s4);
  min-height: var(--titlebar-h);
  padding: var(--s2) var(--s4);
  background: var(--spine);
  color: var(--on-spine);
  box-shadow: inset 0 -1px 0 var(--line-spine);
  position: sticky; top: 0; z-index: 10;
  font: 400 var(--t-small)/var(--lh-mono) var(--mono);
}
.strip :focus-visible { outline-color: var(--signal-true); }
.strip__mark { display: inline-flex; align-items: baseline; gap: var(--s2); color: var(--on-spine);
               text-decoration: none; }
.strip__mark:hover { text-decoration: none; }
.strip__brand { font: 500 var(--t-body)/1 var(--display); letter-spacing: .02em; }
.strip__edition { font-size: var(--t-gutter); letter-spacing: .06em; text-transform: uppercase; color: var(--on-spine-muted); }

/* **THE NINE LINKS ARE A DISCLOSURE, AND CSS IS WHAT OPENS IT.**
 *
 * This bar carried nine links in a flat row. Eight already did not fit a phone — the layout guard
 * measured the document at 624px in a 320px viewport — and the answer then was to let the bar scroll
 * inside itself, which keeps the body honest and leaves a person on a phone swiping a 2mm-tall strip
 * to find a page whose name they cannot see. Operator direction: a hamburger.
 *
 * **IT MUST WORK WITH NO JAVASCRIPT**, and that is why the state is a CHECKBOX rather than a button.
 * The pages are server-rendered and the CSP admits no inline script, so a JS-only menu has two ways
 * to fail closed — a blocked script, a script that did not load — and either one strands somebody on
 * a phone with no way to reach any page at all. The checkbox is a REAL control: it is in the tab
 * order, Space toggles it, its accessible name comes from the label, and its checked state IS the
 * expanded state, announced by the browser rather than asserted by an attribute nothing updates.
 * (An `aria-expanded` written into the server's markup would be a static claim about a state CSS
 * changes underneath it, which is worse than none — see `render.strip`.)
 *
 * It is visually hidden rather than `display: none`, because a control that is not rendered is not
 * focusable, and the focus ring is drawn on the label it belongs to. */
.strip__toggle {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
.strip__burger {
  display: inline-flex; align-items: center; gap: var(--s2);
  min-height: var(--touch); padding: 0 var(--s3);
  margin-left: auto;
  border: 1px solid var(--line-spine);
  color: var(--on-spine);
  font: 500 var(--t-small)/1 var(--mono);
  letter-spacing: .06em; text-transform: uppercase;
  cursor: pointer;
}
/* THE BARS ARE DRAWN, not set in a glyph and not fetched as an icon: three 2px rules in the
 * current colour, which is the same way every other edge in this interface is made. */
.strip__bars { display: block; width: 16px; height: 2px; background: currentColor; position: relative; }
.strip__bars::before, .strip__bars::after {
  content: ""; position: absolute; left: 0; width: 16px; height: 2px; background: currentColor;
}
.strip__bars::before { top: -5px; }
.strip__bars::after  { top: 5px; }
/* OPEN IS SHOWN, not only announced. The control inverts — cream ground, navy type — so the state is
 * legible to somebody who is looking at the screen as well as to somebody who is listening to it. */
.strip__toggle:checked + .strip__burger { background: var(--on-spine); color: var(--spine); border-color: var(--on-spine); }
.strip__toggle:focus-visible + .strip__burger { outline: 2px solid var(--signal-true); outline-offset: 2px; }

/* CLOSED IS THE DEFAULT, because mobile is. The menu opens as a full-width column under the bar,
 * each row a 44px target with its own rule — a list of places, set the way this interface sets every
 * other list of things. */
.strip__nav { display: none; }
.strip__toggle:checked ~ .strip__nav {
  display: flex; flex-direction: column;
  flex: 1 0 100%;
  margin: var(--s2) calc(var(--s4) * -1) calc(var(--s2) * -1);
  border-top: 1px solid var(--line-spine);
}
/* **A NAV ITEM IS NOT A LINK IN A SENTENCE, SO IT IS NOT UNDERLINED.** The global rule underlines
 * every `a` — right for prose on a cream ground, where colour alone is not a signal — and it reached
 * into the spine, so all nine items carried a rule and the one you were ON looked like the other
 * eight. An underline that is always on cannot mark anything. Here the LIST is the affordance: nine
 * words in a bar are obviously navigation, and what the reader needs told is which one is current. */
.strip__nav a {
  display: flex; align-items: center;
  min-height: var(--touch); padding: var(--s2) var(--s4);
  color: var(--on-spine-muted);
  text-decoration: none;
  border-bottom: 1px solid var(--line-spine);
}
.strip__nav a:hover { color: var(--on-spine); text-decoration: underline;
                      text-underline-offset: 4px; }
/* WHICH PAGE YOU ARE ON, in the cyan the open call prints its own structure in — and marked by a
 * RULE as well as by colour, so it survives a reader who cannot tell the two apart. */
.strip__nav a[aria-current="page"] {
  color: var(--on-spine);
  box-shadow: inset 2px 0 0 var(--signal-true);   /* a rule down the side, in the menu's column form */
}
/* THE BAR AT A DESK. Above this width the nine links fit in a row, so the disclosure is put away
 * and the nav becomes the row it always was. Both selectors are restated, because
 * `.strip__toggle:checked ~ .strip__nav` outranks `.strip__nav` and would otherwise hold a menu
 * opened on a phone in its column form after the window was widened. */
@media (min-width: 64rem) {
  .strip { gap: var(--s3) var(--s6); padding: var(--s2) var(--s6); }
  .strip__toggle, .strip__burger { display: none; }
  .strip__nav, .strip__toggle:checked ~ .strip__nav {
    display: inline-flex; flex-direction: row; flex: 0 1 auto;
    gap: var(--s4); margin: 0; border-top: 0;
  }
  .strip__nav a {
    display: inline; min-height: 0; padding: var(--s1) 0;
    white-space: nowrap; border-bottom: 0;
  }
  /* **THE ONE UNDERLINE IN THE BAR IS THE PAGE YOU ARE ON.** Drawn as a 2px rule rather than
   * `text-decoration`, so it sits clear of the descenders and reads as a marker rather than as a
   * link that happens to be styled differently from its eight neighbours. */
  .strip__nav a[aria-current="page"] { box-shadow: inset 0 -2px 0 var(--signal-true); }
}
/* ---- the page: a gutter and a column ---------------------------------------------------------- */

.aid {
  display: grid;
  grid-template-columns: var(--gutter-w) minmax(0, 1fr);
  gap: 0;
  max-width: var(--aid-w);
  margin: 0 auto;
  padding-bottom: var(--s16);
}

/* **ONE SURFACE WANTS THE SCREEN, AND IT SAYS SO BY WHAT IT CONTAINS.**
 *
 * Checklist item 47, operator 2026-09-20: *"it will be nice if it is properly responsive — from
 * extended monitor to a mobile screen."* On their ~2000px monitor the workstation sat in a column
 * 1248px wide with 336px of empty ground either side — measured, not estimated — while its FLOATING
 * WINDOWS, which are `position: fixed` and therefore placed against the VIEWPORT, landed outside
 * that column: `#win-messages` at x=220 stranded in the left gutter, `#win-session` flush against
 * the right edge at x=1526. The windows were not misplaced. The surface they belong to was.
 *
 * `--aid-w` is a READING measure and it is right for the eight pages that are documents — a person
 * should not track a line of prose across two feet of glass. The folder view is not a document: it
 * is a desktop with a sidebar, a canvas of tiles and panels floating over it, and every pixel it is
 * denied is a pixel of the archive somebody cannot see at once.
 *
 * **IT IS `:has(.ws)` RATHER THAN A CLASS THE PAGE PASSES, and that is the smaller of the two.** A
 * `wide=True` argument through `render.page` would be a second place the decision lives — one that
 * a new workstation-bearing page can forget, silently, and render in a column again. The surface
 * that wants the screen is exactly the surface that contains a workstation; asking that question
 * directly cannot go stale. `body:has(.deck) main.aid` above already relies on the same selector.
 *
 * **SPECIFICITY, STATED RATHER THAN HOPED FOR** — the house rule, after four rounds lost to a rule
 * that simply did not win: `main.aid:has(.ws)` is (0,2,1) and beats `.aid`'s (0,1,0). Checked on the
 * live element with `getComputedStyle`, not read off this sheet. */
main.aid:has(.ws) { max-width: none; }

.entry { display: contents; }

/* THE SIGNATURE. A fixed left margin carrying each item's reference and provenance in mono, always
 * visible — never a tooltip, never behind a hover, never a detail pane.
 *
 * THIS IS THE DELIBERATE RISK and the one element to keep if everything else goes. Every instinct
 * says cut it to a hover: it costs a margin, it repeats down the page, it is "metadata". Cutting it
 * would make this look like every other dashboard and would quietly contradict the thing it exists
 * to argue — a residency about the origin of sources that hides the origin of its own.
 *
 * Survivable because it is QUIET: `--ink-muted`, 11px, aligned, and SUNKEN — the track is the
 * register's inset surface, so the margin reads as the system's plate beside a person's prose. You
 * read past it until the moment you need it, which is what a margin is for. */
.gutter {
  grid-column: 1;
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--s4) var(--s6);
  background: var(--board-sunken);
  border-right: 1px solid var(--line);
  border-top: 1px solid var(--line);
  font: 400 var(--t-gutter)/var(--lh-mono) var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
  overflow-wrap: anywhere;            /* an ss58 address must not widen the column */
}
.gutter__line { display: block; }
.gutter__ref { color: var(--ink); font-weight: 500; }
.gutter__line--time { color: var(--foxing); }   /* TIME ONLY — see tokens.css */
/* A kuri or an address the script has turned into a kuriscan link keeps wrapping: the register's own
 * `a.hash` is `nowrap`, which in a 15.5rem margin would push a 64-character hash through the prose. */
.gutter .hash { white-space: normal; overflow-wrap: anywhere; font-family: inherit; }
.gutter .hash .ext { width: 9px; height: 9px; margin-left: 2px; vertical-align: -1px; display: inline-block; }

.content {
  grid-column: 2;
  padding: var(--s4) var(--s8);
  border-top: 1px solid var(--line);
  max-width: var(--measure);
}
.content > :first-child { margin-top: 0; }
.content > :last-child { margin-bottom: 0; }

/* IN A FINDING AID A HEADING IS STRUCTURE, so headings are set in the mono — the face the open call
 * sets nearly everything in — and the prose beneath them is the only thing in the sans. The scale
 * runs 28 / 22 / 18 off the tokens, the tracking tightens as the size grows, and the weight comes
 * DOWN as it grows: DM Mono ships 300-500, and a big heading at 500 in a monospace is a wall. */
h1, h2, h3 {
  font-family: var(--display); line-height: var(--lh-tight);
  letter-spacing: -.01em; text-wrap: balance; margin: 0 0 var(--s3);
}
h1 { font-size: var(--t-title); font-weight: 400; }
h2 { font-size: var(--t-head);  font-weight: 400; }
h3 { font-size: var(--t-lead);  font-weight: 500; }

p { margin: 0 0 var(--s4); }
/* Prose keeps its MEASURE and its INK. Muting the thing a person came to read is how a cream-and-
 * black surface throws away the contrast it was chosen for. */
.content p { max-width: var(--measure); color: var(--ink); }

/* ---- facts the system states, rather than prose a person wrote -------------------------------- */

.fact { font-family: var(--mono); font-size: var(--t-small); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.fact--time { color: var(--foxing); }           /* TIME ONLY */
.eyebrow {
  margin: 0;
  font-family: var(--mono); font-size: var(--t-gutter);
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-muted);
}

/* UNKNOWN RENDERS AS UNKNOWN. A count the system could not obtain shows the em dash, never 0 (data
 * model §1.1) — and it must not look like an error either, because "we could not count" is a resting
 * state here rather than a fault. */
.count--unknown { color: var(--ink-muted); font-family: var(--mono); }

/* Two zones on every time. Not a preference — the residency is distributed, so a single-zone time is
 * an incomplete fact, and Singapore is always one of the two because that is Padimai's working day. */
.when { font-family: var(--mono); font-size: var(--t-small); font-variant-numeric: tabular-nums; }
.when__local { color: var(--ink-muted); }

/* ---- the three things a gutter can say about permanence ----------------------------------------
 * Anchored, anchoring, and not looked up. Told apart by FORM before colour — a glyph each — so a
 * reader who cannot see the colours, or a greyscale print, still reads three different marks:
 *
 *   ■  anchored     a filled square in `--ok` — the record is on the chain, this is its address
 *   □  anchoring    a solid outline in `--signal` — it exists, and its permanence is in flight
 *   ⬚  unfetched    a dashed outline, faint — this page did not look, and says so
 *
 * The glyph rules come FIRST and the text rules LAST, deliberately: the guards read the LAST rule
 * whose selector names a state, and that rule has to be the one carrying the line's colour. */
.gutter__kuri::before {
  content: "";
  display: inline-block;
  width: 8px; height: 8px;
  margin-right: var(--s2);
  vertical-align: 0;
}
.gutter__kuri[data-state="anchored"]::before  { background: var(--ok); }
.gutter__kuri[data-state="anchoring"]::before { border: 1px solid var(--signal); }
.gutter__kuri[data-state="unfetched"]::before { border: 1px dashed var(--ink-faint); }

/* ---- motion: one place, deliberately ----------------------------------------------------------
 * When a record's anchor lands, the gutter's address resolves from `anchoring…` to its kuri. That is
 * the moment the record becomes permanent — the one state change in this system worth marking. */
.gutter__kuri { transition: color 420ms var(--ease); }
/* NO OPACITY ON THE ANCHORING LINE. It once read `opacity: .6`, which took the margin's colour from
 * 5.16:1 down to 2.40:1 at 11px — below any threshold, on the single element this design argues
 * hardest for. `--signal` at full strength is 5.22 on the cream; italic plus the word says the
 * state; and a state a reader cannot READ is not a state. */
.gutter__kuri[data-state="anchoring"] { color: var(--signal); font-style: italic; }
.gutter__kuri[data-state="anchored"]  { color: var(--ink); }
/* THE THIRD STATE, beside the two it must not be mistaken for — and NOT in the same voice.
 * `anchoring…` is TRANSIENT and rare, worth catching the eye for a few seconds, so it keeps the
 * italic and the signal. "Permanence not fetched" is on EVERY row of a resolved calendar — italicising
 * all of them would put a page of emphasis on the one fact that is the same everywhere, and emphasis
 * that never varies is noise. So the third state is plain: the margin's own colour, the margin's own
 * face, a dashed square. It recedes, which is what a constant should do. */
.gutter__kuri[data-state="unfetched"] { color: var(--ink-muted); }

/* == the pages ================================================================================= *
 *
 * Everything below composes the shell above into whole pages. No new colours, no new type sizes —
 * `tokens.css` is the only place either is named, and a component file that invents one is the
 * thing that erodes a palette.
 */

/* -- where the residency is: home's one memorable element ------------------------------------- *
 * Home's whole job is "where am I", so the answer is set large in the display face, over the facts
 * a date can support on its own. THE FAILURE SENTENCE IS THE SAME SIZE as the success
 * one: a failure that shrinks is a failure that gets missed, and the read layer went to some trouble
 * to produce that sentence rather than a blank. */
/* THE ONE MOTIF THE LOGGED-IN VIEW CARRIES, and it is at WATERMARK strength: the wash is baked into
 * the asset at 13-22% and cannot be drawn stronger by a rule here, which is the point of baking it.
 * The object — the open call's hero, and the residency's own word for what it is about — sits in the
 * corner the measure does not use, anchored to an edge rather than centred, and never repeated. Full
 * strength belongs to the public page; this is the shared view, and it is quieter on purpose. */
.where {
  grid-column: 1 / -1;
  display: flex; flex-direction: column; gap: var(--s6);
  padding: var(--s12) var(--s6) var(--s8);
  border-bottom: 1px solid var(--line-strong);
}
/* SCOPED TO THE PAGE THAT DECLARED THE MOTIF, not to `.where` itself. `.where` is the opening block
 * of home, of the feed, AND of every not-built-yet page — drawing the object on the class would have
 * put the residency's hero behind five stub pages saying "not built yet", which is one motif per
 * page in the letter and four wrong pages in the spirit. `render.page(motif=...)` names it once, per
 * page, and this rule draws it. */
.aid--object .where {
  background-image: url("/static/motif-object.webp");
  background-repeat: no-repeat;
  background-position: right -2rem top -3rem;
  background-size: 16rem auto;
}
/* The other three, each anchored to the edge its own crop was cut for: the bloom is a tall column
 * from the left of page 5, the scan sits low and right, the lattice is line art from the top of
 * page 3. Anchored rather than centred, because a centred motif sits under the measure and an
 * edge-anchored one sits in the space the column does not use. */
.aid--bloom, .aid--scan, .aid--lattice { background-repeat: no-repeat; }
/* **THE BLOOM IS ANCHORED RIGHT, AND ITS OWN CROP IS LEFT-HANDED.** Left is where it sits on page 5
 * of the open call and where it was first put here — and on the notice board the left 15.5rem is
 * the gutter, so the wash ran under every notice's date, author and Edit link. The rule is that a
 * motif sits in the space the COLUMN DOES NOT USE, and on this page that space is to the right of
 * the measure. The rule outranks the provenance. */
/* **SIZED SO THE PLATE STAYS A MARGIN NOTE.** Measured at 1280: the scan at `22rem auto` scaled a
 * tall crop to the full height of the page and covered a third of the folder listing, including the
 * one control on it — a motif that is the page rather than beside it. Each is now bounded on its
 * SMALLER dimension and overhangs the edge it is anchored to, so it reads as a plate cropped by the
 * page rather than as a picture that did not fit. They start past x≈1080 at 1280, which is clear of
 * the 66ch measure. */
/* **BOUNDED ON THE HEIGHT, BECAUSE ALL THREE CROPS ARE TALL.** Measured at 1280: `16rem auto`
 * bounds the WIDTH of a 380x1179 crop and lets it run 700px down the page — the scan covered a
 * third of the folder listing and the one control on it, which is a motif that IS the page rather
 * than one beside it. Each is sized by its height and set INSIDE the margin rather than overhanging
 * it, so what you see is the whole plate rather than a picture that did not fit: a strip of the open
 * call standing in the right margin, opposite the finding aid's own gutter on the left. */
.aid--bloom   { background-image: url("/static/motif-bloom.webp");
                background-position: right 2rem top 4rem; background-size: auto 28rem; }
.aid--scan    { background-image: url("/static/motif-scan.webp");
                background-position: right 2rem bottom 2rem; background-size: auto 26rem; }
.aid--lattice { background-image: url("/static/motif-lattice.webp");
                background-position: right 2rem top 3rem; background-size: auto 22rem; }
/* AND NOT ON A PHONE. Below the width at which the measure stops leaving a margin, there is no
 * space the column does not use — so the plate would sit under the prose rather than beside it. */
@media (max-width: 52rem) {
  .aid--bloom, .aid--scan, .aid--lattice, .aid--object .where { background-image: none; }
}
.lede {
  margin: 0;
  max-width: 30ch;
  font-family: var(--display);
  font-weight: 300;
  font-size: var(--t-hero);
  line-height: var(--lh-tight);
  letter-spacing: -.01em;
  color: var(--ink);
  text-wrap: balance;
}
/* NOT smaller, NOT greyer, and NOT alarming — each of those a decision this rule had to get right.
 * `--foxing` means what TIME is doing, and "the residency cannot say where it is" is a statement
 * about the ARCHIVE. `--danger` is worse: the commonest reason this sentence appears is a residency
 * with no manifest yet, which is day one, entirely normal, and not an error anybody should see in
 * red. So it keeps `--ink` at full size and is marked STRUCTURALLY — a rule in the margin's grammar. */
.lede--unknown {
  border-left: 3px solid var(--line-strong);
  padding-left: var(--s4);
}
.facts { margin: 0; display: flex; flex-wrap: wrap; gap: var(--s2) var(--s6); }

/* -- sections ---------------------------------------------------------------------------------- *
 * `display: contents`, and this is the one rule in the file that MUST NOT be simplified away.
 *
 * `.entry` is itself `display: contents` (above) so that `.gutter` and `.content` become items of
 * `.aid`'s two-column grid — that is the whole mechanism behind the fixed left margin, which this
 * file calls "THE SIGNATURE ... the one element to keep if everything else goes".
 *
 * Wrapping entries in `<section>` broke it, silently and completely. A `.section` with its own box
 * makes the gutter and the content items of the SECTION rather than of `.aid`; `grid-column` on
 * them is then inert, and every gutter renders full-width above its own content, on every screen
 * size, with `--gutter-w` unused. Nothing raised, nothing failed, and the page's entire argument —
 * that a residency about the origin of sources does not hide the origin of its own — was switched
 * off by a wrapper added for a heading.
 *
 * So the section contributes no box: its heading and its prose are grid items in their own right,
 * spanning both columns, and the entries below them land in `.aid` exactly as they did before.
 */
.section { display: contents; }
/* EVERY direct child a `display: contents` section leaves behind must span the grid itself. A list
 * missing from here auto-places into column 1 — `--gutter-w`, 15.5rem — with column 2 empty, and
 * nothing raises. The list this rule was written for (`.phases`, the timeline's own) was missing on
 * its first pass and rendered the whole page inside the gutter's width. That list is gone with the
 * phases; the rule it taught is why every other name here is listed.
 *
 * CSS has no `#` line comment and no `//`. A `#:` written above this selector out of Python habit
 * was read as the start of a prelude, ran on to the `{` below, and swallowed this rule whole — every
 * section heading and the outage sentence on four pages rendered inside the gutter's 15.5rem, and
 * the suite stayed green because nothing PARSED the stylesheet. `test_EVERY_RULE_IN_THE_STYLESHEET_
 * IS_A_RULE` now does. */
.section__title, .section__note, .section .empty,
/* `.feed__row` joins these: once `.feed` and `.feed__list` are transparent the ROWS are items of
 * `.aid`, so each must claim the full measure or it lands in the gutter column — which is what the
 * layout guard means by an item that does not land in a column. */
.register, .fields, .signin, .feed__row,
/* `.notices` and `.compose` join them for the same reason, and `.section__action` with them: each is
 * a direct child of a transparent section, so any one of them left out auto-places into the gutter's
 * 15.5rem and renders a whole page of announcements inside the margin. */
.notices, .compose, .section__action,
/* The library's two: the trail across the top of the folder, and the folder's own
 * listing. Both are direct children of a transparent section, so both must claim the
 * grid or they auto-place into the 15.5rem margin — which the layout guard refuses
 * and which would otherwise render a whole folder inside the gutter. */
.crumbs, .shelf,
/* `.more` joins them: the feed's way forward is a direct child of `.aid`, after the transparent
 * `.feed`, so left out it auto-places into the 15.5rem gutter and the one control that reaches the
 * rest of the archive renders inside the margin. */
.more,
/* And the calendar's, for the same reason as every other name on this list. */
/* `.grid` and `.month__views` join them: both are direct children of a transparent section,
 * and the calendar grid auto-placed into the 15.5rem gutter is seven columns inside a margin. */
/* And the chain panel: it is a direct child of a transparent section, so left off this list it
 * auto-places into the 15.5rem gutter and the whole of what the chain says renders inside a margin.
 * It is a REAL BOX rather than `display: contents` on purpose — the note, the rows and the control
 * belong to one another, and `chain.js` redraws its middle from a payload, which wants a container
 * that stays put rather than three items of the page's own grid. */
.chain,
/* And the issue page's two: the remedy block and the list of the four checks. Both are direct
 * children of a transparent section, so either one left off this list auto-places into the 15.5rem
 * gutter — and on that page the block rendered unreadable would be the one explaining why nothing
 * else works. */
.preflight, .preflight__list,
/* And the relationships page's list, both times it is rendered — what a record says, and what says
 * something about it. Each is a direct child of a transparent section, so left off this list it
 * auto-places into the 15.5rem gutter and a page of lookup paths renders inside the margin. That is
 * the fourth defect of exactly this shape in this file, which is why the name joins the list in the
 * same edit that introduces the class rather than after somebody notices. */
.edges,
.month, .month__nav, .month__views, .grid, .withdraw { grid-column: 1 / -1; }
.section__title {
  margin: var(--s8) 0 0;
  padding: 0 var(--s6) var(--s2);
  font-family: var(--mono);
  font-size: var(--t-gutter);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
  border-bottom: 1px solid var(--line-strong);
}
.section__note {
  margin: var(--s3) 0 var(--s2);
  padding: 0 var(--s6);
  font-family: var(--mono);
  font-size: var(--t-gutter);
  line-height: 1.6;
  color: var(--ink-muted);
}
/* the measure is on the TEXT, so the paragraph still spans the grid (the layout guard's rule) */
.section__note-text { display: block; max-width: var(--measure); }

.entry__title {
  margin: 0 0 var(--s2);
  font-family: var(--display);
  font-size: var(--t-lead);
  font-weight: 500;
  line-height: var(--lh-tight);
  letter-spacing: -.005em;
  color: var(--ink);
  text-wrap: balance;
}
.entry__body { margin: 0; max-width: var(--measure); font-size: var(--t-body); line-height: var(--lh-body); }
.entry__body--bare { color: var(--ink-muted); font-style: italic; }

/* -- the two silences, which must not look alike ----------------------------------------------- *
 * "Nothing yet" is the residency being quiet. "Could not be read" is the archive being unreachable.
 * They occupy the same space, so they are told apart by weight and by a rule the eye catches —
 * NOT by colour alone, which would leave them identical to a reader who cannot distinguish it.
 * `.section .empty` would be (0,2,0) and beat `.empty--blind`'s (0,1,0) padding at any order, so
 * both indents are set at the same specificity. */
.empty { margin: var(--s4) 0 0; padding-left: var(--s6); font-size: var(--t-body); color: var(--ink-muted); font-style: italic; }
.empty--blind {
  font-style: normal;          /* quiet is italic; unreachable is not */
  color: var(--ink);           /* quiet is muted; this is a statement, not an aside */
  margin-left: var(--s6);
  border-left: 3px solid var(--line-strong);
  padding-left: var(--s4);
}

/* -- the directory ------------------------------------------------------------------------------ *
 * **IT WAS A RULED LIST AND IS NOW A CARD PER PERSON** (operator direction, 2026-09-19: *"I want it
 * look like a directory page with rendering each actant with their profile pic"*). The list said id,
 * title and email, which is a finding aid for nine rows and tells you nothing about anybody; a
 * residency page about the people in it should show the people in it.
 *
 * The cards flow into as many columns as the measure allows rather than a fixed number, so nine
 * people read as a directory on a laptop and as one column on a phone with nothing to switch. */
.register {
  list-style: none; margin: 0; padding: var(--s4) var(--s6);
  display: grid; gap: var(--s4);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 22rem), 1fr));
}
/* The person's own card. `align-items: start` so the picture sits at the top of a tall card rather
 * than floating in the middle of a bio. */
.card--person { padding: var(--s4); }
.card__id { margin: var(--s1) 0 0; font-family: var(--mono); font-size: var(--t-gutter);
            line-height: var(--lh-mono); color: var(--ink-faint); }
.card__role { margin: var(--s1) 0 0; font-size: var(--t-small); color: var(--ink-muted); }
.card__mail { margin: var(--s2) 0 0; font-family: var(--mono); font-size: var(--t-gutter);
              line-height: var(--lh-mono); color: var(--ink-muted); overflow-wrap: anywhere; }
/* A minted address with no way to sign in yet — a real state, and one a coordinator has to be able
 * to see at a glance rather than deduce from an absence. It is carried by the WORDS ("no email yet —
 * cannot sign in"), not by a colour: it is neither an error nor a deadline, and the two palette
 * entries that would have made it shout are spoken for. */
.card__mail--awaiting { color: var(--ink-muted); font-style: italic; }
.card__prose { margin: var(--s3) 0 0; font-size: var(--t-small); line-height: var(--lh-body); }
.card__prose p { margin: 0 0 var(--s2); }
.card__links { list-style: none; margin: var(--s3) 0 0; padding: 0;
               display: flex; flex-wrap: wrap; gap: var(--s1) var(--s3);
               font-family: var(--mono); font-size: var(--t-gutter); line-height: var(--lh-mono); }
.card__link { min-width: 0; overflow-wrap: anywhere; }
/* THE PICTURE, AND THE FIGURE THAT STANDS IN FOR ONE. Square-cropped so a dozen aspect ratios line
 * up down a column; the placeholder is an inline `<svg>` drawn in `currentColor` rather than an
 * image file, because `img-src 'self'` forbids a `data:` URI and a new asset would need adding to
 * the four `serve.IMAGES` publishes. Faint, because it is an absence rather than a portrait. */
.card__photo { display: block; width: 4.5rem; height: 4.5rem; object-fit: cover;
               border: 1px solid var(--line); }
.card__photo--none { color: var(--line-strong); background: var(--board-sunken); }

/* -- a profile's fields ------------------------------------------------------------------------ */
.fields { list-style: none; margin: 0; padding: 0; }
.field {
  display: grid;
  grid-template-columns: var(--gutter-w) minmax(0, 1fr);
  gap: var(--s6);
  padding: var(--s3) var(--s6);
  border-bottom: 1px solid var(--line);
}
.field__name { font-family: var(--mono); font-size: var(--t-gutter); color: var(--ink-muted); }
/* **A 64-CHARACTER HASH IS ONE WORD TO A BROWSER**, so without this it pushes its own column past
 * the edge of the screen and the whole page scrolls sideways — which is the one layout fault this
 * register does not permit, and which arrives as "the header looks broken" rather than as itself.
 * The chain panel and the profile both put an ss58 address and a kuri in this slot; the gutter
 * already carries the same rule, for the same reason. */
.field__value { font-family: var(--body); font-size: var(--t-body); color: var(--ink);
                max-width: var(--measure); min-width: 0; overflow-wrap: anywhere; }

/* -- what the chain says ----------------------------------------------------------------------- *
 * THE ONE PAGE THAT ASKS THE CHAIN, so the one panel that has to say out loud when it last asked.
 * `.chain__note` is the state line `chain.js` rewrites on every poll — "Asked just now", "cached a
 * moment ago", "Stopped following" — and it is the whole of the honesty of a page that stops
 * updating by design. Set in the mono, because it is the system's own voice about its own work
 * rather than anything the residency wrote. */
.chain { padding: 0 0 var(--s4); }
.chain__note {
  margin: var(--s4) 0 0;
  padding: 0 var(--s6);
  font: 400 var(--t-gutter)/var(--lh-mono) var(--mono);
  letter-spacing: .04em;
  color: var(--ink-muted);
}
/* A FAILED ASK IS NOT A WARNING COLOUR ALONE. `--danger` carries it for the people who can see it,
 * and the rule on the left carries it for the people who cannot — the same pairing `.empty--blind`
 * makes, and for the same reason: the two states occupy one line of one page. */
.chain__note--bad {
  color: var(--danger);
  border-left: 3px solid var(--danger);
  padding-left: var(--s4);
  margin-left: var(--s6);
}
/* The control sits where every other page's action sits — flush right, under what it acts on. It
 * is hidden by `chain.js` while the follow is running, so most of the time this rule draws nothing;
 * with no script it never hides, and is then an ordinary link back to the route. */
.chain__go { display: flex; justify-content: flex-end; margin: 0; padding: var(--s4) var(--s6) 0; }
/* -- the blocks, drawn as blocks ---------------------------------------------------------------- *
 * Operator direction, 2026-09-19: *"make the list - look like blocks - use good gprahics"*. It was
 * a two-column table of a number and a sixty-four character hash: the hash ran off the right edge
 * of a 768px page, and even where it fitted it was texture rather than information.
 *
 * So each block is a CARD — a drawn 1px edge on raised paper, the number loud in the mono, the
 * shortened hash quiet beneath it — and the cards are JOINED, so a column of them reads as a chain
 * rather than as rows. Flat and hard-edged like everything else here: no radius, no shadow, no
 * gradient. The open call is drawn blocks and rules from its first page to its last.
 *
 * **THE CONNECTOR IS DRAWN, NOT IMPORTED.** This app ships no JavaScript dependencies and the CSP
 * admits no external ones, so a chain graphic is a border on a pseudo-element — which is also the
 * form that survives a card being pushed on top of the list without anything rewriting the old
 * one. `.blk + .blk` is every card but the newest, which is exactly the set that has a link
 * upwards. */
.chainlist { list-style: none; margin: 0; padding: var(--s4) var(--s6) 0; }
.blk { display: block; }
.blk + .blk::before {
  content: "";
  display: block;
  width: 0;
  height: var(--s4);
  margin-left: var(--s6);
  border-left: 2px solid var(--line-strong);
}
/* A GAP IN WHAT WAS SEEN, and it is the ordinary case rather than a fault — ten-second polls on a
 * six-second chain miss most blocks. Longer and BROKEN: the connector says "there is more between
 * these two" without claiming to know what, which is the honest shape for a page that will not ask
 * the node to fill it in. Marked by more than colour, so it reads for everybody. */
/* **`.blk + .blk--gap`, NOT `.blk--gap`** — the rule above it is (0,2,1) and the short form is
 * (0,1,1), so a bare `.blk--gap::before` loses on specificity and every gap drew as an adjacency
 * with the class correctly applied and the markup correctly generated. Caught by measuring the
 * computed `border-left-style` rather than the class list, which is the difference between proving
 * the bookkeeping and proving a reader can see it. */
.blk + .blk--gap::before { height: var(--s8); border-left-style: dashed; }
.blk__card {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s1) var(--s4);
  padding: var(--s3) var(--s4);
  background: var(--board-raised);
  border: 1px solid var(--ink);
  transition: border-color var(--t-fast);
}
.blk__card:hover { border-color: var(--key); }
/* THE NUMBER IS THE LOUD THING — it is what moves, and in the old table it wore the muted weight of
 * a column label, which made the one live value on the page its quietest. */
.blk__no { font: 500 var(--t-head)/1 var(--mono); font-variant-numeric: tabular-nums; }
.blk__no a { color: var(--ink); text-decoration: none; }
.blk__no a:hover { color: var(--key); text-decoration: underline; }
/* AND THE HASH IS THE QUIET ONE. Shortened in the middle by `pages._short_hash`, whole in its
 * `title`, and still a link — `anywhere` stays as a floor under a value that arrives longer than
 * this page expects. */
.blk__hash { font: 400 var(--t-gutter)/var(--lh-mono) var(--mono); overflow-wrap: anywhere; }
.blk__hash a { color: var(--ink-muted); }
.blk--none { padding: 0; }

/* -- signing in -------------------------------------------------------------------------------- */
/* The form SPANS the grid like every other section child; it is the field that has a measure. A
 * `max-width` on the form itself left it 640px of 1248 — an item in neither column, which the layout
 * guard refuses because that is the exact shape of a rule swallowed above it. */
.signin {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--s4);
  padding: var(--s4) var(--s6) 0;
}
/* NOT uppercased: the code step's label carries the address the code went to, and an address
 * shown in capitals is an address shown wrong. */
.signin__label {
  flex: 1 0 100%;
  font-family: var(--mono);
  font-size: var(--t-small);
  color: var(--ink-muted);
  overflow-wrap: anywhere;
}
.signin__field {
  flex: 1 1 18rem;
  max-width: 28rem;
  min-height: var(--touch);
  font: 400 var(--t-body)/var(--lh-mono) var(--mono);
  color: var(--ink);
  background: var(--board-raised);
  border: none;
  box-shadow: inset 0 0 0 1px var(--line-strong);
  padding: var(--s3) var(--s4);
}
.signin__field:focus-visible { box-shadow: inset 0 0 0 1px var(--key); outline: 2px solid var(--key); outline-offset: 1px; }
.signin__go {
  font: 500 var(--t-small)/1 var(--mono);
  letter-spacing: .04em; text-transform: uppercase;
  color: var(--board);
  background: var(--ink);
  border: 1px solid var(--ink);
  min-height: var(--touch);
  padding: var(--s3) var(--s8);
  cursor: pointer;
  white-space: nowrap;
}
.signin__go:hover { background: var(--key); border-color: var(--key); color: var(--key-ink); }
.signin__go:focus-visible { outline: 2px solid var(--key); outline-offset: 2px; }
/* While the script has a code in flight the button says so and cannot be pressed twice — the one
 * thing that would spend a person's rate-limit budget on their own impatience. */
.signin__go[disabled] { opacity: .55; cursor: progress; }

/* -- a refusal page ---------------------------------------------------------------------------- */
.oops { grid-column: 1 / -1; }

/* ---- motion off ------------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .gutter__kuri { transition: none; }
}

/* ---- narrow ----------------------------------------------------------------------------------
 * The gutter goes ABOVE its content rather than away. Dropping it on a phone would make provenance a
 * desktop privilege, and the reader on a phone is the one most likely to be checking who made
 * something. The sunken track becomes a sunken band across the top of each entry. */
@media (max-width: 46rem) {
  .aid { grid-template-columns: minmax(0, 1fr); }
  .gutter { grid-column: 1; border-right: 0; padding: var(--s3) var(--s4) var(--s3); }
  .content { grid-column: 1; border-top: 0; padding: var(--s3) var(--s4) var(--s4); }
  .where { padding: var(--s8) var(--s4) var(--s6); }
  .section__title, .section__note, .empty { padding-left: var(--s4); padding-right: var(--s4); }
  .empty--blind { margin-left: var(--s4); padding-left: var(--s3); }
  .strip { padding: var(--s2) var(--s4); gap: var(--s2) var(--s4); }
  .field { grid-template-columns: minmax(0, 1fr); gap: var(--s1); padding-left: var(--s4); padding-right: var(--s4); }
  /* `.person` is gone with the ruled register; the directory's own grid already collapses to one
   * column by `minmax(min(100%, 22rem), 1fr)`, so all the narrow layout owes it is the page's
   * narrow padding. */
  .register { padding-left: var(--s4); padding-right: var(--s4); }
  .feed__row { padding-left: var(--s4); padding-right: var(--s4); }
  .more { padding-left: var(--s4); padding-right: var(--s4); }
  .signin { padding-left: var(--s4); padding-right: var(--s4); }
  .chain__note, .chain__go { padding-left: var(--s4); padding-right: var(--s4); }
  .chain__note--bad { margin-left: var(--s4); padding-left: var(--s3); }
  .chainlist { padding-left: var(--s4); padding-right: var(--s4); }
  .blk + .blk::before { margin-left: var(--s4); }
}


/* ---- the feed (§1a) — one row is one timeline entry, and never opens its subject --------------- */
.feed { display: contents; }
/* TRANSPARENT TOO, and the guard is right to insist. `.feed` alone left `ul.feed__list` as the one
 * opaque child of `.aid`, so the rows were items of the LIST rather than of the page grid — they
 * would have sat in their own column and stopped agreeing with every other page's measure. The
 * transparency has to reach the rows, which is the same rule `.entry` follows. */
.feed__list { list-style: none; margin: 0; padding: 0; display: contents; }
/* **`font: var(--mono)` WAS NOT SETTING THE MONO.** The `font` shorthand requires a size and a
 * family together, so `font: var(--mono)` is an invalid declaration and the browser drops it in
 * silence: the verb, the actant and the date on every row of the home page were rendering in the
 * body sans, at `.75rem` and `.8rem` — two sizes that are in no scale — while the stylesheet said
 * otherwise. `font-family`, and sizes out of the tokens. */
/* **THE POST IS PAPER; THE PAGE IS THE DESK.** Operator direction, 2026-09-19: "the notes should
 * be in white". A row used to sit directly on the marigold ground with only a rule under it, so a
 * post was a region of the page rather than a thing ON it — and the attached record, being the one
 * white block in the row, read as the post itself. White here puts the note on top and leaves the
 * ground to be ground.
 *
 * The hairline stays as the separator rather than each row becoming a detached card: `.feed` and
 * `.feed__list` are `display: contents`, so the rows are items of the finding aid's own grid and
 * detaching them would mean inventing a row gap the grid does not have. A continuous white column
 * ruled into posts is the same reading and does not fight the layout. */
.feed__row { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--s4);
             align-items: start; padding: var(--s6);
             background: var(--board-raised);
             border-block-end: 1px solid var(--line); }
/* **EVERY ROW CARRIES A FACE**, the filed picture or the drawn figure — see `pages._face`. A column
 * of posts where only some have an avatar has a ragged left edge, and the eye reads that as rows of
 * two different kinds rather than one kind with a gap. */
.feed__face { display: block; width: 2.5rem; height: 2.5rem; object-fit: cover;
              border: 1px solid var(--line); background: var(--board-sunken); }
.feed__face--none { color: var(--line-strong); padding: 0; }
.feed__said { min-width: 0; display: grid; gap: var(--s2); }
/* THE HEADER OF A POST: who, what they did, and when — one quiet line above the words they wrote.
 * `baseline` rather than `center`, because three different faces and sizes sit on it. */
.feed__by { margin: 0; display: flex; flex-wrap: wrap; align-items: baseline;
            gap: var(--s1) var(--s2); font-family: var(--mono); font-size: var(--t-gutter);
            line-height: var(--lh-mono); color: var(--ink-muted); }
.feed__verb { font-size: var(--t-gutter); color: var(--signal);
              text-transform: uppercase; letter-spacing: .06em; }
/* THE NAME, IN FULL INK AND NOT A LINK. It was an `<a>` to `/actants#<id>` and the title beside it
 * was an `<a>` whose href was a LOOKUP PATH — a tree address, not a route, so every row on the home
 * page carried a link to a 404. Operator direction, 2026-09-19: plain text. */
.feed__who { font-size: var(--t-small); color: var(--ink); }
/* **THE DETAIL IS CONTENT, NOT A CAPTION.** Until this week it was empty on every row and styled
 * like an aside — muted, off the measure — because there was nothing in it to read. Every session
 * write now files a timeline entry carrying the author's own message (`write.TIMELINE_VERB_FOR`),
 * so this line is the sentence that makes the feed legible: a person wrote it, it is set in the
 * prose face at the prose size in full ink, and it keeps the reading measure like any other prose in
 * this interface. */
.feed__detail { margin: var(--s1) 0 0; color: var(--ink); max-width: var(--measure);
                font-size: var(--t-body); line-height: var(--lh-body); }
.feed__when { font-family: var(--mono); font-size: var(--t-gutter); line-height: var(--lh-mono);
              color: var(--ink-faint); font-variant-numeric: tabular-nums; }

/* ---- the conversation under a post (checklist item 43) ----------------------------------------
 *
 * **A REPLY IS INSIDE THE POST'S COLUMN, NOT BESIDE THE POSTER'S FACE.** It is about what was said,
 * so it is indented under the words; a reply starting its own column of avatars down the page would
 * read as a second feed rather than as an answer. The rule is a left edge, which is what this
 * interface draws instead of a nested card.
 *
 * **THE COMMENTER'S FACE IS SMALLER THAN THE POSTER'S, AND THE SELECTOR IS CHOSEN TO SAY SO.**
 * `.feed__face` is (0,1,0) and sets 2.5rem for every face on the page; `.feed__comment .feed__face`
 * is (0,2,0) and beats it. A rule that merely tied would lose to whichever came later in the file —
 * silently, with a page that only looks wrong — which is how four CSS defects reached this app in
 * one day. */
.feed__replies { margin-block-start: var(--s3); display: grid; gap: var(--s3);
                 border-inline-start: 1px solid var(--line); padding-inline-start: var(--s4); }
.feed__replies-note { margin: 0; font-size: var(--t-gutter); font-family: var(--mono);
                      color: var(--ink-faint); }
.feed__comment-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s3); }
.feed__comment { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--s3);
                 align-items: start; }
.feed__comment .feed__face { width: 1.5rem; height: 1.5rem; }

/* **A REPLY THAT IS STAGED AND NOT YET STORED** — checklist item 67. It sits in the list with the
 * stored replies because that is where the person put it, and it is told apart by a chip rather
 * than by being hidden: the whole difficulty this interface has is that staging looks like nothing
 * happening, so the answer is never to show less.
 *
 * **THE THIRD COLUMN IS DECLARED HERE, because `.feed__comment` is two.** A chip appended to a
 * two-column grid does not sit at the end of the row — it wraps to column 1 of the next line, under
 * the name, reading as a second comment by somebody called "Staged". The variant carries its own
 * template for that reason. */
.feed__comment--staged { grid-template-columns: auto minmax(0, max-content) auto;
                         justify-content: start; }
.feed__comment--staged .feed__comment-body { color: var(--ink-muted); }
.feed__comment-state {
  align-self: start; font-family: var(--mono); font-size: var(--t-gutter);
  letter-spacing: 0.06em; text-transform: uppercase; white-space: nowrap;
  color: var(--accent); border: 1px solid var(--accent); padding: 0 var(--s2);
}
/* The reply box: one row at the reading measure, its label in the gutter's quiet register. */
.feed__reply { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--s2);
               align-items: end; max-width: var(--measure); }
.feed__reply-label { grid-column: 1 / -1; font-size: var(--t-gutter); font-family: var(--mono);
                     color: var(--ink-muted); }
.feed__reply-box {
  font: inherit; font-size: var(--t-small); width: 100%; resize: vertical; color: var(--ink);
  background: var(--bg-surface); border: none; box-shadow: inset 0 0 0 1px var(--line-strong);
  padding: var(--s2); line-height: var(--lh-body);
}
.feed__reply-box:focus-visible { box-shadow: inset 0 0 0 1px var(--key); outline: none; }
/* **REPLY IS THE PRIMARY ACT OF THE ROW IT SITS IN, AND IT WAS DRAWN AS A DEFAULT BROWSER BUTTON.**
 * Checklist item 55, operator 2026-09-20: *"Reply button styling and clicking reloads the page."* The
 * reload is fixed in `session.js`; this is the other half. It carried `--bg-well` — a token from the
 * READING ROOM's register, which `tokens.css` still declares and which this dashboard's OOPS layer
 * otherwise never names — so the one control a reader is meant to reach for in a feed row was the
 * only grey plate on a cream page, at 12px, beside a full-width box.
 *
 * It takes the key, like every other primary in this app, and the touch floor the rest of the
 * interface treats as non-negotiable. NO NEW TOKEN AND NO COLOUR MOVED
 * (`docs/oops-interaction.md` §6): `--key` is the value `.button:hover` and `.deck__go` already use.
 *
 * SPECIFICITY IS NOT IN QUESTION HERE — nothing else in either sheet declares a rule for this class
 * — but the plate is stated in full rather than inherited from `.button`, because a reply control
 * inside a row and a page's filing control are two different things that would be made to drift by
 * one shared rule. That is the argument `.section__action` already carries. */
.feed__reply-go {
  font: 500 var(--t-small)/1 var(--mono); letter-spacing: .04em; text-transform: uppercase;
  color: var(--key-ink); background: var(--key); border: none; box-shadow: 0 0 0 1px var(--key);
  min-height: var(--touch); padding: var(--s2) var(--s4);
  cursor: pointer; white-space: nowrap;
}
.feed__reply-go:hover { background: var(--key-hover); box-shadow: 0 0 0 1px var(--key-hover); }
.feed__reply-go:disabled { background: var(--board-well); color: var(--ink-muted);
                           box-shadow: 0 0 0 1px var(--line-strong); cursor: not-allowed; }
/* The reply's refusal-and-receipt slot, spanning under both columns of the little grid so a sentence
 * is read at the row's width rather than in the button's column. `docs/oops-interaction.md` §3:
 * every control has a place its refusal lands, beside it. */
.feed__reply [data-async-note] { grid-column: 1 / -1; margin: 0; }

/* ---- the attached element: the thing a post is ABOUT ------------------------------------------
 * A post says what somebody did; the card says what they did it to. It is a BLOCK rather than a
 * link in the sentence (operator direction, 2026-09-19) — name, path, and a picture where one could
 * be determined without opening the record. Bordered rather than filled, because this interface
 * draws edges and has no shadows.
 *
 * The card is shared: the feed's subject and the directory's person are the same object — a picture
 * beside a column of said things — and one rule for both is what keeps them looking like one
 * interface as either gains a field. */
.card { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--s4);
        align-items: start; padding: var(--s3); border: 1px solid var(--line);
        background: var(--board-raised); }
.card__said { min-width: 0; }
/* THE PATH IS THE ARCHIVE'S OWN STATEMENT, so it is in the mono voice the gutter uses — and it
 * wraps anywhere, because a lookup path is long and has no spaces in it. A path that could not wrap
 * is the one element that makes a phone scroll sideways. */
.card__path { margin: var(--s1) 0 0; font-family: var(--mono); font-size: var(--t-gutter);
              line-height: var(--lh-mono); color: var(--ink-faint); overflow-wrap: anywhere; }
.card__name { margin: 0; font-weight: 600; overflow-wrap: anywhere; }
.card__thumb { display: block; width: 4.5rem; height: 4.5rem; object-fit: cover;
               border: 1px solid var(--line); }

/* ---- the attachment: the same card, nested inside a post --------------------------------------
 * Operator direction, 2026-09-19: "the yobj should be a smaller card inside the notes white area -
 * like an attachment". So inside a post the shared card steps DOWN — it is subordinate to the
 * sentence above it, not a peer of it.
 *
 * **SUNKEN RATHER THAN RAISED, WHICH IS THE WHOLE TRICK.** The card fills with `--board-raised`
 * everywhere else because it sits on the marigold ground. On a white post that fill is invisible
 * and the card dissolves into the note. `--board-sunken` is the one surface that reads as *inside*
 * white rather than on it — the same relationship a quoted attachment has to the message carrying
 * it.
 *
 * It is held to the reading measure so it ends where the prose ends. A card spanning the full row
 * under a short sentence is the shape of a header, and this is a footnote. */
/* **SLEEK: THE ATTACHMENT IS A LINE, NOT A PANEL.** Operator direction, 2026-09-19. The boxed
 * version had a full border, generous padding and a 4.5rem picture — so it carried more visual
 * weight than the sentence above it, which is backwards for a footnote. What identifies a record is
 * its name and its path; everything else the box was doing was decoration.
 *
 * So: no border at all (the sunken fill is the whole edge), tight padding, small type, and the name
 * and path set as two tight lines rather than a block with air in it. `inline-grid` with
 * `fit-content` lets it end where its content ends instead of ruling a bar across the post — a
 * short filename gets a short chip, which is what makes a column of these read as quiet. */
.feed__row .card { display: inline-grid; grid-template-columns: auto minmax(0, 1fr);
                   width: fit-content; max-width: min(100%, var(--measure));
                   margin-block-start: var(--s1); padding: var(--s2) var(--s3);
                   gap: var(--s3); align-items: center;
                   border: 0; background: var(--board-sunken); }
.feed__row .card__name { font-size: var(--t-small); font-weight: 600; line-height: 1.3; }
.feed__row .card__path { margin-block-start: 0; font-size: var(--t-gutter);
                         line-height: var(--lh-mono); color: var(--ink-faint); }
/* An enclosure, not an illustration — it identifies the record, it is not the reason to read. */
.feed__row .card__thumb { width: 2rem; height: 2rem; }

/* ---- the way forward (§4) ----------------------------------------------------------------------
 * Rendered only when the read layer held rows back, so it always leads somewhere. Given room of its
 * own beneath the last post rather than tucked against it: it is the end of the page, and the end
 * of a page is where a person decides whether to keep going. */
.more { margin: 0; padding: var(--s6); }


/* ---- the notice board: a finding aid entry per announcement ------------------------------------
 * The gutter carries what ORDERS and ADDRESSES the notice — its date (filing time is announcement
 * time here) and its lane. The column carries what a person reads. That division is the same one the
 * register and the field list make, so the board reads as another series in the same aid rather than
 * as a second design. Numbered markers are deliberately absent: a reverse chronology is not a
 * sequence, and numbering one invents an order nobody can follow. */
.notices { list-style: none; margin: 0; padding: 0; }
.notice {
  display: grid;
  grid-template-columns: var(--gutter-w) minmax(0, 1fr);
  gap: 0 var(--s6);
  padding: var(--s6);
  border-top: 1px solid var(--line);
}
/* ONE CELL PER COLUMN. Stamp and lane were separate grid items pinned against an explicit
 * `grid-row` on the subject, so the row's geometry depended on how many lines each wrapped to — and
 * the first notice whose lane wrapped printed its subject over the lane. Two cells, one row. */
.notice__meta { grid-column: 1; }
.notice__said { grid-column: 2; min-width: 0; }
.notice__stamp {
  margin: 0;
  font-family: var(--mono); font-size: var(--t-small);
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted); white-space: nowrap;
}
/* The author sits between the stamp and the lane: when it happened, who said it, which lane. That is
 * the order a person reads a notice board in. A name when the record points at an actant, the signing
 * address when it does not — the address is set in mono because it is an identifier, and the name in
 * the body face because it is a person. */
.notice__by { margin: var(--s1) 0 0; font-size: var(--t-small); }
.notice__who { font-family: var(--body); color: var(--key); }
.notice__role { font-family: var(--mono); font-size: var(--t-gutter); color: var(--ink-faint); }
.notice__addr {
  font-family: var(--mono); font-size: var(--t-gutter); color: var(--ink-faint);
  overflow-wrap: anywhere;
}
.notice__subject {
  margin: 0;
  font-family: var(--display); font-size: var(--t-lead); font-weight: 500;
  color: var(--ink); text-wrap: balance;
}
.notice__body {
  margin: 0;
  max-width: var(--measure);
  color: var(--ink);
}
.notice__body p { margin: var(--s3) 0 0; }

/* ---- composing: the typing column is the READING measure ---------------------------------------
 * The one deliberate move on the form. A person writing an announcement types into a column exactly
 * as wide as the one it will be read in, so composing looks like the result. Everything else is the
 * register as it already stands: labels in the gutter's mono voice, 1px edges, nothing rounded. */
.compose { display: grid; gap: var(--s6); padding: var(--s6) var(--s6) var(--s8); }
.compose__row {
  display: grid;
  grid-template-columns: var(--gutter-w) minmax(0, 1fr);
  gap: 0 var(--s6);
  align-items: start;
}
.compose__label {
  font-family: var(--mono); font-size: var(--t-gutter);
  color: var(--ink-muted); padding-top: var(--s2);
}
.compose__control-wrap { max-width: var(--measure); }
.compose__control {
  width: 100%;
  min-height: var(--touch);
  padding: var(--s3);
  background: var(--board-raised);
  color: var(--ink);
  border: 1px solid var(--line-strong);
  border-radius: 0;
  font: 400 var(--t-body)/var(--lh-body) var(--body);
}
.compose__control:focus-visible { outline: 2px solid var(--key); outline-offset: 1px; }
.compose__control--prose { resize: vertical; min-height: 12rem; }
.compose__control--bad { border-color: var(--danger); }
/* **THE CONTROL TAKES THE FIELD MEASURE; THE PROSE BOX KEEPS THE READING ONE.** Checklist item 47,
 * the too-wide end: the operator's screenshot showed a file input and its hint run the full 624px
 * of `--measure` with the label stranded at the far left of a 248px gutter track. A single-line
 * answer does not become easier to give by being wider — it becomes a longer journey from the label
 * to the box, and on the profile page that is every row.
 *
 * **SPECIFICITY, STATED**: `.compose__control` is (0,1,0) and `.compose__control--prose` is the same,
 * so the prose rule wins ONLY because it is later in this sheet — which is exactly the accident this
 * app has lost four rounds to. So the prose exception is written at (0,2,0)
 * (`.compose__control.compose__control--prose`) and cannot be undone by re-ordering.
 *
 * The HINT and the ERROR keep the full reading measure: they are sentences, and a sentence wrapped
 * at 30rem under a control is a narrower column than the prose it explains. */
.compose__control { max-width: var(--measure-field); }
.compose__control.compose__control--prose { max-width: var(--measure); }
.compose__hint, .compose__error, .compose__status { margin: var(--s2) 0 0; font-size: var(--t-small); }
.compose__hint { color: var(--ink-faint); }
/* The live region "Upload and set" writes its progress into (checklist item 48). It is a STATUS and
 * not an error, so it is drawn in the muted ink; a refusal that arrives through the enhanced path
 * takes the modifier and the page's own danger colour, so the two states cannot be mistaken for
 * each other at a glance. `--bad` is (0,2,0) against the base's (0,1,0). */
.compose__status { color: var(--ink-muted); font-family: var(--mono); }
.compose__status.compose__status--bad { color: var(--danger); }
/* The refusal sits WITH its field. A banner at the top makes the person hunt for which box it meant;
 * here it points at itself. It is drawn in `--danger` — the open call's own page-5 red, 5.98 on the
 * board — rather than in the key, which is the link colour: a correction set in the same colour as
 * every link on the page reads as one more thing to click. */
.compose__error { color: var(--danger); font-family: var(--mono); }
.compose__row--go { align-items: center; }
.compose__note {
  grid-column: 1 / -1; margin: 0 0 var(--s3);
  max-width: var(--measure);
  font-size: var(--t-small); color: var(--ink-muted);
}
.compose__row--go .button { grid-column: 2; justify-self: start; }

/* ---- the one action control ---------------------------------------------------------------------
 * Shares its shape with `.signin__go`, which was the only button in the app until now. Stated as its
 * own class rather than by adding a second selector to that rule: a sign-in control and a filing
 * control drift apart eventually, and a shared rule makes that drift a surprise. */
/* **`align-items: start` IS LOAD-BEARING — checklist item 71.** This is a flex row, and flex's
 * default is `stretch`: the button takes the height of this `<p>`. That was invisible for as long as
 * the `<p>` was content-sized — and it is a GRID ITEM of `.aid`, which stretches it to its row. On a
 * page with little in it (a board holding one notice) the row absorbs the leftover height, the `<p>`
 * became 278px tall, and "Write an announcement" rendered as a 218×262 slab of ink.
 *
 * So the bug was never in the button: it was a stretch two elements up, arriving through a default
 * nobody chose. Pinning it here keeps the control the size of its own label on a full page and an
 * empty one alike. */
.section__action {
  display: flex; justify-content: flex-end; align-items: start;
  margin: 0; padding: var(--s4) var(--s6) 0;
}
/* MONO, UPPERCASE, AND AT LEAST 44px TALL — the hit target is not negotiable on a phone, and the
 * label is the system's word for what will happen, which is the mono's job. The primary action is
 * INK; hover takes it to the key. The open call is flat blocks and hard edges from its first page to
 * its last, and so is this: no radius, no shadow, a drawn 1px edge. */
.button {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: var(--touch);
  padding: var(--s3) var(--s6);
  background: var(--ink);
  color: var(--board);
  border: 1px solid var(--ink); border-radius: 0;
  font: 500 var(--t-small)/1 var(--mono);
  letter-spacing: .04em; text-transform: uppercase;
  cursor: pointer;
  /* **A BUTTON IS NOT A LINK IN A SENTENCE.** Most of these ARE `<a>` elements — "Add a resource",
   * "Write an announcement" — so the global `a { text-decoration: underline }` drew a rule through
   * the label inside a filled navy box, where the box is already the whole affordance. Same reason
   * the spine's nav drops it: an underline marks a word you can click AMONG words you cannot, and
   * inside a button there are none. */
  text-decoration: none;
}
.button:hover { background: var(--key); border-color: var(--key); text-decoration: none; color: var(--key-ink); }
.button:focus-visible { outline: 2px solid var(--key); outline-offset: 2px; }

/* **COLLAPSING THE TEMPLATE IS NOT ENOUGH — THE CHILDREN'S EXPLICIT COLUMNS MUST GO TOO.**
 * Narrowing `grid-template-columns` to one column while `.notice__said` still said
 * `grid-column: 2` did not stack anything: it left the explicit placement pointing at a column the
 * template no longer defines, so the browser created an IMPLICIT second column sized to content.
 * The first notice's prose then took the whole width and the gutter computed to literally `0px`,
 * printing the date under the heading. Diagnosed from `getComputedStyle().gridTemplateColumns`
 * reading `0px 510px` — the shape of the bug was invisible in the markup and obvious in the
 * computed value. */
@media (max-width: 52rem) {
  .notice, .compose__row { grid-template-columns: minmax(0, 1fr); }
  .notice__meta, .notice__said,
  .compose__label, .compose__control-wrap,
  .compose__row--go .button { grid-column: 1; }
  .notice__meta { display: flex; gap: var(--s3); align-items: baseline; flex-wrap: wrap; }
  .notice__by { margin: 0; }
  .notice__said { margin-top: var(--s2); }
}

/* ---- the library: the folder view, scoped ---------------------------------------------------------
 * The Reading Room draws this as a canvas of icon tiles with a sidebar and a floating context panel.
 * Here it is a finding aid like everything else on this dashboard: a trail, a list whose margin says
 * what each row IS, and the opened record set as an entry with its gutter. That is a divergence from
 * "port it as is", and it is deliberate — the tiles, the window chrome and the double-click are the
 * Reading Room's own register, and this app has one already. What was kept is the BEHAVIOUR: where
 * you are, what is here, going in, opening one, taking it away with you. */
.crumbs {
  margin: 0; padding: var(--s4) var(--s6) 0;
  font-family: var(--mono); font-size: var(--t-small);
  color: var(--ink-muted);
}
.crumbs__sep { padding: 0 var(--s2); color: var(--ink-faint); }
/* The crumb you are on is not a link, and it is the one that is INKED — every other crumb is a place
 * to go, and the key belongs on the things that move you. */
.crumbs__here { color: var(--ink); }
.shelf { list-style: none; margin: 0; padding: 0; }
/* ONE ROW IS TWO CELLS: what it is, and what it is called. The same two-column division as `.notice`
 * one section up, on purpose — a folder listing that invented its own geometry would read as a
 * second design inside the same page. */
.shelf__row {
  display: grid;
  grid-template-columns: var(--gutter-w) minmax(0, 1fr);
  gap: 0 var(--s6);
  padding: var(--s3) var(--s6);
  border-top: 1px solid var(--line);
}
.shelf__kind {
  grid-column: 1; margin: 0;
  font-family: var(--mono); font-size: var(--t-small);
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted); white-space: nowrap;
}
.shelf__name {
  grid-column: 2; margin: 0; min-width: 0;
  font-size: var(--t-body); overflow-wrap: anywhere;
}
/* ---- the opened record ----------------------------------------------------------------------------
 * A definition list, because that is what it is: four named fields off one type. The terms sit in the
 * mono voice the gutter uses so the pane reads as part of the margin's vocabulary rather than as a
 * form. */
.facts__list { margin: var(--s3) 0 0; }
.facts__row { display: grid; grid-template-columns: 7rem minmax(0, 1fr); gap: 0 var(--s4); }
.facts__term {
  margin: 0; font-family: var(--mono); font-size: var(--t-small);
  color: var(--ink-faint);
}
.facts__value { margin: 0 0 var(--s2); min-width: 0; overflow-wrap: anywhere; }
.entry__actions { margin: var(--s4) 0 0; }

/* The one-column collapse, joining the rule that already does this for `.notice` and `.compose__row`.
 * Left out, a phone renders the shelf's margin at 15.5rem beside a name column of nothing. */
@media (max-width: 52rem) {
  .shelf__row { grid-template-columns: minmax(0, 1fr); }
  .shelf__kind, .shelf__name { grid-column: 1; }
  .facts__row { grid-template-columns: minmax(0, 1fr); }
}

/* ---- the calendar: a month as a sequence of days ------------------------------------------------
 * The one page where a day heading is real structure rather than decoration — the reader is looking
 * for "when", and the day IS the number. So the date runs down the gutter in the mono face at
 * display size, the way a finding aid numbers a series, and the meetings sit beside it. */
.month { display: grid; gap: 0; }
/* **IT WRAPS, AND THE MEASUREMENT IS WHY.** Two nowrap steps either side of a month name do not fit
 * 320px: the probe found the document 364px wide in a 320px viewport with `a.month__step` hanging
 * 44px past the edge — the calendar was the one page in the app that scrolled sideways on a small
 * phone. `space-between` cannot help, because the overflow is the CONTENT's width and not its
 * distribution. So the row wraps and the steps keep their own `nowrap`, which is what they need:
 * "Next month →" broken across two lines is not a control anybody reads. */
.month__nav {
  display: flex; align-items: baseline; justify-content: space-between; flex-wrap: wrap;
  gap: var(--s2) var(--s4);
  margin: 0; padding: var(--s4) var(--s4) 0;
  font-family: var(--mono); font-size: var(--t-small);
}
.month__now { font-family: var(--display); font-size: var(--t-lead); font-weight: 500;
              min-width: 0; overflow-wrap: anywhere; }
.month__step { white-space: nowrap; min-height: var(--touch); display: inline-flex;
               align-items: center; }

.day {
  display: grid;
  grid-template-columns: var(--gutter-w) minmax(0, 1fr);
  gap: 0 var(--s6);
  padding: var(--s6);
  border-top: 1px solid var(--line);
}
.day__date { grid-column: 1; margin: 0; display: flex; align-items: baseline; gap: var(--s3); }
.day__no {
  font-family: var(--mono); font-size: var(--t-lead); font-weight: 500;
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.day__name { font-family: var(--mono); font-size: var(--t-gutter); color: var(--ink-faint); }
.day__events { grid-column: 2; list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s4); }

.event { display: grid; grid-template-columns: 4.5rem minmax(0, 1fr); gap: 0 var(--s4); }
.event__when { grid-column: 1; }
.event__what { grid-column: 2; min-width: 0; }
.event__time {
  margin: 0; font-family: var(--mono); font-size: var(--t-body);
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.event__edit { margin: var(--s1) 0 0; font-family: var(--mono); font-size: var(--t-gutter); }
.event__title {
  margin: 0; font-family: var(--display); font-size: var(--t-body); font-weight: 500;
  color: var(--ink); text-wrap: balance;
}
.event__where { margin: var(--s1) 0 0; display: flex; flex-wrap: wrap; gap: var(--s3);
                font-size: var(--t-small); }
.event__place { color: var(--ink-muted); }
.event__place--missing { color: var(--ink-faint); font-style: italic; }
.event__summary { margin: var(--s2) 0 0; max-width: var(--measure); color: var(--ink); }
.event__summary p { margin: 0 0 var(--s2); }

/* The quieter of two controls, for the one that cannot be undone by typing again. */
/* **THE MEASURE GOES ON THE TEXT, NOT ON THE GRID ITEM** — the same lesson `_section`'s note
 * records, met again. A `max-width` here capped the form at 66ch, so it spanned neither the grid nor
 * either column, and the layout guard refused it: that is exactly the shape of a rule swallowed by
 * the prelude above it, so an item which is merely narrow is indistinguishable from one that is
 * broken. The form spans; the sentence inside it keeps its line length. */
.withdraw { grid-column: 1 / -1; padding: 0 var(--s6) var(--s8); }
.withdraw__note {
  margin: 0 0 var(--s3); max-width: var(--measure);
  font-size: var(--t-small); color: var(--ink-muted);
}
/* The quiet action: an outline that fills with the poster's marigold. It is the one place a
 * secondary control is allowed any colour at all, and marigold is a GROUND here, carrying ink at
 * 11.96 — never type. */
.button--quiet {
  background: transparent; color: var(--ink);
  border-color: var(--line-strong);
}
.button--quiet:hover { background: var(--field); border-color: var(--ink); color: var(--ink); }

@media (min-width: 52rem) { .month__nav { padding: var(--s4) var(--s6) 0; } }

@media (max-width: 52rem) {
  .day { grid-template-columns: minmax(0, 1fr); }
  .day__date, .day__events { grid-column: 1; }
  .day__events { margin-top: var(--s3); }
}

/* ---- waiting to be anchored --------------------------------------------------------------------
 * A person's own un-anchored actions. Set as a plain list rather than as the finding aid's
 * gutter-and-column, because the reader is scanning for "is my thing here" and the path IS the
 * identifier — there is no descriptive half to put beside it. */
.pending { list-style: none; margin: 0; padding: 0; grid-column: 1 / -1; }
/* The Localhost page's opening block: spans the grid, and the prose inside it keeps the measure. */
.why { grid-column: 1 / -1; padding: var(--s6) var(--s6) 0; }
.why .lede, .why .step__note { max-width: var(--measure); }
.pending__row {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s3);
  padding: var(--s3) var(--s6);
  border-top: 1px solid var(--line);
}
.pending__verb {
  font-family: var(--mono); font-size: var(--t-gutter); color: var(--ink-faint);
  text-transform: uppercase; letter-spacing: .04em; min-width: 4.5rem;
}
.pending__path { font-family: var(--mono); font-size: var(--t-small); overflow-wrap: anywhere; }
.pending__when {
  margin-left: auto; font-family: var(--mono); font-size: var(--t-gutter);
  color: var(--ink-faint); font-variant-numeric: tabular-nums;
}
.pending__note { color: var(--ink-muted); font-size: var(--t-small); max-width: var(--measure); }
.pending__go { grid-column: 1 / -1; padding: var(--s4) var(--s6) var(--s8); }

/* ---- what a record is attached to --------------------------------------------------------------
 *
 * Set as a plain list for the same reason `.pending` is: the reader is scanning for a relationship,
 * and the PREDICATE is the identifier — there is no descriptive half to set beside it. So the
 * predicate runs in the gutter's mono voice at the left of each row, the thing pointed at takes the
 * reading face, and the lookup path follows it quietly.
 *
 * **SCOPED NAMES, AND THAT IS NOT A STYLE PREFERENCE.** Four CSS defects in one day were a class
 * collision, a taken data attribute, a lost specificity and a name missing from the span list —
 * every one of them silent. `.edges__*` collides with nothing in this sheet and with nothing in
 * `workstation.css`, whose relationship list is `.rel-list` for the same reason. */
.edges { list-style: none; margin: 0; padding: 0; }
.edges__row {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s3);
  padding: var(--s3) var(--s6);
  border-top: 1px solid var(--line);
}
.edges__pred {
  font-family: var(--mono); font-size: var(--t-gutter); color: var(--ink-faint);
  text-transform: uppercase; letter-spacing: .04em; min-width: 8rem;
}
.edges__what { color: var(--ink); overflow-wrap: anywhere; }
/* `anywhere`, because a lookup path has no spaces in it and is the one string on this page long
 * enough to push the column past the screen. The house rule is that the body never scrolls
 * sideways. */
.edges__path {
  font-family: var(--mono); font-size: var(--t-small); color: var(--ink-muted);
  overflow-wrap: anywhere;
}
/* The withdraw control sits at the far end of its row, where it is reachable and is not the first
 * thing read. `margin-left: auto` rather than a column, because the row wraps on a phone and a
 * declared column would leave the button stranded mid-line. */
.edges__do { margin: 0 0 0 auto; }

/* ---- running it locally: the one page that is genuinely a sequence -----------------------------
 * Numbered markers are decoration almost everywhere in this app — a register is not a sequence, a
 * reverse chronology is not one. This IS one, so the number runs down the gutter where the finding
 * aid puts its identifiers, and does real work: it tells a reader where they are in something they
 * are part-way through. */
.steps { list-style: none; margin: 0; padding: 0; grid-column: 1 / -1; }
.step {
  display: grid;
  grid-template-columns: var(--gutter-w) minmax(0, 1fr);
  gap: 0 var(--s6);
  padding: var(--s6);
  border-top: 1px solid var(--line);
}
.step__no {
  grid-column: 1;
  font-family: var(--mono); font-size: var(--t-lead); font-weight: 500;
  font-variant-numeric: tabular-nums; color: var(--ink-faint);
}
.step__body { grid-column: 2; min-width: 0; max-width: var(--measure); }
.step__title {
  margin: 0 0 var(--s3);
  font-family: var(--display); font-size: var(--t-lead); font-weight: 500; color: var(--ink);
}
.step__body p { margin: 0 0 var(--s3); color: var(--ink); }
.step__note { font-size: var(--t-small); color: var(--ink-muted); }
.step__note--absent { color: var(--ink-faint); font-style: italic; }
.step__body code {
  font-family: var(--mono); font-size: var(--t-small);
  background: var(--board-well); padding: 0 var(--s1);
}

/* WIDE CONTENT SCROLLS INSIDE ITSELF. A long path in a command is the commonest way a documentation
 * page comes to scroll sideways on a phone, and a body that scrolls sideways is the one layout fault
 * this register never permits. */
.shell {
  margin: 0 0 var(--s3);
  padding: var(--s3) var(--s4);
  background: var(--board-sunken);
  border: 1px solid var(--line);
  overflow-x: auto;
  font-family: var(--mono); font-size: var(--t-small); line-height: 1.6;
  white-space: pre;
}
.shell code { background: none; padding: 0; font: inherit; }

@media (max-width: 52rem) {
  .step { grid-template-columns: minmax(0, 1fr); }
  .step__no, .step__body { grid-column: 1; }
  .step__no { margin-bottom: var(--s2); }
}

/* ---- the keys a person signs with --------------------------------------------------------------
 * Three states, three different facts: MINTED is the address they were provisioned with and the
 * record requires it; CONFIRMED is one the custodian has seated, so it can write; WAITING is a claim
 * nobody has acted on, which can do nothing at all yet. The state runs in the gutter's mono voice
 * because it is the thing a person is scanning for. */
.keys { list-style: none; margin: 0; padding: 0; grid-column: 1 / -1; }
.key {
  display: grid;
  grid-template-columns: var(--gutter-w) minmax(0, 1fr) auto;
  gap: 0 var(--s4);
  align-items: baseline;
  padding: var(--s3) var(--s6);
  border-top: 1px solid var(--line);
}
.key__state {
  font-family: var(--mono); font-size: var(--t-gutter);
  text-transform: uppercase; letter-spacing: .04em;
}
.key__state--minted { color: var(--ink-muted); }
.key__state--confirmed { color: var(--key); }
/* WAITING reads as unfinished rather than as an error — nothing has gone wrong, a person is waiting
 * on somebody else. Italic and faint, never the key, which here means "this one can write". */
.key__state--waiting { color: var(--ink-faint); font-style: italic; }
.key__addr { font-family: var(--mono); font-size: var(--t-small); overflow-wrap: anywhere; }
.key__do { display: flex; gap: var(--s3); justify-self: end; }
.key__form { margin: 0; }

/* A control that is a form but must not look like the page's one real action. */
.link-button {
  background: none; border: 0; padding: 0;
  font: 400 var(--t-small)/var(--lh-mono) var(--mono);
  color: var(--key); cursor: pointer; text-decoration: underline;
  text-underline-offset: 2px;
}
.link-button:hover { color: var(--key-hover); }
.link-button:focus-visible { outline: 2px solid var(--key); outline-offset: 2px; }
.compose--inline { padding-top: var(--s4); }

@media (max-width: 52rem) {
  .key { grid-template-columns: minmax(0, 1fr); }
  .key__do { justify-self: start; margin-top: var(--s2); }
}

/* ---- the issue page: what has to be fixed before any of this works ------------------------------
 *
 * The one page in this app a person reaches without choosing to. Everything on it is in the
 * register's ordinary voice — no red banner, no alarm — because nothing has gone WRONG: a key has
 * not been made yet, or somebody at Padimai has not yet done a thing they were never asked to do.
 * An alarm would make an ordinary, expected first-run state feel like damage.
 *
 * `preflight__*` throughout, and that prefix is load-bearing rather than fastidious. Two collisions
 * on single-word global names have already cost this build an afternoon each (`.pending` and
 * `data-chain`), and both presented as "the thing renders the wrong shape" rather than as an
 * error — nothing raises when two rules meet. */
.preflight { padding: var(--s4) var(--s6) 0; }
.preflight__fix {
  margin: 0 0 var(--s6); max-width: var(--measure);
  font-size: var(--t-lead); line-height: var(--lh-tight); color: var(--ink);
}
.preflight__label {
  margin: 0 0 var(--s2);
  font-family: var(--mono); font-size: var(--t-gutter); font-weight: 500;
  text-transform: uppercase; letter-spacing: .06em; color: var(--ink-muted);
}
/* THE ADDRESS IS THE PAGE'S PAYLOAD, so it is set as one: large, mono, on the sunken plate the
 * margin uses, and wrapping rather than scrolling — an ss58 is 48 characters and a person is going
 * to select it with a mouse. `anywhere` because a 320px screen has no room for it on one line, and
 * a body that scrolls sideways is the one layout fault this register never permits. */
.preflight__addr {
  margin: 0 0 var(--s6);
  padding: var(--s3) var(--s4);
  background: var(--board-sunken); border: 1px solid var(--line);
  font-family: var(--mono); font-size: var(--t-body);
  overflow-wrap: anywhere; user-select: all;
}
.preflight__note {
  margin: 0 0 var(--s3); max-width: var(--measure);
  font-size: var(--t-small); color: var(--ink-muted);
}
.preflight__note--absent { color: var(--ink-faint); font-style: italic; }

/* The four questions, in the order they were asked — the same three-part row `.key` uses, because
 * they are the same shape of fact: a state in the gutter's mono voice, and what it means beside it. */
.preflight__list { list-style: none; margin: 0; padding: 0; }
.check {
  display: grid;
  grid-template-columns: var(--gutter-w) minmax(0, 1fr);
  gap: 0 var(--s4);
  align-items: baseline;
  padding: var(--s3) var(--s6);
  border-top: 1px solid var(--line);
}
.check__state {
  font-family: var(--mono); font-size: var(--t-gutter);
  text-transform: uppercase; letter-spacing: .04em; color: var(--ink-faint);
}
/* THE ONE THAT FAILED IS THE ONLY THING MARKED, AND IT IS MARKED IN INK RATHER THAN IN A COLOUR.
 * Two colours were wrong for it and both were tempting. `--key` means "this is the action" on every
 * other page, and the action here is the remedy above, not the row reporting the problem. `--foxing`
 * is the rust bloom on stored paper and means what TIME is doing — a deadline nearing, an item
 * outstanding — and nothing about an unseated key is about time; the guard that refused it is right,
 * and a colour that means one thing is worth more than a colour that means "look here".
 *
 * So the failed row is simply the only one at full ink and weight among faint ones, which is the
 * register's own way of raising something (`.empty--blind`: upright, at full ink, ruled). */
.check--bad .check__state { color: var(--ink); font-weight: 500; }
.check__said { min-width: 0; }
.check__title { display: block; color: var(--ink); }
/* The detail carries an ss58 and, when the chain would not answer, a Python exception's own text —
 * neither of which was written to fit a column. */
.check__detail {
  display: block; margin-top: var(--s1);
  font-family: var(--mono); font-size: var(--t-gutter); color: var(--ink-muted);
  overflow-wrap: anywhere;
}

@media (max-width: 52rem) {
  .check { grid-template-columns: minmax(0, 1fr); }
  .check__state { margin-bottom: var(--s1); }
  .preflight { padding-left: var(--s4); padding-right: var(--s4); }
}

/* The portrait on the profile form: bounded so a large upload cannot push the column wide, and
 * square-cropped so nine different aspect ratios still line up in a register. */
.portrait {
  display: block; width: 8rem; height: 8rem; object-fit: cover;
  border: 1px solid var(--line); margin: 0 0 var(--s3);
}
.portrait--none {
  width: 8rem; height: 8rem; margin: 0 0 var(--s3);
  display: flex; align-items: center; justify-content: center; text-align: center;
  background: var(--board-sunken); border: 1px solid var(--line);
  font-size: var(--t-small); color: var(--ink-faint);
}

/* ---- the page a stranger sees ------------------------------------------------------------------
 *
 * The home page is two pages at one URL, and this is the one without a session. It describes the
 * residency and offers the way in; it carries NO residency data, which is a rule about the markup
 * `pages.public_home` renders and not a thing this stylesheet can enforce.
 *
 * It is the one place a motif runs at full strength. Everywhere behind the door the artwork is a
 * watermark in the corner the measure does not use, because the people there are working; here it
 * is the first thing on the page, because nobody outside the residency has any other reason to look
 * at it. That difference in INTENSITY is what separates the front of the document from the inside,
 * with the same mark, the same type and the same cream ground carried across both.
 *
 * Each band spans the aid's grid like every other section child; the label-and-prose division
 * INSIDE a band is the finding aid's own, so the public page reads as the front of one document
 * rather than as a different site wearing the same colours. */
.pub { display: contents; }
.pub__hero, .pub__band { grid-column: 1 / -1; }

.pub__hero {
  padding: var(--s12) var(--s4) var(--s12);
  border-bottom: 1px solid var(--line-strong);
}
.pub__eyebrow {
  margin: 0 0 var(--s4);
  font-family: var(--mono); font-size: var(--t-gutter); line-height: var(--lh-mono);
  letter-spacing: .07em; text-transform: uppercase; color: var(--signal);
}
/* THE ACRONYM IS THE PHRASE, and the type is where that is shown. The name reworks object-oriented
 * programming; the four capitals the residency is called by are the first letters of the four words
 * it is named from, so they are the one thing on this page set in the key. Nothing else here is
 * coloured at all — the boldness is spent once. */
.pub__title {
  margin: 0 0 var(--s6);
  max-width: 18ch;
  font-family: var(--display); font-weight: 300;
  font-size: var(--t-hero); line-height: var(--lh-tight);
  letter-spacing: -.02em; color: var(--ink);
  text-wrap: balance;
}
.pub__cap { color: var(--key); }
.pub__lead {
  margin: 0; max-width: var(--measure);
  font-size: var(--t-lead); line-height: 1.5; color: var(--ink);
}

.pub__band {
  display: grid; gap: var(--s3);
  padding: var(--s8) var(--s4);
  border-top: 1px solid var(--line);
}
.pub__band:first-of-type { border-top: 0; }
/* The band's reference, in the margin's voice. It is an `h2` doing a label's job: the page has one
 * heading a reader navigates by per band, and inventing a second, larger one above each label would
 * be a heading level that exists to be decorative. */
.pub__ref {
  margin: 0;
  font-family: var(--mono); font-weight: 400;
  font-size: var(--t-gutter); line-height: var(--lh-mono);
  letter-spacing: .07em; text-transform: uppercase;
  color: var(--signal);
  border-top: 1px solid var(--line-ink); padding-top: var(--s2);
}
.pub__col { min-width: 0; }
.pub__col p { margin: 0 0 var(--s4); max-width: var(--measure); color: var(--ink); }
.pub__col p:last-child { margin-bottom: 0; }

/* AN INVENTORY, RULED RATHER THAN NUMBERED. Six concerns nobody works through in order, so `01 02
 * 03` down the side would be a structure saying something false about its own content. */
.pub__list {
  list-style: none; margin: 0; padding: 0;
  max-width: var(--measure);
  border-left: 2px solid var(--key);   /* ONE rule down the whole inventory, not six stubs */
}
.pub__item {
  padding: var(--s3) 0 var(--s3) var(--s4);
  border-top: 1px solid var(--line);
  color: var(--ink);
}
.pub__item:first-child { border-top: 0; }

/* THE ONE FLAT FIELD. The marigold square is the open call's loudest gesture and it is spent in one
 * place per page — here, on the only thing there is to do. It carries ink, never coloured type. */
.pub__band--in .pub__col { background: var(--field); padding: var(--s6); max-width: var(--measure); }
.pub__band--in .pub__col p { color: var(--ink); }
.pub__go { margin: var(--s4) 0 0; }

/* THE MOTIF AT FULL STRENGTH — and "full strength" is a matter of SIZE and POSITION, never opacity:
 * the wash is baked into the asset and cannot be turned up by a rule here. It sits behind the hero
 * at the top right, where the title's 18ch measure leaves the page empty. */
.aid--object .pub__hero {
  background-image: url("/static/motif-object.webp");
  background-repeat: no-repeat;
  background-position: right -1rem top -1rem;
  background-size: 18rem auto;
}

/* The public spine: a mark and a way in, and the way in sits at the far end of the bar. */
.strip--public { justify-content: space-between; }
.strip__in { color: var(--on-spine); margin-left: auto; text-decoration-color: var(--signal-true); }
.strip__in:hover { color: var(--on-spine); }

/* AT A DESK the band becomes the margin-and-column every other page is set in, at the same 15.5rem
 * so the public page and the private one share a spine of alignment. The hero opens out with it. */
@media (min-width: 52rem) {
  .pub__hero { padding: var(--s16) var(--s6); }
  .pub__band {
    grid-template-columns: var(--gutter-w) minmax(0, 1fr);
    gap: 0 var(--s6);
    padding: var(--s12) var(--s6);
  }
  .aid--object .pub__hero { background-size: 26rem auto; }
}


/* ---- the calendar grid -------------------------------------------------------------------------
 * **A CALENDAR DRAWS THE DAYS WITH NOTHING ON THEM**, which is the whole difference between this and
 * the list underneath it. Seven equal columns, because a week has seven equal days and a layout that
 * sized them by content would make a busy Tuesday wider than a free one — the opposite of what the
 * eye is doing here, which is comparing.
 *
 * `minmax(0, 1fr)` and not `1fr`: a grid track's default minimum is `auto`, so one long event title
 * sets a floor the column cannot go under and the whole grid pushes past the viewport. That is the
 * same overflow the month nav was fixed for, one element in. */
.grid { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line);
        margin: var(--s6) 0 0; }
.grid__heads, .grid__week { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
                            gap: 1px; }
.grid__head {
  background: var(--board);
  padding: var(--s2) var(--s3);
  font-family: var(--mono); font-size: var(--t-gutter); font-weight: 500;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-muted);
  text-align: center;
}
.cell {
  background: var(--board);
  min-height: 6.5rem; padding: var(--s2);
  display: flex; flex-direction: column; gap: var(--s2);
  min-width: 0;                       /* a chip's text may not set the column's floor */
}
/* **A DAY OUTSIDE THIS MONTH IS DIMMED AND IS NOT EMPTY.** `agenda.events` windows the GRID rather
 * than the month, so these cells carry their real meetings — the dimming says "another month", never
 * "we did not look". Kept above 4.5:1 for the same reason: it is still a date somebody reads. */
.cell--outside { background: var(--board-sunken); }
.cell--outside .cell__date { color: var(--ink-muted); }
/* TODAY IS A RULE, NOT A FILL. A filled cell competes with the chips inside it, which are the thing
 * the page is actually about. */
.cell--today { box-shadow: inset 0 3px 0 var(--key); }
.cell--today .cell__date { color: var(--key); font-weight: 500; }
.cell__date { margin: 0; font-family: var(--mono); font-size: var(--t-small); color: var(--ink);
              font-variant-numeric: tabular-nums; }
.cell__events { display: flex; flex-direction: column; gap: var(--s1); min-width: 0; }

/* A CHIP IS ONE MEETING AT THE SMALLEST SIZE IT CAN STILL BE READ AT. It truncates rather than
 * wrapping to four lines: the cell is a fixed height in a grid of equal cells, and a chip that grows
 * pushes the whole week's row down. The full title is in the list below, which is where a reader who
 * needs it goes. */
.chip {
  display: flex; gap: var(--s2); align-items: baseline; min-width: 0;
  padding: 2px var(--s2);
  font-family: var(--mono); font-size: var(--t-gutter); line-height: 1.4;
  color: var(--ink); text-decoration: none;
  border-left: 3px solid var(--signal);
  background: var(--board-sunken);
}
a.chip:hover { background: var(--key-wash); text-decoration: none; }
/* **THE KIND IS A CATEGORY, NOT A WARNING, and `--foxing` was the wrong reach.** An "important
 * date" is a kind of entry somebody chose from a list; `--foxing` means time pressing on a reader —
 * a deadline nearing, an item outstanding — and spending it on a category is how a colour that means
 * one thing stops meaning anything. The guard in `test_oar_ui` caught it by name, which is the only
 * way a rule like this can be enforced at all. */
.chip--mandatory { border-left-color: var(--key); }
.chip--important_date { border-left-color: var(--field); }
.chip__time { color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.chip__title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }

/* THE WEEK VIEW IS THE SAME GRID, TALLER. Seven days across is what makes it comparable with the
 * month at a glance; a seven-ROW list would be a different page wearing the same name. */
.grid--week .cell { min-height: 14rem; }
.grid--week .cell__date { font-size: var(--t-head); }

/* -- the view switcher -------------------------------------------------------------------------- */
.month__views { margin: 0 0 var(--s3); padding: 0 var(--s6); display: flex; gap: 0; }
.month__view {
  min-height: var(--touch); display: inline-flex; align-items: center;
  padding: 0 var(--s4);
  font-family: var(--mono); font-size: var(--t-small);
  color: var(--ink-muted); text-decoration: none;
  border: 1px solid var(--line);
}
.month__view + .month__view { border-left: 0; }
.month__view--on { color: var(--board); background: var(--ink); border-color: var(--ink); }
.month__view:hover { text-decoration: underline; }

/* **THE GRID IS THE PART THAT COMPRESSES ON A PHONE, AND THE LIST IS THE PART THAT DOES NOT.**
 * Seven columns on a 320px screen is 45px a day, which is enough for a date and not for a chip. So
 * the chips drop to a dot per meeting — the grid still answers "is the 14th free", which is its
 * whole job — and the list below carries what each one is. */
@media (max-width: 40rem) {
  /* THE STEP LINKS BECOME ARROWS. "← September 2026" and "November 2026 →" either side of
   * "October 2026" is three long strings on a 390px line, so the row wrapped and the month being
   * shown stopped being the thing in the middle. The arrows keep their 44px target and the month
   * they lead to is still announced, because the name is hidden visually and not removed. */
  .month__step-name {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
  }
  .cell { min-height: 4rem; padding: var(--s1); }
  .grid--week .cell { min-height: 8rem; }
  .chip { padding: 0; border-left-width: 2px; }
  .chip__time { display: none; }
  .chip__title { font-size: 10px; }
}


/* ---- the week as hours -------------------------------------------------------------------------
 * **A BOX CANNOT CARRY ITS OWN POSITION.** `style-src 'self'` blocks a `style=` attribute as
 * surely as a `<script>`, so the `top: 37%` a calendar would normally write per event is not
 * available to this app. The position is a GRID ROW, and the classes below are the whole set of
 * rows a day can have: 48 half-hours, which is a day. `agenda.SLOT_MINUTES` is what makes 48 the
 * right number, and `agenda` computes which one each meeting starts in.
 *
 * GENERATED, and deliberately written out rather than looped: CSS has no loop, and a preprocessor
 * would put a build step between this file and the browser for 96 one-line rules. */
.hours { display: flex; align-items: stretch; }
.hours__axis { display: grid; grid-auto-rows: var(--slot-h); flex: 0 0 auto;
               background: var(--board); }
.hours__days { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
               gap: 1px; background: var(--line); flex: 1 1 auto; min-width: 0; }
/* AN HOUR LABEL SPANS TWO HALF-HOUR ROWS and sits on its own line, so the label names the line
 * below it rather than floating between two. */
.hour { grid-row: span 2; padding: 0 var(--s2) 0 var(--s3); min-width: 4rem;
        font-family: var(--mono); font-size: var(--t-gutter); color: var(--ink-muted);
        border-top: 1px solid var(--line); text-align: right; }
.hour span { position: relative; top: -0.55em; background: var(--board); padding: 0 2px; }
/* THE HOUR RULES ARE THE COLUMN'S BACKGROUND, not 13 empty elements per day. A gradient is one
 * declaration and cannot fall out of step with the axis, because both are driven by --slot-h. */
.hcol { position: relative; background: var(--board);
        display: grid; grid-auto-rows: var(--slot-h); min-width: 0;
        background-image: repeating-linear-gradient(to bottom,
          var(--line) 0, var(--line) 1px, transparent 1px, transparent var(--slot-h)); }
.hcol--today { background-color: var(--key-wash); }
/* A BOX IS A GRID ITEM, so two meetings at the same hour stack rather than overlap — the one
 * thing an absolutely-positioned calendar has to solve and a grid gets right for free. */
.box { margin: 0 2px; padding: 1px var(--s2); min-width: 0; overflow: hidden;
       display: flex; flex-direction: column; gap: 0;
       font-family: var(--mono); font-size: var(--t-gutter); line-height: 1.35;
       color: var(--ink); text-decoration: none;
       background: var(--board-sunken); border-left: 3px solid var(--signal); }
a.box:hover { background: var(--key-wash); text-decoration: none; }
.box--mandatory { border-left-color: var(--key); }
.box--important_date { border-left-color: var(--field); }
.box__time { color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.box__title { font-weight: 500; overflow: hidden; text-overflow: ellipsis; }
.box__place { color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis;
              white-space: nowrap; }
/* **A HALF-HOUR BOX CANNOT STACK THREE LINES, and stacking them anyway showed only the first.** A
 * 30-minute meeting rendered as its start time and nothing else — the one piece of information a
 * reader already has from the axis it is sitting on. On one line the time and the title both fit;
 * the place does not, and it is in the list below. Keyed on the SPAN rather than on a height, so the
 * rule and the box it applies to are decided by the same number. */
.box.span--1 { flex-direction: row; align-items: baseline; gap: var(--s2); }
.box.span--1 .box__place { display: none; }
.box.span--1 .box__title { white-space: nowrap; }
.grid__heads--week { grid-template-columns: 4rem repeat(7, minmax(0, 1fr)); }
.grid__corner { background: var(--board); }
.grid__head--today { color: var(--key); }
.grid__head-day { color: var(--ink-muted); }

/* the 48 half-hours of a day, and the spans a meeting can cover */
.slot--0 { grid-row-start: 1; }
.slot--1 { grid-row-start: 2; }
.slot--2 { grid-row-start: 3; }
.slot--3 { grid-row-start: 4; }
.slot--4 { grid-row-start: 5; }
.slot--5 { grid-row-start: 6; }
.slot--6 { grid-row-start: 7; }
.slot--7 { grid-row-start: 8; }
.slot--8 { grid-row-start: 9; }
.slot--9 { grid-row-start: 10; }
.slot--10 { grid-row-start: 11; }
.slot--11 { grid-row-start: 12; }
.slot--12 { grid-row-start: 13; }
.slot--13 { grid-row-start: 14; }
.slot--14 { grid-row-start: 15; }
.slot--15 { grid-row-start: 16; }
.slot--16 { grid-row-start: 17; }
.slot--17 { grid-row-start: 18; }
.slot--18 { grid-row-start: 19; }
.slot--19 { grid-row-start: 20; }
.slot--20 { grid-row-start: 21; }
.slot--21 { grid-row-start: 22; }
.slot--22 { grid-row-start: 23; }
.slot--23 { grid-row-start: 24; }
.slot--24 { grid-row-start: 25; }
.slot--25 { grid-row-start: 26; }
.slot--26 { grid-row-start: 27; }
.slot--27 { grid-row-start: 28; }
.slot--28 { grid-row-start: 29; }
.slot--29 { grid-row-start: 30; }
.slot--30 { grid-row-start: 31; }
.slot--31 { grid-row-start: 32; }
.slot--32 { grid-row-start: 33; }
.slot--33 { grid-row-start: 34; }
.slot--34 { grid-row-start: 35; }
.slot--35 { grid-row-start: 36; }
.slot--36 { grid-row-start: 37; }
.slot--37 { grid-row-start: 38; }
.slot--38 { grid-row-start: 39; }
.slot--39 { grid-row-start: 40; }
.slot--40 { grid-row-start: 41; }
.slot--41 { grid-row-start: 42; }
.slot--42 { grid-row-start: 43; }
.slot--43 { grid-row-start: 44; }
.slot--44 { grid-row-start: 45; }
.slot--45 { grid-row-start: 46; }
.slot--46 { grid-row-start: 47; }
.slot--47 { grid-row-start: 48; }
.span--1 { grid-row-end: span 1; }
.span--2 { grid-row-end: span 2; }
.span--3 { grid-row-end: span 3; }
.span--4 { grid-row-end: span 4; }
.span--5 { grid-row-end: span 5; }
.span--6 { grid-row-end: span 6; }
.span--7 { grid-row-end: span 7; }
.span--8 { grid-row-end: span 8; }
.span--9 { grid-row-end: span 9; }
.span--10 { grid-row-end: span 10; }
.span--11 { grid-row-end: span 11; }
.span--12 { grid-row-end: span 12; }
.span--13 { grid-row-end: span 13; }
.span--14 { grid-row-end: span 14; }
.span--15 { grid-row-end: span 15; }
.span--16 { grid-row-end: span 16; }
.span--17 { grid-row-end: span 17; }
.span--18 { grid-row-end: span 18; }
.span--19 { grid-row-end: span 19; }
.span--20 { grid-row-end: span 20; }
.span--21 { grid-row-end: span 21; }
.span--22 { grid-row-end: span 22; }
.span--23 { grid-row-end: span 23; }
.span--24 { grid-row-end: span 24; }
.span--25 { grid-row-end: span 25; }
.span--26 { grid-row-end: span 26; }
.span--27 { grid-row-end: span 27; }
.span--28 { grid-row-end: span 28; }
.span--29 { grid-row-end: span 29; }
.span--30 { grid-row-end: span 30; }
.span--31 { grid-row-end: span 31; }
.span--32 { grid-row-end: span 32; }
.span--33 { grid-row-end: span 33; }
.span--34 { grid-row-end: span 34; }
.span--35 { grid-row-end: span 35; }
.span--36 { grid-row-end: span 36; }
.span--37 { grid-row-end: span 37; }
.span--38 { grid-row-end: span 38; }
.span--39 { grid-row-end: span 39; }
.span--40 { grid-row-end: span 40; }
.span--41 { grid-row-end: span 41; }
.span--42 { grid-row-end: span 42; }
.span--43 { grid-row-end: span 43; }
.span--44 { grid-row-end: span 44; }
.span--45 { grid-row-end: span 45; }
.span--46 { grid-row-end: span 46; }
.span--47 { grid-row-end: span 47; }
.span--48 { grid-row-end: span 48; }

/* the rows a week's grid reserves — `--slot-h` is a token, and tokens live in tokens.css */
.hours--2 .hours__axis, .hours--2 .hcol { grid-template-rows: repeat(2, var(--slot-h)); }
.hours--4 .hours__axis, .hours--4 .hcol { grid-template-rows: repeat(4, var(--slot-h)); }
.hours--6 .hours__axis, .hours--6 .hcol { grid-template-rows: repeat(6, var(--slot-h)); }
.hours--8 .hours__axis, .hours--8 .hcol { grid-template-rows: repeat(8, var(--slot-h)); }
.hours--10 .hours__axis, .hours--10 .hcol { grid-template-rows: repeat(10, var(--slot-h)); }
.hours--12 .hours__axis, .hours--12 .hcol { grid-template-rows: repeat(12, var(--slot-h)); }
.hours--14 .hours__axis, .hours--14 .hcol { grid-template-rows: repeat(14, var(--slot-h)); }
.hours--16 .hours__axis, .hours--16 .hcol { grid-template-rows: repeat(16, var(--slot-h)); }
.hours--18 .hours__axis, .hours--18 .hcol { grid-template-rows: repeat(18, var(--slot-h)); }
.hours--20 .hours__axis, .hours--20 .hcol { grid-template-rows: repeat(20, var(--slot-h)); }
.hours--22 .hours__axis, .hours--22 .hcol { grid-template-rows: repeat(22, var(--slot-h)); }
.hours--24 .hours__axis, .hours--24 .hcol { grid-template-rows: repeat(24, var(--slot-h)); }
.hours--26 .hours__axis, .hours--26 .hcol { grid-template-rows: repeat(26, var(--slot-h)); }
.hours--28 .hours__axis, .hours--28 .hcol { grid-template-rows: repeat(28, var(--slot-h)); }
.hours--30 .hours__axis, .hours--30 .hcol { grid-template-rows: repeat(30, var(--slot-h)); }
.hours--32 .hours__axis, .hours--32 .hcol { grid-template-rows: repeat(32, var(--slot-h)); }
.hours--34 .hours__axis, .hours--34 .hcol { grid-template-rows: repeat(34, var(--slot-h)); }
.hours--36 .hours__axis, .hours--36 .hcol { grid-template-rows: repeat(36, var(--slot-h)); }
.hours--38 .hours__axis, .hours--38 .hcol { grid-template-rows: repeat(38, var(--slot-h)); }
.hours--40 .hours__axis, .hours--40 .hcol { grid-template-rows: repeat(40, var(--slot-h)); }
.hours--42 .hours__axis, .hours--42 .hcol { grid-template-rows: repeat(42, var(--slot-h)); }
.hours--44 .hours__axis, .hours--44 .hcol { grid-template-rows: repeat(44, var(--slot-h)); }
.hours--46 .hours__axis, .hours--46 .hcol { grid-template-rows: repeat(46, var(--slot-h)); }
.hours--48 .hours__axis, .hours--48 .hcol { grid-template-rows: repeat(48, var(--slot-h)); }

@media (max-width: 40rem) { .hours { --slot-h: var(--slot-h-tight); } .hour { min-width: 3rem; } }


/* **THE TOP LOADING BAR WAS REMOVED — checklist item 64.** Its own file said why it existed:
 * *"every page here is server-rendered and several of them are SLOW — a cold home page dials the
 * chain once and can take seconds."* Both halves of that stopped being true: the chain is out of
 * the read path entirely and the pages render a shell (`/` 0.85s, `/announcements` 0.007s), so
 * there is no page-load wait left for a page-wide bar to describe. What replaced it is local — the
 * busy control on the thing you pressed, the region spinner where the rows will appear.
 *
 * And it had become WRONG rather than merely redundant: it starts on a click or submit, and the
 * submits no longer navigate, so nothing ever cleared it — a bar reading "working" for ever over a
 * page that had finished. Removed rather than repaired, because repairing it would have given one
 * page two competing signals for the same wait. */


/* ---- reachable, not visible --------------------------------------------------------------------
 * The upload form is driven by the workstation's toolbar rather than shown (see
 * `pages._upload_form`), so it is taken out of the layout without being taken out of the document.
 *
 * CLIPPED RATHER THAN `display: none`, and the difference is not pedantry: a `display: none`
 * control is removed from the accessibility tree and from the tab order, so the keyboard route to
 * uploading would disappear along with the visual one. This keeps the input focusable and
 * clickable — by the toolbar's script and by a person tabbing to it — while occupying no space.
 *
 * `clip-path` alongside the legacy `clip` because the old property is deprecated and the new one is
 * not universally honoured on replaced elements; together they are the pattern that actually works. */
.a11y-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; white-space: nowrap; border: 0;
  clip: rect(0 0 0 0); clip-path: inset(50%);
}
/* **THE FORM CARRIES THE CLIP, NOT ITS CHILDREN — and the first attempt got this exactly wrong.**
 * `display: contents` on the form promoted the file input to a grid item of the finding aid, where
 * it measured 0px and the layout guard refused it. It was right to: a zero-width item in a column
 * layout is indistinguishable from a broken one.
 *
 * `position: absolute` (in `.a11y-only`) takes the form OUT OF FLOW, so it is not a grid item at
 * all and the grid has no row to keep for it. One clipped element, nothing measurable inside it. */
.upload--driven { }

/* The way-forward control's own refusal slot (checklist item 56, property 4). `more.js` appends it
 * beside the link when a page of rows could not be read, so the reader is told rather than silently
 * navigated away from three pages they had already loaded. `data-say-class` on the element is what
 * `oar.say` restores the class from, so the danger state cannot leak into the next message. */
.more__note { margin-left: var(--s3); font-size: var(--t-small); color: var(--ink-muted); }
.more__note.danger { color: var(--danger); }

/* ---- a record's address, copyable — checklist item 58 -------------------------------------------
 *
 * Operator, 2026-09-20: *"wherever we present a yobj that is clickable and reaches the resources
 * page, we have to provide a copyable link as well — so that the user can copy and share it
 * offline."*
 *
 * **THE ADDRESS IS RENDERED AS TEXT, NOT HIDDEN BEHIND THE BUTTON**, and the rule below keeps it
 * readable rather than tidy: `navigator.clipboard` needs JavaScript and a secure context, so a
 * design that put the address inside the control would leave a person with no script looking at a
 * button that does nothing. Quiet and small — it is a reference, in the same register as the
 * gutter's — and it wraps, because a lookup path is one long word to a browser and the one layout
 * fault this register does not permit is a page wider than the screen.
 *
 * `--mono` and `--ink-faint` are the gutter's own voice: this is the same KIND of fact. No token is
 * added and no colour moves (`docs/oops-interaction.md` §6). */
.share { display: inline-flex; align-items: baseline; flex-wrap: wrap; gap: var(--s2);
         margin-left: var(--s3); min-width: 0; }
.share__at { font-family: var(--mono); font-size: var(--t-gutter); color: var(--ink-faint);
             overflow-wrap: anywhere; min-width: 0; }
/* THE CONTROL IS QUIET. Copying an address is a secondary act beside going and reading the record,
 * and a second filled plate per row would make a feed of ten records a page of twenty buttons. */
.share__go {
  font: 500 var(--t-gutter)/1 var(--mono); letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-muted); background: transparent; border: none;
  box-shadow: 0 0 0 1px var(--line-strong);
  padding: var(--s1) var(--s2); cursor: pointer; white-space: nowrap;
}
.share__go:hover { color: var(--ink); background: var(--board-well); }
/* The receipt, and the refusal — `docs/oops-interaction.md` §3: the control has a place its answer
 * lands, beside it. `--ok` is *landed, confirmed*, and `oar.say` clears it again. */
.share__said { font-size: var(--t-gutter); font-family: var(--mono); color: var(--ok); }
.share__said.danger { color: var(--danger); }

/* ─── the calendar's event sheet — checklist item 68 ─────────────────────────────────────────────
 *
 * **FLAT, DRAWN, AND CENTRED ON NOTHING CLEVER.** The register is 1px edges and no radii; a modal
 * is where a design reaches for a shadow and a rounded card, and this one deliberately does not.
 * What separates it from the page is the scrim and the edge, which is what separates every other
 * surface here.
 *
 * `[hidden]` does the hiding (the stylesheet cannot be inline, and `display` in a `style` attribute
 * is dropped by `style-src 'self'` without a word), so nothing below fights it: the `display: grid`
 * is on `.sheet__panel`, never on `.sheet` itself. */
.sheet {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center; padding: var(--s4);
  background: var(--scrim);
}
.sheet[hidden] { display: none; }
.sheet__panel {
  display: grid; gap: var(--s3); align-content: start;
  width: min(34rem, 100%); max-height: 80vh; overflow-y: auto;
  padding: var(--s4); background: var(--bg-surface); border: 1px solid var(--line);
}
/* **`:focus-visible`, NOT `:focus`.** The panel is given the focus by script the moment it opens,
 * so a plain `:focus` ring draws a loud accent border around every sheet a mouse opened — reading as
 * part of the design rather than as "you are here". Keyboard opening still rings, which is the case
 * the ring is for. */
.sheet__panel:focus-visible { outline: 2px solid var(--focus-ring-color); outline-offset: 2px; }
.sheet__head { display: flex; align-items: start; justify-content: space-between; gap: var(--s3); }
.sheet__title { margin: 0; font-size: var(--t-title); }
.sheet__close {
  border: 1px solid var(--line); background: transparent; color: var(--ink-muted);
  font-size: var(--t-title); line-height: 1; padding: 0 var(--s2); cursor: pointer;
}
.sheet__close:hover { color: var(--ink); border-color: var(--ink); }
.sheet__when { margin: 0; font-family: var(--mono); color: var(--ink-muted); }
.sheet__place { margin: 0; }
.sheet__summary { color: var(--ink-muted); }
.sheet__join { margin: 0; }
.sheet__acts { display: flex; gap: var(--s3); margin: 0; flex-wrap: wrap; }
.sheet__act { font-family: var(--mono); font-size: var(--t-gutter); }
/* A chip is a control now, whether it is an anchor or a span — say so to the pointer. */
.chip[data-event] { cursor: pointer; }

/* **THE DEFERRED REGION IS TRANSPARENT — checklist item 73.** It exists only so the script has
 * somewhere to put the answer; what it holds was a direct child of `<main>` before deferral and must
 * go on being one, or the finding aid's columns stop reaching it. `display: contents` is how
 * `_section` already does this, and the grid guard walks through it for exactly that reason. */
.region { display: contents; }
/* The waiting state inside a transparent region is a direct grid child of `<main>` wherever the
 * region is not itself inside a section — so it claims the columns the same way `.section .empty`
 * does, or it auto-places into the gutter and the sentence renders one word per line. */
/* **GRID-COLUMN ONLY — NO `padding-left`.** `.empty` already sets the indent, and repeating it at
 * this higher specificity overrides `.empty--blind`'s own `margin-left`, so an OUTAGE sentence
 * rendered inside a region would silently lose the indent that tells it apart from a quiet one.
 * Caught by `test_THE_TWO_SILENCES_ARE_TOLD_APART_WITHOUT_COLOUR`, which exists for this. */
.region > .empty, .region > noscript > .more { grid-column: 1 / -1; }
