:root {
  --bg: #111417; --panel: #191d21; --line: #262c31;
  --text: #e7ecef; --dim: #97a3ad; --accent: #7fb3ff; --warn: #ffb86b;
  /* How much accent tints an open section of the You window. More in the
     dark: the same step in luminance reads fainter on a dark screen. */
  --fold-mix: 12%;
  color-scheme: dark;
  /* Deliberately NOT scaled by --zoom.
     `rem` is the unit the layout is built from - paddings, gaps, button
     sizes, dialog widths. Scaling the root scaled all of that and, because
     `body` pinned an absolute font-size underneath it, scaled *only* that:
     the buttons and the chrome grew while the text they were made of stayed
     exactly where it was. Reported as "font sizing only affects the menu, the
     chat is still small, and the buttons become huge", which is precisely
     what those two rules do together.
     So the root is the layout's ruler and stays fixed; --zoom moves the text.
     See `body` below. */
}

/* Three states, not two. No `data-theme` means "follow the system", which is
   what most people want and what the app starts as; the attribute is only
   written when somebody chooses. The light palette therefore appears twice -
   once for the system preference and once for an explicit choice - because
   CSS has no way to name a block and reuse it.
   `color-scheme` moves with the palette, or the browser draws its own form
   controls and scrollbars for the wrong one. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --bg: #fbfbfa; --panel: #fff; --line: #e3e5e8; --text: #1b1f23;
    --dim: #6a737d; --accent: #1f6feb; --warn: #b35900;
    --fold-mix: 7%;
    color-scheme: light;
  }
}
:root[data-theme="light"] {
  --bg: #fbfbfa; --panel: #fff; --line: #e3e5e8; --text: #1b1f23;
  --dim: #6a737d; --accent: #1f6feb; --warn: #b35900;
  --fold-mix: 7%;
  color-scheme: light;
}
:root[data-theme="dark"] {
  --bg: #111417; --panel: #191d21; --line: #262c31;
  --text: #e7ecef; --dim: #97a3ad; --accent: #7fb3ff; --warn: #ffb86b;
  /* How much accent tints an open section of the You window. More in the
     dark: the same step in luminance reads fainter on a dark screen. */
  --fold-mix: 12%;
  color-scheme: dark;
}
/* An instance may set its own accent (admin only). Applied from JS as a
   single custom property, so branding can reach the colour of a control and
   nothing else - no stylesheet, no markup. */
* { box-sizing: border-box; }
/* `hidden` means hidden, everywhere.
 *
 * The attribute's effect is a *user-agent* rule - `[hidden] { display: none }`
 * - so any author rule that sets `display` beats it, silently. Three elements
 * on the chat page were marked hidden and rendered anyway: the camera button
 * on a phone, the setup dialog's file input, and the install prompt.
 *
 * The camera button is the one that cost something. It was reported twice as
 * showing on a phone, and twice I looked at the JavaScript - which had been
 * setting `hidden` correctly all along, while `#composer .compose-tools
 * button { display: inline-flex }` quietly overrode it. The console had the
 * same bug in the same week, patched there with one `section[hidden]` rule.
 * This is that fix, made general, because the next one is otherwise identical
 * and equally invisible.
 *
 * `!important` because the whole point is outranking a rule written later and
 * more specifically by somebody who was not thinking about this. */
[hidden] { display: none !important; }
body {
  margin: 0; background: var(--bg); color: var(--text);
  font-family: ui-sans-serif, -apple-system, "Segoe UI", system-ui, sans-serif;
  line-height: 1.55;
  /* The one place text size is decided, and the only thing --zoom moves.
     17px rather than the 15px this shipped with: this is a reading surface,
     and 15px is a size for dense chrome, not for paragraphs somebody reads on
     a phone at arm's length. Everything that should grow with it is sized in
     `em` and inherits; everything structural is in `rem` and does not. */
  font-size: calc(1.0625rem * var(--zoom, 1));
  display: flex; flex-direction: column;
  /* --viewport is the visual viewport, set from JS. 100dvh is the fallback
     and is not enough on its own: it does not shrink for the on-screen
     keyboard, so the composer ends up underneath it, and on a reload it is
     measured while the URL bar is still expanding. */
  height: 100dvh;
  height: var(--viewport, 100dvh);
  overflow: hidden;
}
header {
  display: flex; gap: .6rem; align-items: center; padding: .7rem 1rem;
  border-bottom: 1px solid var(--line); padding-top: max(.7rem, env(safe-area-inset-top));
}
.dot { width: .55rem; height: .55rem; border-radius: 50%; background: var(--dim); }
.dot.live { background: #3fb950; }
.dot.error { background: #f85149; }
/* Working, in a diminished way. Distinct from `error` because a pane that
   paints "semantic search is off" the same red as "the master key does not
   open this store" has thrown away the only ranking it had - and from the dim
   default, which means nobody has set this up and may never have meant to. */
.dot.warn { background: var(--warn); }
header select, header button, #composer button {
  background: var(--panel); color: var(--text); border: 1px solid var(--line);
  border-radius: .5rem; padding: .35rem .7rem; cursor: pointer;
  /* rem, not inherit. The chrome is a fixed frame around a resizable page:
     turning the chat up to 150% should give you bigger words to read, not a
     header three rows deep with nothing left for the conversation. */
  font-family: inherit; font-size: .95rem;
}
header select { max-width: 9rem; }
header select[hidden] { display: none; }
/* The one automatic margin in the row, and the reason the shape holds however
   much of the header is showing.

   It used to be on the picker, with the dot taking over when the picker was
   hidden - a rule per case, because whatever was last on the left had to be
   the thing that pushed. There is no writing that rule for the chips: which
   of them is last depends on whether the budget one is showing, and it is
   hidden most of the month. So the left is a group and the group pushes,
   which is one rule for every case including the ones not built yet. */
.header-left {
  display: flex; align-items: center; gap: .6rem;
  margin-right: auto;
  /* Never squeezed. On a narrow phone the row has to give somewhere, and what
     gives is the brand - it is somebody's own words and it ellipsizes, which
     is a graceful way to lose a few characters. A chip has no graceful way to
     lose any: "83% of budget" half-shown says nothing. */
  flex: none;
}
/* The header is one row again and stays one row. It used to wrap, because six
   controls do not fit across a phone and a row that overflows pushed the
   status dot off the left edge - but wrapping only chose the better of two bad
   answers. The row is one button wide now, so neither happens. `nowrap` is
   kept rather than dropped because the brand is somebody's own words and a
   long one would otherwise start the two-line header over again. */
header { flex-wrap: nowrap; }
.dot { flex: none; }
#brand { overflow: hidden; text-overflow: ellipsis; }

/* The two things the header says about the turn you are about to take: which
   kind of model is answering, and - once it is worth knowing - how much of
   the month's budget is gone.

   Smaller and quieter than the controls beside them, because neither is a
   control: one is a label and the other is a warning that spends most of its
   life not being there. `header .chip` rather than `.chip`, so this outranks
   the `header select, header button` rule that would otherwise size them like
   the menu button - including the phone override, which is a rule of the same
   weight written later and would otherwise win. */
header .chip {
  flex: none;
  display: inline-flex; align-items: center; gap: .3rem;
  padding: .18rem .45rem;
  border: 1px solid var(--line); border-radius: .5rem;
  background: var(--panel); color: var(--dim);
  font-size: .78rem; line-height: 1.4;
  white-space: nowrap;
  cursor: default;
}
.chip-name { color: var(--dim); }
.chip-value { color: var(--text); }
/* Nothing has been asked in this conversation yet, so there is no kind to
   name. Dimmer than a real answer rather than absent: a chip that appears and
   disappears moves the header about, and the dash is the honest reading. */
#kind.unknown .chip-value { color: var(--dim); }
/* It opens the Spending window, so it says so the way every other button on
   this page does. */
header button.chip { cursor: pointer; }
header button.chip:hover, header button.chip:focus-visible { background: var(--bg); }
/* Approaching the cap, and past it. Two colours rather than one, following
   the same split the rest of this file uses: `--warn` is information you
   should act on, red is something that has already stopped working. */
header .chip.warn { color: var(--warn); border-color: var(--warn); }
header .chip.over { color: #f85149; border-color: #f85149; font-weight: 600; }
/* A kind somebody chose, rather than one the routing worked out. Marked on
   the chip because a hold left on the dearest model is a standing decision
   nobody is reminded of otherwise - and a tooltip is not a reminder. The
   accent rather than a warning colour: it is a setting doing what it was
   asked to, not something going wrong. */
header .chip.held { border-color: var(--accent); }
header .chip.held .chip-value { color: var(--accent); font-weight: 600; }
/* Work going on beside the conversation. A dot that breathes while something
   is running - the one thing on this row that is *happening* rather than a
   fact - and still, in the warning colour, when all that is left is waiting on
   a card, because then nothing is happening until you answer it. */
header #working { color: var(--text); }
.working-dot {
  width: .5rem; height: .5rem; border-radius: 50%;
  background: var(--accent); flex: none;
}
header #working.warn .working-dot { background: var(--warn); }
@media (prefers-reduced-motion: no-preference) {
  header #working.busy .working-dot { animation: pulse 1.4s ease-in-out infinite; }
}

/* The window it opens. One card per task, the state on the same line as the
   name because "is it done yet" is the question the window is opened to
   answer. */
.bg-task {
  border: 1px solid var(--line); border-radius: .6rem;
  padding: .6rem .7rem; margin: .5rem 0;
  color: var(--text);
}
.bg-task.running { border-color: var(--accent); }
.bg-task.waiting { border-color: var(--warn); }
.bg-head { display: flex; flex-wrap: wrap; gap: .2rem .6rem; align-items: baseline; }
.bg-head strong { overflow-wrap: anywhere; }
.bg-state { color: var(--dim); font-size: .85rem; }
.bg-task.waiting .bg-state { color: var(--warn); }
.bg-asked { color: var(--dim); font-size: .85rem; margin: .25rem 0; overflow-wrap: anywhere; }
.bg-why { font-size: .85rem; margin: .25rem 0; }
.bg-task details { margin: .3rem 0; }
.bg-task summary { cursor: pointer; color: var(--dim); font-size: .85rem; }
.bg-task .row { display: flex; gap: .5rem; margin-top: .4rem; }
.bg-recent { font-size: .9rem; margin: 1rem 0 .2rem; color: var(--dim); }
/* The card a waiting task is held on, inside its row. Set in from the row
   rather than drawn as a second card, because it belongs to that task. */
.bg-card {
  border-left: 3px solid var(--warn);
  padding: .3rem 0 .3rem .6rem; margin: .4rem 0;
}
.bg-card-title { font-weight: 600; font-size: .9rem; }
.bg-card .facts { margin: .3rem 0; font-size: .85rem; }

/* The kinds, and how long the choice lasts, on one control in the window the
   chip opens. A list that opens and closes rather than nine buttons: the
   buttons were half that window and most of it on a phone. */
#why-model-pick {
  width: 100%; margin: .5rem 0 .4rem;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--line); border-radius: .5rem; padding: .4rem .5rem; font: inherit;
}

/* Everything that was the button row. Anchored to the header rather than
   floating in the page: `position: fixed` would have to be told where the
   header ended, and gets it wrong the moment the safe-area inset changes on
   rotation. */
header { position: relative; }
#menu {
  position: absolute; z-index: 30;
  top: 100%; right: .7rem; margin-top: .3rem;
  display: flex; flex-direction: column; gap: .15rem;
  min-width: 11rem; max-width: min(16rem, calc(100vw - 1.4rem));
  padding: .35rem;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: .6rem;
  /* The one shadow in this file. A panel over a conversation has to read as
     *above* it - without a shadow the rows below show through as a second
     column of text and the eye has to work out which is which. */
  box-shadow: 0 .5rem 1.5rem rgb(0 0 0 / .35);
  /* Long enough to reach past the fold on a small phone in landscape. */
  max-height: calc(var(--viewport, 100dvh) - 5rem); overflow-y: auto;
}
/* Full-width rows, not chips. Chips in a column leave a ragged right edge and
   a target you have to aim at; a row is hit anywhere along it. */
#menu button {
  width: 100%; text-align: left;
  background: none; border: 1px solid transparent; border-radius: .4rem;
  padding: .5rem .6rem;
  display: flex; align-items: center; gap: .4rem;
  /* Said again rather than inherited from the header rule, which shrinks its
     buttons below 32rem. That was right for chips competing for one row and
     is wrong here: these rows have the width to themselves, and a phone is
     where the smaller text would have been read. */
  font-size: .95rem;
}
#menu button:hover, #menu button:focus-visible { background: var(--bg); }

/* The menu, in three groups rather than ten rows.

   `#menu` is a flex column, so a group has to be one too or its rows lay out
   across the panel instead of down it. The gap is the menu's own, said again
   here rather than inherited, so the rows inside a group sit exactly as they
   did when they were all siblings. */
.menu-group { display: flex; flex-direction: column; gap: .15rem; }
/* A hairline above each group but the first: the caption alone reads as a
   dead row, and a full border on every group turns a short list into a stack
   of boxes. */
.menu-group + .menu-group { margin-top: .25rem; padding-top: .3rem; border-top: 1px solid var(--line); }
/* Quieter than the rows and not a target - it is a caption, and one that
   looks pressable is one people press. `default` rather than `pointer` says
   so before they try. */
.menu-group-name {
  font-size: .74rem; color: var(--dim); cursor: default;
  padding: .15rem .6rem .1rem; letter-spacing: .02em;
}

.hamburger {
  /* Square, and sized from the icon rather than from text: this button has no
     label to set its width, so without this it collapses to the padding. */
  display: grid; place-items: center;
  padding: .35rem; width: 2.1rem; height: 2.1rem;
  position: relative;
}
.hamburger[aria-expanded="true"] { background: var(--bg); }
/* Not a number. See the note in index.html. */
.hamburger .pip {
  position: absolute; top: .28rem; right: .28rem;
  width: .45rem; height: .45rem; border-radius: 50%;
  background: var(--accent);
  /* Against the panel the button sits on, so the pip reads as attached to the
     button rather than as a speck on the header. */
  border: .12rem solid var(--panel);
}
.hamburger[aria-expanded="true"] .pip { border-color: var(--bg); }

@media (max-width: 32rem) {
  header { gap: .35rem; padding: .6rem .7rem; }
  .header-left { gap: .35rem; }
  header select, header button { padding: .3rem .5rem; font-size: .88rem; }
  #composer { padding: .6rem .7rem; }
  /* The footer is what sits over the home indicator now, so it carries the
     inset the composer used to. */
  #foot { padding: .3rem .7rem; padding-bottom: max(.3rem, env(safe-area-inset-bottom)); }
  .compose-tools { gap: .35rem; }
  main { padding: .8rem .7rem; }
}
/* The chat and the Today drawer, side by side.
   `min-height: 0` is load-bearing and is the thing that looks removable: a
   flex child defaults to `min-height: auto`, which refuses to shrink below its
   content, so without it the conversation grows the row instead of scrolling
   inside it and the composer is pushed off the bottom of the screen. The same
   applies to `min-width: 0` on `main` - a long unbroken line in a code block
   would otherwise widen the column and squeeze the drawer. */
/* `position: relative` so the narrow-screen drawer can be absolute against
   *this* row rather than the viewport, which keeps it below the header. See the
   media query at the end of the Today block. */
#shell { flex: 1; display: flex; min-height: 0; position: relative; }
/* The conversation and its composer, as one column that the Today panel sits
   beside. The composer used to be a sibling of the shell and therefore as wide
   as the window, so with the panel open it ran on underneath it - a typing bar
   wider than the thing it types into, which reads as the panel overlapping it.
   Being in here, it ends exactly where the conversation ends.
   `min-width: 0` so a long unbroken line in a code block cannot widen this
   column and squeeze the panel; `min-height: 0` so the log scrolls inside it
   rather than growing the row and pushing the composer off the screen. */
#chat { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
main {
  flex: 1; min-width: 0; overflow-y: auto; padding: 1rem; display: flex;
  flex-direction: column; gap: .85rem;
}

/* -------------------------------------------------------------- Today drawer */

/* A column beside the conversation, not on top of it. The day is meant to be
   read *while* you talk about it - "move the second one to tomorrow" only
   makes sense with both on screen - and an overlay would make this a dialog
   with extra steps. Below the breakpoint at the end of this block there is no
   room for two columns and it becomes an overlay after all, because a 22rem
   panel and a usable conversation do not both fit on a phone. */
/* Width that gives way before the conversation does. A fixed 22rem is right on
   a desk and wrong at 800px, where it is a third of the window and the chat is
   left with a column of two-word lines - so it scales with the viewport between
   a floor it stays readable at and the ceiling it wants on a wide screen.
   `flex: none` because a flex item with a `width` will still shrink below it
   otherwise, and a squeezed panel is what this is avoiding. */
/* The width above is the *default*, and only that: dragging the grip writes an
   inline width and the clamp stops applying. Which is the right way round -
   somebody who has never touched it gets a width that adapts to their screen,
   and somebody who has chosen one gets the one they chose. */
#today {
  flex: none; width: clamp(15rem, 30vw, 22rem); overflow-y: auto;
  padding: 1rem; background: var(--panel);
  border-left: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 1rem;
  min-height: 0;
}
/* Something to take hold of, on the edge between the two columns.
   A sibling of the panel rather than a child of it, because the panel scrolls:
   a grip inside it would slide up out of reach the moment somebody's day was
   longer than the screen, which is the day they are most likely to want more
   room for.
   Wider than it looks. The visible line is one pixel - a hairline is what an
   edge should be - and the target around it is nearly half a rem, because a
   one-pixel target is a mouse hunt and, on a trackpad, a miss. */
#today-grip {
  flex: none; width: .45rem; margin-right: -.45rem; z-index: 2;
  cursor: col-resize; background: none; border: 0; padding: 0;
  align-self: stretch; touch-action: none;
}
#today-grip:hover, #today-grip:focus-visible {
  background: var(--accent); outline: none;
}
/* While a drag is happening, every other element stops trying to be helpful:
   text selection across the conversation, and the panel's own hover states. */
body.resizing { cursor: col-resize; user-select: none; }
/* A grid with named areas rather than a wrapping flex row, because *what*
   wraps has to be decided here rather than by whichever element happens to
   overflow first. As a flex row it was the close button that wrapped - landing
   alone on a second line, detached from Refresh, at the one width the panel is
   most often used at. The panel still shut; the control that shuts it had
   simply gone somewhere nobody looks, which is indistinguishable from the
   drawer not being collapsible.
   So: the close button is pinned top-right at every width, Refresh sits beside
   it, and the timestamp - the only part long enough to cause trouble, and the
   only part nothing depends on - is what goes to its own line. */
.today-head {
  display: grid;
  grid-template-columns: 1fr auto auto;
  grid-template-areas:
    "title refresh close"
    "when  when    when";
  align-items: center;
  gap: .25rem .5rem;
}
.today-head h2 { grid-area: title; margin: 0; font-size: 1.05rem; }
.today-head .meta { grid-area: when; }
#today-refresh { grid-area: refresh; }
/* Only where it sits. How it looks is the shared `.close` rule, which is the
   point of #124: this was a little larger and a little differently padded than
   the × in every dialog, for no reason anybody could have named. */
#today-close { grid-area: close; }
#today h3 {
  margin: 0 0 .35rem; font-size: .8rem; text-transform: uppercase;
  letter-spacing: .04em; color: var(--dim); font-weight: 600;
}
.today-part { display: flex; flex-direction: column; gap: .4rem; }
.today-part > button { align-self: flex-start; }
#today .meta { margin: 0; }

/* The place to drop a document into the OneDrive folder this assistant files
   into. A real <button>, so it is reachable from the keyboard and announces
   itself as something to press - a div with a dashed border is neither.
   Written to outrank `#today button:not(.close)` on specificity rather than
   on position, the way the three controls further down this file are: the
   accent-filled look that rule gives every button in the drawer is exactly
   wrong here, because this is an area to aim at and not an action to take,
   and a solid block of colour reads as "send".
   `align-self` because `.today-part > button` pins buttons to the left, which
   is right for Add and wrong for a target the width of the panel. */
#today .today-part button.dropzone {
  align-self: stretch; padding: 1.1rem .8rem; text-align: center;
  background: none; color: var(--dim);
  border: 2px dashed var(--line); border-radius: .6rem;
  font-size: .9rem; cursor: pointer;
}
#today .today-part button.dropzone:not([aria-disabled="true"]):hover,
#today .today-part button.dropzone:focus-visible {
  border-color: var(--accent); color: var(--accent);
}
/* While something is actually over it. Filled rather than merely outlined,
   because at the moment of letting go the question is "will this land here",
   and an outline that also appears on hover does not answer it. */
#today .today-part button.dropzone.over {
  border-color: var(--accent); color: var(--accent);
  border-style: solid;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}
/* Nowhere to put anything yet - no folder named, or no account behind it.
   Still drawn, because the line under it says which of those it is; a box
   that vanished would take the instruction with it. Marked with
   `aria-disabled` rather than `disabled` so a file let go on it is still
   answered here instead of falling through to the conversation. */
#today .today-part button.dropzone[aria-disabled="true"] {
  cursor: default; opacity: .55;
}
/* The way to the folder itself, under the words that name it. Left-aligned
   with everything else in the band rather than centred under the box: it is a
   link in a column of sentences, not a second target. */
#today .folder-link { align-self: flex-start; font-size: .86rem; }

