/* Every colour the page draws with is the theme's, and so are the controls'
   shapes and type:
   `theme/default/tokens.css` (what the components need) and
   `theme/default/luu.css` (luu's own surfaces — bars, dividers, the chat's
   inks, buckets, Monokai, the terminal), both loaded by `index.html` before
   this file. Nothing here declares a value the theme owns.

   **Which surfaces are dark is said in the markup**: the heads, the feet, a
   modal's head and the terminal carry `data-tone="dark"`, and the content
   column and a snippet take it from their tone preference. The theme decides
   what dark is. See RECORD/2026-10-21.the-design-system-applied.completed.md,
   and RECORD/2026-09-15.a-three-pane-inspector.completed.md for why the bars
   are dark on a light page. */

/* The chat's ground, and its inks re-pointed for it. A bar inside sets its own
   tokens, so the chat's head and foot keep theirs. Settings' side menu is the
   same ground, so it is the same rule. */
.col.chat, .rail-col {
  background: var(--luu-chat-bg);
  color: var(--luu-chat-fg);
  --luu-border: var(--luu-chat-line);
  --luu-bg: var(--luu-chat-hover);
  --luu-fg: var(--luu-chat-fg);
  --luu-fg-muted: var(--luu-chat-dim);
  --luu-accent: var(--luu-chat-accent);
  --luu-warn: var(--luu-chat-warn);
  --luu-bad: var(--luu-chat-bad);
  --luu-ok: var(--luu-chat-ok);
}

/* The page's own ground, under everything: a bounce at the end of any scroller
   carries on into the page, which is pulled off this. The bars' grey, so the
   dark heads and feet at the window's edges run on into it rather than off a
   white one. */
html { background: var(--luu-bar); }

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--luu-bg);
  color: var(--luu-fg);
  font: 14px/1.55 var(--luu-font-sans);
}

/* `dvh` rather than `vh`: a mobile browser's toolbar is the difference between
   a page that fits and one whose footer is under the chrome. */
#app { display: block; height: 100dvh; }

/* ---- the page's controls ------------------------------------------------
   A control looks the same in the chat's composer, in the folder picker and in
   the settings modal, so its look is written once, here, rather than in
   whichever component happened to need it first. It used to live in
   `app.html`'s `<style>`, which was the page's *only* unscoped component
   stylesheet and so doubled as this layer by accident: every other component
   was quietly inheriting the shell's rules, and the shell could not be scoped
   without them all losing their buttons. See
   RECORD/2026-09-16.styles-that-stay-in-their-component.completed.md.
   **Each is `:where()`, which weighs nothing**: these are for the bare
   elements the page writes, and a component's scoped style — the theme's
   `TextInput` drawing its own box around an input — must win without
   fighting them. Its focus ring, above all, which `input:…:focus` used to
   draw a second time inside the component's. See
   RECORD/2026-10-21.the-design-system-applied.completed.md. */
/* What the browser draws round a focused button, link or anything else the
   page writes bare, in the theme's outline: one thin grey line, as the
   theme's components draw theirs. */
:where(:focus-visible) {
  outline: var(--luu-outline-width) solid var(--luu-outline-color);
}
:where(input) {
  /* `flex: 1` because a text field is, everywhere on this page, the thing on
     its row that takes the leftover width — the composer, the rename box, a
     provider's cell. It is inert outside a flex row, which is why it can be a
     base rather than a per-component override. */
  flex: 1;
  min-width: 0;
  box-sizing: border-box;
  background: var(--luu-surface);
  border: 1px solid var(--luu-border);
  border-radius: var(--luu-radius-md);
  color: inherit;
  font: inherit;
  padding: .35rem .7rem;
  transition: border-color var(--luu-duration-fast) ease, box-shadow var(--luu-duration-fast) ease;
}
:where(input:not([type="checkbox"]):not([type="radio"]):focus) {
  outline: var(--luu-outline-width) solid var(--luu-outline-color);
  outline-offset: -1px;
}
:where(input[type="number"]) { appearance: textfield; -moz-appearance: textfield; }
:where(input[type="number"])::-webkit-inner-spin-button,
:where(input[type="number"])::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
:where(input[type="checkbox"], input[type="radio"]) {
  flex: none;
  accent-color: var(--luu-accent);
  cursor: pointer;
}

