/* ImpromptBrü home — page styles. Shared by index.html (the standalone preview)
   and ImpromptbruHome.dc.html (the template entry), so the two cannot drift.
   Tokens come from ../../styles.css; the sub-brand layer switches on via
   <html data-brand="impromptbru">.

   RULES HELD HERE, because they are what makes the Astro port cheap:
   1. Every value is a custom property from the design system. No hex literals.
   2. Every rule is a class on markup Site.jsx renders, so each block becomes a
      .astro partial with the same class names and no CSS rewrite.
   3. Nothing is styled off a JS-set attribute except html[data-theme] (theme.js).
   4. Zero layout depends on JavaScript running. */

*,*::before,*::after{box-sizing:border-box}
html{background:var(--color-bg-page)}
body{margin:0}
.page{min-height:100vh;background:var(--color-bg-page);color:var(--color-text);font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-normal);-webkit-font-smoothing:antialiased}
.wrap{max-width:var(--wrap);margin:0 auto;padding:0 var(--space-6)}
:root{--wrap:920px}
/* THE NOTES INDEX RUNS NARROWER THAN THE HOME PAGE. Its whole body is one column of
   short rows, and at 920 the row's three parts drift apart until the date on the
   right stops belonging to the title on the left. 820 pulls them back into one
   object without touching the pages whose content is wide by nature. The header
   comes with it, so the page keeps a single edge. The header and footer are outside
   this scope on purpose: site chrome is the same width everywhere. */
.pagenotes main .wrap{--wrap:820px}
/* Links, defined once, so anything Chris adds later inherits the accent instead of
   browser blue. Jade-ink is the only jade that may carry a word.
   :not([class]) is load-bearing. Without it this rule is (0,1,1) and outranks every
   single-class component rule in this file on SPECIFICITY, not order, so .navlink,
   .gbtn and .chip all silently rendered jade and the graph button's label vanished
   on its own jade fill. Scoping it to unclassed links means prose links get the
   accent and every component keeps the colour it declares. */
.page a:not([class]){color:var(--color-text-accent);text-decoration-color:color-mix(in oklab,var(--color-text-accent) 40%,transparent);text-underline-offset:3px}
.page a:not([class]):hover{color:var(--color-accent-hover);text-decoration-color:currentColor}
.page ::selection{background:var(--color-accent);color:var(--color-on-accent)}
.page a:focus-visible,.page button:focus-visible{outline:2px solid var(--color-ring);outline-offset:3px;border-radius:var(--radius-sm)}
.mono{font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase}
.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* ---------- header ----------
   Row 1 is identity + the theme control. Row 2 is the whole site nav, which
   becomes a horizontal rail below 760px from the same markup. */
.hdr{position:sticky;top:0;z-index:20;background:var(--color-bg-page);border-bottom:var(--border-hair) solid var(--color-border)}
.hdrin{display:flex;align-items:center;gap:var(--space-4);padding-top:var(--space-3);padding-bottom:var(--space-3);flex-wrap:wrap}
.brand{display:flex;align-items:center;gap:var(--space-2);text-decoration:none;color:inherit;flex:none}
.brand:hover{color:inherit}
.wordmark{font-family:var(--font-display);font-weight:var(--weight-extrabold);font-size:var(--text-lg);letter-spacing:-.025em;color:var(--color-heading);line-height:1}
/* The wordmark stands alone in the header. The short tagline used to sit beside it,
   which put "ideas as they brew" three times on one page (header, masthead, footer).
   Twice is the deal: the long form at the top for a stranger, the short form in the
   footer for whoever read that far. .brandtag is kept as a class because other pages
   in this property may want it where there is no masthead tagline above the fold. */
.brandtag{display:none}
.navwrap{display:contents}
.mainnav{display:flex;align-items:center;gap:var(--space-5);margin-left:auto}
.navlink{font-size:var(--text-sm);font-weight:var(--weight-semibold);color:var(--color-text-muted);text-decoration:none;padding:4px 0;border-bottom:2px solid transparent;white-space:nowrap}
.navlink:hover,.navlink[aria-current="page"]{color:var(--color-heading);text-decoration:none}
.navlink[aria-current="page"]{border-bottom-color:var(--color-accent)}
.navutil{display:flex;align-items:center;gap:var(--space-4);padding-left:var(--space-4);border-left:var(--border-hair) solid var(--color-border)}
/* Notebook / Topics: one destination, two entrances. Tight gap plus a hairline
   slash so the pair reads as a single nav slot against the --space-5 rhythm
   between real destinations. */
.navpair{display:flex;align-items:center;gap:8px}
.navpairsep{color:var(--color-border);font-size:var(--text-sm);line-height:1;user-select:none}
.navutil .navlink{display:inline-flex;align-items:center;gap:6px}
/* The theme trio. theme.js owns aria-pressed; CSS owns every pixel of the look.
   The selected ink is --color-on-accent, NOT the parent's hardcoded violet:
   jade is a dark fill on light and a light fill on night, so only the token gets
   both right. */
.themeset{display:flex;align-items:center;gap:2px;border:var(--border-hair) solid var(--color-border);border-radius:var(--radius-pill);padding:2px;flex:none}
[data-theme-set]{display:inline-flex;align-items:center;justify-content:center;gap:4px;min-width:30px;height:26px;padding:0 7px;border:none;border-radius:var(--radius-pill);background:none;color:var(--color-text-muted);cursor:pointer;font:inherit}
[data-theme-set]:hover{color:var(--color-heading)}
[data-theme-set][aria-pressed="true"]{background:var(--color-accent);color:var(--color-on-accent)}
[data-theme-set] .mono{font-size:9px}

/* ---------- section scaffolding ---------- */
/* Padding is set per-EDGE here on purpose: .sec and .mast also carry .wrap, and
   a shorthand `padding:` in either one would silently null .wrap's gutters. */
.sec{padding-top:var(--space-7)}
.shead{display:flex;align-items:baseline;justify-content:space-between;gap:var(--space-4);margin-bottom:var(--space-4);flex-wrap:wrap}
.more{font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-accent);text-decoration:none;white-space:nowrap;display:inline-flex;align-items:center;gap:5px}
.more:hover{text-decoration:underline;text-underline-offset:3px}
.h2{font-family:var(--font-display);font-weight:var(--weight-extrabold);font-size:var(--text-xl);letter-spacing:var(--tracking-tight);line-height:var(--leading-tight);color:var(--color-heading);margin:0}

/* ---------- 1. masthead ----------
   Deliberately thin: the mark, the long tagline, one note, one byline. The mark
   is 64px here because that is the only size where the ü is fully drawn — the
   header runs it at 26 (small cut) and the footer at 22 (flood). */
.mast{display:grid;grid-template-columns:auto minmax(0,1fr);gap:var(--space-5);align-items:start;padding-top:var(--space-7);padding-bottom:var(--space-6)}
.masth{font-family:var(--font-display);font-weight:var(--weight-extrabold);font-size:clamp(1.7rem,7.2vw,var(--text-3xl));letter-spacing:var(--tracking-tight);line-height:var(--leading-tight);color:var(--color-heading);margin:0 0 var(--space-3);text-wrap:balance}
.mastp{margin:0 0 var(--space-4);max-width:56ch;color:var(--color-text-lead);font-size:var(--text-lg);line-height:var(--leading-relaxed);text-wrap:pretty}
.byline{margin:0;font-size:var(--text-sm);color:var(--color-text-muted);max-width:68ch;text-wrap:pretty}
.byline b{color:var(--color-text);font-weight:var(--weight-semibold)}
/* THE WELCOME BLOCK. "New here?" lands before the name on purpose: a first-time
   reader wants a way in, and only then cares whose notebook this is. It was a
   washed, rimmed, uppercase-labelled callout for one revision and shouted — two
   quiet lines under a hairline say the same thing, so the only decoration left is
   the rule. No fill, no rim, no mono label. */