/* One line on the board. A label rather than a div, so the whole row is the
   tick box's hit area - picking a day on a phone is a dozen taps, and a 14px
   checkbox is the wrong target for every one of them. */
.today-item {
  display: grid; grid-template-columns: auto 1fr; gap: .1rem .55rem;
  align-items: baseline; padding: .4rem .5rem; border-radius: .5rem;
  border: 1px solid var(--line); background: var(--bg); cursor: pointer;
}
.today-item:hover { border-color: var(--accent); }
.today-item input { margin: 0; accent-color: var(--accent); }
.today-item .title { overflow-wrap: anywhere; }
/* The source and the date, under the title rather than beside it: at 22rem a
   second column costs the title the words at the end of it. */
.today-item .about {
  grid-column: 2; font-size: .82rem; color: var(--dim);
  display: flex; gap: .45rem; flex-wrap: wrap;
}
.today-item.done .title { text-decoration: line-through; color: var(--dim); }
/* Overdue, and only ever about a date that has passed - not a nudge about
   today's own work. `--warn` rather than red: this is information, and a
   panel that shouts at somebody every morning is a panel they stop reading. */
.today-item .about .late { color: var(--warn); }
.today-tick {
  flex: none; background: none; border: 0; padding: 0 .15rem; cursor: pointer;
  color: var(--dim); font-size: 1rem; line-height: 1;
}
.today-tick:hover { color: var(--accent); }
.today-row { display: flex; align-items: baseline; gap: .35rem; }
.today-row .today-item { flex: 1; }

/* A meeting. Time first and in the same column on every row, so the day reads
   as a schedule down the left edge rather than as a paragraph. */
.today-meeting {
  display: grid; grid-template-columns: auto 1fr; gap: .1rem .55rem;
  padding: .3rem 0; border-bottom: 1px solid var(--line);
}
.today-meeting:last-child { border-bottom: 0; }
.today-meeting .clock { font-variant-numeric: tabular-nums; color: var(--dim); }
.today-meeting .about { grid-column: 2; font-size: .82rem; color: var(--dim); }
.today-meeting .subject { overflow-wrap: anywhere; }

.today-trouble {
  margin: 0; padding: .5rem .6rem; border-radius: .5rem;
  border: 1px solid var(--warn); color: var(--warn); font-size: .85rem;
}

#today-add { display: flex; gap: .4rem; }
#today-add input {
  flex: 1; min-width: 0; background: var(--bg); color: var(--text);
  border: 1px solid var(--line); border-radius: .5rem; padding: .35rem .55rem;
  font-family: inherit; font-size: .95rem;
}
#today-add button { flex: none; }

/* The count on a menu row, so a shut menu still says how much is left. Absent
   rather than zero when there is nothing: a badge reading 0 is a worse answer
   than no badge.

   Written against the menu rather than against `#today-open`, which is what it
   used to say. Forms grew the same badge when the row moved into the column
   beside this one, and two rules that have to be kept identical are one rule
   somebody will change half of.

   The rows that carry a number push it to the far edge, so two counts line up
   with each other instead of sitting wherever each label happens to end. */
#menu .count {
  display: inline-block; margin-left: auto; padding: 0 .35rem;
  border-radius: .7rem; background: var(--accent); color: var(--bg);
  font-size: .78rem; font-weight: 600;
}

/* Below this there is not room for a conversation and a column, so the drawer
   becomes an overlay - the compromise a phone forces, and only on a phone.
   `position: fixed` against the viewport rather than the row, because the row
   scrolls and a panel that scrolled away from its own close button would be
   unshuttable. */
/* Below this the panel stops being a column and becomes a drawer over the
   conversation, because a 15rem panel and a usable chat do not both fit.
   34rem - around 540px - is a phone held upright and nothing else. It was
   60rem, which was wrong by a wide margin and wrong in the direction that
   looks fine to whoever wrote it: a desktop window is comfortably past 960px
   in *device* pixels while reporting well under it in CSS pixels once the
   display is scaled, so the layout somebody actually got on a large screen was
   the phone drawer, sitting on top of the conversation it was meant to sit
   beside. The number is now low enough that the column is what a real window
   gets and the drawer is reserved for a screen that genuinely has no room.

   `absolute` against `#shell` rather than `fixed` against the viewport, so the
   drawer stops below the header instead of covering it. Fixed positioning put
   it over the row that holds the button which opens and shuts it - the panel's
   own × still worked, so it was closable, but the control that had just been
   pressed was underneath the thing it had opened. */
@media (max-width: 34rem) {
  #today {
    position: absolute; z-index: 5; top: 0; right: 0; bottom: 0;
    width: min(20rem, 92vw);
    padding-bottom: max(1rem, env(safe-area-inset-bottom));
    box-shadow: -.5rem 0 1.5rem rgb(0 0 0 / .35);
  }
  /* No grip at this width. The drawer is an overlay here rather than a column,
     so there is no edge between two things to drag - and a resize handle on a
     phone is a target nobody wants next to the one they were aiming for. */
  #today-grip { display: none !important; }
  /* The composer is inside the shell now, so the drawer lands on top of it -
     and at this width it covers about two thirds, which is the worst of the
     three available outcomes: a message box with no Send button, cut down the
     middle. Gone entirely while the drawer is open is better and is also the
     truth about the moment, since somebody picking their day is not typing;
     the × is right there and brings it straight back. */
  #shell:has(#today:not([hidden])) #composer { display: none; }
}
/* -------------------------------------------------------------- the draft */

/* A writing column, where Today would be, and wider than Today: this one is
   read and typed in rather than glanced at. The same grip, the same
   remembered width, and the same overlay on a phone. */
#draft {
  flex: none; width: clamp(20rem, 40vw, 46rem); overflow-y: auto;
  padding: 1rem; background: var(--panel);
  border-left: 1px solid var(--line);
  display: flex; flex-direction: column; gap: .8rem;
  min-height: 0;
}
#draft-grip {
  flex: none; width: .45rem; margin-right: -.45rem; z-index: 2;
  cursor: col-resize; background: none; border: 0; padding: 0;
  align-self: stretch; touch-action: none;
}
#draft-grip:hover, #draft-grip:focus-visible { background: var(--accent); outline: none; }
/* The close button pinned top-right at every width, for Today's reason. */
.draft-head {
  display: grid;
  grid-template-columns: 1fr auto auto;
  grid-template-areas:
    "title mode close"
    "when  when when";
  align-items: center;
  gap: .25rem .5rem;
}
.draft-head h2 {
  grid-area: title; margin: 0; font-size: 1.05rem;
  overflow-wrap: anywhere;
}
.draft-head .meta { grid-area: when; margin: 0; }
#draft-mode { grid-area: mode; }
#draft-close { grid-area: close; }
/* Reading mode: the document as it will read, with room to breathe. */
.draft-view { line-height: 1.55; overflow-wrap: anywhere; }
.draft-view h1 { font-size: 1.35rem; }
.draft-view h2 { font-size: 1.15rem; }
.draft-view h3 { font-size: 1rem; }
.draft-view table { border-collapse: collapse; }
.draft-view th, .draft-view td { border: 1px solid var(--line); padding: .25rem .5rem; }
/* Writing mode: the whole column, in the reading font rather than a code
   face - this is a letter being written, not a file being edited. */
.draft-text {
  flex: 1; min-height: 16rem; width: 100%; box-sizing: border-box;
  resize: none; padding: .7rem; border-radius: .5rem;
  border: 1px solid var(--line); background: var(--bg); color: var(--text);
  font: inherit; line-height: 1.55;
}
.draft-text:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
.draft-conflict {
  border: 1px solid #d29922; border-radius: .5rem; padding: .6rem .75rem;
  display: flex; flex-wrap: wrap; gap: .5rem; align-items: center;
}
.draft-conflict p { margin: 0; flex-basis: 100%; }
.draft-empty p { margin: 0 0 .6rem; color: var(--dim); }
.draft-new { display: flex; gap: .4rem; }
.draft-new input {
  flex: 1; min-width: 0; padding: .4rem .5rem; border-radius: .5rem;
  border: 1px solid var(--line); background: var(--bg); color: var(--text); font: inherit;
}
.draft-foot { display: flex; flex-wrap: wrap; gap: .5rem; }
#draft-discard { margin-left: auto; }
#draft .meta { margin: 0; }
/* The menu entry says when there is a draft waiting, so it can be found
   again from a conversation that never mentioned it. */
#draft-open.has-draft::after { content: " •"; color: var(--accent); }
@media (max-width: 34rem) {
  #draft {
    position: absolute; z-index: 5; top: 0; right: 0; bottom: 0; left: 0;
    width: auto !important;
    padding-bottom: max(1rem, env(safe-area-inset-bottom));
  }
  #draft-grip { display: none !important; }
  #shell:has(#draft:not([hidden])) #composer { display: none; }
}

.turn { display: flex; gap: .55rem; align-items: flex-start; max-width: 44rem; }
.turn.mine { align-self: flex-end; flex-direction: row-reverse; }
.avatar {
  flex: none; width: 2.55rem; height: 2.55rem; border-radius: 50%;
  display: grid; place-items: center; font-size: 1.1rem; line-height: 1;
  background: var(--panel); border: 1px solid var(--line); color: var(--text);
  user-select: none;
}
.avatar.assistant { border-color: var(--accent); color: var(--accent); }
/* A picture fills the circle. object-fit, because the core accepts any square
   the browser produced and a future one may not be exactly 128px. */
img.avatar { object-fit: cover; background: var(--panel); }

/* The two pickers in the You dialog. Their button rules live with the window
   block below, where they can outrank the shared design - a picture with a
   caption under it is not a button with a face painted on it. */
.row.avatars { align-items: center; gap: .8rem; flex-wrap: wrap; }
/* `min-width: 0` is the whole of the phone-width fix, and it is the rule that
   looks like it does nothing.
   The bubble is a flex item inside `.turn`, so it starts at `min-width: auto` -
   "never narrower than my own content". A reply containing a code block or a
   table has a min-content width of whatever the longest line is, so the bubble
   refused to shrink to the column and sat at its own 44rem ceiling instead:
   704px of message inside a 390px phone. `main` scrolls vertically, and a box
   that scrolls on one axis computes the other to `auto` rather than `visible`,
   so the overflow was not clipped - it became a horizontal scrollbar under the
   conversation and a page that measured about twice the screen.
   The blocks inside already scroll themselves (`.msg pre`, `.msg table` below);
   what they needed was a bubble narrow enough to scroll inside. */
.msg { max-width: 44rem; min-width: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
.msg a { color: var(--accent); overflow-wrap: anywhere; }
.msg.user { background: var(--panel); border: 1px solid var(--line);
            border-radius: .8rem; padding: .5rem .8rem; }
/* A brand mark in the header, when an instance sets one. */
#brand { font-weight: 600; margin-right: .3rem; white-space: nowrap; font-size: .95rem; }
#brand:empty { display: none; }
.meta { color: var(--dim); font-size: .82em; }
.tool { color: var(--dim); font-size: .86em; font-style: italic; }
.card {
  border: 1px solid var(--warn); border-radius: .7rem; padding: .8rem;
  background: var(--panel); max-width: 44rem;
}
.card h3 { margin: 0 0 .4rem; font-size: 1em; }
.card pre { margin: .5rem 0; overflow-x: auto; font-size: .84em; color: var(--dim); }
.card .row { display: flex; gap: .5rem; margin-top: .5rem; }
.card button { border-radius: .5rem; padding: .35rem .8rem; font: inherit; cursor: pointer;
               border: 1px solid var(--line); background: var(--panel); color: var(--text); }
.card button.yes { border-color: #3fb950; }
.card button.no { border-color: #f85149; }
/* Third, and quieter than the other two on purpose: it is the answer that
   outlives this card, so it should be chosen rather than hit by reflex. */
.card button.ghost { border-color: transparent; color: var(--dim); }
.card button.ghost:hover { border-color: var(--line); color: var(--text); }
/* A notice is not a question. The card border above is `--warn` because a card
   has always been something waiting on an answer; what arrived while you were
   away is news, and dressing news as a demand is how a channel gets muted. An
   alert about the assistant itself - a mailbox that needs reauthorising - keeps
   the warning colour, because that one does want something from you. */
.card.notice { border-color: var(--line); }
.card.notice .msg { margin: .3rem 0 0; }
.card button.quiet { border-color: transparent; color: var(--dim); }
.card button.quiet:hover { border-color: var(--line); color: var(--text); }
.card details summary { cursor: pointer; color: var(--dim); font-size: .86em; }
/* The arguments as facts. A label column that does not stretch, so the values
   line up and the card can be read down rather than across. */
.facts { display: grid; grid-template-columns: auto 1fr; gap: .15rem .6rem; margin: .1rem 0 .5rem; }
.facts dt { color: var(--dim); font-size: .86em; }
.facts dd { margin: 0; font-size: .9em; overflow-wrap: anywhere; }
#composer {
  display: flex; flex-direction: column; gap: .5rem; padding: .8rem 1rem;
  border-top: 1px solid var(--line);
}
/* The message gets its own line and the whole width; the buttons go under it.
   In one row, six controls left the textarea about eight characters wide on a
   phone - and typing is the thing this bar is for. The buttons lose nothing by
   dropping down: they are the same size and now they have room for labels. */
.compose-tools { display: flex; gap: .5rem; align-items: center; }
/* Send sits at the far end, away from the thumb reaching for attach - and is
   sized for a thumb. 44px is the usual floor for a touch target; it was about
   thirty, which is why it kept getting missed.

   Stop shares every line of it, because it is the same button: exactly one of
   the two is on the page at a time and it stands in the same place. Styled
   separately it landed next to the microphone at its natural width, so the
   control moved and shrank the moment a turn started - and the thumb going
   for the far corner, where Send had been a second earlier, hit nothing. */
#send, #stop { margin-left: auto; min-height: 2.75rem; padding: .5rem 1.4rem; font-weight: 600; }
#composer .compose-tools button {
  min-height: 2.75rem; min-width: 2.75rem;
  display: inline-flex; align-items: center; justify-content: center;
}
/* The last difference between the two, and it needs this selector rather than
   `#send, #stop` above: the square-button rule directly overhead sets
   `min-width` and outranks a bare id pair, so written up there this is
   silently dropped - the same specificity trap the note at the top of this
   file is about, one rule further down.

   "Send" and "Stop" are not the same number of pixels of text, so without a
   floor wide enough for both the button changed width by three or four as the
   turn started. Neither is stretched by it; whichever is showing occupies
   exactly the same box. */
#composer .compose-tools #send,
#composer .compose-tools #stop { min-width: 5rem; }
/* The attach and mic glyphs carry the button on their own, so they get the
   size the letters do not need. */
#attach, #camera { font-size: 1.35rem; line-height: 1; }
#mic svg { display: block; }
/* Recording. The button is the indicator - a separate light somewhere else is
   a thing to miss while a microphone is open. */
#mic.on { border-color: #f85149; color: #f85149; }
@media (prefers-reduced-motion: no-preference) {
  #mic.on { animation: pulse 1.4s ease-in-out infinite; }
}
@keyframes pulse { 50% { opacity: .55; } }
#text {
  flex: 1; resize: none; background: var(--panel); color: var(--text);
  border: 1px solid var(--line); border-radius: .6rem; padding: .55rem .7rem; font: inherit;
  max-height: 9rem;
  /* A textarea has an intrinsic width (cols defaults to 20), and a flex item
     will not shrink below its intrinsic minimum without this. On a wide screen
     nothing shows; on a phone the row overflows and the Send button is cut off
     at the edge. That is the whole of the clipped composer. */
  min-width: 0;
  /* Under 16px, iOS Safari zooms the page on focus and never zooms back. */
  font-size: max(16px, 1em);
}
/* The row must not be wider than the bar, whatever it holds. Buttons keep
   their size; the textarea gives up the space. */
#composer { max-width: 100%; overflow-x: hidden; }

/* ---------------------------------------------------------------- commands */

/* The list of things you can type, while a `/` is being typed.
   In the flow above the box rather than over the conversation - see the note
   on it in index.html - so it needs a ceiling of its own: fifteen rows on a
   phone would leave the message you are writing off the bottom of the screen.
   It scrolls at about six.

   A third of `--viewport` and not of `100vh`, for the reason that property
   exists: the list is only ever up while somebody is typing, which on a phone
   is exactly when the keyboard has taken half the window. `vh` does not
   shrink for a keyboard, so a third of it is most of what is left. */
#commands {
  display: flex; flex-direction: column;
  max-height: calc(var(--viewport, 100dvh) / 3); overflow-y: auto;
  /* A flick that reaches the end of the list stops there rather than carrying
     on into the conversation behind it. On a phone the list is a third of the
     screen and the log is the rest, so without this a thumb aiming at the last
     row scrolls the chat instead. */
  overscroll-behavior: contain;
  background: var(--panel); border: 1px solid var(--line); border-radius: .6rem;
}
/* A row is pressed with a thumb as often as it is chosen with the keyboard, so
   it is a touch target and not a line of text. Name and description on one
   line, and the description gives up its width first: `/spending` is the part
   that has to survive a narrow screen. */
#commands .cmd {
  display: flex; align-items: baseline; gap: .6rem;
  padding: .4rem .7rem; min-height: 2.4rem; cursor: pointer;
}
#commands .cmd + .cmd { border-top: 1px solid var(--line); }
#commands .cmd-name {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 600;
  flex: none;
}
#commands .cmd-does {
  color: var(--dim); font-size: .86em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Which one Enter would run. `aria-selected` carries it rather than a class of
   our own: the attribute is what a screen reader reads, so styling anything
   else would be two states to keep in step and one of them silent. */
#commands .cmd[aria-selected="true"] {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
}
#commands .cmd:hover { background: var(--bg); }
/* And the same list written into the conversation by `/help`. Not a demand, so
   not the warning border - the note above `.card.notice` is the whole of the
   reasoning. The names are monospaced here too: they are things to type, and
   they should look the same in both places somebody meets them. */
.card.commands { border-color: var(--line); }
.card.commands dt {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--text);
}

/* ------------------------------------------------------------------ footer */

/* What is running, and whose this is. One quiet line under everything.
   The version used to live only in the settings window, behind your name and
   a scroll - which is a long way to walk somebody on the phone when the first
   question of every report is whether the new code is even on the server.
   Sized in rem and not in em: this is chrome, so turning the text up to read
   the conversation more comfortably must not spend that room on a footer.
   The safe-area inset moved here from the composer, because on a phone this is
   now the thing sitting over the home indicator. */
#foot {
  flex: none;
  display: flex; flex-wrap: wrap; gap: .1rem .8rem;
  padding: .35rem 1rem;
  padding-bottom: max(.35rem, env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  color: var(--dim); font-size: .75rem; line-height: 1.4;
}
/* One at each end, rather than both in the middle. There is no settled
   convention for a version beside a notice - web footers centre a copyright,
   application status bars pin their readings to the edges - and centred, the
   two read as one sentence that does not parse. At the margins each is its own
   fact: what you are running on the left, whose it is on the right.
   `margin-left: auto` on the notice rather than `justify-content:
   space-between` on the row, because the row is not always two things: with no
   copyright set, or with a health answer that never came, `space-between` has
   nothing to push against and drops the survivor on the left whichever one it
   is. This keeps each side its own. */
#foot .foot-notice { margin-left: auto; }
/* On the console the page scrolls, so this is the end of the document rather
   than a fixed strip - and it wants air above it instead of a hairline that
   would read as one more section divider. */
body.console #foot { border-top: 0; margin-top: 1.5rem; padding-bottom: 1.5rem; }

/* **These windows measure themselves, not the screen.** That sentence is the
   whole of the layout below and it is the thing every rule here used to get
   wrong.
   The rows inside a window - a name beside a sentence beside a value - each
   had a `@media (max-width: 32rem)` rule stacking them, with comments saying
   "rather than being squeezed into a column two words wide". Those rules ask
   how wide the *viewport* is. The thing that is narrow is the *window*, which
   was 34rem however large the screen: so on a phone they fired and the row
   stacked, and on a desktop they did not fire and the same row was squeezed
   into the same 34rem - three columns of prose in 544px, two words per line.
   Reported as "these menus do not fit" and, correctly, "not much better on a
   pc", which is the half a viewport query can never reach.
   So the window is a container and the rows below ask *it*. One answer, and
   it is the same answer at every screen size, because the question is now
   about the box the text is actually in. */
dialog, #today, #draft {
  container-type: inline-size;
  container-name: panel;
}
dialog {
  border: 1px solid var(--line); border-radius: .8rem; background: var(--panel);
  color: var(--text); padding: 1.2rem;
  /* 44rem, not 34. Even stacked, these windows hold a form and a list, and
     the old cap left a third of a laptop screen unused while the content
     inside it wrapped. */
  max-width: 44rem; width: calc(100vw - 2rem);
  max-height: calc(100dvh - 4rem);
}
/* Below this a window stops being a card on top of the app and becomes the
   app: the margin, the corners and the centring are all costing width that
   the sentences inside need more. */
@media (max-width: 32rem) {
  dialog {
    width: 100vw; max-width: none;
    height: 100dvh; max-height: none;
    margin: 0; border: 0; border-radius: 0;
    padding: 1rem;
    padding-top: max(1rem, env(safe-area-inset-top));
    padding-bottom: max(1rem, env(safe-area-inset-bottom));
  }
}
dialog::backdrop { background: rgba(0, 0, 0, .55); }
dialog h2 { margin: 0 0 .3rem; font-size: 1.05rem; }