:where(select) {
  box-sizing: border-box;
  background: var(--luu-surface);
  border: 1px solid var(--luu-border);
  border-radius: var(--luu-radius-md);
  color: inherit;
  font: inherit;
  font-size: var(--luu-text-md);
  padding: .35rem 1.8rem .35rem .65rem;
  appearance: none;
  -webkit-appearance: none;
  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="%239e9e9e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>');
  background-repeat: no-repeat;
  background-position: right .6rem center;
  cursor: pointer;
  transition: border-color var(--luu-duration-fast) ease, box-shadow var(--luu-duration-fast) ease;
}
:where(select:focus) {
  outline: var(--luu-outline-width) solid var(--luu-outline-color);
  outline-offset: -1px;
}

:where(textarea) {
  box-sizing: border-box;
  background: var(--luu-surface);
  border: 1px solid var(--luu-border);
  border-radius: var(--luu-radius-md);
  color: inherit;
  font: inherit;
  font-size: var(--luu-text-md);
  padding: .4rem .7rem;
  resize: vertical;
  transition: border-color var(--luu-duration-fast) ease, box-shadow var(--luu-duration-fast) ease;
}
:where(textarea:focus) {
  outline: var(--luu-outline-width) solid var(--luu-outline-color);
  outline-offset: -1px;
}

:where(button) {
  background: var(--luu-accent);
  color: var(--luu-on-accent);
  border: 0;
  border-radius: var(--luu-radius-md);
  font: inherit;
  font-weight: 600;
  padding: .35rem .9rem;
  cursor: pointer;
}
button.cancel { background: var(--luu-warn); }
/* A button that reads as a link: the affordance for *go there* rather than
   *do this*, and the page's most reused control after the plain button. */
button.link {
  background: none; border: 0; color: inherit; cursor: pointer;
  padding: 0; font: inherit; text-decoration: underline dotted;
}

/* Disabled is a **state**, and a state has to outrank whatever skinned the
   control it is on. `button:disabled` is one element and one pseudo-class,
   which a component's own `.clip { cursor: pointer }` already ties with — and
   `<style scoped>` rewrites that to `.clip[data-jq79="…"]`, one level higher
   again, so the plain form lost to every scoped rule on the page and a
   greyed-out button still offered a pointer. The attribute written twice is
   the two levels back; it selects exactly what `:disabled` does and costs
   nothing to match. */
button[disabled][disabled] { opacity: .4; cursor: default; }
button.link[disabled][disabled] { opacity: .5; }
input[disabled][disabled] { opacity: .6; }
select[disabled][disabled] { opacity: .6; cursor: default; }
textarea[disabled][disabled] { opacity: .6; }

/* A block of the machine's own words — a prompt, a tool's output, a sandbox
   line. Every panel that shows one shows it the same way. */
:where(pre) {
  background: var(--luu-sunken);
  border: 1px solid var(--luu-border);
  border-radius: var(--luu-radius-md);
  padding: .6rem;
  font-size: 12px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 22rem;
  overflow-y: auto;
}

/* A well: the frame and ground of a block of the machine's words that does
   not scroll — the raw answer in a turn's inspector. One that scrolls is a
   `<Scrollable frame="well">` (`theme/default/components/scrollable.html`),
   which draws the same well itself. */
.well {
  background: var(--luu-sunken);
  border: 1px solid var(--luu-border);
  border-radius: 6px;
}
/* A `pre` inside a Scrollable is not a scroller of its own: the Scrollable is.
   Twice the attribute, to outrank a component's own `pre` rule, which keeps
   its padding, its wrapping and its type. */
[data-scrollable][data-scrollable] > pre {
  margin: 0;
  background: none;
  border: 0;
  border-radius: 0;
  max-height: none;
  overflow: visible;
}

/* ---- the words' three volumes -------------------------------------------
   `--luu-fg-muted` is *secondary*, `--luu-warn` is *this may not be what you think*, and
   `--luu-bad` is *this failed*. They are one-word classes used in nine components,
   which is exactly what a global utility is for — and what a component's
   `<style scoped>` cannot be, because nine copies of `color: var(--luu-fg-muted)` is
   nine chances for one of them to drift. */
.empty, .dim { color: var(--luu-fg-muted); }
.warn { color: var(--luu-warn); }
.error {
  margin: 0 1rem .4rem;
  padding: .5rem .75rem;
  border: 1px solid var(--luu-bad);
  border-radius: 6px;
  color: var(--luu-bad);
  font-size: 13px;
}

