/* harner.dev — the design system, one sheet.
 *
 * Every page that has been converted links this file and nothing else. That is what makes
 * it safe to have bare element selectors here while `panel-ui.css` may not have any: a
 * page loading this one is not also loading `panel.css`, so there is nothing to fight and
 * nothing to leak.
 *
 * The register is a machine's, not a dashboard's. harner is not software you configure;
 * it is a thing you talk to that then goes and works. So:
 *
 *   - Color means something or it is not used. The accent appears twice on a screen at
 *     most — the focus ring and the one primary action. A status color appears only
 *     where there is a genuine status. Everything else is ink on paper.
 *   - Machine facts are monospace: paths, ids, tool names, commands, counts. Prose is
 *     sans. That contrast carries most of the product's voice, and it costs nothing.
 *   - Hairlines, not shadows. One elevation, used only for things that genuinely float.
 *   - No message bubbles. What you said is marked; what the bot said is prose. Two
 *     colored blobs facing each other is a chat app, and this is not one.
 *
 * Neutral ramp measured from Beautiful UI (beautifului.dev). Light on :root, dark on
 * [data-theme="dark"].
 */

/* ── the two faces ──────────────────────────────────────────────────────────
 * Geist and Geist Mono, self-hosted. See `vendor/fonts/LICENSE.md`.
 *
 * The sheet's own rule is that machine facts are monospace and prose is sans, and that
 * contrast carries most of the product's voice. Two unrelated families make that a
 * collision; a designed pair makes it one idea, which is the whole reason for choosing a
 * family that has a mono sibling drawn against it.
 *
 * Not Inter, which is what this shipped with and is the house font of every developer
 * tool made since 2018 — the "generic" half of what was wrong here. Not Anthropic's own
 * either: Styrene and Tiempos are licensed to them and are not distributed as web fonts,
 * so that was asked and answered before anything was drawn.
 *
 * `swap`, so the first paint is readable in the fallback rather than blank. The fallback
 * stacks are real stacks and not decoration: at 83 KB these load fast, and the one time
 * they will not is the time somebody most needs the page.
 */
@font-face {
  font-family: "Geist"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("/vendor/fonts/geist-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                 U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Geist"; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url("/vendor/fonts/geist-latin-ext.woff2") format("woff2");
  /* Turkish lives here — ş, ğ, ı, İ. Without this subset every one of them falls back to
     another typeface in the middle of a word, which is what the people using this write. */
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Geist Mono"; font-style: normal; font-weight: 400 600; font-display: swap;
  src: url("/vendor/fonts/geist-mono-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                 U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
                 U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Geist Mono"; font-style: normal; font-weight: 400 600; font-display: swap;
  src: url("/vendor/fonts/geist-mono-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ── tokens ─────────────────────────────────────────────────────────────── */
:root {
  --page:        #fff;
  --canvas:      #f6f6f5;
  --surface:     #fff;
  --inset:       #f6f6f5;
  --hover:       #eeeeeb;
  --hover-2:     #e8e8e5;
  --field:       #f6f6f5;
  --ink:         #292927;
  /* Normal-size secondary/status text must remain readable on hovered rows too. */
  --ink-2:       #686863;
  --ink-3:       #686863;
  --line:        #e8e8e5;
  --line-soft:   #f1f1ee;
  --accent:      oklch(62.6% .205 254.947);
  --accent-ink:  #0064c6;
  --on-accent:   oklch(100% 0 0);
  --green:       #3e7157;
  /* The terminal's own two, handed to xterm as its theme. Without them it renders in
   * xterm's built-in colours, which belong to no product. */
  --term-bg:     oklch(21% .008 258);
  --term-ink:    oklch(92% .004 258);
  --red:         #ba343a;
  /* Behind a dialog. It was `--ink` at 26%, which flips with the theme — so in the dark
   * one it was a pale wash that *lit* the page it was meant to push back. A scrim only
   * ever darkens; it is the same black in both, just deeper where the page is darker. */
  --scrim:       oklch(0% 0 0 / .26);

  --r:    7px;
  --r-sm: 5px;
  --pill: 9999px;

  --rail:    248px;
  --measure: 740px;

  --pop: 0 0 0 1px var(--line), 0 10px 30px oklch(0% 0 0/.10);
  /* Both faces named once. The sans was written into `body` and then again, by hand and
     as the old face, into the one rule that wants prose inside a monospace label — which
     is how a page ends up carrying two typefaces after a change that was supposed to
     carry one. A rule that wants the body face asks for it by name now. */
  --sans: "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
  --mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
}

:root[data-theme="dark"] {
  --page:        #202120;
  --canvas:      #191a19;
  --surface:     #202120;
  --inset:       #252624;
  --hover:       #30322e;
  --hover-2:     #383a35;
  --field:       #252624;
  --ink:         #e8e8e3;
  --ink-2:       #a1a39a;
  --ink-3:       #a1a39a;
  --line:        #363833;
  --line-soft:   #2c2e29;
  --accent:      oklch(68% .173 253.301);
  --accent-ink:  oklch(78.8% .113 248.33);
  --on-accent:   oklch(15% .01 264.4);
  --green:       #8abe9b;
  /* The same two, and deliberately not lightened. A terminal is dark in both themes —
   * every other one a person uses is — and a pale shell inside a dark page would be the
   * one window on the screen that flares. */
  --term-bg:     oklch(21% .008 258);
  --term-ink:    oklch(92% .004 258);
  --red:         #fa7d81;
  --scrim:       oklch(0% 0 0 / .5);

  --pop: 0 0 0 1px var(--line), 0 10px 30px oklch(0% 0 0/.4);
}

/* Changing theme repaints every colour on the page at once. `panel.js` marks the root
   for exactly one frame so that repaint is not eighty simultaneous transitions. */
:root.swapping, :root.swapping * { transition: none !important; }

/* ── base ───────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; }

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 { margin: 0; font-weight: 550; letter-spacing: -.012em; }
h1 { font-size: 24px; }
h2 { font-size: 17px; }
h3 { font-size: 15px; }
p  { margin: 0; }
a  { color: inherit; text-decoration: none; }

:focus-visible { outline: 1.5px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

.ico { width: 16px; height: 16px; flex: none; display: block; }

.mute  { color: var(--ink-2); }
.faint { color: var(--ink-3); }

/* A sentence gets the width it is given. The measure used to be capped here, which meant
 * a description under a heading stopped two thirds of the way across and dropped its tail
 * onto a second line while the rest of the row sat empty — and a line that breaks with
 * room to spare reads as a mistake, whatever it does for the eye's return sweep. Where a
 * narrow measure is wanted it is the column that is narrow, not the paragraph inside it. */
.grow  { flex: 1; }
.vh    { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }

/* Machine facts wear monospace. A path, an id, a command and a count are things the
 * system knows; prose is what somebody said about them. Keeping the two apart is most
 * of this design's voice, and it costs one class. */
.m, .mono, code, kbd { font-family: var(--mono); font-size: .92em; }
.num { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* ── app shell ──────────────────────────────────────────────────────────── */
/* The window, and what is left of it.
 *
 * `.app` and `.op` are fill-the-window shells: everything inside them scrolls in its own
 * column and the page itself must not. That was written as `100dvh` minus a guess at the
 * notice strip's height — a number that has to be exactly right, and was not.
 *
 * The body is the window and the shell is a flex child of it, so a strip of any height
 * costs the shell precisely its own height and nothing has to know what that is. `.mock`
 * is `position: fixed` and takes no room in the flow, so `body.framed` keeps its 34px as
 * padding rather than as arithmetic. `body.public` is excluded: there the shell is
 * `display: block` and the page is meant to scroll as one long document. */
body:not(.public):has(> .app), body:has(> .op) {
  display: flex; flex-direction: column;
  height: 100dvh; overflow: hidden;
}
body:not(.public):has(> .app) > .app,
body:has(> .op) > .op { flex: 1; min-height: 0; }
body.framed:not(.public):has(> .app) { padding-bottom: 34px; }
.app { display: flex; overflow: hidden; }

.rail {
  width: var(--rail); flex: none;
  display: flex; flex-direction: column; min-height: 0;
  background: var(--canvas);
  border-right: 1px solid var(--line);
}

.rail-top { padding: 11px 10px 2px; flex: none; }

.brand {
  display: flex; align-items: center; gap: 8px;
  padding: 2px 4px 4px;
  font-size: 15px; font-weight: 550; letter-spacing: -.01em;
}
/* `.brand-mark` — the h in a rounded box — was here. It is gone rather than kept "in case":
   the wordmark replaces both it and the word beside it, and a rule nothing selects is a
   rule the next reader has to disprove. See `logo.js`. */

.rail-scroll { flex: 1; min-height: 0; overflow-y: auto; padding: 0 6px 8px; }

.rail-label {
  display: flex; align-items: center; gap: 6px;
  padding: 13px 8px 4px;
  font-family: var(--mono); font-size: 11.5px;
  letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-3);
}
.rail-label .all { margin-left: auto; font-family: var(--sans); letter-spacing: 0;
                   text-transform: none; font-size: 13px; }
.rail-label .all:hover { color: var(--ink); cursor: pointer; }

/* An empty section still says what it is for. A Pinned heading with nothing under it
 * and no explanation is a heading nobody acts on. */
.rail-empty { padding: 2px 9px 6px; font-size: 13px; color: var(--ink-3); }

/* A bot in the rail: enough to answer "what is it and is it working" without opening it. */
.bot-row {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 8px; border-radius: var(--r-sm);
  color: var(--ink-2);
}
/* The bot spans both lines of the row — the name and the line under it — rather than
 * sitting on the first and leaving the second unanchored. Centred, so it stays put if
 * a preview ever wraps. */
.bot-row .bot { width: 36px; height: 36px; }
.bot-row:hover { background: var(--hover); }
.bot-row[aria-current="page"] { background: var(--hover-2); }
.bot-body { flex: 1; min-width: 0; }
.bot-top { display: flex; align-items: baseline; justify-content: space-between; gap: 6px; }
.bot-top b { color: var(--ink); font-weight: 500; }
.bot-top .when { flex: none; font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); }
.bot-said { display: block; font-size: 14px; color: var(--ink-2);
            white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The dot belongs on the name's line, beside the time, not in the row's middle. */
.bot-row .dot { align-self: flex-start; margin-top: 6px; }

.fold summary { list-style: none; cursor: pointer; }
.fold summary::-webkit-details-marker { display: none; }
.fold summary .ico { margin-left: auto; }
.fold[open] summary .ico { transform: rotate(180deg); }

.nav-item {
  display: flex; align-items: center; gap: 9px;
  width: 100%; padding: 6px 8px;
  border: 0; border-radius: var(--r-sm);
  background: none; color: var(--ink-2);
  font: inherit; text-align: left; cursor: pointer;
}
.nav-item:hover { background: var(--hover); color: var(--ink); }
.nav-item[aria-current="page"] { background: var(--hover-2); color: var(--ink); }
.nav-item .ico { color: var(--ink-3); }
.nav-item .tail { margin-left: auto; font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); }

.rail-foot { flex: none; border-top: 1px solid var(--line); padding: 6px; position: relative; }

/* The workspace switcher, in the rail header and outside the account menu.
 *
 * A workspace name is a person's own text and may be sixty characters of it, so it is
 * the part that gives way: without `min-width: 0` a flex child refuses to shrink below
 * its content and the chevron is pushed out of the rail entirely, on the one control
 * that says which tenant you are looking at. */
.nav-item.ws .ws-name {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--ink);
}
.nav-item.ws .chev-sm { flex: none; }
/* Which of them you are in. The rows are otherwise a column of names that look alike,
 * and the current one is the question the menu is opened to answer. */
#ws-menu .on { font-weight: 550; }

/* Shut, the rail is gone rather than narrowed. A strip of icons would keep a column
 * of chrome on screen to save two clicks a day; the point of shutting it is the room. */
.app.rail-shut .rail { display: none; }

.railtoggle {
  width: 28px; height: 28px; flex: none;
  margin-left: -4px;
  border: 1px solid transparent; border-radius: var(--r-sm);
  background: none; color: var(--ink-3); cursor: pointer;
  display: grid; place-items: center;
}
.railtoggle:hover { background: var(--hover); color: var(--ink); }

/* ── main column ────────────────────────────────────────────────────────── */
.main { flex: 1; min-width: 0; display: flex; flex-direction: column; background: var(--page); }

.topbar {
  flex: none; height: 50px;
  display: flex; align-items: center; gap: 9px;
  padding: 0 12px;
  border-bottom: 1px solid var(--line);
}
.topbar h1 { font-size: 16px; font-weight: 550; }

/* Scrolling belongs to the body region; ordinary pages receive a bounded reading
 * area in the approved workspace layout below. Conversation tools size separately. */
.body { flex: 1; min-height: 0; overflow-y: auto; }
.pad  { padding: 20px 22px 44px; }
.hold { max-width: none; }

/* The slot for a second column. Nothing uses it yet; it exists so that adding one to
 * a screen later is a class on the page, not a rebuild of the shell. */
.withside { display: flex; min-height: 0; flex: 1; }
.withside > .body { flex: 1; min-width: 0; }
.side {
  width: 320px; flex: none;
  display: flex; flex-direction: column; min-height: 0; overflow-y: auto;
  border-left: 1px solid var(--line);
  background: var(--canvas);
}
.side .head {
  flex: none; height: 42px; display: flex; align-items: center; gap: 8px;
  padding: 0 13px; border-bottom: 1px solid var(--line);
  font-family: var(--mono); font-size: 11px;
  letter-spacing: .04em; text-transform: uppercase; color: var(--ink-3);
}
.side .in { padding: 13px; display: flex; flex-direction: column; gap: 10px; }
[hidden].side { display: none; }

.sec { margin-bottom: 26px; }
.sec-head { display: flex; align-items: baseline; gap: 9px; margin-bottom: 9px; }

/* ── buttons ────────────────────────────────────────────────────────────────
 * Flat, hairlined, and quiet. Exactly one button on a screen may be `primary`; the
 * accent variant is for the single action a page exists to perform, and most pages
 * have none. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  height: 32px; padding: 0 12px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink);
  font: inherit; cursor: pointer; white-space: nowrap;
}
.btn:hover { background: var(--hover); }
.btn .ico { width: 14px; height: 14px; }
.btn.primary { background: var(--ink); color: var(--page); border-color: var(--ink); }
.btn.primary:hover { opacity: .88; }
.btn.accent { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.btn.ghost { background: none; border-color: transparent; color: var(--ink-2); }
.btn.ghost:hover { background: var(--hover); color: var(--ink); }
.btn.danger { color: var(--red); }
.btn.sm { height: 27px; padding: 0 10px; font-size: 13.5px; }
.btn.icon { width: 32px; padding: 0; }
.btn.icon.sm { width: 27px; }

/* ── fields ─────────────────────────────────────────────────────────────── */
.field {
  display: flex; align-items: center; gap: 8px;
  height: 34px; padding: 0 11px;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  color: var(--ink-3);
}
.field input, .field textarea, .field select {
  flex: 1; min-width: 0; border: 0; outline: 0; background: none;
  color: var(--ink); font: inherit;
}
/* Without this a native select keeps its own chrome and sits inside the hairline looking
   like a control from another program. */
.field select { cursor: pointer; }
.field .ico { width: 14px; height: 14px; }
.kbd { font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); }

/* ── surfaces ───────────────────────────────────────────────────────────── */
.card {
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r);
  padding: 13px;
}
.card.flat { background: var(--inset); border-color: transparent; }

.grid { display: grid; gap: 9px; }
/* Readings want more air between them than boxes did: the box was doing the separating,
   and nine pixels between two unboxed figures reads as one figure with a wide label. */
.grid:has(> .card.stat) { gap: 9px 26px; }
.g2 { grid-template-columns: repeat(2, minmax(0,1fr)); }
.g3 { grid-template-columns: repeat(3, minmax(0,1fr)); }
.g4 { grid-template-columns: repeat(4, minmax(0,1fr)); }

/* A chip is a label, not a highlight. It carries color only when the thing it names
 * is genuinely in that state, and then only as ink — never as a filled tint. */
.chip {
  display: inline-flex; align-items: center; gap: 5px;
  height: 23px; padding: 0 8px;
  /* Declared, because a chip is often a <button> and a button with no background of its
     own keeps the browser's `buttonface`. In the dark theme that grey happens to pass;
     in the light one it is grey text on a grey slab, which is how the filters above the
     bot list became unreadable. */
  background: none;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  font-family: var(--mono); font-size: 12px;
  color: var(--ink-2); white-space: nowrap;
}
button.chip { cursor: pointer; }
/* `:not(.on)`, because the hover is a hint that a chip can be pressed and a chosen one
   has been. Without it the pointer's own rule out-specified `.chip.on` — (0,2,1) against
   (0,2,0) — and repainted the label in `--ink` on `--ink`'s background: the chip you had
   just clicked went blank under your cursor and came back when you moved away. True of
   every chip on the site, the bot list's filters included. */
button.chip:not(.on):hover { border-color: var(--ink-3); color: var(--ink); }
/* A filter that would show nothing is still worth seeing — you may be checking that it
   is empty — but it should not compete with the ones that have something behind them. */
.chip.none { color: var(--ink-3); border-color: var(--line-soft); }
.chip.none:hover { color: var(--ink-2); }
.chip.on   { background: var(--ink); border-color: var(--ink); color: var(--page); }
.chip.ok   { color: var(--green); }
.chip.bad  { color: var(--red); }
.chip.plain { border-color: transparent; background: var(--inset); }

/* The four states. Color here is a real signal, so it stays: busy and idle are alive,
 * asleep is not, and never-observed is a claim nobody has made — a ring, not a fill. */
.dot { width: 6px; height: 6px; border-radius: 50%; flex: none; }
.dot.busy   { background: var(--green); animation: pulse 1.5s ease-in-out infinite; }
.dot.idle   { background: var(--green); }
.dot.asleep { background: var(--ink-3); }
.dot.unseen { background: transparent; box-shadow: inset 0 0 0 1.5px var(--ink-3); }
/* In transit. A bot takes about eight seconds to wake, and for those eight seconds it is
   neither of the two states the rail otherwise has a colour for. */
.dot.waking, .dot.parking {
  background: var(--ink-3); animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: .28 } }
@media (prefers-reduced-motion: reduce) { .dot.busy { animation: none } }

/* One tone for every bot. Six colors told them apart at the cost of making the rail
 * the brightest thing on screen; the letter tells them apart for free. */
.avatar {
  width: 22px; height: 22px; flex: none;
  border-radius: 5px;
  background: var(--field); color: var(--ink-2);
  display: grid; place-items: center;
  font-family: var(--mono); font-size: 11px;
}
.avatar.lg { width: 30px; height: 30px; font-size: 13px; border-radius: 6px; }
.avatar.sm { width: 17px; height: 17px; font-size: 9.5px; border-radius: 4px; }
.avatar.you { background: var(--ink); color: var(--page); }

/* ── bots, and how they move ────────────────────────────────────────────────
 *
 * Drawn on nothing rather than inside a tile: a creature reads as itself, and a colored
 * plate behind it would put a second block of color on a screen that is otherwise ink on
 * paper. The bots are the only color in the product.
 *
 * A state is a behavior, not a badge. Working reaches for its own instrument, thinking
 * looks up and away, waiting yawns at you now and then, asleep turns over in its own
 * time, and a bot nobody has ever run is only an outline. You should be able to tell them apart from across the room with
 * the labels covered up.
 *
 * Two kinds of motion live here. The loops below run forever, each bot offset by its own
 * --phase so a rail of five never breathes on the same frame. The rest — blink, yawn and
 * roll — arrive as classes from creature.js at times nobody can predict, which is the
 * whole difference between an animal and a metronome.
 *
 * All of it is off under prefers-reduced-motion, where the states fall back to the dot
 * beside the name, which never depended on motion in the first place.
 */
.bot { display: block; width: 36px; height: 36px; flex: none; overflow: visible; }
.bot.sm { width: 22px; height: 22px; }
.bot.md { width: 28px; height: 28px; }
.bot.lg { width: 48px; height: 48px; }
.bot.xl { width: 76px; height: 76px; }
/* The bot while it is learning something. It is the only thing on the screen for as long
   as it is up, and at any of the sizes above it reads as a spinner with a face rather
   than as the bot doing the work. */
.bot.xxl { width: 132px; height: 132px; }

/* The drawing carries a hue and nothing else; the lightness that reads on this page is
   the theme's business. A bot drawn before someone switched to dark follows them into it
   without being redrawn. */
.bot { --bot: oklch(60% .15 var(--bot-h, 212)); }
:root[data-theme="dark"] .bot { --bot: oklch(72% .13 var(--bot-h, 212)); }
/* Anything that has to read ON the body — glasses, a spark, a crease — takes the body's
   own color pushed toward black, so it works on all eight hues without a second palette.
   Limbs take a lighter push than that, but not so light that they vanish: an arm lying
   across the belly has to separate from it while still obviously belonging to it. */