/* ------------------------------------------------------------------ windows
   One design for every window, because there were four.
   You, Memory, Today and Time were built at different times and each grew its
   own: `#setup .row button` and `#planner button` agreed on an accent-filled
   primary and said so twice; Memory had no rule at all and fell through to
   whatever the browser draws; Today's × was a `quiet` button sitting next to
   Refresh, so the control that shuts the panel was a button among buttons
   rather than the × people look for in a corner. And the You panel - the one
   opened most often, from the button of the same name - had no × at all.
   So the rules below are written once against `dialog` and `#today` together.
   The per-screen copies they replace are gone; what stays scoped is only what
   is genuinely local to one window, like the planner's colour swatches.
   Deliberately not extended to the console or to the approval cards in the
   conversation: those are different surfaces, and this is about the windows a
   person opens from the header. */

/* The ×. One rule, one position, one size, in all of them - including the
   panel, which is not a `dialog` and had its own. */
.close {
  float: right; background: none; border: 0; color: var(--dim);
  font-size: 1.3rem; line-height: 1; cursor: pointer; padding: 0 .2rem;
}
.close:hover { color: var(--text); }

/* The primary action. Accent-filled, which is what two of the four already
   did - so this is the majority made general rather than a new look. */
dialog button:not(.close), #today button:not(.close), #draft button:not(.close) {
  border: 1px solid var(--accent); background: var(--accent); color: var(--panel);
  border-radius: .5rem; padding: .4rem .8rem; font: inherit; cursor: pointer;
}
/* The secondary one beside it. Without this every button in a row is
   accent-filled and "Save" competes with "Not now" for the same glance. */
dialog button.quiet, #today button.quiet, #draft button.quiet {
  background: none; color: var(--dim); border-color: var(--line);
}
dialog button.quiet:hover, #today button.quiet:hover, #draft button.quiet:hover {
  color: var(--text); border-color: var(--dim);
}
/* Chosen. Three signals rather than one, because a border a shade lighter is
   not a difference anybody sees on a phone in daylight: the text and the edge
   take the accent, and the face is tinted so the button reads as filled. */
dialog button.quiet.on:not(.swatch), #today button.quiet.on, #draft button.quiet.on {
  color: var(--accent); border-color: var(--accent); font-weight: 600;
  background: color-mix(in srgb, var(--accent) 18%, transparent);
}
dialog button.quiet.danger, #today button.quiet.danger, #draft button.quiet.danger {
  color: #f85149; border-color: #f85149;
}
dialog button:disabled, #today button:disabled, #draft button:disabled { opacity: .5; cursor: default; }
/* Three kinds of control that are not the button above, each written to
   outrank it rather than to rely on appearing later in the file. Specificity
   is the thing that decides here, and a rule that wins only by its position
   is one that breaks the next time somebody moves a block.
   A picture with a caption under it. */
dialog button.avatar-pick {
  display: flex; flex-direction: column; align-items: center; gap: .2rem;
  background: none; border: 0; padding: 0; cursor: pointer; color: inherit;
}
.avatar-pick .avatar { width: 2.6rem; height: 2.6rem; font-size: 1rem; }
.avatar-pick:hover .avatar { border-color: var(--accent); }
/* A full-width offer - install the app, turn on notifications - which is a
   suggestion rather than the action of the panel it sits in. Dashed, and it
   has to beat `.quiet` because every one of them carries both classes. */
button.quiet.wide { background: none; color: var(--accent); border: 1px dashed var(--line); }
button.quiet.wide.on { border-style: solid; border-color: var(--accent); }
/* One row's own action - Forget, Open, Delete - in a list of many rows. At
   the primary size and colour, a list of twenty of them is twenty buttons
   shouting, and the row itself is what somebody is reading. */
dialog .fact button {
  background: none; color: var(--dim); border: 1px solid var(--line);
  padding: .25rem .6rem; font-size: .8rem;
}
.tier { border-top: 1px solid var(--line); padding: .55rem 0; }
.tier summary {
  cursor: pointer; display: flex; gap: .6rem; align-items: baseline;
  list-style: none;
}
.tier summary::-webkit-details-marker { display: none; }
/* Something has to say these rows open, or nobody finds the picker. */
.tier summary::after {
  content: "▸"; margin-left: auto; color: var(--dim); flex: none;
  transition: transform .12s ease;
}
.tier[open] summary::after { transform: rotate(90deg); }
.tier-name {
  font-weight: 600; font-size: .92rem;
  min-width: 5rem; flex: none;
}
.tier .current {
  font-size: .8rem; color: var(--dim); overflow-wrap: anywhere;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
/* A tier you have moved off the shipped default should be visible at a glance. */
.tier .current.chosen { color: var(--accent); }
/* On neither the deployment's answer nor one of their own. A warning colour
   rather than the accent: `chosen` is somebody's decision and this is the
   absence of one, and the two must not read the same at a glance. */
.tier .current.behind { color: var(--warn); }
/* Only the tier rows, which come from the server lowercase. An
   integration names itself - `n8n` and `Microsoft 365 (Entra)` are not
   improved by being title-cased into `N8n` and `Microsoft 365 (Entra)`. */
#tiers .tier-name { text-transform: capitalize; }
.tier[open] > .meta:first-of-type { margin: .35rem 0 .5rem; }
.tier input[type="search"], .tier select {
  width: 100%; background: var(--bg); color: var(--text);
  border: 1px solid var(--line); border-radius: .5rem; padding: .4rem .5rem; font: inherit;
}
.tier input[type="search"] { margin-bottom: .35rem; }
.tier select { font-size: .85rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.tier select option {
  padding: .15rem .1rem; background: var(--bg); color: var(--text);
}
/* Setting an option background overrides the UA's selection highlight, so the
   current choice has to be marked back in or the list shows no state. */
.tier select option:checked {
  background: var(--accent); color: var(--panel); font-weight: 600;
}
/* A `<label>` is one caption over one field. Nine blocks in this dialog are a
   caption over a *group* of buttons - the theme choices, the morning brief,
   the three pictures - and a label wraps them all in one control: every dead
   pixel in the block, the caption and the explanatory note included, is a
   click on whichever control the browser picks first. Reported as "changing
   the app icon changes the human icon", which is exactly what it did - the
   App icon was the fourth thing inside `Pictures` and the first was *You*.
   Clicking the note under Location turned the device's location on.

   So those blocks are `.field` instead, and this rule gives them the label's
   layout without the label's behaviour. */
#setup label, #setup .field { display: block; margin: .7rem 0; font-size: .88rem; }
#setup .field-name { display: block; }
#setup input, #setup textarea {
  display: block; width: 100%; margin-top: .25rem; background: var(--bg);
  color: var(--text); border: 1px solid var(--line); border-radius: .5rem;
  padding: .45rem .55rem; font: inherit; resize: vertical;
}
/* The signature box. A `contenteditable`, so it gets the same frame every
   other box in this dialog has - without it a control you can type into is the
   only one on the page with no edge, which reads as a paragraph of text.
   `min-height` rather than `rows`: it holds four lines and a logo, and it
   should grow to whatever was pasted rather than scroll a signature. */
#setup .signature-box {
  display: block; width: 100%; margin-top: .25rem; background: var(--bg);
  color: var(--text); border: 1px solid var(--line); border-radius: .5rem;
  padding: .45rem .55rem; font: inherit; min-height: 5rem; overflow-wrap: anywhere;
}
/* A pasted logo is whatever size it was in Outlook, which is regularly wider
   than a phone. Bounded here so the box cannot push the dialog sideways. */
#setup .signature-box img { max-width: 100%; height: auto; }
#setup .signature-box:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
#setup label .meta, #setup .field > .meta { display: block; margin-top: .2rem; }
#setup .row { display: flex; gap: .5rem; margin-top: 1rem; }
#s-theme-row, #s-zoom-row { margin-top: .35rem; align-items: center; }
/* The morning brief: a switch, an hour, and eight day buttons.
   `#setup input` is `display: block; width: 100%`, which is right for a name
   and wrong for a clock - a time box the width of the dialog reads as a text
   field somebody forgot to fill in. And the days have to wrap: eight buttons
   in a row that does not is Sunday off the side of a phone. */
#s-brief-row { align-items: center; margin-top: .35rem; }
#s-brief-time { display: inline-block; width: auto; margin-top: 0; }
#s-brief-days { flex-wrap: wrap; margin-top: .45rem; }
#s-brief-days button.quiet { padding: .35rem .6rem; }
/* Dimmed rather than removed when the brief is off. The days are still the
   answer somebody gave, and hiding them would mean turning it back on to find
   out what it was set to. */
#s-brief-days.off { opacity: .5; }
#s-zoom-now { min-width: 3.5rem; text-align: center; }
#brand-dialog label, #admin-main label { display: block; margin: .7rem 0; font-size: .88rem; }
#brand-dialog input, #admin-main > section input[type="text"],
#admin-main > .pane input[type="text"], #admin-main #b-name,
#admin-main #b-mark, #admin-main #b-accent {
  display: block; width: 100%; margin-top: .25rem; background: var(--bg);
  color: var(--text); border: 1px solid var(--line); border-radius: .5rem;
  padding: .45rem .55rem; font: inherit;
}
/* `#u-save-bar` is in this list because Save this person is deliberately not
   inside a pane any more - it writes fields in several of them and has to be
   reachable with all of them shut. Left out, it was the one button on the
   console rendered as the browser's own grey default, which reads as a control
   that does not work. */
#brand-dialog .row, #admin-main section .row, #admin-main .pane .row,
#admin-main #u-save-bar .row {
  display: flex; gap: .5rem; margin-top: 1rem;
}
#brand-dialog .row button, #admin-main section .row button,
#admin-main .pane .row button, #admin-main #u-save-bar .row button {
  border: 1px solid var(--accent); background: var(--accent); color: var(--panel);
  border-radius: .5rem; padding: .45rem 1rem; font: inherit; cursor: pointer;
}
/* Presentation design: five colour wells in a row, the text fields as wide as
   Branding's, and two small slides drawn from what is set. Every size in the
   preview is a share of the slide's width, so it reads the same at any width. */
#deck-design input:not([type]) {
  display: block; width: 100%; margin-top: .25rem; background: var(--bg);
  color: var(--text); border: 1px solid var(--line); border-radius: .5rem;
  padding: .45rem .55rem; font: inherit;
}
#deck-design select {
  display: block; width: 100%; margin-top: .25rem; background: var(--bg);
  color: var(--text); border: 1px solid var(--line); border-radius: .5rem;
  padding: .45rem .55rem; font: inherit;
}
#deck-design label.check { display: flex; gap: .5rem; align-items: center; }
#deck-design .dd-grid { display: flex; flex-wrap: wrap; gap: .25rem 1.2rem; }
#deck-design .dd-grid label { margin: .5rem 0; }
#deck-design input[type="color"] {
  display: block; width: 3rem; height: 2rem; margin-top: .25rem; padding: 0;
  border: 1px solid var(--line); border-radius: .35rem; background: none; cursor: pointer;
}
#deck-design input[type="file"] { display: block; margin-top: .35rem; font: inherit; }
.dd-preview { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem; }
@media (max-width: 640px) { .dd-preview { grid-template-columns: 1fr; } }
.dd-slide {
  position: relative; aspect-ratio: 16 / 9; overflow: hidden; container-type: inline-size;
  border: 1px solid var(--line); border-radius: .35rem;
}
.dd-band { position: absolute; left: 0; top: 0; bottom: 0; width: 2.4cqw; }
.dd-cover-logo { position: absolute; left: 9cqw; top: 8cqw; max-width: 22cqw; max-height: 8cqw; }
.dd-title {
  position: absolute; left: 9cqw; right: 6cqw; top: 25cqw; font-size: 5cqw; font-weight: 700;
  line-height: 1.1;
}
#dd-preview-cover .dd-rule { position: absolute; left: 9cqw; top: 33cqw; }
.dd-rule { width: 10cqw; height: .7cqw; }
.dd-sub { position: absolute; left: 9cqw; top: 36cqw; font-size: 2.2cqw; opacity: .75; }
.dd-heading {
  margin: 4.5cqw 5.6cqw 0; font-size: 3.6cqw; font-weight: 700; line-height: 1.1;
}
#dd-preview-content .dd-rule { margin: 1.2cqw 5.6cqw 0; }
.dd-slide ul { margin: 2.6cqw 5.6cqw 0 8cqw; padding: 0; font-size: 2.4cqw; line-height: 1.5; }
.dd-slide li::marker { color: var(--dd-marker); }
.dd-foot-logo {
  position: absolute; left: 5.6cqw; bottom: 3cqw; max-width: 10cqw; max-height: 3cqw;
}
.dd-foot {
  position: absolute; left: 18cqw; right: 18cqw; bottom: 3cqw; text-align: center;
  font-size: 1.4cqw; opacity: .7;
}
.dd-number { position: absolute; right: 5.6cqw; bottom: 3cqw; font-size: 1.4cqw; opacity: .6; }
.dd-mark {
  position: absolute; right: 2.2cqw; bottom: 2cqw; max-width: 27cqw; max-height: 17cqw;
  opacity: .11; pointer-events: none;
}
/* The banner layout, at the same proportions as the slides it builds. */
.dd-banner .dd-band { right: 0; bottom: auto; width: auto; height: 7.5cqw; }
.dd-banner .dd-cover-logo {
  left: 0; right: 0; margin: 0 auto; top: 9.4cqw; max-width: 48cqw; max-height: 24cqw;
}
.dd-banner .dd-title { left: 4cqw; right: 4cqw; top: 35cqw; text-align: center; font-size: 5.4cqw; }
.dd-banner .dd-sub { left: 4cqw; right: 4cqw; top: 43cqw; text-align: center; opacity: 1; }
.dd-head-logo { position: absolute; left: 3.75cqw; top: 1.6cqw; max-width: 12cqw; max-height: 6.4cqw; }
.dd-header-line { position: absolute; left: 17cqw; right: 0; top: 4.8cqw; height: .38cqw; }
.dd-header-line.dd-full { left: 0; }
.dd-header-stub { position: absolute; left: 0; width: 3.2cqw; top: 4.8cqw; height: .38cqw; }
.dd-banner .dd-heading { margin: 9cqw 3.75cqw 0; }
.dd-banner ul { margin-left: 6cqw; }
/* The quiet layout: no band and no rules, a dot of the accent over a title
   set low on the title slide. */
.dd-dot { position: absolute; left: 7cqw; top: 26cqw; width: 1.6cqw; height: 1.6cqw;
  border-radius: 50%; }
.dd-quiet .dd-cover-logo { left: 7cqw; top: 6cqw; }
.dd-quiet .dd-title { left: 7cqw; top: 29.5cqw; font-size: 5.6cqw; }
.dd-quiet .dd-sub { left: 7cqw; top: 38cqw; }
.dd-quiet .dd-heading { margin: 5.5cqw 7cqw 0; font-size: 4cqw; }
.dd-quiet ul { margin-left: 9.4cqw; }
.dd-quiet .dd-foot-logo { left: 7cqw; }
.dd-quiet .dd-number { right: 7cqw; }
#dd-people { display: flex; flex-direction: column; gap: .25rem; margin-bottom: .5rem; }
#s-places { display: flex; flex-direction: column; gap: .25rem; }
.place {
  text-align: left; background: var(--bg); color: var(--text); cursor: pointer;
  border: 1px solid var(--line); border-radius: .5rem; padding: .35rem .55rem;
  font: inherit; font-size: .85rem;
}
.compose-row { display: flex; width: 100%; }
#attach, #camera, #mic {
  background: var(--panel); color: var(--text); border: 1px solid var(--line);
  border-radius: .6rem; padding: .35rem .7rem; font: inherit; font-size: 1.1rem;
  line-height: 1.3; cursor: pointer; flex: none;
}
/* The gate. Covers everything, and is not dismissible - it is what stands
   between somebody on the network and a mailbox. */
#gate {
  position: fixed; inset: 0; z-index: 10; display: flex;
  align-items: center; justify-content: center; padding: 1rem;
  background: var(--bg);
}
#gate[hidden] { display: none; }
.gate-card {
  max-width: 26rem; width: 100%; text-align: center;
  border: 1px solid var(--line); border-radius: .8rem;
  background: var(--panel); padding: 1.5rem 1.2rem;
}
.gate-card h2 { margin: 0 0 .5rem; font-size: 1.1rem; }
.gate-card button {
  margin-top: 1rem; width: 100%; border: 1px solid var(--accent);
  background: var(--accent); color: var(--panel); border-radius: .5rem;
  padding: .6rem 1rem; font: inherit; cursor: pointer;
}
.gate-card button:disabled { opacity: .6; cursor: default; }
/* Quieter than the passkey button, and deliberately so: the ordinary way in
   is the passkey, and an invitation is what a minority of arrivals hold. */
.gate-card .gate-secondary {
  width: auto; margin-top: .8rem; padding: .2rem; border: 0;
  background: none; color: var(--accent); font-size: .95rem;
  text-decoration: underline;
}
#gate-code {
  width: 100%; box-sizing: border-box; margin-top: 1rem;
  border: 1px solid var(--line); border-radius: .5rem;
  background: var(--bg); color: inherit; padding: .6rem 1rem;
  font: inherit; text-align: center; letter-spacing: .08em;
}
#gate-error:not(:empty) { margin-top: .8rem; color: var(--warn); }
/* The console. A page you scan, not a sequence of screens. */
body.console { overflow: auto; height: auto; min-height: 100dvh; }
body.console header { position: sticky; top: 0; background: var(--bg); z-index: 2; }
body.console header a {
  color: var(--accent); text-decoration: none; margin-left: auto; font-size: .95rem;
}

#admin-main section, #admin-main .pane {
  border: 1px solid var(--line); border-radius: .8rem; background: var(--panel);
  padding: 1.1rem; margin: 1rem 0;
}
#admin-main section h2, #admin-main .pane h2 { margin: 0 0 .3rem; font-size: 1.05rem; }
/* A console pane you open, rather than one you scroll past.

   Both sides of this screen had grown past a screenful - a person is seven
   panes and the deployment is ten, most of them a paragraph and a form - so
   each is folded and the heading carries a line saying what is behind it.
   Folded, the console is a contents page; open, it is what it always was.

   `.fold` in the app says the same thing about a list inside a window. Kept
   separate on purpose: that one is a heading over a list and this one is the
   whole card, with a border and a background of its own. */
#admin-main .pane > summary {
  cursor: pointer; display: flex; gap: .5rem; align-items: baseline;
  /* Both halves: Safari draws the triangle through `::-webkit-details-marker`
     and everyone else through the list marker, and one without the other
     leaves a stray arrow beside the one this rule draws. */
  list-style: none;
}
#admin-main .pane > summary::-webkit-details-marker { display: none; }
/* Something has to say it opens, or a fold reads as a dead heading. */
#admin-main .pane > summary::after {
  content: "▸"; margin-left: auto; color: var(--dim); flex: none;
  transition: transform .12s ease;
}
#admin-main .pane[open] > summary::after { transform: rotate(90deg); }
/* Shut, the heading is the whole card and its bottom margin is a gap under
   nothing. Open, it is a heading again. */
#admin-main .pane > summary > h2 { margin: 0; min-width: 0; }
#admin-main .pane[open] > summary > h2 { margin-bottom: .3rem; }
/* The summary is the control now, so focus has to be visible on it - the
   default outline lands on a row with no border of its own. */
#admin-main .pane > summary:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px; border-radius: .5rem;
}
/* What is behind the fold, in a few words. Lighter than the heading because
   it is the second thing read, and gone on a narrow screen rather than
   wrapping every title onto three lines. */
.pane-hint { font-weight: 400; font-size: .82rem; color: var(--dim); }
/* On a narrow screen the hint goes under the heading rather than beside it.
   It used to go away entirely, which was defensible while one fold on each of
   these screens opened by itself: the hint was a nicety and the open section
   was the orientation. Now that every fold arrives shut, the hint *is* the
   page - a shut list of seven headings with nothing saying what is behind them
   is a worse index than the paragraphs it replaced, and a phone is where these
   screens are read. So it wraps instead.

   Two rules, because the two screens nest it differently. On the console the
   hint is inside the `<h2>`, so making it a block is the whole of it. In the
   app it is a sibling of the heading text inside a flex `<summary>`, where
   `display: block` does nothing at all - a flex item is blockified already -
   and the fix is to let the line wrap and give the hint the whole of the next
   one. `order` keeps the ▸ up on the first line with the title; without it the
   arrow follows the full-width hint onto a third. */
@media (max-width: 34rem) {
  #admin-main .pane-hint { display: block; margin-top: .15rem; }
  .fold > summary { flex-wrap: wrap; }
  .fold > summary > .pane-hint { flex-basis: 100%; order: 1; margin-top: .1rem; }
  .fold > summary::after { order: 0; }
}
#admin-main input, #admin-main textarea, #admin-main select {
  font-size: max(16px, 1rem);
  /* Full width. An input sized by its `size` attribute truncates the
     placeholder mid-word, which reads as the field being broken. */
  display: block; width: 100%; margin-top: .25rem;
  padding: .45rem .6rem; border-radius: .5rem;
  background: var(--bg); color: var(--text); border: 1px solid var(--line);
}
#admin-main label { display: block; margin: .8rem 0; font-size: .9rem; }
/* A tickbox is not a text field, and the rule above is written for text
   fields: `display: block; width: 100%` turned every checkbox into a
   full-width bar floating above its own label. Scoped by id so it outranks
   it - this stylesheet is id-heavy and a class-level rule loses silently. */
