/* Leaf default theme, vendored into each page directory by `page init` and served
 * at /theme.css. Four tiers, cheapest first: tokens, element styles for plain
 * semantic HTML, class idioms (one rule + one catalog line each), and
 * element-widgets (lf-* tags rendered entirely by CSS). Customization follows
 * the same cascade: override a token, then a widget's rule. `page init` appends
 * user and project theme files after these defaults.
 *
 * Every surface rides a token, so the dark palette is exactly one more token
 * block — nothing else in this file (or the runtime, which styles its UI from
 * the same tokens) knows about color schemes.
 *
 * Three voices, because the page already has three kinds of words and the norms
 * turn on telling them apart. What the page *says* is set in a text serif: prose
 * a user reads closely and points at. What the page *labels* — an eyebrow, a
 * column heading, a chip, the headings across a table, a metric's caption — is
 * apparatus, set in the UI sans at small sizes with tracking, so it reads as
 * machinery around the argument rather than as part of it. What it *shows* is mono:
 * code, diffs, trees, timestamps. The runtime's own chrome takes the same sans
 * (.lf-ui reads --sans), which is what finally makes "this is not the document"
 * a difference you can see rather than a claim resting on size and color. It
 * was invisible while document and chrome were the one system face.
 *
 * The serif is stacked, not shipped. theme.css is inlined whole into every
 * `version export` and parsed by `version check` on every version, so a webfont
 * would have to arrive as a base64 blob in both — and referenced by URL instead
 * it silently falls back in exactly the medium (a copy, mailed on) that has no
 * server to ask. Charter and Iowan Old Style ship on macOS, Georgia everywhere
 * else; each is a screen serif with a large x-height, which is the property that
 * matters at 17px. */

:root {
  /* Warm neutrals: the paper is ivory rather than white and every grey above it
     carries the same warmth, so nothing on the page reads as a cool grey pasted
     onto a warm ground. */
  --paper: #faf9f5;  --ink: #1c1b18;  --muted: #6f6a60;  --rule: #e7e3d9;
  /* The accent marks where the machinery points — a link, a recommendation, the
     active milestone, a focus ring — and so has to stay hue-distinct from the
     three semantic colors it appears beside. That rules out the warm end: a clay
     accent sits a few degrees from --warn, and an accent ring beside a warning dot
     would be a distinction the reader has to measure. Ink blue keeps the link and
     focus conventions and reads as ink on ivory rather than as UI. */
  --accent: #2f5480; --ok: #3f7d4e;   --warn: #b4741a;   --danger: #b4402f;
  --r: 6px;
  --sp-1: 4px; --sp-2: 8px; --sp-3: 16px; --sp-4: 24px;
  /* --rail, the comment margin, has no value here: it is the widest control row plus
     its gap, which is a fact about the controls' own words and face, so lf-suggestion.js
     measures the first row it builds and states it on the root element. The rule that
     spends it is beside .lf-sug-actions below. */
  /* The ladder, body at --t-4. Measured on Charter at 17px: 7.9px a character, so
     the column below holds about 74 of them. */
  --t-6: 11.5px; --t-5: 14px; --t-4: 17px; --t-3: 20px; --t-2: 24px; --t-1: 31px;
  --serif: Charter, "Bitstream Charter", "Iowan Old Style", Georgia, ui-serif, serif;
  --sans: system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, monospace;

  --card: #fffefb;   /* raised surfaces: cards, panels, controls */
  --field: #f4f1e8;  /* tinted fields: details, callouts, notes */
  --code-bg: #eeeade; --pre-bg: #f5f2e9;
  --chip: #eeeade;   /* neutral chip */
  --ink-2: #3c3934;  --muted-2: #9d968a; --border-2: #d5cfc2;
  --veil: #faf9f5ee; /* the banner's translucent backdrop */
  --mark: #f2d38c66; --mark-strong: #f2d38c; --quote-bar: #c1743f;
  --ok-tint: #dfeddf;     --ok-ink: #2f6b40;
  --warn-tint: #f7ecd2;   --warn-ink: #8a5a12;
  --danger-tint: #f7e0da; --danger-ink: #9c3526;
  --add-tint: #e4efe0; --del-tint: #f7e2dc; --hi-tint: #f7eecd;
  /* Syntax roles. Six, because a page is read for its argument and code on it is
     evidence — enough color to find the string and the comment, not a paint box.
     Every scope the tokenizer knows collapses into these in leaf.js, and what
     it doesn't map keeps --ink, so an unmapped scope reads plain, never wrong.
     A role may be quiet and may not be illegible, and the comment was both: #8b8577
     is 3.3:1 on --pre-bg and less on every tint a line can wear, so a block that is
     mostly comment — a shell transcript, a log, an annotated config — came out beige
     on beige, and what the reader reports is that the highlighting is gone. Each of
     these clears 4.5:1 on all four surfaces code is set on (--pre-bg, and the
     add, del and hi tints), which the render gate reads off the drawn page
     (UNREAD_SYNTAX) rather than off this block — so a project's own overlay is held
     to it too. */
  --syn-comment: #696458; --syn-keyword: #a3372c; --syn-string: #1d4f6b;
  --syn-number: #2f5480;  --syn-name: #6b4a9e;    --syn-type: #8a5a12;
}