.hello{margin:var(--space-4) 0 0;max-width:68ch;padding-top:var(--space-3);border-top:var(--border-hair) solid var(--color-border-subtle)}
.hello .byline{margin:0;max-width:none}
/* The one green word on the page. Jade-INK, not jade — see the link rule at the
   top of this file: it is the only jade that may carry a word. It also carries the
   only bold in the line: the name that follows stays plain, because two weights
   back to back read as two headings.
   Both rules are scoped to .hello .byline b so they beat .byline b (0,1,1) on
   specificity rather than order, and :not(.newhere) keeps the de-bold off the
   green word — without it the flatten rule outranks the accent and the line
   renders with no bold at all. */
.hello .byline b.newhere{color:var(--color-stage-ink);font-weight:var(--weight-semibold)}
.hello .byline b:not(.newhere){font-weight:var(--weight-regular);color:inherit}

/* ---------- the activity timeline ----------
   Full width UNDER the stream: the three rows answer "what is newest" and the strip
   answers the question they cannot — how often anything happens here — so it reads
   as the summary of the list rather than a chart the list is filed under.
   ONE COLUMN PER ISO WEEK, 52 of them, each column two stacked segments: edits on
   top in the jade wash, entries STARTED at the base in solid jade fill. Starting a
   note is the rarer event, so it sits on the baseline where it can be seen at 2px;
   floated on top it would be a speck. Columns are flex:1 rather than a fixed width,
   so the strip is exactly as wide as the column at every breakpoint.
   The whole thing is decorative in the accessibility tree: .tlsum carries the same
   information in a sentence, which is more useful read aloud than 52 numbers. */
/* NO KEY. Each channel is named where it is drawn — a mono label in the gutter, the
   line's own jade — so nothing has to be matched back to a swatch. The counts ride
   the labels, which is why the caption is four words. */
.tl{margin:var(--space-4) 0 0;display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:var(--space-3);align-items:end}
.tllab{font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-stage-ink);white-space:nowrap;padding-bottom:3px}
.tllab.st{align-self:start;padding:1px 0 0}
.tlplot{position:relative;height:64px;border-bottom:var(--border-hair) solid var(--color-border)}
.tlsvg{position:absolute;inset:0;width:100%;height:100%;display:block;overflow:visible}
.tlridge{fill:none;stroke:var(--color-stage-rim);stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
/* Month boundaries as hairlines INSIDE the plot rather than only under it: they are
   what turns a shape into a reading, and the labels below then only have to name
   what the ridge already divides. */
.tlt{stroke:var(--color-border-subtle);stroke-width:1;vector-effect:non-scaling-stroke}
/* NOTHING IN THIS CHART IS A FILLED SHAPE. A filled vessel already means brew stage
   everywhere else on the site, and a chart that reuses the fill for volume teaches a
   reader the wrong word. So: edit volume is a LINE, unfilled, and a week that saw
   something start is a TICK below the axis — a register the marker vocabulary does
   not occupy, and lines carry no stage meaning at any length.
   The tick steps at one, two, three-or-more, which is all the resolution a strip
   this size can show honestly. */
.tlmarks{height:11px;display:flex;align-items:flex-start;gap:2px}
.tlw{flex:1;min-width:0;display:flex;justify-content:center}
.tlm{width:1.5px;height:4px;border-radius:1px;background:var(--color-stage-rim)}
.tlm[data-n="2"]{height:7px}
.tlm[data-n="3"]{height:11px}
.tlmonths{grid-column:2;display:flex;gap:2px;margin-top:7px}
.tlmonths span{min-width:0;font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-faint);overflow:hidden;white-space:nowrap}
.tlsum{grid-column:2;display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-3);margin:var(--space-2) 0 0;font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-faint)}

/* ---------- 2. the stream ----------
   ONE list, not two shuffled together: every row has the same three columns and
   the same title size, so the eye reads a single stream. The typing is carried by
   the gutter mark (shape + ink) and by the first word of the meta line, never by
   a differently-shaped row. */
.stream{list-style:none;margin:0;padding:0;border-top:var(--border-hair) solid var(--color-rule)}
.srow{border-bottom:var(--border-hair) solid var(--color-rule)}
.srowa{display:grid;grid-template-columns:12px minmax(0,1fr) auto;column-gap:var(--space-3);align-items:start;min-height:53px;padding:7px var(--space-2);margin:0 calc(-1 * var(--space-2));text-decoration:none;color:inherit;border-radius:var(--radius-sm);transition:background 120ms ease}
.srowa:hover{background:var(--color-bg-surface-raised);color:inherit;text-decoration:none}
.stline{display:flex;align-items:baseline;flex-wrap:wrap;gap:0 9px}
/* Read time, parked beside the title and revealed on hover or focus. Opacity rather
   than display so the row never reflows under the pointer, and mono micro so it reads
   as an instrument and not as part of the sentence the title is making. */
.sread{font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-faint);white-space:nowrap;opacity:0;transition:opacity .1s linear}
.srowa:hover .sread,.srowa:focus-visible .sread{opacity:1}
/* THE RELATIONSHIP, beside the title on the same hover terms as the read time,
   and a step smaller because it qualifies the row rather than describing it.
   Opacity rather than display, so it holds its space (no reflow on hover) and
   stays in the accessibility tree: a screen reader still reads "points here"
   whether or not a pointer ever arrives. */
.sdir{font-family:var(--font-mono);font-size:calc(var(--text-2xs) * .9);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-faint);white-space:nowrap;opacity:0;transition:opacity .1s linear}
.srowa:hover .sdir,.srowa:focus-visible .sdir{opacity:1}
/* A TOUCH DEVICE NEVER HOVERS, so hiding it there hides it for good. On a
   coarse pointer both hints simply stay on. */
@media (hover:none){.sdir,.sread{opacity:1}}
.stitle{display:block;font-size:var(--text-sm);font-weight:var(--weight-semibold);line-height:1.35;color:var(--color-heading);letter-spacing:-.01em;text-wrap:pretty}
.srowa:hover .stitle{text-decoration:underline;text-decoration-color:var(--color-accent);text-decoration-thickness:1.5px;text-underline-offset:3px}
.smeta{display:block;margin-top:3px;font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-faint);line-height:1.5}
.smeta .sep{opacity:.55;padding:0 5px}
/* The edited stamp: pencil + a written-out relative age, and nothing when the last
   edit is more than thirty days old. */
.sedit{display:inline-flex;align-items:center;gap:4px}
.sedit svg{opacity:.75;position:relative;top:-.5px}
.mstage{color:var(--color-stage-ink)}
.swhen{display:inline-flex;align-items:center;gap:5px;font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-faint);white-space:nowrap;padding-top:2px}
/* A calendar glyph so the right-hand date reads as the day the thing was made rather
   than as another recency signal \u2014 the pencil on the meta line is the recent one. */
.swhen svg{opacity:.7;position:relative;top:-.5px}
/* THE MARKER NAMES ITSELF ON HOVER. Kind and stage are not written in the meta line
   any more, so the marker has to be able to say them. A CSS tooltip rather than a
   title attribute: the native one waits about a second before it appears, which for
   a 12px target reads as nothing happening.
   The tooltip hangs off the ROW, not off the marker: the post marker is a rotated
   square and a child of it inherits the rotation. Left-aligned to the marker rather
   than centred on it, so it opens into the column instead of off the page edge.
   Sighted-hover only; the same words are in the marker's .sr text for everyone else. */
.srowa[data-tip]{position:relative}
.srowa[data-tip]::after{content:attr(data-tip);position:absolute;left:0;top:8px;transform:translateY(-100%);z-index:5;padding:3px 7px;border:var(--border-hair) solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg-surface-raised);box-shadow:var(--shadow-card);font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text);white-space:nowrap;opacity:0;visibility:hidden;pointer-events:none;transition:opacity .08s linear}
.srowa[data-tip]:has(.mk:hover)::after,.srowa[data-tip]:focus-visible::after{opacity:1;visibility:visible}
.streamnote{margin:var(--space-4) 0 0;font-size:var(--text-xs);color:var(--color-text-faint);max-width:70ch;text-wrap:pretty}