.bot { --bot-deep: color-mix(in oklab, var(--bot) 58%, #000); }
.bot { --bot-shade: color-mix(in oklab, var(--bot) 71%, #000); }

.bot .body  { fill: var(--bot); }
.bot .mark  { fill: var(--bot); stroke: var(--bot); }
.bot .ball  { fill: #ffffff; }
.bot .pupil { fill: #1b1d22; }
.bot .lid   { stroke: #1b1d22; opacity: 0; }
.bot .mouth { fill: #1b1d22; opacity: 0; }
.bot .grin,
.bot .frown { fill: none; stroke: #1b1d22; stroke-width: 3.6; stroke-linecap: round;
              opacity: 0; }

/* Limbs, desk and props are all in the markup from the start and all invisible. A state
   reveals what it needs; nothing is ever rebuilt to change what a bot is doing. */
.bot .limbs, .bot .desk, .bot .gear, .bot .side,
.bot .feet, .bot .reach, .bot .hold { opacity: 0; }
.bot .feet ellipse { fill: var(--bot-shade); }
.bot .arm   { fill: var(--bot-shade); }
.bot .joint { fill: var(--bot-shade); }
.bot .hand  { fill: var(--bot-shade); }
.bot .board { fill: var(--bot-deep); }
.bot .keys  { stroke: #fff; stroke-width: 1.8; stroke-linecap: round; opacity: .55; }
.bot .ponder circle { fill: var(--bot); }
.bot .specs, .bot .monocle { fill: none; stroke: var(--bot-deep); stroke-width: 3; }
.bot .monocle path { stroke-width: 2; }
.bot .beads circle  { fill: var(--bot); }
.bot .spark  { stroke: #fff; }
.bot .facets { fill: none; stroke: var(--bot-deep); stroke-width: 3.4; stroke-linecap: round; }
.bot .radar .wave { fill: none; stroke: var(--bot); stroke-width: 3.6; stroke-linecap: round; }
.bot .stream text { font: 700 13px var(--mono); fill: var(--bot); text-anchor: middle; }

.bot .whole, .bot .side {
  transform-box: view-box; transform-origin: 50% 50%;
}
.bot .mass, .bot .eyes, .bot .eye, .bot .pupil, .bot .mark, .bot .mouth,
.bot .hand, .bot .ponder circle, .bot .chinarm,
.bot .ring, .bot .strand, .bot .mote, .bot .tip {
  transform-box: fill-box; transform-origin: center;
}
.bot .mouth { transform-origin: center top; }

/* Every loop starts wherever this particular bot's --phase puts it. */
.bot .mass, .bot .pupil, .bot .eyes, .bot .hand, .bot .ponder circle, .bot .chinarm,
.bot .ring, .bot .strand, .bot .mote, .bot .tip, .bot .radar .wave, .bot .stream text,
.bot .beads circle, .bot .facets path, .bot .spark {
  animation-delay: var(--phase, 0s);
}

/* ── idle: awake and nothing to do. It breathes. The blink is scheduled. ── */
@keyframes bot-breathe { 0%, 100% { transform: scale(1, 1) }
                         50%      { transform: scale(1.025, .982) } }
.bot[data-state="idle"] .mass { animation: bot-breathe 3.6s ease-in-out infinite; }

/* ── thinking: it looks up and away, and says so above its head ──
   The eyes and the dots go to the same side, decided per bot by --look, because a
   creature glancing left while its thoughts pile up on the right looks distracted
   rather than thoughtful. */
@keyframes bot-glance {
  0%, 100% { transform: translate(calc(var(--look, 1) * 3.6px), -3.8px) }
  34%      { transform: translate(calc(var(--look, 1) * 4.4px), -4.4px) }
  68%      { transform: translate(calc(var(--look, 1) * 2.2px), -2.6px) }
}
@keyframes bot-hum { 0%, 100% { transform: scale(1) } 50% { transform: scale(1.014) } }
@keyframes bot-pop { 0%, 100% { opacity: .3; transform: scale(.65) }
                     40%      { opacity: 1; transform: scale(1.08) } }
@keyframes bot-chin { 0%, 100% { transform: translateY(0) }
                      50%      { transform: translateY(-1.8px) } }
.bot[data-state="thinking"] .pupil { animation: bot-glance 2.9s ease-in-out infinite; }
.bot[data-state="thinking"] .mass  { animation: bot-hum 2.9s ease-in-out infinite; }
.bot[data-state="thinking"] .side  { opacity: 1; transform: scaleX(var(--look, 1)); }
/* Peaks, a ring and a wide crown all reach into where the first dot lands. Lift the trail
   rather than move it sideways: there is room above every head and none to the right. */
.bot[data-form="crest"]   .ponder { transform: translateY(-10px); }
.bot[data-form="halo"]    .ponder { transform: translateY(-13px); }
.bot[data-form="tendril"] .ponder { transform: translateY(-9px); }
.bot[data-form="split"]   .ponder { transform: translateY(-8px); }
.bot[data-form="shard"]   .ponder { transform: translateY(-7px); }
.bot[data-state="thinking"] .ponder circle { animation: bot-pop 1.5s ease-in-out infinite; }
.bot[data-state="thinking"] .p2 { animation-delay: calc(var(--phase, 0s) + .18s); }
.bot[data-state="thinking"] .p3 { animation-delay: calc(var(--phase, 0s) + .36s); }
@keyframes bot-fade { 0%, 100% { opacity: 0; transform: scale(.4) }
                      26%, 62% { opacity: 1; transform: scale(1) } }
.bot[data-form="orb"][data-state="thinking"] .ponder circle {
  animation: bot-fade 2.2s ease-in-out infinite;
}
.bot[data-form="orb"][data-state="thinking"] .p2 {
  animation-delay: calc(var(--phase, 0s) + .55s);
}
.bot[data-state="thinking"] .chinarm { animation: bot-chin 2.9s ease-in-out infinite; }

/* ── working: hands out, eyes down on the keys, and each form's own instrument ── */
@keyframes bot-type { 0%, 100% { transform: translateY(0) scale(1) }
                      50%      { transform: translateY(-1.4px) scale(1.012, .992) } }
@keyframes bot-keys { 0%, 100% { transform: translateY(0) }
                      50%      { transform: translateY(-3.6px) } }
@keyframes bot-down { 0%, 100% { transform: translate(-1.2px, 2.6px) }
                      50%      { transform: translate(1.2px, 3.2px) } }
.bot[data-state="working"] .mass  { animation: bot-type .62s ease-in-out infinite; }
.bot[data-state="working"] .pupil { animation: bot-down .78s ease-in-out infinite; }
.bot[data-state="working"] .limbs,
.bot[data-state="working"] .desk,
.bot[data-state="working"] .gear  { opacity: 1; }
.bot[data-state="working"] .hand  { animation: bot-keys .32s ease-in-out infinite; }
.bot[data-state="working"] .h2    { animation-delay: calc(var(--phase, 0s) + .16s); }

/* what each form reaches for. None of these is a generic prop: the one with three eyes
   gets three lenses, the one with one gets a monocle, and the one with an antenna does
   not type at all — it listens, and the numbers come up its side. */
@keyframes bot-wave  { 0% { opacity: 0 } 22% { opacity: .9 } 70%, 100% { opacity: 0 } }
@keyframes bot-drip  { 0%       { opacity: 0; transform: translateY(7px) }
                       28%, 58% { opacity: .8 }
                       100%     { opacity: 0; transform: translateY(-15px) } }
@keyframes bot-bead  { 0%, 100% { opacity: 0; transform: scale(.5) }
                       20%, 55% { opacity: 1; transform: scale(1) } }
@keyframes bot-zap   { 0%, 100% { opacity: 0 } 6% { opacity: 1 } 18% { opacity: 0 }
                       32%      { opacity: .85 } 44% { opacity: 0 } }
@keyframes bot-crease { 0%, 100% { opacity: .15 } 45% { opacity: .85 } }
@keyframes bot-ringspin { 0%, 100% { transform: scaleX(1) translateY(0) }
                          50%      { transform: scaleX(.5) translateY(2px) } }
@keyframes bot-tip   { 0%, 100% { transform: scale(1); opacity: 1 }
                       50%      { transform: scale(1.5); opacity: .55 } }
@keyframes bot-sway  { 0%, 100% { transform: rotate(-9deg) } 50% { transform: rotate(9deg) } }
@keyframes bot-orbit { 0%   { transform: rotate(0deg) translateX(3px) rotate(0deg) }
                       100% { transform: rotate(360deg) translateX(3px) rotate(-360deg) } }
.bot[data-state="working"] .radar .wave { animation: bot-wave 1.4s linear infinite; }
.bot[data-state="working"] .w2 { animation-delay: calc(var(--phase, 0s) + .26s); }
.bot[data-state="working"] .w3 { animation-delay: calc(var(--phase, 0s) + .52s); }
.bot[data-state="working"] .stream text { animation: bot-drip 2s linear infinite; }
.bot[data-state="working"] .g2 { animation-delay: calc(var(--phase, 0s) + .66s); }
.bot[data-state="working"] .g3 { animation-delay: calc(var(--phase, 0s) + 1.33s); }
.bot[data-state="working"] .beads circle { animation: bot-bead 2.1s ease-in-out infinite; }
.bot[data-state="working"] .b2 { animation-delay: calc(var(--phase, 0s) + .7s); }
.bot[data-state="working"] .b3 { animation-delay: calc(var(--phase, 0s) + 1.4s); }
.bot[data-state="working"] .spark { animation: bot-zap 1.1s linear infinite; }
.bot[data-state="working"] .facets path { animation: bot-crease 1.5s ease-in-out infinite; }
.bot[data-state="working"] .f2 { animation-delay: calc(var(--phase, 0s) + .5s); }
.bot[data-state="working"] .f3 { animation-delay: calc(var(--phase, 0s) + 1s); }
.bot[data-state="working"] .ring   { animation: bot-ringspin .8s ease-in-out infinite; }
.bot[data-state="working"] .tip    { animation: bot-tip 1.05s ease-in-out infinite; }
.bot[data-state="working"] .strand { animation: bot-sway .9s ease-in-out infinite;
                                     transform-origin: top center; }
.bot[data-state="working"] .strand.two { animation-delay: calc(var(--phase, 0s) + .14s); }
.bot[data-state="working"] .mote   { animation: bot-orbit 2.6s linear infinite; }
.bot[data-state="thinking"] .tip   { animation: bot-tip 2.9s ease-in-out infinite; }
.bot[data-state="thinking"] .mote  { animation: bot-orbit 5.5s linear infinite; }

/* ── waiting on you: it bobs, its eyes are wide, and every so often it yawns ── */
@keyframes bot-bob { 0%, 100% { transform: translateY(0) } 50% { transform: translateY(-3px) } }
.bot[data-state="waiting"] .mass,
.bot[data-state="waiting"] .eyes  { animation: bot-bob 1.6s ease-in-out infinite; }
.bot[data-state="waiting"] .pupil { transform: scale(1.3); }

/* ── asleep: lids down, a long slow rise and fall, and now and then it turns over ── */
@keyframes bot-sleep { 0%, 100% { transform: scale(1, 1) translateY(0) }
                       50%      { transform: scale(1.035, .965) translateY(1.5px) } }
.bot[data-state="asleep"] .ball,
.bot[data-state="asleep"] .pupil { opacity: 0; }
.bot[data-state="asleep"] .lid   { opacity: 1; }
.bot[data-state="asleep"] .body  { opacity: .5; }
.bot[data-state="asleep"] .mark  { opacity: .4; }
.bot[data-state="asleep"] .mass  { animation: bot-sleep 7.4s ease-in-out infinite; }
/* Holding a ring in the air is work. Asleep, halo stops doing it: the ring comes down
   onto its head and settles there crooked. It still rides the breathing, because it is
   resting on the thing that breathes. */
.bot[data-form="halo"][data-state="asleep"] .ring {
  transform: translateY(21px) rotate(-13deg);
}
/* Same idea, one form over: an antenna held bolt upright is a thing being held upright.
   Asleep it leans, from its base rather than its middle, the way a head does. */
.bot[data-form="spire"] .antenna {
  transform-box: view-box; transform-origin: 52px 15px;
}
.bot[data-form="spire"][data-state="asleep"] .antenna { transform: rotate(42deg); }

/* ── never run: an outline, and completely still. Nobody has observed this one, so the
      drawing does not claim anything about it. ── */
.bot[data-state="unseen"] .body {
  fill: none; stroke: var(--bot); stroke-width: 3; stroke-dasharray: 6 6; opacity: .65;
}
.bot[data-state="unseen"] .mark  { opacity: .35; }
.bot[data-state="unseen"] .ball  { fill: none; stroke: var(--bot); stroke-width: 2.2; opacity: .65; }
.bot[data-state="unseen"] .pupil { opacity: 0; }

/* ── the scheduled moves ──
   These are classes, not states: creature.js puts one on for a second or two at a moment
   nobody can predict, then takes it off. */
@keyframes bot-blink  { 0%, 100% { transform: scaleY(1) } 45%, 58% { transform: scaleY(.06) } }
.bot.blink .eye { animation: bot-blink .3s ease-in-out; }
.bot.blink .eye:nth-of-type(2) { animation-delay: .04s; }

@keyframes bot-yawn   { 0%, 100% { transform: scaleY(.06); opacity: 0 }
                        34%, 60% { transform: scaleY(1); opacity: 1 } }
@keyframes bot-squint { 0%, 100% { transform: scaleY(1) } 36%, 58% { transform: scaleY(.28) } }
@keyframes bot-stretch { 0%, 100% { transform: scale(1, 1) }
                         38%, 56% { transform: scale(.97, 1.06) } }
.bot.yawn .mouth { opacity: 1; animation: bot-yawn 4.4s ease-in-out; }
.bot.yawn .eye   { animation: bot-squint 4.4s ease-in-out; }
.bot.yawn .mass  { animation: bot-stretch 4.4s ease-in-out; }

@keyframes bot-roll { 0%, 100% { transform: rotate(0deg) }
                      30%, 68% { transform: rotate(calc(var(--roll, 1) * 9deg)) } }
.bot.roll .whole { animation: bot-roll 4.6s ease-in-out; }

/* ── out of the chair ──
   A bot that gets up leaves its desk behind, grows feet, and can reach the one standing
   next to it. Nothing here belongs to a state: these are things a bot does, put on and
   taken off by whatever is directing the scene. */
@keyframes bot-step { 0%, 100% { transform: translateY(0) } 50% { transform: translateY(-4px) } }
.bot .feet       { transition: opacity .5s ease; }
.bot.walk .feet  { opacity: 1; }
.bot.walk .desk,
.bot.walk .limbs { opacity: 0; }
.bot.walk .feet ellipse {
  animation: bot-step .42s ease-in-out infinite;
  transform-box: fill-box; transform-origin: center;
}
.bot.walk .feet ellipse:nth-of-type(2) { animation-delay: .21s; }

/* A jab, twice, because one tap is a touch and two is getting someone's attention. */
@keyframes bot-jab { 0%, 100% { transform: translateX(-16px); opacity: 0 }
                     16%, 84% { opacity: 1 }
                     34%      { transform: translateX(5px) }
                     52%      { transform: translateX(-5px) }
                     70%      { transform: translateX(5px) } }
.bot.poke .reach { opacity: 1; animation: bot-jab 1.4s ease-in-out; }

/* A shove is one push, not a conversation. Out, contact, gone — and shorter than a jab,
   because something is about to move because of it and the two have to agree on when. */
@keyframes bot-shove { 0%   { transform: translateX(-18px); opacity: 0 }
                       26%  { opacity: 1 }
                       55%  { transform: translateX(7px); opacity: 1 }
                       78%  { transform: translateX(4px); opacity: 1 }
                       100% { transform: translateX(-18px); opacity: 0 } }
.bot.shove .reach { opacity: 1; animation: bot-shove .8s ease-out; }

/* A bot with both hands on a keyboard does not grow a third one. Putting the work down
   takes a moment, and the moment is the point: you can see it decide to get up. */
.bot .desk, .bot .limbs { transition: opacity .6s ease; }
.bot.stow .desk, .bot.stow .limbs { opacity: 0; }

/* No. Side to side, never a tilt: a head that leans is shrugging, and a head that turns
   is refusing. With no neck to turn on, the whole body does it. */
@keyframes bot-nope { 0%, 100% { transform: translateX(0) }
                      13%      { transform: translateX(-8px) }
                      32%      { transform: translateX(8px) }
                      51%      { transform: translateX(-7px) }
                      70%      { transform: translateX(6px) }
                      87%      { transform: translateX(-3px) } }
.bot.nope .whole { animation: bot-nope 1.6s ease-in-out; }

/* Eyes that go somewhere, and eyes that have just had a fright. Both beat the state's own
   pupil animation only when the state has been dropped first — which is the honest way
   round: a bot being interrupted is no longer doing what it was doing. */
.bot.peer-r .pupil { transform: translateX(5px) scale(1.12); }
.bot.peer-l .pupil { transform: translateX(-5px) scale(1.12); }
.bot.startled .pupil { transform: scale(1.5); }

/* The universal one: a hand up, waving off whoever just interrupted. It pivots at the
   shoulder, so it reads as an arm rather than a flag on a stick, and the outer group
   mirrors the whole thing when the interruption came from the other side. */
@keyframes bot-holdon { 0%, 100% { transform: rotate(52deg); opacity: 0 }
                        14%      { transform: rotate(0deg); opacity: 1 }
                        34%      { transform: rotate(-16deg); opacity: 1 }
                        54%      { transform: rotate(16deg); opacity: 1 }
                        74%      { transform: rotate(-11deg); opacity: 1 }
                        90%      { transform: rotate(0deg); opacity: 1 } }

.bot .hold    { transform-box: view-box; transform-origin: 50px 50px; }
.bot .holdarm { transform-box: view-box; transform-origin: 78px 60px; }
.bot.holdon .hold    { opacity: 1; }
.bot.holdon .holdarm { animation: bot-holdon 2.4s ease-in-out; }
.bot.holdon.lefty .hold { transform: scaleX(-1); }

/* ── saying hello ──
   Not a state: a bot cannot be hired for being pleased to see you. It is a layer that
   goes over whatever the bot is already doing, which is also why the bob rides on .whole
   and leaves the state's own rhythm running underneath it — nothing has to be swapped
   out, so nothing snaps on the way in or the way back. */
@keyframes bot-hello { 0%, 100% { transform: translateY(0) rotate(0deg) }
                       50%      { transform: translateY(-3px) rotate(1.5deg) } }
@keyframes bot-wave  { 0%, 100% { transform: rotate(-17deg) }
                       50%      { transform: rotate(19deg) } }
.bot .grin, .bot .frown, .bot .hold { transition: opacity .35s ease; }

/* ── and the other thing ──
   Shoved out of the box it was standing in. It sinks, its mouth turns over and it looks
   at the floor, and after three seconds it gets on with its life. */
@keyframes bot-sulk { 0%   { transform: translateY(-4px) scaleY(1) }
                      26%  { transform: translateY(2px) scaleY(.955) }
                      100% { transform: translateY(1px) scaleY(.975) } }
.bot.sad .frown { opacity: 1; }
.bot.sad .pupil { transform: translateY(2.5px); }
.bot.sad .whole { animation: bot-sulk .9s ease-out forwards; }

@media (prefers-reduced-motion: reduce) {
  .bot *, .bot { animation: none !important; }
}

/* ── a wall of bots ─────────────────────────────────────────────────────────
 *
 * A list gave every bot one line and made them all look like records in a table. They
 * are not records; each one is a thing with a face, a job and a machine, and a card is
 * the shape that says so.
 *
 * Laid out like a profile card, because that is what it is: a face, then who it is and
 * what it does, then everything the system knows about it.
 *
 * The creature's slot is reserved at the size it grows to, not the size it sits at, so
 * hovering a card never moves a word on it.
 */
.wall { display: grid; grid-template-columns: repeat(auto-fill, minmax(268px, 1fr));
        gap: 12px; }
.botcard { display: flex; flex-direction: column;
           border: 1px solid var(--line); border-radius: var(--r);
           background: var(--surface); padding: 12px 13px 10px; }
.botcard:hover { border-color: var(--ink-3); }
.botcard .head { display: flex; align-items: center; gap: 11px;
                 margin-bottom: 6px; padding-bottom: 6px;
                 border-bottom: 1px solid var(--line-soft); }
.botcard .slot { width: 78px; height: 78px; flex: none;
                 display: grid; place-items: center; margin: -9px 0 -9px -11px; }
.botcard .slot .bot { width: 52px; height: 52px;
                      transition: transform .45s cubic-bezier(.34, 1.2, .64, 1); }
.botcard .id { min-width: 0; }
.botcard .id b { display: block; font-size: 19.5px; font-weight: 550;
                 letter-spacing: -.021em; line-height: 1.25; }
.botcard .role { display: block; font-size: 13px; color: var(--ink-3); }
.botcard .say { color: var(--ink-2); font-size: 13.5px; line-height: 1.5; }

/* The machine in glyphs. Two cores and four gigabytes is a fact about a thing, not a
   sentence, and spelling it out in words made every card carry a line of prose that
   nobody reads twice. The sandbox id has gone with it: it belongs on the bot's own page,
   where somebody might actually need to copy it. */
.botcard .facts { display: flex; flex-wrap: wrap; gap: 3px 14px; margin-top: 8px;
                  font-family: var(--mono); font-size: 12px; color: var(--ink-3); }
.botcard .facts > span { display: inline-flex; align-items: center; gap: 5px; }
.botcard .facts .ico { width: 13px; height: 13px; }
.botcard .facts.none { display: block; }

.botcard .end { display: flex; align-items: center; gap: 7px;
                margin-top: auto; padding-top: 10px;
                border-top: 1px solid var(--line-soft); }
.botcard .facts + .end { margin-top: 11px; }

/* Only a bot that is actually there says hello. One that is parked is asleep and one
   that has never run has no character yet, and a drawing that waves at you while its
   own card says "never run" is a drawing telling a lie about the system. */
.botcard:hover .bot:not([data-state="asleep"]):not([data-state="unseen"]) {
  transform: scale(1.5);
}

/* ── hello, in three places ─────────────────────────────────────────────────
 *
 * A card the cursor is on, a face in the picker the cursor is on, and the face already
 * chosen, which greets you for a moment before settling down to wait. The behaviour is
 * one thing; only how far it grows differs, so only that is written per place.
 */
:is(.bot.hi,
    .botcard:hover .bot:not([data-state="asleep"]):not([data-state="unseen"]),
    .swatch.form:hover .bot) .whole { animation: bot-hello 1.7s ease-in-out infinite; }
:is(.bot.hi,
    .botcard:hover .bot:not([data-state="asleep"]):not([data-state="unseen"]),
    .swatch.form:hover .bot) .hold { opacity: 1; }
:is(.bot.hi,
    .botcard:hover .bot:not([data-state="asleep"]):not([data-state="unseen"]),
    .swatch.form:hover .bot) .holdarm { animation: bot-wave .82s ease-in-out infinite; }
:is(.bot.hi,
    .botcard:hover .bot:not([data-state="asleep"]):not([data-state="unseen"]),
    .swatch.form:hover .bot) .grin { opacity: 1; }
/* It puts whatever it was holding down first, the same way it does to get out of its
   chair. You cannot wave with both hands on a keyboard. */
:is(.bot.hi,
    .botcard:hover .bot:not([data-state="asleep"]):not([data-state="unseen"]),
    .swatch.form:hover .bot) :is(.desk, .limbs, .gear) { opacity: 0; }

/* ── a bot's own page ───────────────────────────────────────────────────────
 *
 * The name leads and the job follows it in brackets, quieter, the way you would write
 * somebody down. Under them, one line of character: it is a summary, and the whole thing
 * is in the Character block below, so a sentence that runs out of room can simply stop
 * rather than push the machine strip down a line.
 *
 * The spine under that is the machine, in the register the machine deserves: glyphs and
 * monospace, one strip, no sentences. It is the one place the sandbox id belongs, because
 * this is where somebody is about to copy it.
 */
.bothead { display: flex; gap: 18px; align-items: flex-start; }
.bothead h2 { font-size: 20px; }
.bothead h2 .role { color: var(--ink-3); font-weight: 400; letter-spacing: -.008em; }
.bothead p { margin-top: 3px; max-width: none;
             white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bothead .acts { display: flex; gap: 6px; flex: none; }
.spine { display: flex; flex-wrap: wrap; align-items: center; gap: 5px 14px;
         margin-top: 11px; font-family: var(--mono); font-size: 12px; color: var(--ink-3); }
.spine > span { display: inline-flex; align-items: center; gap: 5px; }
.spine .ico { width: 13px; height: 13px; }
.spine .id { margin-left: auto; letter-spacing: .02em; }

/* A number with its own shape underneath it. The figure says where it ended up and the
   chart says how it got there, which is the half a single number always leaves out. */
.metric .chart, .metric .cols { margin-top: 10px; }
.metric .when { display: block; margin-top: 7px; font-family: var(--mono);
                font-size: 11.5px; color: var(--ink-3); }

/* Deleting a bot takes its machine, its memory and its files. The page should look like
   that before you are anywhere near the button. */
.danger { border-color: color-mix(in oklab, var(--red) 42%, var(--line));
          background: color-mix(in oklab, var(--red) 6%, var(--surface)); }
.danger b { color: var(--red); }
/* Not `.sec-head h2.warn` any more. The class says what it is for; scoping it to one
   container meant the same word in a dialog or inside an opened row came out black. */
h2.warn { color: var(--red); }

/* ── a memory that will not fit on a page ───────────────────────────────────
 *
 * A bot that has been working for a year remembers thousands of things. The old page put
 * every one of them in a card and scrolled; this one gives you three ways to cut the pile
 * down before you start reading — when, what kind, and what it says — and then pages
 * through what is left.
 */
.days { display: grid; grid-template-columns: repeat(30, 1fr); gap: 3px; }
.day { aspect-ratio: 1; min-height: 18px; padding: 0; cursor: pointer;
       border: 1px solid transparent; border-radius: 3px; background: var(--hover-2); }
.day[data-n="1"] { background: color-mix(in oklab, var(--ink) 20%, var(--hover-2)); }
.day[data-n="2"] { background: color-mix(in oklab, var(--ink) 42%, var(--hover-2)); }
.day[data-n="3"] { background: color-mix(in oklab, var(--ink) 66%, var(--hover-2)); }
.day[data-n="4"] { background: var(--ink); }
.day:hover { border-color: var(--ink-3); }
.day[aria-pressed="true"] { border-color: var(--accent); border-width: 2px; }
.dayscale { display: flex; align-items: center; gap: 5px; margin-top: 7px;
            font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); }
.dayscale i { width: 11px; height: 11px; border-radius: 2px; background: var(--hover-2); }

/* One memory, one line. A card each was fine for four of them. */
.memrow { display: flex; align-items: center; gap: 10px; width: 100%;
          padding: 9px 12px; border: 0; background: none; color: var(--ink);
          font: inherit; text-align: left; cursor: pointer;
          border-bottom: 1px solid var(--line-soft); }
.memrow:last-child { border-bottom: 0; }
.memrow:hover { background: var(--hover); }
.memrow b { font-weight: 500; }
.memrow .note { display: block; font-size: 13.5px; color: var(--ink-3);
                white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.memrow.gone b, .memrow.gone .note { color: var(--ink-3); }

.pager { display: flex; align-items: center; gap: 4px; margin-top: 13px; }
.pager button { min-width: 28px; height: 28px; padding: 0 7px;
                border: 1px solid var(--line); border-radius: var(--r-sm);
                background: var(--surface); color: var(--ink-2);
                font: inherit; font-size: 13px; cursor: pointer; }
.pager button:hover { background: var(--hover); color: var(--ink); }
.pager button[aria-current="true"] { background: var(--ink); color: var(--page);
                                     border-color: var(--ink); }
.pager button[disabled] { opacity: .4; cursor: default; }
.pager .of { margin-left: auto; font-family: var(--mono); font-size: 12px;
             color: var(--ink-3); }

/* ── getting a skill in ─────────────────────────────────────────────────────
 *
 * Four ways, one result. They are tabs rather than four stacked forms because only one of
 * them is ever the answer, and a page that shows you three ways you are not using is a
 * page you have to read past.
 */
.way { padding: 14px 15px 15px; }
.way .lab { display: block; margin-bottom: 5px; font-size: 13.5px; color: var(--ink-2); }
.way .lab + .field, .way .lab + .ta { margin-bottom: 11px; }
.way .foot { display: flex; align-items: center; gap: 8px; margin-top: 12px; }
.way p.note { margin-top: 11px; font-size: 13px; color: var(--ink-3); }

.ta { display: block; width: 100%; padding: 9px 11px;
      border: 1px solid var(--line); border-radius: var(--r-sm);
      background: var(--field); color: var(--ink); font: inherit;
      resize: vertical; outline: none; }
.ta:focus { border-color: var(--accent); }
.ta.m { font-size: 13px; line-height: 1.65; }

/* A zip is dropped more often than it is browsed for. */
.drop { display: grid; place-items: center; gap: 5px; padding: 26px 16px;
        border: 1px dashed var(--line); border-radius: var(--r);
        background: var(--inset); text-align: center; cursor: pointer; }
.drop .ico { width: 20px; height: 20px; color: var(--ink-3); }
.drop:hover, .drop.over { border-color: var(--accent-ink); background: var(--hover); }
.drop input { display: none; }

/* ── setting a routine going ────────────────────────────────────────────────
 *
 * The longest form in the product, so it is the one that most needs to read as a sentence
 * rather than a settings screen: what it should do, when, and which bot does it. Anything
 * that has a sensible answer already is folded away.
 */
.when { display: grid; gap: 1px; }
.when label { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
              padding: 7px 9px; border-radius: var(--r-sm);
              color: var(--ink-2); cursor: pointer; }
.when label:hover { background: var(--hover); }
.when label:has(input:checked) { background: var(--hover-2); color: var(--ink); }
.when input[type="radio"] { accent-color: var(--ink); margin: 0; }
/* Every field in the block except the radios, and written that way on purpose.
   This rule used to enumerate `input[type="number"]`, `[type="time"]` and `[type="text"]`,
   and the cron field carries no `type` attribute at all — which behaves as text and
   matches none of those selectors, so it drew with no border, no background and no radius
   while the fields beside it looked like the rest of the site. The enumeration is what
   made forgetting one attribute a visual bug instead of nothing. */
.when input:not([type="radio"]), .when select {
  height: 28px; padding: 0 8px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink);
  font-family: var(--mono); font-size: 13px; outline: none;
}
.when :is(input, select):focus { border-color: var(--accent); }
.when .tiny { width: 74px; }
/* A time field carries its own clock button, and on a twelve-hour locale an AM as well.
   At the width a number field wants, it clips the minutes off. */
.when input[type="time"].tiny { width: 132px; }
.when select.tiny { width: auto; }
.when .mid { width: 158px; }

/* What the five radio buttons add up to, in a sentence. Nobody reads a cron expression
   back to themselves correctly, and the four plain-English options are easy to misread by
   one field. */
.saidwhen { margin-top: 9px; padding: 9px 11px; border-radius: var(--r-sm);
            background: var(--inset); font-size: 13.5px; color: var(--ink-2); }
.saidwhen b { color: var(--ink); font-weight: 500; }

.pickbot { display: grid; grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
           gap: 8px; }
.pickbot label { display: flex; align-items: center; gap: 9px; min-width: 0;
                 padding: 8px 10px; border: 1px solid var(--line);
                 border-radius: var(--r); background: var(--surface); cursor: pointer; }
.pickbot label:hover { border-color: var(--ink-3); }
.pickbot label:has(input:checked) { border-color: var(--ink); }
.pickbot input { display: none; }
.pickbot .bot { width: 30px; height: 30px; }
.pickbot .nm { min-width: 0; }
.pickbot .nm b { display: block; font-weight: 500; }
.pickbot .nm span { display: block; font-size: 12.5px; color: var(--ink-3);
                    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Everything with a sensible answer already, out of the way until somebody disagrees. */
.more > summary { display: flex; align-items: center; gap: 7px; cursor: pointer;
                  list-style: none; color: var(--ink-2); font-size: 14px;
                  padding: 4px 0; }
.more > summary::-webkit-details-marker { display: none; }
.more > summary .ico { width: 14px; height: 14px; color: var(--ink-3);
                       transition: transform .2s ease; }
.more[open] > summary .ico { transform: rotate(180deg); }
.more .rows { margin-top: 11px; }

/* A setting is a row, the same as every other row on this page: what it is, what choosing
   it means, and the control — in that order and in the same place each time. Labels
   stacked over loose selects made four settings look like four different components. */
.setname { width: 128px; flex: none; }
.setctl  { width: 232px; flex: none; }
.setctl .field { width: 100%; height: 32px; }
.setctl select { width: 100%; }

/* Months under a column chart. They line up because they are the same flex as the
   columns above them, with the same gap — a label row measured separately drifts. */
.collabels { display: flex; gap: 2px; margin-top: 7px;
             font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); }
.collabels span { flex: 1; min-width: 0; text-align: center; }

/* ── the feed ───────────────────────────────────────────────────────────────
 *
 * State changes, not output. What an agent driving a dozen tasks cannot get without
 * polling every one of them is which of them just finished, which is asking a question
 * and whose machine went bad — so that is what this carries, and nothing else.
 *
 * Machine-facing, so it is monospace end to end and the event's own kind name is printed
 * rather than translated. Somebody reading this is going to grep for it.
 */
.feed { font-family: var(--mono); font-size: 12.5px; }
.feed .ev { display: flex; align-items: center; gap: 12px;
            padding: 5px 12px; border-bottom: 1px solid var(--line-soft); }
.feed .ev:last-child { border-bottom: 0; }
.feed .ev:hover { background: var(--hover); }
.feed .t   { width: 64px; flex: none; color: var(--ink-3); }
.feed .k   { width: 176px; flex: none; color: var(--ink-2); }
.feed .who { display: flex; align-items: center; gap: 6px; width: 100px; flex: none;
             color: var(--ink-2); }
.feed .who .bot { width: 18px; height: 18px; }
.feed .msg { flex: 1; min-width: 0; color: var(--ink);
             white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Colour only where there is a genuine status: something broke, something finished,
   something is waiting on a person. */
.feed .ev.bad .k, .feed .ev.bad .msg { color: var(--red); }
.feed .ev.ok  .k { color: var(--green); }
.feed .ev.ask .k { color: var(--accent-ink); }
@keyframes ev-in { from { background: var(--hover-2) } }
.feed .ev.fresh { animation: ev-in .9s ease; }
@media (prefers-reduced-motion: reduce) { .feed .ev.fresh { animation: none } }

/* ── rows & tables ──────────────────────────────────────────────────────── */
.rows { border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; background: var(--surface); }
.row {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 11px;
  border-bottom: 1px solid var(--line-soft);
}
.row:last-child { border-bottom: 0; }
.row:hover { background: var(--hover); }

table { width: 100%; border-collapse: collapse; font-size: 14px; }
thead th {
  text-align: left; font-family: var(--mono); font-weight: 400;
  font-size: 11.5px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-3); padding: 8px 12px; border-bottom: 1px solid var(--line);
}
tbody td { padding: 8px 11px; border-bottom: 1px solid var(--line-soft); }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--hover); }
td.right, th.right { text-align: right; }

/* ── the conversation ───────────────────────────────────────────────────────
 * No bubbles. What you said is marked and indented; what the bot said is prose on the
 * page. Two colored blobs facing each other is a messaging app — this is a transcript
 * of work, and it should read like one. */
.thread {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: 20px 22px 8px;
  display: flex; flex-direction: column; gap: 18px;
}
.thread > * { width: 100%; }
/* #scroll is the scroller. The two panes wear .thread so .thread > * hits .turn,
   not the wrappers. Without this reset the inner columns become nested scrollers. */
#scroll {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: 20px 22px 8px;
  display: flex; flex-direction: column; gap: 18px;
}
#history.thread, #thread.thread {
  flex: none; overflow: visible; padding: 0;
}

.turn { display: flex; flex-direction: column; gap: 9px; }

.byline {
  display: flex; align-items: center; gap: 7px;
  font-size: 13.5px; color: var(--ink-3);
}
.byline b { color: var(--ink-2); font-weight: 500; }

/* The two speakers are told apart by side and by shape, not by color. What the person
 * said sits right and is bounded — a short instruction, so it does not need the width.
 * What the bot said runs the full column, because an answer often does.
 *
 * `align-self` rather than an alignment on the parent: the block then lands correctly
 * anywhere it appears, including the sample conversation on the landing page. */
.you-said {
  align-self: flex-end;
  max-width: 72%;
  padding: 9px 13px;
  background: var(--inset);
  border: 1px solid var(--line);
  border-radius: var(--r);
  border-bottom-right-radius: 3px;
  color: var(--ink);
}
.turn.you { align-items: flex-end; }

/* An answer, as it was written.
 *
 * `panel.markdown` builds nodes rather than setting innerHTML — it is the one place a
 * model's text becomes markup, so nothing there is allowed to be a tag it did not make.
 * What it hands back is a `.md` block, and this sheet had no rule for one: `p` is
 * `margin: 0` in the base, so every paragraph of every reply was jammed against the next,
 * and a list, a table or a fence had nothing at all. The rendering was right and the
 * typography was missing, which reads as the rendering being broken.
 *
 * Spacing lives on the bottom of each block and is cleared on the last, so an answer
 * never ends with a gap under it — the thread already sets the distance between turns. */
.md > * { margin: 0 0 10px; }
.md > *:last-child { margin-bottom: 0; }
.md h1, .md h2, .md h3, .md h4, .md h5, .md h6 { margin: 16px 0 6px; }
.md > h1:first-child, .md > h2:first-child, .md > h3:first-child { margin-top: 0; }
.md h1 { font-size: 17px; }
.md h2 { font-size: 15.5px; }
.md h3, .md h4, .md h5, .md h6 { font-size: 15px; }
.md ul, .md ol { padding-left: 22px; }
.md li { margin: 3px 0; }
.md li > ul, .md li > ol { margin: 3px 0 0; }
.md a { color: var(--accent-ink); text-decoration: underline; text-underline-offset: 2px; }
.md blockquote {
  margin-left: 0; padding-left: 12px;
  border-left: 2px solid var(--line); color: var(--ink-2);
}
.md hr { border: 0; border-top: 1px solid var(--line); }
/* Machine facts, in the register the rest of the site uses for them. */
.md code {
  font-family: var(--mono); font-size: 13px;
  background: var(--inset); border: 1px solid var(--line-soft);
  border-radius: var(--r-sm); padding: 1px 5px;
}
.md pre {
  background: var(--inset); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: 11px 13px; overflow-x: auto;
}
/* The fence draws the box; the code inside it must not draw a second one. */
.md pre code { background: none; border: 0; padding: 0; font-size: 13px; }
.md table { font-size: 13.5px; }
.md th { text-align: left; }

/* The work between "you asked" and "it answered": a tool call, a shell command, a
 * stretch of thinking. Smaller and quieter than either, because it is context rather
 * than conversation — somebody scanning a thread should be able to skip all of it, and
 * open the one that matters when the answer surprises them. */
.step {
  align-self: stretch;
  font-size: 12.5px; line-height: 1.6; color: var(--ink-3);
  border-left: 2px solid var(--line); padding-left: 10px;
}
.step > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: baseline; gap: 7px;
  padding: 1px 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.step > summary::-webkit-details-marker { display: none; }
.step > summary::before {
  content: "\203A"; display: inline-block; color: var(--ink-3);
  transform: rotate(0deg); transform-origin: center;
}
.step[open] > summary::before { transform: rotate(90deg); }
.step > summary b { color: var(--ink-2); font-weight: 500; font-family: var(--mono); }
.step:hover { border-left-color: var(--ink-3); }
.step.bad { border-left-color: var(--red); }
.step.bad > summary b { color: var(--red); }
.step-out {
  margin: 6px 0 8px; padding: 8px 10px;
  background: var(--inset); border-radius: var(--r-sm);
  font-family: var(--mono); font-size: 12px; line-height: 1.55;
  color: var(--ink-2); white-space: pre-wrap; word-break: break-word;
  max-height: 320px; overflow: auto;
}
.step-out.bad { color: var(--red); }
/* A thought is prose, not output. */
.step .thinking {
  margin: 6px 0 8px; font-style: italic; color: var(--ink-3);
  white-space: pre-wrap; line-height: 1.6;
}
@media (prefers-reduced-motion: no-preference) {
  .step > summary::before { transition: transform .12s ease; }
}

.answer { line-height: 1.68; }
.answer p + p { margin-top: 11px; }
.answer b { font-weight: 550; }

.cite {
  display: inline-block; min-width: 13px; height: 13px;
  margin: 0 1px; padding: 0 3px;
  border: 1px solid var(--line); border-radius: 3px;
  font-family: var(--mono); font-size: 11px; line-height: 13px;
  color: var(--ink-3); text-align: center; vertical-align: 1px;
}

.after { display: flex; align-items: center; gap: 2px; margin-top: 11px; }
.follow { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.follow button {
  border: 1px solid var(--line); background: none;
  border-radius: var(--r-sm); padding: 4px 10px;
  font: inherit; font-size: 14px; color: var(--ink-2); cursor: pointer;
}
.follow button:hover { background: var(--hover); color: var(--ink); }

/* Thinking: one line until you want more. */
.think { border: 1px solid var(--line); border-radius: var(--r); background: var(--inset); }
.think summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: 8px;
  padding: 9px 12px; font-size: 14px; color: var(--ink-2);
}
.think summary::-webkit-details-marker { display: none; }
.think[open] summary { border-bottom: 1px solid var(--line); }
.think .steps { padding: 3px 10px 9px; }
.think .step { display: flex; gap: 9px; padding: 5px 0; font-size: 14px; color: var(--ink-2); }
.think .step .ico { width: 13px; height: 13px; margin-top: 3px; color: var(--ink-3); }

.dots { display: inline-flex; gap: 3px; align-items: center; }
.dots i { width: 3px; height: 3px; border-radius: 50%; background: var(--ink-3);
          animation: breathe 1.2s ease-in-out infinite; }
.dots i:nth-child(2) { animation-delay: .15s } .dots i:nth-child(3) { animation-delay: .3s }
@keyframes breathe { 0%,100% { opacity: .25 } 50% { opacity: 1 } }
@media (prefers-reduced-motion: reduce) { .dots i { animation: none } }

/* The question the bot stops to ask. It is the one place a screen may raise its voice,
 * and it does it with a rule and a monospace label rather than a colored banner. */
.ask { border: 1px solid var(--ink-3); border-radius: var(--r); background: var(--surface); }
.ask .head {
  display: flex; align-items: center; gap: 7px;
  padding: 8px 11px; border-bottom: 1px solid var(--line);
  font-family: var(--mono); font-size: 11.5px;
  letter-spacing: .04em; text-transform: uppercase; color: var(--ink-2);
}
.ask .q { padding: 11px 12px 2px; }
.ask .opts { display: flex; flex-direction: column; gap: 5px; padding: 9px 12px 12px; }
.ask .opt {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--r-sm);
  background: none; color: inherit; font: inherit; text-align: left; cursor: pointer;
}
.ask .opt:hover { background: var(--hover); }
.ask .opt .k { font-family: var(--mono); font-size: 12.5px; color: var(--ink-3); }

/* What the bot actually did. */
.tools { display: flex; flex-direction: column; gap: 3px; }
.tool {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 9px; border-radius: var(--r-sm);
  background: var(--surface); border: 1px solid var(--line);
  font-family: var(--mono); font-size: 13px; color: var(--ink-2);
}
.tool .name { color: var(--ink); }
.tool .arg { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tool .ico { width: 13px; height: 13px; color: var(--ink-3); }

/* A routine that ran while nobody was watching, and one bot handing work to another.
 * Both belong in the conversation, both are quieter than anything a person said. */
.run, .handoff {
  display: flex; align-items: center; gap: 9px;
  padding: 8px 11px;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--inset);
  font-size: 12.5px; color: var(--ink-2);
}
.run .ico, .handoff .ico { color: var(--ink-3); }
.run b, .handoff b { color: var(--ink); font-weight: 500; }
.handoff .who { display: inline-flex; align-items: center; gap: 6px; color: var(--ink); }

.when-rule { display: flex; align-items: center; gap: 10px;
             font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); }
.when-rule::before, .when-rule::after { content: ""; flex: 1; height: 1px; background: var(--line); }

/* ── the composer ───────────────────────────────────────────────────────── */
.composer {
  /* Positioned, because the `@` menu is placed against it. */
  position: relative;
  flex: none; width: 100%;
  margin: 0 22px 14px; width: auto;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r);
  padding: 9px 9px 7px;
}
.composer:focus-within { border-color: var(--ink-3); }
.composer textarea {
  width: 100%; border: 0; outline: 0; resize: none; background: none;
  color: var(--ink); font: inherit; line-height: 1.6;
  min-height: 22px; max-height: 180px; padding: 1px 3px;
}
.composer textarea::placeholder { color: var(--ink-3); }
.composer .bar { display: flex; align-items: center; gap: 5px; margin-top: 6px; }
.composer .send {
  margin-left: auto; width: 26px; height: 26px;
  border: 1px solid var(--ink); border-radius: var(--r-sm);
  background: var(--ink); color: var(--page);
  display: grid; place-items: center; cursor: pointer;
}
.composer .send[disabled] { background: none; border-color: var(--line); color: var(--ink-3); }

.picker {
  display: inline-flex; align-items: center; gap: 5px;
  height: 28px; padding: 0 9px; border: 0; border-radius: var(--r-sm);
  background: none; cursor: pointer; font: inherit; font-size: 13.5px; color: var(--ink-2);
}
.picker:hover { background: var(--hover); color: var(--ink); }

/* ── popups ─────────────────────────────────────────────────────────────── */
.menu {
  position: absolute; z-index: 40;
  min-width: 208px; padding: 4px;
  border-radius: var(--r); background: var(--surface); box-shadow: var(--pop);
}
.rail-foot .menu { bottom: calc(100% - 2px); left: 6px; right: 6px; }
.menu[hidden] { display: none; }
.menu a, .menu button {
  display: flex; align-items: center; gap: 9px;
  width: 100%; padding: 6px 8px;
  border: 0; border-radius: var(--r-sm);
  background: none; color: var(--ink);
  font: inherit; text-align: left; cursor: pointer;
}
.menu a:hover, .menu button:hover { background: var(--hover); }
.menu .ico { color: var(--ink-3); }
/* The one item you cannot undo says so before you press it, not after. */
.menu .bad, .menu .bad .ico { color: var(--red); }
.menu hr { margin: 4px 2px; border: 0; border-top: 1px solid var(--line); }
.menu .tail { margin-left: auto; font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); }
.menu .who { padding: 7px 8px 3px; }
.menu .who b { display: block; font-weight: 550; }
.menu .who span { font-size: 13.5px; color: var(--ink-2); }

/* ── the conversation menu ─────────────────────────────────────────────────
 * `renderConversations()` builds its own rows — `.pick`, `.conv-open`, `.dot`, `.name`,
 * `.sub` — carried over from the old page's markup, which this page never loads the
 * styles for. Scoped to `.conv-menu` throughout: a bare `.pick` rule would also land on
 * `.plan.pick`, a pricing card that has nothing to do with a chat.
 *
 * `.conv-open` is a grid rather than the plain flex row `.menu button` gives every other
 * item, for the same reason `#sessions.rail .pick` was one on the old page: the dot has
 * to sit on the name's line while the count/date sits under both, and a flex row cannot
 * put one child under two others. `.menu` has no max-width of its own — it shrink-to-fits
 * its containing block — so a long title would otherwise widen the whole popup instead of
 * hitting the ellipsis; bounded to the old rail's own width instead of a number invented
 * for this. */
.conv-menu { max-height: 360px; max-width: var(--rail); overflow-y: auto; }
.conv-menu .pick {
  display: flex; align-items: center; gap: 2px;
  border-radius: var(--r-sm); transition: background .12s ease;
}
.conv-menu .pick:hover { background: var(--hover); }
.conv-menu .pick.on { background: var(--hover-2); }
/* `.menu button:hover` paints the button itself and the button covers nearly the whole
 * row, so without this a hovered `.on` row loses its background to a plain hover one. */
.conv-menu .pick .conv-open:hover { background: none; }
@media (prefers-reduced-motion: reduce) { .conv-menu .pick { transition: none; } }

/* One line each. Two lines per conversation turned a menu of five into a wall, and the
 * count and the date are what somebody scans past on the way to the name. */
.conv-menu .pick .conv-open {
  flex: 1 1 auto; width: auto; min-width: 0;
  display: flex; align-items: baseline; gap: 7px;
  padding: 5px 8px;
}
.conv-menu .pick .conv-open .dot { flex: none; align-self: center; background: var(--ink-3); }
.conv-menu .pick .conv-open .name {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.conv-menu .pick .conv-open .sub {
  flex: none;
  font-family: var(--mono); font-size: 11px; color: var(--ink-3);
  white-space: nowrap;
}
/* Reachable, not sold: the row is what somebody opened this menu for, and deleting is one
 * click that undoes a conversation. An icon rather than the word, because the word beside
 * every row was the widest thing in the menu. */
.conv-menu .pick .conv-del {
  flex: none; display: grid; place-items: center;
  width: 26px; height: 26px; padding: 0;
  border: 0; border-radius: var(--r-sm); background: none;
  color: var(--ink-3); cursor: pointer; opacity: 0;
}
.conv-menu .pick:hover .conv-del, .conv-menu .pick .conv-del:focus-visible { opacity: 1; }
.conv-menu .pick .conv-del:hover { color: var(--red); background: var(--hover-2); }

/* ── a menu you open on a thing ─────────────────────────────────────────────
 * Right-click on a bot. Placed at the cursor rather than under a ⋯ button, because
 * the row is the target and a per-row button would put five of them in a rail that
 * is meant to be quiet. */
.ctx { position: fixed; z-index: 60; min-width: 186px; }
.ctx .sep { margin: 4px 2px; border: 0; border-top: 1px solid var(--line); }
.ctx button, .ctx a { position: relative; }
.ctx .arrow { margin-left: auto; color: var(--ink-3); }

/* The submenu hangs off its row and stays open while the pointer is between the two,
 * which is the whole reason it is a sibling of the row rather than a child. */
.subwrap { position: relative; }
.subwrap > .menu {
  position: absolute; left: calc(100% - 2px); top: -5px;
  min-width: 176px; display: none;
}
.subwrap:hover > .menu, .subwrap:focus-within > .menu { display: block; }

/* ── a dialog ───────────────────────────────────────────────────────────── */
/* Two ways in, one look. `.scrim`/`.dialog` is what a page builds for itself;
 * `.backdrop`/`.modal` is what `panel.ask()` builds, and that script is shared with the
 * pages still on `panel.css`, so it cannot be renamed to match — the names are aliased
 * here instead of the look being drawn twice.
 *
 * Found live: this sheet had no rule for either `panel.ask` name, so every confirmation
 * on every converted page — Destroy a bot, delete the record, revoke a key — was a stack
 * of raw nodes at the foot of the document, below the rail, unstyled. It was not a
 * position that had drifted; there was nothing to position it.
 *
 * The padding is on the scrim so a dialog never touches the edge of a narrow window. */
.scrim, .backdrop {
  position: fixed; inset: 0; z-index: 80;
  background: var(--scrim);
  display: grid; place-items: center; padding: 20px;
}
.dialog, .modal {
  width: 100%; max-width: 380px;
  background: var(--surface); border-radius: 12px;
  box-shadow: var(--pop); padding: 18px;
}
.dialog h2 { margin-bottom: 12px; }
.scrim[hidden], .dialog[hidden] { display: none; }

/* What `panel.ask` puts inside the box. Wider than a plain dialog because the one that
 * cannot be undone asks you to type a sandbox id, and a monospace id wrapping mid-word
 * under the label it belongs to is a confirmation somebody gets wrong.
 *
 * Two of its inner names were already taken by this sheet, and both were doing visible
 * damage: `.bar` is the usage chart's three-column grid, so "Careful" was being laid out
 * as a 92px/1fr/62px row, and `.body` is the page scroller, so the dialog's contents were
 * a flex child with its own overflow. Overridden here rather than renamed, for the same
 * reason the outer names are aliased. */
.modal { max-width: 440px; padding: 0; overflow: hidden; }
.modal > .bar {
  display: block; padding: 9px 14px; border-bottom: 1px solid var(--line);
  font-family: var(--mono); font-size: 11.5px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-3);
}
/* Red word on a hairline, and no wash behind it. The tint it had was not chosen: bare
   `.danger` is a card — a red-tinted surface with a red-tinted border — and the strip
   was picking it up by wearing the same name. One signal is enough, and the sheet spends
   colour once per screen. */
.modal > .bar.danger { color: var(--red); background: none; border-color: var(--line); }
.modal > .body {
  flex: none; overflow: visible; min-height: 0; padding: 16px;
}
.modal > .body h3 { margin: 0 0 8px; }
.modal > .body p { margin: 0 0 8px; color: var(--ink-2); line-height: 1.6; }
.modal > .body p:last-of-type { margin-bottom: 0; }
/* The label is a block over its field, the way every other labelled field on the site
   is. Inline, it left the id to be read on the same line as the box you type it into. */
/* Readable, not decorative: on the dialog that guards destruction this label is the id
   you have to copy into the box under it, so it is a thing to be read rather than a hint
   above a field. Ink-2 and the body size, not the faint 12px a form hint gets. */
.modal label {
  display: block; margin-top: 14px; margin-bottom: 5px;
  font-size: 13px; color: var(--ink-2);
}
.modal input {
  width: 100%; height: 34px; padding: 0 11px;
  background: var(--surface); color: var(--ink); font: inherit;
  border: 1px solid var(--line); border-radius: var(--r-sm); outline: 0;
}
.modal input:focus-visible { border-color: var(--accent); }
.modal .actions { margin-top: 16px; justify-content: flex-end; }
/* `panel.ask` builds bare `<button>`s with `ghost` and `danger` on them, not `btn ghost`
   — so without these the two controls on the one screen that destroys things rendered as
   the browser's own grey buttons. */
.modal .actions button {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  height: 32px; padding: 0 14px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink);
  font: inherit; cursor: pointer; white-space: nowrap;
}
.modal .actions button:hover { background: var(--hover); }
.modal .actions button.ghost { background: none; border-color: transparent; color: var(--ink-2); }
.modal .actions button.ghost:hover { background: var(--hover); color: var(--ink); }
.modal .actions button.danger {
  color: var(--red);
  border-color: color-mix(in oklab, var(--red) 42%, var(--line));
}
.modal .actions button.danger:hover {
  background: color-mix(in oklab, var(--red) 8%, var(--surface));
}

/* Picking a look. Every option is drawn as itself: a dropdown reading "crest, drift,
   shard" is a list of words for things you have never seen. */
/* Positioned, because the two in transit are placed against this box. Without it they
   measure themselves against the scrim and fly off into the corner of the window. */
.dialog.look { position: relative; max-width: 452px; padding: 14px; }
.dialog.look .row-end { margin-top: 12px; }
.lookrow { display: flex; gap: 14px; align-items: flex-start; }
.lookbig { flex: none; display: grid; place-items: center;
           width: 92px; height: 92px; border-radius: var(--r);
           background: var(--inset); border: 1px solid var(--line); }
.lookrow .k { display: block; font-family: var(--mono); font-size: 11.5px;
              letter-spacing: .04em; text-transform: uppercase; color: var(--ink-3);
              margin: 12px 0 5px; }
.swatches { display: flex; flex-wrap: wrap; gap: 5px; }
/* Big enough to tell apart. At thumbnail size every one of them is a colored blob, which
   is a picker that makes you click through all eight to find out what you picked. */
.swatches.forms { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.swatch { display: grid; place-items: center; padding: 0; cursor: pointer;
          border: 1.5px solid transparent; border-radius: var(--r-sm);
          background: none; }
.swatch:hover { border-color: var(--line); }
.swatch.on { border-color: var(--ink); }
.swatch.form { width: 100%; height: 54px; }
/* A tenth bigger is enough to lift the one you are pointing at off the grid without
   shoving its neighbours around. */
.swatch.form .bot { transition: transform .3s cubic-bezier(.34, 1.3, .64, 1); }
.swatch.form:hover .bot { transform: scale(1.1); }
/* The one you have already chosen is standing in the box, so its place in the grid is
   left vacant. Dashed rather than blank: it is a space something came out of, not a gap
   where an option should have been.

   Not called .empty — that class is the empty-state block further down this sheet, and it
   was quietly padding this slot to nearly twice the height of its neighbours. */
.swatch.form.vacant { border-style: dashed; border-color: var(--line); cursor: default; }
.swatch.form.vacant:hover { border-color: var(--line); }

/* Whoever is in transit between the grid and the box. Absolute over the dialog so
   nothing in either place has to move to make room. */
.flier { position: absolute; z-index: 3; pointer-events: none;
         transform: translate(-50%, -50%); }
.swatch.tone { width: 28px; height: 28px; border-radius: var(--pill); }
/* The color chips follow the same theme rule the creatures do, so what you pick is what
   you get on the page behind the dialog. */
.swatch.tone::after { content: ""; width: 18px; height: 18px; border-radius: var(--pill);
                      background: oklch(60% .15 var(--bot-h)); }
:root[data-theme="dark"] .swatch.tone::after { background: oklch(72% .13 var(--bot-h)); }
.dialog .field { height: 36px; }
.dialog .row-end { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
.btn[disabled] { opacity: .45; cursor: default; }

/* ── a switch ───────────────────────────────────────────────────────────────
 * On or off, and which it is readable without a word beside it. This was a button
 * reading "On" or "Off", which is a control that tells you its own state and not what
 * pressing it will do — and the two readings are opposite.
 *
 * The one place on this site where colour marks a setting rather than a status. It earns
 * it: a list of twenty rows has to answer "which of these can the bot reach" at a glance,
 * and a switch that is only ever ink says that no better than the word did. */
.switch {
  flex: none; position: relative; width: 34px; height: 20px; padding: 0;
  border: 1px solid var(--line); border-radius: var(--pill);
  background: var(--hover-2); cursor: pointer;
  transition: background .14s ease, border-color .14s ease;
}
.switch .dot {
  position: absolute; top: 2px; left: 2px; width: 14px; height: 14px;
  border-radius: var(--pill); background: var(--surface);
  box-shadow: 0 1px 2px oklch(0% 0 0/.2);
  transition: transform .14s ease;
}
.switch.on { background: var(--accent); border-color: var(--accent); }
.switch.on .dot { transform: translateX(14px); }
.switch[disabled] { opacity: .6; cursor: default; }

/* Which order the list is in. Quiet until it is the one in force. */
.sortby { display: flex; gap: 4px; }
.sortby .chip { cursor: pointer; }
.sortby .chip.on { background: var(--ink); border-color: var(--ink); color: var(--page); }

/* Editing a skill is a document, not a field. Wider and taller than a plain dialog, and
   its own scroller so the buttons stay where they are on a long one. */
/* A `<dialog>` wearing this class keeps the browser's own inset border and its
   `::backdrop` stays transparent, so the dialog floats over an unshaded page inside a
   frame nothing else on the site has. The scrim-and-div dialogs never meet either. */
dialog.dialog { border: 0; }
dialog.dialog::backdrop { background: var(--scrim); }
.dialog.editdlg { max-width: min(760px, calc(100vw - 48px)); }
/* `.dialog h2` carries the gap under a title that stands alone. In a `.sec-head`
   the row already carries it, and both together is a title floating in the middle
   of its own margins. */
.dialog .sec-head h2 { margin-bottom: 0; }
.dialog.editdlg form { display: flex; flex-direction: column; gap: 4px; }
.dialog.editdlg label { font-size: 12px; color: var(--ink-3); margin-top: 8px; }
.dialog.editdlg input, .dialog.editdlg textarea {
  width: 100%; padding: 8px 10px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink); font: inherit;
}
.dialog.editdlg textarea {
  font-family: var(--mono); font-size: 12.5px; line-height: 1.55;
  max-height: min(52vh, 520px); resize: vertical;
}
.dialog.editdlg .row-end { margin-top: 14px; }

/* ── the four affordances ───────────────────────────────────────────────── */
/* The row of panel buttons, and the anchor for the one menu that hangs off it. */
.panels { display: flex; gap: 1px; position: relative; }
/* Under its own button, right-aligned so it cannot run off the edge of the window on a
 * narrow screen. Without an offset of its own it inherited none, and an absolutely
 * positioned box with no offsets sits where static flow left it — which was the far side
 * of the stage, nowhere near the button that opens it. */
.export-menu { top: calc(100% + 6px); right: 0; }
.panels > button {
  width: 26px; height: 26px;
  border: 1px solid transparent; border-radius: var(--r-sm);
  background: none; color: var(--ink-3); cursor: pointer;
  display: grid; place-items: center;
}
.panels > button:hover { background: var(--hover); color: var(--ink); }
.panels > button[aria-pressed="true"] { background: var(--hover-2); color: var(--ink); border-color: var(--line); }

/* A panel that needs the bot's container, on a bot that is asleep. Dimmed rather than
   hidden: the row of icons is how somebody learns what a bot can do, and one that
   disappears while it sleeps teaches that the feature is missing rather than that the bot
   is. `pointer-events` stays on, or the browser shows no tooltip and the one thing this
   state has to say — wake it first — is unreachable. */
.panels > button[disabled] { opacity: .38; cursor: not-allowed; }
.panels > button[disabled]:hover { background: none; color: inherit; }
/* ── the stage: one screen, panels that open ────────────────────────────────
 * There is no separate workspace page. The chat IS the workspace with everything
 * closed. Opening one is a state, not a navigation. */
.stage { flex: 1; min-height: 0; display: flex; }
.stagemain { flex: 1; min-width: 0; display: flex; flex-direction: column; min-height: 0; }
.stagecols { flex: 1; min-height: 0; display: flex; }
.threadcol { flex: 1; min-width: 0; display: flex; flex-direction: column; min-height: 0; }
.editorpane { flex: 1; min-width: 0; display: flex; flex-direction: column; border-left: 1px solid var(--line); }
/* A file takes half the room, beside the conversation rather than inside the narrow
 * column the tree lives in. `openFile` has always added this class; nothing has ever
 * styled it, so the pane appeared with no width of its own. */
.stagecols.split .threadcol { flex: 1 1 50%; }
.stagecols.split .editorpane { flex: 1 1 50%; }
.editorpane .editor { flex: 1; min-height: 0; position: relative; }
.editorpane .filetabs .btn { margin-left: 6px; }
@media (max-width: 900px) {
  /* Two halves of nothing is nothing. Below this the file takes the room and the
   * conversation waits. */
  .stagecols.split .threadcol { display: none; }
}
.browserpane { flex: 1; min-height: 0; display: flex; flex-direction: column; background: var(--canvas); }

/* Two reads of the same bot. Quiet, because the chat is the default and this strip sits
 * above every conversation somebody opens. */
.talktabs {
  flex: none; display: flex; align-items: center; gap: 2px;
  padding: 6px 20px 0; border-bottom: 1px solid var(--line);
}
.talktab {
  border: 0; border-bottom: 2px solid transparent; background: none;
  padding: 5px 9px 6px; cursor: pointer;
  font: inherit; font-size: 13px; color: var(--ink-3);
}
.talktab:hover { color: var(--ink-2); }
.talktab.on { color: var(--ink); border-bottom-color: var(--ink); }
.tracefilter {
  display: flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--ink-3); cursor: pointer; padding-bottom: 5px;
}
.tracefilter input { accent-color: var(--accent); margin: 0; }

/* The trace itself: one line a record, opened for the whole of it. A log runs to tens of
 * thousands of rows, so it walks backwards and nothing is serialised until it is read. */
.ledger { flex: 1; min-height: 0; overflow-y: auto; padding: 10px 20px 16px; }
.ledrow { border-bottom: 1px solid var(--line-soft); }
.ledrow > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: baseline; gap: 9px; padding: 6px 2px;
  font-size: 12.5px; color: var(--ink-2);
  white-space: nowrap; overflow: hidden;
}
.ledrow > summary::-webkit-details-marker { display: none; }
.ledseq { flex: none; font-family: var(--mono); font-size: 11px; color: var(--ink-3); }
.ledkind { flex: none; font-family: var(--mono); font-weight: 500; color: var(--ink); }
.ledgist { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
           color: var(--ink-3); }
