/* The owner surface, built to the brand strategy of 17 August 2026.
 *
 * EVERY VALUE HERE COMES FROM A TOKEN. `tokens.css` is compiled from
 * `tokens.json` by tools/build_tokens.py; this file consumes it and adds no
 * literals of its own, except where a comment says why. If you need a colour
 * or a size that is not a `var()` below, the answer is a token, not a hex
 * code — that discipline is the whole mechanism by which this identity stays
 * consistent without anybody supervising it (strategy brand §11), and it had
 * already failed once: the stylesheet this replaces carried three colours
 * that existed in no token at all.
 *
 * ─────────────────────────────────────────────────────────────────────────
 * WHAT THE PAGE IS TRYING TO BE
 *
 * A register, not a chat app. That is a positioning decision, not a taste
 * one: brand §8 puts businesses first and brand §3's wedge is custody, so the surface has
 * to read like something a finance director would accept as a record. Two
 * consequences run right through this file — the person's own words are set
 * in a sunken well rather than reversed out of a coloured bubble, and nothing
 * in the record ever animates, because a ledger line that slides in reads as
 * a thing that could slide out.
 *
 * TWO REGISTERS OF TYPE, and one glance separates them. What a person said
 * and what the twin says back are serif — speech, on the record. What the
 * machine DID with it is mono — bookkeeping. That is brand §6's system at
 * work
 * rather than decorating: "it shows its working" is one of the three claims
 * the brand has to carry, and mono on every timestamp, weight, key and
 * version is how it becomes visible at a glance instead of being claimed.
 *
 * THE RECORD'S TWO AXES are the most product-specific thing here and the
 * strategy explicitly leaves them to the build (brand §5, "designed
 * deliberately
 * in the build phase"). See the `record` group in tokens.json for the
 * argument; the short version is that BeliefStatus has eight values and they
 * are not one scale. `stable` versus `hypothesis` is how sure the record is.
 * `contested` and `superseded` are not low confidence at all — one means two
 * versions are on file and the fact is being WITHHELD from every briefing,
 * the other means it was true and is not now. So confidence is a ramp,
 * kind is categorical, and contested is the LOUDEST state on the page rather
 * than the faintest.
 *
 * COLOUR IS NEVER THE ONLY SIGNAL. Every state below carries at least two
 * more — a rule weight, a rule style, a tilde, a strikethrough, or a word.
 * This is a hard rule, not a preference: the palette's own chart note says
 * there is no fully colour-blind-safe qualitative palette above four
 * colours, and the record has eight states.
 */

/* ── the faces ─────────────────────────────────────────────────────────── */
/* Self-hosted, OFL 1.1 (fonts/LICENSES.md). A fonts.googleapis.com link
   would make the browser of somebody whose whole relationship with this
   product is "my words stay mine" announce every page load to a third
   party. `font-display: swap` because a blocked wordmark is worse than an
   unstyled one. */