/* ---------- the bench (the NOTES INDEX, notebook.html) ----------
   The notes index's stream sits in an instrument rather than on the page. Two knobs
   (type, brew stage), a sort, and the rows. A lab bench because that is already
   this property's metaphor, and because the brew stage was always a level meter: the
   stage knob is the three vessels themselves, at the size a row uses them.

   It was on the home page first and it was too much there. A home page introduces the
   place; controls belong on the page whose job is finding something.

   ZERO JAVASCRIPT. Native radios plus :has(). The filter is CSS, so it works with
   JS off, it ports to Astro as plain HTML, and it needs no hydration. :has() has
   been baseline in every evergreen browser since 2023.

   The hooks are the inputs' CLASS and VALUE, never their id. Ids have to be unique
   per instance, and the kit sheet puts four benches on one page; keyed on value,
   every rule is scoped by the :has() to its own .bench and the widget can repeat. */
.bench{border:var(--border-hair) solid var(--color-border);border-radius:var(--radius-md);background:var(--color-bg-surface-raised);overflow:hidden}
/* The control channel is the PAGE paper inside a RAISED plate, so it reads as a
   recessed strip in both themes without inventing a colour for it. */
.benchctl{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-3) var(--space-2);padding:var(--space-3) var(--space-4);background:var(--color-bg-page);border-bottom:var(--border-hair) solid var(--color-border)}
/* Search sits in the bench channel on a ROW OF ITS OWN, under the knobs. It is the
   one control that can reach anything rather than narrow what is shown, and at the
   head of the channel it was competing with the knobs for the same glance; full
   width under them, it reads as the way in rather than as a fourth filter.
   A FORM, not a bare input: with JS off it submits to /notes?q=, and Pagefind
   intercepts it when JS is on, so the control is never dead. */
.bsearch{display:flex;align-items:center;gap:6px;flex:1 1 320px;min-width:0;margin-top:var(--space-1);padding:4px 11px;border:var(--border-hair) solid var(--color-border);border-radius:var(--radius-pill);background:var(--color-bg-surface-raised);color:var(--color-text-faint)}
.bsearch:focus-within{border-color:var(--color-stage-rim);outline:2px solid var(--color-ring);outline-offset:2px}
.bsearch input{flex:1;min-width:0;border:0;padding:0;background:none;font-family:inherit;font-size:var(--text-sm);color:var(--color-text);outline:none;-webkit-appearance:none;appearance:none}
.bsearch input::placeholder{color:var(--color-text-faint);opacity:1}
.bsearch input::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}
/* Four Shown chips instead of three: the channel gives back the millimetres from
   the chip padding and the gaps rather than from the labels, so the three knobs
   still hold ONE LINE at 920. */
.knob{display:flex;align-items:center;gap:5px;flex:none}
.knoblab{font-family:var(--font-mono);font-size:9px;letter-spacing:.12em;text-transform:uppercase;color:var(--color-text-faint)}
/* A track never breaks internally on desktop: the channel wraps whole controls
   instead, so "Notes" can never end up on a line of its own under its own group.
   Below 760 the knobs stack in a column with full width and wrapping is fine again. */
.track{position:relative;display:flex;flex-wrap:nowrap;gap:2px;padding:2px;border:var(--border-hair) solid var(--color-border);border-radius:var(--radius-pill);background:var(--color-bg-surface-raised)}
.track input{position:absolute;width:1px;height:1px;margin:0;opacity:0;pointer-events:none}
.track label{display:inline-flex;align-items:center;gap:4px;padding:4px 5px;border-radius:var(--radius-pill);font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-muted);cursor:pointer;white-space:nowrap;transition:background 110ms ease,color 110ms ease}
/* The Shown track carries four words and the sort rail three more, so the chips in
   the channel run tighter than a chip elsewhere: 4px of side padding and .03em of
   tracking, which is what buys SORT its label back and still holds one line at 920. */
.knob .track label{letter-spacing:.03em;gap:3px;padding-left:4px;padding-right:4px}
.track label:hover{color:var(--color-heading)}
.track input:checked+label{background:var(--color-stage-wash);color:var(--color-text-accent-on-peach)}
.track input:focus-visible+label{outline:2px solid var(--color-ring);outline-offset:2px}
/* ALL THREE ON is the default and the resting state. The track keeps its ordinary
   grey field; what changes is that the three lit chips CLOSE UP into one continuous
   jade pill inside it — the gap goes to zero and only the outer corners stay round.
   Deselect any one and the field breaks back into separate chips, which is the
   moment the control starts saying something. */
.track:has(.k-type[value="topic"]:checked):has(.k-type[value="mine"]:checked):has(.k-type[value="inspired"]:checked):has(.k-type[value="stub"]:checked),
.track:has(.k-stage[value="steeping"]:checked):has(.k-stage[value="brewing"]:checked):has(.k-stage[value="poured"]:checked){gap:0}
.track:has(.k-type[value="topic"]:checked):has(.k-type[value="mine"]:checked):has(.k-type[value="inspired"]:checked):has(.k-type[value="stub"]:checked) label,
.track:has(.k-stage[value="steeping"]:checked):has(.k-stage[value="brewing"]:checked):has(.k-stage[value="poured"]:checked) label{border-radius:0}
.track:has(.k-type[value="topic"]:checked):has(.k-type[value="mine"]:checked):has(.k-type[value="inspired"]:checked):has(.k-type[value="stub"]:checked) .k-type:first-of-type+label,
.track:has(.k-stage[value="steeping"]:checked):has(.k-stage[value="brewing"]:checked):has(.k-stage[value="poured"]:checked) .k-stage:first-of-type+label{border-radius:var(--radius-pill) 0 0 var(--radius-pill)}
.track:has(.k-type[value="topic"]:checked):has(.k-type[value="mine"]:checked):has(.k-type[value="inspired"]:checked):has(.k-type[value="stub"]:checked) .k-type:last-of-type+label,
.track:has(.k-stage[value="steeping"]:checked):has(.k-stage[value="brewing"]:checked):has(.k-stage[value="poured"]:checked) .k-stage:last-of-type+label{border-radius:0 var(--radius-pill) var(--radius-pill) 0}
/* NOTHING checked shows everything, so it must LOOK like everything. With bench.js
   running this state is unreachable — turning off your last chip returns the whole
   track — so what follows is the no-JS fallback, and it keeps the plain checkboxes
   honest. Turning the
   last chip off lights all of them instead of leaving an empty track that contradicts
   the list under it. The inputs stay unchecked — the next click is then "only this
   one", which is what a reader who just emptied the track is reaching for. */
.track:has(.k-type):not(:has(.k-type:checked)) label,
.track:has(.k-stage):not(:has(.k-stage:checked)) label{background:var(--color-stage-wash);color:var(--color-text-accent-on-peach);border-radius:0}
.track:has(.k-type):not(:has(.k-type:checked)),
.track:has(.k-stage):not(:has(.k-stage:checked)){gap:0}
.track:has(.k-type):not(:has(.k-type:checked)) .k-type:first-of-type+label,
.track:has(.k-stage):not(:has(.k-stage:checked)) .k-stage:first-of-type+label{border-radius:var(--radius-pill) 0 0 var(--radius-pill)}
.track:has(.k-type):not(:has(.k-type:checked)) .k-type:last-of-type+label,
.track:has(.k-stage):not(:has(.k-stage:checked)) .k-stage:last-of-type+label{border-radius:0 var(--radius-pill) var(--radius-pill) 0}
.benchbody{padding:0 var(--space-4) var(--space-1)}
.benchbody .stream{border-top:none}
/* SORT, and no JavaScript for it either. The stream becomes a flex column so every
   row can take an `order`, and each row carries all four ranks as custom properties
   written at build time; the checked radio only chooses WHICH one feeds `order`.
   Direction is column-reverse rather than a second set of ranks, because every row
   rules on its bottom edge and reads the same either way up. */