/* The dark palette is the same page at night, not its inverse: the ground keeps
   the warmth (a brown-black, not a blue-black) and every grey above it is mixed
   from the same axis, so a page that themes warm does not go cold when the OS does. */
@media (prefers-color-scheme: dark) {
  :root {
    --paper: #191815;  --ink: #e8e4db;  --muted: #a09889;  --rule: #34312b;
    --accent: #8fb0d8; --ok: #7fb98c;   --warn: #d8a95c;   --danger: #d98b7a;

    --card: #201e1a; --field: #232019;
    --code-bg: #29251e; --pre-bg: #1e1c18;
    --chip: #2d2922;
    --ink-2: #c9c3b6; --muted-2: #756e62; --border-2: #453f36;
    --veil: #191815ee;
    --mark: #a1852a59; --mark-strong: #a1852a99; --quote-bar: #b8763f;
    --ok-tint: #1d2b1f;     --ok-ink: #97cca3;
    --warn-tint: #322715;   --warn-ink: #e2bb75;
    --danger-tint: #35211b; --danger-ink: #e0a091;
    --add-tint: #1b2a1c; --del-tint: #33201b; --hi-tint: #342d14;
    --syn-comment: #a29a8b; --syn-keyword: #e59a8c; --syn-string: #9dc4dd;
    --syn-number: #a8c4e2;  --syn-name: #c3a8de;    --syn-type: #dcb27e;
  }
}

/* ---------- elements ---------- */

/* A run with nothing to break on breaks anyway rather than painting outside the box that
 * was holding it — this page's subject is code, so its prose carries paths, identifiers
 * and shas, and there is no width at which one of them can't be longer. It reaches
 * everything by inheritance and does nothing anywhere a line already can't wrap, so the
 * widgets that scroll instead (lf-board, lf-diff, pre) are untouched; a metric's headline
 * was the one with no width to spare at all, and a twelve-character value ran 287px out
 * of a 138px card and across whatever the grid had put beside it. */
body { margin: 0; background: var(--paper); color: var(--ink);
       font: var(--t-4)/1.6 var(--serif); overflow-wrap: break-word; }
/* Positioned so it is the containing block for anything a widget hangs in the
 * page margin (lf-suggestion's controls): `left: 100%` then means the column's
 * right edge, and `anchor()` reaches the anchor names declared inside it.
 *
 * 720px is stated as a number and not as a token, because `version check` reads
 * this very declaration to find the column it measures every pinned width
 * against (_column_width). A var() there is a value the parser can't resolve, so
 * the gate would quietly fall back to its 780px default and start passing pages
 * that overflow by 60px. The column is one number, and this is where it is said.
 *
 * It narrowed from 760 with the type: 17px Charter runs about 7.9px a character,
 * so the 672px of text inside this padding is ~74 characters — a measure. The
 * old column set 16px system-ui across 93 of them, which is a line the eye loses
 * its place returning from, and the single biggest thing wrong with how the
 * page read. Widgets that want width already scroll (lf-board, lf-diff, pre). */