@font-face {
  font-family: "Instrument Serif"; font-style: normal; font-weight: 400;
  font-display: swap; src: url("/fonts/instrument-serif-400.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Sans"; font-style: normal; font-weight: 400 600;
  font-display: swap; src: url("/fonts/plex-sans-var.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono"; font-style: normal; font-weight: 400;
  font-display: swap; src: url("/fonts/plex-mono-400.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono"; font-style: normal; font-weight: 500;
  font-display: swap; src: url("/fonts/plex-mono-500.woff2") format("woff2");
}

/* ── ground ────────────────────────────────────────────────────────────── */

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

/* A flex or grid child defaults to `min-width: auto` and refuses to shrink
   below its content, and several strings on this page are unbroken and
   machine-generated (`u:L0:user:lives_in:muizenberg:any`, model identifiers)
   and longer than a phone is wide. Setting it once here rather than per
   component is the difference between a rule and a list of exceptions. */
* { min-width: 0; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: var(--type-body);
  -webkit-font-smoothing: antialiased;
  /* Tabular figures everywhere, inherited. Mandatory in every table and
     metric display (brand §6), and a metric that jitters as it updates
     `1` is narrower than `8` is the exact thing it prevents. */
  font-variant-numeric: var(--figures-tabular);
}

.wrap {
  max-width: var(--breakpoint-xl);
  margin: 0 auto;
  padding: var(--space-5) var(--space-4) var(--space-7);
}

/* Keyboard users get the ring the tokens specify, and it is theme-specific
   by necessity: no single colour reaches 3:1 against both linen and ink. The
   offset is what makes it a ring rather than a halo — WCAG 2.2's 2.4.11
   wants the indicator distinguishable, and an outline drawn ON the border of
   a bordered control is not. */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
/* Only for people who cannot see it: a keyboard-only route past the
   masthead into the conversation. */
.skip {
  position: absolute; left: var(--space-2); top: -100vh;
  z-index: var(--layer-dialog);
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3); font: var(--type-label);
}
.skip:focus { top: var(--space-2); }

/* Present to a screen reader, absent to everybody else. `display: none` would
   remove it from the accessibility tree too, which defeats the point. */
.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* Marketing may be expressive; the product must not be. The allow-list is
   short — a dialog's opacity and transform, a colour on hover or focus — and
   this kills all of it for anybody who has asked. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── the mark and the wordmark ─────────────────────────────────────────── */

/* The fill is the accent, not the favicon's madder-light: that token exists
   to clear a white AND a near-black browser tab, and it is 2.88:1 on linen.
   Two different jobs, two different inks — tools/contrast.py measures both.
   Without this rule the mark inherits `fill: black`. */
.mark { display: block; flex: none; fill: var(--accent); }
/* Clear space is one bedding plane on every side, taken from an internal
   measurement of the drawing rather than a pixel count (brand §7 rule 4), so it
   scales with whatever size the mark is placed at. */
.lockup {
  display: flex; align-items: center;
  gap: calc(var(--space-3) + var(--mark-clear-space) * 1px);
}
.wordmark {
  font: var(--type-title); letter-spacing: var(--type-title-tracking);
  margin: 0;
}

/* ── masthead ──────────────────────────────────────────────────────────── */
/* A rule in the text colour, not the accent: this is the top of a register,
   and the accent has better things to spend itself on. */
.masthead {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  border-bottom: 2px solid var(--text);
  padding-bottom: var(--space-3);
}
.masthead .spacer { flex: 1 1 auto; }
.who {
  font: var(--type-data-small); color: var(--data);
  overflow-wrap: anywhere;
}

/* The state line: what this deployment is actually running, in mono, under
   the masthead. It exists because "it shows its working" (brand §3) has to include
   the machine's own state, and because the thing it replaced put a warning
   as serious as "your keystore is missing, every quote will read as erased"
   into 11px metadata beside the sign-out button. */
.state {
  display: flex; gap: var(--space-4); flex-wrap: wrap;
  font: var(--type-data-small); color: var(--text-muted);
  padding: var(--space-2) 0 0;
}
.state b { font-weight: 500; color: var(--data); }

/* ── layout ────────────────────────────────────────────────────────────── */
/* Two columns above the `lg` breakpoint, one below. The conversation leads
   because it is where the person acts; the record follows because it is what
   they came to check. */
.columns {
  display: grid; grid-template-columns: 1fr; gap: var(--space-5);
  margin-top: var(--space-5); align-items: start;
}
@media (min-width: 1000px) {
  .columns { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); }
}

.stack { display: flex; flex-direction: column; gap: var(--space-4); }
.row {
  display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center;
}

.card {
  background: var(--surface);
  border: 1px solid var(--hair);
  border-radius: var(--radius-md);
  box-shadow: var(--elevation-raised);
  padding: var(--space-4);
}
/* A section of a register is labelled like one: mono, uppercased, quiet. */
.eyebrow, .card h2 {
  font: var(--type-eyebrow); letter-spacing: var(--type-eyebrow-tracking);
  text-transform: uppercase; color: var(--text-muted);
  margin: 0 0 var(--space-3);
}
.card h3 {
  font: var(--type-label); margin: var(--space-4) 0 var(--space-2);
}

/* ── the transcript ───────────────────────────────────────────────────── */

.log {
  display: flex; flex-direction: column; gap: var(--space-5);
  max-height: 58vh; overflow-y: auto;
  padding-right: var(--space-1);
  /* A scrollable region has to be reachable by keyboard or its content is
     unreachable without a mouse — 2.1.1, and the reason for tabindex="0" on
     the element itself. `overscroll-behavior` stops a flick at the bottom of
     the transcript scrolling the whole page instead. */
  overscroll-behavior: contain;
}
@media (max-width: 720px) { .log { max-height: none; } }

.turn { display: flex; flex-direction: column; gap: var(--space-2); }

/* The person's own words: the sunken ground, set right, in the serif.
   NOT reversed out of madder — a reversed-out bubble is the single move that
   would make this read as a messaging app, and it would spend the loudest
   styling decision on the page on the least important content. */
.said-by-me {
  align-self: flex-end; max-width: 82%;
  font: var(--type-speech-own);
  background: var(--sunken); color: var(--text);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  white-space: pre-wrap; overflow-wrap: break-word;
}
/* The twin answering. Serif, no container — the page's own voice. */
.said-by-twin {
  max-width: var(--measure-prose);
  font: var(--type-speech);
  white-space: pre-wrap; text-wrap: pretty; overflow-wrap: break-word;
}
/* What the machine did with it: marginalia, in the data face and the data
   colour, with a madder rule because a write is the product working. */
.bookkeeping {
  font: var(--type-data-small); color: var(--data);
  border-left: 2px solid var(--accent);
  padding: var(--space-1) 0 var(--space-1) var(--space-3);
  overflow-wrap: anywhere;
}
.bookkeeping b { font-weight: 500; color: var(--text); }
.bookkeeping ul { margin: var(--space-1) 0 0; }

/* Kora asking. She may sound like a person, so the question is serif; the
   frame says plainly that this is her turn and not the record's. An OPEN
   frame — transparent ground, hairline all round — because the crisis band
   below has to be unmistakably different and this is the block it would
   otherwise be confused with. */
.ask {
  border: 1px solid var(--border);
  border-left: 2px solid var(--accent);
  border-radius: var(--radius-sm);
  padding: var(--space-3);
  background: transparent;
}
.ask > p:first-child {
  margin: 0 0 var(--space-2);
  font: var(--type-speech); font-style: italic;
}

/* §5's crisis branch. The separation from `.ask` is a safety property rather
   than a nicety: a crisis turn is the one moment the system must not look
   like it is doing its ordinary personalising job.
   The first attempt at this failed and the failure is worth keeping. It used
   `--link` for the rule and `--sunken` for the ground. In the dark theme
   `--link` and `--accent` are the same value — madder.light serves both — so
   the crisis rule and Kora's question rule rendered IDENTICALLY, and the
   madder-tinted ground is within 1.06:1 of `--sunken`, so hue alone could
   not carry it either. So the distinction is structural and three properties
   differ at once: a 4px rule against 2px, a filled ground against an open
   frame, and a square edge on the rule side so it reads as a system band
   rather than as a card. */
.crisis {
  border-left: 4px solid var(--accent);
  background: var(--alert);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  padding: var(--space-3) var(--space-4);
  font: var(--type-speech);
  white-space: pre-wrap; overflow-wrap: break-word;
}

/* §5's stance rule made visible: a view they hold is not a fact. `--flagged`
   is ochre, and ochre on the SUNKEN ground is 4.40:1 and fails AA — so this
   rule must never combine with a sunken background. It renders on the card,
   where it is 5.68:1. */
.held { color: var(--flagged); font-style: italic; }

/* ── the record ────────────────────────────────────────────────────────── */

ul, ol { margin: 0; padding-left: 0; list-style: none; }

/* One line of the record. The grid is the point: a stratum column, then the
   fact, so every line in the list shows its own depth in the same place and
   the column reads as a profile down the page. */
.entry {
  display: grid;
  grid-template-columns: 10px minmax(0, 1fr);
  gap: var(--space-3);
  align-items: start;
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--hair);
  font: var(--type-data);
}
.entry:last-child { border-bottom: 0; }
/* `break-word` on the fact, `anywhere` only on the key below. `anywhere`
   here split dates mid-token in the narrow column — "since 2026-08-" then
   "21" on the next line — which is worse than a slightly ragged edge. The
   belief key is the one string that genuinely has to break anywhere. */
