/* oba.css - the look of every Oba window.
 *
 * ONE PALETTE, EIGHT COLOURS, AND NO NINTH. The block below is the founder's
 * design system of 2026-09-05, written out whole: slate is the ground, chalk
 * and steel are the only two inks, surface-1 and surface-2 are the only two
 * raises, border is every hairline, and ochre is the accent. Nothing here may
 * add a colour to that list, in a declaration or in a comment, because a proof
 * reads this file's RAW text and counts the hexes it finds.
 *
 * The page is dark, and dark from its first painted frame, because the palette
 * is written on bare :root rather than inside a block a host has to switch on:
 * every one of these applications ships dark and a white ground inside a docked
 * palette is jarring. The host still stamps data-theme on <html> and the
 * "theme" message still lands; there is no light block for either of them to
 * reach, and there will not be one until the system names the colours a light
 * ground is built from. --oba-ochre-deep is declared and unused for exactly
 * that reason: it is the token that answer arrives through.
 *
 * OCHRE MEANS ONE THING: the agent did this, or it needs you. It is spent in
 * exactly two selectors, the option a pending question is standing on and the
 * Stop control while a turn runs, plus the mark's own pulse. Never a hover,
 * never a focus ring, never a link, and never Send, which is a person acting
 * and not the agent. Elevation is a surface and a hairline and nothing else.
 *
 * Every size here has to hold from a 260 px palette (AutoCAD's minimum is 300,
 * and people drag it narrower than they should) out to about 600 px. That rules
 * out fixed widths anywhere and is why the prompt chips wrap their own text
 * instead of staying on one line. One spacing scale, 4, 8, 12 and 16 px with
 * 1 px hairlines; one radius set, var(--oba-r) for boxes, 999px for pills and
 * 50% for circles, or none, where the cost row turns the bubble's box back
 * off; three type sizes, 13, 12.5 and 11.5 px; one weight above 400, on the
 * wordmark and the loud zone.
 *
 * No @font-face, and the one url() in this sheet points at a file beside it
 * (the status mark's mask): nothing here is ever fetched from a network, which
 * is the rule - see the note in index.html. The wordmark is asked for in a
 * geometric grotesque and set in the system sans, because fetching a face is
 * the one thing this page may never do.
 *
 * ONE GRADIENT, and it is the founder's own (2026-09-12 evening): the chalk
 * band that sweeps across the status line while Oba works. The system says no
 * gradient; his word for this line says a gradient clipped to the letters, in
 * the two inks, and his word is the later one. It is the only one in the sheet
 * and the repository's colour census pins it declaration for declaration, so a
 * second one anywhere is still a gate red.
 */

:root {
  --oba-slate:      #1E1E1E;   /* the page's ground */
  --oba-chalk:      #EBEBEB;   /* primary text, hairline rings, hovers */
  --oba-ochre:      #E8A400;   /* THE accent, two selectors only */
  --oba-ochre-deep: #A67200;   /* light grounds; unused while the page is dark */
  --oba-steel:      #9A9A9A;   /* secondary text, captions, disabled, machinery */
  --oba-surface-1:  #252526;   /* raised: the bar, bubbles, options, chips */
  --oba-surface-2:  #2D2D2D;   /* higher: the composer, the user's bubble, flags */
  --oba-border:     #3C3C3C;   /* hairlines, scrollbar thumbs */
  /* THE NINTH, AND IT IS A DEFAULT AND NOT HIS WORD. The founder asked for
     the compacting word in "a light blue" (2026-09-13) and did not name one;
     the design system says to stop and ask before a colour that is not in the
     eight, so this is taken as a default, said out loud here and in
     docs/look-pass-2026-09-05/design-system.md, and it is HIS TO CONFIRM.
     9.07:1 on slate, against steel's own 4.50:1, so the fold reads brighter
     than "thinking…" - which is what a second state should do. It paints ONE
     thing, the status word while the word is "compacting", and a census
     counts its bearers. */
  --oba-compact:    #D6EAF8;   /* the compacting word: a very light blue, his word 2026-09-13 */
  --oba-sans: "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --oba-mono: "Cascadia Mono", Consolas, "SF Mono", Menlo, monospace;
  --oba-r: 4px;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }

html, body {
  height: 100%;
  margin: 0;
}

body {
  display: flex;
  flex-direction: column;
  overflow: hidden;             /* the transcript scrolls; the page never does */
  background: var(--oba-slate);
  color: var(--oba-chalk);
  font-family: var(--oba-sans);
  font-size: 12.5px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* -- every scroller the page owns --------------------------------------- */

/* One rule for all five, because a scroller nobody painted is drawn by the
   browser in a colour that is none of the eight, and the live block at the end
   of the transcript was exactly that until this pass. The five are the
   transcript itself, that block, the bubble the answer grows in while Oba
   writes it, the strip of chats where a host keeps several, and the composer's
   own box. The thumb is a hairline, the track is
   nothing at all, and the hover is the one step up to steel. #entry is here
   although its overflow-y is hidden in this sheet: oba.js turns it to auto the
   moment a request runs past five lines. */
.log,
.live,
.answering .bubble,
.chat-rows,
#entry {
  scrollbar-width: thin;
  scrollbar-color: var(--oba-border) transparent;
}

.log::-webkit-scrollbar,
.live::-webkit-scrollbar,
.answering .bubble::-webkit-scrollbar,
.chat-rows::-webkit-scrollbar,
#entry::-webkit-scrollbar { width: 8px; }

.log::-webkit-scrollbar-track,
.live::-webkit-scrollbar-track,
.answering .bubble::-webkit-scrollbar-track,
.chat-rows::-webkit-scrollbar-track,
#entry::-webkit-scrollbar-track { background: transparent; }