.benchbody .stream{display:var(--bench-stream,flex);flex-direction:column}
.bench:has(.k-dir:checked) .benchbody .stream{flex-direction:column-reverse}
.bench:has(.k-sort[value="abc"]:checked) .srow{order:var(--o-abc)}
.bench:has(.k-sort[value="created"]:checked) .srow{order:var(--o-created)}
.bench:has(.k-sort[value="edited"]:checked) .srow{order:var(--o-edited)}
/* All three controls hold ONE line at 920 and that is a hard constraint, not a
   preference: the moment sort drops to a second row the bench reads as two
   instruments. It is bought with chip padding, not with shorter words. */
/* Sort is ONE control: four keys and, at the end of the same track, a single chip
   that flips the direction. A pair of A-Z / Z-A radios said the same thing in three
   times the width and read as a second decision; an arrow that points the way the
   list runs is the whole idea, and it fits on the control line.
   The chip does not take the checked fill — it is a button that flips, not an option
   that is on — and it carries the sort-direction glyph (bars plus an arrow) rather
   than a bare arrow, which on its own reads as "move" rather than "order". .benchbreak is an explicit row break
   rather than a hope about wrapping: at any width the search starts a new line. */
.knob-sort{gap:var(--space-2)}
.benchbreak{flex:0 0 100%;height:0}
/* The knobs sit in a <form> only so the reset can be native; display:contents keeps
   them as direct flex children of the channel. The reset itself is associated by
   form= rather than by nesting, which lets it sit on the search row where there is
   room — putting it on the control line would break the one-line rule above. It is
   ABSENT at the defaults, because a reset that does nothing is furniture. */
.benchform{display:contents}
.breset{display:none;flex:none;align-self:center;padding:5px 11px;border:var(--border-hair) solid var(--color-border);border-radius:var(--radius-pill);background:var(--color-bg-surface);font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-muted);cursor:pointer}
.breset:hover{border-color:var(--color-stage-rim);color:var(--color-heading)}
/* The press has to be felt, because the chip's own answer is to VANISH: reset puts
   the bench back to its defaults and the rule below stops matching. Lighting the
   chip while the button is down is the whole acknowledgement — an after-the-click
   flash would play on an element that is already gone. */
.breset:active{background:var(--color-stage-wash);border-color:var(--color-stage-rim);color:var(--color-text-accent-on-peach);transform:translateY(1px)}
.bench:has(.k-type:not(:checked)) .breset,
.bench:has(.k-stage:not(:checked)) .breset,
.bench:has(.k-sort:not([value="created"]):checked) .breset,
.bench:has(.k-dir:not(:checked)) .breset{display:inline-flex}
/* The sort keys are radios in a track, exactly like the two filter tracks: same
   chip, same lit state, same focus ring. They were a <select> while there were five
   of them; at three the rail fits and the channel stops holding two species of
   control. Radios rather than checkboxes because one key is one key — and because
   :checked is what the order rules above read. */
.track .dirchip{margin-left:3px;padding-left:7px;border-left:var(--border-hair) solid var(--color-border)}
.track .k-dir:checked+label{background:none;color:var(--color-text-accent)}
.dirchip>span{display:flex}
.dirchip .dirdn,.k-dir:checked+label .dirup{display:none}
.k-dir:checked+label .dirdn{display:flex}
/* Every row keeps its rule, including the last one: with a filter running, "last"
   and "last visible" are different elements and CSS cannot tell them apart. A list
   that always closes on a rule is filter-proof and reads as a ruled list. */
/* SEARCH RESULTS ARE THE BENCH'S BODY. `search.js` swaps `.benchbody` for
   `.sresults` in place, so the padding, the rows and the closing rule are the
   ones the notebook already uses and the card never changes shape — only its
   contents change. It had no rules at all before this and rendered as a bare
   list outside the card, which is why answering a search read as arriving on a
   different page.
   NO `.bench …` PREFIX ON ANYTHING HERE. These rows sit inside `.bench` but
   must stay outside the Shown/Stage filter, which is enforced on the other side
   too: `search.js` does not write `data-shown` or `data-stage` onto a result. */
.sresults{padding:0 var(--space-4) var(--space-1)}
.sresults .stream{border-top:none}
/* The count and the way out share one line: the sentence is the answer and the
   button is the undo, and stacking them would put two paragraphs above a list. */
.sresh{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:var(--space-2) var(--space-3);padding-bottom:var(--space-2)}
.sresh .streamnote{margin:var(--space-4) 0 0;flex:1 1 20ch}
/* Drawn as `.breset`, because it is the same promise in the same channel: put
   this control back to its resting state. It does NOT hide at rest — unlike the
   reset — because it only exists while there is a search to clear. */
.sclear{flex:none;align-self:center;margin-top:var(--space-4);padding:5px 11px;border:var(--border-hair) solid var(--color-border);border-radius:var(--radius-pill);background:var(--color-bg-surface);font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-muted);cursor:pointer}
.sclear:hover{border-color:var(--color-stage-rim);color:var(--color-heading)}
.sclear:active{background:var(--color-stage-wash);border-color:var(--color-stage-rim);color:var(--color-text-accent-on-peach);transform:translateY(1px)}
.spager{display:flex;flex-wrap:wrap;gap:4px;padding:var(--space-3) 0 var(--space-2)}
.spage{padding:4px 9px;border:var(--border-hair) solid var(--color-border);border-radius:var(--radius-pill);background:var(--color-bg-surface);font-family:var(--font-mono);font-size:var(--text-2xs);color:var(--color-text-muted);cursor:pointer}
.spage:hover{border-color:var(--color-stage-rim);color:var(--color-heading)}
.spage.here{background:var(--color-stage-wash);border-color:var(--color-stage-rim);color:var(--color-text-accent-on-peach)}
.benchempty{display:var(--bench-empty,none);margin:0;padding:var(--space-4) 0;font-size:var(--text-sm);color:var(--color-text-faint);text-wrap:pretty}/* THE FILTER. Both axes are independent toggles, not radios: the kinds are not
   mutually exclusive questions and neither are the stages, so "notes and hubs, but
   only the ones still steeping" is a thing a reader can want and radios cannot say.
   NOTHING checked means everything and so does EVERYTHING checked — which is the
   whole reason there is no "All" chip: All is the resting state of the control, and
   it is drawn as one continuous field rather than as a fourth option.
   That equivalence is load-bearing on the stage axis. A post has no brew stage, so
   ticking all three stages as a FILTER would silently drop every post; reading all
   three as "any stage, and no stage" keeps the resting state honest. If you want to
   drop the posts, the control that says so is the one labelled Shown.
   Pure hide rules, three per axis: a row goes when its own value is switched off.
   EVERY RULE HERE IS SCOPED TO `.benchbody`, not to `.bench`. The bench now has
   a second list of rows inside it — `.sresults`, which `search.js` swaps in for
   the body — and the filter must not reach it: a search answers across the whole
   notebook, so a Shown or Stage knob left narrow would hide rows the header had
   just counted. The unstaged rule below made that certain rather than likely,
   because a result row carries no `data-stage` at all and so matched it. */
.bench:has(.k-type:checked):not(:has(.k-type[value="topic"]:checked)) .benchbody .srow[data-shown="topic"],
.bench:has(.k-type:checked):not(:has(.k-type[value="mine"]:checked)) .benchbody .srow[data-shown="mine"],
.bench:has(.k-type:checked):not(:has(.k-type[value="inspired"]:checked)) .benchbody .srow[data-shown="inspired"],
.bench:has(.k-type:checked):not(:has(.k-type[value="stub"]:checked)) .benchbody .srow[data-shown="stub"]{display:none}
.bench:has(.k-stage:checked):not(:has(.k-stage[value="steeping"]:checked)) .benchbody .srow[data-stage="steeping"],
.bench:has(.k-stage:checked):not(:has(.k-stage[value="brewing"]:checked)) .benchbody .srow[data-stage="brewing"],
.bench:has(.k-stage:checked):not(:has(.k-stage[value="poured"]:checked)) .benchbody .srow[data-stage="poured"]{display:none}
/* The unstaged rows — posts — go only while the stage selection is PARTIAL. Three
   rules, one per stage that could be off: if any of them is off the selection is
   saying something about stages, and a row with no stage is not part of that answer.
   All three on and no rule fires, which is the equivalence above. */