#admin-main input[type="checkbox"] {
  display: inline-block; width: auto; margin: 0; flex: none;
}
/* The console's panes rely on the global `[hidden]` rule near the top of this
   file. There used to be a copy of it here for `#admin-main section` alone,
   written when this looked like a console problem; it turned out to be a
   whole-stylesheet problem and moved. */
#admin-main .row { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
/* A secondary action beside a primary one. Without this every button in a
   row was accent-filled, so "Save" and "Connect Microsoft…" competed for the
   same glance and neither read as the main thing. */
/* A field with something stored behind it. The dots are in the placeholder,
   so they vanish the moment somebody types - which is the whole difference
   between "there is a value here" and "here is the value". */
#admin-main input.is-set::placeholder { color: var(--text); opacity: .75; letter-spacing: .05em; }
/* The dot beside an integration's name. Same three colours as the chat's
   connection dot, because it means the same thing. */
.tier summary > .dot { margin-right: .5rem; flex: none; }
/* A precondition that lives somewhere else - a portal toggle, a DNS record.
   Set apart because reading it after filling the form is reading it after
   the mistake. */
.footnote {
  border-left: 2px solid var(--warn); padding: .3rem 0 .3rem .6rem;
  margin: .5rem 0;
}
#admin-main .row button.quiet {
  background: none; color: var(--dim); border: 1px solid var(--line);
}
/* Which of a row of choices is the one in force - the Appearance buttons, and
   anything else on this screen that becomes a set of three. The same three
   signals the app's version uses, and for the same reason: a border one shade
   lighter is not a difference anybody sees on a phone in daylight. Written
   here as well as there because that rule is scoped to `dialog`, and this
   screen is not one - left out, all three buttons looked identical and nothing
   said which theme was on. */
#admin-main .row button.quiet.on {
  color: var(--accent); border-color: var(--accent); font-weight: 600;
  background: color-mix(in srgb, var(--accent) 18%, transparent);
}
/* The two controls on this screen that take something away. `.quiet` above
   greys every secondary button and outranks `button.danger` on its own, so
   they came out looking exactly like Cancel - which is the one thing they must
   not look like. Scoped by id to win it back. */
#admin-main #remove-box .row button.danger,
#admin-main #pending-person .row button.danger {
  color: #f85149; border-color: #f85149;
}
/* The deployment description is prose, so it gets the width of the panel and
   room to grow rather than a one-line box somebody scrolls sideways in. */
#d-text {
  width: 100%; box-sizing: border-box; resize: vertical; margin: .4rem 0;
  padding: .5rem; border: 1px solid var(--line); border-radius: .5rem;
  background: var(--bg); color: var(--fg); font: inherit; line-height: 1.45;
}
/* Over the limit the server refuses, so say so while there is still time to
   shorten it rather than after the save. */
#d-count.over { color: var(--warn); }
#who { margin-left: .6rem; }
/* Which person is on screen. Sticky and accented, because everything below it
   is scoped to it - reading a setting while looking at the wrong instance is
   the mistake this bar exists to prevent. */
.instance-bar {
  position: sticky; top: 0; z-index: 1; align-items: center; gap: .6rem;
  background: var(--panel); border: 1px solid var(--accent); border-radius: .6rem;
  padding: .6rem .8rem; margin-bottom: 1rem; flex-wrap: wrap;
}
.instance-bar[hidden] { display: none; }
.instance-bar select { font-size: 1rem; padding: .3rem .5rem; border-radius: .4rem;
  background: var(--bg); color: var(--text); border: 1px solid var(--line); }
.all-instances[hidden] { display: none; }
header a#admin {
  color: var(--accent); text-decoration: none; padding: .35rem .5rem;
  /* Chrome, like the buttons beside it. Left inheriting, it was the
     tallest thing in the header and set the header's height on its own. */
  font-size: .95rem; line-height: 1.4;
}
header a#admin[hidden] { display: none; }
#facts, #waiting, #chat-list { display: flex; flex-direction: column; }
.fact.current { opacity: .6; }
/* Armed, on the second tap. Red only while it means it. */
button.danger { border-color: #f85149; color: #f85149; }
/* Not a button: the one line that says a stored value cannot be read. */
p.danger { color: #f85149; font-weight: 600; }

/* The members of a family - the cameras, the repositories. One row each,
   because a comma-separated sentence can be added to and not edited. */
.member-list { margin: 0 0 0.75rem; }
.member-row { align-items: baseline; gap: 0.5rem; padding: 0.25rem 0; }
.member-row .meta { flex: 1; }
#chat-list[data-error]::after, #pin-list[data-error]::after {
  content: attr(data-error); display: block; color: var(--warn); font-size: .82em;
  padding-top: .4rem;
}
#waiting:not(:empty) { margin-bottom: .8rem; }
#waiting h3 { margin: .2rem 0 .4rem; font-size: .9rem; color: var(--warn); }
/* The two lists in the memory window, each behind its own heading. Folded,
   the window is two lines and a count; unfolded, it is the list somebody
   came for. `.tier` says the same thing for the model picker - kept separate
   because that one is a row of controls and this is a heading over a list. */
.fold { border-top: 1px solid var(--line); }
.fold[hidden] { display: none; }
.fold > summary {
  cursor: pointer; display: flex; gap: .4rem; align-items: baseline;
  list-style: none; padding: .55rem 0; font-size: .92rem; font-weight: 600;
}
.fold > summary::-webkit-details-marker { display: none; }
/* Something has to say the list opens, or a fold reads as a dead heading. */
.fold > summary::after {
  content: "▸"; margin-left: auto; color: var(--dim); flex: none;
  transition: transform .12s ease;
}
.fold[open] > summary::after { transform: rotate(90deg); }
/* The You window's sections, once opened, sit on a faint tint of their own,
   so the part being read is told apart from the headings still shut around
   it. Pulled out to the window's edges by the padding it adds, so the heading
   text does not jump sideways when it opens. */
/* Three sizes in the You window, one per level, so where you are is readable
   from the type alone: a section heading, the accounts inside one, and the
   grey line explaining a field - smallest, and the same size wherever it sits.
   It was `em`, so it came out at three different sizes depending on what it
   was nested in, and two of the levels above it were the same .92rem. */
#setup .setup-fold > summary { font-size: 1.02rem; }
#setup .meta { font-size: .76rem; }
#setup .setup-fold[open] {
  background: color-mix(in srgb, var(--accent) var(--fold-mix), var(--panel));
  border-top-color: transparent;
  border-radius: .6rem;
  margin: .3rem -.7rem;
  padding: 0 .7rem .7rem;
}
.fold .count { font-weight: 400; font-size: .85em; color: var(--dim); }
.fold > .meta { margin: 0 0 .4rem; }
.fact {
  display: grid; grid-template-columns: 1fr auto; gap: 0 .5rem;
  border-top: 1px solid var(--line); padding: .5rem 0; font-size: .88rem;
}
.fact .meta { grid-column: 1; }
/* Where it sits in the row. How it looks is the window block above. */
.fact button {
  grid-column: 2; grid-row: 1 / 3; align-self: center;
  border-radius: .5rem; font: inherit; cursor: pointer;
}
.fact button:disabled { opacity: .5; cursor: default; }
/* Chat rows carry two buttons - Open and Delete - so they need a column each.
   The memory rows keep the single-button layout above. */
#chat-list .fact { grid-template-columns: 1fr auto auto; }
#chat-list .fact button:nth-of-type(1) { grid-column: 2; }
#chat-list .fact button:nth-of-type(2) { grid-column: 3; }
/* `-1`, not `3`: a search result has a third line - the quote - and buttons
   pinned to two rows would sit above it rather than beside the row. */
#chat-list .fact button { grid-row: 1 / -1; }
/* The line of the conversation that matched. Quieter than the title and
   wrapped, because the point of it is the words, not the layout. */
#chat-list .snippet {
  grid-column: 1; color: var(--dim); font-size: .84rem; padding: .15rem 0;
  overflow-wrap: anywhere;
}
#chat-list .snippet mark {
  background: none; color: var(--accent); font-weight: 600;
}
#chat-search, #pin-search {
  width: 100%; margin: .2rem 0 .4rem; padding: .45rem .6rem;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--line); border-radius: .5rem; font: inherit;
}

/* ------------------------------------------------------------------- pinned
   The pin beside an answer, and the board it fills.

   The button is the quietest control in the conversation and has to stay that
   way: it sits next to every reply, so at full contrast the transcript would
   read as a column of buttons with some prose between them. It comes up to
   full strength on hover, on focus and once it has been used - the last is
   what makes "did that take?" answerable at a glance. */
.pin {
  flex: none; align-self: flex-start; margin-top: .15rem;
  background: none; border: 0; padding: .15rem; cursor: pointer;
  color: var(--dim); opacity: .45; line-height: 0;
  border-radius: .4rem;
}
.pin:hover, .pin:focus-visible { opacity: 1; color: var(--text); }
.pin.on { opacity: 1; color: var(--accent); }
.pin.danger { opacity: 1; color: #f85149; }
.pin:disabled { cursor: default; }
/* The speaker sits under the pin, in the same margin and with the same
   restraint: two marks in the gutter of an answer is the most this interface
   will spend on controls nobody uses most of the time. */
.pin.speak { margin-top: 0; }
.pin.speak.on { color: var(--accent); opacity: 1; }
/* Synthesising, with nothing to hear yet - about three seconds for the first
   sentence. The button is the indicator, exactly as the microphone's note
   above says: a light somewhere else is a thing to miss while you are looking
   at the thing you pressed.
   Dim rather than accent, and pulsing, so it reads as *working* rather than as
   *playing* - those are different states and the second one has sound to tell
   it apart, which this one does not. */
.pin.speak.busy { opacity: 1; color: var(--dim); }
@media (prefers-reduced-motion: no-preference) {
  .pin.speak.busy { animation: pulse 1.2s ease-in-out infinite; }
}
.turn .msg:empty ~ .pin.speak { visibility: hidden; }
/* Share, under the speaker - on a phone only (app.js, `sharesOut`), where
   there is no comfortable way to select an answer and carry it elsewhere. */
.pin.share { margin-top: 0; }
.turn .msg:empty ~ .pin.share { visibility: hidden; }

/* Nothing to keep yet. The bubble is empty for as long as the model takes to
   say its first word, and a pin on nothing is a tap that does nothing. */
.turn .msg:empty + .pin { visibility: hidden; }
/* In a notice card it is a control among controls rather than a mark in the
   margin, so it sits on the row with "Got it" and is not dimmed away. */
.card .row .pin { opacity: .8; align-self: center; margin: 0; padding: .3rem; }

/* One saved answer. `.fact` gives it the row, the rule above it and the
   buttons on the right; what is added here is that the body of a pin is a
   whole rendered reply rather than a line of text. */
.pin-card { grid-template-columns: 1fr auto auto; }
.pin-card > details { grid-column: 1; min-width: 0; }
.pin-card > details > summary {
  cursor: pointer; font-weight: 600; overflow-wrap: anywhere;
}
/* The reply itself, once opened. Indented to the summary's text so the card
   reads as one thing, and scrolling inside for the same reason the chat
   bubble does - a code fence must not widen the window. */
.pin-card .msg {
  margin: .35rem 0 .2rem; font-weight: normal; font-size: .95em;
  max-width: 100%; min-width: 0;
}
.pin-card .meta { grid-column: 1; }
.pin-card button { grid-row: 1 / -1; align-self: center; }
.pin-card button:nth-of-type(1) { grid-column: 2; }
.pin-card button:nth-of-type(2) { grid-column: 3; }
/* Your own line about why you kept it. Written as a field rather than as text
   with an edit button: there is one value, it is one line, and a note nobody
   can see the shape of is a note nobody writes. */
.pin-note {
  grid-column: 1; margin: .2rem 0 0; padding: .3rem .45rem; width: 100%;
  background: var(--bg); color: var(--text); font: inherit; font-size: .84rem;
  border: 1px solid transparent; border-radius: .4rem;
}
.pin-note:hover, .pin-note:focus { border-color: var(--line); }
.pin-note.danger { border-color: #f85149; }
#pin-list { display: block; }
#pin-count:empty { display: none; }

/* -------------------------------------------------------------------- lists
   Named lists of your own, with items to check off. A card per list, folded
   like a pin - `.fact` gives it the row and the rule above it, overridden to
   block because there is one column here, not a summary beside two buttons -
   and the items inside an opened one are the Today drawer's own rows,
   `.today-item` and `.today-row`/`.today-tick` beside them, unchanged. */
#list-new { display: flex; gap: .4rem; margin: 0 0 .3rem; }
#list-new input {
  flex: 1; min-width: 0; background: var(--bg); color: var(--text);
  border: 1px solid var(--line); border-radius: .5rem; padding: .35rem .55rem;
  font-family: inherit; font-size: .95rem;
}
#list-new button { flex: none; }
#list-board { display: block; }
.list-card { display: block; }
.list-card > details > summary {
  cursor: pointer; font-weight: 600; overflow-wrap: anywhere;
}
.list-rename {
  display: block; margin: .3rem 0 0; padding: .3rem .45rem; width: 100%;
  background: var(--bg); color: var(--text); font: inherit; font-size: .95rem;
  font-weight: 600; border: 1px solid transparent; border-radius: .4rem;
}
.list-rename:hover, .list-rename:focus { border-color: var(--line); }
.list-rename.danger { border-color: #f85149; }
.list-items { display: flex; flex-direction: column; gap: .3rem; margin: .4rem 0; }
.list-item-add { display: flex; gap: .4rem; margin: .3rem 0; }
.list-item-add input {
  background: var(--bg); color: var(--text); border: 1px solid var(--line);
  border-radius: .5rem; padding: .3rem .5rem; font-family: inherit; font-size: .9rem;
}
.list-item-add input:not(.list-item-qty) { flex: 1; min-width: 0; }
.list-item-add .list-item-qty { flex: 0 0 4rem; }
.list-item-add button { flex: none; }
.list-card .row { display: flex; gap: .5rem; margin-top: .4rem; }

/* ------------------------------------------------------------------ trip
   The window somebody opens standing up, one-handed, in a queue. Everything
   here follows from that: the time is in a column of its own so the eye can
   run down it, the reference is monospaced because it is read out character by
   character, and what is over is dimmed rather than removed - "did I already
   land" is a question people ask.

   No colour carries meaning on its own. What is happening now is marked by a
   line down its left edge *and* by the words beside it, because a border is
   the one part of this a colour-blind reader in bright sun gets nothing from. */
/* The three sections of the travel window - Itinerary, Boarding Pass,
   Documents - are one level and look it. They were not: two were `<summary>`
   elements inheriting whatever a summary looks like, and Documents was an
   `<h3>`, so the same rank read as two different ranks and the eye had to work
   out that they belonged together. `h3.trip-heading` is what Documents used to
   be; this is that size, on all three.

   `details.sub` (further down this file) is the level beneath, and Add a
   document sits there - it is a thing you do *to* Documents, not a fourth
   section of the window. */
#trip details.trip-section > summary {
  cursor: pointer;
  margin: 1.2rem 0 .2rem;
  font-size: .95rem;
  font-weight: 600;
}
#trip details.trip-section > summary:hover { color: var(--accent); }
/* What is next, on the heading of the trip it belongs to. This is what pays
   for every section starting shut: the question this window is opened to
   answer is answered without opening anything. Quiet and after the dates,
   because the name and the dates are what somebody scans for. */
#trip details.trip-one > summary .trip-hint {
  font-weight: 400;
  margin-left: .5rem;
}
/* A trip is a section like the three inside it, with a line under it so the
   list reads as a list rather than as three headings in a row. */
#trip details.trip-one { border-top: 1px solid var(--line); }
#trip details.trip-one:first-of-type { border-top: 0; }
/* The trip's own sections sit inside it, so they step in. Without this the
   itinerary of the open trip lines up with the *names* of the shut ones, and
   nothing on screen says which belongs to which. */
#trip details.trip-one > details.trip-section { margin-left: .8rem; }
/* Cancel trip and Delete trip, stepped in with the sections above them. */
#trip .trip-actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  margin: .4rem 0 .8rem .8rem;
}
/* A cancelled trip stays on the list, and must not read as one still on. */
#trip details.trip-cancelled > summary { color: var(--dim); text-decoration: line-through; }
#trip details.trip-cancelled > summary .trip-hint { text-decoration: none; display: inline-block; }
/* Kept: nothing uses it today, and a heading in this window that is not one of
   the three sections should still have somewhere to land. */
#trip h3.trip-heading { margin: 1.2rem 0 .2rem; font-size: .95rem; }
#trip-pick { width: 100%; margin: .2rem 0 .5rem; }
#trip-head { margin-bottom: .6rem; }
.trip-next { margin: .2rem 0 .8rem; }
.trip-next > .meta { margin-bottom: .2rem; }
.trip-day {
  margin: .7rem 0 .2rem; font-variant-numeric: tabular-nums;
  border-bottom: 1px solid var(--line); padding-bottom: .15rem;
}
.trip-item {
  display: grid; grid-template-columns: 3.4rem 1fr auto; gap: .1rem .6rem;
  align-items: baseline; padding: .35rem 0 .35rem .5rem;
  border-left: 2px solid transparent;
}
.trip-when { color: var(--dim); font-variant-numeric: tabular-nums; font-size: .86em; }
.trip-body { min-width: 0; }
.trip-what { overflow-wrap: anywhere; }
.trip-state { white-space: nowrap; }
/* On now. The border and the word beside it say the same thing twice. */
.trip-now { border-left-color: var(--accent); }
/* Done. Dimmed rather than dropped: an itinerary that deletes what has
   happened cannot answer "which flight did I come in on". */
.trip-past { opacity: .55; }
.trip-actions {
  display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; margin-top: .25rem;
}
.trip-ref, .trip-value {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .9em; letter-spacing: .02em; overflow-wrap: anywhere;
}
.trip-value { margin-top: .1rem; }
.trip-warn { color: var(--warn); }
.trip-copyout {
  font: inherit; font-size: .9em; background: var(--bg); color: var(--text);
  border: 1px solid var(--line); border-radius: .4rem; padding: .2rem .4rem;
}
.trip-doc { grid-template-columns: 1fr; }
.trip-form { display: flex; flex-direction: column; gap: .4rem; margin-top: .5rem; }
.trip-form label { display: flex; flex-direction: column; gap: .15rem;
                   color: var(--dim); font-size: .82em; }
.trip-form input, .trip-form select {
  font: inherit; padding: .35rem .45rem; background: var(--bg); color: var(--text);
  border: 1px solid var(--line); border-radius: .4rem;
}
/* The code, big. `.qr` already forces the white plate and the size - see the
   long comment on it above, which was written for the tunnel's code and holds
   for exactly the same reason here: a QR code has to be dark on light, and on
   the dark theme an unpainted one is black on near-black. */
#boarding-code { display: flex; justify-content: center; }
#boarding-what:empty, #boarding-note:empty { display: none; }
#trip-pass-form textarea {
  font: inherit; font-size: .85em; padding: .35rem .45rem; resize: vertical;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--line); border-radius: .4rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.trip-note-line { margin: .3rem 0 .5rem; }
.trip-note-line:empty { display: none; }
#trip-all { margin-top: .4rem; }
#trip-all > summary { cursor: pointer; color: var(--dim); font-size: .86em; }
#trip-docs[data-error]::after { content: attr(data-error); color: var(--warn); }
#trip-foot:empty { display: none; }
/* A claim something else made must not read like something you told it -
   that distinction is the whole of the memory-poisoning defence, and a list
   that flattens it undoes the design at the moment somebody is judging it. */
.fact.untrusted { border-left: 2px solid var(--warn); padding-left: .5rem; }
/* A live microphone should be unmistakable from across the room. */
#mic.on { border-color: var(--accent); color: var(--accent); }
#mic-note:not(:empty) { margin-bottom: .35rem; }
#v-preview { width: 100%; max-height: 60vh; background: #000; border-radius: .5rem; }
/* Only the selfie camera, and only the preview. People expect a mirror of
   themselves; nobody expects a mirrored photo of the thing they pointed at,
   and the captured frame is never flipped either way. */