.log::-webkit-scrollbar-thumb,
.live::-webkit-scrollbar-thumb,
.answering .bubble::-webkit-scrollbar-thumb,
.chat-rows::-webkit-scrollbar-thumb,
#entry::-webkit-scrollbar-thumb {
  background: var(--oba-border);
  border-radius: var(--oba-r);
}

.log::-webkit-scrollbar-thumb:hover,
.live::-webkit-scrollbar-thumb:hover,
.answering .bubble::-webkit-scrollbar-thumb:hover,
.chat-rows::-webkit-scrollbar-thumb:hover,
#entry::-webkit-scrollbar-thumb:hover { background: var(--oba-steel); }

/* -- header ------------------------------------------------------------- */

.bar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
  padding: 8px 12px;
  background: var(--oba-surface-1);
  border-bottom: 1px solid var(--oba-border);
}

/* THE MARK'S MOUNT POINT. The <img> inside it is the whole mark until the
   animated file lands; when it lands it finds this element by its id, draws
   itself into it and takes the <img> out ITSELF, on success. So the flex-child
   rule lives on the mount and not on the image: whichever of the two is
   standing here, the header lays it out the same way. */
.bar-mark {
  display: block;
  flex: 0 0 auto;
  line-height: 0;
}

.bar-icon { display: block; }

.bar-name {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .2px;
}

.bar-app {
  margin-left: auto;
  padding: 0 8px;
  border: 1px solid var(--oba-border);
  border-radius: 999px;
  background: var(--oba-surface-2);
  color: var(--oba-chalk);
  font-size: 11.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 55%;
}

/* -- the chats open in this drawing -------------------------------------- */

/* ONE STRIP UNDER THE BAR, drawn only where the host declared `chats`. It is
   the bar's own surface over the bar's own hairline, so the top of the window
   reads as one band with two jobs rather than as two bars; the rows are a
   vertical list, which is the shape the founder asked for ("a vertical list at
   the top of the window"), and the plus stands at the top of it where he put
   it and stays there however many rows there are.

   THE LIT ROW IS THE SLASH LIST'S LIGHT, WORD FOR WORD: a surface step, a
   hairline and chalk ink. NEVER OCHRE. The accent means the agent did this or
   needs you, and a person choosing which of their own chats to look at is
   neither; the repository's source census counts the bearers
   and there are still two.

   NO FACE IS NAMED HERE. The rows carry the page's one sans through `font:
   inherit`, exactly as the composer does: the monospace face belongs to the
   five aligned numerics and a sixth rule naming a face is a gate red.

   AND THE STRIP SCROLLS INSIDE ITSELF rather than pushing the transcript off
   the bottom of a docked palette. Its thumb is the hairline every other
   scroller on this page wears, which is why .chat-rows joins the four groups
   above rather than being left to the browser to paint. */
/* THE TAB STRIP IS THE CHROME the tabs sit in, like a browser's (the founder,
   2026-09-13/14): the bar's own surface, and the tabs meet its bottom edge so
   the one in front can open down into the transcript below it. align-items is
   flex-end and there is no bottom padding, so the tabs reach that edge; the
   plus keeps a little room above it. */
.chats {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  flex: 0 0 auto;
  padding: 6px 12px 0;
  background: var(--oba-surface-1);
  border-bottom: 1px solid var(--oba-border);
}

/* THE TABS ARE HORIZONTAL, like a browser's (the founder, 2026-09-13): side by
   side along one row, the front one lit, up to ten of them, and the row scrolls
   sideways rather than growing tall when they do not all fit. */
.chat-rows {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: row;
  align-items: flex-end;
  gap: 2px;
  overflow-x: auto;
  overflow-y: hidden;
}

/* ONE TAB, a switch and a close side by side in one box, like a browser tab:
   its own first words cut with an ellipsis, and an X on the right that ends the
   chat. The box is the tab; the two buttons inside it do the two things, so
   neither is a button inside a button. */
/* A TAB, not a pill: rounded at the top only, square at the bottom, and its
   bottom edge overlaps the strip's own hairline (margin-bottom -1px) so the one
   in front joins the transcript below with no line between them, the way a
   browser tab joins its page. An inactive tab shows the chrome through it; the
   front tab is filled with the transcript's own ground, so it reads as opened
   into the content and not floating on top of it. */
.chat-row {
  flex: 0 1 auto;
  display: flex;
  align-items: center;
  min-width: 0;
  max-width: 160px;
  border: 1px solid transparent;
  border-bottom: none;
  border-radius: var(--oba-r) var(--oba-r) 0 0;
  background: none;
  margin-bottom: -1px;
}

.chat-row[aria-selected="true"] {
  background: var(--oba-slate);
  border-color: var(--oba-border);
}

/* The switch: the tab's own words, steel until it is the one in front. */
.chat-switch {
  flex: 1 1 auto;
  min-width: 0;
  padding: 4px 4px 4px 8px;
  border: 0;
  background: none;
  color: var(--oba-steel);
  font: inherit;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}

.chat-row[aria-selected="true"] .chat-switch { color: var(--oba-chalk); }

/* The close X: steel, the page's one hover idiom, and never ochre; ending a
   chat is a person acting, not the agent. */
.chat-close {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 16px;
  height: 16px;
  margin-right: 4px;
  padding: 0;
  border: 0;
  border-radius: var(--oba-r);
  background: none;
  color: var(--oba-steel);
  cursor: pointer;
}

.chat-close:hover { color: var(--oba-chalk); }

