/* ---------------------------------------------------------------------------
   The assistant.

   One conversation that never ends, so the organising device is not a session
   or a bubble — it is time. A hairline rule runs the full height of the thread
   with the clock hung off it; speech sits to the right of the rule and machine
   events sit *on* it. A long silence is a dashed segment with the gap named,
   because in a thread that spans months the gaps carry as much as the words.

   No speech bubbles anywhere. Who said a thing is carried by typeface and
   colour: the user in ink, the assistant in verdigris, anything the machine
   generated in mono. That mapping is the spec's trust boundary made visible —
   an errand result is never dressed as something the assistant said.
   --------------------------------------------------------------------------- */

@import url('fonts.css');

:root {
    /* Drafting paper and patina. Deliberately green-shifted rather than cream:
       this is an instrument, not a periodical. */
    --paper: #f2f4f0;
    --paper-sunk: #e9ece6;
    --ink: #17211c;
    --ink-soft: #465249;
    --ink-faint: #5f6c64;

    /* A hairline between things, and deliberately below the 3:1 floor: it
       separates, it does not carry meaning. The boundary of a control is a
       different job and is `--edge`, which meets it. */
    --rule: #ccd4ca;
    --edge: #808d85;

    /* The assistant's voice. */
    --patina: #2b6553;

    /* In flight, and nothing else. Never used for emphasis or decoration —
       if it is orange on this page, something is still happening. */
    --signal: #ab4813;

    --danger: #9b2c2c;

    --ui: 'Archivo', system-ui, sans-serif;
    --speech: 'Newsreader', Georgia, serif;
    --machine: 'IBM Plex Mono', ui-monospace, monospace;

    /* The clock column plus the rule. One number, so nothing drifts out of
       alignment when a component is added. */
    --gutter: 5.25rem;

    /* The bar's height. It is sticky, so everything that sticks under it needs
       the same number, and two rules were already carrying it as a literal. The
       bar is given this as an explicit height rather than growing to its
       content: it drifted once already, when the nav links were padded to the
       24px target floor, and six pixels of drift puts the thread's month marks
       behind the bar that is painted over them. */
    --bar: 3.8rem;

    --measure: 34rem;
}

@media (prefers-color-scheme: dark) {
    :root {
        --paper: #12160f;
        --paper-sunk: #1a1f18;
        --ink: #e3e8df;
        --ink-soft: #9aa598;
        --ink-faint: #808c7e;
        --rule: #2e3830;
        --edge: #626e61;
        --patina: #6dbb9c;
        --signal: #dd7a41;
        --danger: #e08585;
    }
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    min-height: 100dvh;
    background: var(--paper);
    color: var(--ink);
    font-family: var(--ui);
    font-size: 16px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}

/* The conversation fills the window and the composer sits at the foot of it, so an empty
   thread does not leave the input floating in the middle of the page. */
body:has(.composer) main {
    display: flex;
    flex-direction: column;
    min-height: calc(100dvh - var(--bar));
}

body:has(.composer) .thread { flex: 1; }

a { color: var(--patina); text-decoration-thickness: 1px; text-underline-offset: 2px; }

:focus-visible {
    outline: 2px solid var(--signal);
    outline-offset: 2px;
}

/* Out of the way until a keyboard finds it. Not `.visually-hidden`, which would
   keep it clipped when focused — this has to become a real, visible control. */
.skip {
    position: fixed;
    left: -100vw;
    top: 0;
    z-index: 4;
}

.skip:focus {
    left: 0.5rem;
    top: 0.5rem;
    padding: 0.5rem 0.9rem;
    font-family: var(--ui);
    font-size: 0.875rem;
    color: var(--ink);
    background: var(--paper);
    border: 1px solid var(--edge);
    border-radius: 2px;
    text-decoration: none;
}

main:focus { outline: none; }

/* --- the top bar ---------------------------------------------------------- */

.bar {
    display: flex;

    /* Centred rather than baseline-aligned, because the bar now has a definite
       height: a baseline-aligned flex line sits at the top of one. The links
       keep their own baseline alignment with each other inside their zone. */
    align-items: center;
    gap: 1.5rem;
    height: var(--bar);
    padding: 0 1.5rem;
    border-bottom: 1px solid var(--rule);
    background: var(--paper);
    position: sticky;
    top: 0;
    z-index: 2;
}

.bar__name {
    font-family: var(--ui);
    font-weight: 600;
    font-size: 0.8125rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    margin: 0;
}

/* The nine destinations divide by scope: this assistant's, then everybody's. It
   used to divide by what you do at them — the conversation, what it is holding,
   how it is configured — and that cut straight across scope, so `notes` and
   `sub-agents` sat at one weight and one of them is not one assistant's.

   Gaps alone carried the old grouping. They cannot carry this one: the claim is
   about what a link belongs to, not that some links are near each other, so it
   is spacing plus a divider plus a word, and a current-page mark that differs
   between the two. */
.bar__zone { display: flex; gap: 1.1rem; align-items: baseline; }

/* The bracket the assistant's name opens. Pushed off the switcher rather than
   off the right edge, so what the name governs reads as sitting inside it. */
.bar__zone:first-of-type { margin-left: 0.5rem; }

.bar__zone--shared { gap: 1.1rem; }

/* Not a border on the zone: a border belongs to the thing it is drawn on, and
   this belongs to neither. --edge rather than --rule because it is carrying
   meaning here and has to meet the 3:1 non-text floor. */
.bar__divider {
    align-self: stretch;
    width: 1px;
    background: var(--edge);
    margin: 0.35rem 0;
}

/* Said once for the zone. Same face and size as the state word on a server, and
   for the same reason: this is our word about the thing, not the thing's own
   name. */
.bar__label {
    font-family: var(--machine);
    font-size: 0.6875rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-faint);
}

/* Sign-out is not a destination and does not sit in a list of them — it keeps
   the far edge to itself while the zones read left to right off the name. */
.bar__out { margin-left: auto; }

/* The conversation's own tap on a phone. Above the breakpoint the copy inside
   the fold is the one that shows. */
.bar__phone { display: none; }

.bar__link {
    font-family: var(--machine);
    font-size: 0.75rem;
    color: var(--ink-soft);
    text-decoration: none;

    /* Padded to the 24px target floor rather than sized to it: vertical padding
       on an inline control grows the hit area and leaves the bar's baseline
       alignment alone, which a min-height would not. */
    padding: 0.3rem 0 calc(0.3rem + 2px);
    border-bottom: 1px solid transparent;
}

.bar__link:hover { color: var(--ink); border-bottom-color: var(--rule); }

/* Where you are, in the assistant's colour — this screen is that assistant's. */
.bar__link[aria-current='page'] { color: var(--ink); border-bottom-color: var(--patina); }

/* And in the shared zone, deliberately not: patina is the assistant's voice, and
   a screen that belongs to all of them has no assistant to be in the colour of.
   Two marks rather than one is what makes "am I on something scoped" answerable
   at a glance, and it is a shape difference as well as a hue one. */
.bar__zone--shared .bar__link[aria-current='page'] {
    border-bottom-color: var(--edge);
    border-bottom-style: dotted;
    border-bottom-width: 2px;
}

.bar__signout {
    font-family: var(--machine);
    font-size: 0.75rem;
    color: var(--ink-soft);
    background: none;
    border: 0;
    padding: 0.3rem 0 calc(0.3rem + 2px);
    cursor: pointer;
    border-bottom: 1px solid transparent;
}

