/* ---------------------------------------------------------------------------
   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 `.bar__nav`. */
    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 seven destinations divide the way the application does: the conversation,
   what it is currently doing and holding, and how it is configured. The grouping
   is carried by the gaps and nothing else — a label or a divider per group would
   be three more things to read on a bar nobody came here to read. */
.bar__nav { display: flex; gap: 2.25rem; margin-left: auto; align-items: baseline; }

.bar__group { display: flex; gap: 1.1rem; align-items: baseline; }

/* Sign-out is not a destination and does not sit in a list of them. */
.bar__out { margin-left: 0.75rem; }

.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); }
.bar__link[aria-current='page'] { color: var(--ink); border-bottom-color: var(--patina); }

.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; }

.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; }

.bar__more-panel { display: flex; gap: 2.25rem; 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. */
    .bar__more { display: block; position: relative; }

    .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;
    }

    .bar__group { flex-direction: column; align-items: flex-end; gap: 0.9rem; }

    .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; }
}

/* --- 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);
}

.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); }

.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; }
    .bar__nav { gap: 0.9rem; }
}

/* --- 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;
}