main { max-width: 720px; margin: 0 auto; padding: 72px 24px 96px; position: relative; }
/* The headings are the body serif at weight, size and spacing — one voice, four
 * volumes — rather than a display face over a text face. A page whose subject is
 * a decision and whose furniture is diffs and boards has enough going on; the
 * hierarchy it needs is the one that survives being skimmed, which is size and
 * air, not contrast of family. */
h1 { font-size: var(--t-1); line-height: 1.16; letter-spacing: -0.014em;
     margin: 0 0 12px; text-wrap: balance; }
h2 { font-size: var(--t-2); line-height: 1.25; letter-spacing: -0.008em;
     margin: 48px 0 12px; text-wrap: balance; }
h3 { font-size: var(--t-3); line-height: 1.3; margin: 32px 0 8px;
     text-wrap: balance; }
p { margin: 13px 0; }
ul, ol { margin: 10px 0; padding-left: 24px; }
li { margin: 6px 0; }
a { color: var(--accent); text-underline-offset: 2px;
    text-decoration-color: color-mix(in srgb, var(--accent) 35%, transparent); }
a:hover { text-decoration-color: currentColor; }
hr { border: none; border-top: 1px solid var(--rule); margin: 40px 0; }
code { background: var(--code-bg); border-radius: 4px; padding: 1px 5px;
       font-size: 84%; font-family: var(--mono); }
pre { background: var(--pre-bg); border: 1px solid var(--rule); border-radius: var(--r);
      padding: 12px 14px; overflow-x: auto; font-size: 13px; line-height: 1.55;
      font-family: var(--mono); }
pre code { background: none; padding: 0; font-size: 100%; }
/* Syntax roles. The runtime writes these into any code block that names a language —
   a <pre><code class="language-*"> or a lf-code with `language` — having collapsed the
   tokenizer's scopes into six of leaf's own first. Styled at document level like
   the marks, because they land on the page's own elements; one attribute rather than
   six classes, so the shared class vocabulary doesn't widen at all. A tokenizer swap
   costs one table in leaf.js and no page that has already shipped. */
[data-lf-syn="cm"] { color: var(--syn-comment); font-style: italic; }
[data-lf-syn="kw"] { color: var(--syn-keyword); }
[data-lf-syn="st"] { color: var(--syn-string); }
[data-lf-syn="nu"] { color: var(--syn-number); }
[data-lf-syn="fn"] { color: var(--syn-name); }
[data-lf-syn="ty"] { color: var(--syn-type); }
/* A diff's own lines take the tints lf-diff and lf-suggestion already carry, so an
   added line reads the same whichever of the three drew it. Ink only: these spans
   cover a line's text and not its box, and a background would print as a ragged
   half-width band. */
[data-lf-syn="ins"] { color: var(--ok-ink); }
[data-lf-syn="del"] { color: var(--danger-ink); }
blockquote { margin: 18px 0; padding: 2px 18px; border-left: 2px solid var(--rule);
             color: var(--muted); font-style: italic; }
figure { margin: 22px 0; }
figcaption { font-size: var(--t-6); color: var(--muted); margin-top: 8px; }
img, svg, video { max-width: 100%; height: auto; }
/* A table's columns hold what is in them, and the space between two of them is
 * one gutter wide. `width: 100%` bought the opposite: the browser's auto layout
 * spreads leftover width across every column, so a four-column survey on the
 * 720px measure ran 149/122/267/182 over content 95px and 52px wide, and the eye
 * crossed 124px of nothing to get from an attribute to its values.
 *
 * display:block is what buys it back, and a wide table's answer with it. The
 * grid inside a block box shrink-wraps, so a table takes the measure only when
 * its content asks for it, wraps its cells when the content asks for more, and
 * scrolls inside itself — like pre, like lf-board — when even the wrapping
 * can't fit. Nothing narrower does all three: overflow on a table box is
 * ignored outright, and a table that spills lands in the margin where a
 * suggestion's controls hang, having scrolled the page by nothing the render
 * gate can see at its own viewport width.
 *
 * Two things the display change could have cost, both measured rather than
 * assumed, and neither charged: Chrome's accessibility tree keeps table,
 * rowgroup, row, columnheader and cell through it, with or without an explicit
 * role, and a long table still breaks across printed pages, since a page box
 * fragments an overflow block rather than clipping it. What paper does lose is
 * a table too wide to wrap, which it loses either way — the same trade pre and
 * lf-board already make, and the reason not to write a print rule undoing this
 * one. */