.bar__signout:hover { color: var(--danger); border-bottom-color: var(--danger); }

/* The fold. Above the breakpoint there is nothing to fold: the <details> gets out of the
   layout's way, its content lays out as if it were never there, and the toggle is hidden.
   Forcing the panel visible while the element is closed is an author style beating the UA's
   own — which is the whole reason this can be a <details> and not a script. */
.bar__more { display: contents; }

/* And the box the browser puts between a <details> and its children has to get out of the
   way too, or the panel is a flex item of that instead of of the bar — which is not a
   detail: it is why the panel would not grow and why sign-out's auto margin had no free
   space to absorb, so sign-out sat against the last destination instead of the far edge. A
   browser without ::details-content lands back on exactly that, which is a position rather
   than a break. */
.bar__more::details-content { display: contents; }

.bar__more-toggle {
    display: none;
    font-family: var(--machine);
    font-size: 0.75rem;
    color: var(--ink-soft);
    cursor: pointer;
    list-style: none;
    padding: 0.35rem 0;
}

.bar__more-toggle::-webkit-details-marker { display: none; }

/* Takes the rest of the row, which is what lets sign-out have the far edge. The
   zones themselves size to their content and stay next to the name. */
.bar__more-panel { display: flex; flex: 1; gap: 1.5rem; align-items: baseline; }

@media (max-width: 48rem) {
    /* Now it folds. The conversation keeps its one tap — it is the destination a phone is used
       for — and everything else, sign-out included, goes behind the toggle. The copy of it that
       lives inside the fold goes away, so the open panel does not offer it twice. */
    .bar__phone { display: inline-flex; margin-left: auto; }

    .bar__more { display: block; position: relative; margin-left: 0.9rem; }

    .bar__more-panel { flex: none; }

    .bar__more-toggle { display: inline-block; }

    .bar__more-toggle::after {
        content: '\203A';
        display: inline-block;
        margin-left: 0.35rem;
        transition: transform 120ms ease;
    }

    .bar__more[open] .bar__more-toggle { color: var(--ink); }
    .bar__more[open] .bar__more-toggle::after { transform: rotate(90deg); }

    .bar__more-panel {
        position: absolute;
        right: 0;
        top: calc(100% + 0.6rem);
        z-index: 3;
        flex-direction: column;
        align-items: flex-end;
        gap: 0.9rem;
        padding: 1rem 1.1rem;
        background: var(--paper);
        border: 1px solid var(--rule);
        border-radius: 2px;

        /* A destination's name is one thing to read, not two lines to reassemble. The panel
           takes the width its longest name needs. */
        white-space: nowrap;
    }

    /* Stacked, so position has stopped saying anything about scope — which is the state the
       label and the divider were put there for. The divider turns from a spine into a rule. */
    .bar__zone { flex-direction: column; align-items: flex-end; gap: 0.9rem; }

    .bar__divider { align-self: stretch; width: auto; height: 1px; margin: 0; }

    .bar__label { align-self: flex-end; }

    .bar__out { margin-left: 0; padding-top: 0.6rem; border-top: 1px solid var(--rule); width: 100%; text-align: right; }

    /* 24×24 is the WCAG 2.2 AA floor for a pointer target, and these are the smallest
       controls on the narrowest screen. */
    .bar__link, .bar__signout, .bar__more-toggle { min-height: 24px; display: inline-flex; align-items: center; }

    /* After that rule, not before: it sets a display on every .bar__link and would put this one
       back. The conversation is out in the bar at this width, so the copy inside the fold goes —
       an open panel offering it twice is one destination pretending to be two. */
    .bar__link--wide { display: none; }
}

/* --- the thread ----------------------------------------------------------- */

.thread {
    padding: 2rem 1.5rem 0;
    max-width: calc(var(--gutter) + var(--measure) + 3rem);
}

/* Every row is clock | rule | content. The rule is drawn by the row so it is
   continuous down the page without a separate absolutely-positioned element
   that would fall out of step with the content. */
.entry {
    display: grid;
    grid-template-columns: var(--gutter) 1fr;
    column-gap: 1.25rem;
    align-items: start;
}

.entry__clock {
    font-family: var(--machine);
    font-size: 0.6875rem;
    color: var(--ink-faint);
    text-align: right;
    padding-top: 0.4rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Repeating a timestamp that has not changed is noise; the rule carries the
   continuity instead. */
.entry__clock--same { visibility: hidden; }

/* --- the spine ------------------------------------------------------------

   The gutter already states the time, so it is what states the month too. Each
   mark sticks under the bar for as long as its month is being read.

   They are flat siblings of the entries rather than section wrappers, which
   means they share one containing block and stack rather than push: the next
   month's mark paints over the last one on the way past, and drops back into
   flow when you scroll above it, which is the behaviour wanted anyway. That is
   what the opaque background is for. It hides the rule as entries pass under,
   so the mark draws its own length of it.
   -------------------------------------------------------------------------- */

.month {
    position: sticky;
    top: var(--bar);
    z-index: 1;
    display: grid;
    grid-template-columns: var(--gutter) 1fr;
    column-gap: 1.25rem;
    background: var(--paper);
}

.month__label {
    margin: 0;
    padding: 0.55rem 0 0.5rem;
    text-align: right;
    font-family: var(--machine);
    font-size: 0.6875rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-soft);
    white-space: nowrap;
}

.month__rule { border-left: 1px solid var(--rule); }

/* What the page has, said plainly, so the spine cannot be read as offering to
   reach further back than the window goes. */
.thread__older {
    grid-column: 2;
    margin: 0 0 1.5rem;
    font-family: var(--machine);
    font-size: 0.6875rem;
    color: var(--ink-faint);
}

/* Shown by thread.js when the reader is away from the foot, and only then. It
   is toggled from the script rather than rendered by Blazor on purpose: this
   sits beside a thread that streams, and nothing here may put work on that
   render path (spec.md, Qualities and constraints). */
.jump {
    /* Absolute inside the composer, which is sticky and therefore the containing block. That
       puts it at the right edge of the thread's own measure rather than at the window's, which
       on a wide screen is several hundred pixels of nothing away from the conversation. */
    position: absolute;
    right: 1.5rem;
    top: -0.75rem;
    z-index: 2;
    display: none;
    font-family: var(--ui);
    font-size: 0.8125rem;
    color: var(--ink-soft);
    background: var(--paper);
    border: 1px solid var(--edge);
    border-radius: 2px;
    padding: 0.5rem 0.9rem;
    min-height: 24px;
    cursor: pointer;
}

.jump--shown { display: block; }

.jump:hover { color: var(--ink); border-color: var(--ink); }

.jump::after {
    content: '\2193';
    margin-left: 0.4rem;
    font-family: var(--machine);
    color: var(--ink-faint);
}

.entry__body {
    border-left: 1px solid var(--rule);
    padding: 0 0 1.5rem 1.5rem;
    min-width: 0;
}

.entry:last-child .entry__body { padding-bottom: 3rem; }

.said {
    font-family: var(--speech);
    font-size: 1.0625rem;
    line-height: 1.62;
    margin: 0;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    max-width: var(--measure);
}

.said--user { color: var(--ink); }

/* The one place the assistant is given a colour of its own. */
.said--assistant { color: var(--patina); }