/* THE CONFIRM, when a chat is closed: a small box over the window, the way a
   browser asks. "Never ask again" saves the answer so it is not asked twice
   (kept in the page's own store). */
/* NO SCRIM. The design system forbids shadows and glows, and a dark wash over
   the window is the same idea; the confirm is a bordered box floating on the
   surface it is raised from, and the overlay is transparent, there only to
   catch a click outside the box as a cancel. */
.chat-close-ask {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: grid;
  place-items: center;
  background: transparent;
}

.chat-close-box {
  width: min(280px, 82vw);
  padding: 16px;
  border: 1px solid var(--oba-border);
  border-radius: var(--oba-r);
  background: var(--oba-surface-2);
  color: var(--oba-chalk);
}

.chat-close-q { margin: 0 0 12px; }

.chat-close-never {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 16px;
  color: var(--oba-steel);
  font-size: 11.5px;
  cursor: pointer;
}

.chat-close-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

.chat-close-actions button {
  padding: 4px 12px;
  border: 1px solid var(--oba-border);
  border-radius: var(--oba-r);
  background: none;
  color: var(--oba-chalk);
  font: inherit;
  cursor: pointer;
}

.chat-close-actions button:hover { border-color: var(--oba-chalk); }

/* The same 20 px box the plus has had since it was drawn, on the strip's own
   surface with no fill of its own: a raised button inside a raised band is a
   step nobody can see, so what says it is a control is the hairline and the
   glyph. */
.chat-new {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 1px solid var(--oba-border);
  border-radius: var(--oba-r);
  background: none;
  color: var(--oba-steel);
  cursor: pointer;
  transition: border-color .12s ease;
}

/* The page's one hover idiom, on the hairline, and nothing else moving. */
.chat-new:hover { border-color: var(--oba-chalk); }

/* GREYED WHILE IT CANNOT BE PRESSED, the way Send is and for Send's own
   reason: a control that answers nothing has to look like one. Two moments
   only, since the founder's word of 2026-09-14 left the strip live through a
   turn: before the host has spoken once, when nothing on the strip may be
   pressed, and the running tab's own X, whose reply is on its way to it. The
   same measurable step down, .55, and the pointer says so too.

   AND IT IS THE TWO BUTTONS THAT GREY, NEVER THE ROW. The row is a div -
   oba.js builds it with document.createElement('div') and role="tab", and the
   switch and the close are the buttons inside it - so :disabled and
   :focus-visible on .chat-row match nothing, ever. They stood there until
   2026-09-27, which left the running tab's X at full opacity, with its pointer
   cursor and its chalk hover, for the whole of a turn in which a press on it
   did nothing at all. */
.chat-switch:disabled,
.chat-close:disabled,
.chat-new:disabled { opacity: .55; cursor: default; }
.chat-close:disabled:hover { color: var(--oba-steel); }
.chat-new:disabled:hover { border-color: var(--oba-border); }

.chat-switch:focus-visible,
.chat-close:focus-visible,
.chat-new:focus-visible { outline: 1px solid var(--oba-chalk); outline-offset: 2px; }

/* -- transcript --------------------------------------------------------- */

.log {
  flex: 1 1 auto;
  min-height: 0;                /* without this the flex child refuses to scroll */
  overflow-y: auto;
  overflow-x: hidden;
  padding: 12px 12px 4px;
}

.row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  margin-bottom: 12px;
}

.row.user { align-items: flex-end; }

.bubble {
  max-width: 88%;
  padding: 8px 12px;
  border-radius: var(--oba-r);
  background: var(--oba-surface-1);
  /* The reply is shown exactly as Oba wrote it: the line breaks in a report of
     what was drawn carry the shape of the answer, and wrapping them away turns
     a checked list of entities into a paragraph nobody reads. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* The person's own line. It is told from a reply by one surface step, one
   hairline and the side of the window it sits on, and by nothing else: ochre
   is the agent's colour and a person typing is not the agent. */
.row.user .bubble {
  background: var(--oba-surface-2);
  border: 1px solid var(--oba-border);
  color: var(--oba-chalk);
}

/* The intro is an Oba bubble, but the full width of the window rather than the
   88% a reply gets: it is the window introducing itself, not a turn in the
   conversation, and it is gone the moment there is one. */
.row.intro .bubble {
  max-width: 100%;
  padding: 8px 12px;
}

.at {
  margin-top: 4px;
  padding: 0 4px;
  color: var(--oba-steel);
  font-family: var(--oba-mono);
  font-size: 11.5px;
}

/* -- a question with options -------------------------------------------- */

/* When Oba asks before it guesses, its numbered list is drawn as boxes instead
   of text. They live in the same column an Oba bubble does - 88 %, hard against
   the left - because they are part of what Oba said, not a control panel that
   arrived from somewhere else. Everything below has to hold at 260 px, where
   an option's text is down to a few characters a line; nothing is given a fixed
   width, and the text wraps rather than pushing the box out of the window. */
.options {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 8px;
  /* Both, and both are needed. The row lays its children out from the left and
     lets each one be as wide as its content, which would leave the boxes as
     ragged as the words inside them; width: 100 % squares them off against each
     other and max-width holds them in the same column as the bubble above. */
  width: 100%;
  max-width: 88%;
}

/* WHERE THE KEYBOARD IS, WHICH IS NOT THE SAME THING AS A BOX BEING LIT. The
   page hands the list the keyboard by itself the moment a question arrives, and
   :focus-visible is what keeps that hand-off from drawing anything for somebody
   who is using the mouse. For somebody who typed their way here it does draw,
   and it should: a keyboard with nowhere visible is worse than a quiet line.

   It is the page's ONE focus idiom, a 1 px chalk ring at 2 px offset, worn
   here, on the fold's toggle, on a chip, on Send and on Stop alike: every
   control the page itself puts in the Tab order wears it except the
   composer's own box, and a control that wore none would fall back on the
   browser's own doubled ring in a colour that is none of the eight. The
   composer is the exemption and not a gap: it shows the keyboard on the box
   round the textarea through .composer:focus-within, and #entry keeps
   outline: none for exactly that reason, since a ring of its own inside a box
   already lit would be two rings for one caret. It is not the light: the light
   is ochre and one box wears it, and a ring round the whole list in the
   light's own colour would read as though the whole list were lit, which is
   the one thing this list must never say. The box a question was answered ON
   drops to a steel border for the same reason, so the settled mark and this
   ring cannot be confused either. */
.options:focus-visible {
  outline: 1px solid var(--oba-chalk);
  outline-offset: 2px;
}

/* The number, the text and the tag, in three columns. The middle one takes
   what is left and is allowed to shrink to nothing (min-width: 0), which is
   what keeps a long option from widening the row instead of wrapping. */
.option {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: start;
  gap: 8px;
  padding: 8px 12px;
  border: 1px solid var(--oba-border);
  border-radius: var(--oba-r);
  background: var(--oba-surface-1);
  transition: border-color .12s ease, background-color .12s ease;
}

/* The key that takes this option, drawn as the key it is. The monospace face
   is what makes the badges one width whichever digit is in them, and it is the
   same declaration the four other aligned numerics on this page carry. */
.option .num {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--oba-surface-2);
  color: var(--oba-chalk);
  font-family: var(--oba-mono);
  font-size: 11.5px;
}

