/*
 * Functional styling only — the dedicated styling/polish pass is PRD Phase 7.
 *
 * Targets Office 2024 / WebView2 (Chromium) only — no legacy engine support.
 */

html,
body {
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    font-family: "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, sans-serif;
    font-size: 14px;
    color: #242424;
    /* Forced explicitly: when Word is in dark mode, its host applies a dark background to the
       pane by default (form controls stay light since those are native, separately-themed
       browser chrome) — leaving our text colors, chosen for a light page, unreadable. Always
       rendering light avoids that regardless of Word's theme setting. A real dark theme is a
       bigger undertaking (a parallel color palette, testing everything twice) — out of scope
       for now; this just guarantees legibility. */
    background-color: #ffffff;
    /* body is the flex column that reserves room for #app-footer below #app-root — without
       this, #app-root's own height:100% fills the whole viewport and the footer gets pushed
       out of view, only reachable by scrolling the outer page separately from .screen's own
       internal scroll. */
    display: flex;
    flex-direction: column;
}

#sideload-msg {
    padding: 20px;
}

#app-root {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    box-sizing: border-box;
}

#app-footer {
    flex-shrink: 0;
    text-align: center;
    padding: 6px 12px;
    font-size: 11px;
    color: #616161;
    border-top: 1px solid #e0e0e0;
}

#app-footer a {
    color: #616161;
}

/* EN/CH language toggle (taskpane.ts wires the click, i18n.ts owns the actual translations) —
   plain text buttons matching the footer's own links in size/color, so the toggle reads as part
   of the same footer row rather than a separate control. The active language is bold, same
   "state shown via weight, not a background fill" convention as .override-mode-link.is-active-mode
   elsewhere in this app. */
.footer-lang-btn {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    font-size: 11px;
    color: #616161;
    cursor: pointer;
}

.footer-lang-btn:hover {
    text-decoration: underline;
}

.footer-lang-btn.is-active-lang {
    font-weight: 700;
    text-decoration: underline;
}

/* A second, more prominent instance of the EN/CH toggle (i18n.ts's languageToggleHtml) —
   currently only providerList.ts's empty state, below "+ Add provider". Centered since it's
   sitting alone on an otherwise sparse screen, unlike the footer's own left-aligned row. */
.lang-toggle-inline {
    text-align: center;
}

.screen {
    display: flex;
    flex-direction: column;
    gap: 12px;
    height: 100%;
    min-height: 0;
    /* auto (not hidden) — a screen taller than the pane (e.g. this form, with several fields)
       needs to scroll to reach its Save button, rather than having the excess clipped off. */
    overflow-y: auto;
    overflow-x: hidden;
    box-sizing: border-box;
    padding: 16px;
}

/* Only the message list should ever grow/shrink to fill space — every other direct child
   (header, notices, input area) keeps its natural size instead of being squeezed by flexbox's
   default shrink behavior under space pressure. */
.screen > *:not(.message-list) {
    flex-shrink: 0;
}

.screen-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    column-gap: 12px;
    flex-shrink: 0;
}

.screen-header h2 {
    margin: 0;
    font-size: 16px;
    min-width: 0;
}

/* History, Providers, and the Story Bible list, 2026-08-13 — reworked from an earlier
   `position: sticky` attempt (kept .screen itself as the one scrolling box, pinned the header
   against it) to a real three-region flex layout instead, per explicit follow-up request: "the
   scroll only apply to the list". `.screen.provider-list-screen` stops being a scrolling box at
   all (overflow-y: hidden overrides the base `.screen` rule above); its direct children —
   `.screen-header`/the tab toggle (fixed, top), `.screen-list-scroll` (the one flexible child,
   scrolls internally), `.screen-fixed-footer` (fixed, bottom) — arrange themselves via plain
   flexbox, no `position: sticky`/z-index/background-color juggling needed. Scoped to
   `.provider-list-screen` (the class providerList.ts/sessionHistory.ts/storyBible.ts's
   renderStoryBibleList all three share) rather than the bare `.screen` class, so the Provider
   form/Settings/Story Bible editor/Edit session screens — plain scrolling forms with a Save
   button at the end, not a fixed-header-plus-list shape — are unaffected. */
.screen.provider-list-screen {
    overflow-y: hidden;
}

/* The one scrolling region inside these three screens — the chat/bible list (`.provider-items`)
   or its empty-state paragraph, wrapped in this div (providerList.ts/storyBible.ts) or applied
   directly to #history-tab-content (sessionHistory.ts, which already has the right id). `flex: 1`
   claims exactly the space left over once the fixed header/footer siblings take their own natural
   size; `min-height: 0` is required for that to actually let this child shrink below its content's
   natural height and scroll internally, rather than the flexbox default of refusing to shrink
   past content size (the same fix `.screen > *:not(.message-list)` already carves an exception
   for the chat screen's own `.message-list`, applied here as its own rule for a different set of
   screens instead of extending that one selector's exception list). */
.screen.provider-list-screen > .screen-list-scroll {
    flex: 1 1 0;
    min-height: 0;
    overflow-y: auto;
}

/* Fixed bottom action bar — "+ Add provider"/"+ Add Story Bible" (plus, for those two, the info
   note/language toggle that already sat below the button), or History's bulk select/recover/purge
   bar. A plain flex child already stays fixed-size via `.screen > *:not(.message-list)`'s own
   `flex-shrink: 0` above; this class only adds the visual divider separating it from the
   scrolling list right above it, plus stacking its own contents (e.g. Providers' button + info
   note) with the same 10px rhythm `.screen`'s own gap uses elsewhere. */
.screen-fixed-footer {
    border-top: 1px solid #e0e0e0;
    padding-top: 10px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

/* Export/Edit moved out to the dot-separated actions row under the switcher (.nav-cluster) —
   this header is now just the model/temperature label on its own line, so no flex/space-between
   layout needed anymore, just a block-level truncating line. */
.chat-content-header {
    display: block;
}

.model-tag {
    font-weight: normal;
    font-size: 12px;
    color: #616161;
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    /* Lives inside .topbar-toggle-row-main (a flex row) — min-width: 0 is what actually lets the
       ellipsis truncation above kick in as a flex item, rather than refusing to shrink below the
       tag's natural content width and pushing #session-token-total off the row. */
    min-width: 0;
    flex: 1 1 auto;
}

label {
    display: flex;
    flex-direction: column;
    font-size: 13px;
    color: #333;
}

label > * {
    margin-top: 4px;
}

/* Overrides the 4px above for just this one case — .label-row stands in for the label's plain
   caption text (only needed when an info icon has to sit next to it), and should sit flush at
   the top like the caption text it replaces did. flex-wrap: wrap is for the API key field's
   "Get an API key: <url>" hint (providerForm.ts) — unlike the short "?" info icon this row
   normally carries, a full console URL can be wider than the task pane, so it needs to be able
   to drop to its own line rather than overflow horizontally. */
.label-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin-top: 0;
}

.info-icon-wrap {
    position: relative;
    display: inline-flex;
    /* Never lets this shrink or get squeezed off — the API key row's "?" is the last item
       after a variable-width link (below), so this has to hold its own size regardless of
       what that link does. */
    flex-shrink: 0;
    margin-left: 4px;
}