/* A message the turn that wrote it has not committed. The text is not dimmed —
   the user is reading it — the state is said on the rule instead. */
.said--provisional::after {
    content: 'still being written';
    display: block;
    margin-top: 0.35rem;
    font-family: var(--machine);
    font-size: 0.6875rem;
    color: var(--signal);
}

/* A marker: an errand finishing, a wake firing. Model history, not speech, so
   it sits on the rule in mono and never looks like something that was said. */
.marker {
    display: inline-flex;
    align-items: baseline;
    flex-wrap: wrap;
    max-width: 100%;
    gap: 0.15rem 0.55rem;
    margin-left: -1.5rem;
    padding: 0.15rem 0.7rem 0.15rem 1.5rem;
    font-family: var(--machine);
    font-size: 0.75rem;
    color: var(--ink-soft);
    background: var(--paper-sunk);
    border-radius: 0 2px 2px 0;
}

.marker__kind { color: var(--ink-faint); }

/* A turn that was thrown away. It sits where a message the user may have watched arrive used
   to be, which is the point — a gap would say nothing. */
.marker--lost {
    color: var(--danger);
    background: none;
    border-left: 2px solid var(--danger);
    border-radius: 0;
    padding-left: 1.4rem;
}

.marker--lost .marker__kind { color: var(--danger); opacity: 0.7; }

/* The turn in flight, narrated as named steps. In character by default: the trace behind them
   is on demand, and the model's plain text never appears here at all. */
.entry--live .entry__body { border-left-color: var(--signal); }

.steps {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 0.9rem;
    font-family: var(--machine);
    font-size: 0.75rem;
    color: var(--ink-faint);
    padding-top: 0.35rem;
}

.steps__step--open {
    color: var(--signal);
}

/* The one animated thing on the page, and it marks the one thing that is still happening. */
.steps__step--open::after {
    content: '…';
    animation: working 1.4s steps(4, end) infinite;
}

@keyframes working {
    0% { content: ''; }
    25% { content: '.'; }
    50% { content: '..'; }
    75% { content: '...'; }
}

/* The reply as it is being written, read out of the tool call's arguments while they stream. */
.said--arriving {
    margin-top: 0.6rem;
}

.said--arriving::after {
    content: '';
    display: inline-block;
    width: 0.45em;
    height: 1em;
    margin-left: 0.1em;
    vertical-align: -0.15em;
    background: var(--patina);
    animation: caret 1s steps(2, end) infinite;
}

@keyframes caret {
    50% { opacity: 0; }
}

/* The turn behind a reply. Closed by default: in character unless you ask. */
.trace { margin-top: 0.5rem; max-width: var(--measure); }

.trace__summary {
    font-family: var(--machine);
    font-size: 0.6875rem;
    color: var(--ink-faint);
    cursor: pointer;
    list-style: none;
    display: inline-flex;
    align-items: center;
    min-height: 24px;
    padding-bottom: 1px;
    border-bottom: 1px solid transparent;
}

.trace__summary::-webkit-details-marker { display: none; }

/* The native marker is replaced rather than dropped: without something to turn, a one-word
   summary reads as a stray label and nobody finds out it opens. */
.trace__summary::before {
    content: '\203A';
    display: inline-block;
    width: 0.75rem;
    color: var(--ink-faint);
    transition: transform 120ms ease;
}

.trace[open] > .trace__summary::before { transform: rotate(90deg); }
.trace__summary:hover { color: var(--ink-soft); border-bottom-color: var(--rule); }
.trace[open] .trace__summary { color: var(--ink-soft); }

.trace__body {
    margin-top: 0.5rem;
    padding-left: 0.85rem;
    border-left: 1px solid var(--rule);
}

.trace__step {
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.15rem 1rem;
    font-family: var(--machine);
    font-size: 0.6875rem;
    color: var(--ink-soft);
    padding: 0.15rem 0;
}

/* Wraps to its own line before it wraps inside itself. The tokens line is three
   figures long and was the one thing pushing the conversation past 375px. */
.trace__took { color: var(--ink-faint); overflow-wrap: anywhere; }

.trace__detail {
    font-family: var(--machine);
    font-size: 0.6875rem;
    line-height: 1.5;
    color: var(--ink-soft);
    background: var(--paper-sunk);
    margin: 0.2rem 0 0.6rem;
    padding: 0.5rem 0.6rem;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    border-radius: 2px;
}

/* A silence. Drawn as a break in the rule with the gap named, because a
   three-day pause is information in a conversation that never restarts. */
.gap {
    display: grid;
    grid-template-columns: var(--gutter) 1fr;
    column-gap: 1.25rem;
}

.gap__line {
    border-left: 1px dashed var(--rule);
    padding: 0.4rem 0 1.4rem 1.5rem;
    font-family: var(--machine);
    font-size: 0.6875rem;
    color: var(--ink-faint);
}

/* The shared empty state, placed in the thread's grid. Everything else about
   how it reads is `.empty`, which the other four screens use unchanged. */
.thread__empty { grid-column: 2; }

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

.composer {
    position: sticky;
    bottom: 0;
    background: linear-gradient(to bottom, transparent, var(--paper) 1.5rem);
    padding: 1.5rem 1.5rem 1.75rem;
    max-width: calc(var(--gutter) + var(--measure) + 3rem);
}

.composer__row {
    display: grid;
    grid-template-columns: var(--gutter) 1fr;
    column-gap: 1.25rem;
}

.composer__field {
    grid-column: 2;

    /* A grid item will not shrink below its content either, and the content
       here is a textarea whose intrinsic width is wider than a phone. */
    min-width: 0;

    display: flex;
    gap: 0.75rem;
    align-items: flex-end;
    border-left: 1px solid var(--ink);
    padding-left: 1.5rem;
}

.composer__input {
    flex: 1;

    /* A flex item will not shrink below its intrinsic minimum, and a textarea's
       is its `cols` — about twenty characters of the reading face, which is
       wider than a phone leaves after the gutter and the send button. */
    min-width: 0;
    font-family: var(--speech);
    font-size: 1.0625rem;
    line-height: 1.55;
    color: var(--ink);
    background: none;
    border: 0;
    padding: 0.4rem 0;
    resize: none;
    min-height: 2.6rem;
    max-height: 12rem;
}

.composer__input::placeholder { color: var(--ink-faint); }

/* The composer says it has focus with the rule beside it going verdigris, which
   is enough for a pointer. The keyboard still gets the ring. */
.composer__input:focus:not(:focus-visible) { outline: none; }

/* Not --signal: that colour means something is in flight, and a cursor in a box is not. */
.composer__field:focus-within { border-left-color: var(--patina); }

.composer__note {
    grid-column: 2;
    margin-top: 0.5rem;
    padding-left: 1.5rem;
    font-family: var(--machine);
    font-size: 0.6875rem;
    color: var(--signal);
}

.entry--live { animation: none; }

/* --- controls ------------------------------------------------------------- */

.button {
    font-family: var(--ui);
    font-weight: 500;
    font-size: 0.875rem;
    color: var(--paper);
    background: var(--ink);
    border: 1px solid var(--ink);
    border-radius: 2px;
    padding: 0.5rem 1rem;
    cursor: pointer;
}

.button:hover { background: var(--patina); border-color: var(--patina); }

/* Quiet rather than dimmed. A greyed-out slab on an otherwise empty page is the loudest
   thing on it, which is backwards for a control there is nothing to press. */