.option .text {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* "recommended" - Oba's own mark, kept quiet. It never wraps: it is the one
   thing here narrow enough to have a width of its own, and a two-line pill
   next to a one-line option reads as a second option. */
.option .tag {
  padding: 0 8px;
  border: 1px solid var(--oba-border);
  border-radius: 999px;
  color: var(--oba-steel);
  font-size: 11.5px;
  line-height: 18px;
  white-space: nowrap;
}

/* The one the person is on, and THE OCHRE OF THE WHOLE TRANSCRIPT. A question
   standing unanswered is the pending confirmation the design system reserves
   the accent for, and its primary action is this border. The mouse and the
   keyboard put this same class on the same box - oba.js moves it on a
   mouseover and on an arrow key alike - so there is never a second box
   outlined somewhere else, and never a question about which number Enter would
   take.

   A question arrives with none of them wearing it. Until the pointer is on a
   box, or an arrow key has walked onto one, every option looks exactly like
   every other: a box lit before anybody touched it is the window choosing, and
   Enter is one key away from sending it. That is also why the accent may not
   be spent twice here: the border is the light, and the badge inside it steps
   one surface DOWN with a hairline of its own, which is the most the eight
   colours allow a badge that has to stay legible on the lit ground. */
.option.active {
  border-color: var(--oba-ochre);
  background: var(--oba-surface-2);
}

.option.active .num {
  background: var(--oba-surface-1);
  color: var(--oba-chalk);
  border: 1px solid var(--oba-border);
}

/* Hover paints nothing of its own. The light is .active and the mouseover is
   what puts it there, which is what keeps the two ways of pointing at a box
   down to one mark; all that is left here is the shape of the cursor, saying
   the box can be pressed. */
.options:not(.done) .option:hover {
  cursor: pointer;
}

/* Settled: answered, or waiting on the host to echo the pick back. Dimmed and
   not pressable, because the moment for it has gone - except the one that was
   taken, which stays at full strength with a steel border, so scrolling back
   through a long conversation shows what was decided at each turn. Steel and
   not ochre: the question is answered, so nothing here needs anybody any more,
   and steel is also what keeps the taken box from reading as this page's chalk
   focus ring. */
.options.done .option {
  opacity: .6;
  cursor: default;
}

.options.done .option.chosen {
  opacity: 1;
  border-color: var(--oba-steel);
}

.options.done .option.chosen .num {
  background: var(--oba-surface-1);
  color: var(--oba-chalk);
  border: 1px solid var(--oba-border);
}

/* A palette dragged in past about 340 px has no line left to share. The tag is
   the thing that gives way - it is Oba's aside, and the option's own words are
   what the person is choosing between - so it drops under the text and the
   text gets the whole width back. Measured: at 260 px this is the difference
   between an option reading over three lines and reading over two. */
@media (max-width: 340px) {
  .option { grid-template-columns: auto 1fr; }
  .option .tag {
    grid-column: 2;
    justify-self: start;
    margin-top: 4px;
  }
}

/* The line under the options that Oba did not write: "this turn: $0.03", and
   any warning the application put beneath it. Shown, always - a window that
   swallowed the cost line would be hiding what a turn cost - but a bare steel
   line and not a box: it carries .bubble because the host writes it into the
   reply's own row, and everything that makes a bubble a bubble is turned back
   off here. Monospace, because a cost is an aligned numeric. */
.bubble.trailer {
  margin-top: 8px;
  background: none;
  padding: 0;
  border-radius: 0;
  color: var(--oba-steel);
  font-family: var(--oba-mono);
  font-size: 11.5px;
}

/* -- the zones of a labelled reply -------------------------------------- */

/* What went wrong stands loud ABOVE the answer: full size, never folded, never
   truncated. Loud by weight, position and a chalk hairline, and not by a hue -
   the eight colours have no red and no yellow, and ochre would be wrong here on
   its own terms, because it means "the agent did this, or it needs you" and a
   failed step is neither a pending confirmation nor a live control. */
.flags {
  max-width: 88%;
  margin-bottom: 8px;
  padding: 8px 12px;
  border-left: 1px solid var(--oba-chalk);
  background: var(--oba-surface-2);
  border-radius: var(--oba-r);
  color: var(--oba-chalk);
  font-weight: 500;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* The machinery, folded behind one tap and never dropped. The toggle reads
   like the steel trailer; the lines keep their exact text. */
.detail { max-width: 88%; margin-top: 8px; }
.detail-toggle {
  border: 0;
  padding: 0;
  background: none;
  color: var(--oba-steel);
  font: inherit;
  font-size: 11.5px;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
/* The toggle is a real <button> the page creates and leaves in the Tab order,
   so it takes the same ring the other four controls take. Written out rather
   than left to the browser: with no rule of its own it draws the user agent's
   outline: auto, a doubled hairline in the browser's own focus colour, which
   is not one of the eight and is not the hairline this page draws anywhere
   else. Underline and ring say two different things - the underline says this
   is pressable, the ring says the keyboard is here. */
.detail-toggle:focus-visible {
  outline: 1px solid var(--oba-chalk);
  outline-offset: 2px;
}
.detail-lines {
  margin: 8px 0 0;
  padding: 8px 12px;
  color: var(--oba-steel);
  /* Said out loud rather than left to the browser. This is a <pre>, so the
     user agent would hand it a monospace face of its own while the live block
     these very lines are watched in is a <div> wearing the body's proportional
     stack, and the lines would change shape at the moment they fold. Five
     rules on this page carry this one declaration, character for character,
     and the repository's colour census reads all five. */
  font-family: var(--oba-mono);
  font-size: 11.5px;
  background: var(--oba-slate);
  border: 1px solid var(--oba-border);
  border-radius: var(--oba-r);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* The activity line wearing a failure: the same element, one tone, and the
   tone is weight and chalk rather than a colour this page does not have. */
.status[data-tone="flag"] { color: var(--oba-chalk); }

/* The one aria-live element; heard, never seen. */
.announce {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* -- prompt chips ------------------------------------------------------- */

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 4px 0 12px;
}

.chip {
  max-width: 100%;
  padding: 4px 12px;
  border: 1px solid var(--oba-border);
  border-radius: 999px;
  background: var(--oba-surface-1);
  color: var(--oba-chalk);
  font: inherit;
  font-size: 11.5px;
  line-height: 1.35;
  text-align: left;
  white-space: normal;
  cursor: pointer;
  transition: border-color .12s ease;
}

/* The page's one hover idiom: the hairline comes up to chalk and nothing else
   moves. A chip is a suggestion somebody may press, not the agent asking for
   anything, so it never wears the accent. */
.chip:hover { border-color: var(--oba-chalk); }

.chip:focus-visible {
  outline: 1px solid var(--oba-chalk);
  outline-offset: 2px;
}

/* -- the live block ----------------------------------------------------- */

/* What Oba is doing, while it does it, standing where the dots stand. Quiet
   on purpose and built out of tokens that already exist: the whole palette of
   .detail-lines, which is where these very lines end up folded a moment
   later, in the position .typing holds. Nothing of .flags, which stays the
   only loud zone in the window - machinery is not a warning, and it is not the
   agent needing you either, so it takes no accent.

   It sits on the page's own ground with a hairline round it rather than on a
   raise, because steel on slate is a readable pair and steel on a raise is
   not. The typeface is part of that palette and is the one thing neither
   element would agree on by itself: this is a <div> and the fold is a <pre>,
   so with nothing declared the same lines would go from the body's
   proportional stack to the browser's monospace as they fold. It is also what
   tells an act line from a detail line, since nothing separates them but the
   brain's own indentation, which pre-wrap keeps and a proportional space
   swallows.

   The box is capped and scrolls inside itself, so a turn that prints two
   hundred lines does not push the conversation off the screen, and at that
   height it stops moving the transcript's own height at all. */
.live {
  max-width: 88%;
  margin-bottom: 12px;
  padding: 8px 12px;
  color: var(--oba-steel);
  font-family: var(--oba-mono);
  font-size: 11.5px;
  background: var(--oba-slate);
  border: 1px solid var(--oba-border);
  border-radius: var(--oba-r);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 11em;
  overflow-y: auto;
}

/* -- the answer, while Oba writes it ------------------------------------ */

/* The words as Oba writes them, growing under the block. This is a preview
   and the reply is the record, so it is drawn as the reply's own bubble and
   by the very same rule: the inner node carries .bubble itself and takes
   every colour, corner, padding and wrap from it. Nothing here is a new
   colour token, nothing here is of .flags, and nothing here is ochre - a
   preview carries no caret and no accent of its own - so when the reply lands
   and this goes, nothing about how the answer looks changes but where it
   stands.

   The outer box is the hook, and it lays the bubble out the way .row does: a
   column starting at the left edge, so the bubble is as wide as its words and
   no wider, and the reply's bubble that replaces it is the same shape. It is
   deliberately not a .row, so the transcript's row count and every selector
   written over .row.oba are untouched by something that is not a message.

   What it ADDS to a bubble is the row's own margin, a cap and a scroller.
   The cap is 18em and not the block's 11em because this is prose somebody is
   reading, not machinery they are glancing at; past it the box scrolls
   inside itself rather than pushing the conversation off the screen, wearing
   the same hairline thumb every other scroller here does. */
.answering {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  margin-bottom: 12px;
}

.answering .bubble {
  max-height: 18em;
  overflow-y: auto;
}

/* -- typing indicator --------------------------------------------------- */

.typing {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-bottom: 12px;
  padding: 8px 12px;
  border-radius: var(--oba-r);
  background: var(--oba-surface-1);
}

.typing i {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--oba-steel);
  animation: oba-blink 1.15s infinite ease-in-out;
}

.typing i:nth-child(2) { animation-delay: .17s; }
.typing i:nth-child(3) { animation-delay: .34s; }

@keyframes oba-blink {
  0%, 70%, 100% { opacity: .28; }
  35%           { opacity: 1; }
}

/* -- composer ----------------------------------------------------------- */

.foot {
  position: relative;           /* the slash list stands on the composer's top edge */
  flex: 0 0 auto;
  padding: 8px 12px 12px;
}

/* -- the slash list ------------------------------------------------------ */

/* Up while the box holds one word that begins with a slash and is the start
   of one of the words (oba.js, slashSync), standing on the composer's top
   edge and over the transcript's last lines, so the composer itself never
   moves. One surface, one hairline, and the same two inks as everything else
   on the page. The lit row is a surface step and a hairline and never ochre:
   the light is where a PERSON is, and ochre is where the agent is. The words
   are set in the page's one sans like every other word a person reads: the
   monospace face is for the five aligned numerics and nothing else, and a
   proof counts the rules that name a face. A palette docked short would
   otherwise clip the top rows under the page's own overflow: hidden, so the
   list scrolls inside itself past half the window's height, with the same
   thin hairline thumb as every scroller the page owns. */
.slash {
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: calc(100% - 4px);
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 4px;
  max-height: 50vh;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--oba-border) transparent;
  border: 1px solid var(--oba-border);
  border-radius: var(--oba-r);
  background: var(--oba-surface-1);
  z-index: 2;
}

.slash-row {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: baseline;
  gap: 8px;
  padding: 4px 8px;
  border: 1px solid transparent;
  border-radius: var(--oba-r);
  cursor: pointer;
}

.slash-row .word {
  font-size: 12.5px;
  color: var(--oba-chalk);
  white-space: nowrap;
}

.slash-row .what {
  min-width: 0;
  color: var(--oba-steel);
  font-size: 11.5px;
  overflow-wrap: anywhere;
}

.slash-row.active {
  background: var(--oba-surface-2);
  border-color: var(--oba-border);
}

/* Separated from the transcript by its own hairline and by one surface step,
   and by nothing else: no raise that is not a surface, and nothing cast
   underneath it. */
.composer {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  padding: 4px 4px 4px 12px;
  border: 1px solid var(--oba-border);
  border-radius: var(--oba-r);
  background: var(--oba-surface-2);
  transition: border-color .12s ease;
}

.composer:focus-within { border-color: var(--oba-chalk); }

#entry {
  flex: 1 1 auto;
  min-width: 0;
  height: 24px;                 /* one line; oba.js resizes from here */
  padding: 4px 0;
  border: 0;
  background: transparent;
  color: var(--oba-chalk);
  font: inherit;
  line-height: 1.45;
  resize: none;
  outline: none;
  overflow-y: hidden;
}

#entry::placeholder { color: var(--oba-steel); }
#entry:disabled { color: var(--oba-steel); }

/* SEND AND STOP ARE A PAIR ON ONE GROUND, and since 2026-09-09 they are one
   box twice: the same 26 px circle, the same grid centring the glyph, the
   same absence of a fill. Both sit on the composer's own surface with no fill
   of their own, because a raised button inside a raised box is a step of
   1.08:1 and nobody can see it. What tells them apart is the hairline and the
   glyph and nothing else now: Send is chalk, the person's own control, and
   never the accent; Stop is the composer's ONE ochre and exists only while a
   turn runs, which is exactly the "active state of a control tied to agent
   activity" the accent is for. */
.send {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--oba-border);
  border-radius: 50%;
  background: none;
  color: var(--oba-chalk);
  cursor: pointer;
  transition: border-color .12s ease, opacity .12s ease;
}