.entry .fact { overflow-wrap: break-word; }
.entry .object { color: var(--text); }
/* The atom's key on its own line: inline, it wrapped mid-key in the narrow
   column and broke the alignment that makes a list of atoms scannable. */
.entry code {
  display: block; margin-top: var(--space-1);
  font: var(--type-data-small); color: var(--data);
  word-break: break-all;
}

/* THE STRATUM COLUMN — the mark's own idea at row scale, and the only place
   in the interface where accumulation is drawn rather than described. One
   segment per version on file, oldest at the bottom, the current one at the
   top in the accent and the superseded ones beneath it in the hair colour.
   A line the record has revised four times is visibly deeper than one
   asserted yesterday, which is the product's whole claim rendered on every
   row instead of asserted in a paragraph. */
.strata {
  display: flex; flex-direction: column-reverse; gap: 1px;
  padding-top: 3px;
}
.strata i {
  display: block; height: 3px; background: var(--depth-past);
  border-radius: 1px;
}
.strata i.now { background: var(--depth-current); height: 4px; }

/* AXIS ONE — standing on the ladder. Four steps, each carrying weight and
   style as well as colour. */
.entry { border-left: 3px solid transparent; padding-left: var(--space-3); }
.entry.stable { border-left-color: var(--standing-stable); }
.entry.corroborated {
  border-left-width: 2px; border-left-color: var(--standing-corroborated);
}
.entry.provisional {
  border-left-width: 2px; border-left-style: dashed;
  border-left-color: var(--standing-provisional);
  color: var(--text-muted);
}
.entry.hypothesis {
  border-left-width: 2px; border-left-style: dotted;
  border-left-color: var(--standing-hypothesis);
  color: var(--text-muted); font-style: italic;
}