.bench:has(.k-stage:checked):not(:has(.k-stage[value="steeping"]:checked)) .benchbody .srow:not([data-stage]),
.bench:has(.k-stage:checked):not(:has(.k-stage[value="brewing"]:checked)) .benchbody .srow:not([data-stage]),
.bench:has(.k-stage:checked):not(:has(.k-stage[value="poured"]:checked)) .benchbody .srow:not([data-stage]){display:none}
/* A post has no stage, so with posts the only kind on, the stage knob has nothing to
   say. Dimming a control that would empty the list is kinder than letting it. */
.bench:has(.k-type[value="stub"]:checked):not(:has(.k-type[value="topic"]:checked)):not(:has(.k-type[value="mine"]:checked)):not(:has(.k-type[value="inspired"]:checked)) .knob-stage{opacity:.4;pointer-events:none}

/* EMPTY STATE, computed from the DATA rather than from computed display: if nothing
   in the list can satisfy the current settings, say so instead of showing a blank
   list that always closes on a rule. Every combination of the two axes that can
   constrain gets one test, emitted in the Astro build from the same lists that render
   the controls. What ships here is that matrix PRUNED against the twelve preview rows:
   a combination that has a row in it can never come up empty, so its selector is dead
   weight and is not written. They set two CUSTOM PROPERTIES on the bench rather than
   styling two descendants, which is what keeps this one rule instead of two: the
   properties inherit, and the message and the list read them below. */
.bench:not(:has(.k-type[value="topic"]:checked)):not(:has(.k-type[value="mine"]:checked)):has(.k-type[value="inspired"]:checked):not(:has(.k-type[value="stub"]:checked)):has(.k-stage[value="steeping"]:checked):not(:has(.k-stage[value="brewing"]:checked)):not(:has(.k-stage[value="poured"]:checked)):not(:has(.srow[data-shown="inspired"][data-stage="steeping"])),
.bench:not(:has(.k-type[value="topic"]:checked)):not(:has(.k-type[value="mine"]:checked)):not(:has(.k-type[value="inspired"]:checked)):has(.k-type[value="stub"]:checked):has(.k-stage[value="steeping"]:checked):not(:has(.k-stage[value="brewing"]:checked)):not(:has(.k-stage[value="poured"]:checked)),
.bench:not(:has(.k-type[value="topic"]:checked)):not(:has(.k-type[value="mine"]:checked)):not(:has(.k-type[value="inspired"]:checked)):has(.k-type[value="stub"]:checked):not(:has(.k-stage[value="steeping"]:checked)):has(.k-stage[value="brewing"]:checked):not(:has(.k-stage[value="poured"]:checked)),
.bench:not(:has(.k-type[value="topic"]:checked)):not(:has(.k-type[value="mine"]:checked)):not(:has(.k-type[value="inspired"]:checked)):has(.k-type[value="stub"]:checked):has(.k-stage[value="steeping"]:checked):has(.k-stage[value="brewing"]:checked):not(:has(.k-stage[value="poured"]:checked)),
.bench:not(:has(.k-type[value="topic"]:checked)):not(:has(.k-type[value="mine"]:checked)):not(:has(.k-type[value="inspired"]:checked)):has(.k-type[value="stub"]:checked):not(:has(.k-stage[value="steeping"]:checked)):not(:has(.k-stage[value="brewing"]:checked)):has(.k-stage[value="poured"]:checked),
.bench:not(:has(.k-type[value="topic"]:checked)):not(:has(.k-type[value="mine"]:checked)):not(:has(.k-type[value="inspired"]:checked)):has(.k-type[value="stub"]:checked):has(.k-stage[value="steeping"]:checked):not(:has(.k-stage[value="brewing"]:checked)):has(.k-stage[value="poured"]:checked),
.bench:not(:has(.k-type[value="topic"]:checked)):not(:has(.k-type[value="mine"]:checked)):not(:has(.k-type[value="inspired"]:checked)):has(.k-type[value="stub"]:checked):not(:has(.k-stage[value="steeping"]:checked)):has(.k-stage[value="brewing"]:checked):has(.k-stage[value="poured"]:checked),
.bench:not(:has(.k-type[value="topic"]:checked)):not(:has(.k-type[value="mine"]:checked)):has(.k-type[value="inspired"]:checked):has(.k-type[value="stub"]:checked):has(.k-stage[value="steeping"]:checked):not(:has(.k-stage[value="brewing"]:checked)):not(:has(.k-stage[value="poured"]:checked)):not(:has(.srow[data-shown="inspired"][data-stage="steeping"])){--bench-empty:block;--bench-stream:none}



/* ---------- the brew-stage marker + the type mark ----------
   ROW ANATOMY IS SETTLED: the stage lives in the GUTTER as a level, and the word
   lives in the meta line. The pill treatment beside the title was the alternative;
   it was louder and it cost title width, so it is not in the row. The pill still
   ships for note headers, filters and cards, where it stands alone.
   One 12px box, two drawings. A note is a VESSEL and its stage is how full it is
   (--stage-level-*); a post is a filled diamond in the parent's violet. Shape
   carries it for a colour-blind reader, ink carries it at a glance, the meta word
   carries it for a screen reader. A note with no stage is an empty vessel: no
   placeholder, no reserved gap, nothing to explain. */
.mk{width:12px;height:12px;flex:none;margin-top:4px;box-sizing:border-box}
.mk[data-kind="note"]{border-radius:50%;border:1.5px solid var(--color-stage-rim);background:linear-gradient(to top,var(--color-stage-fill) var(--lvl,0%),transparent var(--lvl,0%))}
/* A topic hub is a note-shaped thing that collects other notes, so it keeps the
   jade ring and the fill level but squares off: same family, different vessel. */
.mk[data-kind="topic"]{border-radius:2px;border:1.5px solid var(--color-stage-rim);background:linear-gradient(to top,var(--color-stage-fill) var(--lvl,0%),transparent var(--lvl,0%))}
.mk[data-kind="post"]{width:10px;height:10px;margin-top:5px;border-radius:1.5px;background:var(--color-type-post);transform:rotate(45deg)}
[data-stage="steeping"]{--lvl:var(--stage-level-steeping)}
[data-stage="brewing"]{--lvl:var(--stage-level-brewing)}
[data-stage="poured"]{--lvl:var(--stage-level-poured)}

/* THE POUR. Hovering anything that carries a stage empties its vessel and fills it back to
   that thing's level in under a second. It began as one gesture on the note header's stage
   pill and it belongs everywhere the marker appears, because it IS the explanation of the
   scale: the mark is a cup and how full it is is the stage. A reader who never opens the
   colophon still learns the system by brushing past a row.
   IT LIVES HERE, not in note.css, because site.css owns .mk. The note page's key ladder
   still adds its own staggered case on top (note.css section 10) — this file owns the
   gesture, that one owns the one piece of theatre built out of it.

   --lvl IS REGISTERED so it can interpolate: an unregistered custom property is a string
   and jumps from 0% to its value in one frame. --lv is the TARGET, kept separate because
   --lvl is the thing being animated and cannot also be its own destination. */