/* The API key field's "Get an API key: <url>" hint (providerForm.ts) — sits inline in the same
   .label-row as the "API key" caption, right after it, so it needs its own left margin the same
   way .info-icon-wrap has its own above (label-row itself has no gap, to avoid adding unwanted
   spacing to the plain "? "-less label-row cases that predate this one). min-width: 0 +
   word-break let a long console URL (can be wider than the task pane) wrap *within* this item
   instead of overflowing past the pane's edge — flex-wrap on the row alone only moves whole
   *items* to a new line, it doesn't break text inside one that's already too wide. Deliberately
   NOT flex-basis: 100% (tried first, reverted per feedback) — that forced this onto its own
   full-width row, which pushed the "?" icon after it down onto a separate line below the URL
   instead of sitting right after its closing paren on the same line; word-break alone keeps them
   adjacent whenever there's room, which is normally always once the URL can actually wrap. */
#api-key-link-wrap {
    min-width: 0;
    word-break: break-all;
    margin-left: 6px;
}

/* The API key info icon (providerForm.ts) sits at the *end* of its row, after the "Get an API
   key" link — which can push it close to the pane's right edge. The default .info-bubble
   (left: 0, extending rightward from the icon) would clip past that edge from there, so this
   flips it to open leftward instead (right-anchored to the icon), same left/right-flip technique
   #session-token-info's own override uses below, just without also flipping the vertical
   direction (that one needed to open downward too, being pinned to the very top of its screen —
   this field isn't). Targeted via the adjacent-sibling selector rather than a dedicated class so
   no JS/providerForm.ts change was needed — matches regardless of whether #api-key-link-wrap
   itself is actually showing a link (some adapter presets have none and hide it), since the
   selector only cares about DOM position, not visibility. */
#api-key-link-wrap + .info-icon-wrap .info-bubble {
    left: auto;
    right: 0;
}

.info-icon {
    width: 15px;
    height: 15px;
    box-sizing: border-box;
    padding: 0;
    border-radius: 50%;
    border: 1px solid #a0a0a0;
    background: #f3f2f1;
    color: #616161;
    font-size: 10px;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}

/* Hidden by default; shown either by hovering .info-icon-wrap (plain CSS, so it also
   auto-closes on mouseleave with no JS) or by .is-open, toggled by wireInfoIcons in
   providerForm.ts on click — that one stays open until a click lands outside the wrap. */
.info-bubble {
    display: none;
    position: absolute;
    bottom: calc(100% + 6px);
    left: 0;
    width: 230px;
    max-width: 70vw;
    background: #242424;
    color: #fff;
    font-size: 12px;
    font-weight: normal;
    line-height: 1.4;
    padding: 8px 10px;
    border-radius: 4px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
    z-index: 20;
    white-space: normal;
    /* Explicit rather than relying on default inheritance — #session-token-info's bubble sits
       inside .session-token-total, which is text-align: right (the label/number next to it are
       meant to be right-aligned), and text-align isn't reset by position: absolute, so without
       this the popup's own content would inherit that and read right-aligned too. Popup content
       should always read left-aligned regardless of what alignment its trigger icon happens to
       sit inside. */
    text-align: left;
}

.info-icon-wrap:hover .info-bubble,
.info-icon-wrap.is-open .info-bubble {
    display: block;
}

/* #session-token-info (chat.ts) sits at the top of the pane, right-aligned in .topbar-toggle-row
   — the opposite position from every other .info-icon-wrap use (providerForm.ts fields, stacked
   in a column with room above but not below). Opening upward/rightward-from-left-edge (the
   defaults) would push the bubble off the top of the pane and/or off its right edge, so this
   flips both axes: down instead of up (plenty of room below, in the message list), anchored to
   its own right edge instead of its left. */
#session-token-info .info-bubble {
    bottom: auto;
    top: calc(100% + 6px);
    left: auto;
    right: 0;
}

input,
select,
textarea {
    font: inherit;
    padding: 6px 8px;
    border: 1px solid #c8c8c8;
    border-radius: 4px;
    box-sizing: border-box;
    width: 100%;
}

textarea {
    resize: vertical;
}

/* Same grey as .field-hint below — placeholder text should read as a suggestion, not real
   input, consistent everywhere it appears (e.g. the preset-derived provider name default). */
input::placeholder,
textarea::placeholder {
    color: #616161;
    opacity: 1;
}

.model-combobox {
    position: relative;
    display: flex;
    align-items: center;
}

.model-combobox input {
    padding-right: 26px;
}

.combobox-toggle {
    position: absolute;
    right: 2px;
    top: 50%;
    transform: translateY(-50%);
    background: none;
    border: none;
    padding: 4px 6px;
    line-height: 1;
    color: #616161;
}

.combobox-toggle:disabled {
    color: #d6d6d6;
    cursor: default;
}

.model-suggestions {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin: 2px 0 0;
    padding: 0;
    list-style: none;
    background: #fff;
    border: 1px solid #c8c8c8;
    border-radius: 4px;
    max-height: 160px;
    overflow-y: auto;
    z-index: 10;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}

.model-suggestion {
    padding: 6px 8px;
    cursor: pointer;
}

.model-suggestion:hover {
    background: #f3f2f1;
}

/* API endpoint field's own combobox (providerForm.ts) — same shape as .model-combobox above
   (input + .combobox-toggle arrow + an absolutely-positioned suggestion list), kept as its own
   small rule set rather than sharing .model-combobox's classes directly: this app's existing
   preference for small self-contained rules over shared abstractions once two things only look
   alike rather than actually being the same widget (see CLAUDE.md's truncation-divider/
   provider-change-divider — same reasoning). The arrow itself is only ever shown when the
   current preset has more than one known endpoint (Kimi/Qwen); everything else never renders a
   populated suggestion list here at all. */
.endpoint-combobox {
    position: relative;
    display: flex;
    align-items: center;
}

.endpoint-combobox input {
    padding-right: 26px;
}

.endpoint-suggestions {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin: 2px 0 0;
    padding: 0;
    list-style: none;
    background: #fff;
    border: 1px solid #c8c8c8;
    border-radius: 4px;
    max-height: 160px;
    overflow-y: auto;
    z-index: 10;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}

.endpoint-suggestion {
    padding: 6px 8px;
    cursor: pointer;
    font-size: 13px;
}

.endpoint-suggestion:hover {
    background: #f3f2f1;
}

.form-actions {
    display: flex;
    align-items: center;
    column-gap: 12px;
    /* 8px — matches the blue-button-to-field gap used everywhere else (chat's Send button,
       the provider list's "+ Add provider"). Affects Edit session/Start chat/Add provider's
       Save, all of which share this class. */
    margin-top: 8px;
}

button {
    font: inherit;
    cursor: pointer;
    border-radius: 4px;
    border: 1px solid transparent;
    padding: 6px 12px;
}

.primary-btn {
    background: #0f6cbd;
    color: #fff;
    border-color: #0f6cbd;
}

.primary-btn:disabled {
    opacity: 0.6;
    cursor: default;
}

/* Send button while a reply is pending (chat.ts's applySendBtnPendingState) — text stays
   "Thinking..." throughout (an earlier version changed it to "Stop Thinking" once clickable;
   reverted per feedback in favor of a color/weight-only change). This rule covers both the
   disabled, not-yet-clickable phase and the later clickable one, sharing this grey background,
   distinct from the plain blue .primary-btn idle "Send" state. .primary-btn:disabled's own 0.6
   opacity still applies on top of this during the not-yet-clickable phase, giving it a slightly
   softer/dimmer grey than the full-strength one the clickable phase shows — a small free visual
   distinction between "please wait" and "you can act now", not a separate rule.
   font-size: 10px (down from the button's inherited 14px) narrows "Thinking..." toward
   "Send"'s own natural width — deliberately the *only* lever used for this, after an earlier
   attempt at also giving #send-btn a fixed `width` shrank "Send" itself well below its correct
   size (buttons get box-sizing: border-box by default in this browser, so a `width` in `ch`
   sets the *total* box including padding, not just the text area — badly underestimated).
   #send-btn itself carries **no** width/min-width/max-width rule of any kind, by explicit
   request — "Send"'s size is the fixed reference point, never to be touched again; this
   font-size is the only thing allowed to move. Not confirmed pixel-exact against a live
   render — if "Thinking..." still doesn't match, adjust this number, not the button. */