#v-preview.mirrored { transform: scaleX(-1); }
#shoot .row { display: flex; gap: .5rem; margin-top: .6rem; }
#shoot .row button {
  border: 1px solid var(--accent); background: var(--accent); color: var(--panel);
  border-radius: .5rem; padding: .45rem 1rem; font: inherit; cursor: pointer;
}
#shoot .row button.quiet { background: none; color: var(--dim); border-color: var(--line); }
#composer { flex-direction: column; align-items: stretch; }
#pending { display: flex; flex-wrap: wrap; gap: .35rem; }
#pending:not(:empty) { margin-bottom: .5rem; }
.chip {
  display: inline-flex; align-items: center; gap: .35rem; font-size: .8rem;
  background: var(--panel); border: 1px solid var(--line); border-radius: 1rem;
  padding: .2rem .3rem .2rem .7rem; max-width: 18rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chip.failed { border-color: #f85149; color: var(--dim); }
.chip button {
  background: none; border: 0; color: var(--dim); cursor: pointer;
  font-size: 1rem; line-height: 1; padding: 0 .25rem;
}
/* Drag feedback has to be on the whole window, because the drop target is. */
body.dropping::after {
  content: "drop to attach"; position: fixed; inset: 0; z-index: 50;
  display: grid; place-items: center; pointer-events: none;
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  outline: 2px dashed var(--accent); outline-offset: -1rem; color: var(--accent);
}
/* One line per tool. The list is sixty rows long and every extra line is
   another screenful between a tool and the one below it. */
.tool-row {
  border-top: 1px solid var(--line); padding: .4rem 0;
  display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap;
}
.tool-row code { font-size: .86rem; }
/* What it does, above what it is called. The sentence is the half somebody
   can decide about; the identifier is what turns up in the journal and in an
   error, so it stays rather than being hidden behind a nicer name. */
.tool-name { display: flex; flex-direction: column; gap: .1rem; min-width: 14rem; }
/* One line per tool, and one only. The row shows the first sentence and keeps
   the full description on its title, so a long one must not wrap the row back
   open - which is the shape this screen had before, ninety-five times over. */
.tool-said {
  font-size: .9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}
.tool-name code { color: var(--dim); }
.tool-facts { margin-right: auto; }

/* Grouped and shut. Sixty rows in one flat list is a screen people scroll
   past rather than read, and a closed heading with a count on it answers the
   question most visits ask without opening anything. */
.cap-band { margin: 1.2rem 0 .2rem; font-size: .95rem; }
.cap-band:first-child { margin-top: .4rem; }

/* The band heading above a run of collapsed groups. Enough air above it that
   it reads as a divider rather than as another group that failed to open. */
.tool-band { margin: 1.4rem 0 .3rem; }
.tool-band:first-child { margin-top: .6rem; }
.tool-band h4 { margin: 0; font-size: .95rem; }
.tool-band p { margin: .15rem 0 0; }

.tool-group { border-top: 1px solid var(--line); padding: .2rem 0; }
.tool-group > summary {
  cursor: pointer; padding: .5rem 0; display: flex; gap: .6rem;
  align-items: baseline; flex-wrap: wrap;
}
.tool-group > summary::marker { color: var(--dim); }
.tool-group .group-name { font-weight: 600; }
.tool-group .group-count { margin-left: auto; }
.tool-group .group-blurb { display: block; padding: 0 0 .5rem 1.1rem; }
.tool-group .tool-row { margin-left: 1.1rem; }
/* The line between what only looks and what changes something. Quiet: it is a
   divider, not a warning, and a group where everything reads has none at all. */
.tool-group .tool-split {
  margin: .45rem 0 .25rem 1.1rem;
  padding-top: .35rem;
  border-top: 1px dashed var(--line);
}

/* Who the personas are, before anybody ticks one. A caption got skipped, and
   the consequence is somebody granting a persona they cannot describe. */
.agent-legend { border-left: 2px solid var(--line); padding: .1rem 0 .1rem .7rem; margin: .4rem 0 .8rem; }
.agent-legend p { margin: .25rem 0; }
.agent-legend strong { color: var(--text); }
.tool-agents { display: flex; flex-wrap: wrap; gap: .8rem; flex: none; }
#admin-main .tool-agents label {
  display: inline-flex; align-items: center; gap: .3rem; margin: 0;
  font-size: .82rem; color: var(--dim); cursor: pointer;
}
/* A grant made here and one inherited from the file look identical in a
   checkbox, and the difference decides whether unticking it will stick. */
#admin-main .tool-agents label.overridden { color: var(--accent); font-weight: 600; }

/* Capabilities: the same grants one altitude up, so they get the room the
   tool rows do not - this is the list somebody actually reads. */
/* What still needs doing. One card per instance, one row per finding.
   The dot sits on the first line of its own row rather than centred, because
   a two-line detail would otherwise drag the marker into the middle of the
   sentence it is marking. */
.ready-card { border-top: 1px solid var(--line); padding: .7rem 0; }
.ready-card .ready-head { display: flex; align-items: center; gap: .5rem; }
.ready-row { display: flex; align-items: flex-start; gap: .5rem; padding: .35rem 0 .1rem; }
.ready-row > .dot { margin-top: .38rem; }
.ready-row .ready-label { font-weight: 600; white-space: nowrap; }
.ready-row .meta { display: block; }
@media (max-width: 40rem) {
  /* The label above its sentence rather than beside it: `nowrap` keeps the
     name whole, which on a phone would squeeze the half people are reading
     into a column three words wide. */
  .ready-row { flex-wrap: wrap; }
  .ready-row .meta { flex-basis: 100%; }
}

/* One shut heading per area, with the capabilities inside it. Thirty-nine
   rows in two bands was a screen people scrolled; ten headings carrying their
   own counts is one they can see the shape of. Same shape as `.tool-group`
   below the fold, deliberately: two panes about the same decisions that open
   differently is two things to learn. */
.cap-area { border-top: 1px solid var(--line); }
.cap-area > summary {
  cursor: pointer; padding: .55rem 0; display: flex; gap: .6rem;
  align-items: baseline; flex-wrap: wrap;
}
.cap-area > summary::marker { color: var(--dim); }
.cap-area .group-name { font-weight: 600; }
.cap-area .group-count { margin-left: auto; }
/* Indented under the heading that owns them, and the heading keeps the only
   rule above it - a border on both reads as a row of its own. */
.cap-area .cap-row { margin-left: 1.1rem; border-top: 1px solid var(--line); }
.cap-area .cap-row:first-of-type { border-top: none; }

/* One row per capability, with a tick per persona on it - rather than the
   same fifteen decisions listed twice, once per persona, which read as
   thirty things. */
.cap-row {
  border-top: 1px solid var(--line); padding: .6rem 0;
  display: flex; gap: 1rem; align-items: start; flex-wrap: wrap;
}
.cap-text { flex: 1 1 18rem; }
.cap-agents { display: flex; gap: 1rem; flex: none; padding-top: .1rem; }
#admin-main .cap-tick {
  display: inline-flex; align-items: center; gap: .35rem; margin: 0;
  cursor: pointer; font-size: .85rem; color: var(--dim);
}
#admin-main .cap-tick input[type="checkbox"] { width: 1.05rem; height: 1.05rem; }
.cap-tick input:disabled { cursor: default; }
.cap-name { display: block; font-size: .95rem; }
.cap-row .meta { display: block; margin-top: .1rem; }
@media (max-width: 40rem) {
  /* The ticks under the sentence rather than beside it. Two personas in a
     column three words wide is the layout that made the old screen unreadable
     on a phone. */
  .cap-agents { flex-basis: 100%; padding-left: 0; }
}
/* Half on. Rendered rather than rounded to one of the other two, because it
   is what you see after ticking a box that only partly applied. */
.cap-row.partial .cap-name { color: var(--accent); }
.cap-row.blocked .cap-name { color: var(--dim); }
/* Copy their ticks: one short line above the table, not three full-width
   fields. Id-scoped, because the field rule above is and a class loses. */
#admin-main #cap-copy { margin: .2rem 0 .6rem; }
#admin-main #cap-copy label { display: inline; margin: 0; }
#admin-main #cap-copy select { display: inline-block; width: auto; margin-top: 0; padding: .3rem .5rem; }
#admin-main #cap-copy button { padding: .3rem .7rem; font-size: .9rem; }

/* Workstations, one line each. It was seven blocks and two rows of
   full-width controls per computer, and three computers filled a page. */
#admin-main .ws-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: .1rem .7rem;
  padding: .3rem 0; border-top: 1px solid var(--line); font-size: .85rem;
}
#admin-main .ws-row:first-child { border-top: none; }
#admin-main .ws-row .meta { font-size: .8rem; }
#admin-main .ws-row .ws-waiting { color: var(--warn); }
#admin-main .ws-row .ws-actions { margin-left: auto; display: flex; gap: .35rem; align-items: center; }
#admin-main .ws-row .ws-own { flex-basis: 100%; }
#admin-main .ws-row button {
  padding: .1rem .5rem; font-size: .78rem; border-radius: .35rem;
}
#admin-main .ws-row button.quiet {
  background: none; color: var(--dim); border: 1px solid var(--line);
}
#admin-main .ws-row select {
  display: inline-block; width: auto; margin-top: 0; padding: .1rem .3rem; font-size: .78rem;
}
/* Refused at load, which is not the same as absent - it is one setting away
   from working, and the row says which. */
.cap-row.blocked .meta { color: var(--warn); }

/* A command to run on the host. Selectable in one go, because retyping it is
   how a hyphen goes missing. */
.command {
  background: var(--line); border-radius: .5rem; padding: .55rem .7rem;
  font-size: .9rem; user-select: all; overflow-x: auto; margin: .6rem 0;
}
/* Requested and not yet created. Not "down": there is nothing wrong with it,
   there is one command left, and the pane says which. */
.nav-item.waiting .nav-badge { border-style: dashed; color: var(--dim); }
#newapi label, #newperson label, #removeperson label {
  display: block; margin: .7rem 0; font-size: .88rem;
}
#newapi input, #newapi textarea, #newapi select,
#newperson input, #removeperson input {
  display: block; width: 100%; margin-top: .25rem; background: var(--bg);
  color: var(--text); border: 1px solid var(--line); border-radius: .5rem;
  padding: .45rem .55rem; font: inherit; resize: vertical;
}
#newapi label .meta, #newperson label .meta, #removeperson label .meta {
  display: block; margin-top: .2rem;
}
#newapi .pair { display: flex; gap: .7rem; }
#newapi .pair label { flex: 1; min-width: 0; }
#newapi .row, #newperson .row, #removeperson .row {
  display: flex; gap: .5rem; margin-top: 1rem;
}
#newapi .row button, #newperson .row button, #removeperson .row button {
  border: 1px solid var(--accent); background: var(--accent); color: var(--panel);
  border-radius: .5rem; padding: .45rem 1rem; font: inherit; cursor: pointer;
}
#newapi .row button.quiet, #newperson .row button.quiet,
#removeperson .row button.quiet {
  background: none; color: var(--dim); border-color: var(--line);
}
/* The button that takes somebody away is not the accent colour: the accent is
   what every Save in this console looks like, and these two are not the same
   act. Red, and outlined rather than filled - loud enough to read as different
   without being the brightest thing on the screen. */
#removeperson .row button.danger {
  background: none; color: #f85149; border-color: #f85149;
}
/* The tickbox that decides between stopping somebody and erasing them. The
   rule above is written for text fields, and it turns a checkbox into a
   full-width bar floating above its own label - same trap as `#admin-main`
   further up, same fix. */
#removeperson label.check {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .4rem;
}
#removeperson label.check input {
  display: inline-block; width: auto; margin: 0; flex: none;
}
#removeperson label.check .meta { flex-basis: 100%; margin-top: .2rem; }
#a-preview {
  white-space: pre-wrap; overflow-x: auto; margin-top: .6rem;
  font-size: .74rem; max-height: 12rem; overflow-y: auto;
}
/* Shape here, colour with the window block above - where it can outrank
   `.quiet`, which every one of these also carries. */
button.wide {
  display: block; width: 100%; margin-top: 1rem; cursor: pointer; font: inherit;
  border-radius: .5rem; padding: .5rem;
}
/* A device permission that is on should look on. Solid rather than dashed,
   because "am I sharing my location right now" deserves a glance, not a tap. */


/* ------------------------------------------------------------ the console

   Two panes: who on the left, their settings on the right.

   A list rather than a dropdown, because the point of an admin screen is
   seeing everyone at once - a picker shows one name and hides the fact that
   somebody's core is down. The shape is the one every mail client settled on
   for the same reason. */
#admin-shell { display: flex; align-items: stretch; flex: 1; min-height: 0; }

#admin-nav {
  flex: none; width: 16rem; overflow-y: auto;
  background: var(--panel); border-right: 1px solid var(--line);
  padding: .9rem .6rem 2rem;
}
.nav-group + .nav-group { margin-top: 1.5rem; }
.nav-group h3 {
  margin: 0 0 .45rem .6rem; font-size: .7rem; letter-spacing: .08em;
  text-transform: uppercase; color: var(--dim); font-weight: 700;
}

/* A row, not a chip. Full width, flat until you touch it, and the selected one
   carries the accent - the three states a list like this needs and no more. */
.nav-item {
  display: flex; align-items: center; gap: .55rem;
  width: 100%; text-align: left; background: none; color: var(--text);
  border: 0; border-left: 3px solid transparent;
  border-radius: 0 .45rem .45rem 0;
  padding: .5rem .7rem; margin-bottom: .1rem;
  font: inherit; font-size: .95rem; line-height: 1.3; cursor: pointer;
}
.nav-item:hover:not(:disabled) { background: var(--line); }
.nav-item.on {
  background: var(--line); border-left-color: var(--accent); font-weight: 600;
}
.nav-item:disabled { cursor: default; }
.nav-item.down .nav-name { color: var(--dim); }

/* Initials in a circle, as the chat does for the same person. It is the
   fastest thing in the row to recognise and it costs nothing to draw. */
.nav-badge {
  flex: none; width: 1.9rem; height: 1.9rem; border-radius: 50%;
  display: grid; place-items: center; font-size: .8rem; font-weight: 600;
  background: var(--bg); border: 1px solid var(--line); color: var(--dim);
}
.nav-item.on .nav-badge { border-color: var(--accent); color: var(--accent); }
.nav-text { min-width: 0; }
.nav-name { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The second line says the instance name, or why it is not answering - a
   stopped container must not read as a person with no settings. */
.nav-item .meta { display: block; font-size: .76rem; line-height: 1.25; }

.nav-add {
  color: var(--accent); font-size: .9rem; padding-left: .95rem;
}

#admin-main {
  flex: 1; min-width: 0; max-width: 46rem; overflow-y: auto;
  padding: 1.2rem 1.4rem 3rem; display: block;
}
#pane-title { margin: 0 0 .2rem; font-size: 1.35rem; }
.invite-code {
  font-size: 1.2rem; letter-spacing: .12em; padding: .3rem .6rem;
  border: 1px dashed var(--accent); border-radius: .5rem; user-select: all;
}
.invite-code:empty { display: none; }
.invite-row { align-items: center; gap: .6rem; margin: .4rem 0; }
.invite-row .meta { margin-right: auto; }

/* A line that has stopped being a footnote. Used when the build line finds the
   two halves of the deployment on different images - at which point it is the
   most important sentence on the screen, not the smallest. */
.warn-line { color: var(--warn); font-weight: 600; }

/* Connecting your own account, in your own setup dialog (§10.8: each person
   consents for themselves). Grid rather than a flex row because the middle
   cell is a sentence that wraps and the button must not be dragged around by
   how long it happens to be. */
.setup-heading { margin: 1.4rem 0 .3rem; font-size: 1rem; }

/* The You page, folded by topic.

   `.fold` already draws a heading over content inside a window, which is what
   these are - not the console's `.pane`, which is a card with a border and a
   background, and inside this dialog would be a panel within a panel.

   So all this adds is the spacing a whole settings section needs: room under
   the summary while it is open and none while it is shut, which is what makes
   the closed page read as a contents page rather than seven crowded lines. */
#setup .setup-fold > summary { font-size: .95rem; padding: .7rem 0; }
#setup .setup-fold[open] { padding-bottom: .7rem; }
/* The first control sits directly under the heading, and its own top margin
   would otherwise double that gap. */
#setup .setup-fold[open] > label:first-of-type,
#setup .setup-fold[open] > .field:first-of-type { margin-top: .3rem; }
/* The summary is the control now, so focus has to be visible on it - the same
   thing the console's panes needed, for the same reason. */
#setup .setup-fold > summary:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; border-radius: .4rem;
}
/* One stacked block, at every width, and that is not a phone compromise.
   It was `auto 1fr auto` - name, sentence, status - which reads as a tidy
   three-column row and is not one. Both outer columns are `auto`, so they take
   their full natural text width and the sentence gets whatever is left: with
   "Show it how" one side and "Nothing recorded yet." the other, that is about
   140px inside a 34rem window, and the paragraph came out two words per line.
   Widening the window does not fix it, because `auto` columns simply take more -
   the sentence is always last in the queue.
   Three columns of prose need a width these windows never have. So the name is
   a heading, the sentence is a paragraph, and the value is its own line. */
.connect-row {
  display: grid;
  gap: .25rem;
  border-top: 1px solid var(--line);
  padding: .7rem 0;
}
.connect-row > strong { font-size: .95rem; }
/* Each account in Your accounts, shut until it is wanted. Seventeen of them
   open at once was a page of instructions nobody could find their way down;
   shut, it is a list of names. A `details` rather than the grid above, which
   a `details` cannot be everywhere - so the grid's gap is margins here. */
details.connect-row { display: block; padding: 0; }
.sub-fold > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: baseline; gap: .5rem;
  padding: .55rem 0;
}
/* Set in from the section it belongs to, by the same amount open or shut, so
   the names line up whichever state each is in. */
details.sub-fold { padding-left: .8rem; }
.sub-fold > summary::-webkit-details-marker { display: none; }
.sub-fold > summary::after {
  content: "▸"; margin-left: auto; color: var(--dim); flex: none;
  transition: transform .12s ease;
}
.sub-fold[open] > summary::after { transform: rotate(90deg); }
.sub-fold > summary > strong { font-size: .86rem; font-weight: 600; }
.sub-fold > :not(summary) { margin-top: .35rem; }
/* Open, it takes the window's own colour inside the tinted section - a
   second step of the same idea, so which account is open reads at a glance. */
.sub-fold[open] {
  background: var(--panel);
  border-top-color: transparent;
  border-radius: .5rem;
  margin: .3rem -.6rem .3rem .2rem;
  padding: 0 .6rem .7rem .6rem;
}
/* Whether an account is already set up, readable while it is shut. Read off
   the row's own Remove or Disconnect button, which each row shows only once
   something is stored - so this cannot say "set up" about a row the row
   itself would not offer to undo. */
.sub-state { font-size: .76rem; color: var(--dim); font-weight: 400; }
.sub-fold:has([id$="-forget"]:not([hidden])) > summary > .sub-state::before {
  content: "set up";
}
.connect-row .row { align-items: center; gap: .6rem; flex-wrap: wrap; }
.connect-row p { margin: .15rem 0 0; }
/* Room between the sentence and whatever acts on it, so a button does not sit
   against the text it belongs to. */
.connect-row > div, .connect-row > button { margin-top: .35rem; }
/* Full width while the window is narrow: a tap target sharing a line is the
   one that gets missed. Wide enough and it takes its own size, on the left
   where the reading is. */
.connect-row button { justify-self: stretch; }
@container panel (min-width: 26rem) {
  .connect-row button { justify-self: start; }
}

/* On a phone the panes stack: the nav is short and the settings are long. */
@media (max-width: 46rem) {
  #admin-shell { flex-direction: column; overflow-y: auto; }
  #admin-nav {
    width: 100%; overflow: visible;
    border-right: 0; border-bottom: 1px solid var(--line);
  }
  #admin-main { overflow: visible; padding: 1rem; }
}

/* The steps of one turn, folded. A turn that reads a mailbox, opens two
   threads and checks a diary used to put five lines of machinery above a
   two-line answer - on a phone the answer was off the bottom of the screen by
   the time it arrived. */
.steps { margin: 0.15rem 0 0.35rem; }
.steps > summary {
  cursor: pointer;
  color: var(--dim);
  font-size: 0.85em;
  list-style: none;
}
.steps > summary::-webkit-details-marker { display: none; }
.steps > summary::before { content: '▸ '; }
.steps[open] > summary::before { content: '▾ '; }
.steps .tool { padding-left: 1rem; }
/* Stuck on a failed call: the folded line turns red rather than the group
   opening, and goes back to grey the moment the turn does anything else. */