/* AXIS TWO — what kind of line this is. Categorical, and contested is the
   loudest state in the system rather than the faintest, because a contested
   fact is being withheld from every briefing and that is a consequence the
   person has to be able to see and settle. */
.entry.contested {
  border-left-width: 3px; border-left-style: solid;
  border-left-color: var(--kind-contested);
  color: var(--text); font-style: normal;
}
.entry.contested .flag {
  display: block; color: var(--kind-contested); font-weight: 500;
}
.entry.superseded {
  border-left-style: solid; border-left-width: 2px;
  border-left-color: var(--kind-superseded);
  color: var(--text-muted);
}
/* Was true, is not now — struck through, and never merely absent. */
.entry.superseded .object { text-decoration: line-through; }
.entry .replaced {
  display: block; color: var(--text-muted);
  font: var(--type-data-small);
}
.entry.erased {
  border-left-color: var(--kind-erased); border-left-style: solid;
  color: var(--text-muted);
}

.entry .meta {
  display: block; color: var(--text-muted); font: var(--type-data-small);
}
.entry.provisional .meta, .entry.hypothesis .meta { color: inherit; }

/* ── controls ──────────────────────────────────────────────────────────── */

input, textarea, select, button {
  font: var(--type-body); color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  /* Colour is on the motion allow-list; layout is not. */
  transition: border-color var(--duration-fast) var(--easing-standard),
              background-color var(--duration-fast) var(--easing-standard);
}
input, textarea { width: 100%; max-width: var(--measure-form); }
textarea { max-width: none; resize: vertical; }

/* WCAG 2.2 SC 2.5.8 is a AA criterion now, not AAA: 24×24 CSS px minimum for
   every pointer target. The surface this replaces had 11px text buttons
   inline on every record line — "why" and "fix" — at roughly 26×18, which
   fails. `min-height` on the base rather than a per-button fix, so a new
   control cannot reintroduce it. */
