/*
 * Stetworth /app — the signed-in page family: the two standalone
 * pages' measures, the first-run band, the in-flight strip, and the
 * assistant conversation surface — cut from style.css's "The
 * signed-in page family" section unchanged and in the original
 * order.  The .assistant-draft-btn cluster and the
 * .assistant-draft-item-meta rule stay behind in style.css: each must
 * beat a shared button or micro-label group there on source order at
 * equal specificity.
 */
/*
 * Load order is load-bearing: the /app shell links every split sheet
 * BEFORE style.css.  That keeps both source-order contracts intact —
 * a rule kept behind in style.css still beats its shared group there
 * by file order, and the responsive tail at the end of style.css
 * still overrides this sheet's base rules at equal specificity.
 * This sheet declares no design-token block of its own and carries
 * no media block of any kind — every width, motion and paged
 * override for these rules lives in style.css's responsive tail
 * (docs/APP.md#the-split-stylesheets).
 */

/* ============================================================
 * The signed-in page family (docs/APP.md#routing)
 *
 * Four pages share one header: the composer and gallery together,
 * the composer alone (#/new), the gallery alone (#/tasks), and the
 * assistant's page (#/), which carries a slim strip of the work
 * still running above the conversation.
 *
 * The two standalone pages need nothing of their own beyond a
 * comfortable measure: they are the same composer and the same gallery
 * the shared page renders, so anything more here would be a second
 * treatment of one component.  Constraining them to the same 880px
 * measure is what stops a lone composer stretching to 1280px on a
 * wide screen.
 * ============================================================ */

.page-composer-only .composer,
.page-tasks-only .task-gallery {
    max-width: 880px;
    margin-left: auto;
    margin-right: auto;
}

/* On `#/new` the page heading already says "New task", and the
   composer's visible "New Task" label sat directly beneath it — the
   same two words twice, in clashing capitalization, as the top of the
   page (the de-duplication spec 3 §4h parked on the screenshot gate;
   the rendered review called it).  The label is DEMOTED, not removed:
   it keeps naming the textarea for assistive tech — a job the page
   heading cannot take over — via the standard clipped
   visually-hidden declaration, scoped to this page alone because the
   flag-off combined home renders the same composer under a "Home"
   heading, where "New Task" is information rather than an echo. */
.page-composer-only .composer-label {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* The first-run band: a brand-new account's first-class path into
   work, above the in-flight strip.  The mount ships empty on every
   entry and collapses through `:empty` (the `.inflight-strip` pattern
   below — no hidden attribute, no chrome around nothing);
   `_syncFirstRunState()` fills it only once both of the page's reads
   have resolved holding zero tasks and zero turns, and empties it
   again the moment either read — or a send's local echo — says
   otherwise. */
.first-run {
    display: flex;
    flex-direction: column;
    gap: var(--s-3);
    max-width: 880px;
    margin: 0 auto var(--s-5);
    width: 100%;
}

.first-run:empty { display: none; }

/* The lede takes the strip's own quiet register — one accent-italic
   sentence of context — because it states a fact, and the anchor under
   it is what must carry the weight. */
.first-run-lede {
    margin: 0;
    font-family: var(--font-accent);
    font-style: italic;
    font-size: var(--fs-body);
    letter-spacing: 0.01em;
    color: var(--c-ink-muted);
}

/* The call to action is a `.btn.btn-primary` anchor to `#/new` — a
   real route, never an in-page fragment.  Content-sized on purpose:
   the column's default stretch would pull the anchor to the full
   880px measure, and a button the width of the page reads as a
   banner, not a control. */
.first-run-cta {
    align-self: flex-start;
}

/* The in-flight strip: the work still running, above the conversation.
   Deliberately slim — one line of context and a row of chips — because
   the task's own page is where progress is read.  It collapses entirely
   when nothing is running (the mount is left empty rather than filled
   with an empty state, so there is no chrome around nothing). */
.inflight-strip {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s-2);
    max-width: 880px;
    margin: 0 auto var(--s-5);
    width: 100%;
}

.inflight-strip:empty { display: none; }