/* ---- the page's own symbols ---------------------------------------------
   One sprite of `<symbol>`s lives at the top of `app.html`; this is the box
   they are drawn into. Global rather than in any component's `<style scoped>`
   because the caret and the refresh are each used in two files, and a scoped
   rule is rewritten per component. See
   RECORD/2026-09-15.a-three-pane-inspector.completed.md. */
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.ic {
  display: block;
  flex: none;
  width: 16px;
  height: 16px;
  /* `currentColor` and no fill is the whole colour model: a symbol takes the
     colour of the control it sits in — `--luu-fg-muted` in a panel head, `--luu-on-accent`
     on a selected row — so the light theme needs no second set, exactly as the
     highlighter uses the page's own tokens rather than a palette of its own. */
  fill: none;
  stroke: currentColor;
  /* 2 on a 24-unit grid is 1.33px once the box is 16, which is the weight the
     file icons a theme draws sit at. One number for the whole set. */
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* The one exception to that: the tree's own file and folder, drawn when no
   icon theme is chosen, are filled — a yellow folder, a white sheet — so they
   read as things and not as outlines, the way an icon theme's do. The colours
   are the theme's (`luu.css`). */
.ic.glyph { fill: var(--luu-glyph-file-fill); stroke: var(--luu-glyph-file-stroke); stroke-width: 1.6; }
.ic.glyph.folder { fill: var(--luu-glyph-folder-fill); stroke: var(--luu-glyph-folder-stroke); }
/* A sheet of a known kind: its edge and its symbol in the kind's colour, its
   paper a tint of it. `data-kind` is `glyphFor`'s answer. */
.ic.glyph[data-kind="code"] { --kind: var(--luu-glyph-code); }
.ic.glyph[data-kind="markup"] { --kind: var(--luu-glyph-markup); }
.ic.glyph[data-kind="data"] { --kind: var(--luu-glyph-data); }
.ic.glyph[data-kind="doc"] { --kind: var(--luu-glyph-doc); }
.ic.glyph[data-kind="image"] { --kind: var(--luu-glyph-image); }
.ic.glyph[data-kind="media"] { --kind: var(--luu-glyph-media); }
.ic.glyph[data-kind="archive"] { --kind: var(--luu-glyph-archive); }
.ic.glyph[data-kind="shell"] { --kind: var(--luu-glyph-shell); }
.ic.glyph:is([data-kind="code"], [data-kind="markup"], [data-kind="data"], [data-kind="doc"],
  [data-kind="image"], [data-kind="media"], [data-kind="archive"], [data-kind="shell"]) {
  stroke: var(--kind);
  fill: color-mix(in srgb, var(--kind) 12%, var(--luu-glyph-file-fill));
}
/* `▾` and `▸` were two codepoints for one control. This is one shape, turned. */
.ic.open { transform: rotate(90deg); transform-origin: center; }

/* ---- the modals ---------------------------------------------------------
   Settings is sections down the side rather than one scroll, because the
   sections are not steps; inside Models, what this server resolved (fixed for
   its lifetime) and the file the next run will read are styled apart on
   purpose — the first reads as a report, the second as a form. See
   RECORD/2026-09-07.configuring-from-the-browser.completed.md and
   RECORD/2026-09-16.three-columns-that-each-have-a-footer.completed.md. */
/* The dialog itself — box, backdrop, the way in and out, and its head — is
   `components/modal.html`. What is here is what goes *in* one: a modal's
   content is its owner's, so its styles are the page's. */
/* **A container pads nothing; what is in it pads itself.** The body scrolls
   and holds two things, the content (`.modal-main`) and the actions
   (`.modal-foot`), and each brings its own padding — so the foot reaches the
   edges because nothing holds it in, not because it pulls itself back out of
   a padding with negative margins, which is how a gap above it was lost. */
.modal-body { padding: 0; overflow: auto; display: flex; flex-direction: column; scroll-padding-bottom: 4rem; }
.modal-main { padding: 1rem; flex: 1 0 auto; }
/* A Settings section's content: a column whose **gap is the only space**
   between its blocks — the hero, then each heading grouped with what it heads
   (`.pane-group`), closer together. The blocks bring no vertical margin of
   their own; the reset is for the page-wide ones (`h3`, `p`, `.refused`,
   `.error`) that keep theirs everywhere else. */
.pane-main { padding: 1.25rem 1.75rem 1.5rem; display: flex; flex-direction: column; gap: 1.5rem; }
/* In Settings, 4rem of room under the last block of a section: the pane ends
   in air rather than at a control, and a section's pinned foot is still at the
   pane's bottom edge, because it is the content that is padded and not the
   pane that scrolls it. */
.modal-body.pane .pane-main { padding-bottom: 4rem; }
.pane-group { display: flex; flex-direction: column; gap: .6rem; }
.modal-body :is(.pane-main, .pane-group) > * { margin-block: 0; }
/* A body with a pinned foot keeps its grey, which moves with it; what a bounce
   uncovers is the parent's, and the parent is grey above and white below — the
   top of the content and the white foot at its bottom. Normally covered whole
   by the head and the body, so seen only past either end. */
/* A foot on screen: Settings keeps every section it has drawn, hidden, and
   only the one shown counts. */
.modal-body:has(.modal-foot:not([aria-hidden="true"] *)) { background: var(--luu-tree-bg); }
dialog:has(> .modal-body .modal-foot:not([aria-hidden="true"] *)) {
  background: linear-gradient(var(--luu-tree-bg) 50%, var(--luu-surface) 50%);
}
.modal-body h3 {
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--luu-fg-muted);
  margin: 1.4rem 0 .3rem;
}
.modal-body h3:first-child { margin-top: 0; }
.small { font-size: 12px; }