button {
  cursor: pointer;
  min-height: var(--target-min);
  background: var(--accent); color: var(--on-accent);
  border-color: var(--accent);
  font: var(--type-label);
  padding: var(--space-2) var(--space-4);
}
button:hover { background: var(--link); border-color: var(--link); }
button.ghost {
  background: transparent; color: var(--text); border-color: var(--border);
  font-weight: 400;
}
button.ghost:hover { background: var(--sunken); border-color: var(--text); }
/* The inline verbs on a record line: quiet, but still a legal target. */
button.inline {
  background: transparent; border-color: transparent;
  color: var(--link); font: var(--type-data-small);
  padding: var(--space-1) var(--space-2);
  min-height: var(--target-min); min-width: var(--target-min);
}
button.inline:hover { background: var(--sunken); border-color: var(--border); }
button.danger { background: transparent; color: var(--link);
                border-color: var(--link); }
button.danger:hover { background: var(--alert); }
button[disabled] { opacity: 0.45; cursor: default; }
button[disabled]:hover { background: var(--accent); }
button.ghost[disabled]:hover, button.inline[disabled]:hover {
  background: transparent;
}

label { display: block; font: var(--type-label); color: var(--text-muted); }
label > input, label > textarea {
  display: block; margin-top: var(--space-1); color: var(--text);
}

.say {
  display: flex; gap: var(--space-2); margin-top: var(--space-4);
  align-items: flex-end;
}
.say textarea {
  font: var(--type-speech-own); max-width: none;
  /* One line to start, growing to six. A single-line input for something a
     person may write a paragraph into hides most of what they typed from
     them while they type it. */
  min-height: var(--target-comfortable);
  field-sizing: content; max-height: 9lh;
}

/* ── tabs ──────────────────────────────────────────────────────────────── */
/* Real tab semantics, and — the actual bug this replaces — a VISIBLE
   selected state. The strip it replaces set `border-bottom: 2px solid
   transparent` on every tab and never set it on any, so the person could not
   see which panel they were looking at. */
.tabs {
  display: flex; gap: 0; flex-wrap: wrap;
  margin-bottom: var(--space-3);
  border-bottom: 1px solid var(--hair);
}
.tabs [role="tab"] {
  font: var(--type-eyebrow); letter-spacing: var(--type-eyebrow-tracking);
  text-transform: uppercase;
  padding: var(--space-2) var(--space-3);
  background: transparent; color: var(--text-muted);
  border: 0; border-bottom: 2px solid transparent; border-radius: 0;
  margin-bottom: -1px;
}
.tabs [role="tab"]:hover { color: var(--text); background: transparent; }
.tabs [role="tab"][aria-selected="true"] {
  color: var(--text); border-bottom-color: var(--accent); font-weight: 500;
}

/* ── notices ───────────────────────────────────────────────────────────── */
/* A banner, for the things that were previously whispered in metadata. A
   keystore that cannot be read means every quote on the page will render as
   erased; that is not a footnote. */
.notice {
  display: flex; gap: var(--space-3); align-items: flex-start;
  border-left: 4px solid var(--accent);
  background: var(--alert);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  padding: var(--space-3) var(--space-4);
  font: var(--type-body);
  margin-bottom: var(--space-4);
}
.notice b { font-weight: 600; }

.note { font: var(--type-caption); color: var(--text-muted);
        max-width: var(--measure-prose); }
.note:empty { display: none; }
.err { color: var(--link); font: var(--type-label); }
.err:empty { display: none; }
.hidden { display: none !important; }

pre {
  white-space: pre-wrap; font: var(--type-data-small); color: var(--text);
  background: var(--sunken); border-radius: var(--radius-sm);
  padding: var(--space-3); max-height: 240px; overflow: auto;
  margin: var(--space-2) 0 0;
}

/* ── dialogs ───────────────────────────────────────────────────────────── */
/* Native <dialog>, replacing prompt() / confirm() / alert(). Not cosmetics:
   `prompt()` cannot show the person the value they are about to change, is
   unstyleable, is suppressed outright in some embedding contexts, and was
   carrying the CORRECTION flow — which is Law 5's "correctable", one of the
   five things the twin belongs to the person BY. The browser's modal also
   brings focus trapping, Escape, and inert background for free, which a
   hand-rolled overlay gets wrong more often than right. */