.ledat { flex: none; font-family: var(--mono); font-size: 11px; color: var(--ink-3); }
.ledrow.bad .ledkind { color: var(--red); }
.ledrow:hover { background: var(--hover); }
.ledraw {
  margin: 2px 0 10px; padding: 9px 11px;
  background: var(--inset); border-radius: var(--r-sm);
  font-family: var(--mono); font-size: 11.5px; line-height: 1.5; color: var(--ink-2);
  white-space: pre-wrap; word-break: break-word; max-height: 380px; overflow: auto;
}
.ledmore { padding: 12px 2px 0; }

/* ── the plan ───────────────────────────────────────────────────────────────
 * What the agent said it would do, ticking itself off as it does it. Above the thread
 * and outside its scroller, because the whole point is that it stays put while the
 * conversation moves under it.
 *
 * No colour. A plan is a list of things in three states and the states are the ink ramp:
 * what is done goes quiet, what is happening is the darkest thing on the card, and what
 * has not started yet sits between them. Spending the accent here would spend it on
 * every screen where an agent is working. */
.plan {
  flex: none; margin: 10px 22px 0;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--surface);
}
.plan[hidden] { display: none; }
.plan > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: baseline; gap: 8px; padding: 8px 12px;
  font-size: 13px; white-space: nowrap; overflow: hidden;
}
.plan > summary::-webkit-details-marker { display: none; }
.plan > summary::before {
  content: "\203A"; color: var(--ink-3); display: inline-block;
  transform: rotate(0deg); transform-origin: center;
}
.plan[open] > summary::before { transform: rotate(90deg); }
.plan-what { font-weight: 550; }
.plan-count { font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); }
/* What it is on right now, so a folded card still answers the question the whole thing
   exists for. */