/* One label column for every `dl.settings` on the page: `max-content` sized
   each list to its own longest label, so two lists one above the other
   started their values at different x. A label longer than this wraps. */
:root { --dt-width: 9rem; }
.settings {
  display: grid;
  grid-template-columns: var(--dt-width) minmax(0, 1fr);
  gap: .45rem 1rem;
  margin: .6rem 0 0;
  font-size: 13px;
}
.settings dt {
  color: var(--luu-fg-muted);
  display: flex;
  align-items: center;
  padding-top: .25rem;
  min-width: 0;
  overflow-wrap: anywhere;
}
.settings dd { margin: 0; display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; }
.settings dd > .warn, .settings dd > .error {
  flex-basis: 100%;
  font-size: 11px;
  line-height: 1.4;
  margin-top: .15rem;
}
.settings dd.caveat, .caveat {
  /* `display: block`, against the `dd` rule above it: a caveat is a *sentence*,
     and a flex container turns every `<b>` and `<code>` in one into an item
     that wraps on its own — which reads as a line break in the middle of a
     clause. The `dd`s above are rows of facts and stay flex. */
  display: block;
  grid-column: 2;
  color: var(--luu-warn);
  font-size: 12px;
  line-height: 1.4;
}
.settings .warn { color: var(--luu-warn); }
.settings .error { color: var(--luu-bad); }
/* The same shape as a caveat and not the same colour: `--luu-warn` means *this may
   not be what you think*, and a settings panel where every explanation is
   yellow has spent the one colour that was supposed to stand out. */
.settings dd.note, .note {
  display: block;
  grid-column: 2;
  color: var(--luu-fg-muted);
  font-size: 12px;
  line-height: 1.5;
}
.sandbox {
  margin: 0;
  font-size: 11px;
  color: var(--luu-fg-muted);
  white-space: pre;
  overflow-x: auto;
  max-height: 9rem;
}

.providers { width: 100%; border-collapse: collapse; font-size: 12px; margin-top: .4rem; }
.providers th {
  text-align: left;
  font-weight: normal;
  color: var(--luu-fg-muted);
  padding: .2rem .3rem;
  border-bottom: 1px solid var(--luu-border);
}
.providers td { padding: .2rem .3rem; vertical-align: middle; }
.providers input, .providers select { width: 100%; font-size: 12px; }
.providers .w-num { width: 5rem; }
.providers td .tag { margin-right: .4rem; }

/* The refusal a person can answer, rather than only read. */
.declare {
  margin-top: .8rem;
  padding: .6rem .8rem;
  border: 1px solid var(--luu-warn);
  border-radius: 4px;
}
.declare p { margin: 0 0 .5rem; font-size: 12px; line-height: 1.5; }
.declare input { width: 18rem; margin-right: .5rem; }
/* Settings → Models: the providers' column and the head over the models on a
   ground of their own, a step off the white the models sit on. Here and not
   in `provider-models.html`, because the pickers that share it keep theirs. */