.primary-btn.is-thinking {
    background: #f3f2f1;
    color: #242424;
    border-color: #c8c8c8;
    font-size: 10px;
}

/* Layered on top of .is-thinking once "Thinking..." becomes clickable — same grey background
   from that rule (not repeated here), same text, just turning red as the "about to cancel" cue.
   Same red as .error-text/.danger-btn/.truncation-divider, so it reads as this app's one
   consistent "stop/attention" color rather than a new one. font-weight: 600 (not the button's
   inherited normal weight, but also not 700/full bold) is a deliberate middle ground: 700 made
   the button visibly wider — bold glyphs take more horizontal space at the same font-size than
   normal ones, even for identical text — while plain weight on this muted grey background read
   as too thin for a cue that needs to register at a glance. 600 matches .selection-chip-state/
   .selection-chip-critique's own weight, already an established "some emphasis, not shouting"
   choice elsewhere in this app. */
.primary-btn.is-thinking.is-stop-thinking {
    color: #d13438;
    font-weight: 600;
}

.secondary-btn {
    background: #f3f2f1;
    border-color: #c8c8c8;
    color: #242424;
}

.danger-btn {
    background: #fff;
    border-color: #d13438;
    color: #d13438;
}

.link-btn {
    background: none;
    border: none;
    color: #0f6cbd;
    padding: 0;
    text-decoration: none;
}

.link-btn:hover {
    text-decoration: underline;
}

.empty-hint {
    color: #616161;
}

.field-hint {
    font-size: 12px;
    color: #616161;
    margin: 0;
}

/* Provider list's own quiet informational note (providerList.ts), below "+ Add provider" and
   the empty-state language toggle — always present (populated or empty list alike), unlike the
   toggle above it. Styled as a secondary aside, same small/grey register as .field-hint/
   .empty-hint, not a callout/banner — this is context, not a warning or a required action. */
.provider-list-info {
    margin-top: 16px;
    padding-top: 12px;
    border-top: 1px solid #e0e0e0;
}

.provider-list-info-heading {
    font-size: 13px;
    font-weight: 600;
    color: #242424;
    margin: 0 0 4px;
}

.provider-list-info-body {
    font-size: 12px;
    color: #616161;
    margin: 0;
}

/* Settings screen (settings.ts) — each independent setting gets its own bordered-off section so
   the Critique prompt form's Save/Revert/Cancel row clearly doesn't apply to Language below it
   (or vice versa) — no divider between the two previously, which read as one combined form. */
.settings-section {
    padding-bottom: 14px;
    border-bottom: 1px solid #e0e0e0;
}

.settings-section:last-child {
    padding-bottom: 0;
    border-bottom: none;
}

/* Language row — left-aligned "Language: English / Chinese", not the centered "EN / CH" toggle
   languageToggleHtml() uses elsewhere (that one's tuned for sitting alone on a sparse onboarding
   screen; this page reads as a form, so it matches the rest of that form's left alignment). */
.settings-language-row {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 4px;
}

.settings-language-sep {
    color: #616161;
}

.settings-lang-btn {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    color: #0f6cbd;
    cursor: pointer;
}

.settings-lang-btn:hover {
    text-decoration: underline;
}

/* Same "bold + underline shows the active state" convention as .footer-lang-btn.is-active-lang —
   updateLanguageToggleButtons() (i18n.ts) toggles this class on any [data-lang] element, not just
   .footer-lang-btn's own, so it applies here unchanged. */
.settings-lang-btn.is-active-lang {
    font-weight: 700;
    text-decoration: underline;
}

/* The Critique prompt field is the very first thing on this screen, right under the header, with
   little to no room above it — the default .info-bubble (opens upward) would clip against the
   top of the pane/screen the same way #session-token-info's own bubble used to. Flips it to open
   downward instead, where the textarea already gives it plenty of room. */
#settings-critique-prompt-label .info-bubble {
    bottom: auto;
    top: calc(100% + 6px);
}

/* Edit session's Temperature/Max context/System prompt labels (sessionSwitcher.ts's
   overrideModeLinks) — replaces the plain "(optional)" those fields used to have with two
   clickable words for switching between inherit/clear. Same 12px/grey register as .field-hint
   (a secondary annotation next to the caption, not the caption itself); the parentheses and the
   " / " separator inherit that grey, while the two words themselves use .link-btn's blue so they
   read as actionable. */
.override-mode-links {
    font-size: 12px;
    color: #616161;
    font-weight: normal;
    margin-left: 4px;
}

.override-mode-link {
    font-size: 12px;
}

/* Marks whichever of inherit/clear currently applies to this field — neither is marked while a
   real typed value is in play ("override" mode isn't represented by either link). Bold rather
   than a background fill: this sits inline in running label text, where a filled pill would
   look heavier than the moment deserves. */
.override-mode-link.is-active-mode {
    font-weight: 700;
    text-decoration: underline;
}

.provider-items {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
    overflow-y: auto;
}

/* History's Chats/Story Bibles toggle (sessionHistory.ts) — two plain buttons, the active one
   in .primary-btn's blue, the inactive one in .secondary-btn's grey. No dedicated "tab" styling
   beyond that swap; .screen's own 12px gap already spaces it from the header above and the
   scrolling list below, so this only needs its own internal gap between the two buttons. */
.history-tab-toggle {
    display: flex;
    gap: 8px;
}

/* Bulk select/recover/purge bar (sessionHistory.ts) — now lives inside #history-bulk-bar-slot,
   itself a `.screen-fixed-footer` (see that class above), pinned at the *bottom* of the screen
   below the scrolling list, per explicit request. Just the toolbar row's own layout (count left,
   buttons right, one line) — the divider above it is the wrapping .screen-fixed-footer's own
   border-top, not this element's, so there's only ever one border between the list and this bar,
   not two stacked ones. */
.history-bulk-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 8px;
}

/* Bulk purge's own inline confirm state replaces the row-and-buttons layout above with a plain
   stacked block (text, then Yes/Cancel) — same shape as a single row's own .confirm-text, just
   without the toolbar's left/right split, since there's nothing to right-align against here. */
.history-bulk-actions.is-confirming {
    flex-direction: column;
    align-items: stretch;
}

.history-bulk-count {
    font-size: 12px;
    color: #616161;
    white-space: nowrap;
}

/* History's two tabs only (sessionHistory.ts) — a checkbox as its own left column of the whole
   card, not inline with the title, so it reads as "select this row" the same way a checkbox-list
   UI normally works, rather than looking like part of the title line. .history-item overrides
   .provider-item's own column direction (title-block-then-actions-block, stacked) with a row
   direction just for these two tabs; providerList.ts/storyBible.ts's entry list, which also
   reuse plain .provider-item, are unaffected since they never add this class.
   **Compound selector, not just `.history-item`** — both classes are single-class selectors of
   equal specificity, and `.provider-item`'s own `flex-direction: column` sits later in this same
   file, so a plain `.history-item` rule here lost the cascade to it regardless of this rule's own
   position (confirmed live: checkbox rendered above the title, not beside it, exactly as if this
   rule were never applied). `.provider-item.history-item` has higher specificity than
   `.provider-item` alone, so it wins outright — no dependence on which rule happens to come last. */