table { display: block; overflow-x: auto; border-collapse: collapse;
        font-size: var(--t-5); margin: 18px 0; font-variant-numeric: tabular-nums; }
caption { text-align: left; font-size: var(--t-6); color: var(--muted);
          margin-bottom: 6px; }
th, td { text-align: left; padding: 8px 14px; border-bottom: 1px solid var(--rule);
         vertical-align: top; }
/* The padding is a gutter, and the columns at the two ends have no neighbour to
   keep away from: 10px of it there set every table's first word 10px right of
   the prose above it, and stopped the last column short of the same edge. */
tr > :first-child { padding-left: 0; }
tr > :last-child { padding-right: 0; }
/* Which kind of header a th is, asked of the author's own word for it rather
   than of where it sits: a header row written without a <thead> is still column
   headings, and `scope="row"` is what a row's name needs said anyway. Each rule
   says which kind it is for, so neither resets the other — a row's name is the
   page's words in the page's face, and shouting it in 11.5px small caps is what
   one rule for both did. */
th:not([scope="row"]) { font-size: var(--t-6); text-transform: uppercase;
     letter-spacing: .06em; color: var(--muted); font-weight: 600;
     border-bottom-color: var(--border-2); }
th[scope="row"] { font-weight: 600; }
details { background: var(--field); border: 1px solid var(--rule);
          border-radius: var(--r); padding: 8px 14px; margin: 18px 0; }
summary { cursor: pointer; font-weight: 600; font-size: var(--t-5); }
details pre { margin: 10px 0 6px; }
dl { margin: 10px 0; }
dt { font-weight: 600; }
dd { margin: 0 0 8px 0; }

/* The apparatus voice, said once per layer. Every label, caption, chip, badge and
 * column heading on the page is the page pointing at its own content rather than
 * saying something — so it takes the UI sans at a small size, usually tracked, and
 * the serif is left to carry what there is to read. Listing them in one rule is the
 * point: what counts as apparatus is a judgement, and a judgement made in twenty
 * places is twenty chances to answer it differently. This rule is the layer's own
 * words — the idioms and the fail-soft box; a widget layer (bundled/theme.css, or
 * any overlay's) states the same rule for the words its widgets wear.
 *
 * This is a look, not a permission — the anchoring norm's distinction exactly. A
 * chip a widget says (data-lf-said) is still the page's words and still something
 * a user can select and quote; it is set in the sans because it reads as
 * machinery, not because it is the runtime's.
 *
 * Nothing a widget builds through `offer` belongs here, and the first draft had
 * four of them (a row's `for` reference, the settled row's count and Δ, a tab's
 * Δ). Those wear lf-ui, which already answers in the same face — so the rule was
 * inert, and it said the wrong thing where anyone read it: that the runtime's own
 * chrome is the page pointing at its content. This list is the page's words. */
.eyebrow, .tag, figcaption, caption, th:not([scope="row"]), .facts dt,
.lf-error {
  font-family: var(--sans); }

/* The pill a small label rides in. These numbers are a contract across layers: the
 * bundled widgets' chips (lf-chip, a milestone's attribute row) restate them in
 * bundled/theme.css so a band of chips and an inline .tag stand the same height —
 * they drifted four pixels apart once, when the inline label padded itself and
 * nobody had decided that. A wearer keeps only where it sits, and a tint of its
 * own where the tint is the point. */
.tag {
  display: inline-block; font-size: var(--t-6); line-height: 1.6; padding: 0 8px;
  border-radius: 999px; background: var(--chip); color: var(--ink-2); }