@property --lvl{syntax:"<percentage>";inherits:true;initial-value:0%}
[data-stage="steeping"]{--lv:var(--stage-level-steeping)}
[data-stage="brewing"]{--lv:var(--stage-level-brewing)}
[data-stage="poured"]{--lv:var(--stage-level-poured)}
@keyframes skpour{from{--lvl:0%}to{--lvl:var(--lv)}}
/* EVERY RULE BELOW REQUIRES .mk[data-stage]. A post has no stage — it is a diamond, not a
   vessel — and an unstaged marker would animate to nothing, which still reads as a flicker.
   The attribute selector is what keeps posts and kind rows out of it for free.
     .srowa  the stream row, so: home page, the notebook bench, and backlinks
     .jrow   the jump palette, on hover AND on keyboard selection — the arrow keys pour
             their way down the list, which is the one place the gesture is not mouse-only
     .gpr    the connections panel's related rows
     .stagec the colophon's three definition cards, where the pour IS the definition
     .track label  the bench's Stage chips, and this is the best place it happens: the chip
             IS the stage, so pouring its cup on hover explains what the filter will do
             before it is clicked. Hovering along the three left to right runs the ladder by
             hand — the same thing the note key's staggered open does, driven by the reader.
     .stagefilter a  the index pages' link version of the same control */
.srowa:hover .mk[data-stage],
.page .jrow:hover .mk[data-stage],
.page .jrow[aria-selected="true"] .mk[data-stage],
.gpr:hover .mk[data-stage],
.stagec:hover .mk[data-stage],
.track label:hover .mk[data-stage],
.page .stagefilter a:hover .mk[data-stage]{animation:skpour .85s cubic-bezier(.22,.61,.36,1)}
@media (prefers-reduced-motion:reduce){.srowa:hover .mk[data-stage],.page .jrow:hover .mk[data-stage],.page .jrow[aria-selected="true"] .mk[data-stage],.gpr:hover .mk[data-stage],.stagec:hover .mk[data-stage],.track label:hover .mk[data-stage],.page .stagefilter a:hover .mk[data-stage]{animation:none}}
/* The pill form: the same marker plus the word, on the jade wash. Used on note
   headers, on filter controls, and as the stream row's alternative treatment. */
.stagepill{display:inline-flex;align-items:center;gap:6px;padding:3px 9px 3px 7px;border-radius:var(--radius-pill);background:var(--color-stage-wash);color:var(--color-text-accent-on-peach);font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;white-space:nowrap}
.stagepill .mk{margin-top:0;border-color:currentColor;background-image:linear-gradient(to top,currentColor var(--lvl,0%),transparent var(--lvl,0%))}
/* Filter control for the index pages. Links, not buttons: each stage is its own
   static route, so filtering costs zero JavaScript. */
.stagefilter{display:flex;flex-wrap:wrap;gap:var(--space-2)}
/* Filter control for the index pages. Links, not buttons: each stage is its own
   static route, so filtering costs zero JavaScript. Prefixed with .page for the
   same specificity reason as the base link rule above: an unclassed <a> inside a
   classed wrapper needs (0,2,1) to beat it. */
.page .stagefilter a{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;border-radius:var(--radius-pill);border:var(--border-hair) solid var(--color-border);background:var(--color-bg-surface);color:var(--color-text-muted);font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;text-decoration:none}
.page .stagefilter a:hover{border-color:var(--color-stage-rim);color:var(--color-heading)}
.page .stagefilter a[aria-current="page"]{background:var(--color-stage-wash);border-color:var(--color-stage-rim);color:var(--color-text-accent-on-peach)}
.stagefilter .n{opacity:.7}

/* ---------- the type badge ----------
   Quiet by design: in a blended stream the marker does the work, so the badge is
   a word, not a chip. It gets a field only where it stands alone (a note header,
   a search result). */
.tbadge{display:inline-flex;align-items:center;gap:6px;font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase}
.tbadge[data-kind="post"]{color:var(--color-primary-deep)}
.tbadge[data-kind="note"]{color:var(--color-stage-ink)}

/* ---------- the topic chip ---------- */
.chip{display:inline-flex;align-items:center;gap:5px;padding:3px 10px;border-radius:var(--radius-pill);border:var(--border-hair) solid var(--color-border);background:var(--color-bg-surface);color:var(--color-text);font-size:var(--text-xs);text-decoration:none;white-space:nowrap}
.chip:hover{border-color:var(--color-stage-rim);color:var(--color-heading);background:var(--color-bg-surface-raised);text-decoration:none}
.chip::before{content:"#";color:var(--color-text-accent);font-family:var(--font-mono);font-size:var(--text-2xs)}
.chiprow{display:flex;flex-wrap:wrap;gap:var(--space-2)}

/* ---------- start with: two curated links, in the masthead ----------
   The numbered "Start here" block is gone. Three reasons it did not earn a section:
   it landed halfway down the page where a first-time reader had already chosen, its
   one-line whys repeated what the titles said, and a home page that already has a
   feed and four doors does not need a third list. It is one line under the byline
   now, which is where a "new here?" belongs. */
.startline{margin:var(--space-3) 0 0;font-size:var(--text-sm);color:var(--color-text-muted)}
.startline b{color:var(--color-text);font-weight:var(--weight-semibold)}
.startline .sep{opacity:.5}

/* ---------- page head (the notes index, and any inner index page) ----------
   Quieter than the home masthead on purpose: no mark, no tagline. A reader who has
   arrived here already knows where they are. */
.phead{padding-top:var(--space-7);padding-bottom:var(--space-2)}
.pheadh{font-family:var(--font-display);font-weight:var(--weight-extrabold);font-size:var(--text-3xl);letter-spacing:var(--tracking-tight);line-height:var(--leading-tight);color:var(--color-heading);margin:var(--space-2) 0 var(--space-3)}
.pheadp{margin:0;max-width:58ch;font-size:var(--text-lg);line-height:var(--leading-relaxed);color:var(--color-text-lead);text-wrap:pretty}
/* THE FOUR KINDS, as four small doors rather than a definition list. Same card
   grammar as the garden doors on the home page — hair rim, raised on hover, jade
   glyph — at two-thirds the size, because this is a key that happens to be
   clickable and not the main way into the notebook. Each one opens the page with
   its own chip already picked. */
.kinds{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--space-3);margin-top:var(--space-4)}
.kcard{display:grid;grid-template-rows:auto auto 1fr;gap:6px;padding:var(--space-3) var(--space-3) var(--space-4);border:var(--border-hair) solid var(--color-border);border-radius:var(--radius-lg);color:inherit;text-decoration:none;transition:background 130ms ease,border-color 130ms ease,box-shadow 130ms ease,transform 130ms ease}
.kcard:hover{background:var(--color-bg-surface-raised);border-color:var(--color-stage-rim);box-shadow:var(--shadow-card);transform:translateY(-1px);color:inherit;text-decoration:none}
.kcard:focus-visible{outline:2px solid var(--color-ring);outline-offset:2px}
/* Count sits on the glyph line, right-aligned — the garden doors' arrangement, and
   the reason the standalone counts line under the title could go: four numbers that
   each belong to something beat four numbers in a row belonging to nothing. */
.ktop{display:flex;align-items:center;justify-content:space-between;gap:var(--space-2);color:var(--color-accent)}
.kcount{font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-faint)}
.kterm{font-family:var(--font-display);font-weight:var(--weight-extrabold);font-size:var(--text-base);letter-spacing:-.01em;color:var(--color-heading);line-height:1.2}
.kdef{font-size:var(--text-sm);line-height:var(--leading-snug);color:var(--color-text-muted);text-wrap:pretty}
.pheadc{margin:var(--space-3) 0 0;font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-faint)}

/* ---------- 4. garden doors ---------- */
.doors{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-4)}
.door{display:grid;grid-template-rows:auto auto 1fr;gap:var(--space-2);padding:var(--space-4) var(--space-4) var(--space-5);border:var(--border-hair) solid var(--color-border);border-radius:var(--radius-md);background:var(--color-bg-surface);text-decoration:none;color:inherit;transition:background 130ms ease,border-color 130ms ease,box-shadow 130ms ease}
.door:hover{background:var(--color-bg-surface-raised);border-color:var(--color-stage-rim);box-shadow:var(--shadow-card);color:inherit;text-decoration:none}
.doortop{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);color:var(--color-accent)}
.doorcount{font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-faint)}
.doorl{font-family:var(--font-display);font-weight:var(--weight-extrabold);font-size:var(--text-lg);letter-spacing:-.015em;color:var(--color-heading);line-height:1.2}
.doorn{margin:0;font-size:var(--text-sm);color:var(--color-text-muted);text-wrap:pretty}
.door .arrow{transition:transform 130ms ease;color:var(--color-text-faint)}
.door:hover .arrow{transform:translate(2px,-2px);color:var(--color-accent)}