.provider-item.history-item {
    flex-direction: row;
    align-items: flex-start;
    gap: 8px;
}

/* Overrides the generic input/select/textarea rule's width:100%/padding/border (this file,
   "input, select, textarea") — same fix, same reason, as .story-bible-entry-checkbox above:
   without this, that rule stretches the checkbox to fill nearly the whole row (its flex-basis
   resolves from that 100% width, and flex-shrink:0 alone just means it refuses to give that width
   back up), squeezing .history-item-body down to almost nothing and wrapping its text one
   character per line. This was the actual bug behind the checkbox appearing to float in the
   middle of the row with the row's real content crushed into a sliver at the far right — not a
   flex-direction/specificity issue (that part, fixed above, was already correct and live). */
.history-item-checkbox {
    width: 16px;
    height: 16px;
    padding: 0;
    border: none;
    margin-top: 2px;
    flex-shrink: 0;
}

/* Wraps the existing .provider-item-info/.provider-item-actions stack so it fills the remaining
   width next to the checkbox column — everything inside is unchanged from before the checkbox
   existed. */
.history-item-body {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
}

.provider-item {
    display: flex;
    flex-direction: column;
    border: 1px solid #e0e0e0;
    border-radius: 4px;
    padding: 8px 10px;
}

.provider-item-info {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.provider-meta {
    font-size: 12px;
    color: #616161;
}

/* Quoted snippet of the configured system prompt — italic sets it apart from the plain
   adapter/temperature/context stats lines above it as "quoted content", not a stat. */
.provider-system-prompt {
    font-style: italic;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Fixed min-height reserves room for the taller "confirm delete" state, so switching
   between it and the normal Start new chat/Edit/Delete row never resizes the card. Bottom-
   aligning the content (flex-end) keeps the button row itself flush at the same fixed position
   in both states too — the confirm text just fills in above it, rather than pushing it down.
   56px was the measured height for the original 2-button (Edit/Delete) state; a third button
   (Start new chat, 2026-08-11) may now wrap to two lines in a narrow pane (see .actions-row's
   flex-wrap below) and need this raised — re-measure live rather than trusting this number
   blindly if the card visibly grows/shrinks when toggling delete-confirm after that change.
   margin-top (below) is the separate knob for trimming the empty space in the shorter, more
   common non-confirming state. */
.provider-item-actions {
    min-height: 56px;
    margin-top: 8px;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: 4px;
}

/* flex-wrap (2026-08-11, added alongside the third "Start new chat" button): three buttons
   side by side risked overflowing a narrow task pane's width — wrapping to two lines is a much
   smaller visual cost than horizontal overflow/clipping. */
.actions-row {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.actions-row button:disabled {
    opacity: 0.5;
    cursor: default;
}

.confirm-text {
    font-size: 12px;
    color: #424242;
    margin: 0;
}

/* Story Bible (storyBible.ts) — the editor's entry list. Reuses .provider-items/.provider-item/
   .provider-meta/.actions-row for the manage screen's own list (same shape as the provider
   list), so only the entry-specific pieces below are new. */

/* Create/Edit sitting directly under the Story Bible dropdown — tighter top margin than the
   generic .form-actions default since this pair reads as part of the same field, not a
   trailing action row. */
.story-bible-field-actions {
    margin-top: 4px;
}

.story-bible-entries-header {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    font-size: 13px;
    margin-top: 4px;
}

.story-bible-token-total {
    font-size: 12px;
    color: #616161;
}

.story-bible-entries-toolbar {
    display: flex;
    justify-content: flex-end;
    margin-top: 4px;
}

.story-bible-entries-toolbar .link-btn {
    font-size: 12px;
}

.story-bible-entries {
    list-style: none;
    margin: 4px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.story-bible-entry {
    display: flex;
    flex-direction: column;
    gap: 4px;
    border: 1px solid #e0e0e0;
    border-radius: 4px;
    padding: 6px 8px;
}

/* Expanded (via "Expand all") reads as a distinct bubble/card rather than the same compact list
   row, so it's visually obvious which mode is showing. */
.story-bible-entry-expanded {
    background: #faf9f8;
    border-color: #c8c8c8;
}

/* Dimmed, non-interactive read while a different entry's add/edit form is open — see
   renderStoryBibleEditor's entryFormOpen handling. Every control inside also carries a real
   `disabled` attribute; this is just the visual cue to match. */
.story-bible-entry.is-locked {
    opacity: 0.55;
}

.story-bible-entry-main {
    display: flex;
    align-items: flex-start;
    gap: 8px;
}

/* Sized/aligned to sit next to the entry's own first text line, not the row's vertical center —
   matches how a checkbox reads in a list (label to its right), not a form field. Overrides the
   generic input/select/textarea rule's width:100%/padding/border (taskpane.css, "input, select,
   textarea") — without this, that rule stretches the checkbox to fill the whole row (its
   flex-basis resolves from that 100% width) and squeezes .story-bible-entry-info down to almost
   nothing, wrapping every word onto its own line. A checkbox was never meant to pick up that
   text-field styling in the first place. */
.story-bible-entry-checkbox {
    width: 16px;
    height: 16px;
    padding: 0;
    border: none;
    margin-top: 2px;
    flex-shrink: 0;
}

.story-bible-entry-info {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1 1 auto;
    min-width: 0;
}

/* Compact preview: a 2-line clamp, not a character-count cutoff — wraps to however many
   characters actually fit across the pane's current width, both narrow and wide. */
.story-bible-entry-text {
    font-size: 12px;
    color: #424242;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* "Expand all" mode shows the entry's real text in full, so the 2-line clamp above is lifted and
   actual line breaks in the entry are preserved instead of being collapsed like the snippet's. */
.story-bible-entry-text-expanded {
    display: block;
    -webkit-line-clamp: unset;
    overflow: visible;
    white-space: pre-wrap;
}

/* Token count + Edit + Delete, in that order, bottom-right of the entry — deliberately smaller
   than the app's base 14px body text, matching the other small/secondary text already used
   throughout this screen (.story-bible-token-total, .field-hint). */
.story-bible-entry-meta {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    font-size: 12px;
}

.story-bible-entry-meta .link-btn {
    font-size: 12px;
}

/* The inline add/edit-entry form, shown in place of "+ Add entry" — same field/label rhythm as
   the rest of this screen, just visually set apart with a light card so it reads as a temporary
   sub-form rather than more of the entry list above it. */
.story-bible-entry-form {
    border: 1px solid #c8c8c8;
    background: #faf9f8;
    border-radius: 4px;
    padding: 10px 12px;
    margin-top: 4px;
}

/* Wraps the edit form when it's spliced into the entries <ul> directly below the entry being
   edited (see entriesSectionHtml) rather than appended after the whole list — a plain list-item
   reset so .story-bible-entry-form's own spacing/border still reads the same as when it's used
   standalone below the list for "+ Add entry". */
.story-bible-entry-form-wrap {
    list-style: none;
}

/* Persistent reminder shown while an entry's add/edit form is open — pairs with every other
   entry link/checkbox being disabled at the same time (see .story-bible-entry.is-locked and the
   `disabled` attributes in renderStoryBibleEditor), so the reason they're unresponsive is always
   visible, not just implied by their dimmed state. */
.pending-entry-note {
    font-size: 12px;
    color: #616161;
    margin: 6px 0 0;
}

/* Linked-chats management (renderStoryBibleEditor, 2026-08-13) — sits below the entries/pending-
   entry-note area, above Save/Export/Cancel, set apart with the same top divider convention as
   .settings-section (secondary management, not the screen's main content). */
.story-bible-linked-chats-section {
    margin-top: 12px;
    padding-top: 10px;
    border-top: 1px solid #e0e0e0;
}

.story-bible-linked-chats-section h3 {
    font-size: 13px;
    margin: 0 0 6px;
}

.story-bible-linked-chats {
    list-style: none;
    margin: 0 0 8px;
    padding: 0;
}

.story-bible-linked-chat {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    font-size: 13px;
    padding: 4px 0;
}

.story-bible-linked-chat .link-btn {
    font-size: 12px;
}

.story-bible-link-row {
    display: flex;
    gap: 8px;
    align-items: center;
}

.story-bible-link-row select {
    flex: 1;
    min-width: 0;
}

/* Add/Edit Provider's cost-acknowledgment step (providerForm.ts) — shown in place of the
   normal Save/Cancel row once validation passes, before saveProvider() actually runs. A plain
   neutral notice box, not the app's red/danger treatment (.truncation-banner, .error-text) —
   this isn't a warning about something going wrong, it's routine informed consent, so it reads
   as a straightforward notice rather than an alarm. Same avoids-window.confirm() reasoning as
   providerList.ts's inline delete confirmation — native modal dialogs are unreliable inside
   Office task panes. */
.save-confirm {
    border: 1px solid #c8c8c8;
    background: #faf9f8;
    border-radius: 4px;
    padding: 10px 12px;
    margin-top: 8px;
}

.save-confirm-text {
    font-size: 12px;
    color: #242424;
    margin: 0 0 8px 0;
}

.save-confirm-text:last-of-type {
    margin-bottom: 0;
}

.error-text {
    color: #d13438;
    font-size: 13px;
    margin: 0;
}

/* Marks the exact point in the message list where getTruncatedHistory's cutoff falls (see
   chat.ts's renderMessages) — a line/label/line divider rather than a banner, so its position
   itself shows *where* the AI stops seeing history, not just *that* some has been dropped.
   Reuses the same red as .error-text/.danger-btn for the line color, so it reads as part of
   this app's one established "needs attention" color rather than introducing a second red. */
.truncation-divider {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 4px 0;
}

/* flex: 1 1 auto lets each line grow to fill whatever space the label doesn't need, but
   min-width: 1cm is the actual guarantee — an absolute physical unit, not a hand-picked px
   value, so "at least 1cm of red line" holds regardless of the pane's zoom or the display's
   DPI. When the pane is too narrow for both 1cm lines plus any label text, the label (below)
   is what shrinks/truncates first, never these. */
.truncation-divider-line {
    flex: 1 1 auto;
    min-width: 1cm;
    height: 0;
    border-top: 1px solid #d13438;
}

/* min-width: 0 overrides flexbox's default min-width: auto, which would otherwise refuse to
   shrink this below its own text's natural width and defeat the ellipsis entirely — this is
   the one declaration that actually makes "cut at the end with …" possible under a narrow
   frame. flex-shrink (via the flex: 0 1 auto shorthand) is what makes the label give up space
   to the two lines above before they'd ever drop below their own 1cm floor. */
.truncation-divider-text {
    flex: 0 1 auto;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 11px;
    color: #d13438;
}

/* Marks a point in the message list where "Edit session" switched this chat to a different
   saved provider (see ChatSession.providerChanges and chat.ts's renderMessages) — same
   line/label/line shape as .truncation-divider above, but in the app's blue (matching Send/
   .primary-btn, #0f6cbd) rather than red: this flags an event that happened, not a warning, so
   it deliberately doesn't borrow the red "needs attention" treatment. */
.provider-change-divider {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 4px 0;
}

/* Same reasoning as .truncation-divider-line: min-width: 1cm is an absolute physical unit, so
   "at least 1cm of line" holds regardless of zoom/DPI, and the label (below) is what shrinks/
   truncates first under a narrow pane, never these. */
.provider-change-divider-line {
    flex: 1 1 auto;
    min-width: 1cm;
    height: 0;
    border-top: 1px solid #0f6cbd;
}

/* Same reasoning as .truncation-divider-text: min-width: 0 overrides flexbox's default
   min-width: auto, which is what actually makes the ellipsis truncation possible under a
   narrow frame. */
.provider-change-divider-text {
    flex: 0 1 auto;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 11px;
    color: #0f6cbd;
}

/* Sits above the compose box (chat.ts's renderActiveSession) — deliberately fixed in that one
   spot rather than inline in the message list like .truncation-divider above, so it's seen
   without having to scroll: this is what actually alerts the author that truncation is
   happening at all, since most people don't scroll back through old messages on their own.
   Same red family as .truncation-divider/.error-text/.danger-btn, tinted background version
   (light red, not the plain white/grey most other notices use) so this reads as belonging to
   the same "context exceeded" feature as the divider, not a generic hint. Dismiss state is
   tracked in chat.ts's truncationBannerDismissed (module-level, resets only when a different
   session is opened — see that variable's comment).
   Column layout (text row, then the × below it) rather than one row with the × on the right —
   the × is meant to sit at the bottom-left, mirroring where .bubble-delete-btn sits in an AI
   reply's footer (bottom-left, next to the collapse arrow; token count is what takes the
   bottom-right there). align-items: flex-start makes that placement the *default* for any
   child here (an unconstrained flex column child sizes to its own content and sticks to the
   start edge) — .truncation-banner-text below opts back out of that with align-self: stretch,
   since unlike the button, the text needs the full row width to wrap normally. */
.truncation-banner {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    font-size: 12px;
    color: #d13438;
    background: #fdecea;
    border: 1px solid #f2b8b5;
    border-radius: 4px;
    padding: 6px 8px;
    margin: 0;
}

.truncation-banner-text {
    align-self: stretch;
}

/* Deliberately identical sizing to .bubble-delete-btn (the AI reply footer's × — same
   width/height/padding/line-height/font-size/flex-shrink), per explicit request that this
   close button read as the same control. Only color departs from that one: .bubble-delete-btn
   is grey-by-default/red-on-hover since it sits on an otherwise-neutral bubble footer, but this
   button already sits on a red-tinted banner, so it just inherits that red directly rather than
   looking oddly muted against its own background. */
.truncation-banner-close {
    width: auto;
    height: auto;
    padding: 2px 4px;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: transparent;
    color: inherit;
    font-size: 15px;
    flex-shrink: 0;
    cursor: pointer;
}

.truncation-banner-close:hover {
    opacity: 0.7;
}

/* Sits in the same spot as .truncation-banner, same row/column shape — but blue instead of red
   (.provider-change-divider's family: this flags something that happened elsewhere, not a
   warning about a limit) and a row layout, not a column: the action here is a real button
   ("Reload"), not a small × dismiss, so it reads better sitting to the right of the message
   than stacked below it. Shown/hidden by chat.ts's 5-second staleness poll (see
   teardownStalenessPoll's own comment) — there's no manual dismiss, it only clears once you
   actually reload and the chat is no longer stale. */
.staleness-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    font-size: 12px;
    color: #205081;
    background: #ddedfa;
    border: 1px solid #a8cfec;
    border-radius: 4px;
    padding: 6px 8px;
    margin: 0;
}

/* .primary-btn's own blue fill, just sized down to actually fit inline in a compact banner
   rather than the full form-button size it normally has. */
.staleness-banner-reload {
    flex-shrink: 0;
    font-size: 12px;
    padding: 3px 10px;
}

/* Sits above the compose box (chat.ts's renderActiveSession), same spot as .truncation-banner
   but shown instead of it only while Regenerate is waiting on optional feedback — the two never
   overlap in practice (truncation is a standing chat-level state, this is a momentary compose-box
   mode), so no stacking rule is needed between them. Blue (.selection-chip's family: #ddedfa/
   #a8cfec/#205081), not red — this isn't a warning, it's the same "compose-time helper" register
   as the selection chip right above it. Single row (not .truncation-banner's column layout): the
   text is short and fixed, and this cancel button doesn't carry the "destructive action" weight
   that justified bottom-left-mirroring .bubble-delete-btn there. */
.regenerate-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    font-size: 12px;
    color: #205081;
    background: #ddedfa;
    border: 1px solid #a8cfec;
    border-radius: 4px;
    padding: 6px 8px;
    margin: 0;
}

/* Same sizing/shape as .truncation-banner-close, just inheriting this banner's blue instead of
   red. */
.regenerate-banner-cancel {
    width: auto;
    height: auto;
    padding: 2px 4px;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: transparent;
    color: inherit;
    font-size: 15px;
    flex-shrink: 0;
    cursor: pointer;
}

.regenerate-banner-cancel:hover {
    opacity: 0.7;
}

/* PRD Section 6 — session switching. Two visually separate clusters, always stacked: session
   controls (switch/new/rename/close — act on or add to the open session list) on top, a
   dot-separated actions row (Edit/Export when a session is active, History/Providers always)
   below. Always two rows (not "share a row when there's room") so the actions row's position
   never shifts with the pane's width. */

/* Collapsing hides the actions row (nav-cluster: Edit/Export/History/Providers) — but
   deliberately leaves session-cluster (the chat switcher dropdown plus new/rename/close) visible
   even while collapsed, so switching between open chats stays a one-click action regardless of
   collapse state. The model/provider/temperature line used to live in its own
   .chat-content-header row that hid on collapse too; per explicit request it now lives inside
   .topbar-toggle-row instead (see that rule below) and stays visible collapsed or not. */
.session-switcher.is-collapsed .nav-cluster {
    display: none;
}

/* One row: collapse arrow + model/provider/temperature tag on the left
   (.topbar-toggle-row-main), token total on the right (#session-token-total) — both stay visible
   whether the rest of the toolbar (nav-cluster above) is collapsed or not, a fixed, predictable
   spot for the arrow that brings the rest back, and per explicit request the model/temperature
   info reads as part of the same row instead of a separate line above it. Also holds
   #session-token-total (chat.ts) — moved here from above the Send button 2026-08-10, per explicit
   request, after an earlier same-day version of this same move had already been tried and
   reverted once; this time it's staying. Flex with space-between puts the arrow+tag group at the
   left and the token total at the right. */
.topbar-toggle-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

/* The arrow-then-model-tag group — min-width: 0 lets .model-tag's own ellipsis truncation
   actually kick in as a flex child (flex items default to a min-width that refuses to shrink
   below their content's natural width, which would otherwise push #session-token-total off the
   row instead of truncating the tag first). flex: 1 1 auto so this group (not the fixed-width
   token total) is what absorbs a narrow pane. */
.topbar-toggle-row-main {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    flex: 1 1 auto;
}

.topbar-toggle-btn {
    width: auto;
    height: auto;
    padding: 2px;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: transparent;
    /* Matches the native <select> dropdown arrow's color (.session-cluster select, browser
       default) so this reads as the same family of control. */
    color: #616161;
    /* Fixed-size icon — never the thing that gives way to .model-tag's ellipsis truncation next
       to it in .topbar-toggle-row-main. */
    flex-shrink: 0;
}

/* Same chevron shape/weight as the native session-select dropdown arrow above it — this one
   just rotates between pointing down (expand) and up (collapse) instead of opening a list. */
.topbar-toggle-icon {
    transition: transform 0.15s ease;
}

.topbar-toggle-icon.is-up {
    transform: rotate(180deg);
}

.session-switcher {
    display: flex;
    flex-direction: column;
}

/* .chat-screen has exactly two direct children (#session-switcher, #chat-content) — one gap
   to manage — so this sets that gap directly rather than overriding .screen's shared 12px. */
.screen.chat-screen {
    gap: 8px;
}

.session-switcher select,
.session-switcher input {
    width: auto;
    flex: 1 1 auto;
    min-width: 90px;
}

.session-cluster {
    display: flex;
    align-items: center;
    gap: 4px;
    flex: 1 1 auto;
    min-width: 0;
    background: #f3f2f1;
    border: 1px solid #c8c8c8;
    border-radius: 5px;
    padding: 3px;
}

.session-cluster select {
    border: none;
    background: transparent;
}

/* Edit/Export/History/Providers as small chip buttons — tried a middot divider, then plain
   spacing, both reverted per feedback ("still not distinguish enough"). Chip's
   background/border matches .session-cluster's box above it, so the two rows read as the same
   family of control. flex-wrap so a narrow pane wraps chips onto more lines instead of
   overflowing/clipping. */
.nav-cluster {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-top: 4px;
    /* Matches .icon-btn's 13px so these read as a consistent size rather than inheriting
       whatever larger default .link-btn would otherwise pick up here. */
    font-size: 13px;
}

.nav-cluster .link-btn {
    background: #f3f2f1;
    border: 1px solid #c8c8c8;
    border-radius: 4px;
    padding: 3px 8px;
}

.nav-cluster .link-btn:hover {
    background: #e8e8e8;
    text-decoration: none;
}

/* Pushes the settings gear to this row's own right edge, past the left-aligned Export/History/
   Providers/Story Bible chips — flex's normal auto-margin trick, no separate wrapper needed. */
.nav-cluster-settings-btn {
    margin-left: auto;
}

.icon-btn {
    width: 26px;
    height: 26px;
    padding: 0;
    font-size: 13px;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 4px;
    flex-shrink: 0;
}

.icon-btn.ghost {
    background: transparent;
    border-color: transparent;
    color: #616161;
    /* Bumped up from the shared 13px — the pencil/× glyphs read visibly smaller than the native
       <select> dropdown arrow they sit next to (browser-drawn, not something this font-size
       controls), even though they were already matched to it in color. */
    font-size: 15px;
}

/* Settings gear (sessionSwitcher.ts) is an inline SVG with fill="currentColor", not a text
   glyph — an earlier `&#9881;` HTML entity attempt rendered as a thin ring of spokes ("a boat
   steering wheel") and didn't reliably pick up .ghost's color at all in at least one render.
   `display: block` clears the small baseline gap an inline SVG would otherwise leave under
   .icon-btn's flex centering. */
.icon-btn svg {
    display: block;
}

/* The chat screen nests an extra flex level: .screen > [.session-tabs (fixed), #chat-content
   (grows)] and #chat-content > [.chat-content-header (fixed), .message-list (grows most),
   notices (fixed), input form (fixed)]. #chat-content needs its own explicit flex-shrink/grow
   and its own child-spacing rule since it's not a direct .screen child in the generic rules
   above (an ID selector's specificity overrides the generic .screen > * rules where they'd
   otherwise apply, e.g. flex-shrink:0 — deliberate, not accidental). */
#chat-content {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}

#chat-content > *:not(:last-child) {
    margin-bottom: 12px;
}

#chat-content > *:not(.message-list) {
    flex-shrink: 0;
}

/* Explicit 6px overrides for the header -> toggle -> message-list gaps, replacing the 12px
   default above for just these two children. Same (1 id, 1 class) specificity as that generic
   rule, so this only wins by coming after it in the cascade — deliberately placed here rather
   than reaching for !important. */
#chat-content > .chat-content-header {
    margin-bottom: 6px;
}