/* The deck's second home.  With the assistant on, the suggestion deck
   is mounted at `#/` between this strip and the conversation rather
   than above the composer, so it takes the same column those two share
   — a deck running the full page width beside an 880px thread would
   read as another page's furniture.  Selected off the strip it follows
   rather than off a class of its own, because the section fragment is
   the one the composer mounts too and gains nothing from knowing which
   page it is on. */
.inflight-strip + .suggestion-deck {
    max-width: 880px;
    margin-left: auto;
    margin-right: auto;
}

.inflight-lede {
    flex-basis: 100%;
    margin: 0;
    font-family: var(--font-accent);
    font-style: italic;
    font-size: var(--fs-body);
    letter-spacing: 0.01em;
    color: var(--c-ink-muted);
}

/* A quiet ledger row, not a chip: no box, just an underline that
   strengthens on hover.  Still an inline-flex <a>. */
.inflight-item {
    display: inline-flex;
    align-items: baseline;
    gap: var(--s-2);
    padding: 0 0 2px;
    border: 0;
    border-bottom: 1px solid var(--c-rule-strong);
    border-radius: 0;
    background: transparent;
    font-size: var(--fs-ui);
    color: var(--c-ink-soft);
}

.inflight-item:hover {
    border-bottom-color: var(--c-ink);
    color: var(--c-ink);
}

/* The live dot ahead of each running row.  Presentation only (empty
   content), so the polite live region announces nothing new; the strip
   repaints only on frames that moved the thread.  Reuses the existing
   pill-pulse keyframes — no new frames. */
.inflight-item::before {
    content: '';
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--c-burnished);
    align-self: center;
    animation: pill-pulse 2.4s ease-in-out infinite;
}