/* ---------- 5. graph teaser ----------
   A picture, not a visualisation: CAFFEINE as a skeletal formula, from coordinates in
   data.js. Fourteen atoms, fifteen bonds, four double bonds. It reads as a graph of
   linked nodes, it contains the family hexagon, and it is coffee, which is as close
   to a free lunch as a motif gets on this property. Heteroatoms take the violet node
   and carbons the jade one, so the structure is legible without a single label. No
   layout engine and no JavaScript. The interactive graph is its own route; this block
   only points at it. */
.graph{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,.85fr);gap:var(--space-5);align-items:center;padding:var(--space-5);border:var(--border-hair) solid var(--color-border);border-radius:var(--radius-md);background:var(--color-bg-surface)}
.graphsvg{width:100%;height:auto;display:block}
.graphsvg .ge{stroke:var(--color-stage-rim);stroke-width:.8;opacity:.42}
/* Double bond: the second line of a skeletal formula, inset toward the ring centre.
   Slightly lighter than the single bonds so the pair reads as one bond, not two. */
.graphsvg .gb2{opacity:.3}
/* Electron shell on the two hub atoms. Non-text at 1px, so the 3:1 bar does not
   apply; it is a hairline halo and it is meant to be nearly subliminal. */
.graphsvg .gshell{fill:none;stroke-width:1;opacity:.3}
.graphsvg .gs-post{stroke:var(--color-type-post)}
.graphsvg .gs-note{stroke:var(--color-type-note)}
.graphsvg .gn-note{fill:var(--color-type-note)}
.graphsvg .gn-post{fill:var(--color-type-post)}
.graphn{margin:var(--space-2) 0 var(--space-4);font-size:var(--text-sm);color:var(--color-text-muted);max-width:44ch;text-wrap:pretty}
.gbtn{display:inline-flex;align-items:center;gap:7px;padding:8px 15px;border-radius:var(--radius-pill);background:var(--color-accent);color:var(--color-on-accent);font-size:var(--text-sm);font-weight:var(--weight-semibold);text-decoration:none}
.gbtn:hover{background:var(--color-accent-hover);color:var(--color-on-accent);text-decoration:none}

/* ---------- 6. footer ---------- */
.foot{border-top:var(--border-hair) solid var(--color-border);margin-top:var(--space-7);padding:var(--space-6) 0 var(--space-7)}
.footin{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:var(--space-5) var(--space-6);align-items:start}
.footlic{margin:var(--space-3) 0 0;font-size:var(--text-xs);color:var(--color-text-faint);max-width:52ch;text-wrap:pretty}
/* Two exits, deliberately unequal, and neither is a button. The invitation is a
   sentence — a framed pill at the very bottom of a quiet page shouted — and it now
   LEADS the footer, in the left column where the mark and the tagline used to sit.
   The coffee link stays a plain muted one, and it moved to the right column above the
   mark, so the two exits are separated by the width of the footer instead of by a
   bullet. The bullet, .footexits and .footsep are gone with it. */
/* nowrap on the two phrases: neither should ever break mid-sentence, and it also
   makes the grid's auto track resolve to the true unwrapped width. */
.page .footkofi{white-space:nowrap}
.footinvite{margin:0;font-size:var(--text-sm);color:var(--color-text);text-wrap:pretty}
.page .footinvite a{font-weight:var(--weight-semibold)}
.page .footkofi{font-size:var(--text-sm);color:var(--color-text-muted);text-decoration:none}
.page .footkofi:hover{color:var(--color-heading);text-decoration:underline;text-underline-offset:3px}
.footright{display:grid;justify-items:end;gap:var(--space-2)}

/* ---------- backlinks: "linked from" ----------
   NOT a new row type. It is the stream row with the date column dropped, which is
   why it needs four lines of CSS instead of a component: one row anatomy for the
   home stream, the index pages, search results and the foot of every note. Sits at
   the bottom of a note page; when a note has no inbound links the block renders
   .blempty and says so, because an empty section header is worse than a sentence. */
.backlinks{margin-top:var(--space-7);padding-top:var(--space-4);border-top:var(--border-hair) solid var(--color-border)}
.blh{display:flex;align-items:baseline;gap:var(--space-3);margin-bottom:var(--space-2);flex-wrap:wrap}
.blcount{font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-faint)}
.backlinks .stream{border-top:none}
.backlinks .srow{border-bottom:none}
.backlinks .srowa{grid-template-columns:12px minmax(0,1fr);padding-top:var(--space-2);padding-bottom:var(--space-2)}
.blempty{margin:0;font-size:var(--text-sm);color:var(--color-text-faint);max-width:60ch}

/* ---------- note header (specified here so the note template inherits it) ----------
   The one place the stage gets to be loud: pill, type badge and topic chips on one
   line above the title. Order is type, stage, topics: what it is, how done it is,
   what it is about. */
.nhead{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2) var(--space-3);margin-bottom:var(--space-3)}
.nhead .sep{color:var(--color-text-faint);opacity:.6}

/* ---------- 320px ----------
   Nothing scrolls horizontally. The nav becomes a rail from the same markup, the
   masthead mark drops to 44 and moves above the type, and the doors stack. */
@media (max-width:760px){
  .wrap{padding:0 var(--space-5)}
  /* The nav becomes a rail from the same markup. .navwrap is display:contents on
     desktop, so nothing changes there; below 760 it is the non-scrolling box that
     carries the right-edge fade. The fade must NOT live on the scroller: inside it,
     it drifts off the edge exactly when it is needed. Same trick, same class names
     as cwagner.co, on purpose. */
  .navwrap{display:block;position:relative;order:3;flex-basis:100%;width:100%;margin-top:var(--space-3);border-top:var(--border-hair) solid var(--color-border-subtle)}
  .navwrap::after{content:"";position:absolute;top:0;right:0;bottom:0;width:34px;pointer-events:none;background:linear-gradient(90deg,transparent,var(--color-bg-page) 76%)}
  .mainnav{margin-left:0;gap:7px;padding:9px 0 0;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none}
  .mainnav::-webkit-scrollbar{display:none}
  .navlink{flex:none;padding:6px 11px;border:var(--border-hair) solid var(--color-border);border-radius:var(--radius-pill);background:var(--color-bg-surface);font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;border-bottom-width:var(--border-hair)}
  .navlink[aria-current="page"]{background:var(--color-stage-wash);border-color:var(--color-stage-rim);color:var(--color-text-accent-on-peach)}
  .navutil{padding-left:var(--space-2);margin-left:var(--space-1);border-left-color:var(--color-border-subtle);gap:7px}
  .navpair{gap:4px}
  .navpairsep{opacity:.7}
  .kinds{grid-template-columns:1fr 1fr}
  .mast{grid-template-columns:1fr;gap:var(--space-4);padding-top:var(--space-6)}
  .benchctl{gap:var(--space-3)}
  .bsearch{flex-basis:100%;max-width:none}
  .track{flex-wrap:wrap}
  .doors{grid-template-columns:1fr}
  .graph{grid-template-columns:1fr;gap:var(--space-4)}
  .graphorder{order:2}
  .footin{grid-template-columns:1fr}
  .footright{justify-items:start}
}
@media (max-width:420px){
  .wrap{padding:0 var(--space-4)}
  .mastp{font-size:var(--text-base)}
  .pheadh{font-size:var(--text-2xl)}
  .pheadp{font-size:var(--text-base)}
  .sec{padding-top:var(--space-6)}
  .srowa{column-gap:var(--space-2)}
  .benchctl,.benchbody{padding-left:var(--space-3);padding-right:var(--space-3)}
  .knob{align-items:flex-start;flex-direction:column;gap:5px}
}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{transition-duration:.01ms!important;animation-duration:.01ms!important}
}