.button:disabled {
    color: var(--ink-faint);
    background: none;
    border-color: var(--rule);
    cursor: default;
}

.button--quiet {
    color: var(--ink-soft);
    background: none;
    border-color: var(--edge);
}

.button--quiet:hover { color: var(--ink); background: none; border-color: var(--ink); }

/* Solid rather than quiet, because the only place this is used is the second
   half of a two-step destructive action: by the time it is on screen the
   question has been asked and this is the answer. */
.button--danger {
    color: var(--paper);
    background: var(--danger);
    border-color: var(--danger);
}

.button--danger:hover { background: var(--danger); border-color: var(--ink); }

/* --- forms: sign-in and the persona card ---------------------------------- */

.sheet {
    padding: 3rem 1.5rem 4rem;
    max-width: calc(var(--gutter) + var(--measure) + 14rem);
}

.sheet__title {
    font-family: var(--ui);
    font-weight: 600;
    font-size: 1.375rem;
    letter-spacing: -0.01em;
    margin: 0 0 0.35rem;
}

.sheet__lede {
    font-family: var(--speech);
    font-size: 1rem;
    color: var(--ink-soft);
    margin: 0 0 2.5rem;
    max-width: var(--measure);
}

.field { margin-bottom: 1.75rem; max-width: var(--measure); }

/* Darker than the hint below the field, not lighter. A label is scanned and a
   hint is read once, so the label has to win — it used to be --ink-faint, which
   put it behind its own guidance in the reading order and below AA besides. */
.field__label {
    display: block;
    font-family: var(--machine);
    font-size: 0.6875rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-soft);
    margin-bottom: 0.4rem;
}

.field__hint {
    font-family: var(--ui);
    font-size: 0.8125rem;
    color: var(--ink-soft);
    margin: 0.4rem 0 0;

    /* Inside a `.field` this is already capped; outside one — a band's own guidance —
       there is nothing else to stop the line running the width of the sheet. */
    max-width: var(--measure);
}

/* A hint that opens a band rather than trailing a field, so its margin runs the other way.
   Was an inline style on two pages, which is the shape a missing class takes. It carries the
   measure itself: outside a `.field` there is nothing else to stop the line getting too long. */
.field__hint--lede { margin: 0 0 0.9rem; max-width: var(--measure); }

.field__input,
.field__area {
    width: 100%;
    font-family: var(--speech);
    font-size: 1rem;
    line-height: 1.55;
    color: var(--ink);
    background: var(--paper-sunk);
    border: 1px solid var(--edge);
    border-radius: 2px;
    padding: 0.55rem 0.7rem;
}

/* Darkens on focus, and keeps the ring. These used to say `outline: none` on
   `:focus`, which takes the ring away from the keyboard as well as the pointer
   — the border alone is not a focus indicator. */
.field__input:focus,
.field__area:focus {
    border-color: var(--ink);
}

.field__input:focus:not(:focus-visible),
.field__area:focus:not(:focus-visible) {
    outline: none;
}

.field__area {
    resize: vertical;
    min-height: 5rem;

    /* Grows to what is in it. A persona card is written once and read often, and a box you have
       to scroll inside to read the thing you are editing is the wrong shape for that. Falls back
       to the row count where it is unsupported. */
    field-sizing: content;
}

/* The identity anchor is the one field the anti-drift mechanism needs to
   exist, and the only one copied verbatim into every request twice. It is
   given the weight to say so.

   The sub-agents page borrows it for the when-to-use line, which is the only
   field the assistant reads when it chooses a sub-agent. Same idiom, same
   meaning: this is the field that does the work. */
.field--anchor .field__area {
    font-size: 1.125rem;
    border-left: 3px solid var(--patina);
}

.field__mono { font-family: var(--machine); font-size: 0.875rem; }

.notice {
    font-family: var(--ui);
    font-size: 0.875rem;
    color: var(--danger);
    border-left: 3px solid var(--danger);
    padding: 0.3rem 0 0.3rem 0.75rem;
    margin-bottom: 1.5rem;
    max-width: var(--measure);
}

.notice--done {
    color: var(--patina);
    border-left-color: var(--patina);
}

/* --- the persona card: exchanges and versions ----------------------------- */

.exchange {
    border-left: 1px solid var(--rule);
    padding: 0 0 0 1rem;
    margin-bottom: 1rem;
}

.exchange__row { display: grid; gap: 0.4rem; margin-bottom: 0.6rem; }

.versions { margin-top: 3rem; max-width: var(--measure); }

.versions__list { list-style: none; margin: 0; padding: 0; }

.versions__item {
    display: grid;
    grid-template-columns: 3.5rem 1fr;
    column-gap: 1.25rem;
    font-family: var(--machine);
    font-size: 0.75rem;
    color: var(--ink-soft);
    padding: 0.4rem 0;
    border-top: 1px solid var(--rule);
}

.versions__number { color: var(--ink); text-align: right; }
.versions__item--current .versions__number { color: var(--patina); font-weight: 500; }

/* --- the errand list ------------------------------------------------------

   The same gutter as the thread, holding the state instead of the clock: an
   errand is a machine record and the state is the only thing you scan for.
   Orange means in flight and nothing else, here as everywhere.
   -------------------------------------------------------------------------- */

/* --- bands and panels ------------------------------------------------------

   A band is one titled region of a sheet, separated from what is next to it by
   a rule rather than by margin alone. It carries spacing and separation and no
   type at all.

   It used to carry type. This was `.field__label`'s small-caps rule — 11px,
   letter-spaced, uppercase — applied to a wrapping <div>, and text-transform
   and letter-spacing both inherit. Every hint, tool description, server name
   and URL inside one rendered in shouted capitals, on the only two pages that
   used it, which were the two the interface rework was asked to fix. Buttons
   and input values were unaffected, so it read as a deliberate style rather
   than a bug and survived a long time.

   The rule it leaves behind: type goes on the element that is text, never on
   the element that wraps it.
   -------------------------------------------------------------------------- */

.band {
    margin-top: 2.25rem;
    padding-top: 1.75rem;
    border-top: 1px solid var(--rule);
}

/* A band that opens a sheet, where the lede above it is separation enough. Not
   `:first-of-type`, which keys on the tag rather than the class: on the persona
   and sub-agent cards the first `div` sibling is the anchor field, so the rule
   silently applied to nothing and only ever worked on the tools page. */
.band--first { border-top: none; padding-top: 0; }

/* Deliberately the same face and size as `.field__label`: a band title and a
   field label are the same rank of thing, and giving them two looks would say
   they were not. Kept as its own rule because a band is not a field, and the
   next person to change one of them should not silently change the other. */
.band__title {
    display: block;
    margin: 0 0 0.9rem;
    font-family: var(--machine);
    font-size: 0.6875rem;
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-faint);
}

/* A list beside the thing it selects. Above the breakpoint the two sit side by
   side; below it they stack, which is the same arrangement read top to bottom
   rather than a second design. The breakpoint is wider than the site's 34rem
   one because two columns need the room, not because the phone case changed. */
.split {
    display: grid;
    grid-template-columns: minmax(0, 17rem) minmax(0, 1fr);
    gap: 2.5rem;
    align-items: start;
}

@media (max-width: 58rem) {
    .split { grid-template-columns: minmax(0, 1fr); gap: 2rem; }
}