.models-pane .providers, .models-pane .models-head { background: #f9f9f9; }
:root[data-theme="dark"] .models-pane .providers,
:root[data-theme="dark"] .models-pane .models-head { background: #1b1b1b; }
/* The chat's model menu, the same way on its own dark ground: the providers'
   column, the head and the foot a step darker than the models, and the
   provider in focus on the models' ground — the tab they hang from — rather
   than the island's near-black. */
.chat-foot .picker .menu .providers,
.chat-foot .picker .menu .models-head,
.chat-foot .picker .menu > footer { background: var(--luu-menu-side); }
.chat-foot .picker .menu .prov.on { background: var(--luu-menu); }

/* The first run. Loud on purpose and once: a page that opens on a chat box
   answering from a fixture is the one thing this says out loud. */
.first-run {
  padding: .7rem .9rem;
  border: 1px solid var(--luu-warn);
  border-left-width: 3px;
  border-radius: 4px;
}
.first-run p { margin: .4rem 0 0; font-size: 12px; line-height: 1.6; color: var(--luu-fg-muted); }
.first-run code { color: var(--luu-fg); }
.refused {
  margin: .4rem 0;
  padding: .5rem .7rem;
  border: 1px solid var(--luu-border);
  border-radius: 4px;
  color: var(--luu-fg-muted);
  font-size: 12px;
  line-height: 1.5;
}
/* Pinned while the report above it scrolls: a Save button below the fold is a
   form people think they cannot submit — and a refusal that scrolls away from
   the button it is about is one somebody clicks past. Both live in the same
   pinned footer for that reason. A sibling of the content, not inside it, so
   it is edge to edge and at the bottom with no margin of its own: `auto` above
   it takes the room a short content leaves. */
.modal-foot {
  position: sticky;
  bottom: 0;
  margin-top: auto;
  background: var(--luu-surface);
  border-top: 1px solid var(--luu-border);
  border-bottom: 1px solid var(--luu-border);
  padding: .2rem 1rem 0;
  margin-bottom: -1px;
  z-index: 2;
}
.modal-actions {
  display: flex;
  gap: .5rem;
  padding: .6rem 0;
}
.modal-actions .save { margin-left: auto; }

/* Skeletons and placeholder bars for progressive loading */
@keyframes skeleton-shimmer {
  0% { opacity: .35; }
  50% { opacity: .75; }
  100% { opacity: .35; }
}
.skeleton-pulse {
  animation: skeleton-shimmer 1.5s ease-in-out infinite;
}
.skeleton-bar {
  display: inline-block;
  height: .8em;
  min-width: 3rem;
  border-radius: 4px;
  background: var(--luu-border, rgba(127, 127, 127, .2));
  vertical-align: middle;
  animation: skeleton-shimmer 1.5s ease-in-out infinite;
}
.skeleton-badge {
  display: inline-flex;
  align-items: center;
  font-size: 11px;
  padding: .15rem .45rem;
  border-radius: 4px;
  border: 1px solid var(--luu-border);
  color: var(--luu-fg-muted);
}
@media (prefers-reduced-motion: reduce) {
  .skeleton-pulse, .skeleton-bar { animation: none; opacity: .55; }
}

/* The file viewer's rows. Global rather than scoped to the viewer for a
   stricter reason than the palette below: `<style scoped>` rewrites
   `.code-rows li` to `.code-rows li[data-jq79="…"]`, and only elements jq79's
   compiler built carry that attribute. These rows are built by
   `web/rows.js`, because the rows are 93% of the viewer's cost and
   none of them ever changes — see
   `RECORD/2026-09-16.the-viewer-in-plain-js.completed.md`.

   **`.code-rows`, not `.code`**: a class that cannot be scoped has to be named
   as though it were global, because it is. `.code` was not — the git panel
   spells a two-letter status `<span class="code">` and the budget bar spells
   its code bucket the same way, and both were being given a file's list box:
   `list-style: none`, `counter-reset: line`, and `.5rem` of padding above and
   below a span the size of two characters. The scoping pass that put every
   other component's rules behind `[data-jq79]` could not reach this one, so it
   got the other half of the same fix. See
   RECORD/2026-09-16.styles-that-stay-in-their-component.completed.md. */
.code-rows { list-style: none; margin: 0; padding: .5rem 0; counter-reset: line; }
.code-rows li {
  display: flex;
  font-family: ui-monospace, monospace;
  /* Settings → Appearance → Editor → Text size (`prefs.js`). */
  font-size: var(--luu-editor-font, 12px);
  line-height: 1.5;
  white-space: pre;
}
.code-rows li::before {
  counter-increment: line;
  content: counter(line);
  flex: none;
  /* As many digits as the file's last line has (`--digits`, set by
     `views/editor/rows.js`), a margin on the left and the gap on the right. */
  box-sizing: content-box;
  width: calc(var(--digits, 3) * 1ch);
  padding: 0 .8rem 0 .6rem;
  text-align: right;
  color: var(--luu-fg-muted);
  opacity: .6;
  user-select: none;
}
.code-rows code { white-space: pre; }
/* Carries a row's own metrics, so `1lh` here is exactly one row. */
.pending { font-size: var(--luu-editor-font, 12px); line-height: 1.5; height: calc(var(--rows) * 1lh); }

/* One class per capture `crate::highlight` emits. Global rather than scoped to
   the viewer because the chunks are plain `<code>` elements and a future panel
   that shows code should colour it the same way. */
/* Code's ground and gutter, from the tone of where it is: the editor's column
   (on the column itself, which does not scroll — see `theme/default/
   components/scrollable.html` for why the ground goes there) and its rows. */
.col.content { background: var(--luu-code-ground); }
.code-rows { background: var(--luu-code-ground); color: var(--luu-code-ink); }
.code-rows li::before { color: var(--luu-code-gutter); opacity: 1; }
.hl-type { font-style: italic; }

.hl-keyword { color: var(--luu-hl-keyword); }
.hl-string { color: var(--luu-hl-string); }
.hl-comment { color: var(--luu-hl-comment); font-style: italic; }
.hl-number { color: var(--luu-hl-number); }
.hl-function { color: var(--luu-hl-function); }
.hl-type { color: var(--luu-hl-type); }
.hl-constant { color: var(--luu-hl-constant); }
.hl-variable { color: var(--luu-hl-variable); }
.hl-property { color: var(--luu-hl-property); }
.hl-operator { color: var(--luu-hl-operator); }
.hl-punctuation { color: var(--luu-hl-punctuation); }
.hl-attribute { color: var(--luu-hl-attribute); }
.hl-tag { color: var(--luu-hl-tag); }
.hl-constructor { color: var(--luu-hl-constructor); }
.hl-heading { color: var(--luu-hl-heading); font-weight: 600; }
.hl-literal { color: var(--luu-hl-literal); }
.hl-link { color: var(--luu-hl-link); text-decoration: underline; text-decoration-color: color-mix(in srgb, currentColor 40%, transparent); }
.hl-emphasis { font-style: italic; }
.hl-strong { font-weight: 600; }

/* A reply's prose, drawn from its Markdown by `helpers/markdown.js` and put in
   through `:html`, which the transcript's scoped style does not reach — so the
   rules are here, under a class named as the global it is. The chat's inks
   and lines, re-pointed by `.col.chat`, are what it draws with. What jq79's
   sanitiser does not keep — a table, a rule, struck text, a task's box — comes
   as `div`s and `span`s with an `md-` class, drawn as what they are here. */
.reply-md { white-space: normal; }
.reply-md > :first-child { margin-top: 0; }
.reply-md > :last-child { margin-bottom: 0; }
.reply-md p { margin: 0 0 .65em; }
.reply-md :is(h1, h2, h3, h4, h5, h6) { margin: 1em 0 .4em; font-size: 1em; font-weight: 600; line-height: 1.3; }
.reply-md h1 { font-size: 1.3em; }
.reply-md h2 { font-size: 1.18em; }
.reply-md h3 { font-size: 1.06em; }
.reply-md :is(ul, ol) { margin: 0 0 .65em; padding-left: 1.5em; }
.reply-md li { margin: .2em 0; }
.reply-md li > :is(ul, ol) { margin: .2em 0 0; }
.reply-md li > p { margin: 0 0 .4em; }
.reply-md li.md-task, .reply-md ol.md-counted > li { list-style: none; }
.reply-md li.md-task { margin-left: -1.2em; }
.reply-md ol.md-counted > li { margin-left: -1.5em; }
.reply-md .md-n { display: inline-block; min-width: 1.5em; }
.reply-md .md-box {
  display: inline-block;
  width: .85em;
  height: .85em;
  margin-right: .35em;
  border: 1px solid var(--luu-fg-muted);
  border-radius: 3px;
  vertical-align: -.1em;
}
.reply-md .md-box.md-on {
  border-color: var(--luu-accent);
  background: var(--luu-accent) url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>') center / 85% no-repeat;
}
.reply-md code {
  padding: .05em .35em;
  border: 1px solid color-mix(in srgb, var(--luu-border) 55%, transparent);
  border-radius: 4px;
  background: var(--luu-bg);
  font-family: var(--luu-font-mono);
  font-size: .88em;
  overflow-wrap: anywhere;
}
.reply-md blockquote {
  margin: 0 0 .65em;
  padding: .1em 0 .1em .8em;
  border-left: 3px solid var(--luu-border);
  color: var(--luu-fg-muted);
}
.reply-md .md-hr { margin: 1em 0; border-top: 1px solid var(--luu-border); }
.reply-md a { color: var(--luu-accent); text-underline-offset: 2px; }
.reply-md .md-del { color: var(--luu-fg-muted); text-decoration: line-through; }
/* Scrolled sideways on its own when it is wider than the column. */
.reply-md .md-table-box { max-width: 100%; overflow-x: auto; margin: 0 0 .65em; }
.reply-md .md-table { display: table; border-collapse: collapse; }
.reply-md .md-row { display: table-row; }
.reply-md .md-cell { display: table-cell; padding: .3em .6em; border: 1px solid var(--luu-border); text-align: left; vertical-align: top; }
.reply-md .md-head .md-cell { background: var(--luu-bg); font-weight: 600; }
.reply-md .md-cell.md-right { text-align: right; }
.reply-md .md-cell.md-center { text-align: center; }

/* While a column's edge is dragged (`resize.js`), the whole page is the
   handle: no text gets selected and the cursor does not flicker back over
   whatever the pointer crosses. */
html.resizing, html.resizing * { cursor: col-resize !important; user-select: none !important; }
html.resizing-rows, html.resizing-rows * { cursor: row-resize !important; user-select: none !important; }

/* ==========================================================================
   Design System (DS) - Foundations, Cards, Heroes, Badges & Visual Enhancers
   ========================================================================== */

/* Hero banner for settings sections and modal views */
.ds-hero {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  padding: 1.1rem 1.35rem;
  background: color-mix(in srgb, var(--luu-surface) 75%, var(--luu-bg));
  border: 1px solid var(--luu-border);
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .04);
}
.ds-hero-icon {
  flex: none;
  width: 52px;
  height: 52px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--luu-accent) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--luu-accent) 25%, transparent);
  color: var(--luu-accent);
  display: flex;
  align-items: center;
  justify-content: center;
}
.ds-hero-icon svg { width: 28px; height: 28px; }
.ds-hero-body { flex: 1; min-width: 0; }
.ds-hero-title {
  margin: 0 0 .25rem;
  font-size: 15px;
  font-weight: 600;
  color: var(--luu-fg);
  letter-spacing: -.01em;
}
.ds-hero-desc {
  margin: 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--luu-fg-muted);
}