/* ---------- the jump palette ----------
   One control, on every page: the magnifier in the header, opening a centred palette on a
   dimmed page. It exists because a reader who has landed in a note and wants a DIFFERENT
   note had only two ways out — a wikilink or the connected list — and neither is "show me
   something else". jump.js owns the behaviour and names the JavaScript exception.
   Everything here is borrowed rather than invented: the field is .bsearch one step taller,
   the rows are .srow with the date column dropped at the rail's density, the group lines
   are the rail's mono, and the panel's field is the garden door's raised paper. No new
   colour anywhere in it. */
.jfind{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;flex:none;border:var(--border-hair) solid var(--color-border);border-radius:var(--radius-pill);background:var(--color-bg-surface);color:var(--color-text-muted);cursor:pointer;position:relative}
.page a.jfind:hover,.jfind:hover{color:var(--color-heading);border-color:var(--color-stage-rim);background:var(--color-bg-surface-raised);text-decoration:none}
.jfind:focus-visible{outline:2px solid var(--color-ring);outline-offset:2px}
.jfind svg{width:15px;height:15px;fill:currentColor}
.jfind[aria-expanded="true"]{color:var(--color-text-accent-on-peach);background:var(--color-stage-wash);border-color:var(--color-stage-rim)}
/* The words and the keystroke live in a tooltip, drawn exactly the way a stream row's
   marker tooltip is drawn — one idiom for "this thing can name itself on hover". */
.jfind::after{content:attr(data-tip);position:absolute;top:calc(100% + 7px);right:0;z-index:30;padding:3px 7px;border:var(--border-hair) solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg-surface-raised);box-shadow:var(--shadow-card);font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-muted);white-space:nowrap;opacity:0;visibility:hidden;transition:opacity .12s ease}
.jfind:hover::after{opacity:1;visibility:visible}
.jfind[aria-expanded="true"]::after{display:none}
.jkey{display:inline-flex;align-items:center;padding:1px 5px;border-radius:3px;border:var(--border-hair) solid var(--color-border);background:var(--color-bg-page);font-family:var(--font-mono);font-size:9px;letter-spacing:0;color:var(--color-text-muted)}
/* THE DIM IS 55% WITH 2px OF BLUR, and both halves are load-bearing. Clicking away from
   the prose is intent to leave, so the page goes quiet — but at a flat dim the note's
   headings and jade markers still pull the eye, and at a deeper dim the page stops being
   recognisable and closing the palette feels like going back rather than resuming. Blur
   is what lets 55% read as SET ASIDE instead of switched off. */
.jscrim{position:fixed;inset:0;z-index:39;background:color-mix(in oklab,var(--color-text) 55%,transparent)}
@supports (backdrop-filter:blur(2px)){.jscrim{backdrop-filter:blur(2px)}}
.jump[hidden],.jscrim[hidden]{display:none}
.jump{position:fixed;z-index:40;top:12vh;left:50%;transform:translateX(-50%);width:min(560px,calc(100vw - 2 * var(--space-5)));max-height:70vh}
.jbox{display:flex;flex-direction:column;min-height:0;max-height:70vh;border:var(--border-hair) solid var(--color-border);border-radius:var(--radius-md);background:var(--color-bg-surface-raised);box-shadow:var(--shadow-card);overflow:hidden}
@media (prefers-reduced-motion:no-preference){.jbox{animation:jin .13s ease-out}}
@keyframes jin{from{opacity:0;transform:translateY(-5px)}to{opacity:1;transform:none}}
.jfield{display:flex;align-items:center;gap:9px;padding:var(--space-4);border-bottom:var(--border-hair) solid var(--color-border);background:var(--color-bg-page)}
.jfield svg{width:18px;height:18px;flex:none;fill:var(--color-text-faint)}
.jfield input{flex:1;min-width:0;border:0;padding:0;background:none;font-family:inherit;font-size:var(--text-lg);color:var(--color-text);outline:none;-webkit-appearance:none;appearance:none}
.jfield input::placeholder{color:var(--color-text-faint);opacity:1}
.jfield:focus-within svg{fill:var(--color-text-accent)}
/* Esc lives in the foot with the other keys — one place that says what the keyboard does,
   rather than a key hint in the field and a key hint underneath it. */
.jesc{display:flex;align-items:center;gap:5px;margin-left:auto;flex:none;font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-faint)}
.jbody{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;padding:var(--space-2) var(--space-3) var(--space-3)}
.jgrp{display:flex;align-items:baseline;gap:7px;margin:var(--space-3) 6px 5px;font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-faint)}
.jgrp:first-child{margin-top:5px}
.jgrpn{color:var(--color-text-accent)}
.jlist{list-style:none;margin:0;padding:0;display:grid;gap:1px}
.page .jrow{display:grid;grid-template-columns:12px minmax(0,1fr) auto;gap:var(--space-3);align-items:baseline;padding:6px;border-radius:var(--radius-sm);text-decoration:none;color:var(--color-text)}
.page .jrow:hover{background:var(--color-bg-surface);color:var(--color-text);text-decoration:none}
.jrow .mk{margin-top:4px}
.jrt{font-size:var(--text-sm);font-weight:var(--weight-semibold);line-height:1.35;color:var(--color-heading);letter-spacing:-.01em;text-wrap:pretty}
.jrt b{color:var(--color-text-accent)}
.jrm{font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-faint);white-space:nowrap}
/* SELECTION, NOT HOVER, is the primary state here: the keyboard is how this list is meant
   to be used, so the selected row takes the jade wash and a spine. Hover mirrors it one
   step quieter so a mouse never has two rows lit at once. */
.page .jrow[aria-selected="true"]{background:var(--color-stage-wash);color:var(--color-text-accent-on-peach);box-shadow:inset 2px 0 0 var(--color-stage-rim)}
.page .jrow[aria-selected="true"] .jrt{color:var(--color-text-accent-on-peach)}
.page .jrow[aria-selected="true"] .jrm{color:var(--color-text-accent-on-peach);opacity:.8}
.jhere{font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-faint);white-space:nowrap}
.jnone{margin:var(--space-4) 6px;font-size:var(--text-sm);color:var(--color-text-muted);text-wrap:pretty}
.jfoot{display:flex;align-items:center;gap:var(--space-3);padding:8px var(--space-4);border-top:var(--border-hair) solid var(--color-border);background:var(--color-bg-page)}
.jkeys{display:flex;align-items:center;gap:5px;font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--color-text-faint)}
/* POUR ME ONE is the cheapest thing in the panel and the truest to the case it serves: a
   reader who wants something else and has no word for it. It fills the field rather than
   navigating, so the last move is still theirs. In the FIELD ROW rather than the foot,
   because it is the second way to use the panel and not a note about the first. */
.jrand{display:inline-flex;align-items:center;gap:6px;padding:3px 9px;border:var(--border-hair) solid var(--color-border);border-radius:var(--radius-pill);background:var(--color-bg-surface);color:var(--color-text-muted);font-family:var(--font-mono);font-size:var(--text-2xs);letter-spacing:var(--tracking-micro);text-transform:uppercase;cursor:pointer}
.jrand:hover{color:var(--color-text-accent);border-color:var(--color-stage-rim)}
.jrand:focus-visible{outline:2px solid var(--color-ring);outline-offset:2px}
.jrand svg{width:13px;height:13px;fill:currentColor}
.jfield .jrand{flex:none}
/* On a phone the palette becomes a sheet off the BOTTOM edge, because the thumb is at the
   bottom of the device and a centred panel is a reach. The field stays at the top of the
   sheet all the same: the keyboard comes up from below and would sit on it. */
@media (max-width:640px){
  .jump{top:auto;left:0;right:0;bottom:0;width:auto;max-width:none;max-height:82vh;transform:none}
  .jbox{max-height:82vh;border-radius:var(--radius-lg) var(--radius-lg) 0 0;border-bottom:0}
}
@media print{.jfind,.jump,.jscrim{display:none}}