.steps.stuck > summary { color: #f85149; }
.steps .tool.failed { color: #f85149; }

/* Working. Green, because green is already what the header dot says while a
   turn runs, and one colour for one meaning is what lets the eye skip the
   reading. Three places, each where somebody might be looking: the assistant's
   face beside the answer that is coming, the line saying what it is doing, and
   the dot. Without motion the face keeps a green ring, so the answer to "is it
   doing anything" does not depend on an animation being allowed. */
.turn.working .avatar.assistant { border-color: #3fb950; }
/* How long the current step has been running, once that is worth reading -
   see `busy` in app.js. */
.steps.live > summary[data-elapsed]::after {
  content: ' ' attr(data-elapsed);
  font-variant-numeric: tabular-nums;
}
@media (prefers-reduced-motion: no-preference) {
  .turn.working .avatar.assistant { animation: working 1.6s ease-out infinite; }
  #status.live { animation: working 1.6s ease-out infinite; }
  .steps.live > summary { animation: pulse 1.4s ease-in-out infinite; }
}
/* A ring that grows out of the edge and fades, rather than the whole thing
   blinking: a picture of somebody's face going half-transparent once a second
   is unpleasant to sit beside, and a ring leaves the face alone. */
@keyframes working {
  0% { box-shadow: 0 0 0 0 rgba(63, 185, 80, .55); }
  70%, 100% { box-shadow: 0 0 0 .45rem rgba(63, 185, 80, 0); }
}

/* Which model answered and what it cost. True, and not what the reply is
   about - shown when somebody asks for it in settings. */
.routing { display: none; }
html.debugging .routing { display: inline; }

/* The one piece of routing that is never hidden: this answer is on a dearer
   model because of an *earlier* question, which nothing about the question in
   front of you accounts for. Quiet - it sits in the meta line under the reply
   at the same size as everything else there - but present, and pressable for
   the sentence behind it. */
.held {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--dim);
  cursor: pointer;
  text-decoration: underline dotted;
  text-underline-offset: 2px;
}
.held:hover, .held:focus-visible { color: var(--text); }

#spending h3 { margin: 1.4rem 0 .4rem; font-size: .95rem; }
/* The report itself, exactly as it would be filed. Scrolls rather than
   stretching the window: the point is that it can be read before it is sent,
   not that all of it is on screen at once. */
#review-body {
  max-height: 16rem; overflow: auto; margin: .6rem 0 0;
  padding: .6rem .7rem; border: 1px solid var(--line); border-radius: .5rem;
  background: var(--bg); color: var(--dim);
  font-size: .78rem; white-space: pre-wrap; overflow-wrap: anywhere;
}
/* One line per filing: when, which issue, how much of a report it was. */
.review-row { display: flex; gap: .6rem; font-size: .82rem; padding: .15rem 0; }
.review-row .meta { color: var(--dim); font-variant-numeric: tabular-nums; }

#why-model h3 { margin: 1.1rem 0 .4rem; font-size: .95rem; }
#why-model h3:first-of-type { margin-top: .9rem; }

/* Markdown inside a reply. The bubble keeps `pre-wrap` for stray text, but
   the blocks below are real elements now and carry their own spacing -
   without this a handbook answer is one undifferentiated column. */
.msg h1, .msg h2, .msg h3, .msg h4, .msg h5, .msg h6 {
  margin: .8em 0 .3em; line-height: 1.25;
}
.msg h1 { font-size: 1.35em; }
.msg h2 { font-size: 1.2em; }
.msg h3 { font-size: 1.08em; }
.msg h4, .msg h5, .msg h6 { font-size: 1em; }
.msg > *:first-child { margin-top: 0; }
.msg > *:last-child { margin-bottom: 0; }
.msg p { margin: .5em 0; white-space: normal; }
.msg ul, .msg ol { margin: .5em 0; padding-left: 1.4em; white-space: normal; }
.msg li { margin: .15em 0; }
.msg pre {
  margin: .6em 0; padding: .6em .7em; overflow-x: auto;
  background: var(--panel); border: 1px solid var(--line); border-radius: 6px;
  font-size: .88em; white-space: pre;
}
.msg code { font-size: .9em; }
.msg :not(pre) > code {
  padding: .1em .3em; background: var(--panel);
  border: 1px solid var(--line); border-radius: 4px;
}
.msg blockquote {
  margin: .6em 0; padding: .1em 0 .1em .8em;
  border-left: 3px solid var(--line); color: var(--dim);
}
.msg hr { border: 0; border-top: 1px solid var(--line); margin: .9em 0; }
/* A table can be wider than a phone. It scrolls itself rather than the page. */
.msg table { display: block; overflow-x: auto; border-collapse: collapse; margin: .6em 0; }
.msg th, .msg td {
  border: 1px solid var(--line); padding: .3em .5em; text-align: left; white-space: normal;
}
.msg th { background: var(--panel); }
/* A picture the agent found. It is its own block even inside a paragraph, so
   a caption written on the line after it reads as a caption. Bounded by the
   bubble rather than by the image's own size - a stock photo is regularly two
   thousand pixels wide, and `height: auto` is what stops the aspect ratio
   being decided by that. */
.msg img.shown {
  display: block; margin: .5em 0; max-width: 100%; height: auto;
  border-radius: 8px; border: 1px solid var(--line); background: var(--panel);
}

/* A week of scheduling windows: the day on the left, its windows on the right,
   so seven rows read as a week rather than as fourteen fields. */
.hours-row {
  display: grid; grid-template-columns: 7rem 1fr; align-items: center;
  gap: .5rem; margin: .2rem 0;
}
.hours-row input { margin: 0; }

/* The scheduling window. Its buttons are the shared window rule now - this
   window is where the chosen state was worked out, and the reason it was
   worked out here is worth keeping: it shipped with every toggle looking
   identical whether it was chosen or not, so the saved answer was on the
   server, on the summary line, and invisible on the control that set it.
   What stays here is what is genuinely local. */
/* Tighter inside a group than a standalone button, so a set of four choices
   fits on one line of a dialog thirty-four rem wide instead of wrapping its
   last option onto a row of its own - which reads as a separate answer.
   The *options*, which are the toggles, and not a Save that shares the row
   with them: this row holds both, and a Save an eighth of an inch smaller
   than every other Save in the app was one of the four designs #124 is
   about. */
.choices button.quiet { padding: .35rem .6rem; }
#planner input {
  background: var(--bg); color: var(--text); border: 1px solid var(--line);
  border-radius: .5rem; padding: .4rem .55rem; font: inherit;
}
#planner input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* One question per line: its name on the left, its answers on the right.
   Written first as a single wrapping row, which ran three questions together -
   "Tentative Title Default Hidden Shown Colour None" is eleven buttons in one
   stream and no way to see where one answer ends and the next begins. */
.field {
  display: grid; grid-template-columns: 6.5rem 1fr;
  align-items: baseline; gap: .3rem .6rem; margin: .45rem 0;
}
.field > .meta { padding-top: .45rem; }
.choices { display: flex; flex-wrap: wrap; gap: .3rem; align-items: center; }
/* A destructive action, pushed to the far end of its own row. */
.choices .push { margin-left: auto; }
#p-r-title, #p-b-title { min-width: 14rem; flex: 1 1 14rem; }
#p-r-from, #p-r-to { width: 4.5rem; }
/* The name goes above its answers rather than into a column two words wide.
   Asked of the window rather than the screen: this row lives in a 44rem
   window, so on a desktop the viewport version of this rule never fired and
   the squeezing it describes happened there too. */
@container panel (max-width: 30rem) {
  .field { grid-template-columns: 1fr; gap: .2rem; }
  .field > .meta { padding-top: 0; }
}

/* One reserved thing per line, tappable, with what it costs a week beside it.
   A button rather than a div: it opens an editor, and a row you can reach with
   a keyboard is the difference between a settings page and a picture of one. */
#planner .listing {
  display: grid; grid-template-columns: 1fr auto; align-items: baseline; gap: .3rem .7rem;
  width: 100%; text-align: left; padding: .5rem .6rem; margin: .25rem 0; cursor: pointer;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--line); border-left: 3px solid var(--line); border-radius: .5rem;
}
#planner .listing:hover { border-color: var(--accent); }
#planner .listing .meta { justify-self: end; }
/* The detail line wraps under the title rather than being squeezed into a
   column two words wide - asked of the window, which is the thing that is
   narrow, rather than of the screen around it. */
@container panel (max-width: 30rem) {
  #planner .listing { grid-template-columns: 1fr; }
  #planner .listing .meta { justify-self: start; }
}

/* ---------------------------------------------------------- the walkthrough
   How far along, as a bar. The words beside it are what somebody reads - "3
   of 7 set up" is the fact - and the bar is only there to say at a glance
   that this ends, which a number on its own does not. */
#wz-progress, #tw-progress {
  height: .35rem; border-radius: .2rem; background: var(--bg);
  border: 1px solid var(--line); overflow: hidden; margin: .6rem 0 .3rem;
}
#wz-bar, #tw-bar {
  height: 100%; width: 0; background: var(--accent);
}
/* Only where somebody has asked for motion. A bar that jumps is honest; a bar
   that jumps *and* animates on a device set to reduce motion is neither. */
@media (prefers-reduced-motion: no-preference) {
  #wz-bar, #tw-bar { transition: width .25s ease; }
}
#wz-step h3, #tw-step h3, #wz-done h3 { margin: .8rem 0 .3rem; font-size: 1rem; }
#wz-actions { margin-top: .7rem; }

/* Every step, for somebody who would rather jump than walk - and the only way
   back to one that was skipped. */
.wz-row { display: flex; align-items: center; gap: .5rem; padding: .15rem 0; }
/* Fixed width so the names line up whatever each row's mark is, including the
   empty one. A ragged left edge down a list of seven reads as a mistake. */
.wz-mark {
  flex: none; width: 1.1rem; text-align: center;
  font-weight: 600; color: var(--dim);
}
.wz-mark.done { color: var(--accent); }
/* The name is the target, so it takes the room rather than the mark beside
   it: a word-sized tap target in a list is the one that gets missed.
   Borderless, unlike the `quiet` buttons it is drawn from: seven bordered
   boxes down a column read as seven controls competing with each other rather
   than as a list of what there is, and the row already has a mark saying
   which is which. The edge comes back on hover, where it says "this one". */
.wz-name {
  flex: 1; text-align: left;
  border-color: transparent; background: none; color: var(--text);
}
.wz-name:hover, .wz-name:focus-visible {
  border-color: var(--line); color: var(--text);
}

/* The editor for one row, set apart from the list it came out of. */
.editor {
  border: 1px solid var(--line); border-radius: .5rem;
  padding: .7rem; margin: .5rem 0; background: var(--bg);
}
.editor input[type="number"] { width: 5.5rem; }

/* Outlook has no per-event colour: an event is coloured by a category, and the
   category's colour comes from a fixed palette. These are that palette as the
   browser can show it - near enough to pick by, and the mailbox has the last
   word on the exact shade. */
#planner .swatch { width: 1.7rem; padding: .35rem 0; }
#planner .swatch[data-colour="red"] { background: #d13438; }
#planner .swatch[data-colour="orange"] { background: #ca5010; }
#planner .swatch[data-colour="yellow"] { background: #c19c00; }
#planner .swatch[data-colour="green"] { background: #498205; }
#planner .swatch[data-colour="teal"] { background: #038387; }
#planner .swatch[data-colour="blue"] { background: #0078d4; }
#planner .swatch[data-colour="purple"] { background: #8764b8; }
#planner .swatch[data-colour="grey"] { background: #7a7574; }
#planner .swatch.on { outline: 2px solid var(--text); outline-offset: 1px; }
#planner .listing[data-colour="red"] { border-left-color: #d13438; }
#planner .listing[data-colour="orange"] { border-left-color: #ca5010; }
#planner .listing[data-colour="yellow"] { border-left-color: #c19c00; }
#planner .listing[data-colour="green"] { border-left-color: #498205; }
#planner .listing[data-colour="teal"] { border-left-color: #038387; }
#planner .listing[data-colour="blue"] { border-left-color: #0078d4; }
#planner .listing[data-colour="purple"] { border-left-color: #8764b8; }
#planner .listing[data-colour="grey"] { border-left-color: #7a7574; }

/* One team per line with its tick, so a dozen of them read as a list rather
   than as a paragraph of names. */
.team-row { display: flex; align-items: center; gap: .4rem; margin: .2rem 0; }
.team-row input { width: auto; margin: 0; }

/* What a tool returned, under the step that ran it. Off with the rest of the
   diagnostics: it is true, and it is not what an answer is about - and when
   something has gone wrong it is the only place the answer has ever been. */
.tool .result { display: none; }
html.debugging .tool .result {
  display: block; white-space: pre-wrap; overflow-wrap: anywhere;
  margin: .3rem 0 .5rem 1rem; padding: .4rem .5rem;
  background: var(--bg); border: 1px solid var(--line); border-radius: .4rem;
  color: var(--dim); font-size: .8rem; max-height: 18rem; overflow-y: auto;
}

/* How long a step took, beside its name. Same gate as .result and the same
   reason: true, and not what an answer is about. */
.tool .timing { display: none; }
html.debugging .tool .timing { display: inline; color: var(--dim); font-size: .8rem; }

/* A picture a tool produced, in the conversation.

   Bounded by height rather than width: these are photographs off a phone and
   a portrait one at full width is a screen and a half of scrolling to get
   past. `contain` so nothing is cropped - which of the two edges matters is
   the photograph's business, not this file's. */
.shot { margin: .5rem 0; }
.shot img {
  display: block; max-width: 100%; max-height: 22rem;
  object-fit: contain; border-radius: .5rem; border: 1px solid var(--line);
  background: var(--bg);
}
.shot figcaption { color: var(--dim); font-size: .75rem; margin-top: .25rem; }

/* ------------------------------------------------------------------- forms
   The panel a published form is filled in on.

   Written after the first version shipped as a paragraph. A question was a
   bare `label`, and every `label { display: block }` rule in this file is
   scoped to the dialog that needed it - `#setup`, `#brand-dialog`,
   `#admin-main`, `#newapi` - so this one inherited the browser's inline
   default and ran the name, the box and the hint of three questions together
   into one stream: "Full Name Department Department they work in First Day".
   The same shape of mistake `.field` above was written to fix, in a screen
   that was not using `.field`.

   It is not using it here either, and deliberately: `.field` puts the name in
   a column six and a half rems wide, which suits "Colour" and "Shows as" and
   truncates "Anything else we should know". A form's questions are prose and
   its answers are full-width boxes, so the name goes above. */
#forms-dialog h3 { margin: 1.1rem 0 .4rem; font-size: .95rem; }
#forms-dialog h3:first-of-type { margin-top: .6rem; }

/* One form per row: what it is on the left, where it goes on the right, and
   the whole row a target.

   The same shape as `#planner .listing`, and for the same reason - this is a
   list of things you open, and the planner already settled what one looks
   like here. It was first written as `quiet wide`, which is the dashed
   full-width *offer* - "install the app", "turn on notifications" - and a
   list of ten forms drawn as ten suggestions reads as a screen nagging you.

   Scoped by the dialog's id because `dialog button:not(.close)` paints every
   button in here accent-on-accent, and a bare `button.fo-row` loses to it -
   which drew the list as six solid blue slabs. The same reason
   `#planner .listing` carries its id. */
#forms-dialog button.fo-row {
  display: grid; grid-template-columns: 1fr auto; align-items: baseline;
  gap: .3rem .7rem; width: 100%; text-align: left;
  padding: .5rem .6rem; margin: .25rem 0; cursor: pointer; font: inherit;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--line); border-left: 3px solid var(--line);
  border-radius: .5rem;
}
#forms-dialog button.fo-row:hover { border-color: var(--accent); }
#forms-dialog button.fo-row .meta { justify-self: end; }
/* Where it goes wraps under the title rather than being squeezed into a column
   two words wide. The window, not the screen: see the note on `dialog`. */
@container panel (max-width: 30rem) {
  #forms-dialog button.fo-row { grid-template-columns: 1fr; }
  #forms-dialog button.fo-row .meta { justify-self: start; }
}

#fo-editor { margin-top: 1rem; border-top: 1px solid var(--line); padding-top: .8rem; }
#fo-editor h3 { margin-top: 0; }

.fo-q { display: block; margin: .9rem 0; }
.fo-name { display: block; margin-bottom: .25rem; font-size: .9rem; }
.fo-q input, .fo-q select, .fo-q textarea {
  display: block; width: 100%; box-sizing: border-box;
  padding: .45rem .55rem; border-radius: .45rem;
  border: 1px solid var(--line); background: var(--bg); color: var(--text);
  font: inherit;
}
.fo-q textarea { resize: vertical; min-height: 4rem; }
.fo-q input:focus-visible, .fo-q select:focus-visible, .fo-q textarea:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
}
/* The hint under the box rather than beside it: it explains the question that
   was just asked, and a phone has no room for a second column. */
.fo-hint { display: block; margin-top: .2rem; }
/* Where the core's refusal lands - under the question it is about, so "that is
   not a date I can read" sits with the date rather than in a note at the
   bottom that could be about any of eight. Empty most of the time, and taking
   up no room when it is. */
.fo-trouble { display: block; margin-top: .2rem; color: var(--warn); }
.fo-trouble:empty { display: none; }

/* A picture question: the chooser, and - once something is on it - its name
   and a way to take it off. Laid out as a wrapping row so the name sits beside
   the button on a desk and under it on a phone. */
.fo-picture { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
/* The file input is the one control here that must not be a box. `.fo-q input`
   gives every answer a full-width bordered field, which is right for a date
   and wrong for this: the browser draws its own button *inside* the element,
   so the border becomes a wide empty frame with a grey button in one corner.
   Stripped back to nothing, and the button inside it is styled instead. */
.fo-q .fo-picture input[type="file"] {
  display: inline-block; width: auto; flex: 0 1 auto;
  border: 0; background: none; padding: 0; font-size: .9rem;
}
.fo-q .fo-picture input[type="file"]::file-selector-button {
  font: inherit; margin-right: .5rem; padding: .35rem .6rem; border-radius: .45rem;
  border: 1px solid var(--line); background: var(--bg); color: var(--text);
  cursor: pointer;
}
.fo-q .fo-picture input[type="file"]::file-selector-button:hover {
  border-color: var(--accent);
}
.fo-picture button { flex: 0 0 auto; padding: .35rem .6rem; }

#fo-editor .row { margin-top: 1rem; }

/* One question in the form editor: two full-width lines, boxed so that five of
   them read as five questions rather than as ten rows of controls.

   Not `.field`, which is the planner's name-left/answers-right grid - putting
   this under it placed the two lines in the two columns and stacked the label,
   the name and the kind into a stripe six and a half rems wide. */
#newform .fm-q {
  border: 1px solid var(--line); border-radius: .5rem;
  padding: .5rem .6rem; margin: .5rem 0;
}
#newform .fm-q .row { flex-wrap: wrap; gap: .4rem; align-items: center; margin: .25rem 0; }
/* The label and the name take the room; the kind is a short chooser. */
#newform .fm-q .row > input { flex: 1 1 9rem; min-width: 0; }
/* `width: 100%` is the console's default for a select, and inside a wrapping
   row that puts the short kind chooser on a line of its own. */
#newform .fm-q .row > select { flex: 0 0 auto; width: auto; }
#newform .fm-q .row > label.meta { flex: 0 0 auto; display: flex; align-items: center; gap: .3rem; }
#newform .fm-q .row > label.meta input { width: auto; flex: none; }
/* The arrows are square and quiet; Remove is pushed to the end so it is never
   the button next to the one somebody is aiming at. */
#newform .fm-q button[data-move] { flex: 0 0 auto; padding: .3rem .55rem; }
#newform .fm-q button.danger { margin-left: auto; }

/* ---------------------------------------------------------- the time window

   Everything below sits inside `<dialog id="planner">`, and that matters more
   than it looks: `dialog button:not(.close)` is specificity (0,1,2) - the
   `:not()` counts its argument - so a plain `.tabs button` at (0,1,1) loses to
   it and every control added here silently came back as a full-size accent
   button. The tab strip rendered as four blue slabs louder than the content
   they switch between. Hence `#planner` on the front of these: it is not
   decoration, it is the only way to win. */

#planner .tabs {
  display: flex;
  gap: .1rem;
  margin: .9rem 0 1rem;
  border-bottom: 1px solid var(--line);
  /* Scrolls rather than wraps. Four tabs on a phone wrapped to two rows and
     took a third of the window before anything was read. */
  overflow-x: auto;
  scrollbar-width: none;
}
#planner .tabs::-webkit-scrollbar { display: none; }
#planner .tabs button {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  padding: .4rem .55rem;
  margin-bottom: -1px;
  color: var(--dim);
  font: inherit;
  font-size: .92rem;
  white-space: nowrap;
  cursor: pointer;
}
#planner .tabs button.on { color: var(--text); border-bottom-color: var(--accent); }
#planner .tabs button:hover { color: var(--text); }
#planner .tab-panel[hidden] { display: none; }

/* The two whole-feature controls. Utilities, not the point of the window, so
   they sit small and quiet under the title rather than competing with it. */
#planner #p-controls { gap: .4rem; margin: .2rem 0 0; }
#planner #p-controls button {
  background: none;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: .28rem .7rem;
  font-size: .85rem;
  color: var(--text);
}
#planner #p-controls button:hover { border-color: var(--accent); }
#planner #p-controls button.danger { color: #f85149; border-color: transparent; }
#planner #p-controls button.danger:hover { border-color: #f85149; }
#planner #p-controls button:disabled { opacity: .45; }
#p-controls-note { white-space: pre-line; display: block; margin: .5rem 0 0; }

#planner .setup-heading { margin: 1.5rem 0 .5rem; }
#planner .setup-heading:first-child { margin-top: .2rem; }

/* What will not fit. Amber rather than red: it is news, not a failure, and a
   full week should not read as a broken one. */
.shortfall {
  display: grid;
  gap: .15rem;
  padding: .5rem .7rem;
  margin: .4rem 0;
  border-left: 3px solid var(--warn);
  background: var(--panel);
  border-radius: 0 6px 6px 0;
}
.shortfall .advice { padding-left: .8rem; opacity: .8; font-size: .86rem; }
.shortfall .advice::before { content: "- "; }
/* Take it up. Sits on the line it belongs to rather than in a row of its own,
   because there can be three of these under one shortfall and three buttons in
   a column read as a form to fill in rather than as three separate offers. */
#planner .shortfall .take {
  margin-left: .4rem;
  background: none;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: .1rem .55rem;
  font: inherit;
  font-size: .8rem;
  color: var(--text);
  white-space: nowrap;
  cursor: pointer;
}
#planner .shortfall .take:hover { border-color: var(--accent); color: var(--accent); }
#planner .shortfall .take:disabled { opacity: .5; cursor: default; }

/* One booked block: the time, what it is, and how long - on one line where
   there is room for one. It was a card per block, three lines tall, which made
   a fortnight of reserved time an afternoon's scrolling. */