.send:hover { border-color: var(--oba-chalk); }
/* The grey is a measurable step down and always has been: .55 rather than .4
   because .4 of steel on slate is under 2:1 and stops being readable at all,
   and the colour drops to steel in the same breath so the delta survives. */
.send:disabled { opacity: .55; color: var(--oba-steel); cursor: default; }
.send:disabled:hover { border-color: var(--oba-border); }
.send:focus-visible { outline: 1px solid var(--oba-chalk); outline-offset: 2px; }

/* -- the two attachment controls, and the files waiting to go ------------ */

/* SEND'S BOX TWICE MORE, in steel. Declaration for declaration these are
   `.send` above with one difference and no second: the INK. Send is the
   composer's own control and wears chalk; a picker and a crop are machinery
   the person reaches for before they write, and machinery is steel. NO OCHRE
   in either: Stop is the composer's one accent bearer, and the accent means
   the agent needs you.

   Neither is greyed by a turn. They are shut by exactly one thing, the host
   not having said a word yet, which is Send's own reason; from then on they
   answer while Oba works, because only Send waits. */
.pick, .crop {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--oba-border);
  border-radius: 50%;
  background: none;
  color: var(--oba-steel);
  cursor: pointer;
  transition: border-color .12s ease, opacity .12s ease;
}