.plan-now {
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
  font-size: 12.5px; color: var(--ink-2);
}
.plan-steps {
  margin: 0; padding: 2px 12px 10px; list-style: none;
  display: flex; flex-direction: column; gap: 3px;
}
.plan-steps li {
  display: flex; gap: 8px; align-items: flex-start; min-width: 0;
  font-size: 13px; line-height: 20px; color: var(--ink-2);
}
.plan-mark {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 16px; width: 16px; height: 16px; margin-top: 2px;
  border: 1.5px solid currentColor; border-radius: 50%;
  font-family: var(--sans); font-size: 11px; line-height: 1;
  color: var(--ink-3);
}
.plan-name { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.plan-steps li.done { color: var(--ink-3); }
.plan-steps li.done .plan-name { text-decoration: line-through; }
.plan-steps li.done .plan-mark { color: var(--green); }
.plan-steps li.now { color: var(--ink); font-weight: 500; }
.plan-steps li.now .plan-mark { color: var(--ink); }
.plan-steps li.now .plan-mark::after {
  content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor;
}

/* ── the shape of a run ─────────────────────────────────────────────────────
 * One cell per step, in order. A list of a thousand rows says how much happened and
 * nothing about where; this says where the tool calls cluster and where it broke before
 * anybody scrolls.
 *
 * Equal cells, not duration-proportional: one forty-second tool call beside forty
 * two-hundred-millisecond ones makes the forty invisible, and seeing the small ones is
 * the point.
 *
 * Read as a density, not as a legend — the kinds are the ink ramp, darkest for what a
 * person said and lightest for bookkeeping, so the rhythm of a run is visible without
 * eight colours to learn. Colour is spent twice, where it means something: red where a
 * step failed, green where the agent stopped to ask. */
.ribbon {
  display: flex; gap: 1px; height: 22px; margin: 2px 0 8px;
  border-radius: var(--r-sm); overflow: hidden;
}
.ribbon[hidden] { display: none; }
.ribbon .cell { flex: 1 1 0; min-width: 1px; background: var(--line); }
.ribbon .cell.led-you       { background: var(--ink); }
.ribbon .cell.led-agent     { background: var(--ink-2); }
.ribbon .cell.led-tool,
.ribbon .cell.led-shell,
.ribbon .cell.led-orphan    { background: var(--ink-3); }
.ribbon .cell.led-compacted,
.ribbon .cell.led-edge      { background: var(--hover-2); }
.ribbon .cell.led-thinking  { background: var(--line); }
.ribbon .cell.led-noise     { background: var(--line-soft); }
.ribbon .cell.led-ask       { background: var(--green); }
.ribbon .cell.led-failure,
.ribbon .cell.bad           { background: var(--red); }

.led-count { margin: 0 0 8px; font-size: 12px; }
/* Measured, so it is a machine fact. The gap between steps is not one and is not shown
   as though it were. */
.ledtook { flex: none; font-family: var(--mono); font-size: 11px; color: var(--ink-3); }
/* What somebody said, and what the agent answered, read as the conversation they are.
   Everything else in this list is machinery. */
.ledrow.led-you .ledgist, .ledrow.led-agent .ledgist { color: var(--ink-2); }
.ledrow.led-noise .ledkind, .ledrow.led-thinking .ledkind { color: var(--ink-3); }

/* What this bot is carrying: the skills it knows and the values it is given. Narrow, on
 * the same side as the file tree, because it answers the same kind of question — what is
 * in there — and two columns of that kind on opposite sides would be a layout nobody can
 * hold in their head. */
.library {
  width: 246px; flex: none;
  display: flex; flex-direction: column; min-height: 0;
  background: var(--canvas); border-right: 1px solid var(--line);
}
.libitems { flex: 1; min-height: 0; overflow-y: auto; padding: 6px 4px 12px; }
.libitem { padding: 7px 9px; border-radius: var(--r-sm); }
.libitem:hover { background: var(--hover); }
.libname { display: flex; align-items: center; gap: 8px; cursor: pointer; }
.libname input { flex: none; accent-color: var(--accent); margin: 0; }
.libtext {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--mono); font-size: 12.5px; color: var(--ink);
}
/* A name alone says neither what a skill does nor where a value came from. */
/* Switched off, and out of this bot's reach. The row says so itself — it used to be a
   line at the top of the column naming one of ten rows, which is a second place to look
   for something already on screen and is gone the moment you look away. Quieter, not
   struck through: the value is still stored and still here, it is simply not being
   handed over. */
.libitem.off .libtext { color: var(--ink-3); }
.libitem.off .libnote { color: var(--ink-3); opacity: .7; }

.libnote {
  margin: 3px 0 0 24px; font-size: 12px; line-height: 1.45; color: var(--ink-3);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
#library-msg { margin: 0 10px 4px; }
[hidden] { display: none !important; }

/* ── files, editor, terminal, browser ───────────────────────────────────── */
.files {
  width: 208px; flex: none;
  display: flex; flex-direction: column; min-height: 0;
  background: var(--canvas); border-right: 1px solid var(--line);
}
.files .head, .term .head {
  flex: none; height: 32px;
  display: flex; align-items: center; gap: 7px; padding: 0 9px;
  border-bottom: 1px solid var(--line);
  font-family: var(--mono); font-size: 11.5px;
  letter-spacing: .04em; text-transform: uppercase; color: var(--ink-3);
}
.tree { flex: 1; min-height: 0; overflow-y: auto; padding: 5px; }
.tree a {
  display: flex; align-items: center; gap: 7px;
  padding: 3px 7px; border-radius: var(--r-sm);
  font-family: var(--mono); font-size: 13px; color: var(--ink-2); cursor: pointer;
}
.tree a:hover { background: var(--hover); color: var(--ink); }
.tree a[aria-current="true"] { background: var(--hover-2); color: var(--ink); }
.tree .ico { width: 13px; height: 13px; color: var(--ink-3); }
.tree .depth { padding-left: 22px; }

.filetabs { flex: none; display: flex; border-bottom: 1px solid var(--line); background: var(--canvas); }
.filetabs a {
  display: flex; align-items: center; gap: 7px;
  padding: 0 11px; height: 32px;
  font-family: var(--mono); font-size: 13px; color: var(--ink-2);
  border-right: 1px solid var(--line);
}
.filetabs a[aria-current="true"] { background: var(--page); color: var(--ink); }
.filetabs .ico { width: 12px; height: 12px; }
/* The strip draws a seam between its tabs. A control in the strip is not a tab and must
   not draw one — only a link needs saying, since a <button> never matched `.filetabs a`. */
/* `align-self`, because a control in this strip is shorter than a tab is: `.btn.sm` wins
   the height and `.filetabs` sets no cross-axis alignment, which leaves it sitting on the
   strip's top edge five pixels above the buttons beside it. */
.filetabs a.btn { border-right: 0; align-self: center; }

/* ── the file pane's two halves ─────────────────────────────────────────────
 * The editor and the preview share one box and take turns. Both are placed on it rather
 * than flowed inside it: Monaco measures the element it is handed, and a `flex: 1` child
 * of a scrolling box has no height of its own until something fills it — which is an
 * editor nought pixels tall. */
.filecode, .filepreview { position: absolute; inset: 0; }
.filepreview { overflow: auto; background: var(--page); padding: 18px 22px; }
.filepreview img, .filepreview video { max-width: 100%; height: auto; display: block; }
.filepreview audio { width: 100%; }
/* A previewed page is a page: it takes the pane whole, or it scrolls inside the pane's
   own scroll and neither bar reaches the end of it. */
.filepreview iframe {
  position: absolute; inset: 0; width: 100%; height: 100%;
  border: 0; background: var(--page);
}
/* JSON and anything shown as itself. Wrapped, because a minified file is one line and a
   pane that scrolls sideways forever shows the first eighty characters of it. */
.reading { font-size: 13.5px; white-space: pre-wrap; overflow-wrap: anywhere; }
.sheet { overflow: auto; }
.sheet table { border-collapse: collapse; font-family: var(--mono); font-size: 13px; }
.sheet th, .sheet td {
  border: 1px solid var(--line); padding: 4px 9px; text-align: left; white-space: nowrap;
}
/* The header stays while the rows go past — a spreadsheet with no header on screen is a
   grid of unlabelled numbers. */
.sheet th { position: sticky; top: 0; background: var(--surface); }

.editor { flex: 1; min-height: 0; overflow: auto; background: var(--page);
          font-family: var(--mono); font-size: 13.5px; }
.editor .ln, .code .ln { display: flex; padding: 0 12px; line-height: 1.8; }
.editor .ln .n, .code .ln .n {
  width: 26px; flex: none; text-align: right; margin-right: 15px;
  color: var(--ink-3); user-select: none;
}
/* A diff is the one place a filled color earns its keep: added and removed are not a
 * status, they are the content. Kept to the faintest wash that still reads. */
.editor .ln.add, .code .ln.add { background: color-mix(in oklch, var(--green) 9%, transparent); }
.editor .ln.del, .code .ln.del { background: color-mix(in oklch, var(--red) 9%, transparent); }
.editor .ln.add .n { color: var(--green); } .editor .ln.del .n { color: var(--red); }
.k1 { color: var(--ink-2) } .k2 { color: var(--ink) } .k3 { color: var(--ink-2) }
.k4 { color: var(--ink-3) }

/* Copy what was said, as it was written. A reader who wants an answer wants the markdown
 * behind it, not the rendering — so this hands over the raw. Quiet until the turn is
 * hovered or the button itself is focused, because a control on every message that is
 * always visible turns a conversation into a list of buttons. */
.turn { position: relative; }
.copy-said {
  position: absolute; right: 4px; bottom: -2px;
  display: grid; place-items: center; width: 24px; height: 24px;
  border: 0; border-radius: var(--r-sm); background: none;
  color: var(--ink-3); cursor: pointer; opacity: 0;
}
.turn:hover .copy-said, .copy-said:focus-visible { opacity: 1; }
.copy-said:hover { background: var(--hover); color: var(--ink); }
.copy-said.done { color: var(--green); opacity: 1; }
.copy-said.bad { color: var(--red); opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
  .copy-said { transition: opacity .12s ease; }
}

/* A drawer under the composer, not a fixed strip above it. The height is a custom
 * property so the grip has somewhere to write and the size survives a close, a reopen and
 * a reload — it was a hard-coded 168px with no way to change it. */
.term { flex: none; height: var(--term, 220px); display: flex; flex-direction: column;
        border-top: 1px solid var(--line); background: var(--term-bg); }
/* The handle. Four pixels of visible line, a taller invisible target — a grip you have to
 * aim for is a grip nobody uses. */
.term > .grip {
  flex: none; height: 4px; cursor: row-resize; position: relative;
  background: var(--line);
}
.term > .grip::after { content: ""; position: absolute; inset: -5px 0; }
.term > .grip:hover, .term > .grip:focus-visible { background: var(--accent); outline: none; }
/* xterm draws onto a canvas and wants the whole box; the placeholder line below is what
 * shows before a terminal has been opened. */
.term .out .xterm { height: 100%; }
.term .out { flex: 1; min-height: 0; overflow-y: auto; padding: 7px 11px;
             font-family: var(--mono); font-size: 13px; line-height: 1.75;
             color: var(--term-ink); }
.term .out b { color: var(--term-ink); font-weight: 400; }
/* Thin, because it holds no title any more — only the shell's name and the way out. */
.term .term-head { height: 24px; padding: 0 6px 0 10px; border-bottom: 0;
                   color: var(--term-ink); background: transparent; }
.term .term-head .btn { color: var(--term-ink); }

.chrome {
  flex: none; display: flex; align-items: center; gap: 8px;
  height: 36px; padding: 0 9px; border-bottom: 1px solid var(--line); background: var(--surface);
}
.urlbar {
  flex: 1; display: flex; align-items: center; gap: 7px;
  height: 24px; padding: 0 9px; border-radius: var(--r-sm);
  background: var(--field); color: var(--ink-2);
  font-family: var(--mono); font-size: 13px;
}
/* A recording is the one state in this drawer nobody may lose track of: while it is on,
 * every click and every field is being written down. Red and heavier, and the dot beside
 * it pulses — but only for somebody who has not asked for stillness. */
#browser-teach.recording { color: var(--red); font-weight: 600; }
#browser-teach.recording::before {
  content: ""; display: inline-block; width: 7px; height: 7px; margin-right: 6px;
  border-radius: var(--pill); background: var(--red); vertical-align: middle;
  animation: teachpulse 1.6s ease-in-out infinite;
}
@keyframes teachpulse { 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) {
  #browser-teach.recording::before { animation: none; }
}

/* Pushed to the end of the chrome row: it is a statement and an escape hatch, not a
 * control somebody reaches for while browsing. */
.browser-owned { margin-left: auto; white-space: nowrap; }
/* The tab strip, above the picture and below the address bar. A tab is one button: the
   cross inside it closes, anywhere else switches. Two buttons would mean a close control
   that is its own tab stop inside a tab, which is a tab somebody cannot press. */
.tabstrip {
  display: flex; align-items: stretch; gap: 2px;
  padding: 4px 6px 0; overflow-x: auto;
  background: var(--canvas); border-bottom: 1px solid var(--line);
}
.tabstrip .tab {
  display: flex; align-items: center; gap: 6px;
  max-width: 190px; min-width: 0; padding: 5px 8px;
  border: 1px solid transparent; border-bottom: 0;
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  background: none; color: var(--ink-2); font: inherit; font-size: 12.5px;
  cursor: pointer;
}
.tabstrip .tab:hover { background: var(--hover); color: var(--ink); }
.tabstrip .tab.on {
  background: var(--surface); border-color: var(--line); color: var(--ink);
}
.tabstrip .tab-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tabstrip .tab-x { flex: none; color: var(--ink-3); font-size: 14px; line-height: 1; }
.tabstrip .tab-x:hover { color: var(--ink); }
.tabstrip .tab.add { max-width: none; color: var(--ink-3); font-size: 15px; padding: 5px 10px; }
.viewport { flex: 1; min-height: 0; overflow: hidden; background: var(--surface);
            position: relative; display: flex; align-items: flex-start;
            justify-content: center; }
/* The picture of the page. `width: 100%` with the height following keeps the aspect the
 * browser renders at, which is what lets a click be mapped back to a point in that frame
 * rather than to wherever the drawer happens to be sized. */
#browser-screen { width: 100%; height: auto; display: block; background: #fff;
                  -webkit-user-select: none; user-select: none; }
/* The focus ring is on the viewport and it earns its place: keys go to the page only when
 * this holds focus, so somebody has to be able to see that it does. */
#browser-view:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
#browser-view { outline: none; }
.browser-empty { padding: 18px; }
/* What a page is asking. Over the picture rather than beside it, because while one of
 * these is open the page has stopped answering and nothing else in the drawer can be
 * done — showing it anywhere else would invite somebody to try. */
.browser-ask {
  position: absolute; inset: 0; z-index: 3;
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in oklch, var(--ink) 28%, transparent);
}
.browser-ask-card {
  width: min(420px, calc(100% - 40px));
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r);
  box-shadow: var(--shadow-lg, 0 12px 32px rgb(0 0 0 / .18));
  padding: 18px; display: flex; flex-direction: column; gap: 10px;
}
.browser-ask-card p { margin: 0; overflow-wrap: anywhere; }
.picklist { max-height: 220px; overflow-y: auto; display: flex; flex-direction: column;
            gap: 2px; border: 1px solid var(--line); border-radius: var(--r-sm);
            padding: 4px; }
.pickrow { text-align: left; background: none; border: 0; padding: 6px 8px;
           border-radius: var(--r-sm); color: var(--ink); font: inherit; cursor: pointer; }
.pickrow:hover { background: var(--inset); }
.browser-ask-card input {
  width: 100%; padding: 7px 9px; border: 1px solid var(--line);
  border-radius: var(--r-sm); background: var(--field); color: var(--ink); font: inherit;
}
/* What a recording captured, reviewed before it becomes a skill. Wider than the other
 * dialogs in this drawer, and lets itself scroll, because there is more to read here than
 * one line: every step, the sign-in details kept out of them, and the draft itself. */
.browser-ask-card.teach-card {
  width: min(560px, calc(100% - 40px));
  max-height: calc(100% - 40px); overflow-y: auto;
}

/* Waiting for the write-up.
 *
 * The page behind is pushed out of focus rather than covered: what somebody just did to
 * it is the thing being turned into a skill, and a blank screen loses that. Blurred on
 * the picture itself, not on the layer over it — a `filter` on an ancestor blurs its
 * descendants too, which would have taken the creature with it. */
.viewport.learning #browser-screen { filter: blur(7px); transform: scale(1.02); }
.browser-ask.learning { background: color-mix(in oklch, var(--page) 62%, transparent); }

/* ── the bot has this browser ───────────────────────────────────────────────
 * A red border that breathes, and a strip saying who has it and how to take it back.
 *
 * It pulses because a still frame and a page being worked look exactly the same until
 * something moves, and a page being worked by something other than you is the one state
 * on this screen worth interrupting for. Red because this is the only place on the site
 * where the thing to say is "stop" — everywhere else red is a failure that has already
 * happened. `inset` so the ring is inside the viewport and cannot be mistaken for the
 * drawer's own edge. */