#chat-content > .topbar-toggle-row {
    margin-bottom: 6px;
}

/* Same idea as .chat-content-header above — .form-actions already supplies its own 8px
   margin-top (shared with Add provider's Save, which doesn't have this problem since its
   fields sit inside an actual <form>, breaking the direct-#chat-content-child chain). Edit
   session and Start a new chat render their fields straight into #chat-content with no such
   wrapper, so without this, the field right before .form-actions doubles up: the generic 12px
   here plus .form-actions' 8px stack to ~20px instead of the intended 8px. */
#chat-content > .form-last-field {
    margin-bottom: 0;
}

.message-list {
    flex: 1;
    overflow-y: auto;
    overflow-x: hidden;
    border: 1px solid #e0e0e0;
    border-radius: 4px;
    padding: 8px;
    min-height: 0;
}

.message {
    display: flex;
    flex-direction: column;
    margin-bottom: 18px;
}

.message:last-child {
    margin-bottom: 0;
}

.message-user {
    align-items: flex-end;
}

.message-assistant {
    align-items: flex-start;
}

.message-bubble {
    max-width: 85%;
    padding: 8px 10px;
    border-radius: 8px;
    border: 1px solid transparent;
    white-space: pre-wrap;
    word-break: break-word;
    text-align: left;
}