.pick:hover, .crop:hover { border-color: var(--oba-chalk); }
.pick:disabled, .crop:disabled { opacity: .55; cursor: default; }
.pick:disabled:hover, .crop:disabled:hover { border-color: var(--oba-border); }
.pick:focus-visible, .crop:focus-visible {
  outline: 1px solid var(--oba-chalk);
  outline-offset: 2px;
}

/* THE STRIP OF WHAT IS WAITING TO GO, directly above the composer and on the
   transcript's own ground: it is not part of the composer box, because a box
   that grew a row of chips would push the caret down the window every time
   somebody attached something. It wraps, so four chips on a 260 px palette
   are four lines and never a sideways scroll. */
.attach {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 6px;
}

/* ONE CHIP, AND IT IS NOT PRESSABLE. The chip says what is going; the only
   control in it is the X, which takes the file off the message. It is capped
   at 220 px and the name ellipses inside it, so a file called
   `site-survey-final-final-2026.pdf` cannot push the composer open or the
   strip sideways. */
.attach-chip {
  display: flex;
  align-items: center;
  gap: 6px;
  max-width: 220px;
  padding: 3px 4px 3px 6px;
  border: 1px solid var(--oba-border);
  border-radius: var(--oba-r);
  background: var(--oba-surface-1);
  color: var(--oba-chalk);
  font-size: 11.5px;
}