/* Structured cards */
.ds-card {
  border: 1px solid var(--luu-border);
  border-radius: 8px;
  background: var(--luu-surface);
  padding: 1rem 1.15rem;
  margin: .75rem 0;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.ds-card:hover {
  border-color: color-mix(in srgb, var(--luu-border) 60%, var(--luu-accent));
}
.ds-card-head {
  display: flex;
  align-items: center;
  gap: .65rem;
  margin-bottom: .75rem;
  flex-wrap: wrap;
}
.ds-card-title {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--luu-fg);
  display: flex;
  align-items: center;
  gap: .4rem;
}
.ds-card-body {
  font-size: 12.5px;
  color: var(--luu-fg);
}

/* Card grid layouts */
.ds-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: .75rem;
  margin: .75rem 0;
}

/* Interactive preview cards (e.g. themes, layouts, modes) */
.ds-preview-card {
  border: 1.5px solid var(--luu-border);
  border-radius: 8px;
  background: var(--luu-bg);
  padding: .75rem .85rem;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: .5rem;
  transition: all .15s ease;
  user-select: none;
  text-align: left;
}
.ds-preview-card:hover {
  border-color: color-mix(in srgb, var(--luu-accent) 60%, var(--luu-border));
  transform: translateY(-1px);
}
.ds-preview-card.active {
  border-color: var(--luu-accent);
  background: color-mix(in srgb, var(--luu-accent) 7%, var(--luu-bg));
  box-shadow: 0 0 0 1px var(--luu-accent);
}
.ds-preview-thumb {
  height: 58px;
  border-radius: 5px;
  border: 1px solid var(--luu-border);
  overflow: hidden;
  display: flex;
  position: relative;
}
.ds-preview-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--luu-fg);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.ds-preview-caption {
  font-size: 11px;
  color: var(--luu-fg-muted);
  line-height: 1.35;
}