/* A band whose contents are folded away until asked for. Used for the thing
   that is done once and then costs a third of the page forever — registering a
   server is the case that produced it. The summary is a plain label rather than
   a button: it has to say what is behind it, not that it opens. */
.fold__toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    min-height: 24px;
    font-family: var(--ui);
    font-weight: 500;
    font-size: 0.875rem;
    color: var(--ink-soft);
    cursor: pointer;
    list-style: none;
}

.fold__toggle::-webkit-details-marker { display: none; }

.fold__toggle::before {
    content: '+';
    font-family: var(--machine);
    color: var(--ink-faint);
}

.fold[open] .fold__toggle { color: var(--ink); }
.fold[open] .fold__toggle::before { content: '\2212'; }

.fold__body { margin-top: 1.5rem; }

/* A screen with nothing on it yet. Set in the reading face, because it is a
   sentence addressed to a person and not a record of anything. */
.empty {
    font-family: var(--speech);
    font-size: 1.0625rem;
    color: var(--ink-soft);
    max-width: var(--measure);
    margin: 0;
}

/* One control is working. Orange because something is in flight, which is the
   only thing orange ever means here. It replaces disabling the whole page:
   a page where every control greys out says something is wrong, not that one
   slow request is outstanding. */
.working {
    font-family: var(--machine);
    font-size: 0.6875rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    white-space: nowrap;
    color: var(--signal);
}

/* The question a destructive action asks before it does anything. Sits where
   the action was, so the answer is given in the place the question was raised. */
.confirm {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem;
    margin: 0.9rem 0 0;
    padding: 0.7rem 0.85rem;
    border-left: 3px solid var(--danger);
    background: var(--paper-sunk);
    max-width: var(--measure);
}

.confirm__ask {
    margin: 0;
    font-family: var(--ui);
    font-size: 0.875rem;
    color: var(--ink);
}

/* Who else loses something. Weight rather than colour: the panel is already
   bordered in --danger and its button is --danger, and a fourth red thing in a
   red box stops meaning anything. This is not a warning, it is the fact the
   screen otherwise cannot state — /servers names no assistant, so without this
   the one irreversible act on it has an invisible blast radius. */
.confirm__reach {
    display: block;
    margin-top: 0.35rem;
    font-weight: 600;
}

.field__check {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    font-family: var(--ui);
    font-size: 0.9375rem;
    color: var(--ink);
    cursor: pointer;
}

.field__check input {
    accent-color: var(--patina);
    width: 1.5rem;
    height: 1.5rem;
    margin: 0;
}

/* The sub-agents. A role's name is a token the assistant literally types, so it is set in the
   machine face for the same reason every other machine-generated string on the site is.

   The list sits in the narrow half of `.split` and the definition in the wide half, so the rows
   are read as an index and the fields keep the measure they need. */
.roles { list-style: none; margin: 0 0 1.25rem; padding: 0; }

.role {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: baseline;
    gap: 0.75rem;
    border-bottom: 1px solid var(--rule);
}

.role__pick {
    display: block;
    width: 100%;
    padding: 0.85rem 0;
    border: 0;
    background: none;
    text-align: left;
    cursor: pointer;
    font: inherit;
    color: inherit;
}

.role__name {
    display: block;
    font-family: var(--machine);
    font-size: 0.9375rem;
    color: var(--ink);
    overflow-wrap: anywhere;
}

/* The line the assistant chooses on, and the only thing that tells two rows apart. Held to two
   lines: this is the index, and the whole of it is one field away in the panel beside it. */
.role__when {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    margin-top: 0.15rem;
    font-family: var(--ui);
    font-size: 0.875rem;
    line-height: 1.4;
    color: var(--ink-soft);
}

.role--open .role__name { color: var(--patina); }

.role--off .role__name,
.role--off .role__when { color: var(--ink-faint); }

.role__marks {
    display: grid;
    justify-items: end;
    gap: 0.15rem;
    font-family: var(--machine);
    font-size: 0.75rem;
    letter-spacing: 0.04em;
    color: var(--ink-faint);
    padding-top: 0.9rem;
}

/* An edit held but not written. Orange because it is work in flight, which is the only thing
   orange means here — the same mark a provisional message and a pending wake carry. */
.role__unsaved { color: var(--signal); }

/* --- a definition beside its list -----------------------------------------

   The right half of `.split` on the sub-agents page. Its heading is the whole of what was
   broken: the editor was never hidden, it was indistinguishable from the page it sat on, so
   nothing said which sub-agent the eight fields belonged to.
   -------------------------------------------------------------------------- */

.panel { min-width: 0; }

.panel__head {
    padding-bottom: 1.25rem;
    margin-bottom: 1.75rem;
    border-bottom: 1px solid var(--ink);
}

.panel__title {
    margin: 0;
    font-family: var(--ui);
    font-weight: 600;
    font-size: 1.25rem;
    letter-spacing: -0.01em;
    overflow-wrap: anywhere;
}

.panel__name { font-family: var(--machine); color: var(--patina); }

.panel__what {
    margin: 0.3rem 0 0;
    font-family: var(--machine);
    font-size: 0.75rem;
    letter-spacing: 0.04em;
    color: var(--ink-faint);
}

.panel__what--unsaved { color: var(--signal); }

.panel__empty { margin: 0; }

/* --- the tools page --------------------------------------------------------

   Three bands in the order the questions get asked: what the assistant can
   reach right now, then each server as an object, then registration folded
   away. The first band is the page's answer to the question the page exists
   for, so it comes before any server and is readable without scrolling.
   -------------------------------------------------------------------------- */

/* The reach. A statement, not a control — every switch that changes it lives
   further down the page, next to the sentence saying what it opens. */
.reach__count {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    margin: 0 0 0.5rem;
}

.reach__figure {
    font-family: var(--machine);
    font-size: 1.75rem;
    line-height: 1;
    color: var(--ink);
}

.reach__of {
    font-family: var(--ui);
    font-size: 0.875rem;
    color: var(--ink-soft);
}

/* Tools grouped by where they come from, in the site's gutter arrangement: a
   machine token on the left, what it holds on the right. The origin is not
   upper-cased the way a state word is — a server's name is a token the
   assistant literally types, and shouting it would misquote it. */
.reach__group {
    display: grid;
    grid-template-columns: 7rem 1fr;
    column-gap: 1.25rem;
    padding: 0.7rem 0;
    border-top: 1px solid var(--rule);
}

.reach__origin {
    font-family: var(--machine);
    font-size: 0.75rem;
    color: var(--ink-soft);
    text-align: right;
    padding-top: 0.2rem;
    overflow-wrap: anywhere;
}

.reach__list {
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    gap: 0.2rem 1rem;
    margin: 0;
    padding: 0;
}

.reach__tool {
    font-family: var(--machine);
    font-size: 0.8125rem;
    color: var(--ink);
    overflow-wrap: anywhere;
}

/* The eight built-ins are part of the answer to "what is in front of the model
   this turn", but no switch here can remove them, so they are named and then
   got out of the way. */
.reach__rest { margin-top: 0.25rem; }

.reach__rest summary {
    font-family: var(--ui);
    font-size: 0.8125rem;
    color: var(--ink-soft);
    cursor: pointer;
    min-height: 24px;
    display: flex;
    align-items: center;
}

.reach__rest[open] summary { margin-bottom: 0.3rem; }

.reach__none {
    font-family: var(--ui);
    font-size: 0.875rem;
    color: var(--ink-soft);
    margin: 0.75rem 0 0;
    max-width: var(--measure);
}