.inflight-item-name {
    font-weight: 500;
    max-width: 260px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The status word, never colour alone — set as a tracked eyebrow so it
   reads as ledger metadata rather than sentence text. */
.inflight-item-status {
    color: var(--c-ink-muted);
    white-space: nowrap;
}

/* The needs-you marker, on a row whose task has a clarification
   question open in the thread below.  Words plus the gold accent,
   never colour alone — and the text tier of the gold, because a
   marker asking for the person's attention must clear the contrast
   floor for reading, not decoration. */
.inflight-item-needs-you {
    font-size: var(--fs-meta);
    font-weight: 500;
    color: var(--c-burnished-text);
    white-space: nowrap;
}

/* The conversation itself: the turn list, and one bubble per turn.
   The section is always present while the assistant is on (the mount
   is what lets a turn arrive as a paint rather than a re-render); what
   collapses when there is nothing to show is the empty turn list, the
   `.inflight-strip:empty` rule again — the pattern every collapsing
   mount on this page shares, the first-run band included.  No chrome
   around nothing. */
.assistant-thread {
    display: flex;
    flex-direction: column;
    gap: var(--s-4);
    max-width: 880px;
    margin: 0 auto;
    width: 100%;
    /* The raised paper: a lighter sheet on the parchment ground, an
       oxblood head rule, and the settle shadow.  Every state renders
       inside this one sheet — including the unresolved read, where the
       chrome standing while the list paints nothing is what makes
       "paint nothing" look deliberate. */
    background: var(--c-paper);
    border: 1px solid var(--c-rule-strong);
    border-top: 3px solid var(--c-oxblood);
    border-radius: var(--radius-sm) var(--radius-sm) var(--radius) var(--radius);
    box-shadow: var(--sh-sheet);
    padding: var(--s-5) var(--s-6) var(--s-4);
}

/* Letterhead: a double hairline rule drawn with borders — no words, so
   there is no copy to govern.  A flex item above the turn list in every
   state, including the unresolved read. */
.assistant-thread::before {
    content: '';
    height: 5px;
    border-top: 1px solid var(--c-rule);
    border-bottom: 1px solid var(--c-rule);
}

/* The turn list.  It scrolls within its own box rather than growing the
   page, because the paint pins `scrollTop` to `scrollHeight` — a
   conversation that appended below the fold without following it would
   look, to the person watching, exactly like one that stopped. */
.assistant-turns {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0;
    max-height: 68vh;
    overflow-y: auto;
    width: 100%;
    scrollbar-width: thin;
    scrollbar-color: var(--c-rule-strong) transparent;
}

.assistant-turns:empty { display: none; }

/* Turns are correspondence on the sheet, not bubbles: full-width
   entries separated by dotted ledger rules, the last one open so the
   list hands off cleanly to the reply slip below. */
.assistant-turn {
    align-self: stretch;
    max-width: none;
    width: 100%;
    padding: var(--s-4) 0;
    border: 0;
    border-bottom: 1px dotted var(--c-rule);
    border-radius: 0;
    background: transparent;
    font-size: var(--fs-body);
    line-height: 1.6;
    color: var(--c-ink);
}

/* `:where()` keeps this at (0,1,0) so the framed modifiers below —
   the clarification memo, the late notice, the draft memo — keep their
   own bottom edges when they are the newest turn, which for a pending
   clarification is the usual case.  A bare `:last-child` here would
   outweigh every one of them. */
.assistant-turn:where(:last-child) { border-bottom: 0; }

/* The person's own turns keep their trailing edge — the support
   widget's arrangement — but as a small filed note on the soft fill,
   its lower-trailing corner clipped the same way as that widget's user
   side, rather than as a full-width entry. */
.assistant-turn-user {
    align-self: stretch;
    background: transparent;
    border-color: var(--c-rule);
    display: flex;
    justify-content: flex-end;
}

.assistant-turn-user .assistant-turn-body {
    background: var(--c-paper-soft);
    border: 1px solid var(--c-rule-strong);
    border-radius: 12px 12px var(--radius-sm) 12px;
    padding: var(--s-2) var(--s-3);
    max-width: min(75%, 480px);
}

/* Replies are typeset: the serif accent at reading size, on a measure
   capped for long-form legibility. */
.assistant-turn-assistant {
    align-self: stretch;
    font-family: var(--font-accent);
    font-size: var(--fs-lede);
    line-height: 1.7;
}

.assistant-turn-assistant .assistant-turn-body {
    max-width: var(--measure-turn);
}

/* Actionable turns (drafts, clarifications — the only turns that
   render an actions row) go back to the working sans: their bodies are
   instructions and controls, not typeset replies. */
.assistant-turn-assistant:has(.assistant-turn-actions) {
    font-family: var(--font-body);
    font-size: var(--fs-body);
    line-height: 1.6;
}

.assistant-turn-assistant:has(.assistant-turn-actions) .assistant-turn-body {
    max-width: none;
}

/* A turn body is plain text: newlines the person typed are the only
   structure it carries, and they are preserved rather than collapsed. */
.assistant-turn-body {
    margin: 0;
    white-space: pre-wrap;
}

/* A pending turn says what is being read.  Quieter than an answer, and
   still legible text — --c-ink-muted is the AA-cleared secondary tier,
   never the decorative --c-ink-faint. */
.assistant-turn-pending .assistant-turn-body {
    color: var(--c-ink-muted);
    font-style: italic;
    font-family: var(--font-accent);
    font-size: var(--fs-base);
}

/* The pending turn's live dot — same presentation-only device as the
   strip's, on the existing pill-pulse frames. */
.assistant-turn-pending .assistant-turn-body::before {
    content: '';
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--c-burnished);
    margin-right: var(--s-2);
    animation: pill-pulse 2.4s ease-in-out infinite;
}

/* The failed-first-read state.  Painted into the turn list's own mount
   by `_paintAssistantThreadError()` when an entry's first read rejects
   — never sibling chrome, so nothing here ships `hidden` and the
   `:empty` collapse above un-collapses because content landed.  A
   later successful read repaints straight over it.  The copy takes the
   inline error's own dress — the brick accent as a left rule — and the
   retry control is the stock small secondary button. */
.assistant-thread-error {
    list-style: none;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-3);
    padding: var(--s-4) 0;
}

.assistant-thread-error p {
    margin: 0;
    font-size: var(--fs-ui);
    color: var(--c-status-failed);
    border-left: 3px solid var(--c-status-failed);
    padding-left: var(--s-2);
}

/* The handoff, under the draft it belongs to.  It sits inside the
   bubble rather than beside it because it acts on that turn's text and
   nothing else, and a control floating between two turns would be
   ambiguous about which one it would carry away. */