.viewport.taken-on { position: relative; }
.viewport.taken-on::after {
  content: ""; position: absolute; inset: 0; z-index: 4;
  border: 2px solid var(--red); border-radius: var(--r-sm);
  /* Nothing under it is reachable anyway — the gateway drops gestures while the agent
     has it — but a layer that swallowed clicks would also swallow the Stop button. */
  pointer-events: none;
  animation: taken-pulse 1.6s ease-in-out infinite;
}
@keyframes taken-pulse {
  0%, 100% { opacity: .95; box-shadow: inset 0 0 0 0 color-mix(in oklab, var(--red) 22%, transparent); }
  50%      { opacity: .55; box-shadow: inset 0 0 14px 0 color-mix(in oklab, var(--red) 22%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  /* The ring still says it, and it still says it in red. What goes is the breathing —
     which is the part that is decoration once somebody has seen the border. */
  .viewport.taken-on::after { animation: none; opacity: .9; }
  .taken-dot { animation: none; }
}
.taken {
  position: absolute; z-index: 5; top: 10px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 8px;
  padding: 5px 6px 5px 11px; border-radius: var(--pill);
  background: var(--ink); color: var(--page);
  font-size: 12.5px; white-space: nowrap;
  box-shadow: 0 4px 14px oklch(0% 0 0/.22);
}
.taken[hidden] { display: none; }
.taken-dot {
  width: 7px; height: 7px; border-radius: var(--pill); background: var(--red);
  animation: taken-pulse 1.6s ease-in-out infinite;
}
.taken-stop {
  display: inline-flex; align-items: center; gap: 5px;
  height: 22px; padding: 0 9px;
  border: 0; border-radius: var(--pill);
  background: var(--red); color: oklch(100% 0 0);
  font: inherit; font-size: 12px; cursor: pointer;
}
.taken-stop:hover { opacity: .9; }
.taken-stop .ico { width: 12px; height: 12px; }
.learncard {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  text-align: center; padding: 8px;
}
.learncard .learnsay { margin: 10px 0 0; font-size: 16px; font-weight: 550; }
.learncard p { margin: 0; }
.learnface { display: block; }
.teach-steps { max-height: 160px; overflow-y: auto; display: flex; flex-direction: column;
               gap: 2px; border: 1px solid var(--line); border-radius: var(--r-sm);
               padding: 4px; }
.teach-step { padding: 6px 8px; border-radius: var(--r-sm); font-size: 13px;
              display: flex; align-items: center; gap: 8px; }
/* The one row somebody has to notice before they save this: the target it clicked will
 * not always be there again, and nothing else on the card says so. */
.teach-step.brittle { background: color-mix(in oklch, var(--red) 8%, transparent); }
.teach-secrets { display: flex; flex-direction: column; gap: 6px; }
.teach-secret-list { display: flex; flex-wrap: wrap; gap: 6px; }
.teach-secret { font-family: var(--mono); font-size: 12.5px; padding: 2px 8px;
                border-radius: var(--pill); background: var(--inset); }
.urlbar input {
  flex: 1; min-width: 0; border: 0; background: none; padding: 0;
  color: var(--ink); font: inherit; outline: none;
}
.urlbar input::placeholder { color: var(--ink-3); }
/* The address is typed into, so it has to look like a field the whole way across rather
 * than inherit a reset that leaves an input transparent and un-clickable. */

.cursor { position: absolute; width: 11px; height: 11px; border-radius: 50%;
          background: var(--ink); box-shadow: 0 0 0 5px color-mix(in oklch, var(--ink) 14%, transparent); }

/* ── flow / routines ────────────────────────────────────────────────────────
 * A routine is three sentences in sequence, so the connector between them is doing the
 * work, not the boxes. It was a 1px hairline in the faintest ink on the sheet, which
 * left three cards floating unconnected on a page whose whole subject is an order. */
.flow {
  padding: 18px; border-radius: var(--r);
  background: radial-gradient(var(--ink-3) 1px, transparent 1.2px) 0 0 / 14px 14px, var(--inset);
  background-blend-mode: normal;
  border: 1px solid var(--line);
  display: flex; flex-direction: column; align-items: center;
}
.flow > * { position: relative; z-index: 1; }
.node { width: 100%; max-width: 320px; background: var(--surface);
        border: 1px solid var(--line); border-radius: var(--r);
        padding: 10px 12px; display: flex; align-items: center; gap: 10px; }
.link {
  width: 22px; height: 22px; color: var(--ink-3);
  display: grid; place-items: center;
}
.link::before {
  content: ""; width: 1.5px; height: 100%;
  background: currentColor; opacity: .55;
}
.link::after {
  content: ""; position: absolute; bottom: 0;
  width: 5px; height: 5px; border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor; transform: rotate(45deg);
  opacity: .55;
}

/* A routine whose bot was deleted, and the hole where that bot would be.
 *
 * A drawn creature is the wrong answer here: a bot's look is hashed from its id, so a
 * dead id draws a confident little animal that reads exactly like a bot which exists.
 * This is the same footprint with nothing in it, which is the true thing to show.
 *
 * It takes the size classes the creature takes, because it stands in the same slots. */
.nobot { display: block; width: 28px; height: 28px; flex: none; border-radius: 50%;
         border: 1px dashed var(--ink-3); opacity: .6; }
.nobot.sm { width: 22px; height: 22px; }
.nobot.lg { width: 48px; height: 48px; }
/* Red, not faint. The routine is still firing, still charged for, and reaching nothing —
   it is the one row on the page that wants somebody to do something about it. */
.gone-said { color: var(--red); }
.node.gone-node { border-color: color-mix(in oklab, var(--red) 42%, var(--line));
                  background: color-mix(in oklab, var(--red) 6%, var(--surface)); }
.node.gone-node b { color: var(--red); }

/* The routine `panel.routineDetail` builds, in the two places it is opened.
 *
 * The two columns are the page's — what the routine is on the left, what it has done on
 * the right — and they only work side by side while there is room for both. The bot's
 * Routines tab is a third of the width the Routines page is, so the same two columns
 * there put the prompt and the run history into forty-character ribbons. Asked of the
 * container rather than the viewport: it is the drawer that is narrow, on a screen that
 * is not. */
.rt-detail { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line);
             container-type: inline-size; }
@container (max-width: 620px) { .rt-detail > .grid.g2 { grid-template-columns: 1fr; } }
/* Where a container query is not answered, the columns stay as the page has always had
   them: two, and readable on the screen this was written on. */

/* ── numbers and their shapes ───────────────────────────────────────────── */
.stat .k { font-family: var(--mono); font-size: 11.5px; letter-spacing: .04em;
           text-transform: uppercase; color: var(--ink-3); }
.stat .v { font-family: var(--mono); font-size: 27px; letter-spacing: -.02em; margin-top: 3px; }

/* A reading that is itself the problem. The operator's storage page is the only place
   the host's free space is ever shown, so when it falls under the reserve the figure has
   to say so instead of sitting there looking like every other number on the row. */
.stat.bad .v { color: var(--red); }
.stat.bad .k { color: var(--red); }

.chart { display: block; width: 100%; color: var(--ink-2); }
.chart.line { height: 72px; margin-top: 10px; }

/* Bars sit in a grid so every label and every number lines up down the column. Ragged
 * numbers make two bars of the same length look different. */
.bars { display: grid; gap: 7px; }
.bar { display: grid; grid-template-columns: 92px 1fr 62px; align-items: center; gap: 10px; }
.bar-k { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-t { height: 8px; border-radius: 2px; background: var(--inset); overflow: hidden; }
.bar-f { display: block; height: 100%; background: var(--ink-2); border-radius: 2px; }
.bar-v { font-family: var(--mono); font-variant-numeric: tabular-nums;
         text-align: right; color: var(--ink-2); }

.cols { position: relative; display: flex; align-items: flex-end; gap: 2px; }
.cols .col { flex: 1; min-width: 0; background: var(--ink-3); border-radius: 1.5px;
             transition: background .12s ease; }
/* Not `--ink`: that token is the sheet's text colour, and text colour is theme-inverted
   (near-black ink on light, near-white ink on dark), so a hover fill painted with it comes
   out fine in one theme and a blown-out slab in the other — indistinguishable from the
   card behind it, which is what "vanish" meant here. `--ink-2` reads as emphasis in both
   themes without ever reaching the text-ink extreme. */
.cols .col:hover { background: var(--ink-2); }

/* What the column says when you point at it. Ink on paper inverted, because it sits over
   the chart and has to win. */
.cols .hint, #chart .hint {
  position: absolute; bottom: calc(100% + 9px); transform: translateX(-50%);
  z-index: 6; pointer-events: none; white-space: nowrap;
  padding: 6px 9px; border-radius: var(--r-sm);
  background: var(--ink); color: var(--page);
  font-family: var(--mono); font-size: 11.5px; line-height: 1.55;
  box-shadow: 0 4px 14px oklch(0% 0 0/.18);
}
.cols .hint.below, #chart .hint.below { bottom: auto; top: calc(100% + 9px); }
.cols .hint[hidden], #chart .hint[hidden] { display: none; }
.cols .hint b, #chart .hint b   { display: block; font-weight: 550; }
.cols .hint .sub, #chart .hint .sub { display: block; opacity: .62; }

/* ── empty state ────────────────────────────────────────────────────────── */
.empty { display: grid; place-items: center; text-align: center; padding: 44px 16px; gap: 5px; }
.empty .ico { width: 22px; height: 22px; color: var(--ink-3); }
.empty p { color: var(--ink-2); max-width: 320px; }

/* ── tabs ───────────────────────────────────────────────────────────────── */
.tabs { display: flex; gap: 1px; border-bottom: 1px solid var(--line); padding: 0 4px; }
.tabs button {
  border: 0; background: none; cursor: pointer; font: inherit;
  padding: 7px 10px; color: var(--ink-2);
  border-bottom: 1px solid transparent; margin-bottom: -1px;
}
.tabs button[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--ink); }

/* ── the doorway ────────────────────────────────────────────────────────────
 *
 * Everything a stranger sees before signing in fits on one screen with room around it.
 * No nav, no plan table, no feature grid: they cannot use any of it yet, and a product
 * that needs a page of explaining is a product that does not trust itself to be obvious.
 * Two sentences and a button.
 */
.gate { min-height: 100dvh; display: grid; place-items: center; padding: 44px 20px; }
.gate .in { width: 100%; max-width: 540px; text-align: center; }

/* The wordmark stands on its own up here rather than in a header bar, because a header
   with one thing in it is a rule drawn across a page for no reason. */
/* The mark stands at the same height on every doorway, and that is why it is out of the
   flow. The gate centres its content, so a page with more in it — the price list has four
   plan cards where the front page has four bots — starts higher, and the mark rode that
   difference: eighty pixels apart on two pages that are meant to be the same place.
   Pinned to the gate's top instead, it lands identically on all of them while each page's
   own content stays optically centred, which is what the centring was for. */
.gate { position: relative; }
/* `> .in >` and not a plain descendant, because `.mark` is not a name this page owns: a
   creature carries a `<g class="mark">` for whatever sits on its head — a crest, a halo, a
   mote — and the four of them on the landing page live inside this same gate. As a
   descendant rule the `translateX(-50%)` below reached into all four and slid each one's
   headwear left by exactly half its own width, which is what a percentage translate does
   against a fill-box. The brand is a direct child of `.in` on every page that has one;
   nothing inside an `<svg>` ever is. */
.gate > .in > .mark { position: absolute; top: 96px; left: 50%; transform: translateX(-50%);
              display: inline-flex; align-items: center; gap: 8px;
              margin-bottom: 0; font-size: 15px; font-weight: 550;
              letter-spacing: -.01em; color: var(--ink-2); }

.gate h1 { font-size: 46px; line-height: 1.04; letter-spacing: -.038em; }
/* Two short sentences deserve to break where the sense breaks, not where the box ends. */
.gate h1, .gate p { text-wrap: balance; }
.gate p  { margin: 16px auto 0; font-size: 17px; color: var(--ink-2); }
.gate .rate { margin-top: 18px; font-family: var(--mono); font-size: 13px;
              color: var(--ink-3); letter-spacing: -.01em; }
.gate .btn.primary { width: 100%; max-width: 300px; height: 44px; margin-top: 32px; }
.gate .foot { margin-top: 46px; font-size: 13px; }
.gate .foot a { color: var(--ink-3); }
.gate .foot a:hover { color: var(--ink); }

/* The password door, folded. It is the second way in and it belongs to whoever was
   invited rather than to whoever is reading — so it is a line under the button, and the
   doorway looks the way the front page looks until somebody opens it. */
.gate .pwfold { margin-top: 22px; }
.gate .pwfold summary { font-size: 13px; color: var(--ink-3); text-align: center; }
.gate .pwfold summary:hover { color: var(--ink-2); }
.gate .pwfold[open] summary { margin-bottom: 14px; }
.gate .pwfold form { text-align: left; }

/* The sentence about how accounts are made is a footnote, not a paragraph. It was set at
   the page's own 17px and left-aligned inside the form's narrow column, which made the
   quietest thing on the doorway the loudest thing under the button. Same register as the
   line above it now, and centred on the page rather than on the form. */
.gate #returning { width: 100%; }
.gate #returning > .note {
  margin-top: 18px; text-align: center; font-size: 13px; color: var(--ink-3);
}

@media (max-width: 520px) { .gate h1 { font-size: 34px; } }

/* Four plans, and no feature matrix. A grid of ticks is what you build when the plans do
   not differ in any way you can say out loud; these differ in two, so each card says both
   and stops. The machine facts go in monospace like every other machine fact here. */
.gate.wide .in { max-width: 1020px; }
.plans { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px;
         margin-top: 34px; text-align: left; }
.plan { display: flex; flex-direction: column;
        border: 1px solid var(--line); border-radius: var(--r);
        padding: 16px 16px 15px; background: var(--surface); position: relative; }
.plan .who { font-family: var(--mono); font-size: 11.5px; letter-spacing: .06em;
             text-transform: uppercase; color: var(--ink-3); }
.plan .fee { margin-top: 9px; font-size: 30px; font-weight: 550; letter-spacing: -.03em; }
.plan .fee span { font-size: 14px; font-weight: 400; color: var(--ink-3);
                  letter-spacing: 0; }
.plan .line { margin-top: 7px; font-size: 14px; color: var(--ink-2); }
/* Two facts, one per line, so the four read down as a spec sheet rather than as four
   sentences of different lengths that happen to be near each other. */
.plan .spec { margin-top: auto; padding-top: 13px; border-top: 1px solid var(--line-soft);
              font-family: var(--mono); font-size: 12px; line-height: 1.7;
              color: var(--ink-3); }
.plan .line + .spec { margin-top: 13px; }
/* One card is the answer for most people. It says so quietly, by being the only one drawn
   in ink rather than in hairline. */
.plan.pick { border-color: var(--ink); }
.plan.pick .tag { position: absolute; top: -8px; left: 14px; padding: 1px 7px;
                  border-radius: var(--pill); background: var(--ink); color: var(--page);
                  font-family: var(--mono); font-size: 10.5px; letter-spacing: .05em;
                  text-transform: uppercase; }

@media (max-width: 900px) { .plans { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 520px) { .plans { grid-template-columns: 1fr; }
                            .plan .line { min-height: 0; } }

/* ── pages a stranger sees ──────────────────────────────────────────────────
 * What is left of the public pages shares one header and one measure. They used
 * to carry a <style> block each, which is exactly the per-page styling this sheet
 * exists to make unnecessary. The hero rules went with the old landing page. */
.pubwrap { max-width: 940px; margin: 0 auto; padding: 0 20px 80px; }

/* ── the reference ──────────────────────────────────────────────────────────
 * Docs is not the app. It carries no rail and no bots: a person reading the API is
 * not looking for their own staff, and a stranger reading it does not have any. It
 * gets the public header and a nav of its own. */
.pubtop { position: sticky; top: 0; z-index: 10; background: var(--page);
          border-bottom: 1px solid var(--line); }
.pubtop .in { max-width: 1060px; margin: 0 auto; padding: 0 20px; height: 56px;
              display: flex; align-items: center; gap: 16px; }
.pubtop a.nav { color: var(--ink-2); }
.pubtop a.nav:hover { color: var(--ink); }
.pubtop a.nav[aria-current="page"] { color: var(--ink); }

.doc { display: flex; max-width: 1060px; margin: 0 auto; padding: 0 20px;
       align-items: flex-start; }
.docnav { width: 190px; flex: none; position: sticky; top: 56px;
          padding: 22px 0 40px; max-height: calc(100dvh - 56px); overflow-y: auto; }
.docnav a { display: block; padding: 4px 9px; border-radius: var(--r-sm); color: var(--ink-2); }
.docnav a:hover { background: var(--hover); color: var(--ink); }
.docnav a[aria-current="page"] { background: var(--hover-2); color: var(--ink); }
.docnav a.sub { padding-left: 20px; font-size: 13.5px; }
.docnav .grp { padding: 14px 9px 4px; font-family: var(--mono); font-size: 11px;
               letter-spacing: .04em; text-transform: uppercase; color: var(--ink-3); }
.docmain { flex: 1; min-width: 0; padding: 22px 0 80px 34px; max-width: var(--measure); }

/* The operator's strip. The notice placeholder becomes a `.preview` paragraph or
 * nothing, as a sibling above the shell. The look is so a reader sees an announcement
 * rather than a sentence in the body's type.
 *
 * Its height is nobody's constant any more. `--strip-h` said 23px and the strip measures
 * 28.81 at 11px over 1.62 with its padding and its rule — so every page carrying a notice
 * stood 5.81px taller than the window: a scrollbar for nothing, and a page that slides a
 * few pixels under the pointer. Reported as exactly that. A notice long enough to wrap
 * was out by a further eighteen, and `.op` never subtracted the strip at all, so every
 * operator page was out by the whole of it. See the shell rule near the top. */
.preview {
  margin: 0;
  padding: 5px 16px;
  text-align: center;
  font-size: 11px;
  font-weight: 650;
  letter-spacing: .06em;
  color: var(--page);
  background: var(--ink);
  border-bottom: 1px solid var(--line);
}

/* ── mockup chrome ──────────────────────────────────────────────────────── */
.mock {
  position: fixed; inset: auto 0 0 0; z-index: 90;
  display: flex; align-items: center; gap: 8px;
  padding: 6px 11px;
  background: var(--surface); border-top: 1px solid var(--line);
  font-size: 13px;
}
.mock .who { font-family: var(--mono); font-size: 11px; letter-spacing: .04em;
             text-transform: uppercase; color: var(--ink-3); }
.mock a { color: var(--ink-2); padding: 2px 6px; border-radius: var(--r-sm); }
.mock a:hover { background: var(--hover); color: var(--ink); }

@media (max-width: 900px) {
  .rail { position: absolute; inset: 0 auto 0 0; z-index: 20; box-shadow: var(--pop); }
  .app:not(.railed) .rail { display: none; }
  .g3, .g4 { grid-template-columns: repeat(2, minmax(0,1fr)); }
}

/* ── panel.start writes these; a converted page has no other sheet ─────────
 * `panel.status` sets `#status-note` to `note` or `note err`. An empty note is
 * hidden by the helper, so a visible empty one is a bordered strip of nothing. */
/* Explanation, and the width it is allowed.
 *
 * This product needs a lot of it — it is unusual enough that most screens have to say
 * why — and the keys page had four of these stacked at the full width of the column,
 * which is about ninety characters a line. Comfortable reading is roughly forty-five to
 * seventy-five; past that the eye loses its place on the return sweep, and a page of it
 * is the "tiring" in tiring to read.
 *
 * In `ch`, so it is a measure and not a size: in a column already narrower than this —
 * the rail, the library, the chat's own panes — it changes nothing at all.
 *
 * The spacing is the other half. Three of these ran together as one block at 8px; two
 * paragraphs of explanation with nothing between them read as one long one, and the
 * second point is lost inside the first.
 */
.note { margin: 8px 0 0; font-size: 13.5px; color: var(--ink-2); max-width: 68ch; }
.note + .note { margin-top: 11px; }
/* The lead sentence under a heading. It is the widest prose on most of these pages, so
   capping the explanation under it and leaving this to run the full column inverted the
   hierarchy: the summary read wider than the detail.

   `p.mute` and not `.mute`: on a span that class is a colour applied to a few words
   inside a line, and a measure there would wrap a phrase in the middle of a sentence.
   On a paragraph it is always this — the quiet sentence under a heading — which is what
   `.lede` was named for and never given. */
.lede, p.mute { max-width: 68ch; }
.lede { margin-bottom: 16px; }
.note.err { color: var(--red); }
.note.ok { color: var(--green); }
.note[hidden] { display: none; }
.actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.who {
  font-family: var(--mono); font-size: 12px; color: var(--ink-2);
  max-width: 28ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ink-3 { color: var(--ink-3); }
.ico.chev-sm { width: 12px; height: 12px; }
.ico.arrow { width: 13px; height: 13px; }

#status, #panel { flex: 1; min-height: 0; overflow: auto; }
#status[hidden], #panel[hidden] { display: none; }
#panel { display: flex; flex-direction: column; overflow: hidden; }
#panel .withside { flex: 1; min-height: 0; }

/* A reading is not an object, so it is not in a box.
 *
 * Four white boxes with hairlines on a near-white page is the dashboard idiom every
 * product ships, and it puts a container around a number — which is the one thing on
 * these pages that already has a shape of its own. Without the box the figures are the
 * structure, and the boxes that are left mean something: a box now says "this holds a
 * list or a drawing", not "this is a thing".
 *
 * The label keeps its own left rule instead. One hairline where four sides used to be,
 * which is the same idea the trace's step rows use — an edge that says where a reading
 * begins rather than a frame that encloses it.
 */
.card.stat {
  display: flex; flex-direction: column;
  background: none; border: 0; border-left: 1px solid var(--line);
  border-radius: 0; padding: 2px 0 2px 11px;
}
.stat .v .of { font-size: 16px; }
.card p.mute { margin-top: 2px; }
.danger-row { display: flex; align-items: center; gap: 12px; }
.inline-bot { display: inline-block; vertical-align: -4px; }

/* One column. `.withside` is the page's two-column layout; this list must not
 * inherit a second track or its rows land on top of each other. */
.by-bucket { display: grid; gap: 6px; margin-top: 12px; }
.by-bucket[hidden] { display: none; }
.by-bucket-row { display: flex; align-items: center; gap: 8px; font-size: 12px; }
.by-bucket-k { flex: 0 0 9ch; color: var(--ink-2); }
.by-bucket-bar { flex: 1 1 auto; min-width: 0; height: 8px;
                 background: var(--inset); border-radius: 2px; overflow: hidden; }
.by-bucket-fill { display: block; height: 100%; background: var(--ink-2); }
.by-bucket-n { flex: 0 0 9ch; text-align: right; font-family: var(--mono);
               font-variant-numeric: tabular-nums; color: var(--ink-2); }

#month { margin-bottom: 14px; }
/* Positioned, because the hint that names the day under the pointer is placed against
   this box — the same hint, in the same place, as the column charts on the other pages. */
#chart { min-height: 124px; position: relative; }
#chart svg { display: block; width: 100%; height: 124px; }
#chart .axis { stroke: var(--line); }
#chart .run { fill: var(--ink); }
#chart .idle { fill: var(--ink-3); }
/* The target, and only the target. It is drawn over the bars so that a day with a short
   column is still easy to point at — which meant filling it on hover painted `--hover`,
   a near-white, straight over the bar it was meant to be highlighting: the column
   disappeared under the cursor. What lights up instead is a band behind them. */
#chart .hit { fill: transparent; pointer-events: all; }
#chart .band { fill: transparent; pointer-events: none; }
#chart .band.on { fill: var(--hover-2); }
.chart-days { display: flex; justify-content: space-between; color: var(--ink-3);
              font-size: 11px; margin-top: 6px; }

/* ── /docs ──
 * The mockup's fifteen inline styles, once each. The code-block frames share
 * `.card.snip` rather than repeating padding:0;overflow:hidden. Scalar's
 * palette is pointed at this sheet's tokens so the reference follows the
 * reader's theme without a second copy of every colour. */
.pubtop .brand { padding: 0; }
.pubtop .field.find { width: 230px; flex: none; }
.pubtop .svc { font-family: var(--mono); font-size: 12px; color: var(--ink-2);
               white-space: nowrap; }
.pubtop .svc .chip { height: 20px; }
.docmain > h1 { margin-bottom: 8px; }
.docmain > .lede { margin-bottom: 12px; }
.docmain > .card.flat.lead { margin-bottom: 26px; }
.docmain .sec-head .m { font-weight: 400; }
.docmain .sec > .mute { margin-bottom: 10px; }
.card.snip { padding: 0; overflow: hidden; }
.card.snip + .card.snip { margin-top: 8px; }
.snip-bar { display: flex; align-items: center; gap: 8px; padding: 7px 11px;
            border-bottom: 1px solid var(--line); }
.snip-body { font-family: var(--mono); font-size: 13px; line-height: 1.85;
             padding: 10px 12px; }
.snip-body.out { color: var(--ink-2); }

/* Scalar owns the window under the public bar. Bound every link in the
 * chain or it picks the phone sidebar on a desktop window — which is what
 * the hand-rolled `.docnav` was standing in for. */
body[data-page="docs"] { height: 100dvh; overflow: hidden;
                         display: flex; flex-direction: column; }
body[data-page="docs"] > .preview,
body[data-page="docs"] > .pubtop { flex: 0 0 auto; }
body[data-page="docs"] .pubtop .in { max-width: none; }
body[data-page="docs"] #status:not([hidden]) {
  flex: 1 1 auto; min-height: 0; overflow: auto; padding: 24px 20px; }
body[data-page="docs"] #panel:not([hidden]) {
  flex: 1 1 auto; min-height: 0; display: flex; overflow: hidden; }
body[data-page="docs"] #panel > #app {
  flex: 1; min-width: 0; min-height: 0; display: flex; }
body[data-page="docs"] #app > * { flex: 1; min-width: 0; min-height: 0; }
/* Scalar draws a document taller than the window and has no scroller of its own — its
   root is the only element with the overflow, and it was hidden, so the reference simply
   could not be read past the first screen. The height chain above it stays exactly as it
   was, because that is what makes Scalar pick its desktop layout instead of the phone
   sidebar; only the overflow changes. */