/* The small picture of a crop, or of a picture that was picked. The host sends
   at most 96 px on its long edge and this draws it at 18: the chip is a label,
   not a viewer, and object-fit keeps a wide crop from stretching. The corner is
   the page's own box radius, because the page has three shapes and this is not
   a fourth one. */
.attach-thumb {
  flex: 0 0 auto;
  display: block;
  width: 18px;
  height: 18px;
  border-radius: var(--oba-r);
  object-fit: cover;
}

.attach-name {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.attach-size { flex: 0 0 auto; color: var(--oba-steel); }

/* The X, the one control in the chip: the strip's own idiom, steel on nothing,
   chalk under the pointer, exactly as a chat tab's X is. */
.attach-x {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 16px;
  height: 16px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--oba-steel);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}

.attach-x:hover { color: var(--oba-chalk); }
.attach-x:disabled { opacity: .55; cursor: default; }
.attach-x:focus-visible {
  outline: 1px solid var(--oba-chalk);
  outline-offset: 1px;
}

/* AND THE SAME CHIPS ON THE ROW THAT SPENT THEM, under the person's own
   words and above the clock. No X: the message has gone, and there is
   nothing left to take off it. */
.row-files {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  max-width: 88%;
  margin-top: 6px;
}

.row.user .row-files { justify-content: flex-end; }

/* -- the undo control, on a prompt that drew ----------------------------- */

/* SEND'S BOX, at 20 px and in steel. Declaration for declaration it is `.send`
   above with exactly two differences and no third: the SIZE, because this one
   sits inside the conversation and not in the composer, where 26 px would read
   as a second Send; and the INK, because the person's own control under their
   own line is machinery, and chalk is what the composer's own control wears.
   NO OCHRE, ever: Stop is the page's one accent bearer and the accent means
   the agent needs you.

   It is drawn only on a prompt that really drew something and only where the
   host declared it can take a prompt back, and it is greyed while a turn runs
   exactly as Send is. */
.undo {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 1px solid var(--oba-border);
  border-radius: 50%;
  background: none;
  color: var(--oba-steel);
  cursor: pointer;
  transition: border-color .12s ease, opacity .12s ease;
}

.undo:hover { border-color: var(--oba-chalk); }
.undo:disabled { opacity: .55; color: var(--oba-steel); cursor: default; }
.undo:disabled:hover { border-color: var(--oba-border); }
.undo:focus-visible { outline: 1px solid var(--oba-chalk); outline-offset: 2px; }

/* A PROMPT THAT HAS BEEN TAKEN BACK, and every row under it as far as the
   reply that took it: the words stay where they are, because the conversation
   happened and hiding it would be a lie, and they drop to the machinery ink so
   the drawing and the screen agree about what is still there. The control
   itself is gone, taken off the row by oba.js: a control that would take back
   what is already gone is a control nobody should be able to press. */
.row.undone .bubble { color: var(--oba-steel); }

.status {
  margin-top: 8px;
  color: var(--oba-steel);
  font-size: 11.5px;
}

/* -- the thinking light -------------------------------------------------- */

/* THE FOUNDER'S WORD, 2026-09-12 evening: "the light gliding over the
   thinking part ... we also need to add the logo to it". While Oba works the
   status word is painted steel with a chalk band sweeping across it every
   1.6 s, and the founder's small mark stands in front of the word, drawn by
   the animated renderer on a canvas of its own.

   NO OCHRE IN THIS STYLESHEET ON THIS LINE. Stop is the composer's one accent
   and the rule caps ochre at one bearer per region, so nothing below paints
   any: the word's two inks are steel and chalk. The mark's own ochre pulse is
   the RENDERER'S, read out of --oba-chalk and --oba-ochre at mount
   (brand/animated/oba-states.js), on a canvas no rule here can reach, and it
   is the identity moving rather than a second accent on the composer.

   IDLE IS BYTE FOR BYTE WHAT IT WAS. Every rule below hangs off
   .status[data-live="1"], which oba.js writes off state.busy alone, so
   "ready" is the same plain steel line it has always been with no mark beside
   it. AND NEITHER HALF IS STILLED BY A MOTION SETTING any more: the founder
   reversed that on 2026-09-16, because his machine keeps switching "reduce
   motion" on by itself and a mark frozen mid-turn read as a window that had
   stopped. The blanket reduced-motion rule at the foot of this sheet exempts
   the word's band by name, and the renderer reads no media query at all.

   THE WORD IS DRAWN ONLY WHERE IT CAN BE DRAWN. Its half stands inside an
   @supports of its own, and outside it the word keeps the line's inherited
   steel: a browser that cannot clip a background to text would otherwise show
   an invisible word. The mark needs no such guard, for the reason the block
   below gives. Today's five hosts all embed Chromium and all clip to text;
   the guard is what makes that a fact about them and not an assumption about
   the next one. */