dialog {
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface); color: var(--text);
  box-shadow: var(--elevation-overlay);
  padding: var(--space-5);
  width: min(30rem, calc(100vw - var(--space-6)));
  max-height: 85vh; overflow: auto;
}
/* Dark themes get no shadow, so the overlay's edge comes from the border and
   the lighter surface — and the scrim behind it has to be heavier there,
   which is why it is a per-theme token rather than one value. */
dialog::backdrop { background: var(--scrim); }
dialog h2 { font: var(--type-label); margin: 0 0 var(--space-2); }
dialog .quote {
  font: var(--type-data); background: var(--sunken);
  border-radius: var(--radius-sm); padding: var(--space-2) var(--space-3);
  margin: 0 0 var(--space-3); overflow-wrap: anywhere;
}
dialog form { display: flex; flex-direction: column; gap: var(--space-3); }
dialog .actions { display: flex; gap: var(--space-2); justify-content: flex-end;
                  flex-wrap: wrap; }
@media (prefers-reduced-motion: no-preference) {
  dialog[open] { animation: rise var(--duration-base) var(--easing-entrance); }
  @keyframes rise {
    from { opacity: 0; transform: translateY(4px); }
    to { opacity: 1; transform: none; }
  }
}

/* ── the front door ───────────────────────────────────────────────────── */
/* The one place the page raises its voice. */
.gate { max-width: 27rem; margin: 10vh auto; }
.gate .wordmark { font: var(--type-display);
                  letter-spacing: var(--type-display-tracking); }
.gate .lede {
  font: var(--type-speech); color: var(--text-muted);
  margin: var(--space-3) 0 0; max-width: 30ch;
  border-top: 2px solid var(--accent); padding-top: var(--space-3);
}
.gate .stack { margin-top: var(--space-5); }

/* ── theme control ────────────────────────────────────────────────────── */
/* Three states, not two: system is the default and has to stay reachable,
   because a person who has set their whole machine to switch at sunset does
   not want this one page pinned. brand §11 requires honouring the system
   preference by default WITH an explicit override, and the surface this
   replaces had the CSS hook for it and no control — so a person whose system
   said light could not get dark at all. */
.themer { display: flex; border: 1px solid var(--border);
          border-radius: var(--radius-sm); overflow: hidden; }
.themer button {
  border: 0; border-radius: 0; background: transparent; color: var(--text-muted);
  font: var(--type-eyebrow); letter-spacing: var(--type-eyebrow-tracking);
  text-transform: uppercase;
  padding: 0 var(--space-2); min-height: var(--target-min);
}
.themer button:hover { background: var(--sunken); color: var(--text); }
.themer button[aria-pressed="true"] {
  background: var(--accent); color: var(--on-accent);
}

/* ── print ─────────────────────────────────────────────────────────────── */
/* An archival product gets printed and handed to somebody. Force the light
   tokens, drop what cannot be acted on from paper, and let the transcript
   run to its full length instead of being clipped at 58vh. */
@media print {
  :root { color-scheme: light; }
  /* Paper, not linen: the warm ground is right on a screen and is a page of
     wasted ink on a printer. */
  body { background: var(--colour-linen-raised); }
  .card { box-shadow: none; border-color: var(--border); }
  .log { max-height: none; overflow: visible; }
  button, .say, .themer, .skip, dialog { display: none !important; }
  .entry { break-inside: avoid; }
}

/* ── the microphone (D-032) ────────────────────────────────────────────── */
/* Voice in and out is the browser's own Web Speech API — no dependency, and
   no audio reaching a Mimir server. Everything here is layout and state;
   every colour is a token, because app.css may hold no literal of its own
   (brand §11, and a test greps for it). */
.say button { white-space: nowrap; }

/* A page that is listening to the room has to look unmistakably different
   from one that is not, and `--accent` is the only pair on this palette that
   carries its own foreground (`--on-accent`) on all three surfaces. The
   earlier draft of this rule reached for a colour that no longer exists as a
   token, which rendered the live state invisible on linen — the one signal
   that must never fail quietly. */