/* Badges and status pills */
.ds-badge {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .15rem .55rem;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 500;
  line-height: 1.3;
  border: 1px solid var(--luu-border);
  background: var(--luu-bg);
  color: var(--luu-fg-muted);
}
.ds-badge-ok {
  border-color: color-mix(in srgb, var(--luu-ok) 35%, transparent);
  color: var(--luu-ok);
  background: color-mix(in srgb, var(--luu-ok) 12%, transparent);
}
.ds-badge-warn {
  border-color: color-mix(in srgb, var(--luu-warn) 35%, transparent);
  color: var(--luu-warn);
  background: color-mix(in srgb, var(--luu-warn) 12%, transparent);
}
.ds-badge-bad {
  border-color: color-mix(in srgb, var(--luu-bad) 35%, transparent);
  color: var(--luu-bad);
  background: color-mix(in srgb, var(--luu-bad) 12%, transparent);
}
.ds-badge-accent {
  border-color: color-mix(in srgb, var(--luu-accent) 35%, transparent);
  color: var(--luu-accent);
  background: color-mix(in srgb, var(--luu-accent) 12%, transparent);
}
.ds-badge-purple {
  border-color: color-mix(in srgb, var(--luu-bucket-code) 35%, transparent);
  color: var(--luu-bucket-code);
  background: color-mix(in srgb, var(--luu-bucket-code) 12%, transparent);
}