/* Crowding is a degradation, not a loss, but --danger is the page's existing
   choice for it and a fourth colour would say less than the third does. */
.reach__crowded {
    margin: 0.9rem 0 0;
    font-family: var(--ui);
    font-size: 0.8125rem;
    color: var(--danger);
    max-width: var(--measure);
}

/* Tool servers. A server is an address and a name; the tools under it are a table because the
   two switches are the point and a table is the one shape that lets you read a column of them. */
.server {
    display: flex;
    align-items: baseline;
    gap: 0.75rem;
    flex-wrap: wrap;
}

.server__name { font-family: var(--machine); font-size: 0.9375rem; color: var(--ink); }

.server__url {
    font-family: var(--machine);
    font-size: 0.8125rem;
    color: var(--ink-faint);
    word-break: break-all;
}

/* A state word, upper-cased the way the errand list's is: this one is ours to
   write, unlike the server's own name beside it. */
.server__state {
    font-family: var(--machine);
    font-size: 0.6875rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    white-space: nowrap;
    color: var(--patina);
}

.server__state--off { color: var(--ink-faint); }
.server__state--bad { color: var(--danger); }

.server__facts {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.9rem;
    margin: 0.4rem 0 0;
    font-family: var(--machine);
    font-size: 0.75rem;
    color: var(--ink-faint);
}

.server__bad { color: var(--danger); }

/* The way to the screen that owns the switch this one does not. Inherits the
   facts line's mono and size so it sits among them rather than on top of them;
   the base link colour and underline are what make it a way out. Padded to the
   24px target floor and pulled back by the same amount, so the hit area grows
   and the flex line does not. */
.server__elsewhere {
    padding: 0.3rem 0;
    margin: -0.3rem 0;
}

.server__acts {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
    align-items: center;
    margin: 0.9rem 0 0;
}

/* Removing a server is the one destructive thing on this page and it used to be
   one click on a button identical to its two harmless neighbours. It is now a
   different shape entirely — no border, underlined, in --danger — and it opens
   a question rather than doing the thing. */
.server__remove {
    font-family: var(--ui);
    font-size: 0.875rem;
    color: var(--danger);
    background: none;
    border: 0;
    padding: 0.5rem 0.25rem;
    min-height: 24px;
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 3px;
}

.server__remove:disabled { color: var(--ink-faint); cursor: default; }

.tools {
    width: 100%;
    max-width: var(--measure);
    margin: 1.25rem 0 0.75rem;
    border-collapse: collapse;
    font-size: 0.875rem;
}

.tools th {
    padding: 0 0 0.5rem;
    border-bottom: 1px solid var(--rule);
    text-align: left;
    font-family: var(--ui);
    font-size: 0.6875rem;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-faint);
    vertical-align: bottom;
}

.tools th:not(:first-child) { width: 6rem; text-align: center; }

/* Wider than its neighbour because it carries the trust-boundary line below the label. */
.tools th:last-child { width: 9.5rem; }

/* The trust boundary, said at the column the switch is in rather than in a
   paragraph below the table. It resets the two inherited type properties its
   header sets — the whole of Phase 2 was one rule that forgot to. */
.tools__warn {
    display: block;
    margin-top: 0.25rem;
    font-family: var(--ui);
    font-size: 0.625rem;
    font-weight: 400;
    line-height: 1.3;
    letter-spacing: 0;
    text-transform: none;
    color: var(--danger);
}

.tools td { padding: 0.6rem 0; border-bottom: 1px solid var(--rule); vertical-align: top; }

.tools td:not(:first-child) { text-align: center; }

/* WCAG 2.2 SC 2.5.8 wants 24x24 CSS pixels, and a default checkbox is thirteen.
   These two columns are the reason this page exists, so they are sized rather
   than given an exception. */
.tools input {
    accent-color: var(--patina);
    width: 1.5rem;
    height: 1.5rem;
    margin: 0;
}

.tools__name { font-family: var(--machine); color: var(--ink); overflow-wrap: anywhere; }

.tools__sep { color: var(--ink-faint); }

.tools__what {
    display: block;
    margin-top: 0.15rem;
    font-family: var(--ui);
    color: var(--ink-soft);
}

.tools__gone { display: block; margin-top: 0.15rem; color: var(--danger); }

.tools__row--gone .tools__name { color: var(--ink-faint); text-decoration: line-through; }

@media (max-width: 34rem) {
    .reach__group { grid-template-columns: 1fr; row-gap: 0.3rem; }
    .reach__origin { text-align: left; padding-top: 0; }

    /* The two switch columns keep the width a 24px target needs and give the rest
       back to the name, which otherwise breaks mid-word to fit what is left. */
    .tools th:not(:first-child),
    .tools th:last-child { width: 3.75rem; }
}

.errands { list-style: none; margin: 1rem 0 0; padding: 0; }

.errand {
    display: grid;
    grid-template-columns: 5rem 1fr;
    column-gap: 1.25rem;
    padding: 1.25rem 0;
    border-top: 1px solid var(--rule);
}

.errand__state {
    margin: 0;
    font-family: var(--machine);
    font-size: 0.6875rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-faint);
    text-align: right;
    padding-top: 0.2rem;
}

.errand--open .errand__state { color: var(--signal); }
.errand--failed .errand__state { color: var(--danger); }

.errand__body { min-width: 0; max-width: var(--measure); }

.errand__title {
    margin: 0;
    font-size: 1rem;
    line-height: 1.35;
    color: var(--ink);
}

.errand__facts {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.9rem;
    margin: 0.35rem 0 0;
    font-family: var(--machine);
    font-size: 0.75rem;
    color: var(--ink-faint);
}

.errand--open .errand__deadline { color: var(--signal); }

.errand__failure {
    margin: 0.6rem 0 0;
    font-family: var(--speech);
    font-size: 0.9375rem;
    color: var(--danger);
}

.errand .trace { margin-top: 0.6rem; }

@media (max-width: 34rem) {
    .errand { grid-template-columns: 1fr; row-gap: 0.35rem; }
    .errand__state { text-align: left; padding-top: 0; }
}

/* --- wakes ----------------------------------------------------------------

   The gutter carries the time, as it does in the thread — for a wake that is
   the organising fact, and the same column in the same place means the two
   pages read as one instrument. Orange because a wake is in flight in the
   only sense that matters: it has not happened yet and it will.
   -------------------------------------------------------------------------- */

.wakes { list-style: none; margin: 2.5rem 0 0; padding: 0; }

.wake {
    display: grid;
    grid-template-columns: 5rem 1fr;
    column-gap: 1.25rem;
    padding: 1.25rem 0;
    border-top: 1px solid var(--rule);
}

.wake__due {
    margin: 0;
    padding-top: 0.15rem;
    font-family: var(--machine);
    font-size: 0.75rem;
    color: var(--signal);
    text-align: right;
    display: grid;
    gap: 0.15rem;
}

.wake__away { color: var(--ink-faint); }

.wake__body { min-width: 0; max-width: var(--measure); }

.wake__note {
    margin: 0;
    font-family: var(--speech);
    font-size: 1.0625rem;
    line-height: 1.45;
    color: var(--ink);
}

.wake__facts {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.25rem 0.9rem;
    margin: 0.35rem 0 0;
    font-family: var(--machine);
    font-size: 0.75rem;
    color: var(--ink-faint);
}