body[data-page="docs"] .scalar-app {
  height: 100%; min-height: 0; overflow-y: auto; overflow-x: hidden; }

.light-mode, .dark-mode {
  --scalar-color-1: var(--ink);
  --scalar-color-2: var(--ink);
  --scalar-color-3: var(--ink-2);
  --scalar-color-accent: var(--accent);
  --scalar-background-1: var(--page);
  --scalar-background-2: var(--surface);
  --scalar-background-3: var(--canvas);
  --scalar-background-accent: var(--hover-2);
  --scalar-border-color: var(--line);
  --scalar-color-green: var(--green);
  --scalar-color-red: var(--red);
  --scalar-color-yellow: var(--accent);
  --scalar-color-blue: var(--accent-ink);
  --scalar-color-orange: var(--accent-ink);
  --scalar-color-purple: var(--accent);
  --scalar-font: var(--mono);
  --scalar-font-code: var(--mono);
  --scalar-radius: 0;
  --scalar-radius-lg: 0;
  --scalar-radius-xl: 0;
}
.light-mode .sidebar, .dark-mode .sidebar {
  --scalar-sidebar-background-1: var(--canvas);
  --scalar-sidebar-color-1: var(--ink);
  --scalar-sidebar-color-2: var(--ink-2);
  --scalar-sidebar-color-active: var(--ink);
  --scalar-sidebar-item-hover-background: var(--hover);
  --scalar-sidebar-item-hover-color: var(--ink);
  --scalar-sidebar-item-active-background: var(--hover-2);
  --scalar-sidebar-border-color: var(--line);
  --scalar-sidebar-search-background: var(--inset);
  --scalar-sidebar-search-border-color: var(--line);
  --scalar-sidebar-search-color: var(--ink-3);
}
.scalar-app button, .scalar-app input, .scalar-app select,
.scalar-app .scalar-card, .scalar-app pre, .scalar-app code { border-radius: 0; }
.scalar-app button, .scalar-app input, .scalar-app select, .scalar-app textarea {
  font-family: inherit; font-size: inherit; letter-spacing: normal;
  background: none; color: inherit; border: 0; padding: 0; width: auto; min-height: 0;
}
.scalar-app button:hover:not(:disabled) { background: none; color: inherit; }
body[data-page="docs"] table { min-width: 0; }

/* ── /signin ──
 *
 * Four bots and one thing that happens to them. This lives here rather than on the
 * page because no other screen stands four of them in a row with room to move. The
 * page script may not paint a constant; resting after an animation is a class. */
.meet { position: relative; display: flex; justify-content: center; gap: 24px;
        margin-bottom: 44px; }
/* They stand on something. One hairline, fading out at both ends: it gives the row a
   floor, explains why they are standing in a line, and is the only mark on this page
   that is not either a bot or a sentence. */
/* Keep the decorative floor inside the viewport on narrow sign-in screens. */
.meet::after { content: ""; position: absolute;
               left: max(-14%, calc(50% - 50vw));
               right: max(-14%, calc(50% - 50vw)); bottom: -3px;
               height: 1px;
               background: linear-gradient(90deg, transparent, var(--line) 16%,
                                           var(--line) 84%, transparent); }
/* Bigger than they ship anywhere else, because they are the picture on this page. */
.meet .bot { width: 62px; height: 62px; transform-origin: 50% 88%; }
.meet .bot.rest { transform: none; }
@media (max-width: 360px) { .meet { gap: 8px; } }
/* Waking is the one state change on this page you watch happen, so the lids and the
   ring that fell on its head both take their time about it. */
.meet .bot .ball, .meet .bot .pupil, .meet .bot .lid { transition: opacity .45s ease; }
.meet .bot .ring { transition: transform .8s cubic-bezier(.34, 1.4, .6, 1); }

/* What travels between them is what actually travels between them. A dot is decoration;
   a 1 and a 0 is the thing itself. The arc is drawn in the script, because it needs
   both ends measured. */
.bit { position: absolute; top: 0; left: 0; margin-left: -4px;
       font: 700 14px var(--mono); line-height: 1; color: currentColor;
       opacity: 0; pointer-events: none; }

@media (prefers-reduced-motion: reduce) {
  .bit { display: none; }
  .meet .bot .ball, .meet .bot .pupil, .meet .bot .lid,
  .meet .bot .ring { transition: none; }
}

/* The password forms are the second door, under the GitHub button. They inherit the
   gate's measure so they sit on the same column as the primary action. */
.gate .door { margin: 28px auto 0; max-width: 300px; text-align: left; }
.gate .door .lab { display: block; margin: 12px 0 5px; font-size: 13.5px;
                   color: var(--ink-2); }
.gate .door .field { width: 100%; }
.gate .door .btn { width: 100%; margin-top: 16px; }
.gate .invite { margin: 0 0 8px; font-size: 14px; color: var(--ink-2); }
.gate .problem { margin: 22px auto 0; max-width: 300px; }
/* ── /me/chat ──
 * The four closed panels, the memory fold, and the sizes the mockup put in
 * style= attributes. Nothing here is a second sheet: the page dropped all three. */
.ico-15 { width: 15px; height: 15px; }
.ico-14 { width: 14px; height: 14px; }
.ico-13 { width: 13px; height: 13px; }
.ico-12 { width: 12px; height: 12px; }
.ico-rot-90 { transform: rotate(90deg); }
.ico-rot-n90 { transform: rotate(-90deg); }
.picker-title { font-size: 14px; font-weight: 550; color: var(--ink); }
.stage { position: relative; }
#panel .stage { flex: 1; min-height: 0; }
.export-menu { left:auto; bottom:auto; min-width:240px; max-width:calc(100vw - 32px); border:1px solid var(--line); }
.export-menu > button { min-height:44px; padding:10px 12px; line-height:1.4; }
.export-menu .ico { flex:0 0 18px; width:18px; height:18px; }
.term-cwd { text-transform: none; letter-spacing: 0; }
.tab-grow { border-right: 0; }
.tab-close { border-right: 0; }
.browser-empty { padding: 22px 26px; }
.memorypane { flex: 1; min-height: 0; overflow-y: auto; background: var(--page); }
.mem-lead { margin-bottom: 15px; }
.kinds { display: flex; gap: 6px; margin-bottom: 11px; flex-wrap: wrap; }
.mem-search { width: 240px; margin-bottom: 11px; }
.mem-grow { min-width: 0; }
.mem-on { width: 88px; text-align: right; }
.field-tall { height: auto; padding: 8px 10px; }
.field-tall textarea {
  width: 100%; border: 0; background: none; color: inherit;
  font: inherit; resize: vertical; outline: none;
}
.dlg-meta { margin-top: 9px; font-size: 13px; }
.push-start { margin-right: auto; }
.of-alone { margin-left: 0; }
.dayscale i[data-n="1"] { background: color-mix(in oklab, var(--ink) 20%, var(--hover-2)); }
.dayscale i[data-n="2"] { background: color-mix(in oklab, var(--ink) 42%, var(--hover-2)); }
.dayscale i[data-n="3"] { background: color-mix(in oklab, var(--ink) 66%, var(--hover-2)); }
.dayscale i[data-n="4"] { background: var(--ink); }
.conv-menu { top: 50px; left: var(--rail); }
.app.rail-shut .conv-menu { left: 12px; }
.dialog.wide { max-width: 520px; }
.composer .bar .send { margin-left: 0; }

/* Naming a skill, above the box you are typing in — that box is already at the bottom of
   the screen, so the only room is up. It is a menu by any other name and is built from
   the same parts: one hairlined surface with `--pop` under it, and the row you are on
   washed rather than coloured. The name is monospace because it is about to be typed
   into a message verbatim, and it is the accent's job nowhere: on this screen the accent
   is the focus ring. */