.message-user .message-bubble {
    background: #0f6cbd;
    color: #fff;
}

.message-assistant .message-bubble {
    background: #f3f2f1;
    color: #242424;
    border-color: #e0e0e0;
    /* Wider than the shared 85% max-width above — AI replies are usually the longer messages,
       so this leaves a fixed, small gap instead of an increasingly large one as the pane widens. */
    max-width: calc(100% - 1.5cm);
}

/* Marks whichever reply Regenerate currently targets (chat.ts's pendingRegenerateIndex) — same
   blue as .regenerate-banner/.selection-chip, so it reads as "this is what that banner refers
   to" rather than an unrelated highlight. Border only (no background tint): a filled bubble
   would fight the collapsed/expanded text's own readability, and this reply's content hasn't
   changed yet — it's still the old one, just marked as about to be replaced. */
.message.is-regenerate-target .message-bubble {
    border-color: #0f6cbd;
    box-shadow: 0 0 0 1px #0f6cbd;
}

/* Truncates the reply to a fixed 2 lines via line-clamp (same technique as
   .quoted-block.is-collapsed .quoted-block-text below) rather than blanking it — the footer row
   (collapse arrow + token count) keeps its normal 4px margin-top above it either way, same as
   the expanded state, since there's always real text above it now. */
.message-bubble.is-collapsed .message-bubble-text {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.message-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-top: 4px;
}