.wake__provisional { color: var(--signal); }

.wakes__past { margin-top: 2.5rem; max-width: var(--measure); }

.wake__gone {
    padding: 0.6rem 0;
    border-top: 1px solid var(--rule);
}

.wake__gone:first-child { border-top: none; padding-top: 0; }

.wake__gone .wake__note { font-size: 0.9375rem; color: var(--ink-soft); }

.wake__came { color: var(--patina); }
.wake__called-off { color: var(--ink-faint); }

@media (max-width: 34rem) {
    .wake { grid-template-columns: 1fr; row-gap: 0.35rem; }
    .wake__due { text-align: left; grid-auto-flow: column; justify-content: start; gap: 0.6rem; padding-top: 0; }
}

/* --- standing notes -------------------------------------------------------

   The same gutter again, holding the reference the assistant addresses a
   note by. A note is the assistant's own writing about a person, so the
   text is set in the speech face rather than the interface one; everything
   around it is machine record.
   -------------------------------------------------------------------------- */

.notes__budget {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1rem;
    margin: 2rem 0 0;
    padding-bottom: 0.6rem;
    border-bottom: 1px solid var(--rule);
    font-family: var(--machine);
    font-size: 0.75rem;
    color: var(--ink-faint);
    max-width: var(--measure);
}

.notes__budget--over { color: var(--signal); }
.notes__budget--ceiling { color: var(--danger); }

.notes { list-style: none; margin: 0; padding: 0; }

.note {
    display: grid;
    grid-template-columns: 5rem 1fr;
    column-gap: 1.25rem;
    padding: 1.25rem 0;
    border-bottom: 1px solid var(--rule);
}

.note__ref {
    margin: 0;
    padding-top: 0.15rem;
    font-family: var(--machine);
    font-size: 0.75rem;
    color: var(--ink-faint);
    text-align: right;
}

.note__body { min-width: 0; max-width: var(--measure); }

.note__text {
    margin: 0;
    font-family: var(--speech);
    font-size: 1.0625rem;
    line-height: 1.5;
    color: var(--ink);
}

.note__facts {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.25rem 0.9rem;
    margin: 0.35rem 0 0;
    font-family: var(--machine);
    font-size: 0.75rem;
    color: var(--ink-faint);
}

.note__provisional { color: var(--signal); }

.note__actions { display: flex; gap: 0.5rem; margin: 0.6rem 0 0; }

.note__version {
    padding: 0.6rem 0;
    border-top: 1px solid var(--rule);
}

.note__version:first-child { border-top: none; padding-top: 0; }

.note__version .note__text { font-size: 0.9375rem; color: var(--ink-soft); }

/* The one control in a run of metadata, so it is padded to the 24px target
   floor rather than left at the line's own height. */
.note__restore {
    font: inherit;
    color: var(--patina);
    background: none;
    border: none;
    border-bottom: 1px solid transparent;
    padding: 0.3rem 0;
    cursor: pointer;
}

.note__restore:hover { border-bottom-color: var(--patina); }

.notes__forgotten { margin-top: 2rem; max-width: var(--measure); }

.notes__add { margin-top: 2.5rem; max-width: var(--measure); }
.notes__add .field__area { margin-bottom: 0.75rem; }

/* --- sign-in -------------------------------------------------------------- */

.signin {
    min-height: 100dvh;
    display: grid;
    align-content: center;
    padding: 2rem 1.5rem;
}

.signin__inner {
    max-width: 20rem;
    border-left: 1px solid var(--rule);
    padding-left: 1.75rem;
}

.signin__name {
    font-family: var(--ui);
    font-weight: 600;
    font-size: 0.8125rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-faint);
    margin: 0 0 0.75rem;
}

.signin__line {
    font-family: var(--speech);
    font-size: 1.25rem;
    line-height: 1.4;
    margin: 0 0 2.25rem;
    max-width: 18rem;
}

/* --- motion --------------------------------------------------------------- */

@keyframes arrive {
    from { opacity: 0; transform: translateY(2px); }
    to { opacity: 1; transform: none; }
}

.entry { animation: arrive 160ms ease-out; }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

/* --- narrow --------------------------------------------------------------- */

@media (max-width: 34rem) {
    :root { --gutter: 3.25rem; }

    .thread, .composer, .sheet { padding-left: 1rem; padding-right: 1rem; }
    .entry__body, .gap__line, .composer__field { padding-left: 1rem; }
    .marker { margin-left: -1rem; padding-left: 1rem; }
    .bar { padding-left: 1rem; padding-right: 1rem; gap: 1rem; }
}

/* --- accessibility -------------------------------------------------------- */

/* The page heading each screen needs for a screen reader, where the design's own
   typography already says what the screen is. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* --- the settings page -----------------------------------------------------

   Two bands, in the order the questions get asked: what the assistant runs on,
   then what it is holding. The second band states the number before anything
   offers to remove it — the tools page's arrangement, and for the same reason.
   -------------------------------------------------------------------------- */

/* Putting a model back. Sits under the field it undoes rather than beside the
   save button: it is a property of what the model *was*, not a second way to
   save. */
.revert {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.75rem;
    margin: -1rem 0 1.75rem;
    max-width: var(--measure);
}

.revert__was {
    font-family: var(--machine);
    font-size: 0.75rem;
    color: var(--ink-faint);
}

/* The two sampling fields can stop the assistant answering, and only while they
   hold a value — so this appears when they do and is silent when they do not. It
   is `--signal` because something *is* in flight in the sense that matters here:
   a value is going to every turn from now until it is emptied. */
.caution {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.75rem;
    margin: 0 0 1.75rem;
    padding: 0.7rem 0.85rem;
    border-left: 3px solid var(--signal);
    background: var(--paper-sunk);
    max-width: var(--measure);
    font-family: var(--ui);
    font-size: 0.875rem;
    line-height: 1.5;
    color: var(--ink);
}

/* One row per scope: the count on the left in the machine face, what it is and
   what it takes on the right. The same gutter arrangement as `.reach__group` on
   the tools page, because it is the same shape of statement — a machine-read
   figure, and the sentence it belongs to. */
.scope {
    display: grid;
    grid-template-columns: 4.5rem minmax(0, 1fr);
    column-gap: 1.25rem;
    padding: 0.9rem 0;
    border-top: 1px solid var(--rule);
    align-items: start;
}

.scope__figure {
    font-family: var(--machine);
    font-size: 1.125rem;
    line-height: 1.4;
    color: var(--ink);
    text-align: right;
}

/* A zero is not a quantity here, it is the absence of one, and setting it in the
   same weight as 153 makes the page look like it holds five things when it holds
   none. */
.scope__figure--none { color: var(--ink-faint); }

.scope__what {
    margin: 0 0 0.6rem;
    font-family: var(--ui);
    font-size: 0.9375rem;
    line-height: 1.5;
    color: var(--ink);
}

.scope__name { color: var(--ink); }

.scope__takes {
    display: block;
    font-size: 0.875rem;
    color: var(--ink-soft);
}

/* Said on the row as well as in the confirmation. The confirmation is the last
   word, but a consequence only revealed after the button is pressed is one the
   owner had no chance to weigh. */
.scope__implies {
    margin-left: 0.75rem;
    font-family: var(--ui);
    font-size: 0.8125rem;
    color: var(--signal);
}