.assistant-turn-actions {
    display: flex;
    justify-content: flex-start;
    margin-top: var(--s-3);
}
/* Single-draft memo: the turn that carries the handoff control reads
   as a filed memo awaiting sign-off — gold left rule on the soft fill.
   Gated on `[data-assistant-draft]`, the handoff control's own
   attribute, because the batch list's per-item send controls share the
   `.assistant-draft-btn` class (they carry `data-assistant-draft-send`
   instead) and a class gate would wrap every batch turn in this memo
   dress on top of its own ledger treatment. */
.assistant-turn-assistant:has([data-assistant-draft]) {
    background: var(--c-paper-soft);
    border: 1px solid var(--c-rule);
    border-left: 3px solid var(--c-burnished);
    border-radius: var(--radius-sm);
    padding: var(--s-4);
    margin: var(--s-2) 0;
}

/* The batch list: two or more drafted assignments in one bubble,
   rendered as a reviewable column — each item its own editor, its own
   send control and its own outcome line, because no control on this
   surface may act on more than one draft.  The actions row goes
   column-wise for the clarification row's reason: a box above its
   button, one item after the other. */
.assistant-draft-list {
    flex-direction: column;
    align-items: stretch;
    gap: var(--s-4);
}

.assistant-draft-item {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-2);
}

/* Ledger rules between batch items — every item after the first opens
   with the same dotted hairline the turn list uses. */
.assistant-draft-item + .assistant-draft-item {
    border-top: 1px dotted var(--c-rule);
    padding-top: var(--s-3);
}

/* The thread's editing well, shared by both boxes a person edits
   before their own explicit send — the batch item's draft editor and
   the clarification answer.  One declaration on purpose: the two wells
   were byte-identical apart from their minimum height, and two copies
   were two chances to restyle one act.  The clarification box keeps
   the shallower floor (a two-row answer), the draft editor the deeper
   one (a whole assignment). */
.assistant-clarification-input,
.assistant-draft-input {
    width: 100%;
    min-height: 48px;
    resize: vertical;
    font-family: var(--font-body);
    font-size: var(--fs-body);
    line-height: 1.5;
    color: var(--c-ink);
    background: var(--c-paper);
    border: 1px solid var(--c-rule-strong);
    border-radius: var(--radius-sm);
    padding: var(--s-2) var(--s-3);
    box-shadow: var(--sh-well);
    transition: border-color var(--t-base);
}

.assistant-draft-input { min-height: 96px; }

.assistant-clarification-input:focus,
.assistant-draft-input:focus {
    outline: none;
    border-color: var(--c-burnished);
    box-shadow: var(--sh-well), var(--focus-ring);
}

/* The per-item outcome line — sent, or why that send failed.  Empty
   until there is an outcome to state; `:empty` collapses it so an item
   with nothing to report carries no gap. */
.assistant-draft-item-status {
    margin: 0;
    font-size: var(--fs-ui);
    color: var(--c-ink-soft);
}

.assistant-draft-item-status:empty {
    display: none;
}

/* The submitted-build bubble: what the assistant already filed, under
   the answer that filed it (docs/ASSISTANT.md#the-submitted-build-bubble).
   Column-wise for the batch list's reason — a report, then the task it
   names, then the one control that undoes it. */
.assistant-submit-list {
    flex-direction: column;
    align-items: stretch;
    gap: var(--s-4);
}

.assistant-submit-item {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-2);
}

/* Ledger rules between filed builds — the batch items' hairline, so a
   turn that filed two reads as two entries rather than one paragraph. */
.assistant-submit-item + .assistant-submit-item {
    border-top: 1px dotted var(--c-rule);
    padding-top: var(--s-3);
}

/* The report line: past tense, quiet, and the first thing read. */
.assistant-submit-label {
    margin: 0;
    font-size: var(--fs-ui);
    font-weight: 600;
    color: var(--c-ink);
}

/* Which task, and what it is called — the link carries the id, the
   name comes from the loaded rows and is written in by the wiring
   pass, so it collapses cleanly on a task whose row has not arrived. */
.assistant-submit-meta {
    margin: 0;
    font-size: var(--fs-ui);
    color: var(--c-ink-soft);
}

.assistant-submit-link {
    color: var(--c-ink);
    text-decoration: underline;
}

