/* ---------------------------------------------------------------------------
   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: #56635b;
    --ink-faint: #8a968e;
    --rule: #ccd4ca;

    /* 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: #b64f18;

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

    --measure: 34rem;
}

@media (prefers-color-scheme: dark) {
    :root {
        --paper: #12160f;
        --paper-sunk: #1a1f18;
        --ink: #e3e8df;
        --ink-soft: #9aa598;
        --ink-faint: #6a7568;
        --rule: #2e3830;
        --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 - 3.4rem);
}

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

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

.bar {
    display: flex;
    align-items: baseline;
    gap: 1.5rem;
    padding: 0.9rem 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;
}

.bar__nav { display: flex; gap: 1.25rem; margin-left: auto; align-items: baseline; }

.bar__link {
    font-family: var(--machine);
    font-size: 0.75rem;
    color: var(--ink-soft);
    text-decoration: none;
    padding-bottom: 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 0 2px;
    cursor: pointer;
    border-bottom: 1px solid transparent;
}

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

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

.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;
    gap: 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-block;
    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;
    gap: 1rem;
    font-family: var(--machine);
    font-size: 0.6875rem;
    color: var(--ink-soft);
    padding: 0.15rem 0;
}

.trace__took { color: var(--ink-faint); white-space: nowrap; }

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

.thread__empty {
    grid-column: 2;
    font-family: var(--speech);
    font-size: 1.0625rem;
    color: var(--ink-soft);
    max-width: var(--measure);
}

/* --- 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;
    display: flex;
    gap: 0.75rem;
    align-items: flex-end;
    border-left: 1px solid var(--ink);
    padding-left: 1.5rem;
}

.composer__input {
    flex: 1;
    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); }
.composer__input:focus { 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(--rule);
}

.button--quiet:hover { color: var(--ink); background: none; 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); }

.field__label {
    display: block;
    font-family: var(--machine);
    font-size: 0.6875rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-faint);
    margin-bottom: 0.4rem;
}

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

.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(--rule);
    border-radius: 2px;
    padding: 0.55rem 0.7rem;
}

.field__input:focus,
.field__area:focus {
    border-color: var(--ink);
    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. */
.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.
   -------------------------------------------------------------------------- */

.sheet__section {
    margin: 2.5rem 0 0;
    font-size: 0.6875rem;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-faint);
}

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

.note__restore {
    font: inherit;
    color: var(--patina);
    background: none;
    border: none;
    border-bottom: 1px solid transparent;
    padding: 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;
}