/* ---------- class idioms ---------- */

.eyebrow { font-size: var(--t-6); letter-spacing: .1em; text-transform: uppercase;
           color: var(--muted); font-weight: 500; margin: 0 0 10px; }
/* The one paragraph a reader is guaranteed to read, so it is set as one: larger
   than the body, looser, and in the ink the body isn't. */
.lede { color: var(--ink-2); font-size: var(--t-3); line-height: 1.5;
        margin: 0 0 10px; }
/* Riding in a line of prose is all this adds to the pill above. */
.tag { vertical-align: middle; }
/* A tinted field with a hairline border — never a left-accent bar. */
.callout { background: var(--field); border: 1px solid var(--rule);
           border-radius: var(--r); padding: 12px 16px; margin: 18px 0;
           font-size: 16px; }
.facts { display: grid; grid-template-columns: max-content 1fr;
         gap: var(--sp-1) var(--sp-3); margin: 14px 0; }
.facts dt { color: var(--muted); font-weight: 400; font-size: var(--t-5);
            padding-top: 1px; }
.facts dd { margin: 0; }
/* A column of quantities lines up on its last digit — the comparison a table of
   numbers is there for, and one tabular figures alone don't make. Said per cell,
   heading included, because which column holds quantities is the author's to
   declare: "60" and "60 sessions" are one shape and two different things, and a
   pass that read the text would be guessing at the difference. */
th.num, td.num { text-align: right; }
ol.steps { list-style: none; counter-reset: step; padding-left: 0; }
ol.steps > li { counter-increment: step; position: relative; padding-left: 34px;
                margin: 10px 0; }
ol.steps > li::before { content: counter(step); position: absolute; left: 0;
                        top: 2px; width: 21px; height: 21px; border-radius: 50%;
                        background: var(--accent); color: var(--card); font-size: 12px;
                        font-weight: 600; display: grid; place-items: center; }

/* Something a version restated: it rewrote words the user had decided on, so
   the runtime dropped what they recorded and is asking again. Without the tint,
   a decision undone looks exactly like one never made — the user would find
   it by rereading sixty drafts. Why it was undone belongs in the changelog note,
   where a version explains itself. The runtime sets this, from the log; the
   author's word is `restated` on the element. */
[data-lf-restated] { outline: 2px solid color-mix(in srgb, var(--accent) 40%, transparent);
                     outline-offset: 3px; border-radius: 2px; }

/* Something decided and not yet honored: the user's recorded state differs
   from what this version's markup arrived showing — a moved card, an edited
   draft, a pick no version has marked. The runtime sets this from the log's
   fold (x-state in the registry), one pass for every widget alike, and it
   clears the moment a version carries the decision or a retraction hands the
   state back. Green, the user's color: their decision stands here in
   replay, not yet in the page's own words. */
[data-lf-pending] { outline: 2px solid color-mix(in srgb, var(--ok) 50%, transparent);
                    outline-offset: 3px; border-radius: 2px; }

/* A worker's standing report: this element's state arrived through the log
   (`leaf report`) and no version has answered it yet. Provisional news
   rather than a decision — amber and dashed, so it never reads as the user's
   green, and it clears the moment a version absorbs or overrules the report
   (the note's `reports` ids). The runtime sets this from the log; the
   answering version's word is `overruled` on the element, or simply writing
   the reported state. */
[data-lf-reported] { outline: 2px dashed color-mix(in srgb, var(--warn) 55%, transparent);
                     outline-offset: 3px; border-radius: 2px; }

/* The box for words a widget carries (the runtime's sayBox): an answer the widget's
 * own controls don't cover goes here rather than sending the reader hunting for a
 * passage to select. Withheld rather than undone everywhere it would do nothing — a
 * box with no handler behind it invites the reader to type into a page that cannot
 * send it — so the offer is made once, in the live page, and a copy and a printout
 * get the page without it by never being handed it. Where the box sits in a widget
 * is the widget's own rule; this is only what the box is. */