.assistant-submit-name {
    margin-left: var(--s-2);
}

.assistant-submit-name:empty {
    margin-left: 0;
}

/* The quote, exactly as the server rendered it.  Metadata tier: it is a
   figure about the person's money, stated once, never restyled into a
   headline. */
.assistant-submit-quote {
    margin: 0;
    font-size: var(--fs-ui);
    color: var(--c-ink-muted);
}

/* The bubble's Cancel control (`.assistant-submit-cancel`) is styled in
   web/app/css/style.css beside the `.btn` family it overrides: both
   rules tie at (0,1,0) and this sheet links before style.css, so a
   delta held here would lose the tie to `.btn`'s own background — the
   stays-together rule the split applies to every cross-sheet tie pair
   (docs/APP.md#the-stylesheet-split). */

/* The per-build outcome line — cancelled, or why that cancel failed.
   Empty until there is an outcome, and collapsed while it is. */
.assistant-submit-status {
    margin: 0;
    font-size: var(--fs-ui);
    color: var(--c-ink-soft);
}

.assistant-submit-status:empty {
    display: none;
}

/* A turn carrying a pending clarification — a modifier on the ordinary
   bubble, never a block of its own.  The gold accent flags the one
   bubble on the surface that is waiting on the person, decoration-tier
   on the border with the text inside unchanged. */
.assistant-turn-clarification {
    /* An urgent memo: the one entry waiting on the person gets its box
       back — full frame, 3px gold left rule — so it stands out of the
       correspondence flow the ordinary turns settle into. */
    border: 1px solid var(--c-rule);
    border-left: 3px solid var(--c-burnished);
    border-radius: var(--radius-sm);
    background: var(--c-paper);
    padding: var(--s-4);
    margin: var(--s-2) 0;
}

/* The meta line under the question: which task is asking, and the
   honest consequence of leaving it — the task continues on its own at
   the stated time.  Metadata tier, on its own ruled-off row. */
.assistant-turn-clarification-meta {
    margin: var(--s-3) 0 0;
    font-size: var(--fs-ui);
    color: var(--c-ink-muted);
    border-top: 1px dotted var(--c-rule);
    padding-top: var(--s-2);
}

.assistant-turn-clarification-build {
    font-weight: 500;
    color: var(--c-ink-soft);
}

/* The deadline as an italic gold aside — the text tier of the gold,
   because a time somebody must beat has to clear the reading floor. */
.assistant-turn-clarification-deadline {
    margin-left: var(--s-2);
    font-family: var(--font-accent);
    font-style: italic;
    color: var(--c-burnished-text);
    letter-spacing: 0.01em;
}

/* The Answer control fills the bubble's action row: a box above its
   button rather than the handoff's single control beside nothing, so
   the row stacks.  The box borrows the input row's well treatment —
   same act, smaller stage. */
.assistant-turn-clarification .assistant-turn-actions {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-2);
}

/* `.assistant-clarification-input` shares the thread's editing well —
   declared beside `.assistant-draft-input` above. */

/* The answered-late notice — interface copy in the list, like the
   opening line, but rendered as a bubble because it is the product
   telling the person something.  Quieter than an answer and flagged
   with the same gold as the question it closes out. */
.assistant-turn-late-notice {
    background: transparent;
    border: 0;
    border-left: 3px solid var(--c-burnished);
    border-bottom: 1px dotted var(--c-rule);
    border-radius: 0;
    padding: var(--s-4) 0 var(--s-4) var(--s-4);
}

.assistant-turn-late-notice .assistant-turn-body {
    color: var(--c-ink-soft);
    font-family: var(--font-accent);
    font-style: italic;
    font-size: var(--fs-body);
}

/* The opening line, for a thread nobody has said anything in yet.  It
   is not a turn and does not read as one: no bubble, no side, no
   border — the empty state of the list, in the list's own box. */
.assistant-opening {
    align-self: stretch;
    max-width: 44ch;
    padding: var(--s-2) 0 var(--s-4);
    border-bottom: 1px dotted var(--c-rule);
    font-family: var(--font-accent);
    font-size: var(--fs-title);
    line-height: 1.5;
    color: var(--c-ink);
}