#mic.live {
  background: var(--accent); color: var(--on-accent);
  border-color: var(--accent);
  animation: mic-pulse var(--duration-slow) var(--easing-standard) infinite
             alternate;
}
@keyframes mic-pulse { to { opacity: 0.6; } }

/* The hands-free row sits under the say form. `.tick` overrides the width
   inputs inherit here, and the negative top margin cancels the `label input`
   rule that assumes a stacked field. */
.voice-row { margin-top: var(--space-2); align-items: baseline; }
.voice-row .note { margin: 0; }
#hf-row { display: flex; align-items: center; gap: var(--space-2);
          cursor: pointer; }
input.tick {
  width: auto; min-width: 0; margin: 0; flex: none;
  min-height: 0; accent-color: var(--accent);
}
#voicenote { text-align: right; max-width: 42ch; }

/* ── Daedalus, the side panel (D-009) ──────────────────────────────────── */
/* The developer AI that lives inside the machine. It reads the deployment and
   proposes; it has no write path at all, so everything it says has to pass
   through the owner and a promotion with a reason on it.
   A drawer rather than a column: it has to be open *beside* whichever
   instruction is being edited — that is the whole point of it — and a grid
   cell would push the editor around every time it opened. */
#dpanel {
  position: fixed; top: 0; right: 0; bottom: 0; width: min(30rem, 92vw);
  z-index: var(--layer-overlay);
  display: flex; flex-direction: column;
  background: var(--surface); border-left: 1px solid var(--border);
  box-shadow: var(--elevation-overlay);
  transform: translateX(100%);
  transition: transform var(--duration-base) var(--easing-standard);
}
#dpanel.open { transform: none; }
#dpanel header {
  display: flex; align-items: baseline; gap: var(--space-3);
  padding: var(--space-4); border-bottom: 1px solid var(--hair);
}
#dpanel h2 { font: var(--type-title); margin: 0; }
#dthread {
  flex: 1; overflow-y: auto; padding: var(--space-4);
  display: flex; flex-direction: column; gap: var(--space-4);
}
#dpanel form { padding: var(--space-4); border-top: 1px solid var(--hair);
               display: flex; flex-direction: column; gap: var(--space-2); }
/* Its answers are proposals, so they read as quoted material rather than as
   instructions the machine has already accepted. */
.dturn { display: flex; flex-direction: column; gap: var(--space-2); }
.dasked { font: var(--type-label); color: var(--text-muted); }
.dsaid { white-space: pre-wrap; border-left: 2px solid var(--accent);
         padding-left: var(--space-3); }
.dsaid.offline { border-left-color: var(--unsettled); }
.dctx { font: var(--type-caption); color: var(--text-muted); }
@media (prefers-reduced-motion: reduce) {
  #dpanel { transition: none; }
}

/* ── the settings area, grouped and named ──────────────────────────────── */
.sgroup { margin-top: var(--space-5); }
.sgroup:first-of-type { margin-top: 0; }
.sgroup > h3 { margin-bottom: var(--space-2); }
/* A setting is a heading, a sentence, and a state — not a key. */
.setting {
  display: block; width: 100%; text-align: left; cursor: pointer;
  background: none; border: 0; border-top: 1px solid var(--hair);
  border-radius: 0; padding: var(--space-3) 0; color: var(--text);
  min-height: var(--target-comfortable);
}
.setting:hover { background: var(--sunken); }
.setting .name { display: block; font-weight: 500; }
.setting .desc { display: block; font: var(--type-caption);
                 color: var(--text-muted); margin-top: 2px; }
.setting .pin { font: var(--type-data-small); color: var(--flagged); }
.setting .tracking { font: var(--type-data-small); color: var(--text-muted); }
/* The number dials, rendered as fields rather than as JSON somebody types. */
.dial { display: flex; align-items: center; gap: var(--space-3);
        border-top: 1px solid var(--hair); padding: var(--space-2) 0; }
.dial label { flex: 1; margin: 0; }
.dial input { width: 6rem; flex: none; }
.arcq { display: flex; gap: var(--space-2); align-items: flex-start;
        border-top: 1px solid var(--hair); padding: var(--space-3) 0; }
.arcq textarea { flex: 1; }