.lf-say { display: none; align-items: flex-end;
          gap: var(--sp-2); margin-top: var(--sp-2); }
.lf-say textarea { flex: 1; min-width: 0; }
@media screen {
  html:not(.lf-copy) {
    /* :not([hidden]), because a caller may take the box behind a collapse (a settled
       group takes it with its options) — the display here would outrank the
       attribute's own hiding. */
    .lf-say:not([hidden]) { display: flex; }
  }
}

/* lf-suggestion / lf-old / lf-new: a proposed change to content the user has seen. The
 * wrapper generates no box, so a suggestion sits inline mid-sentence or wraps
 * whole sections without disturbing either flow; the slots carry the marks,
 * inline by default and block when they hold block content. A decision is the
 * end of the matter visually: the settled slot loses every mark and reads as
 * ordinary prose, and a suggestion with nothing left to show disappears. */
lf-suggestion { display: contents; }
lf-old, lf-new { border-radius: 3px; padding: 0 3px; }
lf-old { background: var(--del-tint); color: var(--muted);
         text-decoration: line-through; text-decoration-color: var(--danger); }
lf-new { background: var(--add-tint); }
:is(lf-old, lf-new):has(> :is(p, h1, h2, h3, h4, h5, h6, ul, ol, dl, table, pre,
    blockquote, section, article, div, figure, details, aside, lf-diagram, lf-diff,
    lf-code, lf-tree, lf-board, lf-options, lf-milestones, lf-tasks, lf-timeline,
    lf-compare, lf-metrics, lf-tabs, lf-draft, lf-shot, lf-specimen)) {
  display: block; padding: 2px 10px; margin: 10px 0; }
/* Decided: the surviving slot is plain prose, the other one gone. The retired one is
 * folded away by the widget rather than dropped in a frame (lf-suggestion.js), and the
 * survivor gives up its padding over the same span — a block slot's 10px is the sentence
 * the reader is looking at stepping sideways, which is the jerk the fold is there to
 * remove, only smaller. Declared on the slot rather than on the decided state, because a
 * transition is read from the state the element is leaving; the reduced-motion guard at
 * the foot of this file takes it with everything else.
 *
 * Padding alone. The tint going is paint, which a state change may do freely, and a
 * transitioned background is the thing the choose group's own comment above warns
 * about: the tokens themselves move when the reader's system flips to dark, so every
 * slot on the page would animate from one palette to the other for a change nobody
 * made. */
lf-old, lf-new { transition: padding .22s ease; }
lf-suggestion[data-lf-state] > :is(lf-old, lf-new) {
  background: none; color: inherit; text-decoration: none; padding: 0; }
lf-suggestion[data-lf-state="accept"] > lf-old,
lf-suggestion[data-lf-state="reject"] > lf-new { display: none; }
lf-suggestion[data-lf-state="accept"]:not(:has(lf-new)),
lf-suggestion[data-lf-state="reject"]:not(:has(lf-old)) { display: none; }
/* The controls hang in the page margin: absolute against <main> for the
 * horizontal edge, and against the anchor the widget prepends to the change for
 * the vertical — `anchor(top)` is the line that empty span sits on, mid-sentence
 * included. The row is <main>'s own child so that <main> is what `left: 100%`
 * resolves against (lf-suggestion.js hangs it there); one line tall, so two
 * changes a line apart don't collide, and the widget nudges the pair that still
 * would. Docked, it is the same box in flow at that same point: a control line
 * under the block the change sits in, wherever it can't hang clear of the column.
 * Waiting, the change is inside something collapsed and there is no line yet. */
.lf-sug-actions { position: absolute; left: 100%; margin-left: 22px; top: anchor(top);
    display: inline-flex; gap: 4px; white-space: nowrap; }
.lf-sug-actions.lf-waiting { display: none; }
.lf-sug-actions.lf-docked { position: static; margin: 2px 0 12px; }
/* The controls are .lf-pill — the margin's press, stated once in the runtime's
 * document-level vocabulary, where the 💬 that shares this margin wears it too. What
 * is here is only what a suggestion's own states add to it. */