/* THE MARK IS THE ANIMATED RENDERER'S CANVAS, not a masked picture (the
   founder, 2026-09-13: "the thinking needs to have the logo the animated ones
   as they are described when thinking and when executing"). It mounts itself
   on this span, whose id is "mark", and carries its own two inks, so the slot
   is a plain box the size the renderer draws for and nothing more. The band
   that used to cross it is gone: two motions on one small object would be two
   lights, and a canvas cannot be masked by a background band, so the gliding
   light stays on the word and the mark carries only its own ochre pulse.

   :NOT(:EMPTY) IS THE WHOLE OF THE DEGRADATION. The slot is drawn only while
   the line is live AND only when the renderer actually mounted a canvas into
   it. A payload that did not carry the asset leaves the span empty, so it is
   not drawn and the line is the word and its band alone, exactly the line
   before the mark landed, with no empty gap. No @supports guards it: a canvas
   needs neither mask nor background-clip, and it paints nothing until the
   script mounts it. A failure is the renderer's to still, which it does off
   the one word markWord() hands it; nothing here animates, and nothing here
   is stilled by a motion setting either (the founder's reversal of
   2026-09-16, and the renderer reads no media query). */
.status-mark { display: none; }

.status[data-live="1"] .status-mark:not(:empty) {
  display: inline-block;
  width: 16px;
  height: 16px;
  margin-right: 6px;
  vertical-align: -4px;
}

@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .status[data-live="1"] .status-word {
    background-color: var(--oba-steel);
    background-image: linear-gradient(100deg, transparent 42%,
                                      var(--oba-chalk) 50%, transparent 58%);
    background-size: 320% 100%;
    background-repeat: no-repeat;
    background-position: 0 0;
    -webkit-background-clip: text;
    background-clip: text;
    /* The letters are holes now: what the eye reads is the stack above,
       clipped to their shapes. Nothing but this selector is ever transparent
       on this page. */
    color: transparent;
    animation: oba-sweep 1.6s linear infinite;
    /* NO DELAY NOW. The .16s delay existed only so one band crossed the mark
       first and the word after it; the mark is a canvas with its own pulse
       now and there is nothing in front of the word to cross, so the word's
       light starts at zero (docs/animated-mark-2026-09-13). */
  }
  .status[data-live="1"][data-tone="flag"] .status-word {
    background-image: none;
    background-color: var(--oba-chalk);
    animation: none;
  }
  /* THE COMPACTING WORD (the founder, 2026-09-13: "the color is a light blue
     and the look should also have the reflection sort of breathing function
     going over it like the thinking part would"). The SAME gliding light: one
     declaration changes and it is the ink under the band, so band, size,
     delay and keyframe are not copied here, they are simply not touched. The
     rule stands inside this same @supports for the same reason the one above
     it does - a browser that cannot clip to text would show a light blue box
     - and it stands BELOW the flag rule so a failure over the fold is still
     drawn as a failure.

     THE MARK STAYS STEEL. The mark is the identity and the word is the state,
     so the colour has exactly one bearer, which is what a census can pin. */
  .status[data-live="1"][data-tone="compact"] .status-word {
    background-color: var(--oba-compact);
  }
}

/* LEFT TO RIGHT, AND IT RESTS WHERE IT ENDS. At 100% the image's right edge
   sits on the box's right edge, which with a box 320% wide puts the band's
   centre at -60%, off the left; at 0 it is at 160%, off the right. The
   resting position in both rules above is this keyframe's own `to`, so an
   animation that is stilled - by the flag tone - leaves plain steel behind
   and needs no second rule to undo it. */
@keyframes oba-sweep {
  from { background-position: 100% 0; }
  to   { background-position: 0 0; }
}

.disclaimer {
  margin: 4px 0 0;
  color: var(--oba-steel);
  font-size: 11.5px;
  line-height: 1.35;
}

/* Movement is decoration here, never information: the typing indicator still
   reads as three dots when it stops moving, and the composer still shows focus
   through its border colour.

   EXCEPT THE THINKING LINE (the founder's ruling of 2026-09-16). His machine
   keeps switching "reduce motion" on by itself, and a frozen light over
   "thinking" read as a window that had stopped. So the band over the word
   keeps its sweep whatever the setting says, in the same two numbers the
   rule above the keyframe declares, and the mark in front of it animates
   the same way on its own side (brand/animated/oba-states.js reads no media
   query). Everything else on the page still goes still. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .status[data-live="1"] .status-word {
    animation-duration: 1.6s !important;
    animation-iteration-count: infinite !important;
  }
  .typing i { opacity: .55; }
}

/* -- the stop control ---------------------------------------------------- */

/* THE FOUNDER'S ASK OF 2026-09-09: Stop wears Send's box. Every one of the
   seven declarations above the border is copied from `.send` unchanged, which
   is the whole of the change - a 26 px circle with the glyph centred in a
   grid, in place of the padded box Stop used to be. It matters past the look:
   box-sizing is border-box for everything on this page, so a padded auto
   height made Stop the composer's TALLEST child and the composer grew about
   9 px the instant a turn armed it and shrank again when the turn ended. Two
   buttons of one height cannot move it. What is NOT copied is Send's
   transition: Stop has never had one and the ask was shape and size. */
.stop {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--oba-ochre);
  border-radius: 50%;
  background: none;
  color: var(--oba-ochre);
  cursor: pointer;
}

/* The hover is the page's one idiom, on the hairline; the circle keeps its
   ochre, so the composer still carries exactly one accent under the pointer. */
.stop:hover { border-color: var(--oba-chalk); }
.stop:focus-visible { outline: 1px solid var(--oba-chalk); outline-offset: 2px; }