#planner .block {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  padding: .3rem .1rem;
  border: none;
  border-bottom: 1px solid var(--line);
  background: none;
  border-radius: 0;
}
#planner .block .when { font-variant-numeric: tabular-nums; color: var(--dim); font-size: .88rem; }
#planner .block .what { flex: 1; }
#planner .block .meta { margin-left: auto; white-space: nowrap; }
#planner .block button {
  background: none;
  border: none;
  border-radius: 0;
  padding: 0;
  color: var(--dim);
  font: inherit;
  font-size: .82rem;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
#planner .block button:hover { color: var(--accent); }
#planner .day {
  margin: 1rem 0 .2rem;
  font-weight: 600;
  font-size: .82rem;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--dim);
}

/* How the week went. */
#planner .gauge { display: grid; gap: .1rem; margin: .2rem 0 1.2rem; }
/* The colour is set because it is not inherited: the gauge sits in a `.meta`
   block, so the one number somebody opens this tab for came out the same grey
   as the sentence explaining it. */
#planner .gauge strong {
  font-size: 2.4rem;
  line-height: 1;
  letter-spacing: -.02em;
  color: var(--text);
}

#planner .totals {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: .6rem;
}
#planner .totals + .against { display: block; margin: .35rem 0 1.4rem; font-size: .78rem; }
#planner .totals > div { display: grid; gap: .05rem; align-content: start; }
#planner .totals strong { font-size: 1.25rem; }
#planner .totals .meta { font-size: .78rem; line-height: 1.25; }
/* Three numbers across need about a `rem` each per digit plus their labels.
   Against the window, so the pair-up happens when the box is narrow rather
   than when the screen is. */
@container panel (max-width: 26rem) {
  #planner .totals { grid-template-columns: 1fr 1fr; }
}

/* Focus per day. Flex rather than grid rows, because a percentage height needs
   a definite parent to resolve against - inside the grid it collapsed to the
   two-pixel minimum, so the chart was seven hairlines under an empty box. */
#planner .bars {
  display: flex;
  align-items: stretch;
  gap: .35rem;
  height: 5rem;
  margin-bottom: 1.4rem;
}
#planner .bar {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: .3rem;
  text-align: center;
}
#planner .bar .fill {
  background: var(--accent);
  border-radius: 3px 3px 0 0;
  min-height: 2px;
}
#planner .bar .fill.none { background: var(--line); }
#planner .bar .meta { font-size: .75rem; }

/* The breakdown, as rows with the proportion drawn behind them rather than ten
   bordered cards stacked down the page. */
#planner .breakdown { display: grid; gap: .1rem; }
#planner .breakdown .slice {
  position: relative;
  display: flex;
  justify-content: space-between;
  gap: .5rem;
  padding: .35rem .5rem;
  border-radius: 4px;
  overflow: hidden;
}
#planner .breakdown .slice .bg {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--accent);
  opacity: .16;
}
#planner .breakdown .slice span { position: relative; }
#planner .breakdown .slice .meta { font-variant-numeric: tabular-nums; }

/* The week picker, and any other toggle row inside this window. `.choices`
   buttons are sized for a settings form; three of them in a row wrapped onto
   two lines and dwarfed the figures they were there to switch between. */
#planner #p-week-pick { gap: .3rem; margin: .2rem 0 .8rem; }
#planner #p-week-pick button {
  border-radius: 999px;
  padding: .25rem .7rem;
  font-size: .85rem;
}

/* Adding a reservation is an ordinary action among several, not the point of
   the panel; it was the largest control on the tab. */
#planner .row button.quiet {
  border-radius: 999px;
  padding: .3rem .8rem;
  font-size: .88rem;
}

/* ------------------------------------------------ a newer version is waiting

   A strip between the header and the conversation, in the flow rather than
   over it: the body is a flex column, so this shortens the log instead of
   covering the last thing that was said. `flex: 0 0 auto` because the log
   beside it is `flex: 1` and would otherwise squeeze this to nothing on a
   short window. */
#update {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: .6rem;
  flex-wrap: wrap;
  padding: .55rem 1rem;
  border-bottom: 1px solid var(--line);
  background: var(--panel);
  font-size: .95rem;
}
#update[hidden] { display: none; }
#update-what { margin-right: auto; }
#update button {
  background: none;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: .25rem .8rem;
  color: var(--text);
  font: inherit;
  font-size: .88rem;
  cursor: pointer;
}
/* The reload is the point of the strip and the dismissal is the way past it,
   so only one of them is drawn as an action. Two equal buttons make a person
   read both to find out which is which. */
#update #update-go { border-color: var(--accent); color: var(--accent); }
#update #update-go:hover { background: var(--accent); color: var(--bg); }
#update #update-later { color: var(--dim); border-color: transparent; }
#update #update-later:hover { border-color: var(--line); }

/* What changed, after the reload that changed it. */
#news-list h3 { margin: 1.1rem 0 .3rem; font-size: .95rem; }
#news-list h3:first-child { margin-top: .2rem; }
#news-list ul { margin: 0; padding-left: 1.1rem; }
#news-list li { margin: .3rem 0; }

/* ------------------------------------------- the deployment's whole bill
   The same bar as the walkthrough, in the colour the number deserves: this
   one is money, and a bar that stays accent-blue while the deployment is
   over its ceiling is a bar somebody reads as fine. */
#d-progress {
  height: .45rem; border-radius: .25rem; background: var(--bg);
  border: 1px solid var(--line); overflow: hidden; margin: .6rem 0 .4rem;
}
#d-bar { height: 100%; width: 0; background: var(--accent); }
#d-bar.warn { background: var(--warn); }
#d-bar.over { background: #f85149; }
@media (prefers-reduced-motion: no-preference) {
  #d-bar { transition: width .25s ease; }
}
/* One person per line, biggest first. A list rather than a table because it
   is two columns and a table would draw rules nobody needs to follow. */
#d-list .row { padding: .2rem 0; border-top: 1px solid var(--line); }

/* ----------------------------------------------------------------- spending
   Categorical slots for the by-month chart. From the validated reference
   palette, re-checked against *this* app's dialog surfaces rather than the
   ones it shipped against: `#ffffff` light, `#191d21` dark. Both modes pass
   every hard gate on the adjacent pairlist a stacked bar uses.
   Light carries a contrast warning on three of the five, which the per-model
   table under the chart discharges - identity is never colour alone here.
   Fixed order, never cycled: a sixth model folds into `Other` (slot 6). */
:root {
  --series-1: #2a78d6;
  --series-2: #eb6834;
  --series-3: #1baf7a;
  --series-4: #eda100;
  --series-5: #e87ba4;
  --series-6: #8b949e;   /* Other - deliberately grey, it is not an identity */
}
/* Selected for the dark surface, not flipped: the same five hues stepped for
   a dark band, validated as a set. Declared under both scopes so the theme
   toggle wins in either direction. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --series-1: #3987e5;
    --series-2: #d95926;
    --series-3: #199e70;
    --series-4: #c98500;
    --series-5: #d55181;
    --series-6: #6a737d;
  }
}
:root[data-theme="dark"] {
  --series-1: #3987e5;
  --series-2: #d95926;
  --series-3: #199e70;
  --series-4: #c98500;
  --series-5: #d55181;
  --series-6: #6a737d;
}

.spend-total { font-size: 1.15rem; font-weight: 600; margin: .2rem 0 .6rem; }
.spend-chart h4, .spend-row + h4 { margin: 0; }
/* The two settings above the chart: a window, and what one bar covers.
   Eight buttons loose over two ragged lines read as eight decisions, and the
   two lines started at different places because one of them led with a label.
   A label column of a fixed width fixes the alignment; joining each row's
   choices into one control fixes the count. */
.spend-controls { display: grid; gap: .45rem; margin: .9rem 0 1rem; }
.spend-control { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
/* Wide enough for the longer of the two words, so both controls start on the
   same column. A `ch` unit rather than a rem: it is sized by the text it has
   to clear. */
.spend-control-name { color: var(--dim); font-size: .82rem; min-width: 7ch; }

/* One control with divisions, not four buttons that happen to be adjacent. */
.segmented {
  display: inline-flex; flex-wrap: wrap;
  border: 1px solid var(--line); border-radius: .5rem; overflow: hidden;
}
/* Written to outrank `dialog button:not(.close)` on specificity rather than on
   position, which is the rule this file already follows for every control that
   is not the accent-filled default. */
dialog .segmented button:not(.close) {
  border: 0; border-radius: 0; background: none; color: var(--dim);
  padding: .38rem .75rem; font-size: .88rem; font-weight: 400;
}
.segmented button + button { border-left: 1px solid var(--line); }
dialog .segmented button:not(.close):hover { color: var(--text); }
/* Chosen. The same three signals the rest of the app uses for a picked
   option - accent text, a tinted face, more weight - minus the border, which
   this control does not have per button. */
dialog .segmented button.on:not(.close) {
  color: var(--accent); font-weight: 600;
  background: color-mix(in srgb, var(--accent) 18%, transparent);
}
dialog .segmented button:not(.close):focus-visible {
  outline: 2px solid var(--accent); outline-offset: -2px;
}
/* Narrow enough that one of the two rows would wrap and the other would not,
   which puts the labels back out of line - the thing this layout is for. So
   below the width where both still fit, both stack. */
@media (max-width: 32rem) {
  .spend-control { flex-direction: column; align-items: flex-start; gap: .25rem; }
}

#spending h4, .spend-host h4 {
  margin: 1rem 0 .3rem; font-size: .9rem; color: var(--dim);
  font-weight: 600; text-transform: none;
}

.spend-chart { margin: .2rem 0 .5rem; }
/* Bars grow from the baseline, which is what makes them comparable. The rule
   under them is the baseline made visible, and it is what keeps a day with
   nothing in it - a two-pixel hairline - from reading as a strike through the
   tick of the bar beside it. */
.spend-plot {
  display: flex; align-items: stretch; gap: .3rem;
  height: 8rem; padding-top: .3rem;
  border-bottom: 1px solid var(--line);
}
/* The ticks, in their own row under that rule. It lines up with the plot for
   one reason and no other: the same number of equal children with the same
   gap, so column N sits under column N without either row knowing about the
   other. */
.spend-axis { display: flex; gap: .3rem; padding-top: .3rem; }
/* A grid, not a flex column, and that is load-bearing: a bar's height is a
   percentage, and a percentage needs a parent with a definite height to
   resolve against. In a flex column the track is `auto`, the percentage
   resolved to nothing, and every bar came out a hairline on the baseline - a
   chart that looked like a deployment which had spent nothing all year. */
.spend-col, .spend-axis > * {
  flex: 1; display: grid; align-items: end; justify-items: center;
  /* The rule that looks like it does nothing. A flex item starts at
     `min-width: auto` - "never narrower than my own content" - and only some
     ticks carry text, so the labelled columns refused to shrink below the
     width of their date and came out several times wider than their
     neighbours, with the text spilling over the bars either side. */
  min-width: 0;
}
.spend-col .spend-stack { width: 100%; align-self: end; }
.spend-stack {
  display: flex; flex-direction: column-reverse;
  border-radius: 3px 3px 0 0; overflow: hidden; min-height: 2px;
  /* A day with nothing in it is a hairline on the baseline rather than a gap:
     an absent bar reads as no data, and a flat one reads as no spend. */
  background: var(--line);
}
.spend-stack.empty { height: 2px !important; }
/* A surface-coloured gap between segments, so two hues never touch. */
.spend-seg { min-height: 2px; border-bottom: 2px solid var(--panel); }
.spend-seg:first-child { border-bottom: 0; }
/* Kept on one line and allowed out of its column: only some bars carry a tick,
   and a `1 Aug` broken over two lines is worse than one that overhangs
   slightly. `min-width: 0` above is what stops the overhang pushing the bars
   about; naming the month only where it changes keeps the overhang small. */
.spend-tick { font-size: .7rem; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* The ticks at either end overhang outwards, where there is nothing to
   overhang into - so the last one, which is always labelled because it is the
   bar somebody is looking for, was clipped by the edge of the window. Anchored
   inwards instead: the column does not move, only the text inside it. */
/* `text-align`, not `justify-items`: the tick's text is an anonymous item that
   fills its grid area, so aligning the *item* moves nothing and the text
   overflowed as before. Aligning the text inside the area is what makes it
   overhang inwards. */
.spend-axis > *:first-child { text-align: left; }
.spend-axis > *:last-child { text-align: right; }

.spend-legend { display: flex; flex-wrap: wrap; gap: .3rem .8rem; margin: .1rem 0 .2rem; }
.spend-key { display: inline-flex; align-items: center; gap: .3rem; font-size: .8rem; }
.spend-swatch { width: .7rem; height: .7rem; border-radius: 2px; flex: none; }

/* The table half. It is what discharges the light-mode contrast warning, so
   it is not optional decoration beside the chart. */
.spend-row {
  /* Wrapping, because the note below carries `flex-basis: 100%` to claim its
     own line. Without it the note stayed on the row and squeezed the label
     beside it to about one character, which rendered a stage name as a
     column of single letters. */
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .1rem .5rem;
  padding: .25rem 0; border-top: 1px solid var(--line);
}
.spend-name { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.spend-cost { font-variant-numeric: tabular-nums; font-weight: 600; }
.spend-why { flex-basis: 100%; }

/* The one control under a stalled answer: ask again, or look again.
   Quiet, because it appears at a moment that is already annoying and should
   not also be loud. Scoped to `.meta` and sized off it, so it sits on the
   line it belongs to rather than towering over it - and stated fully rather
   than left to inherit, for the reason the drill rows below give: inside a
   dialog `dialog button:not(.close)` would paint it as an accent slab. */
.meta .retry {
  font: inherit; font-size: inherit; line-height: 1.2;
  padding: .1rem .45rem; margin-left: .15rem;
  color: var(--accent); background: none;
  border: 1px solid var(--line); border-radius: 5px; cursor: pointer;
}
.meta .retry:hover { border-color: var(--accent); }

/* A row that leads to the turns behind it - the drill-down.

   Two problems, and the second is the one that bites. The first: these are
   real `<button>`s, because that is what makes them reachable by keyboard, so
   the browser's own button has to be undone - a centred, bordered, grey box
   with its own font is nothing like a row of a list.

   The second: `dialog button:not(.close)` is specificity (0,1,2) - the
   `:not()` counts its argument - and it paints every button in a dialog as a
   full accent slab. A plain `.spend-row.spend-drill` is (0,2,0) and wins on
   what it declares, which is exactly the trap: whatever it forgets to declare
   is still an accent button underneath. Hence `.spend-host` on the front of
   each and padding stated explicitly rather than left to the base row: the
   same rows are drawn in a dialog in the app and in a plain pane in the
   console, and they have to come out identical in both.

   Left as a row that hints rather than one that shouts. Every line in these
   lists is pressable, so a border or a colour on each would be decoration on
   every row and information on none; the cursor and the hover say it, and the
   focus ring says it to a keyboard. */
.spend-host .spend-row.spend-drill {
  width: 100%; text-align: left; font: inherit; color: inherit;
  background: none; border: 0; border-top: 1px solid var(--line);
  border-radius: 0; padding: .25rem 0; cursor: pointer;
}
.spend-host .spend-row.spend-drill:hover { background: var(--bg); }
/* Inside the padding rather than around it, so a focused row does not shift
   the two beside it. */
.spend-host .spend-row.spend-drill:focus-visible {
  outline: 2px solid var(--accent); outline-offset: -2px;
}

/* Same undoing for a bar of the chart. `align-items: end` is re-stated
   because the bar has to keep growing from the baseline: one that floats is
   one that cannot be compared with its neighbours. */
.spend-host .spend-col.spend-drill {
  padding: 0; font: inherit; color: inherit; background: none;
  border: 0; border-radius: 0; cursor: pointer; align-items: end;
}
.spend-host .spend-col.spend-drill:hover .spend-stack { filter: brightness(1.15); }
.spend-host .spend-col.spend-drill:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
}

/* The detail: one row of the report, opened. */
.spend-host .spend-back {
  display: inline-block; margin: .2rem 0 .4rem; padding: .25rem .6rem;
  font: inherit; font-size: .85rem; cursor: pointer;
  color: var(--dim); background: none;
  border: 1px solid var(--line); border-radius: 6px;
}
.spend-host .spend-back:hover { color: var(--text); border-color: var(--dim); }
/* Names what was pressed, so the list underneath is never a list of turns
   with no statement of what they have in common. It carries the focus on
   arrival - it is the answer to "what am I looking at" - and shows no ring
   for it, because a heading nobody can type into with a box round it reads
   as something gone wrong. */
.spend-host .spend-focus { margin: .2rem 0 .1rem; color: var(--text); }
.spend-host .spend-focus:focus-visible { outline: none; }

/* The difficulty boundaries, under the model pickers on the same pane. A row
   reads as a sentence - "difficult   from [85]   shipped: 75" - because that
   is what somebody is trying to say when they come here. */
.level-head { margin: 1.4rem 0 0.2rem; }
.level-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  padding: 0.3rem 0;
}
.level-name { min-width: 6rem; font-weight: 600; }
.level-box { width: 5rem; }
/* Pushed to its own line on a narrow screen rather than squeezing the box:
   the number is the control and the note is commentary on it. */
.level-note { flex: 1 1 8rem; }
/* A chat that changed model re-billed its whole history, because prompt
   caching is per model. Marked rather than described: it is a flag on a row
   in a long log, and a sentence there would be read once and then scrolled
   past on every row that does not have it. */
.spend-moved {
  flex-basis: 100%;
  color: var(--warn, #b45309);
  font-weight: 600;
}

/* ---------------------------------------------------------------- notes
   Taking notes in a meeting. Three states in one window, and only the middle
   one is designed for use *while somebody else is talking*: one column, the
   box where the thumb already is, and the lines above scrolling on their own
   so the composer never moves down the screen.

   Everything here is scoped to `#notes` for the reason `#forms-dialog
   button.fo-row` is: `dialog button:not(.close)` paints every button in a
   window accent-on-accent, which drew the list of past meetings as solid blue
   slabs and the delete-this-line × as a button bigger than the line. A bare
   class loses to it. */

#notes label { display: block; margin: .7rem 0; font-size: .88rem; }
#notes label .meta { font-size: .95em; }
#notes input, #notes textarea {
  display: block; width: 100%; margin-top: .25rem; background: var(--bg);
  color: var(--text); border: 1px solid var(--line); border-radius: .5rem;
  padding: .45rem .55rem; font: inherit; resize: vertical;
}
#notes .row { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
#notes .setup-heading { margin-top: 1.2rem; }

.notes-lines {
  max-height: 40vh; overflow-y: auto; margin: .6rem 0;
  border: 1px solid var(--line); border-radius: .5rem; padding: .3rem;
  display: flex; flex-direction: column; gap: .1rem;
}
.notes-lines:empty::after {
  content: "Nothing noted yet."; color: var(--dim); font-size: .9em; padding: .5rem;
}

/* One line: when it was said, what was said, and a way to take it out.
   `baseline` so a two-line note still lines up with its time. */
#notes .note-line {
  display: grid; grid-template-columns: 3rem 1fr 1.6rem; gap: .5rem;
  align-items: baseline; padding: .3rem .35rem; border-radius: .35rem;
}
#notes .note-line:hover { background: var(--bg); }
#notes .note-when {
  color: var(--dim); font-variant-numeric: tabular-nums; font-size: .82em;
}
/* Said rather than typed. A dictated line that reads oddly is usually the
   microphone rather than a colleague, and knowing which is the difference
   between correcting it and quoting it. */
#notes .note-dictated { font-style: italic; }

/* Heard in the room rather than put in by hand - the third answer to "who
   decided this was worth writing down", and the one where the answer is
   nobody. Dimmed rather than italicised: italic is taken, there may be twelve
   hundred of these against a dozen typed lines, and the typed ones are the
   ones somebody chose. A rule down the side rather than a colour, so that a
   whole meeting of them does not read as a whole meeting of warnings. */
#notes .note-recorded {
  color: var(--dim); border-left: 2px solid var(--line); padding-left: .45rem;
}

/* And the line nobody said at all. This is the assistant writing down that a
   stretch of the meeting is missing, so it is marked as loudly as a line of
   text can be: it is the one thing in a transcript that must not be skimmed
   past, because everything above and below it looks continuous. */
#notes .note-line-gap { background: color-mix(in srgb, var(--warn) 12%, transparent); }
#notes .note-gap {
  color: var(--warn); font-style: italic;
  border-left: 2px solid var(--warn); padding-left: .45rem;
}
#notes button.note-drop {
  background: none; border: 0; color: var(--dim); cursor: pointer;
  font-size: 1rem; line-height: 1; padding: .1rem .2rem; border-radius: .3rem;
  justify-self: end; align-self: start;
}
#notes button.note-drop:hover { color: var(--text); background: var(--line); }

/* A past meeting, as a row you press rather than a suggestion. */
#notes button.note-session {
  display: block; width: 100%; text-align: left; cursor: pointer; font: inherit;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--line); border-left: 3px solid var(--line);
  border-radius: .5rem; padding: .5rem .6rem; margin: .25rem 0;
}
#notes button.note-session:hover { border-color: var(--accent); }

/* The line box is the whole of this state, so it gets room to hold two lines
   of dictation without growing under the buttons. */
#notes #notes-line { min-height: 3.4rem; }
#notes #notes-composer .row:last-child { margin-top: .4rem; }
#notes #notes-state { color: var(--dim); font-size: .85em; }

