/* Shadow Shell Chat
   ================================================================================
   Direction: a TUI program, not a chat app in a terminal costume.

   Monochrome phosphor. There is no "accent colour on a dark UI" here -- the whole
   interface is one green, graded into five intensities (rule / dim / fore / hot),
   and emphasis is spent as BRIGHTNESS rather than as hue. Amber means one thing
   only, "this is running right now", and red means one thing only, "this failed".
   Nothing else is ever coloured.

   Two devices carry it, and nothing else is allowed to be loud:

   1. REVERSE VIDEO for state. Selection, the hovered menu row, the toast, the
      default button and the status line are solid phosphor with dark text -- the
      way a console has shown "this one" since VT100s. Never a tint.
   2. THE TITLED FRAME for windows. Every dialog-class surface (confirm sheet,
      empty state, login, command palette) is a hairline box with its label
      punched into the top rule, and a hard 3px offset shadow with no blur --
      Turbo Vision, not Material.

   Type is one lineage, two roles: IBM 3270 (the face off an IBM 3278 terminal) for
   every piece of chrome, and IBM Plex Mono for everything a person reads. There is
   no sans-serif anywhere. Prose is monospaced at an 80-column measure because that
   is what this program is.

   Colour survives in exactly one place: inside a code block, where the ANSI hues
   mark tokens. Green inside a fence means "plain text"; anything else is syntax.
   ============================================================================ */