.mentions {
  position: absolute; z-index: 40;
  left: 0; right: 0; bottom: calc(100% + 6px);
  max-height: min(46vh, 320px); overflow-y: auto;
  padding: 4px;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r);
  box-shadow: var(--pop);
}
.mentions[hidden] { display: none; }
.mentions .hit {
  display: block; width: 100%; text-align: left;
  padding: 6px 8px;
  border: 0; border-radius: var(--r-sm); background: none;
  color: var(--ink); font: inherit; cursor: pointer;
}
.mentions .hit:hover, .mentions .hit.on { background: var(--hover); }
.mentions .hit b {
  display: block; font-family: var(--mono); font-size: 12.5px; font-weight: 500;
}
/* One line. A skill's description is a sentence and this is a list of names. */
.mentions .hit .what {
  display: block; margin-top: 1px; font-size: 12px; color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mentions .none { padding: 7px 8px; font-size: 12.5px; color: var(--ink-3); }
#chat-msg { margin: 0 22px 10px; }

/* ── /me/sandboxes ──
 * Cards, not rows. The five agent-state dots must stay distinguishable by more
 * than hue (three of them are absences). The card's focus ring is drawn inside
 * the box so it is not clipped. Action glyphs stay on one line. */
.botcard:focus-visible { outline: 1.5px solid var(--accent); outline-offset: -2px; }
.botcard .name-btn {
  display: block; padding: 0; border: 0; background: none; color: inherit;
  font: inherit; font-size: 19.5px; font-weight: 550; letter-spacing: -.021em;
  line-height: 1.25; text-align: left; cursor: pointer;
}
.botcard .name-btn.muted { color: var(--ink-3); }
.filters { display: flex; gap: 6px; margin-bottom: 12px; flex-wrap: wrap; }
/* The line that stands in for the cards when there are none. The wall is a grid of
   268px columns, so a paragraph dropped into it is a paragraph in one column — one
   sentence of guidance broken over two lines with two thirds of the page empty beside
   it. It is not a card, so it gets the whole row. */
.wall > .note { grid-column: 1 / -1; }
/* The two halves of where a month went. Two columns wide enough to hold a bar and a
   table, one under the other when there is not room for both. `align-items: stretch` is
   the grid's own default and `.grow` inside each pane is what makes the two boxes end
   level: a pane is a column, its head is its own height, and the box takes what is left.
   Without it the shorter of the two floated above the bottom of its column. */
.grid.spend { grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 9px 26px; }
.grid.spend .pane { display: flex; flex-direction: column; min-width: 0; }
.grid.spend .pane > .grow { flex: 1; }
.wall-tools { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.find { width: 210px; }
.acts-cell { display: flex; flex-wrap: nowrap; align-items: center; gap: 6px; }
.fields { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
/* `.fieldhead` is the same thing over a control that is not a form field — a row of
   choices, a button that opens a picker. A `<label for>` pointing at either is a label
   attached to nothing, so those get a heading that only looks like one. */
.fields label, .stack-field label, .fieldhead {
  display: block; font-size: 12px; color: var(--ink-3); margin-bottom: 4px;
}
.fields select, .fields input, .stack-field input, .stack-field textarea, .stack-field select {
  width: 100%; height: 34px; padding: 0 10px; border: 1px solid var(--line);
  border-radius: var(--r-sm); background: var(--surface); color: var(--ink); font: inherit;
}
.stack-field { margin-top: 10px; }
.stack-field textarea { height: auto; min-height: 72px; padding: 8px 10px; resize: vertical; }
.grouphead { display: block; margin: 16px 0 6px; font-weight: 550; }
.connect-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.connect-row input, .connect-row select { height: 34px; padding: 0 10px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink); font: inherit; }
.keyline { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.keyline code { font-family: var(--mono); font-size: 13px; }
.caution { margin-top: 6px; color: var(--red); font-size: 13px; }
/* The maker, which is a dialog now. Wider than a confirmation because it carries two
   rows of fields and a paragraph, and capped in height with its own scroll: on a short
   window the form used to run off the bottom of the screen with the button on it. */
.dialog.maker {
  max-width: 560px; padding: 0;
  max-height: min(86vh, 780px); overflow-y: auto;
}
.dialog.maker > .sec { margin: 0; padding: 18px; }

/* The face a bot will be born with, and the way into changing it. Sized to the drawing
   rather than to a field: it is a picture of the answer, not a box holding one. */
.lookpick {
  display: flex; align-items: center; gap: 10px;
  width: 100%; padding: 5px 10px 5px 6px;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  font: inherit; cursor: pointer; text-align: left;
}
.lookpick:hover { background: var(--hover); }
.lookpick .slot { display: grid; place-items: center; flex: none; }

/* A row of answers. `.filters` above the bot list is the same shape, kept separate
   because that row is a filter and this one is a choice. */
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
/* The eighth choice, drawn as what it is. It sits in the same row rhythm as the seven
   beside it — a chip's height and a chip's corners — so the row reads as eight options
   and not as seven options above a form. */
.chipfield {
  margin-top: 6px; height: 27px; padding: 0 9px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--surface); color: var(--ink);
  font-family: var(--mono); font-size: 12px; outline: 0;
  width: 100%; max-width: 260px;
}
.chipfield:focus-visible { border-color: var(--accent); }
.char-body { padding: 12px 13px; border-bottom: 1px solid var(--line);
             font-family: var(--mono); font-size: 13px; line-height: 1.75; color: var(--ink-2);
             white-space: pre-wrap; }
.char-foot { display: flex; align-items: center; gap: 8px; padding: 9px 12px; }
.char-card { padding: 0; }
.danger-rule { margin: 13px 0; border: 0;
               border-top: 1px solid color-mix(in oklab, var(--red) 28%, var(--line)); }
.one[hidden], #persona[hidden], #detail[hidden], #maker[hidden] { display: none; }
.bot-dot { display: inline-block; flex: 0 0 auto; width: 8px; height: 8px;
           border-radius: 50%; background: var(--ink-3); }
.bot-dot.running { background: var(--green); }
.bot-dot.sleeping { background: var(--ink-3); }
.bot-dot.gone { background: transparent; border: 1px solid var(--ink-3); }
.bot-dot.unknown { background: transparent; border: 1px dashed var(--ink-3); }
.bot-dot.building { background: transparent; border: 1px dashed var(--ink-3); }
/* Waking or parking: something asked for and not yet true. It pulses because it is the
 * only one of these that is going somewhere — a still dot beside the word "waking" reads
 * as a state the bot is stuck in rather than one it is passing through. */
.bot-dot.waking { background: var(--ink-3); animation: bot-waking 1.1s ease-in-out infinite; }
@keyframes bot-waking { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .bot-dot.waking { animation: none; } }
.bot-dot.working { background: var(--green); box-shadow: 0 0 0 3px color-mix(in oklab, var(--green) 22%, transparent);
                   animation: botbreath 1.8s ease-in-out infinite; }
@keyframes botbreath { 50% { box-shadow: 0 0 0 6px transparent; } }
@media (prefers-reduced-motion: reduce) {
  .bot-dot.working { animation: none; }
}
.bot-state { display: inline-flex; align-items: center; gap: 6px; }
.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
                   overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }

/* ── /me/skills ──
 *
 * The mockup's twenty-three inline styles. They are classes here so a converted
 * page never carries style=; the sheet already had the tabs, the drop, the chips
 * and the ways, and these are the leftovers that would otherwise stay on the
 * elements.
 */
.card.flush { padding: 0; }
.card.flush .tabs { padding: 0 13px; }
.row .grow { min-width: 0; }

/* What you ran it with, in a 320px column.
 *
 * A model id, who serves it and two counts do not fit on one line at that width, and the
 * row was one flex line: the name broke mid-token into three lines and the counts, which
 * had nothing stopping them, were drawn straight across it. Two deliberate lines is both
 * the fix and the smaller of the two — measured 48px against the 74px the collapse was
 * taking.
 *
 * The id is a machine fact, so it is monospace and it truncates rather than wraps: half
 * a model name on a second line tells a reader less than an ellipsis does. */
.row.model { align-items: baseline; padding: 7px 11px; }
.row.model .grow { display: flex; flex-direction: column; gap: 1px; }
.row.model b {
  font-weight: 500; font-size: 13px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.row.model .faint { font-size: 11.5px; }
/* Who serves it, held to its own width: it is one short word and the id beside it is the
   thing that needs the room. */
.row.model > .faint { flex: none; }
a.under { text-decoration: underline; }
.lead { display: flex; gap: 11px; align-items: flex-start; }
.lead .ico { color: var(--ink-3); margin-top: 3px; }
.way .lead { gap: 12px; }
.way .lead .ico { width: 22px; height: 22px; margin-top: 2px; }
.way .lead .mute { margin-top: 3px; }
.way[hidden] { display: none; }

/* ── /me/schedules ──
 *
 * The mockup's eighteen inline styles. The sheet already had .when, .saidwhen,
 * .pickbot, .more, .flow and .setname; these are the leftovers that would
 * otherwise stay on the elements. The new-routine view is full width — a
 * measure here is the 820px cap the user already rejected.
 */
.row[aria-current="true"] { background: var(--hover-2); }
.row-acts { display: flex; gap: 6px; margin-top: 9px; }
.card.tight { padding: 7px; }
.finish-lab { width: 150px; flex: none; }
.tz-field { height: 28px; padding: 0 8px; }
.pill { display: inline-flex; align-items: center; font-family: var(--mono);
        font-size: 12px; }
.pill.dead { color: var(--red); }
.pill.gone { color: var(--ink-3); }
#form-view .hold { max-width: none; }
.form-head { display: flex; align-items: center; gap: 9px; margin-bottom: 18px; }
.form-head h2 { font-size: 16px; font-weight: 550; }

/* ── /me/secrets ──
 *
 * The mockup's seven inline styles. Search and the lock icon stay off this
 * topbar (same rail as /me); the leftovers that would otherwise sit on the
 * lede, the readers cell and the footnote are classes here.
 */
.readers { display: flex; align-items: center; gap: 6px; }
.field-search { width: 200px; }

/* ── /me/keys ──
 *
 * The mockup's three inline styles. The key icon stayed off this topbar
 * (same rail as /me); its ink-3 color is .ico-mute if a later screen
 * needs it. The lede's 16px gap is already .lede. The shown-once sentence
 * lives on the issued card, and the gap under it is .follows like every other
 * paragraph of its kind.
 */
.ico-mute { color: var(--ink-3); }
/* The card that carries a key, once. It follows a hidden message rather than
   another card, so `.card + .card` never reaches it and it has to say the gap
   itself — the same nine, said once, where it used to be said three times at
   twelve, twelve and ten, of which only the last one ever applied. */
.issued { margin-top: 9px; }
.issued .caution { font-weight: 550; margin-bottom: 8px; }
.keyline { display: flex; align-items: center; gap: 10px; }
.keyline code { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.fields { display: grid; grid-template-columns: 1fr 1fr; gap: 11px; margin-bottom: 11px; }
.wrap-cell { white-space: normal; overflow-wrap: anywhere; }
.cred-rows { margin-top: 12px; }

/* ── /me/billing ──
 *
 * The mockup's 23 inline styles (billing.html 10 + usage.html 13). Usage has
 * no HTML page — `/me/usage` is JSON — so both halves resolve here. The plan
 * row, the day-axis, and the bot name cell were the ones that would otherwise
 * sit as style= on the page.
 */
.plan-row { display: flex; align-items: center; gap: 16px; }
.plan-name { font-size: 16px; }
.plan-lead { margin-top: 2px; }
.plan-due { text-align: right; flex: none; }
.usage-row { display: flex; align-items: flex-start; gap: 26px; }
.usage-stat { flex: none; }
.usage-chart-wrap { min-width: 0; }
.usage-days { display: flex; justify-content: space-between; margin-top: 6px; }
.usage-day { font-size: 12px; }
.bot-cell { display: flex; align-items: center; gap: 8px; }
.struck { text-decoration: line-through; }
tr.clickable { cursor: pointer; }
th.num, td.num { text-align: right; }

/* ── /admin ──
 * The operator console is seven routes in one dress. These rules came from the
 * mockup's only <style> block; none of them may stay on a page. */
.op     { display: flex; flex-direction: column; overflow: hidden; }
.optop  { flex: none; height: 50px; display: flex; align-items: center; gap: 10px;
          padding: 0 14px; border-bottom: 1px solid var(--line); }
.optop .brand { padding: 0; }
.opnav  { flex: none; padding: 0 10px; overflow-x: auto; }
.opnav a, .opnav button { display: flex; align-items: center; gap: 7px; white-space: nowrap; }
.tabs a {
  background: none; border: 0; border-bottom: 2px solid transparent;
  color: var(--ink-2); padding: 9px 11px; font: inherit; cursor: pointer;
  text-decoration: none;
}
.tabs a:hover { color: var(--ink); }
.tabs a[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--ink); }
.opnav .ico { width: 14px; height: 14px; }
.opnav .count { font-family: var(--mono); font-size: 11px; color: var(--ink-3);
                background: var(--inset); border-radius: 4px; padding: 0 5px; }
.opnav a[aria-selected="true"] .count { color: var(--red); }
.opbody { flex: 1; min-height: 0; overflow-y: auto; }

/* Eleven columns is what the operator asked for. A table that has to be read across
   scrolls in its own box rather than dropping the columns nobody thought to keep. */
.scrollx { overflow-x: auto; }
/* Direct child only. A table nested inside an opened row is a different, narrower
   thing, and inheriting this floor made its own columns overflow. */
.scrollx > table { min-width: 940px; }

.keyline { display: flex; align-items: center; gap: 10px;
           font-family: var(--mono); font-size: 13px; }
.form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.form .field { height: 34px; }
.tick { display: flex; align-items: center; gap: 7px; color: var(--ink-2); font-size: 13.5px; }

/* The door. Same page, no console behind it — a locked operator tool that shows you
   its contents greyed out has already told you what it holds. */
.lockroom { display: grid; place-items: center; flex: 1; padding: 20px; }
.lockroom .card { width: 340px; padding: 20px; }
.lockroom .btn.primary { width: 100%; margin-top: 9px; }
.lockroom h2 { margin-bottom: 4px; }
.lockroom .mute { margin-bottom: 12px; }

.detail { background: var(--inset); }
.detail > td { padding: 0 !important; }
.detail .in { padding: 14px 16px 16px; display: grid; gap: 16px; }
.msg { display: grid; gap: 6px; padding: 12px 13px; border-bottom: 1px solid var(--line-soft); }
.msg:last-child { border-bottom: 0; }
.msg .who { display: flex; align-items: center; gap: 8px; }
.msg.done { opacity: .55; }

/* Builders on these pages still stamp the old listing names. Dress them here so a
   converted operator table does not fall back to unstyled body text. */
.muted { color: var(--ink-2); }
.small { font-size: 12px; }
.pill {
  display: inline-flex; align-items: center; height: 22px; padding: 0 7px;
  border-radius: 999px; font-size: 12px; background: var(--inset);
}
.pill.ok, .pill.gone { color: var(--green); }
.pill.bad { color: var(--red); }
.pill.dead { color: var(--ink-3); }
table.listing { width: 100%; border-collapse: collapse; }
.listing th, .listing td { padding: 8px 10px; text-align: left; border-bottom: 1px solid var(--line-soft); }
.listing th { font-size: 12px; color: var(--ink-3); font-weight: 500; }
.caution { color: var(--red); font-size: 12.5px; margin-bottom: 8px; }
.addr { word-break: break-all; }
.wrap-cell { overflow-wrap: anywhere; }
#healthchip { background: none; cursor: pointer; font: inherit; }
.seen { opacity: .55; }
.host-stats { margin-bottom: 9px; }
.card.accent { border-color: var(--accent); }
.sec > .mute { margin-top: 9px; }
.card + .card, .card + .rows, .form + .rows { margin-top: 9px; }
/* The sentence that explains the thing above it. One class and one gap, because the pages
   were converted from their mockups one at a time and each conversion named this gap for
   itself: .sleep-note, .meter-note, .issued-once, .note-follow, .note-after, .hint-follow
   and .hint-after were seven classes doing one job at 8, 9, 10, 10, 11, 11 and 12 pixels.
   Nothing on any page distinguished them; a reader moving between two pages met the same
   paragraph sitting at a different distance and had no way to know why. Nine, because
   that is what this sheet already answers with directly above. */
.follows { margin-top: 9px; }
.w150 { width: 150px; } .w170 { width: 170px; } .w210 { width: 210px; }
.w230 { width: 230px; } .w250 { width: 250px; } .w280 { width: 280px; }
.w290 { width: 290px; }
/* A workspace over the warn size. The old sheet tinted the row; a class with
   no rule here rendered those rows identical to the rest. */
.rows tr.over td { color: var(--red); }

/* ── /me/workspace ──
 *
 * A preset button ("Full access") and a permission toggle ("✓ Bots") used to share one
 * `.chips` box on the team row, both drawn as the same bordered pill -- an action and a
 * state wearing one costume, with nothing on screen to say that pressing the first
 * *sets* the eight around it. `.permrow` is the shape that keeps them apart: a small
 * mono label in a fixed-width left gutter names what the chips after it *are* (Preset,
 * Can, Bots), so the three meanings read as three different rows rather than one long
 * run of pills. The width is fixed so a member's own three rows, and the invite block's,
 * line up on the left instead of each starting wherever its own word ends.
 */
.permrow { display: flex; align-items: flex-start; gap: 10px; }
.permrow-lab {
  flex: none; width: 54px; padding-top: 4px;
  font-family: var(--mono); font-size: 12px; color: var(--ink-3);
}
.permrow .chips { flex: 1; min-width: 0; }

/* One member's row, stacked rather than run together. Email, name field and three chip
   groups used to sit in one `.grow` column with no gap of its own, so the row's rhythm
   was whatever each piece's own margin happened to add up to -- which for most of them
   was none. `gap` on the column says the distance once, so the identity line, the name
   field and each Preset/Can/Bots row sit the same distance from their neighbour without
   the number having to be remembered anywhere else. */
.row.member { padding: 11px; }
.member-body { display: flex; flex-direction: column; gap: 10px; }
/* The address on the left, and whatever belongs to this row on the right -- "owner" for
   a row nobody can edit, this row's Save/Remove for one that can. `space-between`
   rather than a `.grow` on the address: the address is plain text with no box of its
   own, and a flex item with no intrinsic width does not push anything by growing. */
.member-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
/* A flex item's default `min-width` is `auto`, not `0` -- it will not shrink below its
   own content's width. An email is one unbreakable token, so without this a long
   address held `.member-head` open at the address's full width and pushed
   `.member-actions` past the edge of the row instead of wrapping under it. */
.member-who { min-width: 0; overflow-wrap: anywhere; }
/* A display name is two words. Left to `.field`'s own width it takes the whole row, so
   "Ugur" sits in a 520px trough with its Save pushed to the far edge — the field looks
   like it is waiting for a sentence and the button looks like it belongs to something
   else. Capped, the pair reads as one control. */
.namefield { max-width: 260px; }
.member-actions { display: flex; gap: 8px; flex: none; }
/* `.field` carries no width of its own -- it takes the browser's default text-input
   size, about 20 characters -- which read as cramped beside a Save button with nothing
   pulling it wider. Scoped to this row rather than widening `.field` itself, which
   other pages size on purpose. */
.member-name .field { flex: 1 1 260px; min-width: 160px; }
/* `.note`'s own 8px top margin assumes it is the next thing under a paragraph. Beside
   the name field, in the same flex line as the input and its Save button, that margin
   only pushed the confirmation out of line with the two controls it belongs beside. */
.member-name .note { margin: 0; }
/* The invite block's three chip rows are `.row` for the border between them, not
   because pressing anywhere on one does anything -- only the chips inside do. `.row`'s
   own hover wash says "click me" about the whole line, which is true of a member row
   (the row named the member) and false of a permrow (naming Preset/Can/Bots is not an
   action). The chips already have their own hover state; the row does not need one on
   top of it. */
.row.permrow:hover { background: none; }

/* ── /help ──────────────────────────────────────────────────────────────────
 * A stranger and a suspended reader wear `body.public`: the rail would be seven
 * links that all refuse them. The account chrome stays in the markup so a
 * signed-in reader keeps the rail they arrived with. Public paint is the same
 * doorway type as /pricing — a headline, then the facts — not a panel card
 * stuffed with twelve disclosures.
 */
body.public .app { display: block; height: auto; overflow: visible; }
body.public .rail,
body.public .railtoggle { display: none; }
body.public[data-page="help"] { overflow-x: clip; }
/* Same box as the bar: logo on the left, Sign in on the right. A second
 * measure here is what made the facts sit inside a narrower column, and
 * opening a fold then pushed the document past both. */
body[data-page="help"] .pubtop .in,
body.public[data-page="help"] .pad.hold {
  width: 100%; max-width: 1060px; margin-inline: auto; padding-inline: 20px;
}
body.public[data-page="help"] .pad.hold { padding-top: 8px; padding-bottom: 80px; }
body.public[data-page="help"] .help-lede { margin: 36px 0 28px; max-width: 28em; }
body.public[data-page="help"] .help-lede h1 {
  font-size: 40px; line-height: 1.05; letter-spacing: -.034em;
}
body.public[data-page="help"] .help-lede p {
  margin: 12px 0 0; font-size: 17px; color: var(--ink-2);
}
body[data-page="help"] .help-lede[hidden] { display: none; }
body[data-page="help"] .help-split {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 34%);
  gap: 36px; align-items: start; width: 100%; min-width: 0;
}
body[data-page="help"] .help-split > * { min-width: 0; }
body[data-page="help"] .faq { margin-top: 2px; min-width: 0; }
body[data-page="help"] .fold { border-bottom: 1px solid var(--line); }
body[data-page="help"] .fold:first-child { border-top: 1px solid var(--line); }
body[data-page="help"] .fold summary { padding: 14px 0; font-size: 15px; }
body[data-page="help"] .fold .mute {
  padding: 0 0 16px; overflow-wrap: anywhere;
}
body[data-page="help"] .side .card { position: sticky; top: 72px; }
@media (max-width: 1080px) {
  body[data-page="help"] .help-split { grid-template-columns: 1fr; }
  body[data-page="help"] .side .card { position: static; }
}

/* ── the file drawer's own file ──────────────────────────────────────────────
 *
 * One file open, in place of the tree rather than beside it: this panel is a drawer in a
 * conversation, and two columns inside a drawer is a second workspace nobody asked for.
 */
.filepane { display: flex; flex-direction: column; min-height: 0; flex: 1; }
.filepane .head { display: flex; align-items: center; gap: 8px; }
.filepane .editor-area {
  flex: 1; min-height: 220px; margin-top: 8px; resize: none;
  font-size: 12.5px; line-height: 1.7; tab-size: 2; white-space: pre;
  overflow-wrap: normal; overflow-x: auto;
}
/* A row in the tree is a button, so it needs the chip's argument: without a background
   of its own it keeps the browser's, and the tree turns into a stack of grey slabs. */
.file-row {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 5px 7px; border: 0; border-radius: var(--r-sm);
  background: none; color: var(--ink); font: inherit; text-align: left; cursor: pointer;
}
.file-row:hover { background: var(--hover); }
.file-row .ico-slot { width: 10px; color: var(--ink-3); font-family: var(--mono); }
.file-row .num { font-size: 11.5px; }

/* ── the wordmark ────────────────────────────────────────────────────────────
 *
 * One loop, one story: work crosses the word and the word watches it go. A pulse enters
 * at the h, the a's eye tracks it the whole way, the last r lifts its shoulder as it
 * arrives, and for the final two seconds the three-eyed one comes up from behind the n to
 * see it leave. Then thirteen of the seventeen seconds are nothing, which is most of the
 * loop and the reason it never reads as a spinner.
 *
 * ── why the eye and the pulse cannot drift apart ──
 *
 * They share a clock *and a shape*. `wm-cross` and `wm-follow` both run 17s, both put
 * their whole travel between 0% and 54%, and both carry the same cubic-bezier. CSS
 * interpolates a property between the keyframes that mention it, so each of those is a
 * single eased segment from end to end — which means the pupil is at the same fraction of
 * its travel as the dot is of its own, at every frame. Not approximately: the same curve
 * evaluated at the same time.
 *
 * The blink is the one move on its own clock, 5.9s, which does not divide into 17. So the
 * eye closes at a different point in every crossing and no two loops land alike.
 */
.wm { display: block; height: 1em; width: auto; overflow: visible; color: inherit; }

/* How big the mark is, per place it stands, in the sheet rather than in the script that
   draws it. It used to carry an inline height, which no rule could answer — so a page
   that wanted a different size had to redraw the mark instead of styling it, and the
   doorway and the price list ended up disagreeing about the same thing.
   A doorway leads with it: it is the first thing there and the only thing above the
   bots. A bar carries it beside other words, and there it is a label. */
.gate > .in > .mark .wm { height: 66px; }
/* Half again as large twice over: 20px, then 30, now 45. The mark is a drawing that
   moves — the creature in it waves — and at 20px the animation was a wobble you had to
   know about to see. The public bar keeps its own smaller size below: there the mark
   stands beside other words and is a label, not the first thing on the page. */
.brand .wm      { height: 45px; }
.pubtop .brand .wm { height: 22px; }
.wm .wm-pupil { fill: currentColor; }
.wm .wm-dot   { fill: currentColor; }
.wm .wm-wake  { stroke: currentColor; opacity: .3; }
/* Their own colours, off the creature sheet. Each carries a hue and nothing else; the
   lightness that reads on this page is the theme's business, exactly as the bots do it —
   so a mark drawn before somebody switched to dark follows them into it without being
   redrawn. A wordmark whose creatures are the same ink as the letters has one character
   in it; this one has four. */
.wm .wm-crest { --bot-h: 68 }
.wm .wm-peek  { --bot-h: 300 }
.wm .wm-halo  { --bot-h: 128 }
.wm .wm-crest, .wm .wm-peek, .wm .wm-halo { --bot: oklch(60% .15 var(--bot-h)); }
:root[data-theme="dark"] .wm .wm-crest,
:root[data-theme="dark"] .wm .wm-peek,
:root[data-theme="dark"] .wm .wm-halo { --bot: oklch(72% .13 var(--bot-h)); }

.wm .wm-body { fill: var(--bot); stroke: none; }
.wm .wm-mark { stroke: var(--bot); stroke-linecap: round; stroke-linejoin: round; }
/* A limb takes the body's own colour pushed toward black — enough to separate an arm
   lying across a belly, not so much that it stops belonging to it. */
.wm .wm-limb { stroke: color-mix(in oklab, var(--bot) 71%, #000);
               fill: none; stroke-linecap: round; }
.wm .wm-ball { fill: #ffffff; stroke: none; }
.wm .wm-eye  { fill: #1b1d22; stroke: none; }


/* The work crossing. It starts and ends outside the box, so it arrives from somewhere and
   leaves for somewhere rather than blinking into being at the first letter. */
@keyframes wm-cross {
  0%        { transform: translateX(-34px); opacity: 0 }
  6%        { opacity: 1 }
  46%       { opacity: 1 }
  54%       { transform: translateX(340px); opacity: 0 }
  100%      { transform: translateX(340px); opacity: 0 }
}
.wm .wm-pulse { animation: wm-cross 17s cubic-bezier(.55,0,.45,1) infinite; }

/* The wake, stretching. The crossing eases slow-fast-slow, so the stream is short where
   the work is starting and settling and long through the middle where it is moving. A
   trail that keeps one length whatever its speed is a dash being dragged, not a flow. */
@keyframes wm-wake {
  0%, 100% { transform: scaleX(.3) }
  27%      { transform: scaleX(1.7) }
  54%      { transform: scaleX(.3) }
}
.wm .wm-wakebox {
  transform-box: fill-box; transform-origin: 100% 50%;
  animation: wm-wake 17s cubic-bezier(.4,0,.6,1) infinite;
}

/* The a, following. Same duration, same 0→54% span, same curve as the crossing above —
   that is the whole of how it stays with the dot. The small downward offset is the eye
   looking at a line that runs below it rather than through it. */
@keyframes wm-follow {
  0%        { transform: translateX(-9px) }
  54%       { transform: translateX(9px) }
  63%       { transform: translateX(9px) }
  80%, 100% { transform: translateX(0) }
}
.wm .wm-look { animation: wm-follow 17s cubic-bezier(.55,0,.45,1) infinite; }

/* Looking *down* at it, on its own element. The dip cannot share the sliding one: extra
   keyframes on that transform would break the single eased 0→54% segment the exact
   tracking depends on, and the eye would stop being where the dot is. So x slides here
   and y dips one layer in, and neither can disturb the other.
   The dip peaks at 22.4%, which is the moment the work is directly under the a — the same
   inverted easing that placed the three creatures. */
@keyframes wm-lookdown {
  0%, 12%   { transform: translateY(1px) }
  22.4%     { transform: translateY(4.2px) }
  33%       { transform: translateY(1px) }
  54%       { transform: translateY(1px) }
  80%, 100% { transform: translateY(0) }
}
.wm .wm-lookdown { animation: wm-lookdown 17s ease-in-out infinite; }

/* The blink. On the pupil and only the pupil: the bowl is a letter before it is an eye,
   and scaling *it* — which is what this rule used to do — squashed the a. Sixty
   milliseconds closed, a hundred and fifty end to end, which is roughly what an eye
   takes. */
@keyframes wm-blink {
  0%, 93%, 100% { transform: scaleY(1) }
  95.5%, 96.5%  { transform: scaleY(.12) }
}
.wm .wm-pupil {
  transform-box: fill-box; transform-origin: 50% 50%;
  animation: wm-blink 5.9s ease-in-out infinite;
}

/* The catch. The last r's shoulder lifts a few degrees as the pulse reaches it and settles
   back slower than it rose, which is what makes it read as a nod rather than a twitch. */
@keyframes wm-catch {
  0%, 40%   { transform: rotate(0deg) }
  47%       { transform: rotate(-7deg) translateY(-1.5px) }
  58%       { transform: rotate(-7deg) translateY(-1.5px) }
  72%, 100% { transform: rotate(0deg) }
}
.wm .wm-catch {
  transform-box: fill-box; transform-origin: 0% 100%;
  animation: wm-catch 17s cubic-bezier(.34,.9,.4,1) infinite;
}

/* The lean. Static, not animated — it is a pose, not a move — and taken about the point
   where the creature is holding on, so the body swings off the letter's edge rather than
   spinning about its own middle. crest gets thirty degrees, which is as far as a body
   leans before the arm is doing the work instead of helping; halo gets forty-five, which
   it can afford because it is holding a stroke that goes all the way down. */
.wm .wm-crest-pose { transform: rotate(-30deg); transform-origin: 16px 36px; }



/* ── who is watching, and when ───────────────────────────────────────────────
 *
 * Each of the three is out only while the work is on its stretch of the word, and these
 * percentages are not taste. The crossing's easing was inverted to find the moment the dot
 * is actually at each letter — properly this time: cubic-bezier is a parametric curve, so
 * you solve X(s) for the time and read Y(s) for the distance, and doing it as one
 * dimension puts every creature seconds away from the thing it is supposed to be watching.
 * The model was then checked against the rendered page and agrees to a tenth of a unit.
 *
 *   crest   at the h        0.9s – 3.9s    out as the work appears, gone once it is past
 *   drift   over the n      4.6s – 6.6s    up the moment it reaches the n, two seconds
 *   halo    the last r      6.1s – 9.5s    leans out as it goes by, runs when it is gone
 *
 * crest comes out up-left and halo up-right, both on a true diagonal — 34 by 34 — and
 * drift straight up over the shoulder. Each retreats down past its own clip, so retracted
 * it is absent rather than merely hidden. Up fast, down slower, all three: something that
 * ducks faster than it looked was startled, and these are only curious.
 */
@keyframes wm-crest {
  0%, 3%      { transform: translate(42px, 42px) }
  5.5%        { transform: translate(0, 0) }
  19.7%       { transform: translate(0, 0) }
  23.5%, 100% { transform: translate(42px, 42px) }
}
.wm .wm-crest-rise { animation: wm-crest 17s cubic-bezier(.3,.85,.4,1) infinite; }

@keyframes wm-peek {
  0%, 27.3%   { transform: translateY(34px) }
  29.5%       { transform: translateY(0) }
  39.1%       { transform: translateY(0) }
  42%, 100%   { transform: translateY(34px) }
}
.wm .wm-peek-rise { animation: wm-peek 17s cubic-bezier(.3,.85,.4,1) infinite; }

/* halo does not lean out and back; it climbs out and jumps off. Out through the side of
   the stem with a little overshoot past the grip — it arrives faster than it can stop —
   then it hangs. Leaving is a hop up before the drop, because letting go and sliding away
   is what the other two do and this one is not the other two. */
@keyframes wm-halo {
  0%, 36%     { transform: translate(-36px, 14px) }
  38.5%       { transform: translate(2px, -3px) }
  40%         { transform: translate(0, 0) }
  54%         { transform: translate(0, 0) }
  55.5%       { transform: translate(-8px, -14px) }
  59%, 100%   { transform: translate(-36px, 14px) }
}
.wm .wm-halo-rise { animation: wm-halo 17s cubic-bezier(.35,.05,.35,1) infinite; }

/* The swing, about the hand it is holding with. Four passes while the work goes by, each
   one a little smaller — it is settling, not being pushed. */
@keyframes wm-halo-swing {
  0%, 40%   { transform: rotate(0deg) }
  43%       { transform: rotate(9deg) }
  46%       { transform: rotate(-6.5deg) }
  49%       { transform: rotate(4deg) }
  51.5%     { transform: rotate(-2.5deg) }
  54%       { transform: rotate(1deg) }
  56%, 100% { transform: rotate(0deg) }
}
.wm .wm-halo-swing {
  transform-box: view-box; transform-origin: 293px 44px;
  animation: wm-halo-swing 17s ease-in-out infinite;
}

/* Their eyes, each tracking across its own window and on the shared clock. */
@keyframes wm-crest-look {
  0%, 3%      { transform: translateX(-1.4px) }
  23.5%       { transform: translateX(1.4px) }
  27%, 100%   { transform: translateX(0) }
}
@keyframes wm-peek-look {
  0%, 27.3%   { transform: translateX(-1.3px) }
  42%         { transform: translateX(1.3px) }
  45%, 100%   { transform: translateX(0) }
}
@keyframes wm-halo-look {
  0%, 38.5%   { transform: translateX(-1.6px) }
  55.5%       { transform: translateX(1.6px) }
  59%, 100%   { transform: translateX(0) }
}
.wm .wm-crest-look { animation: wm-crest-look 17s cubic-bezier(.55,0,.45,1) infinite; }
.wm .wm-peek-look  { animation: wm-peek-look  17s cubic-bezier(.55,0,.45,1) infinite; }
.wm .wm-halo-look  { animation: wm-halo-look  17s cubic-bezier(.55,0,.45,1) infinite; }

/* halo carries a ring and crest three peaks, the way they do on the creature sheet. Thin,
   because they are small things: a two-and-a-half unit stroke beside the letters' nine is
   what the difference in scale actually looks like. */


/* Two marks on one page must not march in step. */
.wm.wm-b .wm-pulse, .wm.wm-b .wm-look, .wm.wm-b .wm-lookdown, .wm.wm-b .wm-catch,
.wm.wm-b .wm-wakebox, .wm.wm-b .wm-peek-rise, .wm.wm-b .wm-peek-look,
.wm.wm-b .wm-crest-rise, .wm.wm-b .wm-crest-look,
.wm.wm-b .wm-halo-rise,  .wm.wm-b .wm-halo-swing, .wm.wm-b .wm-halo-look { animation-delay: -6.2s; }
.wm.wm-b .wm-pupil { animation-delay: -2.4s; }

/* Held still: a favicon capture, a print sheet, a screenshot of the page. */
.wm.still .wm-pulse, .wm.still .wm-look, .wm.still .wm-lookdown, .wm.still .wm-catch, .wm.still .wm-pupil,
.wm.still .wm-wakebox, .wm.still .wm-peek-rise, .wm.still .wm-peek-look,
.wm.still .wm-crest-rise, .wm.still .wm-crest-look,
.wm.still .wm-halo-rise,  .wm.still .wm-halo-swing, .wm.still .wm-halo-look { animation: none; }
.wm.still .wm-pulse, .wm.still .wm-peek,
.wm.still .wm-crest, .wm.still .wm-halo { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .wm .wm-pulse, .wm .wm-look, .wm .wm-lookdown, .wm .wm-catch, .wm .wm-pupil,
  .wm .wm-wakebox, .wm .wm-peek-rise, .wm .wm-peek-look,
  .wm .wm-crest-rise, .wm .wm-crest-look,
  .wm .wm-halo-rise,  .wm .wm-halo-swing, .wm .wm-halo-look { animation: none; }
  .wm .wm-pulse, .wm .wm-peek, .wm .wm-crest, .wm .wm-halo { opacity: 0; }
}

/* ── a grid of cards keeps its own row's height ───────────────────────────
   `.card + .card` further up this sheet gives a vertical stack of cards a gap between
   them. An adjacent-sibling selector cannot tell that stack from two cards a `.grid`
   already laid out side by side: it matched the second one there too, so it carried a
   margin the grid's own `gap` had already paid for. `align-items: stretch` had given
   both cards the row's full height; the extra margin then ate into the second card's
   share of it, so Memory and Skills — one row in `.grid.g2`, and every `.grid.g3` /
   `.grid.g4` row of cards beside it — sat at two different heights for no reason either
   card's own content had anything to do with. A `.card` that is itself a grid item never
   wants the stacked-list rule; only a `.card` stacked under another outside a `.grid` do. */
.grid > .card + .card { margin-top: 0; }

/* The Routines tab: what a bot does without being asked.
 *
 * Almost nothing of its own on purpose. The rows are `.row`, the form is `.sec` and
 * `.ta` and the same five `when` radios `/me/schedules` asks with — the first version of
 * this screen invented a layout for all of it and looked like a different product. What
 * is left here is the one thing the shared pieces cannot know: that this list sits in a
 * scroller inside the thread column rather than on a page of its own. */
.rt-row { gap: 8px; }
#routines > form { padding: 4px 2px 24px; }
#routines .sec { margin-top: 18px; }


/* Owner integration setup, grants and external channels. */
/* Integration settings use standalone labels and controls. The shared `.field`
 * is a fixed-height inline shell, so it must not wrap these stacked labels. */
.integration-page { max-width: none; margin: 0; padding: 36px 32px 72px; }
.integration-page h2 { font-size: 19px; line-height: 1.35; letter-spacing: -.025em; font-weight: 600; }
.integration-page h3 { font-size: 14px; font-weight: 600; margin: 0; }
.integration-page p { text-wrap: pretty; }
.integration-page .note { font-size: 13px; line-height: 1.65; max-width: 66ch; margin: 6px 0 0; color: var(--ink-2); }
.integration-page .btn { min-height: 34px; height: auto; padding-top: 6px; padding-bottom: 6px; font-size: 13px; }
.integration-page .btn[aria-busy="true"]::after { content: "…"; }
.integration-page .btn:disabled { opacity: .5; cursor: wait; }
.integration-page .btn, .integration-filter, .integration-field input, .integration-field select { transition: background-color .15s, border-color .15s; }
.integration-page :is(button, a, summary, input, select):focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.integration-intro { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; margin-bottom: 26px; }
.integration-intro h2 { font-size: clamp(23px, 2.2vw, 30px); margin: 5px 0 8px; letter-spacing: -.04em; }
.integration-intro .note { font-size: 14px; max-width: 58ch; }
.integration-eyebrow { font-size: 11px; font-weight: 550; color: var(--ink-2); margin: 0 0 7px; }
.integration-owner { font-size: 11px; color: var(--ink-2); white-space: nowrap; padding-top: 7px; }
.integration-steps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin: 0; padding: 20px 0; border-block: 1px solid var(--line); list-style: none; }
.integration-steps li { display: flex; align-items: flex-start; gap: 12px; padding: 0 18px; }
.integration-steps li:first-child { padding-left: 0; }
.integration-steps li + li { border-left: 1px solid var(--line); }
.integration-step-number { width: 25px; height: 25px; display: grid; place-items: center; flex-shrink: 0; border: 1px solid var(--line); border-radius: 50%; font: 12px var(--mono); }
.integration-steps strong { display: block; font-size: 13px; font-weight: 550; margin: 1px 0 4px; }
.integration-steps div > span { font-size: 12px; color: var(--ink-2); line-height: 1.5; display: block; }
.integration-sharing { font-size: 12px; color: var(--ink-2); margin: 14px 0 30px; }
.integration-sharing summary { cursor: pointer; width: fit-content; }
.integration-sharing p { max-width: 85ch; line-height: 1.7; margin: 10px 0; }
.integration-message { padding: 12px 16px; background: var(--inset); border: 1px solid var(--line); border-radius: var(--r); font-size: 13px; margin: 0 0 24px; overflow-wrap: anywhere; }
.integration-message.is-error { border-color: var(--red); }
.integration-section { margin-top: 26px; scroll-margin-top: 78px; }
.integration-section-head { display: flex; align-items: center; justify-content: space-between; gap: 20px; margin-bottom: 18px; }
.integration-section-head h2 { margin: 0; display: flex; align-items: center; gap: 9px; }
.integration-count { font: 11px var(--mono); background: var(--inset); border: 1px solid var(--line); color: var(--ink-2); min-width: 22px; padding: 3px 6px; text-align: center; border-radius: 4px; }
.integration-search .sr-only { display: block; font-size: 11px; color: var(--ink-2); margin-bottom: 6px; }
.integration-search { width: 300px; max-width: 100%; flex-shrink: 0; }
.integration-search input { width: 100%; height: 38px; padding: 0 12px; border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--surface); color: var(--ink); font: 13px var(--sans); }
.integration-page input::placeholder { color: var(--ink-2); opacity: .8; }
.integration-filters { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 18px; }
.integration-filter { border: 1px solid transparent; border-radius: var(--r-sm); background: transparent; font: 12px var(--sans); padding: 7px 10px; color: var(--ink-2); cursor: pointer; }
.integration-filter:hover { background: var(--hover); }
.integration-filter[aria-pressed="true"] { border-color: var(--line); color: var(--ink); background: var(--surface); font-weight: 550; }
.integration-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.integration-card { border: 1px solid var(--line); padding: 20px; border-radius: var(--r); min-width: 0; background: var(--surface); }
.integration-app-card { display: flex; flex-direction: column; gap: 14px; }
.integration-app-heading, .integration-connection-heading { display: flex; align-items: center; gap: 12px; min-width: 0; }
.integration-app-mark { display: grid; place-items: center; width: 38px; height: 38px; border: 1px solid var(--line); border-radius: 9px; background: var(--inset); font: 550 12px var(--sans); letter-spacing: -.04em; flex-shrink: 0; }
.integration-app-mark.has-logo { background: #fff; }
.integration-app-mark img { display: block; width: 26px; height: 26px; object-fit: contain; }
.integration-category { display: block; font-size: 11px; color: var(--ink-2); margin-top: 3px; }
.integration-app-card > .note { margin: 0; }
.integration-app-footer { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: auto; padding-top: 4px; }
.integration-caption { font-size: 11px; line-height: 1.6; color: var(--ink-2); }
.integration-app-footer .integration-caption { max-width: 17ch; }
.integration-connections { display: grid; gap: 12px; }
.integration-empty { padding: 20px 22px; border: 1px dashed var(--line); border-radius: var(--r); font-size: 13px; color: var(--ink-2); grid-column: 1 / -1; }
.integration-empty strong { color: var(--ink); font-weight: 550; }
.integration-connection { padding: 20px; border: 1px solid var(--line); border-radius: var(--r); background: var(--surface); min-width: 0; }
.integration-connection h3 { overflow-wrap: anywhere; }
.integration-connection-heading .note { margin-top: 2px; font-size: 12px; }
.integration-connection > .integration-caption { margin: 14px 0; }
.integration-status { color: var(--ink-2); font-size: 11px; padding: 4px 8px; border-radius: 4px; background: var(--inset); flex-shrink: 0; }
.integration-status[data-state="connected"]::before { content: ''; display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--green); margin-right: 7px; }
.integration-page .actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.integration-manage { font-size: 12px; }
.integration-manage summary { color: var(--ink-2); cursor: pointer; padding: 8px; }
.integration-manage[open] { flex-basis: 100%; }
.integration-manage .actions { padding: 8px 0; }
.integration-editor { border: 1px solid var(--line); background: var(--surface); border-radius: 10px; padding: 26px; margin-bottom: 32px; }
.integration-editor > h2 { margin: 0 0 10px; }
.integration-editor-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 26px; }
.integration-editor-head h2 { font-size: 22px; margin: 0; }
.integration-setup-layout { display: grid; grid-template-columns: minmax(200px, .8fr) minmax(0, 1.6fr); gap: 32px; }
.integration-guide { background: var(--inset); border-radius: var(--r); padding: 20px; align-self: start; font-size: 12px; line-height: 1.7; color: var(--ink-2); }
.integration-guide h3 { color: var(--ink); margin: 0 0 10px; }
.integration-guide p { margin: 8px 0; }
.integration-guide ol { padding-left: 18px; margin: 18px 0; }
.integration-guide li { padding-left: 3px; margin-bottom: 14px; }
.integration-guide a { font-weight: 550; color: var(--accent-ink); text-decoration: underline; text-underline-offset: 3px; }
.integration-guide .integration-guide-foot { border-top: 1px solid var(--line); margin-top: 20px; padding-top: 14px; font-size: 11px; }
.integration-setup-form { min-width: 0; }
.integration-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; gap: 20px 18px; margin: 0 0 22px; }
.integration-field { display: flex; flex-direction: column; align-items: stretch; gap: 8px; min-width: 0; height: auto; padding: 0; border: 0; background: transparent; color: var(--ink); font-size: 12px; font-weight: 550; line-height: 1.5; }
.integration-field input, .integration-field select { display: block; width: 100%; min-width: 0; height: 40px; padding: 0 11px; border: 1px solid color-mix(in srgb, var(--ink-2) 38%, var(--surface)); border-radius: var(--r-sm); background: var(--surface); color: var(--ink); font: 13px var(--sans); }
.integration-field input:hover, .integration-field select:hover { border-color: var(--ink-2); }
.integration-field input:focus, .integration-field select:focus { border-color: var(--accent); }
.integration-field small { display: block; font-size: 11px; line-height: 1.6; color: var(--ink-2); font-weight: 400; }
.integration-help { background: var(--inset); border: 1px solid var(--line); border-radius: var(--r-sm); padding: 14px 16px; font-size: 12px; line-height: 1.7; color: var(--ink-2); margin: 0 0 22px; overflow-wrap: anywhere; }
.integration-help strong { color: var(--ink); font-weight: 550; }
.integration-help p { margin: 6px 0 12px; }
.integration-copy { display: flex; align-items: center; gap: 10px; }
.integration-copy code { flex: 1; min-width: 0; font: 11px/1.65 var(--mono); overflow-wrap: anywhere; user-select: all; color: var(--ink); }
.integration-copy .btn { flex-shrink: 0; }
.integration-advanced { border-top: 1px solid var(--line); font-size: 12px; padding: 14px 0; }
.integration-advanced > summary { cursor: pointer; font-weight: 550; color: var(--ink); }
.integration-advanced > summary span { font-weight: 400; color: var(--ink-2); margin-left: 6px; font-size: 11px; }
.integration-advanced > .integration-fields { margin: 18px 0 0; }
.integration-advanced > .note { font-size: 12px; margin-top: 12px; }
.integration-form-note { font-size: 11px; line-height: 1.7; color: var(--ink-2); margin: 14px 0 20px; max-width: 75ch; }
.integration-form-actions { border-top: 1px solid var(--line); padding-top: 20px; margin-top: 22px; display: flex; flex-wrap: wrap; align-items: center; gap: 14px; }
.integration-form-actions .note { font-size: 11px; margin: 0; }
.integration-error { font-size: 12px; color: var(--red); padding: 12px 0; margin: 0; overflow-wrap: anywhere; }
.integration-bot-picker { max-width: 380px; margin: 22px 0; }
#grant-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 24px; margin: 20px 0 26px; }
#grant-actions > .integration-bulk-actions { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 8px; }
#grant-actions > .note { grid-column: 1 / -1; }
#grant-actions h3 { grid-column: 1 / -1; margin: 16px 0 5px; }
.integration-choice { display: flex; align-items: flex-start; gap: 10px; padding: 12px; border: 1px solid var(--line); border-radius: var(--r-sm); font-size: 12px; cursor: pointer; }
.integration-choice:hover { background: var(--hover); }
.integration-choice input { width: 16px; height: 16px; margin: 1px 0 0; flex-shrink: 0; accent-color: var(--accent); }
.integration-choice strong { display: block; font-weight: 500; }
.integration-choice small { display: block; margin-top: 4px; color: var(--ink-2); line-height: 1.55; }
.integration-choice:has(input:checked) { border-color: var(--ink-2); background: var(--inset); }
.integration-channel-form { border: 1px solid var(--line); border-radius: var(--r); padding: 24px; margin-top: 0; background: var(--surface); }
.integration-channel-form > h3 { margin-bottom: 20px; }
.integration-link-result { white-space: pre-wrap; overflow-wrap: anywhere; font: 12px/1.8 var(--mono); }
.integration-page .row { overflow-wrap: anywhere; min-width: 0; }
.integration-page [hidden] { display: none !important; }
@media (max-width: 1100px) {
  .integration-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .integration-owner { display: none; }
  .integration-setup-layout { grid-template-columns: minmax(190px, .8fr) minmax(0, 1.4fr); gap: 22px; }
  .integration-setup-form > .integration-fields { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 760px) {
  .integration-page { padding: 24px 18px 48px; }
  .integration-intro { margin-bottom: 20px; }
  .integration-steps { gap: 15px; grid-template-columns: 1fr; }
  .integration-steps li { padding: 0; }
  .integration-steps li + li { border: 0; }
  .integration-steps strong { margin-bottom: 2px; }
  .integration-section-head { align-items: flex-start; flex-wrap: wrap; gap: 14px; }
  .integration-search { width: 100%; }
  .integration-editor { padding: 20px; }
  .integration-setup-layout { grid-template-columns: minmax(0, 1fr); }
  .integration-guide { padding: 18px; }
  .integration-guide .integration-guide-foot { display: none; }
  .integration-editor-head { flex-wrap: wrap; }
  .integration-fields, #grant-actions { grid-template-columns: minmax(0, 1fr); }
  .integration-field input, .integration-field select, .integration-search input { font-size: 16px; }
  .integration-copy { align-items: flex-start; flex-direction: column; }
  .integration-channel-form { padding: 20px; }
}
@media (max-width: 480px) {
  .integration-grid { grid-template-columns: minmax(0, 1fr); }
  .integration-connection-heading { flex-wrap: wrap; }
  .integration-status { margin-left: 50px; }
  .integration-app-footer .integration-caption { max-width: none; }
}
@media (prefers-reduced-motion: reduce) {
  .integration-page * { transition: none !important; scroll-behavior: auto; }
}

/* One integration task at a time: account lists, app discovery, or chat setup. */
.integration-page { max-width: none; }
.integration-intro { margin-bottom: 20px; }
.integration-intro h2 { font-size: clamp(22px, 2vw, 27px); }
.integration-tabs { display: flex; gap: 24px; border-bottom: 1px solid var(--line); margin-bottom: 14px; overflow-x: auto; }
.integration-tabs > button { flex-shrink: 0; border: 0; border-bottom: 2px solid transparent; background: transparent; padding: 14px 2px; color: var(--ink-2); font: 500 14px var(--sans); cursor: pointer; }
.integration-tabs > button:hover { color: var(--ink); }
.integration-tabs > button[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--accent); }
.integration-sharing { margin: 12px 0 20px; font-size: 11px; }
.integration-flow-nav { margin: 22px 0; }
.integration-flow-nav > .btn { padding-left: 0; margin-bottom: 18px; }
.integration-steps { max-width: none; padding: 0; border: 0; gap: 16px; }
.integration-steps li { padding: 0; align-items: center; opacity: .6; }
.integration-steps li + li { border: 0; }
.integration-steps li[aria-current="step"] { opacity: 1; }
.integration-steps li[aria-current="step"] .integration-step-number { background: var(--ink); border-color: var(--ink); color: var(--surface); }
.integration-steps strong { font-size: 12px; margin: 0; }
.integration-editor { max-width: none; padding: 30px; margin-top: 22px; }
.integration-setup-layout { display: block; }
.integration-guide { padding: 0; background: transparent; font-size: 13px; max-width: 64ch; }
.integration-guide ol { margin: 22px 0; padding-left: 22px; }
.integration-guide li { padding-left: 7px; margin-bottom: 18px; }
.integration-guide .integration-guide-foot { border: 0; margin: 16px 0 22px; padding: 0; }
.integration-form-intro { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; margin-bottom: 24px; }
.integration-form-intro .btn { font-size: 12px; }
.integration-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.integration-app-card { gap: 12px; }
.integration-app-footer { justify-content: flex-end; }
.integration-app-footer > .integration-caption { margin-right: auto; }
.integration-empty { padding: 28px; border-style: solid; background: var(--surface); }
.integration-empty > strong { display: block; font-size: 16px; }
.integration-empty > .btn { margin-top: 20px; }
.integration-channel-form { max-width: none; }
.integration-channel-form > .integration-editor-head { margin-bottom: 26px; }
.integration-page #history .row { display: block; padding-block: 14px; }
.integration-page #history .integration-advanced { border: 0; padding: 8px 0 0; }
@media (max-width: 760px) {
  .integration-tabs { gap: 20px; }
  .integration-tabs > button { font-size: 13px; }
  .integration-steps { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
  .integration-steps li { flex-direction: column; align-items: flex-start; gap: 8px; }
  .integration-steps strong { font-size: 11px; }
  .integration-editor { padding: 22px 18px; }
  .integration-guide .integration-guide-foot { display: block; }
  .integration-grid { grid-template-columns: minmax(0, 1fr); }
  .integration-channel-form { padding: 20px 18px; }
}


/* Approved workspace visual system. Shared tokens also dress the conversation, but
 * page geometry below deliberately excludes its independently sized tool docks.
 * Keep component display/visibility contracts in their original rules above. */
body { line-height: 1.5; }
h1 { font-size: 27px; line-height: 1.2; letter-spacing: -.03em; font-weight: 600; }
.topbar { height: 60px; padding-inline: 32px; gap: 12px; }
.optop { height: 60px; padding-inline: 24px; }
.rail-top { padding: 20px 14px 12px; }
.rail-scroll { padding-inline: 14px; }
.rail-workspace { position: relative; width: 100%; margin: 0 0 6px; }
.rail-workspace .nav-item.ws { width: 100%; margin-bottom: 0; }
.rail-workspace #ws-menu { top: auto; bottom: calc(100% + 6px); left: 0; right: 0; min-width: 0; max-height: min(60dvh, 420px); overflow-y: auto; }
.rail-label { font-family: var(--sans); font-size: 12px; text-transform: none; letter-spacing: 0; padding-top: 22px; }
.nav-item { padding: 9px 12px; margin-bottom: 4px; }
.bot-row { padding: 8px 10px; }

body:not([data-page="chat"]) .topbar h1 { font-size: 27px; line-height: 1.2; letter-spacing: -.03em; }
body:not([data-page="chat"]) .pad { padding: 40px 24px 70px; }
body:not([data-page="chat"]) .pad.hold { width: 100%; min-width: 0; max-width: none; margin-inline: 0; }
body:not([data-page="chat"]) .sec { margin-bottom: 32px; }
body:not([data-page="chat"]) .sec-head { gap: 12px; margin-bottom: 14px; flex-wrap: wrap; }
body:not([data-page="chat"]) .card:not(.flush):not(.tight):not(.snip):not(.stat) { padding: 20px; }
body:not([data-page="chat"]) .grid { gap: 16px; }
body:not([data-page="chat"]) .row { padding-block: 15px; }
body:not([data-page="chat"]) .btn:not(.icon):not(.sm) { min-height: 36px; height: auto; padding-block: 7px; }
body:not([data-page="chat"]) .field:not(.field-tall) { min-height: 40px; height: auto; padding-block: 7px; }
body:not([data-page="chat"]) :is(.fields label, .stack-field label, .fieldhead) { font-size: 14px; color: var(--ink-2); margin-bottom: 7px; }
body:not([data-page="chat"]) :is(.fields input, .fields select, .stack-field input, .stack-field select, .connect-row input, .connect-row select) { height: 40px; }
body:not([data-page="chat"]) .note { font-size: 14px; line-height: 1.6; }
body:not([data-page="chat"]) .tabs { gap: 26px; overflow-x: auto; }
body:not([data-page="chat"]) .tabs :is(button, a) { border-radius: 0; padding: 13px 0; flex-shrink: 0; }
body:not([data-page="chat"]) .tabs :is(button, a):hover,
.integration-tabs > button:hover { background: transparent; }

.integration-page { width: 100%; min-width: 0; max-width: none; padding: 40px 24px 70px; }
.integration-intro h2 { font-size: 27px; line-height: 1.2; letter-spacing: -.03em; }
.integration-page h3 { font-size: 15px; }
.integration-field { font-size: 14px; }
.integration-field input, .integration-field select, .integration-search input { font-size: 15px; }
.integration-page :is(.integration-category, .integration-caption, .integration-status, .integration-form-note, .integration-field small) { font-size: 13px; }
.integration-tabs { gap: 26px; margin-bottom: 28px; }
.integration-tabs > button[aria-selected="true"] { border-bottom-color: var(--ink); }
.integration-editor { border-radius: var(--r); }

@media (max-width: 1080px) {
  body:not([data-page="chat"]) .pad, .integration-page { padding: 32px 28px 56px; }
}
@media (max-width: 760px) {
  .topbar { padding-inline: 18px; }
  .optop { padding-inline: 16px; }
  body:not([data-page="chat"]) .topbar h1 { font-size: 23px; }
  body:not([data-page="chat"]) .pad, .integration-page { padding: 26px 18px 48px; }
  body:not([data-page="chat"]) .sec-head { gap: 8px; }
  body:not([data-page="chat"]) .grid.spend { grid-template-columns: minmax(0, 1fr); }
  body:not([data-page="chat"]) :is(.field input, .field select, .field textarea, .fields input, .fields select, .stack-field input, .stack-field select, .stack-field textarea, .ta),
  .integration-field input, .integration-field select, .integration-search input { font-size: 16px; }
}

/* Match the approved rail wordmark scale without changing its animation. */
.rail .brand .wm { width:100px; height:auto; }


/* Public navigation wraps as a complete row on narrow screens. Keep every
 * destination and the account action available rather than clipping the bar. */
@media (max-width: 600px) {
  .pubtop .in {
    height: auto; min-height: 60px; padding-block: 12px;
    flex-wrap: wrap; gap: 12px 16px;
  }
  .pubtop .brand { flex: 0 0 auto; }
  .pubtop .grow { flex-basis: 0; min-width: 0; }
  .pubtop .svc { white-space: normal; overflow-wrap: anywhere; }
  .pubtop .btn { margin-left: auto; }
}
/* The help contact column inherits a fixed 320px tool-panel width from .side.
 * Its grid owns the width here, including when the FAQ stacks above it. */
body[data-page="help"] .help-split > .side { width: auto; max-width: 100%; }
body[data-page="help"] .help-split > .body { width: auto; max-width: 100%; }
@media (max-width: 1080px) {
  body[data-page="help"] .help-split { grid-template-columns: minmax(0, 1fr); }
}
/* Scalar's fixed toast viewport otherwise retains desktop width on a phone. */
body[data-page="docs"] .scalar-toaster { max-width: calc(100vw - 32px); }

/* Intermediate work stays in one bounded rail; details never widen the chat. */
.step-group { min-width: 0; max-width: 100%; margin: 18px 0; }
.step-rail { display: flex; gap: 6px; max-width: 100%; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: thin; padding: 2px 0 6px; }
.step-rail .step-tab { display: inline-flex; align-items: center; gap: 7px; flex: 0 0 auto; white-space: nowrap; padding: 7px 10px; border: 0; border-bottom: 2px solid transparent; border-radius: 0; background: transparent; color: var(--ink-2); font-size: 14px; }
.step-rail .step-tab:hover { background: transparent; color: var(--ink); }
.step-rail .step-tab[aria-expanded="true"] { color: var(--ink); border-bottom-color: currentColor; }
.step-rail .step-tab.bad { color: var(--bad, #b64444); }
.step-detail { min-width: 0; max-width: 100%; padding: 12px 14px; border-left: 2px solid var(--line); }
.step-detail[hidden] { display: none; }
.step-detail .step-out { max-width: 100%; overflow-x: auto; }
.step-detail .thinking, .step-gist { white-space: pre-wrap; overflow-wrap: anywhere; color: var(--ink-2); }

.bot-top { min-width: 0; flex-wrap: wrap; }
.bot-top b { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bot-top .when { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
.bot-row > .bot, .bot-row > .dot { flex-shrink: 0; }

.step-rail { gap: 20px; }
.step-rail .step-tab { position: relative; }
.step-rail .step-tab + .step-tab::before {
  content: ""; position: absolute; left: -17px; top: calc(50% - 6px);
  width: 12px; height: 12px; background: var(--ink-3); pointer-events: none;
  clip-path: polygon(0 44%, 72% 44%, 43% 15%, 52% 6%, 96% 50%, 52% 94%, 43% 85%, 72% 56%, 0 56%);
}

/* Durable first-contact conversation, in the existing full-width chat column. */
.onboarding { padding: 24px 0; line-height: 1.6; overflow-wrap: anywhere; }
.onboarding[hidden] { display: none; }
.onboarding-intro { white-space: pre-wrap; font-size: 17px; }
.onboarding-question { font-weight: 600; margin: 24px 0 12px; }
.onboarding-options { display: grid; gap: 10px; margin: 16px 0 24px; }
.onboarding-options .btn {
  display: flex; align-items: center; justify-content: flex-start; gap: 14px;
  width: 100%; min-height: 52px; height: auto; padding: 13px 16px;
  border: 1px solid var(--line); border-radius: 10px; background: var(--surface);
  color: var(--ink); font-size: 16px; font-weight: 400; line-height: 1.45;
  white-space: normal; text-align: left;
}
.onboarding-options .btn::before {
  content: ""; flex: 0 0 16px; width: 16px; height: 16px;
  border: 1px solid var(--ink-3); border-radius: 50%;
}
.onboarding-options .btn:hover { background: var(--hover); border-color: var(--ink-3); }
.onboarding-options .btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.onboarding-form { display: grid; gap: 10px; margin: 20px 0 8px; }
.onboarding-form label { font-size: 14px; color: var(--ink-2); }
.onboarding-form textarea {
  width: 100%; box-sizing: border-box; resize: vertical; min-height: 94px;
  padding: 14px 16px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface); color: var(--ink); font: inherit; font-size: 16px; line-height: 1.5;
}
.onboarding-form textarea:focus { outline: 2px solid var(--accent); outline-offset: 2px; }
.onboarding-form .btn { justify-self: start; }
.onboarding-form .onboarding-submit {
  min-height: 44px; padding: 10px 20px; margin-top: 2px; border-radius: 8px;
  background: var(--ink); border: 1px solid var(--ink); color: var(--surface);
  font-size: 15px; font-weight: 600;
}
.onboarding-form .onboarding-submit:hover { opacity: .85; }
.onboarding-actions { display: flex; gap: 8px; margin-top: 8px; }
.onboarding-actions .btn { min-height: 44px; padding: 10px 12px; color: var(--ink-2); font-size: 14px; }
#composer.onboarding-answering { display: none; }
.onboarding-answer { margin: 18px 0; padding-left: 16px; border-left: 2px solid var(--line); white-space: pre-wrap; }
.onboarding-answer p { margin: 6px 0; }
.onboarding-agreement { margin-top: 24px; }
.onboarding-agreement summary { cursor: pointer; font-weight: 600; }

.team-work-directory { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); gap: 20px; margin: 20px 0; }
.team-work-person { border-top: 1px solid var(--line); padding: 12px 0; overflow-wrap: anywhere; }
.team-work-person h3 { font-size: 15px; margin: 0 0 6px; }
.team-work-person p { margin: 5px 0; white-space: pre-wrap; }
.team-work-form { display: grid; gap: 8px; margin: 16px 0; min-width: 0; max-width: none; }
.team-work-form input, .team-work-form textarea, .team-work-form select { box-sizing: border-box; max-width: 100%; }
.team-work-form textarea { resize: vertical; }
.team-work-form .btn { justify-self: start; }
.team-work-check { display: flex; gap: 8px; align-items: center; }
.team-work-check input { width: auto; }

.bot-row.has-unread .bot-body > .bot-top > b::after { content: ""; display: inline-block; width: 6px; height: 6px; margin-left: 8px; border-radius: 50%; background: currentColor; vertical-align: middle; }

/* Shared native controls, including forms outside .field wrappers. */
.team-work-form > input, .team-work-form > textarea, .team-work-form > select {
  width:100%; min-height:44px; padding:10px 12px; border:1px solid var(--line);
  border-radius:var(--r); background:var(--surface); color:var(--ink);
  font:inherit; font-size:16px; line-height:1.5; color-scheme:inherit;
}
.team-work-form > textarea { min-height:88px; }
.team-work-form > label { margin-top:8px; font-weight:500; }
.team-work-form :is(input,textarea,select):focus-visible { outline:2px solid var(--ink-3); outline-offset:2px; }
.team-work-check input { accent-color:var(--ink); width:18px; height:18px; }
body[data-page="invite"] { min-height:100dvh; background:var(--bg); color:var(--ink); }
.invite-shell { width:min(100% - 40px,480px); margin:clamp(48px,12vh,120px) auto; }
.invite-shell > .mark { display:block; width:148px; margin-bottom:48px; }
.invite-shell > .mark > svg { width:100%; height:auto; }
.invite-shell h1 { font-size:32px; line-height:1.2; letter-spacing:-.025em; margin:0 0 16px; }
.invite-shell p { font-size:17px; line-height:1.6; overflow-wrap:anywhere; }
.invite-shell .btn { font-size:17px; width:100%; min-height:48px; justify-content:center; margin-top:20px; }
.invite-shell footer { margin-top:36px; font-size:15px; color:var(--ink-2); }

/* Pending chat messages stay beside their composer controls. */
body[data-page="chat"] .message-queue { max-height:240px; overflow:auto; margin-bottom:12px; border-bottom:1px solid var(--line, #ddd); }
.message-queue-row { display:flex; align-items:center; gap:10px; padding:10px 0; min-width:0; }
.message-queue-text { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.message-queue-row textarea { flex:1; min-width:0; white-space:normal; }
.message-queue-row button { flex-shrink:0; }

/* Personal updates and the owner’s internal collaboration reader. */
.notification-bell { position: relative; display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0; }
.notification-count { font-size: 11px; font-family: var(--mono); border-radius: 999px; padding: 1px 5px; background: var(--ink); color: var(--page); }
.notification-drawer { position: fixed; inset: 0 0 0 auto; margin: 0; width: min(460px, 100vw); max-width: 100vw; height: 100dvh; max-height: 100dvh; padding: 24px; border: 0; border-left: 1px solid var(--line); background: var(--page); color: var(--ink); overflow-y: auto; box-sizing: border-box; }
.notification-drawer::backdrop { background: rgb(0 0 0 / .25); }
.notification-controls { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin: 16px 0; }
.notification-controls h2 { flex: 1; margin: 0; }
.notification-controls [aria-pressed="true"] { background: var(--ink); color: var(--page); }
.notification-content { min-width: 0; }
.notification-item { padding: 20px 0; border-bottom: 1px solid var(--line); overflow-wrap: anywhere; }
.notification-unread { border-left: 2px solid var(--ink); padding-left: 14px; }
.notification-meta { color: var(--ink-2); font-size: 12px; margin: 0 0 8px; }
.notification-title { text-align: left; white-space: normal; font-weight: 600; height: auto; }
.notification-body { margin: 12px 0; }
.notification-body summary { cursor: pointer; color: var(--ink-2); }
.notification-body p { white-space: pre-wrap; }
.collaboration-page { width: 100%; max-width: none; min-width: 0; box-sizing: border-box; }
.collaboration-row, .collaboration-turn { border-bottom: 1px solid var(--line); padding: 24px 0; overflow-wrap: anywhere; }
.collaboration-row > button { text-align: left; height: auto; white-space: normal; }
.collaboration-turn h3 { margin: 0 0 8px; }
.collaboration-text { white-space: pre-wrap; line-height: 1.7; }
.collaboration-activity { margin-top: 16px; }
.collaboration-activity summary { cursor: pointer; }
.collaboration-activity pre { white-space: pre-wrap; overflow-wrap: anywhere; max-width: 100%; }
@media (max-width: 600px) { .notification-drawer { padding: 16px; } .notification-controls .btn { min-height: 40px; } }

/* Workspace settings use one scrollable navigation row on narrow screens. */
.workspace-settings-nav { display:flex; gap:8px; overflow-x:auto; flex-wrap:nowrap; margin-bottom:28px; padding-bottom:8px; }
.workspace-settings-nav .btn { flex:none; white-space:nowrap; }
.workspace-settings-nav [aria-current="page"] { color:var(--page); background:var(--ink); }
[data-settings-section][hidden] { display:none; }
#rename-workspace { align-items:end; flex-wrap:wrap; }
#rename-workspace .field { min-width:0; }

.team-work-columns { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:24px 32px; margin-top:16px; align-items:start; }
.team-work-card { min-width:0; padding-top:16px; border-top:1px solid var(--line); }
.team-work-card > h3 { font-size:16px; font-weight:600; margin:0 0 12px; }
.team-work-card > p { font-size:14px; margin:0 0 12px; }
.team-work-card .team-work-form { gap:6px; margin:12px 0 0; }
.team-work-card .team-work-form > label { margin-top:4px; font-size:14px; }
.team-work-card .team-work-form > textarea { min-height:64px; }
.team-work-timing h4 { font-size:14px; margin:16px 0 8px; }
.team-work-card .team-work-directory { margin:0; gap:12px; }
.team-work-card .team-work-person { font-size:14px; }
@media(max-width:800px) { .team-work-columns { grid-template-columns:minmax(0,1fr); gap:20px; } }