/* The document, once written. Same class the chat uses, so headings, bullets
   and emphasis are rendered by the rules that already exist. */
#notes #notes-document { margin-top: .8rem; }

/* The download is an anchor rather than a button - `dialog button` does not
   reach it, so it is painted here to match the primary beside it. */
#notes a.primary {
  display: inline-block; text-decoration: none; text-align: center;
  border: 1px solid var(--accent); background: var(--accent); color: var(--panel);
  border-radius: .5rem; padding: .4rem .8rem; font: inherit;
}
#notes a.primary:hover { filter: brightness(1.05); }

/* The actions, as things to tick. A checkbox, what it is, and when - and
   nothing preselected, because a meeting produces more actions than anybody
   wants on their list and the choosing is the point of the button. */
#notes .note-action {
  display: grid; grid-template-columns: auto 1fr auto; gap: .5rem;
  align-items: baseline; padding: .4rem .3rem; margin: 0;
  border-bottom: 1px solid var(--line); font-size: .95em;
}
#notes .note-action:last-of-type { border-bottom: 0; }
#notes .note-action input { width: auto; margin: 0; }
#notes .note-action .meta { white-space: nowrap; }

/* The second download, offered as a sentence rather than as a button: the PDF
   is what people send each other, and the text file is for the one person in
   twenty who wants to paste it somewhere. */
#notes a.quiet-link { color: var(--dim); font-size: .85em; text-decoration: underline; }
#notes a.quiet-link:hover { color: var(--text); }

#notes select {
  display: block; width: 100%; margin-top: .25rem; background: var(--bg);
  color: var(--text); border: 1px solid var(--line); border-radius: .5rem;
  padding: .45rem .55rem; font: inherit;
}

/* The microphone level, beside whichever button is recording.
   Vertically centred against the button rather than sitting on the text
   baseline: it is an instrument, not a word. `image-rendering: pixelated`
   because the bars are drawn on integer pixels and a browser smoothing them
   makes twelve crisp segments into a grey smear. */
.mic-level {
  display: inline-block; vertical-align: middle; margin-left: .4rem;
  width: 96px; height: 16px; border-radius: .2rem;
  image-rendering: pixelated;
}

/* Room between the rows.
   Reported as "buttons in the notetaking sections are too close", and the
   screenshot showed why it matters rather than how it looks: "Note it" and
   "Throw away" were a thumb's width apart on a phone, and one of those two
   deletes a meeting. */
#notes .row { margin-top: .7rem; }
#notes .row + .row { margin-top: .55rem; }
#notes .row.notes-end {
  margin-top: 1.1rem; padding-top: .9rem; border-top: 1px solid var(--line);
}

/* What just happened, said where it will be read. A grey line under two rows of
   buttons is the same as silence - which is exactly how "the draft is in your
   mailbox" went unnoticed. */
#notes .said {
  display: block; margin: .7rem 0 0; padding: .55rem .7rem;
  border-radius: .5rem; border-left: 3px solid var(--line);
  background: var(--bg); font-size: .92em; line-height: 1.45;
}
#notes .said.said-ok { border-left-color: var(--accent); color: var(--text); }
#notes .said.said-wrong { border-left-color: #f85149; color: var(--text); }

/* Recording the room, and the loudest thing in this window while it happens.
   Not a subtle state, deliberately: recording a meeting is a different act
   from typing a note about one, and everybody else in the room has an interest
   in knowing which is going on. Where this runs, Illinois asks every party to
   agree to a private conversation being recorded - the software's part of that
   is making the fact impossible to lose track of, and this is that part. */
#notes .notes-recording {
  display: flex; align-items: flex-start; gap: .6rem;
  margin: 0 0 .7rem; padding: .6rem .75rem;
  border: 1px solid #f85149; border-left-width: 4px; border-radius: .5rem;
  background: color-mix(in srgb, #f85149 10%, transparent);
  font-size: .9rem; line-height: 1.45;
}
#notes .notes-recording .rec-words { flex: 1; min-width: 0; }
#notes .notes-recording b { color: var(--text); }
#notes .rec-dot {
  width: .7rem; height: .7rem; border-radius: 50%; background: #f85149;
  flex: none; margin-top: .35rem;
}
/* The clock. Tabular figures because it is redrawn every second and digits
   that change width make a line of text twitch beside them; ruled off, because
   without a divider it sits at the end of the sentence beside it and reads as
   part of it - "everybody in it 0:01 should know". */
#notes .rec-elapsed {
  flex: none; align-self: stretch; color: var(--text);
  font-variant-numeric: tabular-nums; font-size: .95rem;
  padding-left: .7rem; border-left: 1px solid color-mix(in srgb, #f85149 40%, transparent);
}
#notes .notes-recording.paused .rec-elapsed {
  border-left-color: color-mix(in srgb, var(--warn) 40%, transparent);
}
#notes .notes-recording.stopping .rec-elapsed { border-left-color: var(--line); }
/* Paused is a state somebody chose, so it is calm; nothing is being recorded
   and the banner must stop shouting that it is. */
#notes .notes-recording.paused {
  border-color: var(--warn); background: color-mix(in srgb, var(--warn) 10%, transparent);
}
#notes .notes-recording.paused .rec-dot { background: var(--warn); }
#notes .notes-recording.stopping {
  border-color: var(--line); background: var(--bg);
}
#notes .notes-recording.stopping .rec-dot { background: var(--dim); }
/* The message under the banner's own words is already inside a red box, so it
   drops the second border the `.said` rule would give it. */
#notes .notes-recording .said {
  margin: .4rem 0 0; padding: .3rem 0 0; background: none;
  border-left: 0; border-top: 1px solid var(--line);
}
#notes-record.on { border-color: #f85149; color: #f85149; }

@media (prefers-reduced-motion: no-preference) {
  /* Only while it is actually recording. A dot that went on pulsing through a
     pause would be the one thing in the window still saying "live". */
  #notes .notes-recording:not(.paused):not(.stopping) .rec-dot {
    animation: pulse 1.4s ease-in-out infinite;
  }
}

/* Getting onto the network: one device per row, and its state beside it.
   Mirrors `.invite-row` above, because the two sit together on the page and a
   list that changed shape halfway down reads as two unrelated things. */
.device-row { align-items: center; gap: .6rem; margin: .4rem 0; }
.device-row .meta { margin-right: auto; }

/* The QR code is drawn in black, always - a scanner needs the dark modules
   dark and the quiet zone light, so this is the one thing on the page that
   must NOT follow the theme. On the dark theme an unpainted code is black on
   near-black and simply does not scan. */
.qr { margin: .8rem 0; }
/* Big, and this is the line that decides whether a phone can read it at all.
   The symbol is 57 modules across, so at the 260px it used to render each one
   was 4.3 physical pixels - about half what a camera needs at arm's length,
   and it failed on a real phone while looking perfectly crisp on the screen.
   It is vector, so size costs nothing: 420px puts each module at roughly 7px
   and it stays sharp at any width. */
.qr svg {
  background: #fff; padding: .6rem; border-radius: .5rem; display: block;
  width: min(420px, 100%); height: auto;
}
.qr:empty { display: none; }

/* The configuration, when there is no code to scan or the clipboard refused.
   `user-select: all` so one tap takes the whole thing: it is a file, and half
   of one is worse than none. */
pre.config {
  background: var(--bg); border: 1px solid var(--line); border-radius: .5rem;
  padding: .6rem .8rem; overflow-x: auto; user-select: all; font-size: .85rem;
}

/* "If this machine made its own key" - the companion case, folded away because a
   phone is the ordinary one. */
details.sub { margin: .6rem 0; }
details.sub > summary { cursor: pointer; color: var(--dim); font-size: .9rem; }

/* ------------------------------------------------------------------ panels
   A working surface the assistant built, drawn inside the conversation.

   Everything here is a `pn-` class. The `--panel` custom property above is a
   *surface colour* and has nothing to do with this feature - the two words
   collided the moment this shipped, and a prefix is cheaper than renaming a
   token every rule in this file reads.

   It sits at the full width of the bubble it replaces and is bordered rather
   than raised: it is a thing inside the conversation, and a drop shadow makes
   it read as a dialog that has landed in the wrong place. */
.pn {
  border: 1px solid var(--line); border-radius: .8rem;
  background: var(--panel); overflow: hidden;
  margin: .2rem 0;
}
/* The panel named inside a sentence.

   A link is a phrase, so what replaces it has to be one: `inline` rather than
   a block, and sized off the surrounding text rather than off the panel. The
   first version put the panel itself here, which cut the sentence in half and
   pushed the halves apart around nine rows of review.

   It reads as a link and behaves as a disclosure - the panel opens under the
   paragraph, not inside it. */
.pn-chip {
  display: inline; font: inherit; color: var(--accent);
  background: none; border: none; padding: 0; margin: 0;
  cursor: pointer; text-align: left;
  /* Wraps with the prose around it rather than pushing the line wide. */
  white-space: normal; overflow-wrap: anywhere;
  border-bottom: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
}
.pn-chip:hover { border-bottom-color: var(--accent); }
/* A caret after the words, turned when it is open. Drawn rather than a
   character, so it cannot be read out as punctuation or picked up by a copy. */
.pn-chip::after {
  content: ''; display: inline-block; vertical-align: middle;
  width: .32em; height: .32em; margin: -.15em 0 0 .4em;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
}
.pn-chip[aria-expanded="true"]::after { transform: rotate(-135deg); margin-top: .1em; }

/* The panel under the paragraph that named it. */
.pn-holder { margin: .55rem 0; }

.pn-head { padding: .85rem 1rem .1rem; }
.pn-head h3 { margin: 0; font-size: 1.05rem; line-height: 1.25; }
.pn-intro { margin: .35rem 0 0; color: var(--dim); font-size: .85em; max-width: 60ch; }

/* The counters, which are also the filter. A scrolling row rather than a wrap:
   six statuses on a phone wrap to three lines and push the rows off the
   screen, and a strip that slides keeps the shape of the thing constant. */
.pn-counts {
  display: flex; gap: .4rem; padding: .8rem 1rem;
  overflow-x: auto; scrollbar-width: none;
}
.pn-counts::-webkit-scrollbar { display: none; }
.pn-count {
  flex: none; display: flex; flex-direction: column; gap: .1rem;
  min-width: 4.6rem; padding: .45rem .6rem;
  background: none; color: inherit; font: inherit; text-align: left;
  border: 1px solid var(--line); border-radius: .6rem; cursor: pointer;
}
.pn-count b { font-size: 1.15rem; font-weight: 650; line-height: 1.1; }
.pn-count span {
  color: var(--dim); font-size: .68rem; letter-spacing: .04em;
  text-transform: uppercase; white-space: nowrap;
}
.pn-count:hover { border-color: var(--dim); }
/* The one being filtered to. Border and weight rather than a fill: a filled
   tile among five outlined ones reads as a button somebody must press, and
   this is a state. */
.pn-count.on { border-color: var(--accent); }
.pn-count.on b, .pn-count.on span { color: var(--accent); }

.pn-bar {
  display: flex; align-items: center; gap: .5rem;
  padding: .8rem 1rem;
}
/* The counter strip already carries the space above the bar where it is
   drawn, so the bar only supplies its own when it is the first thing under
   the head. */
.pn-counts + .pn-bar { padding-top: 0; }
.pn-spacer { flex: 1; }
/* At the size of the quiet buttons beside it, not the size of a dialog's
   Save. It is one of four controls on that bar; drawn full-size and filled it
   read as the only thing worth pressing. */
.pn-primary {
  background: var(--accent); color: var(--bg); border: 1px solid var(--accent);
  border-radius: .5rem; padding: .3rem .75rem; font: inherit; font-size: .85em;
  font-weight: 550; cursor: pointer; white-space: nowrap;
}
.pn-quiet {
  background: none; color: var(--dim); border: 1px solid var(--line);
  border-radius: .5rem; padding: .3rem .7rem; font: inherit; font-size: .85em;
  cursor: pointer; text-decoration: none; white-space: nowrap;
}
.pn-quiet:hover { color: var(--text); border-color: var(--dim); }

.pn-rows { list-style: none; margin: 0; padding: 0; counter-reset: pn; }
.pn-row {
  display: flex; align-items: flex-start; gap: .6rem;
  padding: .7rem 1rem; border-top: 1px solid var(--line);
  counter-increment: pn;
}
/* The number is drawn rather than stored, so filtering to four of twenty rows
   numbers them one to four - which is what somebody working down a filtered
   list is counting. */
.pn-row::before {
  content: counter(pn) "."; color: var(--dim); font-size: .85em;
  min-width: 1.4rem; padding-top: .15rem; font-variant-numeric: tabular-nums;
}
/* A decided row steps back. Not struck through and not faded to unreadable:
   the reason it says is the thing somebody re-reads when they change their
   mind about it. */
.pn-row[data-decided] .pn-lead { color: var(--dim); }
.pn-cells { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .15rem; }
.pn-lead { font-weight: 600; }
.pn-rest { color: var(--dim); font-size: .87em; overflow-wrap: anywhere; }
.pn-label { font-style: normal; opacity: .7; }
.pn-empty { padding: 1.1rem 1rem; color: var(--dim); font-size: .9em; text-align: center; }

/* The decision, as a pill. A `select` for the reasons `statusPicker` gives;
   this is what makes it look like the chip it replaces. */
.pn-status {
  flex: none; appearance: none; -webkit-appearance: none;
  background: none; color: var(--dim); font: inherit; font-size: .85em;
  border: 1px solid var(--line); border-radius: 1rem;
  padding: .25rem 1.5rem .25rem .7rem; cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right .7rem center, right .45rem center;
  background-size: .25rem .25rem, .25rem .25rem;
  background-repeat: no-repeat;
}
.pn-status[data-set] { color: var(--accent); border-color: var(--accent); }
.pn-drop {
  flex: none; background: none; border: 1px solid transparent; color: var(--dim);
  border-radius: .4rem; padding: .1rem .4rem; font-size: 1rem; cursor: pointer;
  line-height: 1.2;
}
.pn-drop:hover { color: #f85149; border-color: var(--line); }

.pn-editor {
  display: flex; flex-direction: column; gap: .6rem;
  padding: .9rem 1rem; border-top: 1px solid var(--line);
  background: var(--bg);
}
.pn-field { display: flex; flex-direction: column; gap: .25rem; }
.pn-field span { color: var(--dim); font-size: .78rem; }
.pn-field input, .pn-field textarea {
  background: var(--panel); color: var(--text);
  border: 1px solid var(--line); border-radius: .5rem;
  padding: .45rem .55rem; font: inherit; resize: vertical;
}
.pn-field input:focus, .pn-field textarea:focus {
  outline: none; border-color: var(--accent);
}
.pn-actions { display: flex; gap: .5rem; }

/* Narrow: a phone, and the column the rows are read in on one.

   The controls drop under the text rather than squeezing it to a column two
   words wide. The number goes into the gutter rather than staying a flex item:
   with the text at full width it was pushed onto a line of its own, so every
   row began with a bare "1." above the thing it numbered.

   The counters wrap instead of scrolling. Five tiles do not fit at this width,
   and the one that falls off the end is Undecided - the count somebody is
   actually working down. A second row of tiles costs less than hiding it. */
@media (max-width: 30rem) {
  .pn-row {
    position: relative; flex-wrap: wrap; padding-left: 2.7rem; row-gap: .5rem;
  }
  .pn-row::before { position: absolute; left: 1rem; top: .75rem; padding-top: 0; }
  .pn-cells { flex-basis: 100%; }
  .pn-counts { flex-wrap: wrap; overflow-x: visible; }
  .pn-count { min-width: 4.1rem; flex: 1 1 auto; }
}

/* Printing one panel.
   `data-printing` is set on `body` for the length of the print call and names
   the panel wanted; everything else is taken off the page rather than hidden
   in place, so the panel starts at the top of the sheet instead of after four
   blank pages of chat. The controls go too: a printed sheet of paper has no
   Add row button, and the decision is printed as the word it says. */
@media print {
  body[data-printing] > * { display: none !important; }
  body[data-printing] .pn-holder,
  body[data-printing] .pn-holder * { display: revert !important; }
  body[data-printing] .pn-holder {
    display: block !important;
    position: absolute; inset: 0; margin: 0; padding: 0;
  }
  body[data-printing] .pn { border: none; background: none; }
  body[data-printing] .pn-bar,
  body[data-printing] .pn-editor,
  body[data-printing] .pn-drop,
  body[data-printing] .pn-row button { display: none !important; }
  body[data-printing] .pn-status {
    appearance: none; background-image: none; border-color: transparent;
    padding-right: .7rem; color: inherit;
  }
  body[data-printing] .pn-row { break-inside: avoid; }
}

/* ------------------------------------------------------------------ feedback
   The window behind **Feedback** in the menu: what is wrong or missing, in
   somebody's own words, going to the tracker for this software.

   Laid out to match the expense app's, which is the same window on the same
   issue format - three cards with their hints, the name above each box, and
   the primary action last on the right. Its Tailwind classes do not exist
   here, so this is the same arrangement written out.

   Its own block rather than `.fo-q`, which is the same shape and belongs to
   the forms window - a published form's questions, with a `fo-` prefix that
   says so. Borrowing them would make every change to that window a change to
   this one. */
#feedback .fb-kinds { border: 0; margin: 1rem 0; padding: 0; }
#feedback .fb-kinds legend { padding: 0; margin-bottom: .45rem; font-size: .88rem;
  font-weight: 500; color: var(--dim); }
/* Three across where there is room, stacked where there is not. A card holds
   a name and a sentence, so two of them side by side on a phone is two
   columns of one word per line. */
.fb-cards { display: grid; gap: .5rem; grid-template-columns: repeat(3, 1fr); }
@container panel (max-width: 30rem) { .fb-cards { grid-template-columns: 1fr; } }
.fb-card {
  display: block; cursor: pointer; padding: .55rem .65rem; position: relative;
  border: 1px solid var(--line); border-radius: .5rem; background: var(--bg);
}
.fb-card:hover { border-color: var(--accent); }
/* The radio itself is not drawn - the card is the control. Kept in the page
   rather than `display: none` so it is still focusable and still announced,
   which is the whole reason this is a radio group and not three buttons. */
/* `#feedback input` below is wider in specificity and gives every input the
   full width, so this has to match it or the hidden radio is as wide as the
   window and the window grows a sideways scrollbar over nothing. */
#feedback .fb-card input { position: absolute; width: 1px; height: 1px; padding: 0;
  border: 0; opacity: 0; }
.fb-card:has(input:checked) {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}
.fb-card:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 1px; }
.fb-card-name { display: block; font-size: .88rem; font-weight: 600; color: var(--text); }
.fb-card:has(input:checked) .fb-card-name { color: var(--accent); }
.fb-card-hint { display: block; margin-top: .15rem; font-size: .78rem; color: var(--dim); }

/* The name above the box rather than beside it, which is what the expense
   app's `Field` does - and for `#forms-dialog`'s reason too: `.field` puts
   the name in a column six and a half rems wide, which suits "Colour" and
   truncates "What did you expect, and what happened instead". */
#feedback .fb-field { display: block; margin: .9rem 0; }
#feedback .fb-label { display: block; margin-bottom: .25rem; font-size: .88rem;
  font-weight: 500; color: var(--dim); }
#feedback .fb-hint { display: block; margin-top: .25rem; font-size: .78rem; color: var(--dim); }
#feedback input, #feedback textarea {
  display: block; width: 100%; box-sizing: border-box;
  padding: .45rem .55rem; border-radius: .45rem;
  border: 1px solid var(--line); background: var(--bg); color: var(--text);
  font: inherit;
}
#feedback textarea { resize: vertical; min-height: 8rem; }
#feedback input:focus-visible, #feedback textarea:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
}
/* Cancel first and Send last, at the right - the expense app's order, and
   the one a dialog's confirming action conventionally sits in.
   Its own `display: flex`, and not `.row`: there is no global `.row` rule in
   this file. Every one of them is scoped to the dialog that wanted it -
   `#setup .row`, `#admin-main .row`, `.card .row` - so a bare `class="row"`
   here inherited nothing and left both buttons inline at the left, with a
   `justify-content` on a box that was not a flex container doing nothing
   about it. */
#feedback .fb-actions {
  display: flex; gap: .5rem; justify-content: flex-end; margin-top: 1rem;
}
/* Why it was refused, under the form it is about. Takes up no room when there
   is nothing to say, which is almost always. */
#fb-trouble { color: var(--warn); }
#fb-trouble:empty { display: none; }
/* Sent. A tinted panel like the expense app's, in the accent rather than a
   green: this stylesheet has no success colour, and inventing one for a line
   that appears once would be a fifth token nothing else uses. */
.fb-sent {
  margin: 0 0 .8rem; padding: .5rem .7rem; border-radius: .45rem;
  border: 1px solid var(--accent); font-size: .88rem;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}
/* The issue's own address, once there is one. A line of its own, and the
   second rule is not redundant: `display` here would otherwise outrank the
   browser's `[hidden]` and show a link to nothing before anything is sent. */
#fb-issue { display: block; margin: 0 0 .8rem; }
#fb-issue[hidden] { display: none; }

/* The public demo's line under the header (assistant/demo.py) */
.demo-line {
  padding: 8px 16px; font-size: 14px; color: var(--text);
  background: color-mix(in srgb, var(--accent) 12%, var(--panel)); border-bottom: 1px solid var(--line);
}
.demo-line a { color: var(--accent); }