@font-face { font-family: "3270"; src: url("/fonts/3270-regular.woff2") format("woff2"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Plex Mono"; src: url("/fonts/plex-mono-400.woff2") format("woff2"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Plex Mono"; src: url("/fonts/plex-mono-500.woff2") format("woff2"); font-weight: 500; font-display: swap; }

:root {
  /* --- phosphor, dark to bright ------------------------------------------- */
  --void:  #050806;   /* screen glass */
  --sunk:  #0B120D;   /* a filled well: code, inputs, tool bodies */
  --rule:  #1D3D28;   /* hairlines, panel edges, inert marks */
  --dusk:  #36684A;   /* recessive but still readable: counts, ticks, gutters */
  --dim:   #4E9468;   /* labels, timestamps, frames, the status bar ground */
  --fore:  #7FD99A;   /* body text */
  --hot:   #D9FFE6;   /* active, selected, emphatic */

  /* --- the only two non-green signals in the chrome ------------------------ */
  --amber: #FFC04A;   /* RUNNING. Nothing else. */
  --alarm: #FF5F52;   /* FAILED. Nothing else. */
  /* A third signal, added deliberately rather than by drift: OUTSIDE THIS
     MACHINE. External consultation is the only thing in the product that sends
     content off the box, and it cannot borrow amber (which means running) or
     red (which means failed) without diluting both. Cold blue against an
     all-green chrome reads as foreign at a glance, which is the entire point --
     you should be able to tell from across the room that a transcript left. */
  --out:   #7FB4FF;   /* LEFT THIS SERVER. Nothing else. */

  /* --- ANSI, and only inside a fence -------------------------------------- */
  --c-key: #D08BE0;
  --c-fn:  #6FC9DE;
  --c-str: #DCC77F;
  --c-num: #E8846A;

  /* --- type --- */
  --vt:   "3270", "Plex Mono", ui-monospace, monospace;
  --mono: "Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;

  /* Seven sizes, retuned for a monospaced body: mono reads a shade larger than
     sans at the same pixel size, so the body sits at 13.5 rather than 14.5. */
  --t-2xs: 10px;
  --t-xs:  11px;
  --t-sm:  12px;
  --t-md:  13.5px;
  --t-lg:  15px;
  --t-xl:  19px;
  --t-2xl: 26px;

  /* --- space --- */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px; --s5: 24px; --s6: 34px;

  --rail: 68px;
  --axis: 52px;   /* the x of the transcript rail; every mark centres on it */
  --bar:  44px;
  --side: 258px;
  --stat: 24px;   /* status-line height -- the sidebar foot and the compose hint
                     share it so they read as ONE bar across the bottom */
  --lead: 1.6;
  /* ONE COLUMN. Do not write `ch` in this file.
     Measured, because the unit lies here: our subset of Plex Mono renders at
     .602em per character, but reports `1ch` as .5em -- Chrome's "no usable 0
     glyph" fallback. So `80ch` sets 66 columns, not 80, and the figure moves if
     the font is ever re-subset. An em is what the text actually does. */
  --col: .602em;
  --measure: calc(80 * var(--col));   /* the page is eighty columns wide */
  /* rail + measure + the reel's right padding: the column, its margin notes and
     the composer all end on the same x. Left as a calc so it follows --measure
     down a step when the density changes; a fixed px value drifts out of
     alignment in compact. */
  --page: calc(var(--rail) + var(--measure) + var(--s5));
}

/* Compact: less air, one step down on prose, same information. For people who
   keep two of these side by side on a 13-inch screen. */
html[data-density="compact"] {
  --t-md: 12.5px; --t-sm: 11.5px; --t-lg: 14px; --t-xl: 17px; --t-2xl: 22px;
  --s1: 3px; --s2: 6px; --s3: 9px; --s4: 12px; --s5: 17px; --s6: 24px;
  --bar: 38px; --rail: 58px; --axis: 44px; --lead: 1.52;
}

/* Light: the same thesis printed on paper instead of thrown at phosphor.
   One green, graded as ink density instead of brightness; reverse video
   stays -- a selected row is now solid ink carrying paper text; amber, red
   and blue keep their single meanings, darkened to survive white. Glow dies
   (paper does not phosphoresce) and the scanline relaxes into faint ruling.
   Per-browser like density: daylight on a phone and midnight at a desk may
   disagree, and both are right. */
html[data-skin="light"] {
  color-scheme: light;
  --void:  #F3F7F2;   /* screen paper */
  --sunk:  #E9F0E8;   /* a filled well */
  --rule:  #C6D4C5;   /* hairlines, frame edges */
  --dusk:  #6E8A73;   /* recessive: counts, gutters, ticks */
  --dim:   #476C51;   /* labels, timestamps, status-bar ground */
  --fore:  #1F3D2A;   /* body text */
  --hot:   #10331D;   /* active, selected, emphatic: ink, not glare */

  --amber: #8F6400;   /* RUNNING */
  --alarm: #C13A2E;   /* FAILED */
  --out:   #3563B0;   /* LEFT THIS SERVER */

  --c-key: #7C3A8C; --c-fn: #1F6E85; --c-str: #7A661C; --c-num: #AE4A32;
}

/* The handful of rules that hardcode the dark screen -- dark-on-reverse
   tints, phosphor glow, the CRT veil -- translated to paper. Everything
   else rides the tokens untouched. One attribute selector up on each, so
   these win without !important and stop applying the moment the skin is
   off. Hard 3px shadows stay: light Turbo Vision kept them too. */
html[data-skin="light"] body::after {
  background: repeating-linear-gradient(to bottom,
    rgba(31, 61, 42, .05) 0 1px, transparent 1px 3px);
}
html[data-skin="light"] .mark b,
html[data-skin="light"] .prose h2,
html[data-skin="light"] .blank h2 { text-shadow: none; }
html[data-skin="light"] .cursor { box-shadow: none; }
html[data-skin="light"] .row.on .tick { color: rgba(243, 247, 242, .66); }
html[data-skin="light"] .rrow:hover .tick { color: rgba(243, 247, 242, .7); }
html[data-skin="light"] .prow.on .ph { color: rgba(243, 247, 242, .72); }
html[data-skin="light"] .prow.on kbd,
html[data-skin="light"] .menu button:hover kbd { border-color: rgba(243, 247, 242, .4); }
html[data-skin="light"] .dl.add { background: rgba(31, 90, 50, .14); }
html[data-skin="light"] .dl.del { background: rgba(193, 58, 46, .1); }
html[data-skin="light"] .go.stop:hover,
html[data-skin="light"] .go.danger:hover { background: #A9281D; border-color: #A9281D; }
html[data-skin="light"] .scrim { background-color: rgba(240, 246, 240, .9); }

/* Classic: the same program wearing office clothes. For the customer whose
   muscle memory is Open WebUI, not a TUI -- nothing terminal survives except
   the vocabulary. Sans instead of 3270/Plex, white cards on a gray page,
   soft shadows instead of the stamped 3px frame, rounded corners, sentence
   case, and the brand green demoted from phosphor ramp to quiet accent.
   Mono keeps the one place it is content rather than costume: code fences.
   Same per-browser switch as the other skins; the login page stays dark. */
html[data-skin^="classic"] {
  color-scheme: light;
  --void:  #F7F8FA;   /* the page */
  --sunk:  #FFFFFF;   /* inputs, cards, wells: white on the page */
  --rule:  #E4E7EC;   /* hairlines */
  --dusk:  #98A2B3;   /* recessive: counts, gutters, ticks */
  --dim:   #667085;   /* labels, timestamps */
  --fore:  #182230;   /* body text */
  --hot:   #15803D;   /* the brand green, now an accent */

  --amber: #B45309;   /* RUNNING */
  --alarm: #DC2626;   /* FAILED */
  --out:   #2563EB;   /* LEFT THIS SERVER */

  --c-key: #7C3A8C; --c-fn: #1F6E85; --c-str: #7A661C; --c-num: #AE4A32;

  --vt:   -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

/* The de-terminalize pass. Tokens carry colour and type everywhere; these
   are the rules that hardcode the CONSOLE -- the CRT veil, glow, the hard
   stamped frame, shouting uppercase, and reverse-video state. Each gets one
   attribute-scoped override, so it wins without !important and vanishes the
   moment the skin is off. State becomes the classic idiom instead: a soft
   tint and a coloured text, never a filled bar. */
html[data-skin^="classic"] body::after { background: none; }
html[data-skin^="classic"] .mark b { text-shadow: none; font-weight: 600; }
html[data-skin^="classic"] .cursor {
  width: 2px; border-radius: 1px; box-shadow: none;
}
html[data-skin^="classic"] .stamp,
html[data-skin^="classic"] .mark i,
html[data-skin^="classic"] .go,
html[data-skin^="classic"] .ghost,
html[data-skin^="classic"] .prose h2, html[data-skin^="classic"] .prose h3,
html[data-skin^="classic"] .prose h4, html[data-skin^="classic"] .prose h5,
html[data-skin^="classic"] .prose th,
html[data-skin^="classic"] .pgh {
  text-transform: none; letter-spacing: normal;
}
html[data-skin^="classic"] .prose h2, html[data-skin^="classic"] .prose h3,
html[data-skin^="classic"] .prose h4, html[data-skin^="classic"] .prose h5 {
  font-weight: 600;
}
html[data-skin^="classic"] .palette,
html[data-skin^="classic"] .sheet,
html[data-skin^="classic"] .blank,
html[data-skin^="classic"] .gate form,
html[data-skin^="classic"] .menu,
html[data-skin^="classic"] .fpanel {
  border-radius: 12px;
  box-shadow: 0 12px 32px rgba(16,24,40,.14);
  border-color: var(--rule);
}
html[data-skin^="classic"] .go,
html[data-skin^="classic"] .ghost,
html[data-skin^="classic"] .compose textarea,
html[data-skin^="classic"] input,
html[data-skin^="classic"] textarea,
html[data-skin^="classic"] select,
html[data-skin^="classic"] button { border-radius: 8px; }
html[data-skin^="classic"] .pill,
html[data-skin^="classic"] .chip { border-radius: 999px; }
html[data-skin^="classic"] .prow { border-radius: 6px; }
html[data-skin^="classic"] .toast {
  background: #FFFFFF; color: var(--fore); border: 1px solid var(--rule);
  box-shadow: 0 4px 16px rgba(16,24,40,.16);
}
html[data-skin^="classic"] .toast.bad {
  background: #FEECEB; color: #B42318; border-color: #F5B5B0;
}
html[data-skin^="classic"] .scrim { background-color: rgba(16,24,40,.45); }
/* Punched sheet titles become plain headings sitting inside the card. */
html[data-skin^="classic"] .sheet > .stamp:first-child,
html[data-skin^="classic"] .blank > .stamp:first-child {
  position: static; background: none; padding: 0;
  margin-bottom: var(--s2); display: block;
}
html[data-skin^="classic"] .sheet h2, html[data-skin^="classic"] .blank h2 { font-weight: 600; }
/* The palette's ":" prompt is a console tell; a plain field says web app. */
html[data-skin^="classic"] .palette::before { content: none; }
html[data-skin^="classic"] .palette > input { padding-left: var(--s4); }
/* The default action: a normal primary button, green like the brand. */
html[data-skin^="classic"] .go {
  background: var(--hot); border-color: var(--hot); color: #fff; font-weight: 600;
}
html[data-skin^="classic"] .go:hover { background: #166B34; border-color: #166B34; }
html[data-skin^="classic"] .go.stop, html[data-skin^="classic"] .go.danger {
  background: var(--alarm); border-color: var(--alarm); color: #fff;
}
html[data-skin^="classic"] .go.stop:hover, html[data-skin^="classic"] .go.danger:hover {
  background: #B42318; border-color: #B42318;
}
html[data-skin^="classic"] .ghost { font-weight: 500; }
/* Reverse video -> soft tint. Selected and hovered rows keep their text
   colour and sit on a wash instead of becoming a filled bar. */
html[data-skin^="classic"] .row.on { background: #E8F1EA; color: var(--fore); }
html[data-skin^="classic"] .row.on:hover { background: #DCEAE0; color: var(--fore); }
html[data-skin^="classic"] .row.on .tick { color: var(--dim); }
html[data-skin^="classic"] .row.on .dots,
html[data-skin^="classic"] .row.on .dots:hover { color: var(--dim); }
html[data-skin^="classic"] .row.on .dot { background: var(--hot); }
html[data-skin^="classic"] .row.on .dot.note { background: none; border-color: var(--dim); }
html[data-skin^="classic"] .row.on .cflag,
html[data-skin^="classic"] .row.on .xflag { color: var(--dim); }
html[data-skin^="classic"] .rrow:hover { background: #EEF2F4; color: var(--fore); }
html[data-skin^="classic"] .rrow:hover .tick { color: var(--dim); }
html[data-skin^="classic"] .prow.on { background: #E8F1EA; color: var(--fore); }
html[data-skin^="classic"] .prow.on .ph { color: var(--dim); }
html[data-skin^="classic"] .prow.on kbd { border-color: var(--rule); }
html[data-skin^="classic"] .dl.add { background: rgba(21,128,61,.10); }
html[data-skin^="classic"] .dl.del { background: rgba(220,38,38,.08); }
/* Code keeps Plex Mono: inside a fence the mono face is content. */
html[data-skin^="classic"] .prose pre,
html[data-skin^="classic"] .prose code,
html[data-skin^="classic"] kbd {
  font-family: "Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;
}
html[data-skin^="classic"] :focus-visible { outline: 2px solid var(--hot); outline-offset: 1px; }

* { box-sizing: border-box; }
html, body, #root { height: 100%; }
html { background: var(--void); }
body {
  margin: 0; background: var(--void); color: var(--fore);
  font: 400 var(--t-md)/var(--lead) var(--mono);
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

/* The glass. Two scanlines' worth of grille at 22% over everything, including
   the overlays -- a CRT does not stop at the dialog. It must never eat a click. */
body::after {
  content: ""; position: fixed; inset: 0; z-index: 100; pointer-events: none;
  background: repeating-linear-gradient(to bottom, rgba(2,6,3,.26) 0 1px, transparent 1px 3px);
}

#root { display: flex; flex-direction: column; overflow: hidden; }
:focus-visible { outline: 1px dashed var(--hot); outline-offset: 1px; }
button, input, textarea, select { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; padding: 0; }
input, textarea { caret-color: var(--hot); }
a { color: var(--hot); text-underline-offset: 2px; }
::selection { background: var(--fore); color: var(--void); }

/* A console label: the terminal face, uppercase, tracked, dim. Every piece of
   chrome wears one, so the interface reads as a program and not a website. */
.stamp {
  font-family: var(--vt); font-weight: 400;
  font-size: var(--t-xs); letter-spacing: .13em; text-transform: uppercase;
  color: var(--dim);
}
.sp { flex: 1; }

/* The only two animations in the build. Both mean "right now". */
.pulse { color: var(--amber); animation: blink 1.1s steps(2) infinite; }
@keyframes blink { 50% { opacity: 0; } }

.boot { height: 100%; display: grid; place-items: center; }
.boot .stamp { font-size: var(--t-lg); color: var(--fore); }
.boot .stamp::after { content: "█"; margin-left: .3em; animation: blink 1.05s steps(2) infinite; }

/* ============================================================== top strip == */

.top {
  height: var(--bar); flex: none; display: flex; align-items: center; gap: var(--s4);
  padding: 0 var(--s3) 0 var(--s4); border-bottom: 1px solid var(--rule);
  background: var(--void); position: relative; z-index: 5;
}
.top .icon { display: none; color: var(--dim); font-size: 18px; line-height: 1; padding: 0 var(--s1); }
.mark { display: flex; align-items: baseline; gap: 8px; flex: none; }
.mark b {
  font-family: var(--vt); font-weight: 400; font-size: var(--t-md);
  letter-spacing: .2em; text-transform: uppercase; color: var(--hot);
  text-shadow: 0 0 7px rgba(150,255,185,.3);
}
.mark i {
  font-style: normal; font-family: var(--vt); font-size: var(--t-xs);
  color: var(--dim); letter-spacing: .13em; text-transform: uppercase;
}
/* The wordmark gave way to the mark itself: the favicon at topbar scale.
   icon-192, not favicon-32, so it is sharp on 2x screens -- and already
   in cache, since index.html declares it an icon. */
.mark img.mlogo {
  height: 22px; width: 22px; display: block; flex: none;
  align-self: center; border-radius: 4px;
}
.top .now {
  flex: 1; min-width: 0; font-size: var(--t-sm);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--dim);
}

/* The context meter, cut into cells. A meter on a console is discrete -- you
   read how many blocks are lit, not how far along a smooth bar is. */
.gauge { display: flex; align-items: center; gap: var(--s2); flex: none; }
.gauge .bar {
  position: relative; width: 168px; height: 11px; background: var(--sunk);
  border: 1px solid var(--rule); display: flex; overflow: hidden;
}
.gauge .bar::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: repeating-linear-gradient(to right, transparent 0 5px, var(--void) 5px 6px);
}
.gauge .floor { background: var(--rule); flex: none; }
.gauge .used  { background: var(--dim); flex: none; transition: width .35s ease; }
.gauge.warn .used { background: var(--alarm); }
.gauge .read { font-family: var(--vt); font-size: var(--t-xs); color: var(--fore); }
.gauge .read i { font-style: normal; color: var(--dim); }

.pill {
  font-family: var(--vt); font-size: var(--t-xs); color: var(--fore);
  background: var(--sunk); border: 1px solid var(--rule); padding: 3px var(--s2); flex: none;
  max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pill::after { content: " ▾"; color: var(--dim); }
.pill:hover { border-color: var(--dim); color: var(--hot); }
.pill.off { color: var(--alarm); border-color: color-mix(in srgb, var(--alarm) 45%, var(--rule)); }
.top .who { flex: none; }

/* Seat blocked. Persists until an operator acts, so it holds ground rather than
   toasting away -- and a fatal banner on a console is reverse-video red. */
.seatbar {
  flex: none; padding: var(--s2) var(--s4);
  background: var(--alarm); color: var(--void);
  font: 400 var(--t-sm)/1.45 var(--vt); letter-spacing: .06em;
}

/* Workspace storage past 80%. Not amber -- amber means RUNNING -- so it is the
   seat bar's shape in the hot ink on a sunk well; at 100% it becomes a seatbar. */
.storebar {
  flex: none; padding: var(--s2) var(--s4);
  background: var(--sunk); color: var(--hot);
  border-bottom: 1px solid var(--rule);
  font: 400 var(--t-sm)/1.45 var(--vt); letter-spacing: .06em;
}

/* ================================================================= layout == */

.shell { flex: 1; display: flex; min-height: 0; }

/* ================================================================ sidebar == */
/* Panels do not stack surfaces. Everything is the same glass; separation is a
   hairline, the way it is in a real full-screen program. */

.side {
  width: var(--side); flex: none; border-right: 1px solid var(--rule);
  background: var(--void); display: flex; flex-direction: column; min-height: 0;
}
/* (2026-09-30) Desktop sidebar collapse. The burger is visible at every
   width now; on >=861px it toggles html[data-sideoff] (set pre-paint from
   localStorage in index.html). Scoped inside this media query so the phone
   drawer at <=860px keeps its own class and its own semantics, and the
   width transition never animates the drawer sliding. */
@media (min-width: 861px) {
  .top .icon#burger { display: block; }
  .side { transition: width .16s ease, border-right-width .16s ease; }
  html[data-sideoff="1"] .side {
    width: 0; min-width: 0; border-right-width: 0; overflow: hidden;
  }
  html[data-sideoff="1"] .top .icon#burger { color: var(--dim); }
}
/* The drawer's close-target on a phone. Inert everywhere else -- the rule that
   gives it a size and a layer lives in the mobile block at the foot of this
   file, so on a desktop it is not even a flex item in .shell.
   Deliberately NOT called .scrim: that name is already the modal/sheet
   backdrop below (z-index 80, display:grid), and since it is defined later it
   would win on equal specificity and cover the entire app. */
.sidescrim { display: none; }
.sidetop { padding: var(--s3); border-bottom: 1px solid var(--rule); }
.find {
  width: 100%; background: var(--sunk); border: 1px solid var(--rule);
  padding: 6px var(--s2); font-size: var(--t-sm); color: var(--hot);
}
.find::placeholder { color: var(--dim); }
.find:focus { outline: none; border-color: var(--fore); }
.find::-webkit-search-cancel-button { filter: grayscale(1) opacity(.5); }
.sidetop .acts { display: flex; gap: var(--s1); margin-top: var(--s2); }
.sidetop .acts button {
  flex: 1; border: 1px solid var(--rule); padding: 3px 0; text-align: center;
  color: var(--dim);
}
.sidetop .acts button:hover { color: var(--void); background: var(--dim); border-color: var(--dim); }

.side .scroll { flex: 1; overflow-y: auto; padding-bottom: var(--s5); }

.grp { display: block; }
.grp.drop { outline: 1px dashed var(--hot); outline-offset: -1px; }
.ghead {
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s4) var(--s3) var(--s1); cursor: pointer; user-select: none;
}

/* Folder reordering. The insertion point is a rule drawn on the edge the folder
   will land on. Nothing reflows while dragging on purpose -- a list that opens
   a gap under the cursor moves the target you are aiming at. Solid rather than
   the dashed outline above so "drop INTO this folder" and "drop BESIDE it" are
   tellable apart at a glance, since both can be live at the same moment. */
.ghead.dropbefore { box-shadow: inset 0 2px 0 0 var(--hot); }
.ghead.dropafter  { box-shadow: inset 0 -2px 0 0 var(--hot); }
.ghead.dragging { opacity: .45; }
.ghead:hover .stamp { color: var(--hot); }
.ghead .stamp { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ghead .n { font-family: var(--vt); font-size: var(--t-2xs); color: var(--dusk); }

/* A collapsed node in a tree is [+] and an open one is [-]. The markup ships a
   "▸" here; it is replaced rather than rotated, because a console does not
   animate its tree. */
.fold { font-size: 0; flex: none; color: var(--dim); }
.fold::before {
  content: "[+]"; font-family: var(--vt); font-size: var(--t-xs); letter-spacing: 0;
}
.fold.open::before { content: "[-]"; }
.ghead:hover .fold { color: var(--hot); }

.row {
  display: flex; align-items: center; gap: var(--s2); width: 100%;
  padding: 4px var(--s2) 4px var(--s3); text-align: left; color: var(--fore);
  font-size: var(--t-sm); cursor: pointer; user-select: none;
}
.row.sub { padding-left: var(--s5); }
.row:hover { background: var(--rule); color: var(--hot); }
/* Reverse video, not a tint: this is the one that is open. */
.row.on { background: var(--dim); color: var(--void); }
.row.on:hover { background: var(--fore); color: var(--void); }
.row .txt { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.row .tick { font-family: var(--vt); font-size: var(--t-2xs); color: var(--dusk); flex: none; }
.row:hover .tick { color: var(--dim); }
.row.on .tick { color: rgba(5,8,6,.66); }
.row .dot { width: 6px; height: 6px; flex: none; background: var(--dusk); }
.row .dot.note { background: none; border: 1px solid var(--dusk); }
.row:hover .dot { background: var(--dim); }
.row:hover .dot.note { background: none; border-color: var(--hot); }
.row.on .dot { background: var(--void); }
.row.on .dot.note { background: none; border-color: var(--void); }
.row.live .dot { background: var(--amber); border-color: var(--amber); animation: blink 1.2s steps(2) infinite; }
.row .dots, .ghead .dots {
  flex: none; color: var(--dim); opacity: 0; font-size: 13px; line-height: 1;
  padding: 0 3px; font-family: var(--vt);
}
.row:hover .dots, .ghead:hover .dots, .row .dots:focus-visible { opacity: 1; }
.row.on .dots { color: var(--void); }
.row .dots:hover, .ghead .dots:hover { color: var(--hot); }
.row.on .dots:hover { color: var(--void); text-decoration: underline; }
.ren {
  flex: 1; min-width: 0; background: var(--sunk); border: 1px solid var(--hot);
  color: var(--hot); font-size: var(--t-sm); padding: 0 var(--s1); outline: none;
}
.empty { padding: var(--s1) var(--s3) var(--s2) var(--s5); color: var(--dusk); font-size: var(--t-sm); }

/* --- the status line, left half ------------------------------------------- */
/* This and .hint below are the same bar. Keep their heights identical. */
/* Three items in 258px, measured at 11px x .13em tracking: the readout, then
   "Commands", then "Sign out" -- about 193px of the 194 the row has once the
   padding and gaps are taken out, and "12 waiting" is wider still. So the gap
   comes down a rung and the row nominates who yields: the buttons hold their
   width, because half a control is not a control, and the readout is the one
   that ellipses. It is the only item here that can lose a character and still
   say what it came to say. */
.sidefoot {
  flex: none; height: var(--stat); display: flex; align-items: center; gap: var(--s1);
  padding: 0 var(--s3); background: var(--dim); color: var(--void); overflow: hidden;
}
.sidefoot .stamp { color: var(--void); white-space: nowrap; }
.sidefoot > .stamp:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* (2026-10-02) The two controls here read as buttons, not as more readout:
   boxed softkeys in the status bar, the same idiom as the + CHAT row above. */
.sidefoot button { padding: 0 6px; flex: none; border: 1px solid var(--void); line-height: 16px; }
.sidefoot button:hover { background: var(--void); color: var(--hot); }
.sidefoot .out:hover { background: var(--alarm); color: var(--void); }

/* ============================================================== main pane == */

.main { flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; position: relative; }

/* The status line runs edge to edge. .sidefoot paints the sidebar's share of it
   and .hint paints the composer's; this paints whatever is left, so the bar does
   not stop dead halfway across a screen with no chat open. */
.main::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: var(--stat);
  background: var(--dim); pointer-events: none; z-index: 0;
}
/* Reserve the scrollbar gutter whether or not there is a scrollbar: without it
   the centred transcript sits 5px left of the centred composer, and the two
   right edges that the whole layout is built on stop lining up. */
.tape {
  flex: 1; overflow-y: auto; overflow-x: hidden; scrollbar-gutter: stable;
  position: relative; z-index: 1;
}
.reel {
  max-width: var(--page); margin: 0 auto;
  padding: var(--s4) var(--s5) calc(var(--s6) + var(--stat)) 0;
  position: relative; min-height: 100%;
}

/* --- the rail ------------------------------------------------------------- */
/* A broken vertical rule, so it reads as a drawn column rather than a border.
   Every mark on it centres on --axis. */
.reel::before {
  content: ""; position: absolute; left: var(--axis); top: 0; bottom: 0; width: 1px;
  background: repeating-linear-gradient(to bottom, var(--rule) 0 4px, transparent 4px 8px);
}
/* No transcript, no rail -- otherwise it hangs down an empty screen with nothing
   pinned to it. */
.reel:has(.blank)::before { display: none; }

.entry { position: relative; padding: 0 0 2px var(--rail); min-height: 24px; }

.stampline { position: absolute; left: 0; top: 2px; width: var(--rail); height: 18px; }
.stampline time {
  position: absolute; left: 0; width: calc(var(--axis) - 10px); text-align: right;
  font-family: var(--vt); font-size: var(--t-2xs); line-height: 18px; color: var(--dusk);
}
.entry:hover .stampline time { color: var(--dim); }

/* The marks. Intensity, not hue: an inert event is rule-level, a live one is
   amber, a failed one is red. */
.node { position: absolute; left: calc(var(--axis) - 4px); top: 5px; width: 9px; height: 9px; }

.node.n-prompt { background: var(--hot); }
.node.n-answer { border: 1px solid var(--fore); background: var(--void); }
.node.n-note   { background: var(--alarm); }
.node.n-wait   { border: 1px solid var(--rule); background: var(--void); }

/* A tool's mark lies along the rail itself and is as LONG as the call took --
   the one piece of chrome in here that carries information rather than
   decorating. Height is set inline, log-scaled, 11px to 71px. Absolute, so a
   95-second bar overhangs its own row instead of pushing the card down. */
.node.n-tool {
  left: calc(var(--axis) - 1px); top: 4px; width: 3px; height: 11px;
  background: var(--rule); transition: height .3s ease;
}
.node.n-tool::after {                      /* the lit cap of the column */
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 3px;
  background: var(--fore);
}
.node.n-tool.is-run { background: color-mix(in srgb, var(--amber) 40%, var(--void)); }
.node.n-tool.is-run::after { background: var(--amber); animation: blink 1.1s steps(2) infinite; }
.node.n-tool.is-ok  { background: var(--rule); }
.node.n-tool.is-ok::after  { background: var(--fore); }
.node.n-tool.is-bad { background: color-mix(in srgb, var(--alarm) 32%, var(--void)); }
.node.n-tool.is-bad::after { background: var(--alarm); }
/* Cut off by Stop. Deliberately neither amber nor alarm -- it is not running
   and it did not fail, so it recedes. The mark keeps its real length, so the
   rail still shows how far the call got before it was stopped. */
.node.n-tool.is-cut { background: color-mix(in srgb, var(--rule) 60%, var(--void)); }
.node.n-tool.is-cut::after { background: var(--dusk); }

/* --- turn separator ------------------------------------------------------- */
/* TURN 3 ──────────────────────  A ruled break with the label punched through. */
.turnsep {
  position: relative; padding-left: var(--rail); margin: var(--s6) 0 var(--s4);
  display: flex; align-items: center;
}
.turnsep::after {
  content: ""; position: absolute; left: var(--axis); right: 0; top: 50%; height: 1px;
  background: var(--rule);
}
.turnsep .stamp {
  position: relative; z-index: 1; background: var(--void); padding-right: var(--s3);
  color: var(--dim);
}

/* --- prompt --------------------------------------------------------------- */
/* What the operator typed, at a shell prompt, hung in the margin. */
.prompt { padding: 0 0 var(--s4); position: relative; }
.prompt .body {
  font-size: var(--t-lg); color: var(--hot); line-height: 1.5;
  white-space: pre-wrap; word-break: break-word; max-width: var(--measure);
  padding-left: calc(2 * var(--col)); text-indent: calc(-2 * var(--col));
}
.prompt .body::before { content: "$"; color: var(--dim); margin-right: var(--col); }
.promptact { opacity: 0; transition: opacity .12s; margin-top: var(--s1); }
.entry:hover .promptact { opacity: 1; }

/* --- answer prose --------------------------------------------------------- */
.answer { padding: 0 0 var(--s4); }
.answeract {
  display: flex; align-items: center; gap: var(--s3); margin-top: var(--s2);
  opacity: 0; transition: opacity .12s;
}
.entry:hover .answeract { opacity: 1; }

.prose { max-width: var(--measure); }
.prose > :first-child { margin-top: 0; }
.prose > :last-child { margin-bottom: 0; }
.prose p { margin: 0 0 .8em; }
.prose h2, .prose h3, .prose h4, .prose h5 {
  font-family: var(--vt); font-weight: 400; text-transform: uppercase;
  letter-spacing: .1em; color: var(--hot); margin: 1.6em 0 .5em; line-height: 1.3;
}
.prose h2 {
  font-size: var(--t-xl); border-bottom: 1px solid var(--rule); padding-bottom: .2em;
  text-shadow: 0 0 7px rgba(150,255,185,.22);
}
.prose h3 { font-size: var(--t-lg); }
.prose h4 { font-size: var(--t-md); color: var(--fore); }
.prose h5 { font-size: var(--t-sm); color: var(--dim); }
.prose ul, .prose ol { margin: 0 0 .8em; padding-left: calc(3 * var(--col)); }
.prose ul { list-style: none; }
.prose ul > li { position: relative; }
.prose ul > li::before {
  content: "▪"; position: absolute; left: calc(-2 * var(--col)); color: var(--dusk); font-family: var(--vt);
}
.prose ol { list-style: decimal; }
.prose ol::marker, .prose li::marker { color: var(--dim); }
.prose li { margin: .25em 0; }
.prose li > ul, .prose li > ol { margin-top: .25em; margin-bottom: .1em; }
.prose li.tick::before { display: none; }

/* [ ] and [x] -- a console checkbox is typed, not drawn. */
.task { font-family: var(--vt); color: var(--dim); margin-right: var(--col); }
.task::before { content: "[ ]"; }
.task.on { color: var(--fore); }
.task.on::before { content: "[x]"; }

.prose blockquote {
  margin: 0 0 .8em; padding: 2px 0 2px var(--s3);
  border-left: 1px solid var(--dim); color: var(--dim);
}
.prose hr { border: 0; border-top: 1px solid var(--rule); margin: var(--s5) 0; }
.prose strong { color: var(--hot); font-weight: 500; }
.prose em { font-style: italic; color: var(--hot); }
.prose s { color: var(--dusk); }
.prose code {
  font-family: var(--mono); font-size: .92em; background: var(--sunk);
  border: 1px solid var(--rule); padding: 0 4px; color: var(--hot);
  word-break: break-word;
}

/* --- tables --------------------------------------------------------------- */
/* A table is data, so it gets rules and alignment, not zebra stripes. */
.tablewrap { overflow-x: auto; margin: 0 0 .9em; max-width: 100%; }
.prose table { border-collapse: collapse; font-size: var(--t-sm); min-width: 100%; }
.prose th, .prose td {
  text-align: left; padding: 4px var(--s3) 4px 0; vertical-align: top;
  border-bottom: 1px solid var(--rule);
}
.prose th {
  font-family: var(--vt); font-size: var(--t-xs); letter-spacing: .12em;
  text-transform: uppercase; color: var(--dim);
  border-bottom-color: var(--dim); white-space: nowrap;
}
.prose tbody tr:last-child td { border-bottom: 0; }
.prose td { color: var(--fore); }
.prose td code { font-size: .92em; }

/* ============================================================ code blocks == */

.block { margin: 0 0 .9em; border: 1px solid var(--rule); background: var(--sunk); }
.block figcaption {
  display: flex; align-items: center; gap: var(--s2);
  padding: 2px var(--s2) 2px var(--s3); border-bottom: 1px solid var(--rule);
}
.block figcaption .stamp {
  flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  direction: rtl; text-align: left;              /* long paths keep their tail */
}
.block .wrap { display: flex; align-items: stretch; min-width: 0; }
.block .gutter {
  flex: none; display: flex; flex-direction: column;
  padding: var(--s2) var(--s2) var(--s2) var(--s3);
  border-right: 1px solid var(--rule); background: var(--void);
  font-family: var(--mono); font-size: var(--t-xs); color: var(--dusk);
  text-align: right; user-select: none;
  /* A number has to sit on the line it counts, so the gutter's line box is the
     CODE's line box -- t-sm x 1.6 -- not its own smaller font's. At 11px/1.6 it
     drifted 1.6px per line: line 20 was labelled 23, line 131 was 232px adrift.
     The smaller type is deliberate; the smaller leading was not. */
  line-height: calc(var(--t-sm) * 1.6);
}
pre.code {
  margin: 0; flex: 1; min-width: 0; padding: var(--s2) var(--s3);
  font-family: var(--mono); font-size: var(--t-sm); line-height: 1.6;
  color: var(--fore); overflow-x: auto; tab-size: 4;
}
.block.cap pre.code { max-height: 420px; overflow-y: auto; }
/* Not `auto`: the numbers are not a second thing to scroll. Code.js drives this
   from the pre's own scroll position. */
.block.cap .gutter { max-height: 420px; overflow: hidden; }
.block.cap .wrap { max-height: 420px; }
pre.code code { font: inherit; background: none; border: 0; padding: 0; color: inherit; }
.prose pre.code code { color: inherit; }

.copy { flex: none; border: 1px solid transparent; padding: 1px var(--s2); color: var(--dim); }
.copy:hover { background: var(--dim); color: var(--void); }
.copy.ok { color: var(--hot); }
.copy.no { color: var(--alarm); }

/* --- diff ----------------------------------------------------------------- */
pre.code.diff { padding: var(--s2) 0; }
.dl { display: block; padding: 0 var(--s3); white-space: pre-wrap; word-break: break-word; }
.dl.add { background: rgba(79,150,105,.18); color: var(--hot); }
.dl.del { background: rgba(255,95,82,.13); color: var(--alarm); }
.dl.hunk { color: var(--c-key); }
.dl.meta { color: var(--dim); }
.dl.ctx  { color: var(--fore); }

/* --- syntax --------------------------------------------------------------- */
/* The one place colour is allowed, and it is the ANSI set rather than a theme:
   inside a fence, green means plain text and a hue means a token. */
.hljs-keyword, .hljs-selector-tag, .hljs-section, .hljs-doctag,
.hljs-keyword.hljs-literal { color: var(--c-key); }
.hljs-built_in, .hljs-title, .hljs-title.function_, .hljs-name, .hljs-tag,
.hljs-title.class_, .hljs-type { color: var(--c-fn); }
.hljs-string, .hljs-addition, .hljs-regexp, .hljs-char.escape_ { color: var(--c-str); }
.hljs-number, .hljs-literal, .hljs-symbol, .hljs-bullet, .hljs-quote { color: var(--c-num); }
.hljs-attr, .hljs-attribute, .hljs-property, .hljs-variable,
.hljs-template-variable, .hljs-selector-attr, .hljs-selector-class,
.hljs-selector-id, .hljs-params { color: var(--hot); }
.hljs-comment, .hljs-meta, .hljs-meta .hljs-keyword { color: var(--dim); font-style: italic; }
.hljs-deletion { color: var(--alarm); }
.hljs-operator, .hljs-punctuation, .hljs-subst { color: var(--dim); }
.hljs-emphasis { font-style: italic; }
.hljs-strong { color: var(--hot); }
.hljs-link { color: var(--c-fn); text-decoration: underline; }

/* ============================================================== tool cards == */
/* Tool calls are the content, not a disclosure to hide. They get a real frame,
   and their state is legible from the header alone. */

.tool { margin: 2px 0 var(--s2); border: 1px solid var(--rule); background: var(--void); }
.tool.run { border-color: color-mix(in srgb, var(--amber) 40%, var(--rule)); }
.tool.bad { border-color: var(--alarm); }
.tool.cut { border-color: var(--rule); opacity: .72; }
.tool > .head {
  width: 100%; display: flex; align-items: center; gap: var(--s2);
  padding: 3px var(--s2); text-align: left;
}
.tool > .head:hover { background: var(--sunk); }
.tool .tri { font-size: 0; flex: none; color: var(--dim); }
.tool .tri::before { content: "[+]"; font-family: var(--vt); font-size: var(--t-xs); }
.tool.open .tri::before { content: "[-]"; }
.tool > .head:hover .tri { color: var(--hot); }
.tool .name {
  font-family: var(--vt); font-size: var(--t-xs);
  letter-spacing: .14em; text-transform: uppercase; color: var(--amber); flex: none;
}
.tool.ok  .name { color: var(--fore); }
.tool.bad .name { color: var(--alarm); }
.tool.cut .name { color: var(--dusk); }
.tool .arg {
  flex: 1; min-width: 0; font-family: var(--mono); font-size: var(--t-xs); color: var(--dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tool > .head:hover .arg { color: var(--fore); }
.tool .trunc { color: var(--dim); flex: none; }   /* display trim, not a failure */
.tool .dur { font-family: var(--vt); font-size: var(--t-xs); color: var(--dim); flex: none; }
.tool .body { border-top: 1px solid var(--rule); padding: var(--s2); background: var(--sunk); }
.tool .body > .block { margin-bottom: var(--s2); background: var(--void); }
.tool .body > .block:last-child { margin-bottom: 0; }
.tool .body > .block .gutter { background: var(--sunk); }
.quiet { color: var(--dim); font-size: var(--t-sm); padding: var(--s1) var(--s1) 0; }

.hits { font-family: var(--mono); font-size: var(--t-xs); max-height: 340px; overflow: auto; }
.hits .hitc { padding: 0 0 var(--s2); }
.hit { color: var(--fore); padding: 0 var(--s1); white-space: pre-wrap; word-break: break-all; }
.hit:hover { background: var(--rule); color: var(--hot); }

/* --- status lines --------------------------------------------------------- */
/* Scoped to the tape: an unscoped .note also matches the sidebar's
   <span class="dot note">, which then inherits this padding. */
.entry .note { padding: 0 0 var(--s3); font-size: var(--t-sm); color: var(--dim); max-width: var(--measure); }
.entry .note.err { color: var(--alarm); }
.entry .note.queue { color: var(--amber); }

.cursor {
  display: inline-block; width: var(--col); height: 1.05em; background: var(--hot);
  vertical-align: text-bottom; margin-left: 1px;
  box-shadow: 0 0 8px rgba(150,255,185,.4);
  animation: blink 1.05s steps(2) infinite;
}

/* ================================================================ compose == */

.compose {
  flex: none; border-top: 1px solid var(--rule); background: var(--void);
  padding: var(--s3) var(--s5) 0; position: relative; z-index: 1;
}
/* Indented by the rail so what you are about to type starts on the same column
   as everything you have already said. */
.compose .box {
  max-width: var(--page); margin: 0 auto; padding: 0 var(--s5) 0 var(--rail);
  display: flex; align-items: flex-end; gap: var(--s2);
}
/* min-width:0 is load-bearing, not tidying. A flex item defaults to
   min-width:auto, i.e. it refuses to shrink below its own min-content width --
   and for a <textarea> that is derived from `cols`, which defaults to 20. Blink
   resolves that to ~43px here and everything fits; WebKit resolves it to the
   real 20-column box, measured 182.5px at this font and size. The composer's
   other four children (three .clip at 38 and .go at 106.5 for "Stopping...")
   are all flex:none and total 252.5px with the gaps, so on iOS the row asks for
   435px inside a 366px phone and the overflow lands on the LAST item: at 390px
   the Send/Stop button was 47% off the right edge, and body{overflow:hidden}
   means there is no scrolling to it. Reported on a phone 2026-09-20 and
   invisible in Chrome, which is why it survived §89's mobile pass.
   Pinning it to 0 makes the row independent of whatever any engine thinks a
   textarea's intrinsic width is. Same idiom as .sheet .keyrow input and
   .sheet.models .mcustom input. */
.compose textarea {
  flex: 1; min-width: 0;
  background: var(--sunk); border: 1px solid var(--rule); color: var(--hot);
  padding: var(--s2) var(--s3); resize: none; min-height: 38px; max-height: 220px;
  font-size: var(--t-md); line-height: 1.5;
}
.compose textarea:focus { border-color: var(--fore); outline: none; }
.compose textarea::placeholder { color: var(--dim); }

/* The council ask sheet's question field: the composer's idiom,
   unflexed -- a dialog field is block-level and resizable, not a
   growing composer row. */
.sheet.ask textarea {
  width: 100%; box-sizing: border-box;
  background: var(--sunk); border: 1px solid var(--rule); color: var(--hot);
  padding: var(--s2) var(--s3); resize: vertical; min-height: 96px; max-height: 320px;
  font-size: var(--t-md); line-height: 1.5;
}
.sheet.ask textarea:focus { border-color: var(--fore); outline: none; }
.sheet.ask textarea::placeholder { color: var(--dim); }

/* The handoff dialog's target picker, in the same idiom. */
.sheet.ask select {
  width: 100%; box-sizing: border-box;
  background: var(--sunk); border: 1px solid var(--rule); color: var(--hot);
  padding: var(--s2) var(--s3); font-size: var(--t-md);
}
.sheet .acts .go:disabled { opacity: .45; cursor: default; }

/* The default action is the lit one. Reverse video, again. */
.go {
  font-family: var(--vt); font-size: var(--t-sm); letter-spacing: .16em;
  text-transform: uppercase; color: var(--void); background: var(--fore);
  padding: 10px var(--s4); flex: none; border: 1px solid var(--fore);
}
.go:hover { background: var(--hot); border-color: var(--hot); }
.go.stop, .go.danger { background: var(--alarm); border-color: var(--alarm); color: var(--void); }
.go.stop:hover, .go.danger:hover { background: #FF8378; border-color: #FF8378; }

/* Interrupt-and-send (2026-09-28): the outline form reads as "acts on the
   running turn" -- distinct from Send (filled) and Stop (alarm fill). */
.go.now { background: transparent; color: var(--hot); border-color: var(--hot); }
.go.now:hover { background: var(--hot); border-color: var(--hot); color: var(--void); }

/* The secondary action is typed, not filled: < CANCEL >. */
.ghost {
  font-family: var(--vt); font-size: var(--t-sm); letter-spacing: .16em;
  text-transform: uppercase; color: var(--dim); background: none;
  border: 1px solid var(--rule); padding: 10px var(--s4); flex: none;
}
.ghost:hover { color: var(--hot); border-color: var(--dim); }

/* --- phone: the input gets its own line ----------------------------------- */
/* The composer is one flex row of five, and four of them are fixed width:
   three .clip at 38px and .go at 69px for SEND -- 113px while it says
   STOPPING... The textarea is the only flexible item, so it absorbed every
   squeeze. Measured off this stylesheet at 390px: 366px inside .compose, minus
   114 of clips, 69 of SEND and 32 of gaps, leaves the textarea 151px -- about
   19 characters, and about 13 mid-turn when the button widens. A few words at
   a time.
   Shrinking the buttons was the other option and it is not enough: icon-only
   SEND plus 32px clips returns ~50px. Wrapping the textarea onto its own line
   returns 215px -- the full 366 -- for the cost of a 38px button stripe plus
   8px, because gap:var(--s2) on .box is a two-axis gap and wrapping turns it
   into a row-gap as well: 46px total, once, at the bottom of the screen.
   No markup change. order:-1 lifts the textarea above the buttons, which then
   fall in DOM order (+ = ✦) with SEND pushed right by margin-left:auto. That
   also reads better than what it replaces: utilities grouped together on the
   left, the primary action alone on the right, instead of two on each side.
   min-height:541px guards SHORT windows, not landscape phones -- a landscape
   phone is already out on max-width. What it protects is split screen, a
   foldable cover panel, a small desktop window: under ~540px of height the
   extra 38px stripe costs more than the width it wins. */
@media (max-width: 560px) and (min-height: 541px) {
  .compose .box { flex-wrap: wrap; }
  /* flex-basis 100% forces the wrap; shrink 0 stops it being pulled back up
     onto the button line. Beats .compose textarea on specificity (0,2,1). */
  .compose .box textarea { flex: 1 0 100%; order: -1; }
  .compose .box .go { margin-left: auto; }
}

/* --- the status line, right half ------------------------------------------ */
/* Full-bleed out of the compose padding so it meets .sidefoot edge to edge and
   the two read as one bar across the bottom of the screen. */
.hint {
  height: var(--stat); margin: var(--s3) calc(var(--s5) * -1) 0; padding: 0 var(--s4);
  display: flex; align-items: center; gap: var(--s4);
  background: var(--dim); color: var(--void);
  font-family: var(--vt); font-size: var(--t-xs); letter-spacing: .08em;
  text-transform: uppercase; white-space: nowrap; overflow: hidden;
}
.hint .tight { background: var(--alarm); color: var(--void); padding: 0 var(--s2); }
/* The room-left chip is the bar's sacrificial item: when the row
   overflows (a phone with the consult toggle present) it ellipsizes
   instead of being clipped mid-glyph at the right edge. */
.hint .room { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* ================================================================== empty == */
/* A window: hairline frame, its title punched into the top rule, hard shadow. */

.blank {
  position: relative; max-width: 560px; margin: 9vh auto 0; padding: var(--s5);
  border: 1px solid var(--dim); background: var(--void);
  box-shadow: 3px 3px 0 rgba(0,0,0,.75);
}
.blank.wide { max-width: 640px; }
.blank > .stamp:first-child {
  position: absolute; top: -8px; left: var(--s4);
  background: var(--void); padding: 0 var(--s2); color: var(--dim);
}
.blank h2 {
  font-family: var(--vt); font-weight: 400; font-size: var(--t-2xl);
  letter-spacing: .06em; text-transform: uppercase; margin: 0 0 var(--s3);
  color: var(--hot); text-shadow: 0 0 10px rgba(150,255,185,.25);
}
.blank p { color: var(--dim); margin: 0 0 var(--s4); max-width: calc(60 * var(--col)); font-size: var(--t-sm); }
.blank .acts { display: flex; gap: var(--s2); margin-bottom: var(--s5); }
.blank .recent { border-top: 1px solid var(--rule); padding-top: var(--s3); }
.blank .recent .stamp { display: block; margin-bottom: var(--s2); }
.rrow {
  display: flex; width: 100%; gap: var(--s3); align-items: baseline;
  padding: 3px var(--s1); color: var(--fore); font-size: var(--t-sm); text-align: left;
}
.rrow:hover { background: var(--dim); color: var(--void); }
.rrow .txt { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rrow .tick { font-family: var(--vt); font-size: var(--t-2xs); color: var(--dusk); }
.rrow:hover .tick { color: rgba(5,8,6,.7); }

/* =================================================================== note == */

.editor {
  flex: 1; display: flex; flex-direction: column; min-height: 0;
  padding: var(--s4) var(--s5); position: relative; z-index: 1;
}
.editor > input {
  background: none; border: 0; border-bottom: 1px solid var(--rule);
  font-family: var(--vt); font-size: var(--t-xl); letter-spacing: .06em;
  padding: 0 0 var(--s2); margin-bottom: var(--s3); color: var(--hot);
}
.editor > input:focus { outline: none; border-bottom-color: var(--fore); }
.editor textarea {
  flex: 1; background: var(--sunk); border: 1px solid var(--rule); color: var(--fore);
  padding: var(--s3); resize: none; font-family: var(--mono); font-size: var(--t-sm); line-height: 1.65;
}
.editor textarea:focus { outline: none; border-color: var(--fore); }
.editor .preview {
  flex: 1; overflow-y: auto; border: 1px solid var(--rule); background: var(--sunk);
  padding: var(--s3) var(--s4);
}
/* The editor's foot is the same status line as .hint and .sidefoot: one bar
   across the bottom of the screen, whatever happens to be open above it. */
.editor .meta {
  flex: none; height: var(--stat);
  margin: var(--s3) calc(var(--s5) * -1) calc(var(--s4) * -1);
  padding: 0 var(--s4); display: flex; align-items: center; gap: var(--s4);
  background: var(--dim); color: var(--void);
}
.editor .meta .stamp { color: var(--void); }
.editor .meta button { padding: 0 var(--s1); }
.editor .meta button:hover { background: var(--void); color: var(--hot); }
.editor .meta .danger { color: var(--void); }
.editor .meta .danger:hover { background: var(--alarm); color: var(--void); }

/* ================================================================== login == */

.gate { height: 100%; display: grid; place-items: center; }
.gate form {
  position: relative; width: 330px;
  border: 1px solid var(--dim); background: var(--void);
  padding: var(--s5) var(--s5) var(--s4);
  box-shadow: 3px 3px 0 rgba(0,0,0,.75);
}
.gate .mark { justify-content: center; margin-bottom: var(--s5); }
.gate label { display: block; margin-bottom: var(--s3); }
.gate .stamp { display: block; margin-bottom: var(--s1); }
.gate .sub {
  position: absolute; top: -8px; left: 50%; transform: translateX(-50%);
  margin: 0; background: var(--void); padding: 0 var(--s2); white-space: nowrap;
}
.gate input {
  width: 100%; background: var(--sunk); border: 1px solid var(--rule);
  padding: var(--s2) var(--s3); color: var(--hot);
}
.gate input:focus { outline: none; border-color: var(--fore); }
.gate .go { width: 100%; margin-top: var(--s3); }
.gate .err { color: var(--alarm); font-size: var(--t-sm); margin-top: var(--s3); min-height: 19px; }

/* The reCAPTCHA checkbox. Absent unless the panel has captcha switched on, so
   this must collapse to nothing rather than reserve a gap.
   Google ships v2 as a fixed 304x78 iframe and will not let the page resize it.
   The gate is 330px wide with var(--s5) of padding, leaving 280px of content --
   24px short. Scaling the widget is far less disruptive than widening the gate
   and re-tuning every field in it. The wrapper's height is clamped to the
   scaled height, or the 8% taken off the bottom would show as dead space above
   the Sign in button. The challenge image grid is a fixed-position overlay on
   <body>, not a child of this box, so overflow:hidden does not clip it.
   v3 renders no widget at all -- only a fixed badge -- and login.js leaves the
   .on class off, so this stays display:none there. */
.gate .cap { display: none; }
/* The widget is a fixed 304x78 iframe that Google will not let the page resize.
   The form's content box is only 280px, so this used to scale it to .92 -- which
   drew 279.68x71.76 inside a 280x72 clipped box and left a sub-pixel strip of
   the iframe's white backdrop along the right and bottom edges (only those two:
   transform-origin 0 0 pins the top-left).
   Cancel the form's side padding instead. That yields 328px of room, enough for
   the native 304px, so there is no transform and nothing to clip. */
.gate .cap.on {
  display: flex; justify-content: center;
  margin: var(--s4) calc(-1 * var(--s5)) 0;
}

/* Cancelling the padding stopped the page clipping the widget, but a white
   strip remained on the right and bottom. Measured 2026-09-23 in Chrome:
   Google draws a 302x76 dark anchor (.rc-anchor, #222) inside a 304x78 iframe
   whose html AND body are both rgba(0,0,0,0), so the leftover 2px falls back to
   the browser's default white canvas. Neither a background on the iframe
   element nor `color-scheme: dark` reaches that canvas -- both were tried and
   photographed, both left the strip. Clipping the wrapper to the anchor's real
   302x76 is what removed it, and nothing is lost because the anchor is pinned
   top-left. The challenge popup is untouched: it is a position:fixed bframe
   appended OUTSIDE #cap (verified by clicking through to it). */
.gate .cap.on > div {
  width: 302px !important; height: 76px !important; overflow: hidden;
}

/* =============================================================== overlays == */
/* Hard offset shadows, no blur. A console draws a shadow by darkening the two
   character cells behind the window; it does not have a blur radius. */

.toast {
  position: fixed; left: 50%; bottom: calc(var(--stat) + 14px); transform: translateX(-50%); z-index: 95;
  background: var(--fore); color: var(--void);
  padding: var(--s2) var(--s4); font-size: var(--t-sm); max-width: 560px;
  box-shadow: 3px 3px 0 rgba(0,0,0,.75);
}
.toast.bad { background: var(--alarm); }

/* Above sheets (80) so a ⋯ inside Chat options can open one, below the
   confirmation (92) a menu item may raise. Long menus scroll, never run
   off the bottom of the screen. */
.menu {
  position: fixed; z-index: 90; min-width: 200px; padding: var(--s1) 0;
  max-height: calc(100vh - 16px); max-height: calc(100dvh - 16px); overflow-y: auto;
  background: var(--void); border: 1px solid var(--dim);
  box-shadow: 3px 3px 0 rgba(0,0,0,.75);
}
.menu button {
  display: flex; align-items: center; gap: var(--s3); width: 100%; text-align: left;
  padding: 2px var(--s3); font-size: var(--t-sm); color: var(--fore);
}
.menu button span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu button:hover { background: var(--dim); color: var(--void); }
.menu button:hover kbd { color: var(--void); border-color: rgba(5,8,6,.4); }
.menu button.danger { color: var(--alarm); }
.menu button.danger:hover { background: var(--alarm); color: var(--void); }
.menu .sep { height: 1px; background: var(--rule); margin: var(--s1) 0; }

/* Confirmations (Ask) stack above every sheet that can spawn one. Both
   use .scrim at z-index 80 and the picker mounts AFTER <Ask/> in app.js,
   so at equal z the picker's backdrop painted OVER the confirmation it
   had just opened (operator report 2026-09-27: "the confirmation opens
   behind it"). Two classes beat one, so this cannot be reordered away. */
.scrim.askscrim { z-index: 92; }

/* The classic stipple backdrop, not a blur. */
.scrim {
  position: fixed; inset: 0; z-index: 80;
  display: grid; padding: var(--s4);
  /* `safe` centring, not plain centring. A sheet taller than the screen -- the
     settings sheet on a landscape phone -- gets centred into overflowing BOTH
     edges, and the top half is then unreachable, because the body does not
     scroll. `safe` keeps it centred while it fits and pins it to the start when
     it does not. overflow:auto then lets the part that does not fit be scrolled
     to instead of being cut off, which is how the sheet behaved before it had a
     definite row to be centred in. */
  place-items: safe center; overflow: auto;
  /* The explicit track is load-bearing, not tidiness. Without it this grid has
     one IMPLICIT `auto` column, and an auto track is sized from its item's
     intrinsic contribution -- in which a percentage width on the item counts as
     `auto`. So a sheet asking for `width:100%` got a track as wide as its own
     max-content and then filled it: 742px inside a 390px phone, clipped off the
     right edge, with `max-width:100%` clamping to that same inflated number and
     therefore clamping nothing. minmax(0,1fr) pins the track to this element's
     own content box, which is what 100% was always meant to mean. Desktop never
     showed it because a definite `width:680px` sizes the track honestly. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  background-color: rgba(3,8,5,.88);
  background-image: radial-gradient(var(--rule) .5px, transparent .6px);
  background-size: 3px 3px;
}
.scrim.high { align-items: start; padding-top: 12vh; }   /* not .top -- that is the header bar */
.sheet {
  position: relative; width: 430px; max-width: 100%;
  background: var(--void); border: 1px solid var(--dim);
  padding: var(--s5) var(--s5) var(--s4);
  box-shadow: 3px 3px 0 rgba(0,0,0,.75);
}
.sheet > .stamp:first-child {
  position: absolute; top: -8px; left: var(--s4);
  background: var(--void); padding: 0 var(--s2);
}
.sheet h2 {
  font-family: var(--vt); font-weight: 400; font-size: var(--t-lg);
  letter-spacing: .08em; text-transform: uppercase;
  margin: 0 0 var(--s2); color: var(--hot);
}
.sheet p { color: var(--dim); font-size: var(--t-sm); margin: 0 0 var(--s5); }
.sheet .acts { display: flex; justify-content: flex-end; gap: var(--s2); }
.sheet .acts .go, .sheet .acts .ghost { padding: 7px var(--s4); }

/* ================================================================== vault == */
/* The unlock screen reuses .gate; these are the extras. */
.gate .vnote { color: var(--dim); font-size: var(--t-xs); line-height: 1.5; margin: 0 0 var(--s4); }
.gate .linky {
  display: block; margin: var(--s3) auto 0; color: var(--dim);
  font-size: var(--t-xs); text-decoration: underline; text-underline-offset: 2px;
}
.gate .linky:hover { color: var(--fore); }

/* Enrolment is mandatory, so it is full-screen like the unlock gate -- but it
   has to SAY far more: what the vault is, why the passphrase is a second
   secret, and that there is no way back. 330px cannot carry that, and on a
   short laptop viewport the grid centring of .gate would push the button off
   the bottom with no way to scroll to it. So this variant is wider, and it
   scrolls from the top instead of centring. */
.gate.enrol { display: block; overflow-y: auto; padding: var(--s5) var(--s4); }
.gate.enrol form { width: 420px; max-width: 100%; margin: 0 auto; }
.gate .warn { color: var(--alarm); font-size: var(--t-xs); line-height: 1.5; margin: 0 0 var(--s4); }
.gate .warn b { color: var(--alarm); }
.gate .vnote b { color: var(--fore); }
.gate label.check {
  display: flex; align-items: flex-start; gap: var(--s2);
  color: var(--fore); font-size: var(--t-xs); margin: var(--s2) 0 var(--s3);
}
.gate label.check input { width: auto; margin-top: 2px; }
/* Sits directly under the field it judges, so it pulls up into the gap the
   label below it already reserves rather than adding a second one. */
.gate .hint {
  color: var(--dim); font-size: var(--t-xs); line-height: 1.4;
  margin: calc(-1 * var(--s2)) 0 var(--s3);
}
.gate .hint.bad { color: var(--alarm); }

/* The set-up / change-passphrase sheets carry a form, which the plain .sheet
   does not style. Keep it visually identical to the sign-in gate's fields. */
.sheet.vault label { display: block; margin-bottom: var(--s3); }
/* A checkbox row inside a plain .sheet. The gate and the vault sheets each
   had their own copy of this; the plain-language sheet is the third, so it
   is generalised here rather than pasted again. .sheet.vault below is more
   specific and keeps its own spacing unchanged. */
.sheet label.check {
  display: flex; align-items: flex-start; gap: var(--s2);
  color: var(--fore); font-size: var(--t-sm); line-height: 1.5;
  margin: var(--s3) 0; cursor: pointer;
}
.sheet label.check input { width: auto; margin-top: 3px; flex: none; }

.sheet.vault label.check {
  display: flex; align-items: flex-start; gap: var(--s2);
  color: var(--fore); font-size: var(--t-xs); margin: var(--s2) 0 var(--s4);
}
.sheet.vault label.check input { width: auto; margin-top: 2px; }
.sheet.vault .stamp { display: block; margin-bottom: var(--s1); }
.sheet.vault input {
  width: 100%; background: var(--sunk); border: 1px solid var(--rule);
  padding: var(--s2) var(--s3); color: var(--hot);
}
.sheet.vault input:focus { outline: none; border-color: var(--fore); }
.sheet.vault .warn { color: var(--alarm); }
.sheet.vault .warn b { color: var(--alarm); }
.sheet.vault .err { color: var(--alarm); font-size: var(--t-sm); min-height: 19px; margin-bottom: var(--s2); }

/* The palette is a command line, so it opens with one. */
.palette {
  position: relative; width: 620px; max-width: 100%;
  background: var(--void); border: 1px solid var(--dim);
  box-shadow: 3px 3px 0 rgba(0,0,0,.75); display: flex; flex-direction: column;
  max-height: 64vh;
}
.palette::before {
  content: ":"; position: absolute; left: var(--s4); top: var(--s3);
  font: var(--t-lg)/1.5 var(--vt); color: var(--hot);
}
.palette > input {
  border: 0; border-bottom: 1px solid var(--rule); background: none;
  padding: var(--s3) var(--s4) var(--s3) calc(var(--s4) + 2 * var(--col));
  font-size: var(--t-lg); color: var(--hot);
}
.palette > input:focus { outline: none; }
.palette > input::placeholder { color: var(--dim); }
.plist { overflow-y: auto; padding: var(--s1) 0; }
.pnone { padding: var(--s4); color: var(--dim); font-size: var(--t-sm); }
.prow {
  display: flex; align-items: baseline; gap: var(--s3); width: 100%; text-align: left;
  padding: 3px var(--s4); color: var(--fore); font-size: var(--t-sm);
}
.prow.on { background: var(--dim); color: var(--void); }
.prow.on .ph { color: rgba(5,8,6,.72); }
.prow.on kbd { color: var(--void); border-color: rgba(5,8,6,.4); }
.pgh {
  padding: var(--s2) var(--s4) 2px; font-size: var(--t-2xs);
  letter-spacing: .14em; text-transform: uppercase; color: var(--dusk);
  border-top: 1px solid var(--rule); margin-top: var(--s1);
}
.pgh:first-child { border-top: 0; margin-top: 0; }
.pfoot {
  padding: var(--s2) var(--s4) var(--s3); color: var(--dusk);
  font-size: var(--t-2xs); border-top: 1px solid var(--rule); margin-top: var(--s1);
}
.pl { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ph { flex: none; font-family: var(--vt); font-size: var(--t-2xs); color: var(--dusk); }
kbd {
  font-family: var(--vt); font-size: var(--t-xs); color: var(--dim);
  border: 1px solid var(--rule); padding: 0 4px; flex: none;
}

/* ============================================================== scrollbar == */

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--sunk); }
::-webkit-scrollbar-thumb { background: var(--rule); }
::-webkit-scrollbar-thumb:hover { background: var(--dim); }
::-webkit-scrollbar-corner { background: var(--sunk); }
* { scrollbar-color: var(--rule) var(--sunk); scrollbar-width: thin; }

/* ================================================================= mobile == */

@media (max-width: 860px) {
  :root { --rail: 52px; --axis: 40px; --side: 250px; }
  /* A phone is held closer than a monitor, so the desktop scale reads a
     shade large. Step the type ladder and the big spacing rungs down
     together -- gentler than compact density, same information. */
  :root {
    --t-2xl: 23px; --t-xl: 18px; --t-lg: 14.5px;
    --t-md: 13px;  --t-sm: 11.5px; --t-xs: 10.5px; --t-2xs: 9.5px;
    --s5: 20px; --s6: 28px;
  }
  .top .icon { display: block; }
  .mark i { display: none; }            /* the wordmark alone is enough up here */
  /* The right end of the header (model pill, then seat name) must never be
     pushed off-screen. Tighten the row and make the wordmark the only thing
     that gives way when space runs out. */
  .top { gap: 6px; padding-left: var(--s2); padding-right: var(--s2); }
  .mark { flex: 0 1 auto; min-width: 0; overflow: hidden; }
  .mark b { font-size: var(--t-sm); letter-spacing: .12em; white-space: nowrap; }
  .gauge .read { font-size: var(--t-2xs); }
  .stampline time { letter-spacing: 0; }
  /* Keyboard hints are for keyboards. On a phone the only number that matters
     is how much room is left. */
  .hint > :first-child { display: none; }
  .side {
    position: absolute; left: 0; top: 0; bottom: 0; z-index: 30;
    transform: translateX(-100%); transition: transform .18s ease;
    box-shadow: 3px 0 0 rgba(0,0,0,.75);
  }
  .side.open { transform: none; }
  /* Touch anywhere off the drawer to close it, instead of having to hit the
     burger again. The scrim only exists while the drawer is open, and sits at
     29 -- under the drawer (30) so the menu itself stays tappable, over the
     header (5) and the chat (1-5) so the whole rest of the screen closes it.
     Context menus (70) and sheets (80) are above it and keep working, which is
     what stops a long-press menu raised from the drawer being dismissed by its
     own first tap. Fixed, not absolute: .shell starts below the header, and
     the header has to be a close target too. */
  .side.open ~ .sidescrim {
    display: block; position: fixed; inset: 0; z-index: 29;
    background: rgba(0, 0, 0, .45);
  }
  .shell { position: relative; }
  .stampline time { font-size: 9px; }
  .reel { padding-right: var(--s3); }
  .gauge .bar { width: 48px; }
  .gauge .stamp { display: none; }
  .top .now { display: none; }
  /* No title to absorb the row on small screens: the mark's auto
     margin pushes the whole right group -- gauge, marks, files, model,
     name -- hard into the corner. */
  .top .mark b { display: none; }
  .top .mark { margin-right: auto; }
  /* The gauge belongs to the LEFT cluster with the logo: when it is
     rendered, IT absorbs the slack instead of the mark, so the meter
     hugs the logo while files/model/name corner on their own. Note
     views render no gauge -- there the mark keeps the job. :has picks
     the right one with no DOM changes. */
  .top:has(.gauge) .mark { margin-right: 0; }
  .top:has(.gauge) .gauge { margin-right: auto; }
  /* Uncapped, a long model name sets the header's min-content width and drags
     the whole document wider than the screen. */
  .pill { max-width: 92px; }
  /* The seat name must land whole on the right edge. The files pill
     keeps glyph and count but drops the word; the gauge keeps the used
     figure but drops the denominator -- the bar already shows the
     fraction, and the tooltip still carries both numbers. */
  .top .fpill .lbl { display: none; }
  .gauge .read i { display: none; }
  .top .who { white-space: nowrap; }
  /* The badges do not earn their pixels up here: the in-chat banners carry
     the consult/ext warning where the typing happens, and the model pill
     still names the engine. Glyph-only was tried first and the row still
     overran a 375px phone and pushed the seat name off the edge, so on
     mobile they go entirely. */
  .top .cmark, .top .xmark { display: none; }
  /* One line for the persistent banners: title and action. The sentence and
     the model id are wide-screen luxuries; the enable-time confirmations
     carry the explanation, and the pill/banner title carries the engine. */
  .extbanner .mid, .extbanner .bwhy,
  .consultbar .bwhy, .chatonlybar .bwhy { display: none; }
  .compose { padding-left: var(--s3); padding-right: var(--s3); }
  .compose .box { padding: 0; }   /* the rail indent is a luxury a phone has no room for */
  .editor { padding-left: var(--s3); padding-right: var(--s3); }
  .editor .meta {
    margin-left: calc(var(--s3) * -1); margin-right: calc(var(--s3) * -1);
    padding: 0 var(--s3); gap: var(--s3);
  }
  .hint { margin-left: calc(var(--s3) * -1); margin-right: calc(var(--s3) * -1); padding: 0 var(--s3); }
  .palette { width: 100%; }
  .blank { margin-top: var(--s5); }
  .reel:has(.blank) { padding-left: var(--s3); }
}

/* ================================================ external consultation == */
/* Everything in this block is --out, and --out appears nowhere else. The
   design brief was "semi but not too loud": loud enough that you cannot
   operate the feature without noticing it, quiet enough that it does not
   nag once you have decided. So the state is always on screen (the toggle,
   the rail mark, the sidebar flag) and the prose warning appears only where a
   decision is being made -- the confirm sheet, and the strip above the
   composer while it is armed. */

/* --- the toggle, in the composer's status line ---------------------------- */
/* Lives in .hint, which is reverse video (dim ground, void ink), so the "on"
   state has to invert the other way: the void fills and --out becomes the ink. */
.hint .ctoggle {
  font-family: var(--vt); font-size: var(--t-xs); letter-spacing: .08em;
  text-transform: uppercase; background: none; border: 1px solid transparent;
  color: color-mix(in srgb, var(--void) 62%, var(--dim)); padding: 0 var(--s2);
  flex: none; white-space: nowrap;
}
.hint .ctoggle:hover { color: var(--void); border-color: color-mix(in srgb, var(--void) 35%, transparent); }
.hint .ctoggle.on { background: var(--out); color: var(--void); border-color: var(--out); }
/* Off now, but this chat has form. Outlined rather than filled: a record, not a state. */
.hint .ctoggle.ever { color: var(--void); border-color: color-mix(in srgb, var(--void) 45%, transparent); }

/* --- the armed strip above the composer ----------------------------------- */
.compose.consulting { border-top-color: var(--out); }
.consultbar {
  max-width: var(--page); margin: 0 auto var(--s3);
  padding: var(--s2) var(--s3) var(--s2) var(--rail);
  border-left: 2px solid var(--out);
  background: color-mix(in srgb, var(--out) 8%, var(--void));
  color: var(--dim); font-size: var(--t-xs); line-height: 1.5;
  display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap;
}
.consultbar b { color: var(--out); font-weight: 400; }
.consultbar .stamp { margin-left: auto; border: 1px solid var(--rule); padding: 1px var(--s2); }
.consultbar .stamp:hover { color: var(--out); border-color: var(--out); }
.consultbar .cpick { display: inline-flex; align-items: center; gap: var(--s1); min-width: 0; }
.consultbar .cpick select, .consultbar .cpick input.model {
  font: inherit; color: inherit; background: transparent; border: 1px solid var(--rule);
  padding: 0 var(--s1); max-width: 14em; min-width: 0; }
.consultbar .cpick .stamp { margin-left: 0; }
.consultmark.suggest .stamp { border: 1px solid var(--rule); padding: 1px var(--s2); margin-left: var(--s1); }
.consultmark.suggest .stamp:hover { color: var(--out); border-color: var(--out); }

/* --- the rail mark -------------------------------------------------------- */
/* A hollow diamond: not a prompt (filled square), not an answer (outlined
   square), not a tool (a column). Something else happened here. */
.node.n-consult {
  border: 1px solid var(--out); background: var(--void);
  transform: rotate(45deg); width: 8px; height: 8px; top: 6px;
  left: calc(var(--axis) - 4px);
}
.node.n-consult.is-ok  { background: color-mix(in srgb, var(--out) 45%, var(--void)); }
.node.n-consult.is-bad { border-color: var(--alarm); background: color-mix(in srgb, var(--alarm) 32%, var(--void)); }
.node.n-mark { border-left: 2px solid var(--out); background: none; width: 0; height: 12px; top: 4px; }

/* --- the card ------------------------------------------------------------- */
.consultcard {
  max-width: var(--measure); margin: 0 0 var(--s3);
  border: 1px solid color-mix(in srgb, var(--out) 40%, var(--rule));
  border-left-width: 2px; border-left-color: var(--out);
  background: color-mix(in srgb, var(--out) 5%, var(--void));
}
.consultcard.bad { border-color: color-mix(in srgb, var(--alarm) 45%, var(--rule)); border-left-color: var(--alarm); }
.consultcard .chead {
  display: flex; align-items: baseline; gap: var(--s2);
  padding: var(--s2) var(--s3); border-bottom: 1px solid var(--rule);
  font-family: var(--vt); font-size: var(--t-xs); letter-spacing: .08em;
  text-transform: uppercase; color: var(--out);
}
.consultcard.bad .chead { color: var(--alarm); }
.consultcard .chead i { font-style: normal; color: var(--dusk); text-transform: none; letter-spacing: 0; }
.consultcard .chead .sp { flex: 1; }
.consultcard .chead .stamp { color: var(--dusk); }

/* What was sent. Deliberately the first thing in the card and never collapsed
   away: the question that left the machine outranks the answer that came back,
   because it is the part you cannot take back. */
.consultcard .cq {
  display: block; width: 100%; text-align: left; background: var(--sunk);
  border: none; border-bottom: 1px solid var(--rule);
  padding: var(--s2) var(--s3); color: var(--fore);
  font-size: var(--t-sm); line-height: 1.5; white-space: pre-wrap;
}
.consultcard .cq:hover { color: var(--hot); }
.consultcard .cq .stamp { color: var(--dusk); }
.consultcard .cctx {
  margin: 0; padding: var(--s3); max-height: 320px; overflow: auto;
  background: var(--void); border-bottom: 1px solid var(--rule);
  color: var(--dim); font-size: var(--t-xs); line-height: 1.5; white-space: pre-wrap;
}
.consultcard .ca { padding: var(--s2) var(--s3); }
.consultcard .ca > :first-child { margin-top: 0; }
.consultcard .ca .answeract { margin-top: var(--s2); }
.consultcard .note.err { margin: 0; padding: var(--s2) var(--s3); }   /* failed/not-sent line, aligned with .cq */
.consultcard.live { border-style: dashed; }
.consultcard.live .chead .pulse { color: var(--out); }
.entry .note.consultmark { color: var(--dusk); }
.entry .note.consultmark.on { color: var(--out); }

/* --- reasoning ------------------------------------------------------------ */
/* The rail mark: a small hollow ring. Not a prompt (filled square), an answer
   (outlined square), a tool (a column) or a consult (blue diamond). Drawn in
   --rule so a turn full of reasoning does not turn the rail into a ladder. */
.node.n-think {
  border: 1px solid var(--rule); background: var(--void);
  border-radius: 50%; width: 7px; height: 7px; top: 6px;
  left: calc(var(--axis) - 3px);
}

.thinkcard {
  max-width: var(--measure); margin: 0 0 var(--s3);
  border-left: 1px dashed var(--rule);
}
.thinkcard .thead {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s2); width: 100%;
  background: none; border: none; text-align: left;
  padding: var(--s1) var(--s3); cursor: pointer;
  font-family: var(--vt); font-size: var(--t-xs); letter-spacing: .08em;
  text-transform: uppercase; color: var(--dusk);
}
.thinkcard .thead:hover { color: var(--dim); }
.thinkcard .thead i { font-style: normal; letter-spacing: 0; }
.thinkcard .thead .sp { flex: 1; }
.thinkcard .thead .stamp { color: var(--dusk); margin-left: auto; }
/* The preview of a collapsed block: the first sentence or two, on its own row
   under the label, clamped to two lines. It used to be a nowrap/ellipsis span
   sharing the label row, which can only ever show one line -- and one line of
   a thousand-word block is not enough to decide whether to open it. Still lower
   case and still clamped: a hint, not content to be read on the rail. */
.thinkcard .tpeek {
  flex: 1 1 100%; min-width: 0; overflow: hidden;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  line-clamp: 2;
  text-transform: none; letter-spacing: 0; color: var(--rule);
  line-height: 1.5; margin: 2px 0 0;
}
.thinkcard .tpeek:empty { display: none; margin: 0; }
.thinkcard.open .thead { color: var(--dim); }
.thinkcard .tbody {
  margin: 0; padding: var(--s2) var(--s3); max-height: 420px; overflow: auto;
  background: var(--sunk); border-left: 1px solid var(--rule);
  color: var(--dusk); font-size: var(--t-xs); line-height: 1.55;
  white-space: pre-wrap; word-break: break-word;
}
/* While it streams. No --amber here: amber is reserved for RUNNING marks on
   the rail, and borrowing it would make reasoning look like a tool call. */
.thinkcard.live { border-left-color: var(--dusk); }
.thinkcard.live .thead { color: var(--dim); }
.thinkcard.live .tbody { color: var(--dim); }

/* Narration on the way to a tool call. Folded like reasoning, but a SOLID rule
   rather than a dashed one: this is text the model meant to be read, it just is
   not the answer. Deliberately close to the reasoning card -- two near-identical
   folds read as one habit, two unrelated designs read as clutter. */
.thinkcard.narr { border-left-style: solid; }
.thinkcard.narr .thead i { color: var(--dusk); }
/* Markdown, not a <pre>: unlike raw reasoning this is formatted prose, and
   rendering it preformatted turns every list into a wall. The nested resets
   keep an answer-weight stylesheet from making a folded aside look like the
   answer it is not. */
.thinkcard .narrbody {
  margin: 0; padding: var(--s2) var(--s3); max-height: 460px; overflow: auto;
  background: var(--sunk); border-left: 1px solid var(--rule);
  color: var(--dim); font-size: var(--t-sm); line-height: 1.6;
}
.thinkcard .narrbody > :first-child { margin-top: 0; }
.thinkcard .narrbody > :last-child { margin-bottom: 0; }
.thinkcard .narrbody h1, .thinkcard .narrbody h2, .thinkcard .narrbody h3 {
  font-size: var(--t-sm); margin: var(--s2) 0 var(--s1);
}

/* The rail mark for narration: a half-height tick, between the reasoning ring
   and the answer square in weight, so a turn of narration does not look like a
   turn of answers. */
.node.n-narrate {
  background: var(--rule); width: 3px; height: 9px; top: 5px;
  left: calc(var(--axis) - 1px); border-radius: 1px;
}

/* --- persistent marks: top bar and sidebar -------------------------------- */
/* These are the "even though this chat is not using it now" disclosure. They
   are not dismissible and nothing in the app clears them. */
.top .cmark {
  font-family: var(--vt); font-size: var(--t-xs); flex: none;
  padding: 3px var(--s2); letter-spacing: .06em; text-transform: uppercase;
  color: var(--out); background: none;
  border: 1px solid color-mix(in srgb, var(--out) 40%, var(--rule));
}
.top .cmark:hover { border-color: var(--out); }
.top .cmark.on { background: var(--out); color: var(--void); border-color: var(--out); }
.row .cflag { flex: none; color: color-mix(in srgb, var(--out) 70%, var(--dusk)); font-size: var(--t-xs); }
.row .cflag.on { color: var(--out); }
.row.on .cflag { color: var(--void); }

/* --- the settings sheet --------------------------------------------------- */
.sheet.wide { width: 560px; }
.sheet .lede { color: var(--fore); margin-bottom: var(--s3); }
.sheet .fine { font-size: var(--t-xs); color: var(--dusk); margin: var(--s2) 0 var(--s4); line-height: 1.5; }
/* The one place the cost is spelled out in full. Left rule in --out so it is
   visibly the same subject as every other mark in this block. */
.sheet .warn {
  border-left: 2px solid var(--out);
  background: color-mix(in srgb, var(--out) 7%, var(--void));
  padding: var(--s3); margin: 0 0 var(--s4);
  color: var(--dim); font-size: var(--t-xs); line-height: 1.6;
}
.sheet .warn b { color: var(--out); font-weight: 400; }
.sheet .keys { display: flex; flex-direction: column; gap: var(--s2); margin-bottom: var(--s3); }
.sheet .keyrow { display: flex; align-items: center; gap: var(--s2); }
.sheet .keyrow label {
  flex: none; width: 82px; font-family: var(--vt); font-size: var(--t-xs);
  letter-spacing: .08em; text-transform: uppercase; color: var(--dim);
}
.sheet .keyrow input {
  flex: 1; min-width: 0; background: var(--sunk); border: 1px solid var(--rule);
  padding: var(--s2) var(--s3); color: var(--hot); font-size: var(--t-sm);
}
.sheet .keyrow input:focus { outline: none; border-color: var(--out); }
/* API-key boxes are masked in CSS, not by type="password", and that is load
   bearing rather than cosmetic. A real password field on the page makes every
   password manager read the nearest preceding text input -- the sidebar search
   -- as the username half of a sign-in form, and fill BOTH with the saved
   chat login. No password field, no pair, no autofill.
   -webkit-text-security is in Chrome, Edge, Safari and Firefox 118+. Where it
   is missing, consult.js falls back to a real password field: a key sitting
   visible on screen is worse than an autofill nuisance. */
.sheet .keyrow input.secret { -webkit-text-security: disc; }
.sheet .keyrow .stamp { flex: none; border: 1px solid var(--rule); padding: 4px var(--s2); color: var(--dim); }
.sheet .keyrow .stamp:hover:not(:disabled) { color: var(--hot); border-color: var(--dim); }
.sheet .keyrow .stamp:disabled { opacity: .4; }
.sheet .keyrow .stamp.danger:hover:not(:disabled) { color: var(--alarm); border-color: var(--alarm); }
.sheet .pickrow { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.sheet .pickrow label {
  display: flex; align-items: center; gap: var(--s2);
  font-family: var(--vt); font-size: var(--t-xs); letter-spacing: .08em;
  text-transform: uppercase; color: var(--dim);
}
.sheet .pickrow select, .sheet .pickrow input.model {
  background: var(--sunk); border: 1px solid var(--rule); color: var(--hot);
  padding: var(--s1) var(--s2); font-family: var(--mono); font-size: var(--t-sm);
  text-transform: none; letter-spacing: 0; max-width: 240px;
}
.sheet .pickrow select:focus, .sheet .pickrow input.model:focus { outline: none; border-color: var(--out); }

@media (max-width: 720px) {
  .sheet.wide { width: 100%; }
  .sheet .keyrow { flex-wrap: wrap; }
  .sheet .keyrow label { width: 100%; }
  .consultbar { padding-left: var(--s3); }
}

/* Phone portrait only. Wider than this and the one-line lockup fits. */
@media (max-width: 560px) {
  /* "SHADOW SHELL" on one line is ~95px of a ~390px bar, and the rule above
     nominates the wordmark as the thing that gives way -- so the second word was
     simply being clipped off. Wrap it at its own space instead: min-content
     makes the box as wide as the longer word, both words visible, in about half
     the width. That hands ~50px BACK to the pill, the seat name and the chat
     title, so nothing had to be taken away to fit "LINK".
     Two lines at 11px x 1.05 is ~23px -- comfortable inside the 44px bar. */
  /* Deliberately NOT flex:none. The rule above nominates the wordmark as the
     one item that yields, and that is still right -- with the consult button
     present the row can exceed even a 430px bar, and the pill and seat name
     must not be the things pushed off. Stacking means it now asks for ~44px
     instead of ~91px, so it survives far longer before it ever has to yield. */
  .mark { align-items: center; }
  .mark b {
    width: min-content; white-space: normal; line-height: 1.05;
    font-size: var(--t-xs); letter-spacing: .1em;
  }
}

/* Small phones, and any phone with the system font scaled up. The stacked
   wordmark above needs 40px; measured against the real header at 320px the row
   left it 14. Nothing is dropped to fix that -- the row just tightens: the
   inter-item gap goes 8->4 across four gaps and the context bar 48->28, which
   returns ~36px. The bar is still a legible proportion, the readout, the model
   pill and the seat name are all untouched. */
@media (max-width: 380px) {
  .top { gap: var(--s1); }
  .gauge .bar { width: 28px; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .001ms !important; animation-iteration-count: 1 !important;
      transition-duration: .001ms !important; }
}

/* ============================================================= uploads == */
/* A chip is a filed docket card: hairline box, mono label, size on the right.
   Nothing here is filled -- an attachment is a noun the turn will act on, not
   an action, so it must not compete with the one lit button on the bar. */

.chip {
  display: inline-flex; align-items: center; gap: var(--s2); max-width: 22em;
  border: 1px solid var(--rule); background: var(--sunk); color: var(--fore);
  padding: 3px var(--s2); font-family: var(--vt); font-size: var(--t-xs);
}
.chip i { font-style: normal; color: var(--dusk); flex: none; }
.chip .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chip em { font-style: normal; color: var(--dusk); flex: none; letter-spacing: .06em; }
.chip.busy { color: var(--amber); border-color: var(--rule); }
.chip.busy i { color: var(--amber); }
/* An image is the one kind the model looks at rather than reads, and it is
   worth being able to tell that apart at a glance before sending. */
.chip.k-image i { color: var(--dim); }
/* Staged large files: temporary by design, so the clock is part of the chip.
   Double rule = "held elsewhere, not in the workspace". */
.chip.staged { border-style: double; border-width: 3px; padding: 1px var(--s2); }
.chip.staged.dead { color: var(--dusk); text-decoration: line-through; }
.chip.staged.dead .nm { text-decoration: line-through; }

/* --- the pending tray, above the composer --------------------------------- */
/* Same indent as .box so the chips line up with the text you are typing. */
.compose .attbar {
  max-width: var(--page); margin: 0 auto var(--s2); padding: 0 var(--s5) 0 var(--rail);
  display: flex; flex-wrap: wrap; gap: var(--s2);
}
.compose .attbar .chip button {
  background: none; border: none; color: var(--dusk); padding: 0 0 0 2px;
  font-size: var(--t-sm); line-height: 1; flex: none;
}
.compose .attbar .chip button:hover { color: var(--alarm); }

/* --- the type-ahead tray -------------------------------------------------- */
/* Same tray, dashed: an attachment is a thing that exists on the server, a
   queued message is a thing that has not happened yet. Dashed says "pending"
   without spending a colour, and the colours here are all spoken for. */
/* A queued message is prose, not a filename. `white-space: pre` kept its
   newlines but still did not wrap, so the inherited nowrap/ellipsis from .chip
   plus the 34em cap clipped every long message to one line. Stack the chips in
   a column at full width and let the text wrap; the bar is capped at 30vh and
   scrolls so a long queue can never push the composer off screen. */
.compose .queuebar { flex-direction: column; align-items: stretch;
                     max-height: 30vh; overflow-y: auto; }
.compose .queuebar .chip { max-width: 100%; border-style: dashed; color: var(--dim);
                           align-items: flex-start; }
.compose .queuebar .chip i { color: var(--dim); }
.compose .queuebar .chip .nm { flex: 1 1 auto; min-width: 0; overflow: visible;
                               text-overflow: clip; white-space: pre-wrap;
                               overflow-wrap: anywhere; }

/* --- the attach button ---------------------------------------------------- */
/* Square, so it reads as a key on the bar next to SEND rather than a word. */
.clip {
  font-family: var(--vt); font-size: var(--t-lg); line-height: 1;
  color: var(--dim); background: none; border: 1px solid var(--rule);
  width: 38px; height: 38px; flex: none;
}
.clip:hover { color: var(--hot); border-color: var(--dim); }
.clip:disabled { color: var(--rule); border-color: var(--rule); }

/* --- the tape mark -------------------------------------------------------- */
.entry .note.attachmark { color: var(--dusk); display: flex; flex-wrap: wrap;
  align-items: center; gap: var(--s2) var(--s3); }
.entry .note.attachmark b { font-weight: normal; color: var(--dim); }
.entry .note.attachmark .chips { display: inline-flex; flex-wrap: wrap; gap: var(--s2); }

/* --- drop target ---------------------------------------------------------- */
/* The veil covers the tape, not the composer: you can still see what you were
   about to attach the file to. */
.main.dropping { outline: 1px dashed var(--dim); outline-offset: -3px; }
/* .compose and .tape both sit at z-index 1, so the veil would bury the composer
   too. Lift it over the veil while a drag is in flight. */
.main.dropping .compose { z-index: 5; }
.dropveil {
  position: absolute; inset: 0; z-index: 4; display: grid; place-items: center;
  background: color-mix(in srgb, var(--void) 78%, transparent);
  pointer-events: none;
}
.dropveil > div {
  border: 1px dashed var(--dim); padding: var(--s4) var(--s5);
  background: var(--void); color: var(--hot);
  font-family: var(--vt); font-size: var(--t-sm); letter-spacing: .12em;
  text-transform: uppercase;
}

@media (max-width: 820px) {
  .compose .attbar { padding: 0; }
}

/* ================================================== saved prompts + sharpen ==
   Two additions to the composer: a ≡ that inserts saved text, and a ✦ that
   proposes a rewrite. The rewrite is shown ABOVE the box, never in it, so the
   thing you typed is never altered without a click. */

.clip.sharp.working { color: var(--amber); border-color: var(--amber); }

/* The proposal. Same indent as .box and .attbar so it lines up with the text it
   is about, and a left edge in --fore to mark it as this pod's own output --
   deliberately NOT --out, which means "left this server" and would be a lie. */
.compose .enhbar {
  max-width: var(--page); margin: 0 auto var(--s2);
  padding: 0 var(--s5) 0 var(--rail);   /* same indent trick as .attbar */
}
.compose .enhbar .ecard {
  padding: var(--s2) var(--s3);
  background: var(--sunk); border: 1px solid var(--rule); border-left: 2px solid var(--fore);
}
.compose .enhbar .ecard .ehead {
  display: flex; align-items: center; gap: var(--s2); margin-bottom: var(--s2);
}
.compose .enhbar .ecard .ehead b {
  font-family: var(--vt); font-size: var(--t-xs); font-weight: 400;
  letter-spacing: .1em; text-transform: uppercase; color: var(--dim);
}
.compose .enhbar .ecard .ehead .sp { flex: 1; }
.compose .enhbar .ecard .ehead .stamp {
  border: 1px solid var(--rule); padding: 3px var(--s2); color: var(--dim); flex: none;
}
.compose .enhbar .ecard .ehead .stamp:hover { color: var(--hot); border-color: var(--dim); }
/* "Use this" is the default action here, so it is the lit one -- but small, and
   it must not inherit .go's block padding inside a one-line header. */
.compose .enhbar .ecard .ehead .stamp.go {
  background: var(--fore); border-color: var(--fore); color: var(--void);
  padding: 3px var(--s2); letter-spacing: .1em;
}
.compose .enhbar .ecard .ehead .stamp.go:hover { background: var(--hot); border-color: var(--hot); }
.compose .enhbar .ecard .ebody {
  margin: 0; white-space: pre-wrap; word-break: break-word;
  font-family: inherit; font-size: var(--t-md); line-height: 1.5; color: var(--hot);
  max-height: 180px; overflow: auto;
}
/* A question is not a failure, so it is amber-framed guidance, not an alarm. */
.compose .enhbar.ask .ecard { border-left-color: var(--amber); }
.compose .enhbar.ask .ecard .ebody { color: var(--fore); }
.compose .enhbar .ecard .efine {
  margin-top: var(--s2); font-family: var(--vt); font-size: var(--t-xs);
  letter-spacing: .06em; color: var(--dusk);
}

/* --- the manage sheet ----------------------------------------------------- */

.sheet .sniplist { display: flex; flex-direction: column; gap: var(--s1); }
.sheet .sniprow {
  display: flex; align-items: center; gap: var(--s2);
  border: 1px solid var(--rule); padding: var(--s2);
}
.sheet .sniprow b {
  font-weight: 400; color: var(--hot); font-size: var(--t-sm);
  flex: none; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sheet .sniprow .pre {
  flex: 1; min-width: 0; color: var(--dusk); font-size: var(--t-xs);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sheet .sniprow .stamp {
  flex: none; border: 1px solid var(--rule); padding: 3px var(--s2); color: var(--dim);
}
.sheet .sniprow .stamp:hover { color: var(--hot); border-color: var(--dim); }
.sheet .sniprow .stamp.danger:hover { color: var(--alarm); border-color: var(--alarm); }

.sheet .snipbody {
  width: 100%; box-sizing: border-box; background: var(--sunk);
  border: 1px solid var(--rule); color: var(--hot);
  padding: var(--s2) var(--s3); resize: vertical;
  font-size: var(--t-md); line-height: 1.5; margin-bottom: var(--s2);
}
.sheet .snipbody:focus { border-color: var(--fore); outline: none; }

/* A <select> in a key row: .sheet .keyrow input does not match it. */
.sheet .keyrow select.keyfield {
  flex: 1; min-width: 0; background: var(--sunk); border: 1px solid var(--rule);
  color: var(--hot); padding: var(--s2); font-size: var(--t-sm);
}

@media (max-width: 820px) {
  /* The rail indent is a luxury a phone has no room for -- match .compose .box. */
  .compose .enhbar { padding-left: 0; padding-right: 0; }
  .sheet .sniprow { flex-wrap: wrap; }
  .sheet .sniprow b { max-width: 100%; }
  .sheet .sniprow .pre { display: none; }
}

/* ========================================================= model picker == */
/* Two facts have to be legible without reading: "this chat is answered
   somewhere else" and "this model cannot actually do anything".
   
   The first borrows --out, which already means LEFT THIS SERVER, rather than
   inventing a fifth signal colour. The second reuses the outline/fill idiom the
   consult marks established -- outlined is a record, filled is a live state --
   so a chat-only engine is a FILLED --out and an agent-capable external engine
   is an outlined one. Amber and red are not available: they mean running and
   failed, and a capability warning is neither. */

.top .pill.ext {
  color: var(--out);
  border-color: color-mix(in srgb, var(--out) 45%, var(--rule));
  max-width: 300px;
}
.top .pill.ext:hover { border-color: var(--out); color: var(--hot); }
.top .pill.chatonly { background: var(--out); color: var(--void); border-color: var(--out); }
.top .pill.chatonly:hover { color: var(--void); }
.top .pill.chatonly::after { color: var(--void); }
.top .pill em {
  font-style: normal; text-transform: uppercase; letter-spacing: .08em;
  font-size: var(--t-2xs); margin-left: var(--s2);
  border: 1px solid currentColor; padding: 0 3px;
}

/* The permanent mark, sibling of .cmark and deliberately identical in weight:
   both say "content from this conversation has left the building". */
.top .xmark {
  font-family: var(--vt); font-size: var(--t-xs); flex: none;
  padding: 3px var(--s2); letter-spacing: .06em; text-transform: uppercase;
  color: var(--out); background: none;
  border: 1px solid color-mix(in srgb, var(--out) 40%, var(--rule));
}
.top .xmark:hover { border-color: var(--out); }
.top .xmark.on { background: var(--out); color: var(--void); border-color: var(--out); }

/* The sidebar's share of the same disclosure, sitting beside the consult flag. */
.row .xflag.chatonly { color: var(--out); }
.row.on .xflag { color: var(--void); }

/* --- the banner over the conversation ------------------------------------- */
/* Outside the scroller, so it is still there at message forty. Not dismissible:
   what it says does not stop being true once you have read it. */
.extbanner {
  flex: none; max-width: 100%;
  padding: var(--s2) var(--s4) var(--s2) var(--rail);
  border-bottom: 1px solid color-mix(in srgb, var(--out) 30%, var(--rule));
  border-left: 2px solid var(--out);
  background: color-mix(in srgb, var(--out) 8%, var(--void));
  color: var(--dim); font-size: var(--t-xs); line-height: 1.5;
  display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap;
}
.extbanner b { color: var(--out); font-weight: 400; }
.extbanner .stamp { margin-left: auto; border: 1px solid var(--rule); padding: 1px var(--s2); }
.extbanner .stamp:hover { color: var(--out); border-color: var(--out); }
/* Reverse video. The agent-capable banner is information; this one is a limit
   on what any answer below it can possibly be worth, and it is loud on purpose. */
.extbanner.chatonly {
  background: var(--out); color: color-mix(in srgb, var(--void) 78%, var(--out));
  border-left-color: var(--hot); border-bottom-color: var(--out);
}
.extbanner.chatonly b { color: var(--void); }
.extbanner.chatonly .stamp { color: var(--void); border-color: color-mix(in srgb, var(--void) 45%, transparent); }
.extbanner.chatonly .stamp:hover { color: var(--void); border-color: var(--void); background: color-mix(in srgb, var(--void) 12%, transparent); }

/* --- the strip above the composer ----------------------------------------- */
.compose.chatonly { border-top-color: var(--out); }
.chatonlybar {
  max-width: var(--page); margin: 0 auto var(--s3);
  padding: var(--s2) var(--s3) var(--s2) var(--rail);
  border-left: 2px solid var(--out);
  background: color-mix(in srgb, var(--out) 12%, var(--void));
  color: var(--dim); font-size: var(--t-xs); line-height: 1.5;
  display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap;
}
.chatonlybar b { color: var(--out); font-weight: 400; }
.chatonlybar .stamp { margin-left: auto; border: 1px solid var(--rule); padding: 1px var(--s2); }
.chatonlybar .stamp:hover { color: var(--out); border-color: var(--out); }

/* --- the mark on the tape ------------------------------------------------- */
.entry .note.resumemark { color: var(--dusk); }
.entry .note.stopmark { color: var(--dusk); }
.entry .note.reconmark { color: var(--amber); }
.entry .note.compactmark { color: var(--dusk); }
.entry .note.compactmark.pending { color: var(--amber); }
.entry .note.modelmark { color: var(--dusk); }
.entry .note.modelmark.ext { color: var(--out); }
.entry .note.modelmark.chatonly b,
.entry .note.modelmark.chatonly { color: var(--out); }

/* Ask() takes an array of paragraphs as well as a string. Scoped to .ask so the
   settings sheets, whose <p>s are separated by other blocks, keep their spacing. */
.sheet.ask p { margin-bottom: var(--s3); }
.sheet.ask p:last-of-type { margin-bottom: var(--s5); }

/* --- the picker sheet ----------------------------------------------------- */
/* Fixed height, three bands: a pinned head, a scrolling middle, a pinned foot.
   The first version let the whole sheet scroll, which put Close underneath
   every provider's entire catalogue -- with three keys stored that was several
   screens of scrolling to reach a button. Only the model list scrolls now, so
   the sheet is the same size whatever the catalogues return and Close never
   moves. One provider at a time for the same reason: the list you are reading
   is the only list on screen. */
.sheet.models {
  width: 680px; max-width: 100%; height: min(560px, 100%);
  display: flex; flex-direction: column;
  padding: var(--s5) var(--s4) var(--s3);
  /* 100% of the scrim's grid area, not of the viewport: the scrim already
     subtracts its own padding, so there is no `calc(100vh - <guess>)` to keep
     in sync with it. */
  /* No overflow:hidden here -- the corner stamp is positioned outside the box
     and would be clipped. The flex math below keeps the content in anyway. */
}
.sheet.models .mhead { flex: none; position: relative; padding-right: var(--s5); }
.sheet.models .mhead h2 { margin-bottom: var(--s1); }
.sheet.models .mhead .lede {
  margin: 0 0 var(--s3); font-size: var(--t-xs); color: var(--dim); line-height: 1.5;
}
.sheet.models .mhead .lede .cur { color: var(--hot); font-weight: 400; }
.sheet.models .mx {
  position: absolute; top: -4px; right: -4px;
  background: none; border: none; padding: 2px var(--s2);
  color: var(--dusk); font-size: var(--t-xl); line-height: 1;
}
.sheet.models .mx:hover { color: var(--hot); }

.sheet.models .mbody {
  flex: 1; min-height: 0; display: flex; gap: var(--s3);
  border-top: 1px solid var(--rule); padding-top: var(--s3);
}

/* One tab per engine. The tag under each name is the whole decision -- "full
   agent" or "chat only" -- so it is on the tab, before the list is opened, not
   only inside it. */
.sheet.models .mrail {
  flex: none; width: 188px; display: flex; flex-direction: column; gap: 2px;
  overflow-y: auto; overflow-x: hidden;
  border-right: 1px solid var(--rule); padding-right: var(--s3);
}
.sheet.models .mtab {
  display: block; width: 100%; text-align: left; background: none;
  border: 1px solid transparent; border-left: 2px solid transparent;
  padding: var(--s2); color: var(--dim);
}
.sheet.models .mtab b {
  display: block; font-family: var(--vt); font-weight: 400; font-size: var(--t-xs);
  letter-spacing: .06em; text-transform: uppercase; color: var(--fore);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sheet.models .mtab em {
  display: block; font-style: normal; font-size: var(--t-2xs);
  color: var(--dusk); margin-top: 2px;
}
/* Here em.warn means "this tag, in the alarm colour" -- NOT the padded warning
   panel `.sheet .warn` draws in the settings sheet. That generic rule was
   winning everything it set (12px padding, a left rule, a tint, a 16px bottom
   margin, line-height 1.6) because this one only claimed the colour, which made
   the ChatGPT tab 62px tall against every other tab's 24px on every screen
   size. Take the box back explicitly. */
.sheet.models .mtab em.warn {
  color: var(--out);
  border-left: none; background: none; padding: 0; margin: 2px 0 0;
  font-size: var(--t-2xs); line-height: inherit;
}
.sheet.models .mtab:hover { background: var(--sunk); }
.sheet.models .mtab:hover b { color: var(--hot); }
.sheet.models .mtab.on { background: var(--sunk); border-left-color: var(--hot); }
.sheet.models .mtab.on b { color: var(--hot); }
.sheet.models .mtab.chatonly.on { border-left-color: var(--out); }

.sheet.models .mpane {
  flex: 1; min-width: 0; min-height: 0; overflow-y: auto; padding-right: var(--s1);
  /* min-height:0 matters only in the phone layout, where .mbody turns into a
     column and the pane becomes the flex item that has to give. Without it its
     automatic minimum is its content height, so a long catalogue pushes the
     footer out of the bottom of the sheet instead of scrolling. */
}

.sheet.models .mfoot {
  flex: none; display: flex; align-items: center; gap: var(--s3);
  border-top: 1px solid var(--rule); margin-top: var(--s3); padding-top: var(--s3);
}
.sheet.models .mfoot .fine {
  flex: 1; min-width: 0; margin: 0; font-size: var(--t-2xs);
  color: var(--dusk); line-height: 1.45;
}
.sheet.models .mfoot .ghost { flex: none; padding: 6px var(--s4); }

/* The same sentence the banner and the confirmation use, in the place where the
   choice is actually being made. Repetition here is the feature. */
.sheet.models .mwarn {
  background: color-mix(in srgb, var(--out) 12%, var(--void));
  border-left: 2px solid var(--out); padding: var(--s2) var(--s3);
  margin: 0 0 var(--s2); color: var(--dim); font-size: var(--t-xs); line-height: 1.5;
}
.sheet.models .mwarn b { color: var(--out); font-weight: 400; }
.sheet.models .mrow {
  display: flex; align-items: baseline; gap: var(--s2); width: 100%;
  background: none; border: 1px solid transparent; padding: 3px var(--s2);
  color: var(--fore); font-size: var(--t-sm); text-align: left;
}
.sheet.models .mrow i { font-style: normal; color: var(--dusk); flex: none; }
.sheet.models .mrow .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sheet.models .mrow em {
  font-style: normal; margin-left: auto; color: var(--dusk); font-size: var(--t-xs);
}
.sheet.models .mrow:hover:not(:disabled) { border-color: var(--rule); color: var(--hot); background: var(--sunk); }
.sheet.models .mrow.on { color: var(--hot); }
.sheet.models .mrow.on i { color: var(--fore); }
.sheet.models .mrow:disabled { cursor: default; }
.sheet.models .mnone { color: var(--dusk); font-size: var(--t-xs); padding: var(--s1) var(--s2); line-height: 1.5; }
.sheet.models .mnone.bad { color: var(--alarm); }
.sheet.models .mnone.rail { padding: var(--s2) 0; }
.sheet.models .mpane .fine { margin: var(--s2) 0 0; }
.sheet.models .mnone .linky, .sheet.models .linky {
  display: inline; background: none; border: none; padding: 0;
  color: var(--dim); font-size: inherit; text-decoration: underline; text-underline-offset: 2px;
}
.sheet.models .linky:hover { color: var(--fore); }
.sheet.models .mcustom { display: flex; gap: var(--s2); margin-top: var(--s3); }
.sheet.models .mcustom input {
  flex: 1; min-width: 0; background: var(--sunk); border: 1px solid var(--rule);
  color: var(--hot); padding: var(--s1) var(--s2); font-size: var(--t-sm);
}
.sheet.models .mcustom input:focus { outline: none; border-color: var(--out); }
.sheet.models .mcustom .stamp { flex: none; border: 1px solid var(--rule); padding: 2px var(--s2); color: var(--dim); }
.sheet.models .mcustom .stamp:hover:not(:disabled) { color: var(--hot); border-color: var(--dim); }
.sheet.models .mcustom .stamp:disabled { opacity: .4; }

/* On a phone the rail moves above the list rather than beside it: 188px of
   engine names next to a model id leaves room for neither.

   It wraps into a grid rather than becoming a sideways-scrolling strip. The
   strip was the first attempt and it measures badly -- five tabs come to ~716px
   of content in a ~330px box, so two engines are visible and the other three
   are behind a horizontal swipe on an element that gives no hint it scrolls.
   auto-fit/minmax puts two per row at any phone width and one per row below
   ~320px, and every engine is on screen without a gesture.

   Keyed on height as well as width: a phone held sideways is 844x390, which is
   "desktop" by width alone, and the desktop rail measured as a 183px column
   holding five tabs -- the last engine was behind a vertical scroll. Wide and
   short wants the same treatment as narrow. */
@media (max-width: 820px), (max-height: 540px) {
  .sheet.models {
    width: 100%; max-width: 680px; height: 100%;
    padding: var(--s5) var(--s3) var(--s3);
  }
  .sheet.models .mbody { flex-direction: column; gap: var(--s2); }
  .sheet.models .mrail {
    width: auto; flex: none; min-width: 0;
    /* 128px, not 140: at 140 a 320px-wide phone drops to a single column, the
       rail grows to 250px and the model list is left 13px tall. 128 keeps two
       columns all the way down to 320 and still fits the longest engine name
       ("SHADOW SHELL POD" measures 104px); only the tag below it ellipsises. */
    display: grid; grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
    gap: var(--s1);
    overflow: visible;
    border-right: none; border-bottom: 1px solid var(--rule);
    padding: 0 0 var(--s2);
  }
  .sheet.models .mtab {
    width: auto; min-width: 0;
    border-left: none; border-bottom: 2px solid transparent;
    padding: var(--s1) var(--s2);
  }
  .sheet.models .mtab em { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .sheet.models .mtab.on { border-bottom-color: var(--hot); }
  .sheet.models .mtab.chatonly.on { border-bottom-color: var(--out); }
  .sheet.models .mnone.rail { grid-column: 1 / -1; }
}

/* Narrow only. The footer's fine print and Close will not sit side by side in
   330px, so the print takes its own row -- but on a wide-and-short screen there
   is ample width and that extra row is pure waste of the little height there
   is, so it does not belong in the shared block above. */
@media (max-width: 820px) {
  .sheet.models .mfoot { flex-wrap: wrap; gap: var(--s2); }
  .sheet.models .mfoot .fine { flex: 1 1 100%; }
  .sheet.models .mfoot .ghost { margin-left: auto; }
  .extbanner, .chatonlybar { padding-left: var(--s3); }
}

/* Short only -- a phone in landscape, or a short desktop window. Height is the
   scarce axis here, so the tabs collapse to one line each: name and tag side by
   side instead of stacked. Stacked two-line tabs in two rows measured 143px of
   a 358px sheet and left the model list 10px tall. The tag still shows; it is
   the whole decision and hiding it to save 20px would be the wrong saving. */
@media (max-height: 540px) {
  .sheet.models { padding-top: var(--s4); }
  .sheet.models .mhead h2 { font-size: var(--t-md); }
  .sheet.models .mtab {
    display: flex; align-items: baseline; gap: var(--s2); padding: 2px var(--s2);
  }
  .sheet.models .mtab b { display: inline; flex: none; }
  .sheet.models .mtab em,
  .sheet.models .mtab em.warn { display: inline; margin-top: 0; min-width: 0; }
}

/* --- jump to latest ------------------------------------------------------- */
/* Sticky to the foot of the scrollport, and deliberately height: 0. It is the
   permanent last child of .reel, so if it had a height it would change the
   reel's height, and the ResizeObserver that keeps the tape pinned would be
   reading its own output. align-items: flex-end lets the button overflow
   upward out of a zero-height line instead -- overflow up does not count
   toward scrollHeight, so the pin arithmetic never sees it.
   padding-left tracks --rail, so the button centres over the text column
   rather than the column plus the stamp rail, on both layouts. */
.jumpwrap {
  position: sticky; bottom: 0; z-index: 4; height: 0;
  display: flex; align-items: flex-end; justify-content: center;
  padding-left: var(--rail); pointer-events: none;
}
.jump {
  pointer-events: auto; margin-bottom: var(--s4);
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s1) var(--s3);
  background: var(--void); color: var(--dim);
  border: 1px solid var(--dusk); box-shadow: 3px 3px 0 rgba(0, 0, 0, .75);
}
.jump:hover { color: var(--hot); border-color: var(--dim); }
.jump i { font-style: normal; }
/* Amber means running, and a turn running while you read back up the tape is
   exactly what this button is for. Solid, not .pulse: a blinking thing in the
   eyeline of someone reading is the wrong trade. */
.jump.live i { color: var(--amber); }

/* Policy-block notice on the tape: what was blocked, and a one-click
   false-positive report that files the block id for operator review.
   Scoped like the other tape notes (2026-09-22). */
.entry .note.gateblock { color: var(--dusk); display: flex; flex-direction: column; gap: var(--s1); }
.entry .note.gateblock b { color: var(--amber); font-weight: 600; }
.gateblock .gb-report { align-self: flex-start; border: 1px solid var(--dusk); padding: 2px var(--s2); color: var(--dusk); background: none; font: inherit; font-size: var(--t-sm); cursor: pointer; }
.gateblock .gb-report:hover:not(:disabled) { border-color: var(--hot); color: var(--hot); }
.gateblock .gb-report:disabled { opacity: .5; cursor: default; }
.gateblock .gb-done { color: var(--dim); }

/* ===================================================== maintenance banner == */
/* The one element an operator can make LOUD on purpose. Amber everywhere else
   means "running"; here it is spent on "everyone look up" -- switched on from
   the panel for the duration of heavy work and off afterwards, so the
   exception stays rare enough to keep its force. Same construction in red
   (lv-danger) for "this will hurt", and plain reverse phosphor (lv-info) for
   notices that only need to be words. */

.mbanner {
  --mb: var(--amber);
  flex: none; display: flex; align-items: center; gap: var(--s3);
  padding: var(--s2) var(--s4);
  background: var(--mb); color: var(--void);
  font: 400 var(--t-sm)/1.45 var(--vt); letter-spacing: .06em;
  position: relative;
}
/* .mbanner's own display:flex would out-rank the UA's [hidden], so the guard
   has to live here too. */
.mbanner[hidden] { display: none; }
.mbanner::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 4px;
  background: repeating-linear-gradient(45deg,
    var(--void) 0 10px, var(--mb) 10px 20px);
}
.mbanner .mb-blink { flex: none; animation: mb-blink 1.1s steps(2, jump-none) infinite; }
.mbanner .mb-text  { overflow-wrap: anywhere; }
.mbanner.lv-danger { --mb: var(--alarm); }
.mbanner.lv-info   { --mb: var(--dim); }
@keyframes mb-blink { 0% { opacity: 1; } 50% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .mbanner .mb-blink { animation: none; }
}

/* --- artifacts: agent-made files ------------------------------------------ */
/* Chips are links here -- the whole chip downloads -- so the anchor defaults
   go before the hover states. */
a.chip.dl, a.chip.dl:visited { color: var(--fore); text-decoration: none; }
a.chip.dl { cursor: pointer; }
a.chip.dl:hover { border-color: var(--hot); }
a.chip.dl:hover i, a.chip.dl:hover em { color: var(--hot); }
.note.attachmark.arte b { display: block; margin: 0 0 var(--s2); }

/* --- the Files drawer ------------------------------------------------------ */
.fpanel {
  width: min(46rem, 92vw); max-height: 74vh; display: flex; flex-direction: column;
  background: var(--sunk); border: 1px solid var(--rule);
  box-shadow: 0 1.5rem 3rem rgba(0, 0, 0, .35);
}
.fhead { display: flex; align-items: center; justify-content: space-between;
  padding: var(--s3) var(--s3) var(--s2); }
.fhead b { font-family: var(--vt); letter-spacing: .04em; text-transform: uppercase;
  font-size: var(--t-sm); }
.flist { overflow-y: auto; padding: 0 var(--s3) var(--s3); }
.fsec { font-family: var(--vt); color: var(--dusk); font-size: var(--t-xs);
  letter-spacing: .08em; text-transform: uppercase; margin: var(--s3) 0 var(--s2); }
.frow { display: flex; align-items: center; gap: var(--s2); padding: var(--s2) 0;
  border-bottom: 1px solid var(--rule); color: var(--fore); text-decoration: none; }
.frow:hover { color: var(--hot); }
.frow i { font-style: normal; color: var(--dusk); flex: none; }
.frow .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
.frow em { font-style: normal; color: var(--dusk); font-size: var(--t-xs); flex: none; }
.frow .stamp { flex: none; }
.fnone { color: var(--dim); font-size: var(--t-sm); padding: var(--s2) 0; }
/* ---- artifacts v2: transcript thumbnails, drawer previews, zip link ---- */
.frowline { display: flex; flex-wrap: wrap; align-items: center;
  border-bottom: 1px solid var(--rule); }
.frowline .frow { flex: 1 1 auto; min-width: 0; border-bottom: none; }
.eye { flex: none; background: none; border: 1px solid var(--rule); color: var(--dusk);
  border-radius: .4rem; padding: .1rem .45rem; cursor: pointer; margin-left: var(--s1);
  font-size: var(--t-sm); }
.eye:hover, .eye.on { color: var(--hot); border-color: var(--hot); }
.fprev { flex-basis: 100%; margin: 0 0 var(--s2); }
.fprev.load { color: var(--dim); font-size: var(--t-sm); padding: var(--s1) 0; }
.fprev.txt { overflow: auto; max-height: 40vh; background: var(--sunk);
  border: 1px solid var(--rule); border-radius: .4rem; padding: var(--s2);
  font: .78rem/1.45 ui-monospace, SFMono-Regular, Menlo, monospace;
  white-space: pre-wrap; word-break: break-word; color: var(--fore); }
.fprev.img { max-height: 48vh; object-fit: contain; border: 1px solid var(--rule);
  border-radius: .4rem; background: var(--sunk); }
.fprev.pdf { width: 100%; height: 60vh; border: 1px solid var(--rule);
  border-radius: .4rem; background: var(--sunk); }
.fhead .zipall { margin-left: auto; margin-right: var(--s2); color: var(--dusk);
  font-size: var(--t-xs); text-decoration: none; border: 1px solid var(--rule);
  border-radius: 999px; padding: .2rem .6rem; white-space: nowrap; }
.fhead .zipall:hover { color: var(--hot); border-color: var(--hot); }

/* ---- artifacts v3: one-line transcript pointer, right-docked panel ---- */
.artline { background: none; border: none; padding: var(--s1) 0; cursor: pointer;
  color: var(--dusk); font-size: var(--t-xs); text-align: left; }
.artline:hover { color: var(--hot); }
.fside { position: fixed; top: 0; right: 0; bottom: 0; display: flex;
  width: min(27rem, 94vw); padding: var(--s2); z-index: 70; }
.fside .fpanel { width: 100%; height: 100%; max-height: none; }

/* ---- inline images (2026-10-02): generated art in the tape + lightbox ----
   The tape stays text-first; images are the one deliverable that deserves
   pixels. Thumbs are square-cropped previews -- the lightbox shows truth. */
.artimgs { display: flex; flex-wrap: wrap; gap: var(--s1); padding: var(--s1) 0 var(--s2); }
.artimg { padding: 0; width: 132px; height: 132px; border: 1px solid var(--rule);
  border-radius: 8px; overflow: hidden; cursor: zoom-in; background: var(--sunk); }
.artimg img { width: 100%; height: 100%; object-fit: cover; display: block; }
.artimg:hover { border-color: var(--hot); }
.artmore { background: none; border: 1px dashed var(--rule); border-radius: 8px;
  color: var(--dusk); font-size: var(--t-xs); cursor: pointer;
  width: 72px; height: 132px; }
.artmore:hover { color: var(--hot); border-color: var(--hot); }
.lbox { position: fixed; inset: 0; z-index: 110; background: rgba(8, 8, 10, 0.86);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--s2); cursor: zoom-out; }
.lbox img { max-width: min(92vw, 1200px); max-height: 78vh; object-fit: contain;
  border-radius: 6px; box-shadow: 0 12px 56px rgba(0, 0, 0, 0.7); cursor: default; }
.lbox .lclose { position: absolute; top: var(--s3); right: var(--s3);
  color: #f2f2f2; font-size: 1.2rem; }
.lbox .lclose:hover { color: #fff; }
.lbox .lbar { display: flex; gap: var(--s3); align-items: baseline; }
.lbox .lname { color: #d8d8d8; font-size: var(--t-sm); }
.lbox .ldl { color: #9ecbff; font-size: var(--t-sm); }

/* ---- Classic ink pass ------------------------------------------------
   Text is ink, green is accent. The rules above this block re-skin the
   chrome; this one re-inks the words. Kept green on purpose: the .go
   primary button, links, the .mark b brand wordmark, the models-sheet
   tab-on marker, small icon-button hovers, and .copy.ok's tick. */
html[data-skin^="classic"] input,
html[data-skin^="classic"] textarea,
html[data-skin^="classic"] select,
html[data-skin^="classic"] .editor > input,
html[data-skin^="classic"] .compose textarea,
html[data-skin^="classic"] .palette > input,
html[data-skin^="classic"] .gate input,
html[data-skin^="classic"] .sheet.vault input,
html[data-skin^="classic"] .sheet .keyrow input,
html[data-skin^="classic"] .sheet .keyrow select.keyfield,
html[data-skin^="classic"] .sheet .pickrow select,
html[data-skin^="classic"] .sheet .pickrow input.model,
html[data-skin^="classic"] .sheet .snipbody,
html[data-skin^="classic"] .sheet.models .mcustom input,
html[data-skin^="classic"] .compose .enhbar .ecard .ebody {
  color: var(--fore); caret-color: var(--fore);
}
html[data-skin^="classic"] .prompt .body,
html[data-skin^="classic"] .prose h2, html[data-skin^="classic"] .prose h3,
html[data-skin^="classic"] .prose h4, html[data-skin^="classic"] .prose h5,
html[data-skin^="classic"] .prose strong,
html[data-skin^="classic"] .prose em,
html[data-skin^="classic"] .prose code,
html[data-skin^="classic"] .sheet h2,
html[data-skin^="classic"] .sheet .sniprow b,
html[data-skin^="classic"] .sheet.models .mhead .lede .cur {
  color: var(--fore);
}
/* List rows hover to ink like every classic list; green hover text is a
   phosphor habit. Selected model row: ink plus weight, matching .prow.on. */
html[data-skin^="classic"] .row:hover,
html[data-skin^="classic"] .hit:hover,
html[data-skin^="classic"] .frow:hover,
html[data-skin^="classic"] .artline:hover,
html[data-skin^="classic"] .cq:hover,
html[data-skin^="classic"] .jump:hover,
html[data-skin^="classic"] .sheet.models .mrow:hover:not(:disabled) {
  color: var(--fore);
}
html[data-skin^="classic"] .sheet.models .mrow.on {
  color: var(--fore); font-weight: 600;
}

/* ---- sharpen working state: motion + a live counter, so a 15-30s wait on
   a busy pod reads as progress, not a dead button ---- */
@keyframes slspin { to { transform: rotate(360deg); } }
.clip.sharp.working { animation: slspin 1.1s linear infinite; }
.compose .enhbar.pending .ecard { border-left-color: var(--amber); }
@media (prefers-reduced-motion: reduce) {
  .clip.sharp.working { animation: none; }
}


/* generate_image: pictures ride in the tool card, under the header and
   outside the toggle -- the image is the point of the call. */
.genpics { display: flex; flex-wrap: wrap; gap: var(--s2); padding: 0 var(--s2) var(--s2); }
.genpics img { max-width: 280px; max-height: 280px; display: block;
  border: 1px solid var(--rule); border-radius: 6px; }

/* ======================================================= GUI polish 2026-10 ==
   One pass, appended so it reads (and reverts) as a unit.
   1. Sidebar rows: the title gets the row. Before this pass a 257px row gave
      its title 149px -- 38px of indent+dot on the left, 70px of always-on
      "Xm ago" plus a reserved-but-invisible ⋯ on the right. The kind dot now
      only appears when it says something (running chat, note), and the age
      and ⋯ float over the end of the title on hover instead of renting space.
   2. Phones: wider drawer, 40px+ touch rows, 16px inputs (iOS zooms on
      anything smaller), the rail indent cut down, row/answer actions
      reachable without hover.
   3. Classic: the rest of the terminal leaves -- no rail, gutter clock or $,
      user turns as bubbles, a card composer, chevrons, a quiet status line. */

/* --- 1. sidebar rows, every skin ------------------------------------------- */
.side .scroll { padding-top: var(--s1); }
.ghead { padding: var(--s4) var(--s3) var(--s1) 14px; }
.row, .row.sub {
  --rowbg: var(--void);
  position: relative; width: auto; margin: 1px 6px; padding: 5px 8px;
  gap: 6px; background: transparent;
}
.row:hover   { --rowbg: var(--rule); background: var(--rowbg); }
.row.on      { --rowbg: var(--dim);  background: var(--rowbg); }
.row.on:hover { --rowbg: var(--fore); background: var(--rowbg); }
.row .txt { line-height: 1.45; }
.row .dot { display: none; }
.row .dot.note, .row.live .dot { display: block; }
/* Age and ⋯ ride over the tail of the title, fading it out rather than
   cutting it, and only when the pointer is on the row. */
.row .tick, .row .dots {
  position: absolute; top: 0; bottom: 0; display: flex; align-items: center;
  visibility: hidden;
}
.row .dots { right: 0; padding: 0 8px; opacity: 1; background: var(--rowbg); font-size: 14px; }
.row .tick {
  right: 26px; padding: 0 4px 0 22px; white-space: nowrap;
  background: linear-gradient(to right, transparent, var(--rowbg) 18px);
}
.row:hover .tick, .row:hover .dots,
.row:focus-within .dots, .row .dots:focus-visible { visibility: visible; }
.row:has(.ren) .dots { display: none; }
/* (2026-10-02) One right edge. The folder count and every title tail now end
   on the same line, 14px from the drawer edge, where the row text ends. The
   folder's ⋯ no longer rents space beside the count: it floats over it on
   hover, the way the row's does. Titles fade out instead of stopping at the
   last whole glyph, so a column of monospace tails lands on a straight edge
   rather than a ragged one (and no "for ..." with a stranded space). */
.ghead { position: relative; padding-right: 14px; }
.ghead .n { flex: none; }
.ghead .dots {
  position: absolute; top: 0; bottom: 0; right: 6px; display: flex; align-items: center;
  padding: var(--s4) 8px var(--s1); opacity: 1; visibility: hidden;
}
.ghead:hover .dots, .ghead .dots:focus-visible { visibility: visible; }
.ghead:hover .n, .ghead:has(.dots:focus-visible) .n { visibility: hidden; }
.row .txt {
  text-overflow: clip;
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
          mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
}
.empty { padding-left: 14px; }

/* --- 2. phones and touch ---------------------------------------------------- */
@media (max-width: 860px) {
  :root { --side: min(86vw, 320px); --rail: 22px; --axis: 8px; }
  .side { box-shadow: 0 0 0 1px var(--rule), 8px 0 28px rgba(0,0,0,.45); }
  /* The gutter clock costs a phone ~40px of every line of the transcript.
     The node stays on the rail; the time is in the tooltip-free desktop view. */
  .stampline time { display: none; }
  .reel { padding-left: 0; }
  .turnsep .stamp { padding-left: 0; }
  .hint { gap: var(--s2); letter-spacing: .02em; }
  .hint .ctoggle { padding: 0 4px; letter-spacing: .02em; }
  .compose { padding-bottom: env(safe-area-inset-bottom, 0); }
  .sidefoot { padding-bottom: 0; }
  .find, .compose textarea, .ren, .gate input { font-size: 16px; }
}
@media (hover: none), (pointer: coarse) {
  .row, .row.sub { padding: 10px 10px; margin: 1px 6px; font-size: var(--t-md); }
  .ghead { padding-top: var(--s4); padding-bottom: var(--s2); }
  /* Glass has no hover, so the folder ⋯ stays in flow at the edge and the
     count sits beside it. */
  .ghead { padding-right: 4px; }
  .ghead .dots { position: static; visibility: visible; opacity: 1; padding: 6px 10px; }
  .ghead:hover .n { visibility: visible; }
  /* A header with no menu (Archive while empty) keeps its count in the same
     column as the others: an invisible ⋯ of the same metrics holds the slot. */
  .ghead:not(:has(.dots))::after {
    content: "⋯"; visibility: hidden; flex: none; font-family: var(--vt); font-size: 13px;
    line-height: 1; padding: 6px 10px;
  }
  /* No hover on glass: the open row keeps its ⋯ (long-press opens the menu on
     the others), and per-message actions stay visible, just quieter. */
  .row.on .dots {
    visibility: visible; font-size: 16px; padding: 0 12px 0 26px;
    background: linear-gradient(to right, transparent, var(--rowbg) 20px);
  }
  .row:hover .tick { visibility: hidden; }
  .row:not(.on):hover .dots { visibility: hidden; }
  .sidetop .acts button { padding: 8px 0; }
  .find { padding: 9px var(--s3); }
  .sidefoot button { padding: 0 var(--s2); }
  .promptact, .answeract { opacity: .7; }
}

/* (2026-10-04) Phones: the right margin matches the rail. The left gutter holds
   the timeline marks, and with only the touch padding (12px) on the right the
   whole transcript read as pushed right -- 26px left vs 12px right. Same width
   both sides now; the blank screen keeps its own symmetric padding, and the
   classic skins (rail 0) override .reel padding with higher specificity. */
@media (max-width: 860px) {
  .reel:not(:has(.blank)) { padding-right: var(--rail); }
}

/* --- 3. classic ------------------------------------------------------------- */
html[data-skin^="classic"] {
  --rail: 0px; --axis: 0px;
  --measure: 720px; --page: calc(var(--measure) + 2 * var(--s5));
  --t-md: 14.5px; --t-sm: 13px; --t-lg: 15.5px; --t-xs: 11.5px;
  --lead: 1.62;
}
html[data-skin^="classic"][data-density="compact"] { --t-md: 13.5px; --t-sm: 12.5px; --lead: 1.5; }

/* top bar */
html[data-skin^="classic"] .top { background: #FFFFFF; padding-left: var(--s3); }
html[data-skin^="classic"] .mark b {
  text-transform: none; letter-spacing: .01em; font-size: 15px; font-weight: 650;
}
html[data-skin^="classic"] .gauge .bar {
  height: 6px; border: 0; border-radius: 999px; background: #E9ECF1; width: 120px;
}
html[data-skin^="classic"] .gauge .bar::after { display: none; }
html[data-skin^="classic"] .gauge .used { background: var(--hot); border-radius: 999px; }
html[data-skin^="classic"] .gauge .floor { background: #CDD3DC; }
html[data-skin^="classic"] .gauge .read { font-size: 12px; color: var(--dim); }
html[data-skin^="classic"] .pill { padding: 4px 10px; font-size: 12.5px; }
html[data-skin^="classic"] .top .icon { border-radius: 8px; padding: 4px 6px; }
html[data-skin^="classic"] .top .icon:hover { background: #EEF0F3; color: var(--fore); }

/* sidebar */
html[data-skin^="classic"] .side { background: #F3F4F6; border-right-color: #E6E8EC; }
html[data-skin^="classic"] .sidetop { border-bottom: 0; padding: var(--s3) 10px var(--s2); }
html[data-skin^="classic"] .find {
  border-radius: 10px; padding: 8px 12px; background: #FFFFFF; color: var(--fore);
  font-size: 13.5px;
}
html[data-skin^="classic"] .find:focus { border-color: #98A2B3; box-shadow: 0 0 0 3px rgba(152,162,179,.18); }
html[data-skin^="classic"] .sidetop .acts { gap: 6px; }
html[data-skin^="classic"] .sidetop .acts button {
  background: #FFFFFF; border-color: #E4E7EC; color: var(--fore); font-weight: 500;
  font-size: 12.5px; padding: 5px 0;
}
html[data-skin^="classic"] .sidetop .acts button:hover { background: #EBEDF0; border-color: #D0D5DD; color: var(--fore); }
html[data-skin^="classic"] .ghead .stamp { font-size: 12px; font-weight: 600; color: var(--dim); letter-spacing: 0; }
html[data-skin^="classic"] .ghead:hover .stamp { color: var(--fore); }
html[data-skin^="classic"] .fold { width: 12px; color: var(--dusk); }
html[data-skin^="classic"] .fold::before {
  content: "›"; display: inline-block; font-size: 15px; line-height: 1;
  transition: transform .15s ease; font-family: var(--vt);
}
html[data-skin^="classic"] .fold.open::before { content: "›"; transform: rotate(90deg); }
html[data-skin^="classic"] .ghead:hover .fold { color: var(--fore); }
html[data-skin^="classic"] .row, html[data-skin^="classic"] .row.sub {
  border-radius: 8px; font-size: 13.5px; color: #344054;
}
html[data-skin^="classic"] .row:hover { --rowbg: #E7E9ED; color: var(--fore); }
html[data-skin^="classic"] .row.on,
html[data-skin^="classic"] .row.on:hover { --rowbg: #E2E5EA; background: var(--rowbg); color: var(--fore); font-weight: 500; }
html[data-skin^="classic"] .row .tick { color: var(--dusk); font-size: 11.5px; }
html[data-skin^="classic"] .row .dot { border-radius: 50%; }
html[data-skin^="classic"] .row.live .dot { animation: none; box-shadow: 0 0 0 3px rgba(180,83,9,.16); }
html[data-skin^="classic"] .row .dot.note { border-radius: 2px; }

/* the status line: a quiet footer, not a slate bar */
html[data-skin^="classic"] .main::after { background: transparent; }
html[data-skin^="classic"] .sidefoot {
  background: transparent; color: var(--dim); border-top: 1px solid #E6E8EC;
  height: calc(var(--stat) + 10px); padding: 0 10px;
}
html[data-skin^="classic"] .sidefoot .stamp { color: var(--dim); font-size: 12px; }
html[data-skin^="classic"] .sidefoot button {
  border-radius: 6px; padding: 3px 8px; line-height: 1.3; background: #FFFFFF;
  border-color: #E4E7EC; color: var(--fore); font-weight: 500;
}
html[data-skin^="classic"] .sidefoot button:hover { background: #E7E9ED; color: var(--fore); }
html[data-skin^="classic"] .sidefoot .out:hover { background: #FDECEC; color: var(--alarm); }
html[data-skin^="classic"] .hint {
  background: transparent; color: var(--dusk); text-transform: none; letter-spacing: 0;
  font-family: var(--vt); font-size: 11.5px; height: calc(var(--stat) + 10px);
  margin-top: 0;
}
html[data-skin^="classic"] .hint .ctoggle {
  color: var(--dim); border: 1px solid var(--rule); border-radius: 999px;
  text-transform: none; letter-spacing: 0; font-size: 11.5px; padding: 1px 9px; background: #FFFFFF;
}
html[data-skin^="classic"] .hint .ctoggle:hover { color: var(--fore); border-color: #D0D5DD; }
html[data-skin^="classic"] .hint .ctoggle.on { background: var(--out); border-color: var(--out); color: #FFFFFF; }
html[data-skin^="classic"] .hint .ctoggle.ever { color: var(--out); border-color: color-mix(in srgb, var(--out) 40%, var(--rule)); }
html[data-skin^="classic"] .hint .tight { border-radius: 999px; color: #FFFFFF; }

/* transcript: no rail, no clock, no $ -- the conversation is the chrome */
html[data-skin^="classic"] .reel { padding-left: var(--s5); padding-right: var(--s5); }
html[data-skin^="classic"] .reel::before,
html[data-skin^="classic"] .stampline,
html[data-skin^="classic"] .turnsep { display: none; }
html[data-skin^="classic"] .entry { padding-left: 0; }
html[data-skin^="classic"] .prompt {
  display: flex; flex-direction: column; align-items: flex-end;
  padding: var(--s3) 0 var(--s4);
}
html[data-skin^="classic"] .entry:first-child .prompt { padding-top: var(--s2); }
html[data-skin^="classic"] .prompt .body {
  background: #EBEEF2; border-radius: 18px; padding: 10px 16px;
  max-width: min(85%, 600px); text-indent: 0; font-size: var(--t-md); line-height: 1.55;
}
html[data-skin^="classic"] .prompt .body::before { content: none; }
html[data-skin^="classic"] .prompt > .stamp { margin-top: var(--s1); }
html[data-skin^="classic"] .answeract .copy, html[data-skin^="classic"] .promptact .copy { border-radius: 6px; }
html[data-skin^="classic"] .prose h2 { border-bottom: 0; font-size: 1.3em; }
html[data-skin^="classic"] .prose h3 { font-size: 1.12em; }
html[data-skin^="classic"] .prose ul > li::before { content: "•"; color: var(--dim); }
html[data-skin^="classic"] .prose code { border-radius: 5px; border-color: #E4E7EC; background: #F2F4F7; font-size: .88em; }
html[data-skin^="classic"] .prose blockquote { border-left-width: 3px; border-left-color: #D0D5DD; }
html[data-skin^="classic"] .prose th { color: var(--fore); font-weight: 600; font-size: 12.5px; }
html[data-skin^="classic"] .block { border-radius: 10px; overflow: hidden; }
html[data-skin^="classic"] .tool { border-radius: 10px; background: #FFFFFF; overflow: hidden; margin-bottom: var(--s3); }
html[data-skin^="classic"] .tool > .head { padding: 7px 12px; }
html[data-skin^="classic"] .tool > .head:hover { background: #F7F8FA; }
html[data-skin^="classic"] .tool .tri { width: 12px; }
html[data-skin^="classic"] .tool .tri::before {
  content: "›"; display: inline-block; font-size: 15px; line-height: 1;
  transition: transform .15s ease; font-family: var(--vt);
}
html[data-skin^="classic"] .tool.open .tri::before { content: "›"; transform: rotate(90deg); }
html[data-skin^="classic"] .tool .name {
  text-transform: none; letter-spacing: 0; font-size: 12.5px; font-weight: 600;
}
html[data-skin^="classic"] .tool.ok .name { color: var(--fore); }
html[data-skin^="classic"] .tool .arg { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; }
html[data-skin^="classic"] .tool .dur { font-size: 11.5px; }
html[data-skin^="classic"] .tool .body { background: #F9FAFB; }

/* composer: one rounded card holding the field and its buttons */
html[data-skin^="classic"] .compose {
  border-top: 0; background: transparent; padding-top: var(--s2);
}
html[data-skin^="classic"] .compose .box {
  max-width: calc(var(--page) - 2 * var(--s5)); padding: 8px;
  background: #FFFFFF; border: 1px solid #DDE1E7; border-radius: 18px;
  box-shadow: 0 1px 2px rgba(16,24,40,.04), 0 6px 20px rgba(16,24,40,.06);
  align-items: flex-end; gap: 6px;
  transition: border-color .12s, box-shadow .12s;
}
html[data-skin^="classic"] .compose .box:focus-within {
  border-color: #B4BCC8;
  box-shadow: 0 1px 2px rgba(16,24,40,.05), 0 8px 24px rgba(16,24,40,.09);
}
html[data-skin^="classic"] .compose .box textarea {
  border: 0; background: transparent; box-shadow: none; outline: none;
  padding: 8px 6px; min-height: 38px; font-size: var(--t-md);
}
html[data-skin^="classic"] .clip {
  border: 0; border-radius: 10px; width: 36px; height: 36px; font-size: 17px; color: var(--dim);
}
html[data-skin^="classic"] .clip:hover { background: #F2F4F7; color: var(--fore); }
html[data-skin^="classic"] .clip:disabled { background: none; color: #D0D5DD; }
html[data-skin^="classic"] .compose .go {
  border-radius: 12px; height: 36px; padding: 0 var(--s4); font-size: 13.5px;
}
html[data-skin^="classic"] .compose.consulting .box { border-color: color-mix(in srgb, var(--out) 45%, #DDE1E7); }

@media (max-width: 860px) {
  html[data-skin^="classic"] {
    --t-md: 15px; --t-sm: 13.5px; --t-lg: 16px; --t-xs: 12px;
    --rail: 0px;
  }
  html[data-skin^="classic"] .reel { padding-left: var(--s4); padding-right: var(--s4); }
  html[data-skin^="classic"] .compose { padding-left: var(--s3); padding-right: var(--s3); }
  html[data-skin^="classic"] .compose .box { max-width: none; border-radius: 20px; }
  html[data-skin^="classic"] .compose .box textarea { font-size: 16px; }
  html[data-skin^="classic"] .side { box-shadow: 8px 0 30px rgba(16,24,40,.18); }
  html[data-skin^="classic"] .row, html[data-skin^="classic"] .row.sub { font-size: 15px; }
  html[data-skin^="classic"] .find { font-size: 16px; }
  html[data-skin^="classic"] .prompt .body { max-width: 88%; }
  html[data-skin^="classic"] .gauge .bar { width: 48px; }
  html[data-skin^="classic"] .pill { padding: 3px 8px; font-size: 12px; }
  html[data-skin^="classic"] .top .who { font-size: 12px; }
}

/* ===== GUI polish 2026-10-01, pass 2 ======================================= */
/* Touch: the open row keeps its ⋯ permanently, and that overlay sat on the
   title's ellipsis, so a long title ended "…Jelly." with the dots hidden.
   Reserve the overlay's width on the open row only. */
@media (hover: none), (pointer: coarse) {
  .row.on .txt { padding-right: 36px; }
}

/* ===== GUI polish 2026-10-01, pass 3 ======================================= */
/* Status line: the keyboard-hint list is the item that gives way, not the
   number. It is 813px of 3270 caps on the terminal skin, and with the two
   toggles beside it the "tokens of room left" figure was ellipsized at 1440
   and squeezed to nothing at 1180. Shrink weight 100 against the room's 1, so
   the hints lose first and the figure holds its width until a phone, where the
   hints are hidden anyway and .room stays the sacrificial item. */
.hint > :first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; flex: 0 100 auto; }
/* Above the phone breakpoint the hints can always absorb the whole deficit,
   so the figure is rigid there: a weighted shrink still leaked 1-2px into it
   at 1024 and ellipsized "left" to "le…". */
@media (min-width: 861px) { .hint .room { flex-shrink: 0; } }
/* The collapsed-reasoning peek was set in the hairline colour, which is a
   border, not text: 1.7:1 on phosphor, 1.2:1 on classic -- invisible on a
   laptop in daylight, and the peek exists to be skimmed. One step up the
   ramp: still the quietest text on the page. Classic needs two steps, because
   its --dusk is a 2.3:1 gray on white. Same two-step for classic's status
   line and the folder counts, which sat at 2.4:1 and 2.3:1. */
.thinkcard .tpeek { color: var(--dusk); }
html[data-skin^="classic"] .thinkcard .tpeek,
html[data-skin^="classic"] .hint,
html[data-skin^="classic"] .ghead .n { color: var(--dim); }

/* Chat options sheet (2026-10-02; compact pages 2026-10-03). A bounded flex
   column like .sheet.models: header and footer stay put, only .cobody
   scrolls, so the sheet never runs off the screen. */
.sheet.chatopts {
  width: 520px; max-height: 100%; display: flex; flex-direction: column;
  padding: var(--s5) var(--s4) var(--s3);
}
.chatopts .cohead { flex: none; }
.chatopts .cohead h2 { display: flex; align-items: center; gap: var(--s2); margin-bottom: var(--s2); }
.chatopts .coback { font: inherit; color: var(--dim); background: none; border: 0; padding: 0 var(--s1); cursor: pointer; line-height: 1; }
.chatopts .coback:hover { color: var(--hot); }
.chatopts .cobody { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; margin: 0 calc(-1 * var(--s2)); padding: 0 var(--s2); }
.chatopts .busyline { color: var(--out); margin: 0 0 var(--s2); }
.chatopts .acts { flex: none; margin-top: var(--s3); padding-top: var(--s3); border-top: 1px solid var(--rule); align-items: center; }
.chatopts .cobar { display: flex; align-items: center; gap: var(--s2); padding-bottom: var(--s3); }
.chatopts .cobar .pill { max-width: 46%; }
.chatopts .stamp:disabled, .chatopts .obtns button:disabled { opacity: .4; cursor: default; }
.chatopts .cobar .stamp, .chatopts .orow .stamp, .chatopts .kbline .stamp {
  border: 1px solid var(--rule); padding: 2px var(--s2); color: var(--dim); flex: none;
}
.chatopts .cobar .stamp:hover:not(:disabled), .chatopts .orow .stamp:hover:not(:disabled),
.chatopts .kbline .stamp:hover:not(:disabled) { color: var(--hot); border-color: var(--dim); }
/* One switch per row: name, a one-line status, the control. */
.chatopts .orow {
  display: flex; align-items: center; gap: var(--s2); width: 100%; min-height: 34px;
  border-top: 1px solid var(--rule); padding: var(--s2) 0; font: inherit; color: inherit; text-align: left;
}
.chatopts .orow b { flex: none; font-weight: 400; color: var(--hot); min-width: 7.5em; }
.chatopts .orow .osum {
  flex: 1; min-width: 0; font-size: var(--t-xs); color: var(--dusk);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chatopts .orow .switch { flex: none; }
.chatopts button.orow.onav { background: none; border-left: 0; border-right: 0; border-bottom: 0; cursor: pointer; }
.chatopts button.orow.onav:hover { background: var(--sunk); }
.chatopts .orow .ogo { flex: none; color: var(--dim); font-size: var(--t-lg); line-height: 1; padding: 0 var(--s1); }
.chatopts .osub { padding: 0 0 var(--s2); }
.chatopts .osub .pickrow { gap: var(--s2); flex-wrap: nowrap; }
.chatopts .osub .pickrow select, .chatopts .osub .pickrow input.model { min-width: 0; flex: 1 1 0; }
.chatopts .osub .pickrow select:first-child { flex: 0 1 9em; }
.chatopts .osub .fine { margin: var(--s1) 0 0; }
.chatopts .obtns { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s2); }
.chatopts .obtns .ghost.danger:hover:not(:disabled) { color: var(--alarm); border-color: var(--alarm); }
/* Segmented tabs (Server keys: This chat / My servers; add: saved / new). */
.chatopts .seg { display: flex; align-items: stretch; gap: 0; border-bottom: 1px solid var(--rule); margin-bottom: var(--s2); }
.chatopts .seg button {
  font: inherit; font-size: var(--t-sm); background: none; border: 0; border-bottom: 2px solid transparent;
  color: var(--dim); padding: var(--s1) var(--s3); margin-bottom: -1px; cursor: pointer;
}
.chatopts .seg button:hover { color: var(--hot); }
.chatopts .seg button.on { color: var(--hot); border-bottom-color: var(--out); }
.chatopts .seg .segx { padding: var(--s1) var(--s2); }
.switch { display: inline-flex; align-items: center; gap: var(--s2); background: transparent;
  border: 1px solid var(--rule); color: var(--dim); padding: 2px var(--s2); font: inherit; cursor: pointer; }
.switch i { width: 22px; height: 12px; border: 1px solid currentColor; position: relative; flex: none; }
.switch i::after { content: ""; position: absolute; top: 1px; left: 1px; width: 8px; height: 8px;
  background: currentColor; transition: left .12s; }
.switch.on { color: var(--out); border-color: var(--out); }
.switch.on i::after { left: 11px; }
.switch:disabled { opacity: .45; cursor: default; }
.compose .clip.opts.lit { color: var(--out); }

/* ===== Classic dark (2026-10-01) ============================================
   Classic's shapes and type on a dark, neutral surface. Every
   html[data-skin="classic"] rule above was widened to [data-skin^="classic"]
   so both tones share one rulebook; this block only re-inks the forty-odd
   values Classic hard-codes for paper. Slate, not phosphor: the brand green
   stays an accent, text is off-white rather than white to keep the glare
   down, and buttons carry ink on colour because white on this green is 2:1.
   Measured on the page colour: body 12.9:1, labels 7.1:1, ticks 4.0:1. */
html[data-skin="classic-dark"] {
  color-scheme: dark;
  --void:  #16191F;   /* the page */
  --sunk:  #1E222A;   /* inputs, cards, wells */
  --rule:  #2A2F39;   /* hairlines */
  --dusk:  #6F7889;   /* recessive: counts, gutters, ticks */
  --dim:   #9CA5B4;   /* labels, timestamps */
  --fore:  #D9DDE3;   /* body text */
  --hot:   #3DC97A;   /* the brand green, accent */

  --amber: #F2B544;   /* RUNNING */
  --alarm: #F4746A;   /* FAILED */
  --out:   #7FB4FF;   /* LEFT THIS SERVER */

  --c-key: #D08BE0; --c-fn: #6FC9DE; --c-str: #DCC77F; --c-num: #E8846A;
}
/* top bar */
html[data-skin="classic-dark"] .top { background: var(--void); }
html[data-skin="classic-dark"] .top .icon:hover { background: #262B35; }
html[data-skin="classic-dark"] .gauge .bar { background: #2A2F39; }
html[data-skin="classic-dark"] .gauge .floor { background: #3A4150; }
/* sidebar */
html[data-skin="classic-dark"] .side { background: #12151A; border-right-color: #242933; }
html[data-skin="classic-dark"] .find { background: var(--sunk); }
html[data-skin="classic-dark"] .find:focus { border-color: #4A5262; box-shadow: 0 0 0 3px rgba(74,82,98,.28); }
html[data-skin="classic-dark"] .sidetop .acts button { background: var(--sunk); border-color: var(--rule); }
html[data-skin="classic-dark"] .sidetop .acts button:hover { background: #262B35; border-color: #3A4150; }
html[data-skin="classic-dark"] .row, html[data-skin="classic-dark"] .row.sub { color: #C3C9D3; }
html[data-skin="classic-dark"] .row:hover { --rowbg: #1F242D; }
html[data-skin="classic-dark"] .row.on,
html[data-skin="classic-dark"] .row.on:hover { --rowbg: #2A303B; background: var(--rowbg); }
html[data-skin="classic-dark"] .rrow:hover { background: #262B35; }
html[data-skin="classic-dark"] .prow.on { background: #2A303B; }
html[data-skin="classic-dark"] .row.live .dot { box-shadow: 0 0 0 3px rgba(242,181,68,.2); }
html[data-skin="classic-dark"] .sidefoot { border-top-color: #242933; }
html[data-skin="classic-dark"] .sidefoot button { background: var(--sunk); border-color: var(--rule); }
html[data-skin="classic-dark"] .sidefoot button:hover { background: #262B35; border-color: #3A4150; }
html[data-skin="classic-dark"] .sidefoot .out:hover { background: #3A1E1E; }
/* status line */
html[data-skin="classic-dark"] .hint .ctoggle { background: var(--sunk); }
html[data-skin="classic-dark"] .hint .ctoggle:hover { border-color: #3A4150; }
html[data-skin="classic-dark"] .hint .ctoggle.on { color: #0B1220; }
html[data-skin="classic-dark"] .hint .tight { color: #1A0C0B; }
/* transcript */
html[data-skin="classic-dark"] .prompt .body { background: #262B35; }
html[data-skin="classic-dark"] .prose code { border-color: var(--rule); background: var(--sunk); }
html[data-skin="classic-dark"] .prose blockquote { border-left-color: #3A4150; }
html[data-skin="classic-dark"] .tool { background: #1B1F27; }
html[data-skin="classic-dark"] .tool > .head:hover { background: #22262F; }
html[data-skin="classic-dark"] .tool .body { background: #13161C; }
html[data-skin="classic-dark"] .dl.add { background: rgba(61,201,122,.14); }
html[data-skin="classic-dark"] .dl.del { background: rgba(244,116,106,.14); }
/* composer */
html[data-skin="classic-dark"] .compose .box {
  background: var(--sunk); border-color: #2F3541;
  box-shadow: 0 1px 2px rgba(0,0,0,.2), 0 6px 20px rgba(0,0,0,.3);
}
html[data-skin="classic-dark"] .compose .box:focus-within {
  border-color: #4A5262;
  box-shadow: 0 1px 2px rgba(0,0,0,.25), 0 8px 24px rgba(0,0,0,.4);
}
html[data-skin="classic-dark"] .compose.consulting .box { border-color: color-mix(in srgb, var(--out) 45%, #2F3541); }
html[data-skin="classic-dark"] .clip:hover { background: #262B35; }
html[data-skin="classic-dark"] .clip:disabled { color: #3A4150; }
/* buttons: ink on colour */
html[data-skin="classic-dark"] .go { color: #0E1A12; }
html[data-skin="classic-dark"] .go:hover { background: #5BD892; border-color: #5BD892; }
html[data-skin="classic-dark"] .go.stop, html[data-skin="classic-dark"] .go.danger { color: #1A0C0B; }
html[data-skin="classic-dark"] .go.stop:hover, html[data-skin="classic-dark"] .go.danger:hover { background: #F7948B; border-color: #F7948B; }
/* overlays */
html[data-skin="classic-dark"] .toast { background: #22262F; box-shadow: 0 4px 16px rgba(0,0,0,.45); }
html[data-skin="classic-dark"] .toast.bad { background: #3A1E1E; color: #F6A9A3; border-color: #6B2B2B; }
html[data-skin="classic-dark"] .scrim { background-color: rgba(0,0,0,.6); }
html[data-skin="classic-dark"] .palette, html[data-skin="classic-dark"] .sheet, html[data-skin="classic-dark"] .blank,
html[data-skin="classic-dark"] .gate form, html[data-skin="classic-dark"] .menu, html[data-skin="classic-dark"] .fpanel {
  box-shadow: 0 12px 32px rgba(0,0,0,.55);
}
@media (max-width: 860px) {
  html[data-skin="classic-dark"] .side { box-shadow: 8px 0 30px rgba(0,0,0,.6); }
}

/* Server keys (keybox, 2026-10-02; compact page 2026-10-03) -- a page of
   Chat options. One line per server; Test and a ⋯ menu carry the actions. */
.chatopts .kblist { list-style: none; margin: 0 0 var(--s2); padding: 0; }
.chatopts .kbrow { border-bottom: 1px solid var(--rule); padding: var(--s1) 0; }
.chatopts .kbline { display: flex; align-items: center; gap: var(--s2); min-width: 0; }
.chatopts .kbline b, .chatopts .kbpickrow b { font-weight: 400; color: var(--hot); font-family: var(--mono); flex: none; }
.chatopts .kbt {
  color: var(--dim); font-family: var(--mono); font-size: var(--t-sm); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chatopts .kbline .sp { flex: 1; }
.chatopts .kbdot { flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--alarm); }
.chatopts .kbdot.ok { background: var(--out); }
.chatopts .kbtag { flex: none; font-size: var(--t-xs); color: var(--out); border: 1px solid var(--rule); padding: 0 var(--s1); }
.chatopts .kbnote { margin: 0 0 0 15px; }
.chatopts .kbbusy { margin: 0; }
.chatopts .kbfix { margin: var(--s1) 0 var(--s1) 15px; }
.chatopts .kbmsg { margin: 0 0 var(--s2); padding: var(--s1) var(--s2); border-left: 3px solid var(--out); font-size: var(--t-sm); }
.chatopts .kbmsg.bad { border-left-color: var(--alarm); color: var(--alarm); }
.chatopts .kbpubrow { display: flex; align-items: flex-start; gap: var(--s2); margin: 0 0 var(--s2); }
.chatopts .kbpub {
  flex: 1; min-width: 0; margin: 0; padding: var(--s2); background: var(--sunk); border: 1px solid var(--rule);
  font-family: var(--mono); font-size: var(--t-xs); white-space: pre-wrap; word-break: break-all;
  user-select: all; color: var(--hot); max-height: 6em; overflow: auto;
}
.chatopts .kbpubrow .stamp { flex: none; border: 1px solid var(--rule); padding: 2px var(--s2); color: var(--dim); }
.chatopts .kbaddbox { border: 1px solid var(--rule); padding: 0 var(--s3) var(--s3); margin-top: var(--s2); }
.chatopts .kbaddbox .seg { margin: 0 calc(-1 * var(--s3)) var(--s2); padding: 0 var(--s1); }
.chatopts .kbadd { display: grid; gap: var(--s2); }
.chatopts .kbadd .keyrow label { width: 70px; }
.chatopts .kbadd input.kbport { flex: none; width: 4.5em; }
.chatopts .kbadd input.kbuser { flex: 0 1 6em; min-width: 3em; }
.chatopts .kbadd .kbat { flex: none; color: var(--dim); }
.chatopts .kbaddfoot { display: flex; align-items: center; gap: var(--s2); }
.chatopts .kbaddfoot .go { padding: 4px var(--s4); }
.chatopts .kbadd .fine { margin: 0; }
.chatopts .kbsearch { width: 100%; margin-bottom: var(--s2); }
.chatopts .kbmine { list-style: none; margin: 0; padding: 0; max-height: 14em; overflow-y: auto; border: 1px solid var(--rule); }
.chatopts .kbmine li + li { border-top: 1px solid var(--rule); }
.chatopts .kbmine li.fine { padding: var(--s2) var(--s3); margin: 0; }
.chatopts .kbpickrow {
  display: flex; align-items: center; gap: var(--s2); width: 100%; min-width: 0;
  padding: var(--s1) var(--s3); background: none; border: 0; text-align: left; color: inherit; cursor: pointer; font: inherit;
}
.chatopts .kbpickrow:hover:not(:disabled) { background: var(--sunk); }
.chatopts .kbpickrow:disabled { opacity: .5; cursor: default; }
.chatopts .kbuse { flex: none; font-size: var(--t-xs); color: var(--dusk); margin-left: auto; }
.chatopts .kbplus { flex: none; font-size: var(--t-sm); color: var(--out); }
.chatopts .kbsave { display: flex; align-items: center; gap: var(--s2); font-size: var(--t-sm); color: var(--dim); cursor: pointer; }
.chatopts .kbsave input { margin: 0; }
@media (max-width: 860px) {
  .chatopts .kbt { font-size: var(--t-xs); }
  .chatopts .kbuse { display: none; }
  .chatopts .orow b { min-width: 0; }
}

/* ===== GUI polish 2026-10-03, pass 4 (desktop + mobile) ===================== */
/* 1. Tablets had a header with nothing in the middle: the title rule for
   phones (nothing can absorb the row) was applied from 860px down, so an
   820px tablet in portrait hid the conversation title with 400px to spare.
   The title returns between the phone cut and the drawer cut; the mark's
   auto margin steps aside so the title can take the row. */
@media (max-width: 860px) and (min-width: 561px) {
  .top .now { display: block; }
  .top .mark { margin-right: 0; }
}
/* 2. The keyboard-hint list ellipsized mid-item at laptop widths ("+ at…").
   It now takes the free space itself (the spacer is redundant while it is
   visible) and fades at its right edge, so what is cut is cut by a gradient,
   never by three dots after half a word. The fade zone is empty whenever the
   list fits, because the box is wider than its text. Scoped to the composer's
   status line via .room so the vault gate's form hint is untouched. */
@media (min-width: 861px) {
  .hint:has(.room) > :first-child {
    flex: 1 100 auto; text-overflow: clip;
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 40px), transparent);
            mask-image: linear-gradient(to right, #000 calc(100% - 40px), transparent);
  }
  .hint:has(.room) .sp { display: none; }
}
/* 3. Classic skins: the modal backdrop kept the terminal's stipple dot grid
   under its tint, which reads as static over a web app. A flat tint only.
   The drawer scrim on phones takes the same cool tint as the modal one. */
html[data-skin^="classic"] .scrim { background-image: none; }
@media (max-width: 860px) {
  html[data-skin^="classic"] .side.open ~ .sidescrim { background: rgba(16,24,40,.4); }
}
/* 4. Classic palette: the selected row keeps dark text on a pale green, but
   its shortcut badge still inherited the terminal's reverse-video colour
   (page background on highlight) and vanished -- "Ctrl+N" was unreadable on
   the one row the keyboard is about to act on. */
html[data-skin^="classic"] .prow.on kbd { color: var(--dim); }
/* 5. Classic empty state: the headline was the console's -- caps, tracked,
   with a phosphor glow. Sentence case, no glow, one step down the scale; the
   brand green stays as the accent. */
html[data-skin^="classic"] .blank h2 {
  text-transform: none; letter-spacing: -.01em; text-shadow: none; font-size: 22px;
}