.message-footer-left {
    display: flex;
    align-items: center;
    /* Wide enough that a slightly-off tap lands on empty space rather than the neighboring
       button — collapse and delete have very different consequences, so a mis-tap here is
       worse than the same mistake elsewhere. */
    gap: 10px;
}

/* Same small transparent icon-button pattern as .topbar-toggle-btn — kept as its own rule
   (rather than sharing that class) since it lives inside a message bubble, not the toolbar. */
.bubble-collapse-btn {
    width: auto;
    height: auto;
    padding: 2px;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: transparent;
    color: #8a8a8a;
    flex-shrink: 0;
}

/* Same chevron shape/rotation convention as .topbar-toggle-icon: up = "click to collapse",
   down (the un-rotated base state) = "click to expand". */
.bubble-collapse-icon {
    transition: transform 0.15s ease;
}

.bubble-collapse-icon.is-up {
    transform: rotate(180deg);
}

/* Same small transparent icon-button footprint as .bubble-collapse-btn, right next to it. */
.bubble-delete-btn {
    width: auto;
    height: auto;
    padding: 2px 4px;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: transparent;
    color: #8a8a8a;
    /* Bumped up from the shared 13px, same reasoning and same final size as .icon-btn.ghost's ×. */
    font-size: 15px;
    flex-shrink: 0;
}

.bubble-delete-btn:hover {
    color: #d13438;
}

/* Anchors the floating confirmation popup to the delete button, same pattern as
   .info-icon-wrap/.info-bubble in providerForm.ts (position:relative wrap + absolutely
   positioned bubble) — clicking the X never changes the footer row itself, only opens this
   popup on top of it. */
.delete-confirm-wrap {
    position: relative;
    display: inline-flex;
}

/* Hidden by default; shown by .is-open, toggled in chat.ts's wireDeleteButtons on click and
   closed by a click anywhere outside the wrap — same mechanics as .info-bubble above. Opens
   upward (bottom: 100%) since the footer already sits at the bottom of the bubble; opening
   downward would risk spilling past the message list into the input area below.
   Styled as a light card with a real .danger-btn, not a dark info-tooltip — this is a
   destructive-action confirmation (same intent as sessionHistory.ts's delete card), and a plain
   dark bubble reads as neutral/informational, under-signaling that. */
.delete-confirm-popup {
    display: none;
    position: absolute;
    bottom: calc(100% + 6px);
    left: 0;
    width: 190px;
    max-width: 70vw;
    background: #fff;
    color: #424242;
    font-size: 12px;
    line-height: 1.4;
    padding: 10px;
    border: 1px solid #e0e0e0;
    border-radius: 4px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
    z-index: 20;
    white-space: normal;
    text-align: left;
}

.delete-confirm-wrap.is-open .delete-confirm-popup {
    display: block;
}

/* Session-switcher's own use of this same confirm-popup pattern (sessionSwitcher.ts's × /
   "Delete this chat" button) — that button sits at the very top of the screen, in
   .session-cluster's row, unlike the message-footer's own X this pattern was built for (which
   sits at the bottom of a bubble, hence the default opening upward). Opening upward here would
   push the popup off the top of the pane. Flipped to open downward, and leftward (right: 0)
   since this button is also the last, rightmost item in its row — same left/right-flip idea
   #session-token-info's own override uses elsewhere for the same "near the pane's right edge"
   reason, just also flipping the vertical direction this time. */
.session-close-confirm-wrap .delete-confirm-popup {
    bottom: auto;
    top: calc(100% + 6px);
    left: auto;
    right: 0;
}

.delete-confirm-popup p {
    margin: 0 0 8px;
}

.delete-confirm-popup-actions {
    display: flex;
    gap: 6px;
}

/* Smaller than the shared button base (6px/12px padding, inherited font-size) so "Yes, delete"
   and "Cancel" both fit comfortably in the popup's 190px width without wrapping. */
.delete-confirm-popup-actions button {
    font-size: 11px;
    padding: 4px 8px;
}

.message-tokens {
    font-size: 10px;
    color: #8a8a8a;
}

/* Lives in .topbar-toggle-row, right of the collapse arrow (see that rule's comment) — the
   running total across the whole conversation, as opposed to each reply's own in/out count
   (.message-tokens). Single line: "Total Tokens: <number>" then the "?" icon at the end, per
   explicit request. Flex + gap handles spacing between the three pieces directly, so none of
   them need their own margin — simpler than the stacked-two-line version this replaced, which
   needed a manual margin hack to level the icon against the text. Font size and color
   intentionally match .message-tokens so the two token displays read as one consistent style.
   Only #session-token-info (the icon) is interactive — the label and number are plain sibling
   spans outside .info-icon-wrap, not wrapped inside it, so hovering/clicking them does nothing,
   per explicit request. */