/* Below the breakpoint the figure column costs more than it carries: the number
   goes above its sentence rather than shrinking into a third of the width. */
@media (max-width: 34rem) {
    .scope { grid-template-columns: minmax(0, 1fr); row-gap: 0.35rem; }
    .scope__figure { text-align: left; }
    .scope__implies { display: block; margin-left: 0; margin-top: 0.4rem; }
}

/* The figures under a model field: what the model in the box holds and what it
   costs, from the estate's catalogue. Attached to the field rather than to the
   list of choices, because the list is a convenience and this is the decision.

   Wrapping rather than a row of columns: there are two or three facts and they
   are different lengths, so a grid would leave a ragged gap on every model whose
   catalogue entry is incomplete. */
.figures {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 0.9rem;
    margin: 0.45rem 0 0;
    max-width: var(--measure);
    font-family: var(--ui);
    font-size: 0.8125rem;
    line-height: 1.5;
    color: var(--ink-soft);
}

/* A figure the catalogue gave us. Machine face, because it is a machine-read
   number and every other one on the site is set that way. */
.figures__fact {
    font-family: var(--machine);
    font-size: 0.75rem;
    color: var(--ink-soft);
}

/* Not knowing is a different thing from a bad answer, and reads quieter. */
.figures__unknown { color: var(--ink-faint); }

/* A model that cannot hold the working set. `--signal` rather than `--danger`:
   nothing is broken yet, it will break as the conversation grows — which is the
   distinction the two colours carry everywhere else on the site. */
.figures__warn {
    flex-basis: 100%;
    color: var(--signal);
}

/* Where the list came from and how fresh it is. A picker built on a silently
   stale catalogue is worse than no picker, because it looks authoritative. */
.figures__source {
    margin-top: 0.6rem;
    font-size: 0.75rem;
    color: var(--ink-faint);
}

/* --- the assistant switcher, and the list of them -------------------------- */

/* Where the application's name used to be. With several assistants, "what is this" and "which
   one am I talking to" have the same answer, so the name in the corner is the switch. */
.switch {
    position: relative;
    margin: 0;
    min-width: 0;
}

.switch__here {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    /* 24px minimum target, WCAG 2.5.8, met by the padding rather than by a height that would
       push the bar off the number --bar carries. */
    padding: 0.35rem 0.25rem;
    margin: -0.35rem -0.25rem;
    cursor: pointer;
    list-style: none;
    color: var(--ink);
    font-family: var(--ui);
    font-size: 1.05rem;
    font-weight: 500;
    letter-spacing: -0.01em;
    border-radius: 2px;
}

.switch__here::-webkit-details-marker { display: none; }

.switch__here:hover { color: var(--patina); }

.switch__name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 11rem;
}

/* The same caret the folded nav uses, turned the same way. One vocabulary in one bar. */
.switch__caret::after {
    content: '\203A';
    display: inline-block;
    color: var(--ink-faint);
    transition: transform 120ms ease;
}

.switch[open] .switch__caret::after { transform: rotate(90deg); }

/* Patina, not signal: --signal means something is still happening, and a reply that has
   already arrived is the opposite of in flight. Never the only carrier — every mark has a
   visually-hidden word beside it (WCAG 1.4.1). */
.switch__unseen {
    width: 0.45rem;
    height: 0.45rem;
    border-radius: 50%;
    background: var(--patina);
    flex: none;
}

.switch__unseen--summary { margin-left: 0.1rem; }

.switch__panel {
    position: absolute;
    top: calc(100% + 0.6rem);
    left: -0.25rem;
    z-index: 3;
    min-width: 13rem;
    max-width: 20rem;
    padding: 0.4rem;
    background: var(--paper);
    border: 1px solid var(--rule);
    border-radius: 2px;
}

.switch__list {
    margin: 0;
    padding: 0;
    list-style: none;
}

.switch__to {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.45rem 0.55rem;
    color: var(--ink-soft);
    text-decoration: none;
    border-radius: 2px;
}

.switch__to:hover { color: var(--ink); background: var(--paper-sunk); }

.switch__to-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.switch__all {
    display: block;
    margin-top: 0.3rem;
    padding: 0.45rem 0.55rem;
    border-top: 1px solid var(--rule);
    color: var(--ink-faint);
    font-size: 0.85rem;
    text-decoration: none;
}

.switch__all:hover { color: var(--ink); }

/* The list screen. Not a table: what is worth knowing about an assistant is a name, a link and
   a couple of counts, and four of those in a row would be a grid of mostly zeros. */
.roster {
    margin: 0;
    padding: 0;
    list-style: none;
}

.roster__one {
    padding: 1.1rem 0 1.2rem 1rem;
    border-top: 1px solid var(--rule);
    border-left: 2px solid transparent;
}

.roster__one:first-child { border-top: none; }

/* The one you are on is marked by a rule and by a word, never by colour alone. */
.roster__one--here { border-left-color: var(--patina); }

.roster__head {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.6rem;
}

.roster__name {
    color: var(--ink);
    font-size: 1.15rem;
    font-weight: 500;
    text-decoration: none;
    border-bottom: 1px solid transparent;
}

.roster__name:hover { border-bottom-color: var(--patina); }

.roster__mark {
    color: var(--patina);
    font-size: 0.8rem;
    letter-spacing: 0.02em;
}

.roster__link {
    margin: 0.2rem 0 0;
    color: var(--ink-faint);
    font-family: var(--machine);
    font-size: 0.85rem;
}

.roster__held {
    display: flex;
    flex-wrap: wrap;
    gap: 0.45rem;
    margin: 0.45rem 0 0;
    color: var(--ink-soft);
    font-size: 0.9rem;
}

.roster__sep { color: var(--rule); }

.roster__busy { color: var(--signal); }

/* Quiet until it is wanted: deleting an assistant is rare, and a danger-coloured button on
   every row would make the list read as a page about deleting them. */
.roster__remove {
    margin-top: 0.7rem;
    padding: 0.35rem 0;
    background: none;
    border: none;
    border-bottom: 1px solid transparent;
    color: var(--ink-faint);
    font-family: var(--ui);
    font-size: 0.85rem;
    cursor: pointer;
}

.roster__remove:hover { color: var(--danger); border-bottom-color: var(--danger); }

.roster__only {
    margin: 0.7rem 0 0;
    color: var(--ink-faint);
    font-size: 0.85rem;
}

@media (prefers-reduced-motion: reduce) {
    .switch__caret::after { transition: none; }
}

@media (max-width: 46rem) {
    .switch__name { max-width: 7.5rem; }
    .switch__panel { max-width: calc(100vw - 2rem); }
}

/* --- deleting a sub-agent -------------------------------------------------- */

/* Quiet until it is wanted, like the roster's: deleting a sub-agent is rare, and a danger
   button under every definition would make the panel read as a page about removing them. */
.role__delete {
    display: block;
    margin-top: 1.5rem;
    padding: 0.35rem 0;
    background: none;
    border: none;
    border-bottom: 1px solid transparent;
    color: var(--ink-faint);
    font-family: var(--ui);
    font-size: 0.85rem;
    cursor: pointer;
}

.role__delete:hover { color: var(--danger); border-bottom-color: var(--danger); }

/* The name an errand ran under, once there is no row to read it from. Faint and in the same
   monospace slot the name occupied, so the line keeps its shape. */
.errand__gone {
    color: var(--ink-faint);
    font-family: var(--machine);
    font-style: italic;
}