/* Visual step workflows */
.ds-flow {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: .65rem;
}
.ds-flow-step {
  border: 1px solid var(--luu-border);
  border-radius: 7px;
  background: var(--luu-bg);
  padding: .7rem .85rem;
  display: flex;
  gap: .65rem;
  align-items: flex-start;
}
.ds-flow-num {
  width: 22px;
  height: 22px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--luu-accent) 18%, transparent);
  color: var(--luu-accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 700;
  flex: none;
}
.ds-flow-body { flex: 1; min-width: 0; }
.ds-flow-title { font-size: 12px; font-weight: 600; color: var(--luu-fg); margin-bottom: .2rem; }
.ds-flow-desc { font-size: 11px; color: var(--luu-fg-muted); line-height: 1.35; margin: 0; }

/* Friendly illustrations and diagrams */
.ds-illustration {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: .5rem;
}
.ds-diagram {
  border: 1px solid var(--luu-border);
  border-radius: 7px;
  background: var(--luu-bg);
  padding: .85rem 1rem;
  margin: .75rem 0;
  display: flex;
  align-items: center;
  justify-content: space-around;
  gap: .75rem;
  flex-wrap: wrap;
}
.ds-diagram-node {
  padding: .45rem .75rem;
  border-radius: 6px;
  border: 1px solid var(--luu-border);
  background: var(--luu-surface);
  font-size: 11.5px;
  font-weight: 500;
  color: var(--luu-fg);
  text-align: center;
}
.ds-diagram-arrow {
  color: var(--luu-fg-muted);
  display: flex;
  align-items: center;
  font-size: 14px;
}


/* A live session not on screen wants a look: a turn ended there, or (amber)
   it waits at its gate. See RECORD/2026-10-30.sessions-side-by-side.WIP.md §6. */
.chat .acts button.history { position: relative; }
.chat .acts button.history .behind {
  position: absolute;
  top: 3px;
  right: 3px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--luu-accent);
  box-shadow: 0 0 0 2px var(--luu-bg, transparent);
}
.chat .acts button.history .behind.gate { background: var(--luu-warn); }