.session-token-total {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 10px;
    color: #8a8a8a;
    /* Fixed width — .topbar-toggle-row-main (the arrow + model tag) is the side that shrinks
       first in a narrow pane, via that rule's own flex: 1 1 auto; this side keeps its natural
       size instead of getting squeezed. */
    flex-shrink: 0;
}

/* Shown in place of the reply while a message is in flight — same bubble shape/position an
   assistant reply will land in, so nothing shifts when the real reply replaces it.
   Clickable while a reply is pending — the second of two ways to cancel it (the other is
   Send/"Stop Thinking" — chat.ts's stopPendingRequest, same click target either way). A custom
   red "X" cursor (not a stock keyword — CSS has no built-in "X" cursor) is the visual cue, per
   explicit request: it's meant to read specifically as "kill this," which crosshair (a generic
   precision-select "+") doesn't communicate, and red ties it to the same stop/attention color
   as .is-stop-thinking. `10 10` hotspot centers the click point on the icon; `crosshair` is the
   fallback for the rare case a custom cursor image fails to load. */
.typing-bubble {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 10px 14px;
    cursor:
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cline x1='4' y1='4' x2='16' y2='16' stroke='%23d13438' stroke-width='3' stroke-linecap='round'/%3E%3Cline x1='16' y1='4' x2='4' y2='16' stroke='%23d13438' stroke-width='3' stroke-linecap='round'/%3E%3C/svg%3E")
        10 10,
        crosshair;
}

.spinner {
    width: 14px;
    height: 14px;
    border: 2px solid #c8c8c8;
    /* Only the top edge colored — that's what makes it read as spinning rather than a static ring. */
    border-top-color: #616161;
    border-radius: 50%;
    animation: spinner-rotate 0.7s linear infinite;
}

@keyframes spinner-rotate {
    to {
        transform: rotate(360deg);
    }
}

/* Trailing blink at the end of a reply's growing text while it's still streaming in (chat.ts's
   pendingReplyBubbleHtml) — replaced entirely once the bubble lands as a real message (a full
   renderMessages() rebuild), so it never needs explicit removal of its own. A plain inline
   block, not a ::after pseudo-element, since it has to sit right after AI-generated text whose
   own trailing content (bold/italic close tags) can vary. */
.streaming-cursor {
    display: inline-block;
    width: 2px;
    height: 1em;
    margin-left: 1px;
    vertical-align: text-bottom;
    background: #242424;
    animation: streaming-cursor-blink 1s step-start infinite;
}

@keyframes streaming-cursor-blink {
    50% {
        opacity: 0;
    }
}

/* Renders a toQuoteBlock()-marked passage (see domUtils.ts) as a set-off block instead of raw
   "> " characters — used for Word-selected text attached to an outgoing message. A nested
   "bubble" (rounded card) rather than a border-strip, echoing the same reply-preview convention
   as iMessage/WhatsApp. Same light-blue palette as .selection-chip, so the passage looks the
   same before and after sending. Plain block layout — when renderMarkdownLite was given
   collapsibleQuote options, .quoted-block-text (block-level) is followed by .quote-collapse-btn,
   which lands on its own line below via normal block flow, bottom-left, same position as the
   collapse arrow in an AI reply's footer. */
.quoted-block {
    display: block;
    margin: 4px 0;
    padding: 6px 10px;
    background: #ddedfa;
    color: #205081;
    border-radius: 8px;
}

/* Collapsed to a fixed 2 lines via line-clamp rather than a max-height guess — stays exactly 2
   lines regardless of font-size changes, instead of an arbitrary pixel cutoff mid-character. */
.quoted-block.is-collapsed .quoted-block-text {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Same small transparent icon-button pattern as .bubble-collapse-btn, colored to match the
   quote block's own blue text/border instead of the neutral gray used elsewhere — this control
   lives inside a colored bubble, not the plain message background. Same 4px top margin as
   .message-footer, for the same "space above reads like the gap below" reasoning. */
.quote-collapse-btn {
    width: auto;
    height: auto;
    padding: 2px;
    margin-top: 4px;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background: transparent;
    color: #205081;
}

/* Same chevron shape/rotation convention as .bubble-collapse-icon/.topbar-toggle-icon: up =
   "click to collapse", down (the un-rotated base state) = "click to expand". */
.quote-collapse-icon {
    transition: transform 0.15s ease;
}

.quote-collapse-icon.is-up {
    transform: rotate(180deg);
}

/* One button now (Regenerate — Insert/Replace were dropped 2026-08-10: real usage showed authors
   don't want a one-click "commit this exact AI wording" action for prose, they blend/rewrite it
   themselves). Kept as its own row/class rather than folding into .message-footer since it's
   still conceptually a distinct "act on this whole reply" affordance, not part of the
   collapse/delete/token-count footer. */
.message-actions {
    margin-top: 4px;
    align-self: stretch;
    display: flex;
}

.message-actions button {
    text-align: center;
    font-size: 12px;
    padding: 4px 8px;
    /* Same blue as .selection-chip's border, keeping .secondary-btn's grey fill. */
    border-color: #a8cfec;
}

#selection-chip-container {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.selection-chip {
    display: flex;
    align-items: center;
    gap: 6px;
    background: #ddedfa;
    border: 1px solid #a8cfec;
    border-radius: 4px;
    padding: 6px 8px;
    font-size: 12px;
    color: #205081;
    cursor: pointer;
}

/* Toggled off — still visible (so it can be turned back on), just visually de-emphasized. */
.selection-chip.excluded {
    background: #f3f2f1;
    border-color: #c8c8c8;
    color: #616161;
}

.selection-chip.excluded .selection-chip-text {
    text-decoration: line-through;
}

.selection-chip-text {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.selection-chip-state {
    font-size: 10.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    flex-shrink: 0;
    padding: 2px 7px;
    border-radius: 999px;
    background: #0f6cbd;
    color: #fff;
}

/* Same solid-blue pill shape as .selection-chip-state, but clickable — reads as "part of this
   blue chip" while still visually distinct from the plain Attached/Excluded status label next
   to it. Only appears once a passage is actually selected/attached (see renderChip), since
   critiquing "this chapter" only makes sense once the chapter's own text is attached. */
.selection-chip-critique {
    font-size: 10.5px;
    font-weight: 600;
    flex-shrink: 0;
    padding: 2px 8px;
    border: none;
    border-radius: 999px;
    background: #0f6cbd;
    color: #fff;
    cursor: pointer;
}

.selection-chip-critique:hover {
    background: #0c5aa0;
}

/* Greyed out once this exact passage has already been critiqued (chat.ts's
   critiqueUsedForSelection) — placed after :hover so it wins the tie on specificity if the
   pointer happens to be resting over the button while it's disabled. Un-grays automatically the
   moment the underlying Word selection actually changes to something new. */
.selection-chip-critique:disabled {
    background: #c8c8c8;
    color: #757575;
    cursor: default;
}

/* Toggled off — a neutral gray bubble instead of the blue "attached" one. */
.selection-chip.excluded .selection-chip-state {
    background: #c8c8c8;
    color: #424242;
}

.selection-chip-remove {
    background: none;
    border: none;
    color: inherit;
    cursor: pointer;
    font-size: 14px;
    line-height: 1;
    padding: 0 2px;
    flex-shrink: 0;
}

.chat-input-form {
    display: flex;
    align-items: flex-end;
    gap: 8px;
}

.chat-input-form textarea {
    flex: 1;
}

/* Stacks the running total directly above Send, both right-aligned — keeps the total right
   next to the button it's reporting on instead of off with the message list above. */
.send-btn-column {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 4px;
}