.lf-sug-actions [role="button"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Hover is an offer, so the coloured edge is the pending row's alone: a decided one has
 * nothing left to press and its own colour to keep. Scoped rather than unsaid
 * afterwards — a hover rule undone by a later one is the shape the row form's first
 * draft got wrong. (The pill's own hover wash needs no scoping: a decided control's
 * background carries the attribute the decision wrote, which outranks it.) */
.lf-sug-actions:not([data-lf-outcome]) .lf-sug-accept:hover { border-color: var(--ok); color: var(--ok-ink); }
.lf-sug-actions:not([data-lf-outcome]) .lf-sug-reject:hover { border-color: var(--danger); color: var(--danger-ink); }
/* Decided. The control the user pressed states the outcome where it stood, in the
 * outcome's own colour; its pair stops showing without giving up its room, so the one
 * line a press must not disturb is the line the press leaves exactly as it found it.
 * The past tense grows into room the control reserved from its own decided word as the
 * row was built (lf-suggestion.js), which is why the word can change without the box
 * doing the same.
 *
 * visibility, not display: the room is the point. It also takes the hidden control out
 * of the tab order and out of the accessibility tree, which is what a press nobody can
 * make any more should be. */
.lf-sug-actions[data-lf-outcome="accept"] .lf-sug-reject,
.lf-sug-actions[data-lf-outcome="reject"] .lf-sug-accept { visibility: hidden; }
.lf-sug-actions[data-lf-outcome] [role="button"] { cursor: default; }
.lf-sug-actions[data-lf-outcome="accept"] .lf-sug-accept { border-color: var(--ok);
    color: var(--ok-ink); background: var(--ok-tint); }
.lf-sug-actions[data-lf-outcome="reject"] .lf-sug-reject { border-color: var(--danger);
    color: var(--danger-ink); background: var(--danger-tint); }
/* A page carrying a change to decide gives the rail up out of its own width,
 * rather than hanging the controls in whatever centring happens to leave over.
 * How wide is the row's own business: lf-suggestion.js measures the first row it
 * builds — controls already holding their decided words' room — and states --rail
 * on the root element, so the rail follows the controls' face instead of standing
 * here as a number, and an exported copy carries the value it was rendered with
 * in the root's own style attribute.
 * Leftovers ran out in the posture a user actually reads in: the open comment
 * panel takes the right of the window, the margin thins below the row, and every
 * row docked above the change it decides — which reads as belonging to the
 * paragraph before it. Reserved here, the column simply centres in what remains,
 * and the row keeps the line it hangs beside. The rail is a strip of body's
 * padding rather than a narrower column, so it survives the panel's own margin
 * (syncLayout) without the two having to agree about anything. It is keyed on the
 * row and not on lf-suggestion, so a page whose only changes are exhibits — a
 * specimen grows no controls — spends nothing on it, and a page that has settled
 * every change keeps the rail it started with rather than shifting under the
 * user at the last decision. Below the breakpoint no strip is worth the
 * column it costs, and the widget docks as it always has; on paper the controls
 * are gone, so their room would be a blank margin.
 *
 * Presence is also all the reservation can go on. What the row can't say is
 * whether it will use the rail, and the question feeds back on itself: a row
 * docks because it found no room, and the room is what the answer would reserve,
 * so a rail granted only to rows that aren't docked is a rail no row is ever
 * granted. */
@media screen and (min-width: 900px) {
  body:has(.lf-sug-actions) { padding-right: var(--rail); }
}

/* Where a widget upgrade fails, it fails soft into this box. */
.lf-error { border: 1px solid var(--danger); background: var(--danger-tint);
            color: var(--danger-ink); border-radius: var(--r); padding: 8px 12px;
            font-size: var(--t-5); white-space: normal; }
.lf-error pre { margin: 8px 0 0; white-space: pre; }

/* Reduced motion: one global guard covers every animation and transition in the
 * theme and the injected layers (!important outranks inline styles, so
 * JS-driven transitions stop too). JS-driven motion goes through the runtime's
 * motion() and SCROLL, which answer the same preference themselves. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}