/* The reply slip, below the conversation.  No card of its own any
   more — it lives on the sheet, opened by the letterhead's device in
   reverse: a double hairline rule (border-top plus a hairline cast one
   pixel above it by the box-shadow — a rule drawn with a shadow, not
   elevation, so it stays a named literal outside the --sh-* set).  It
   is never repainted (the turn list is), so text typed into it
   survives every turn that arrives while somebody is mid-sentence. */
.assistant-form {
    display: flex;
    flex-direction: column;
    gap: var(--s-2);
    padding: var(--s-4) 0 0;
    background: transparent;
    border: 0;
    border-top: 1px solid var(--c-rule);
    border-radius: 0;
    box-shadow: 0 -4px 0 -3px var(--c-rule);
}

/* The slip itself has no focus treatment — the input well below
   carries it — so the old card's `:focus-within` lift and its
   transition are gone rather than left as no-ops for the next reader
   to preserve. */

.assistant-label {
    font-family: var(--font-accent);
    font-size: var(--fs-lede);
    color: var(--c-ink);
}

.assistant-input {
    width: 100%;
    min-height: 72px;
    resize: vertical;
    font-family: var(--font-body);
    font-size: var(--fs-base);
    line-height: 1.5;
    color: var(--c-ink);
    background: var(--c-paper);
    border: 1px solid var(--c-rule-strong);
    border-radius: var(--radius-sm);
    padding: var(--s-3) var(--s-4);
    box-shadow: var(--sh-well);
    transition: border-color var(--t-base);
}

.assistant-input::placeholder {
    color: var(--c-ink-muted);
}

/* The focused well carries the whole slip's focus treatment: the
   burnished border plus var(--sh-well) under var(--focus-ring) — the
   same composition every converged text input shares. */
.assistant-input:focus {
    outline: none;
    border-color: var(--c-burnished);
    box-shadow: var(--sh-well), var(--focus-ring);
}

/* The inline error a refused send writes.  It collapses when empty
   instead of carrying a `hidden` attribute — the `.assistant-turns:empty`
   rule again, so the whole thread fragment ships nothing hidden and the
   cleared state is simply "there is no text here". */
.assistant-error {
    margin: 0;
    font-size: var(--fs-ui);
    color: var(--c-status-failed);
    border-left: 3px solid var(--c-status-failed);
    padding-left: var(--s-2);
}

.assistant-error:empty { display: none; }

.assistant-form-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--s-3);
}

/* The slip's footer copy — a keyboard hint on the leading edge and a
   remaining-characters count by the send control.  Both are markup the
   thread script owns; the rules are inert until it emits them.  The
   count collapses when empty, like every quiet mount on this sheet. */
.assistant-form-hint {
    margin: 0 auto 0 0;
    font-size: var(--fs-meta);
    font-weight: 500;
    color: var(--c-ink-muted);
}

.assistant-form-count {
    margin: 0;
    font-size: var(--fs-meta);
    color: var(--c-ink-muted);
    font-variant-numeric: tabular-nums;
}

.assistant-form-count:empty { display: none; }

/* --- Viewport ownership ------------------------------------------
   Once a conversation exists (the `page-conversing` latch) the sheet
   caps to the viewport and the turn list becomes the flexible region —
   overflow stays on [data-assistant-turns], the element every paint
   pins to its own scrollHeight. */
.page.page-conversing .assistant-thread {
    max-height: max(360px, calc(100vh - 21rem));
    max-height: max(360px, calc(100dvh - 21rem));
}

.page.page-conversing .assistant-turns {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;
}

/* The support launcher keeps its every-page resting spot on the
   assistant page too — a launcher that moves between pages reads as a
   different control.  Above 720px it gets no clearance rule: like
   every fixed launcher, it can sit over scrolled content mid-scroll
   (here, the sheet's lower corner at narrow desktop widths), the same
   tradeoff every other page already makes over its gallery rows; at
   full scroll the page's 6rem bottom padding plus the sheet's own
   bottom padding rest the sheet's controls above it.  The phone-width
   lift in the 720px block below is the one true collision fix: at
   near-full-bleed sheet widths the slip's Send control and the
   launcher genuinely share a corner. */

