:root {
    --bg: #f7f6f3;
    --surface: #ffffff;
    --surface-2: #efece6;
    --border: #e2ddd4;
    --text: #23211d;
    --text-dim: #6f6a60;
    --accent: #c8632f;
    --accent-soft: #f6e9e1;
    --danger: #b3402c;
    --radius: 10px;
    --sidebar-w: 288px;
    --lookup-w: 320px;
    /* Topbar box: 8px padding × 2 + 42px control + 1px border. The mobile toast
       offset is derived from this, so keep the two in sync. */
    --topbar-h: 59px;
    --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    --mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
    :root {
        --bg: #1c1b19;
        --surface: #262421;
        --surface-2: #2f2c28;
        --border: #3b3733;
        --text: #edeae4;
        --text-dim: #a29b90;
        --accent: #d97a4b;
        --accent-soft: #3a2b22;
        --danger: #d4634c;
    }
}

* {
    box-sizing: border-box;
}

/* The UA stylesheet's `[hidden] { display: none }` loses to any author rule
   that sets display — `.btn { display: inline-block }` was leaving the Stop and
   Install buttons permanently visible. */
[hidden] {
    display: none !important;
}

html,
body {
    height: 100%;
}

html {
    /* Stop iOS Safari inflating text in landscape. */
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
    font-family: var(--font);
    font-size: 15px;
    line-height: 1.6;
    color: var(--text);
    background: var(--bg);
}

.app {
    display: grid;
    grid-template-columns: var(--sidebar-w) 1fr;
    height: 100vh;
    /* dvh tracks the mobile URL bar as it collapses; vh above is the fallback. */
    height: 100dvh;
}

/* ---------------------------------------------------------------- topbar */
/* Hidden on desktop — the sidebar is always visible there. */

.topbar {
    display: none;
    align-items: center;
    gap: 10px;
    min-height: calc(var(--topbar-h) + env(safe-area-inset-top));
    padding: 8px 12px;
    padding-top: calc(8px + env(safe-area-inset-top));
    background: var(--surface);
    border-bottom: 1px solid var(--border);
}

.topbar__heading {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    line-height: 1.25;
}

.topbar__title {
    overflow: hidden;
    font-size: 15px;
    font-weight: 650;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* The prompt + model line the hidden chat header used to carry. */
.topbar__sub {
    overflow: hidden;
    font-size: 11.5px;
    color: var(--text-dim);
    white-space: nowrap;
    text-overflow: ellipsis;
}

.topbar__burger,
.topbar__icon {
    display: flex;
    flex-direction: column;
    flex-shrink: 0;
    gap: 4px;
    align-items: center;
    justify-content: center;
    /* 42px clears the 44px minimum tap target once the 1px border counts. */
    width: 42px;
    height: 42px;
    padding: 0;
    font: inherit;
    font-size: 20px;
    color: var(--text);
    cursor: pointer;
    background: none;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    -webkit-tap-highlight-color: transparent;
}

.topbar__burger:active,
.topbar__icon:active {
    background: var(--surface-2);
}

.topbar__burger span {
    width: 17px;
    height: 2px;
    background: var(--text);
    border-radius: 2px;
    transition:
        transform 0.18s ease,
        opacity 0.15s ease;
}

/* Hamburger morphs into a close (×) while the drawer is open. */
.topbar__burger[aria-expanded='true'] span:nth-child(1) {
    transform: translateY(6px) rotate(45deg);
}
.topbar__burger[aria-expanded='true'] span:nth-child(2) {
    opacity: 0;
}
.topbar__burger[aria-expanded='true'] span:nth-child(3) {
    transform: translateY(-6px) rotate(-45deg);
}

/* ------------------------------------------------------------- popover -- */

.popover-wrap {
    position: relative;
    flex-shrink: 0;
}

.popover {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: 45;
    min-width: 190px;
    padding: 6px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: 0 10px 30px rgb(0 0 0 / 18%);
}

.popover__item {
    display: block;
    width: 100%;
    padding: 10px 12px;
    font: inherit;
    font-size: 14px;
    text-align: left;
    color: var(--text);
    cursor: pointer;
    background: none;
    border: 0;
    border-radius: 8px;
}

.popover__item:active,
.popover__item:hover {
    background: var(--surface-2);
}

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

.scrim {
    position: fixed;
    inset: 0;
    z-index: 39;
    background: rgb(0 0 0 / 45%);
    border: 0;
}

/* --------------------------------------------------------------- sidebar */

.sidebar {
    display: flex;
    flex-direction: column;
    min-height: 0;
    background: var(--surface-2);
    border-right: 1px solid var(--border);
}

.sidebar__head {
    padding: 16px;
    border-bottom: 1px solid var(--border);
}

.brand {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 14px;
    font-weight: 650;
    font-size: 15px;
    color: var(--text);
    text-decoration: none;
}

.brand__dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--accent);
}

.thread-list {
    flex: 1;
    overflow-y: auto;
    padding: 8px;
}

.thread {
    display: flex;
    align-items: center;
    gap: 4px;
    border-radius: var(--radius);
    transition: background 0.12s;
}

.thread:hover {
    background: var(--surface);
}
.thread.is-active {
    background: var(--accent-soft);
}

.thread__link {
    flex: 1;
    min-width: 0;
    padding: 9px 10px;
    color: inherit;
    text-decoration: none;
}

.thread__title {
    display: block;
    overflow: hidden;
    font-size: 14px;
    font-weight: 550;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.thread__meta {
    display: block;
    overflow: hidden;
    font-size: 11.5px;
    color: var(--text-dim);
    white-space: nowrap;
    text-overflow: ellipsis;
}

.thread__actions {
    display: none;
    gap: 2px;
    padding-right: 6px;
}
.thread:hover .thread__actions,
.thread.is-active .thread__actions {
    display: flex;
}

.thread__actions button {
    padding: 3px 5px;
    font-size: 13px;
    color: var(--text-dim);
    cursor: pointer;
    background: none;
    border: 0;
    border-radius: 6px;
}

.thread__actions button:hover {
    color: var(--text);
    background: var(--surface-2);
}

.sidebar__foot {
    display: flex;
    /* The link + model badge don't fit side by side in a narrow sidebar; let
       them stack instead of the link wrapping mid-phrase. */
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 6px 8px;
    padding: 12px 16px;
    border-top: 1px solid var(--border);
}

.install-btn {
    display: block;
    margin: 0 16px calc(14px + env(safe-area-inset-bottom));
    text-align: center;
}

.sidebar__link {
    font-size: 13px;
    color: var(--text-dim);
    text-decoration: none;
}
.sidebar__link:hover {
    color: var(--text);
}

.sidebar__logout {
    flex-basis: 100%;
    margin: 0;
}

.sidebar__link--button {
    padding: 0;
    font: inherit;
    font-size: 13px;
    color: var(--text-dim);
    cursor: pointer;
    background: none;
    border: 0;
}

/* ------------------------------------------------------------------ login */

.auth-body {
    display: grid;
    place-items: center;
    min-height: 100vh;
    min-height: 100dvh;
    padding: 24px;
    padding-top: calc(24px + env(safe-area-inset-top));
    padding-bottom: calc(24px + env(safe-area-inset-bottom));
}

.auth {
    width: 100%;
    max-width: 380px;
}

.auth__head {
    margin-bottom: 22px;
    text-align: center;
}

.auth__logo {
    width: 64px;
    height: 64px;
    margin-bottom: 14px;
    border-radius: 16px;
    box-shadow: 0 6px 18px rgb(200 99 47 / 28%);
}

.auth__title {
    margin: 0 0 6px;
    font-size: 23px;
    letter-spacing: -0.01em;
}

.auth__sub {
    margin: 0;
    font-size: 14px;
    color: var(--text-dim);
}

.auth__card {
    padding: 24px 22px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 16px;
    box-shadow: 0 10px 40px rgb(0 0 0 / 8%);
}

/* `.auth` prefix on purpose: a bare `.auth__submit` ties with `.btn`, and `.btn`
   is declared later in this file, so its 8px padding would win. */
.auth .auth__submit {
    display: block;
    width: 100%;
    margin-top: 6px;
    padding: 13px;
    font-size: 15px;
}

.auth__note {
    margin: 16px 0 0;
    font-size: 12.5px;
    color: var(--text-dim);
    text-align: center;
}

/* On a phone the floating card reads as a cramped island with a lot of dead
   space around it. Full-bleed, top-aligned, larger controls instead. */
@media (max-width: 620px) {
    .auth-body {
        display: block;
        padding: 0;
    }

    .auth {
        max-width: none;
        min-height: 100dvh;
        padding: 0 20px;
        padding-top: calc(8vh + env(safe-area-inset-top));
        padding-bottom: calc(24px + env(safe-area-inset-bottom));
    }

    .auth__head {
        margin-bottom: 26px;
    }

    .auth__logo {
        width: 72px;
        height: 72px;
        border-radius: 18px;
    }

    .auth__title {
        font-size: 26px;
    }

    /* Card chrome adds nothing once it spans the whole width. */
    .auth__card {
        padding: 0;
        background: none;
        border: 0;
        box-shadow: none;
    }

    /* --surface instead of --bg so the fields don't dissolve into the page now
       that the card behind them is gone. (The 16px floor is applied by the
       shared .field rule above.) */
    .auth__card input {
        padding: 13px 14px;
        background: var(--surface);
    }

    .auth .auth__submit {
        padding: 15px;
        font-size: 16px;
    }
}

.model-badge {
    padding: 2px 7px;
    font-family: var(--mono);
    font-size: 10.5px;
    color: var(--text-dim);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 999px;
}

.empty {
    padding: 16px 10px;
    font-size: 13px;
    color: var(--text-dim);
}

/* ------------------------------------------------------------------ main */

.main {
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
}

.chat-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    padding: 14px 24px;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
}

.chat-head__title {
    margin: 0;
    font-size: 16px;
    font-weight: 650;
}
.chat-head__sub {
    margin: 2px 0 0;
    font-size: 12.5px;
    color: var(--text-dim);
}
.chat-head__actions {
    display: flex;
    flex-shrink: 0;
    gap: 8px;
}

.chat {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-height: 0;
}

.messages {
    flex: 1;
    padding: 28px 24px 12px;
    overflow-y: auto;
    /* Stop a scroll that hits the end from bouncing the whole page on mobile. */
    overscroll-behavior: contain;
}

/* Assistant sits on the left, the user on the right: `row-reverse` flips both
   the avatar/bubble order and the packing edge in one declaration. */
.msg {
    display: flex;
    gap: 12px;
    align-items: flex-start;
    max-width: 860px;
    margin: 0 auto 18px;
}

.msg--user {
    flex-direction: row-reverse;
}

.msg__avatar {
    display: flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    font-size: 11px;
    font-weight: 650;
    border: 1px solid var(--border);
    border-radius: 8px;
}

/* Avatars are gone at every size. Right-aligned tinted bubble = you, plain
   left-aligned text = the assistant; that reads instantly and hands the 32px
   column plus its gap back to the text. */
.msg__avatar {
    display: none;
}

.msg__body {
    min-width: 0;
    overflow-wrap: anywhere;
    font-size: 15.5px;
    line-height: 1.68;
}

/* The assistant gets no bubble — the same layout Claude uses. A card around a
   long explanation shortens every line and boxes the answer into a corner of a
   frame that is mostly empty. Plain text takes the full reading column. */
.msg--assistant .msg__body {
    max-width: 100%;
    flex: 1;
}

/* The user's turn keeps a bubble: it's short, and it's the only marker of who
   is speaking now that the avatars are gone. */
.msg--user .msg__body {
    max-width: 78%;
    padding: 10px 15px;
    background: var(--accent-soft);
    border-radius: 16px;
    border-bottom-right-radius: 5px;
}
.msg__body > :first-child {
    margin-top: 0;
}
.msg__body > :last-child {
    margin-bottom: 0;
}
.msg__body p {
    margin: 0 0 10px;
}
.msg__body ul,
.msg__body ol {
    margin: 0 0 10px;
    padding-left: 22px;
}
.msg__body li {
    margin-bottom: 4px;
}

.msg__body code {
    padding: 1px 5px;
    font-family: var(--mono);
    font-size: 0.88em;
    background: var(--surface-2);
    border-radius: 4px;
}

.msg__body pre {
    padding: 12px 14px;
    overflow-x: auto;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.msg__body pre code {
    padding: 0;
    background: none;
}

/* The assistant's text now sits directly on --bg, so its code blocks need a
   surface of their own to read as blocks. --surface-2 also disappears against
   the user bubble's tint. */
.msg--assistant .msg__body pre,
.msg--user .msg__body code,
.msg--user .msg__body pre {
    background: var(--surface);
}

/* the blinking caret while text is being typed out */
.msg__body.is-typing::after {
    display: inline-block;
    width: 7px;
    height: 1em;
    vertical-align: text-bottom;
    content: '';
    background: var(--accent);
    border-radius: 1px;
    animation: blink 1s steps(2, start) infinite;
}

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

.chat-empty {
    max-width: 640px;
    margin: 12vh auto 0;
    text-align: center;
}
.chat-empty h2 {
    margin: 0 0 6px;
    font-size: 20px;
}
.chat-empty p {
    margin: 0 0 20px;
    color: var(--text-dim);
}

.suggestions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    justify-content: center;
}

.chip {
    padding: 7px 13px;
    font-size: 13px;
    font-family: inherit;
    color: var(--text);
    cursor: pointer;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 999px;
}

.chip:hover {
    border-color: var(--accent);
}

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

.composer {
    display: flex;
    flex-direction: column;
    gap: 8px;
    max-width: 860px;
    width: 100%;
    margin: 0 auto;
    padding: 12px 24px 24px;
}

.composer__row {
    display: flex;
    gap: 10px;
    align-items: flex-end;
}

.composer__meta {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 20px;
    padding-left: 2px;
}

.model-picker {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12.5px;
    color: var(--text-dim);
}

.model-picker select {
    padding: 3px 6px;
    font: inherit;
    font-size: 12.5px;
    color: var(--text);
    cursor: pointer;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
}

.model-picker select:focus {
    border-color: var(--accent);
    outline: none;
}

.model-picker__hint {
    font-size: 12px;
    color: var(--text-dim);
}

.model-picker__hint.is-error {
    color: var(--danger);
}

.composer textarea {
    flex: 1;
    min-width: 0;
    max-height: 220px;
    padding: 12px 14px;
    font: inherit;
    color: inherit;
    resize: none;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

/* Send / Stop share one slot — an icon+label pill on desktop that collapses
   to a circular icon button on phones, the way every chat app does it. */
.composer__action {
    display: flex;
    flex-shrink: 0;
    gap: 6px;
    align-items: center;
    justify-content: center;
    height: 44px;
    padding: 0 16px;
    font: inherit;
    font-size: 13.5px;
    font-weight: 550;
    color: #fff;
    cursor: pointer;
    background: var(--accent);
    border: 1px solid var(--accent);
    border-radius: var(--radius);
    -webkit-tap-highlight-color: transparent;
}

.composer__action:hover {
    filter: brightness(1.07);
}

.composer__action:disabled {
    cursor: not-allowed;
    opacity: 0.5;
}

.composer__action--stop {
    color: var(--danger);
    background: var(--surface);
    border-color: var(--border);
}

.composer__action svg {
    width: 18px;
    height: 18px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.composer textarea:focus {
    border-color: var(--accent);
    outline: none;
}

/* --------------------------------------------------------------- buttons */

.btn {
    padding: 8px 14px;
    font: inherit;
    font-size: 13.5px;
    font-weight: 550;
    color: var(--text);
    cursor: pointer;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    text-decoration: none;
    display: inline-block;
}

.btn:hover {
    border-color: var(--text-dim);
}
.btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.btn--primary {
    color: #fff;
    background: var(--accent);
    border-color: var(--accent);
}

.btn--primary:hover {
    filter: brightness(1.07);
}
.btn--danger {
    color: var(--danger);
    border-color: var(--border);
}
.btn--danger:hover {
    color: #fff;
    background: var(--danger);
    border-color: var(--danger);
}
.btn--block {
    display: block;
    width: 100%;
}
.btn--sm {
    padding: 5px 10px;
    font-size: 12.5px;
}

/* --------------------------------------------------------------- welcome */

.welcome {
    max-width: 680px;
    margin: 0 auto;
    padding: 12vh 24px 24px;
}
.welcome h1 {
    margin: 0 0 10px;
    font-size: 26px;
}
.welcome p {
    color: var(--text-dim);
}
.welcome__actions {
    display: flex;
    gap: 10px;
    margin: 22px 0 34px;
}

.welcome__prompts {
    display: grid;
    gap: 10px;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}

.prompt-card {
    padding: 12px 14px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.prompt-card strong {
    display: block;
    font-size: 14px;
}
.prompt-card em {
    font-size: 12.5px;
    font-style: normal;
    color: var(--text-dim);
}

/* ---------------------------------------------------------------- dialog */

.dialog {
    width: min(620px, calc(100vw - 32px));
    padding: 0;
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 14px;
}

/* ----------------------------------------------------------------- toast */

/* Anchored top-right on desktop and top-centre on mobile — deliberately not the
   bottom, where it would sit on top of the composer. */
.toast-host {
    position: fixed;
    top: calc(16px + env(safe-area-inset-top));
    right: 16px;
    z-index: 60;
    display: flex;
    flex-direction: column;
    gap: 8px;
    align-items: flex-end;
    /* The host spans the corner; only the toasts themselves are clickable. */
    pointer-events: none;
}

.toast {
    display: flex;
    align-items: center;
    gap: 8px;
    max-width: min(360px, calc(100vw - 32px));
    padding: 11px 15px;
    font-size: 14px;
    font-weight: 550;
    color: var(--text);
    cursor: pointer;
    pointer-events: auto;
    background: var(--surface);
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent);
    border-radius: 10px;
    box-shadow: 0 8px 26px rgb(0 0 0 / 16%);
    animation: toast-in 0.2s ease-out;
}

.toast--success {
    border-left-color: #3f8f5f;
}

.toast--error {
    border-left-color: var(--danger);
}

.toast.is-out {
    animation: toast-out 0.18s ease-in forwards;
}

@keyframes toast-in {
    from {
        opacity: 0;
        transform: translateY(-8px);
    }
}

@keyframes toast-out {
    to {
        opacity: 0;
        transform: translateY(-8px);
    }
}

/* Below 900px a topbar exists; drop the toast under it instead of over the
   hamburger and the new-thread button. */
@media (max-width: 900px) {
    .toast-host {
        top: calc(var(--topbar-h) + env(safe-area-inset-top) + 10px);
    }
}

@media (max-width: 620px) {
    .toast-host {
        right: 12px;
        left: 12px;
        align-items: stretch;
    }

    .toast {
        max-width: none;
    }
}

/* Confirm / prompt / alert — narrower than the thread-creation dialog. */
.dialog--sm {
    width: min(420px, calc(100vw - 32px));
}

.dialog::backdrop {
    background: rgb(0 0 0 / 45%);
}

.dialog__text {
    margin: 0;
    color: var(--text-dim);
}

/* Outlined, the destructive choice reads the same weight as Cancel. In a
   confirmation that's the one place the two must never look alike. */
.dialog__actions .btn--danger {
    color: #fff;
    background: var(--danger);
    border-color: var(--danger);
}

.dialog__actions .btn--danger:hover {
    filter: brightness(1.08);
}

/* Give the modal a little presence on open rather than snapping in. */
@media (prefers-reduced-motion: no-preference) {
    .dialog[open] {
        animation: modal-in 0.16s ease-out;
    }
}

@keyframes modal-in {
    from {
        opacity: 0;
        transform: translateY(6px) scale(0.98);
    }
}
/* Three bands: fixed head, scrolling middle, fixed foot. Scrolling the whole
   body pushed the title and the primary action out of reach in the prompt
   picker, which is the one dialog tall enough to need scrolling. */
.dialog__body {
    display: flex;
    flex-direction: column;
    max-height: min(82vh, 82dvh);
    padding: 0;
}

.dialog__head {
    flex-shrink: 0;
    padding: 18px 22px 14px;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
}

.dialog__body h2 {
    margin: 0;
    font-size: 18px;
}

.dialog__content {
    flex: 1;
    min-height: 0;
    padding: 18px 22px;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* The last field carries the section's bottom padding already. */
.dialog__content > .field--last,
.dialog__content > :last-child {
    margin-bottom: 0;
}

.dialog__foot {
    flex-shrink: 0;
    padding: 14px 22px;
    padding-bottom: calc(14px + env(safe-area-inset-bottom));
    background: var(--surface);
    border-top: 1px solid var(--border);
}

.dialog__actions {
    display: flex;
    gap: 10px;
    justify-content: flex-end;
}

.dialog__error {
    margin: 0 0 12px;
    padding: 9px 12px;
    font-size: 13px;
    color: var(--danger);
    background: var(--accent-soft);
    border-radius: 8px;
}

.field {
    display: block;
    margin-bottom: 16px;
    border: 0;
    padding: 0;
}
.field > span,
.field > legend {
    display: block;
    margin-bottom: 6px;
    font-size: 13px;
    font-weight: 600;
}
.field small {
    display: block;
    margin-top: 4px;
    font-size: 12px;
    color: var(--text-dim);
}
.field--inline {
    display: flex;
    align-items: center;
    gap: 8px;
}
.field--inline > span {
    margin: 0;
    font-weight: 500;
}

/* type='password' was missing here, so the login field rendered at the browser
   default width — visibly narrower than the username box next to it. */
.field input[type='text'],
.field input[type='password'],
.field textarea,
.field select {
    width: 100%;
    padding: 9px 11px;
    font: inherit;
    color: inherit;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 8px;
}

.field select {
    cursor: pointer;
}

.field textarea {
    font-family: var(--mono);
    font-size: 13px;
    resize: vertical;
}
.field input:focus,
.field textarea:focus,
.field select:focus {
    border-color: var(--accent);
    outline: none;
}

.prompt-picker {
    display: grid;
    gap: 8px;
}

.prompt-option {
    display: flex;
    gap: 10px;
    padding: 11px 13px;
    cursor: pointer;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.prompt-option:has(input:checked) {
    background: var(--accent-soft);
    border-color: var(--accent);
}
.prompt-option__body {
    display: block;
    min-width: 0;
}
.prompt-option__body strong {
    display: block;
    font-size: 14px;
}
.prompt-option__body em {
    display: block;
    font-size: 12.5px;
    font-style: normal;
    color: var(--text-dim);
}

.prompt-option__body code {
    display: block;
    margin-top: 6px;
    overflow: hidden;
    font-family: var(--mono);
    font-size: 11.5px;
    color: var(--text-dim);
}

/* -------------------------------------------------------- prompt manager */

.prompt-manager {
    display: grid;
    flex: 1;
    gap: 24px;
    grid-template-columns: minmax(320px, 420px) 1fr;
    min-height: 0;
    padding: 24px;
    overflow-y: auto;
}

.prompt-form {
    position: sticky;
    top: 0;
    align-self: start;
    padding: 18px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 14px;
}

.prompt-form__actions {
    display: flex;
    gap: 10px;
}
.prompt-table {
    display: grid;
    gap: 14px;
    align-content: start;
}

.prompt-row {
    padding: 16px 18px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 14px;
}

.prompt-row__head {
    display: flex;
    align-items: center;
    gap: 8px;
}
.prompt-row__desc {
    display: block;
    margin-bottom: 10px;
    font-size: 12.5px;
    font-style: normal;
    color: var(--text-dim);
}

.prompt-row__content {
    max-height: 190px;
    margin: 0 0 12px;
    padding: 11px 13px;
    overflow: auto;
    font-family: var(--mono);
    font-size: 12px;
    white-space: pre-wrap;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 8px;
}

.prompt-row__actions {
    display: flex;
    gap: 8px;
}

.tag {
    padding: 1px 8px;
    font-size: 11px;
    font-weight: 600;
    color: var(--accent);
    background: var(--accent-soft);
    border-radius: 999px;
}

/* -------------------------------------------------------- lookup panel -- */

/* Closed, it must not occupy an implicit grid column, so display is the switch
   rather than width or transform. */
.lookup {
    display: none;
    flex-direction: column;
    min-height: 0;
    background: var(--surface-2);
    border-left: 1px solid var(--border);
}

.app.has-lookup .lookup {
    display: flex;
}

.app.has-lookup {
    grid-template-columns: var(--sidebar-w) 1fr var(--lookup-w);
}

.lookup__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 14px 16px;
    border-bottom: 1px solid var(--border);
}

.lookup__title {
    margin: 0;
    font-size: 14px;
    font-weight: 650;
}

/* The thread's target language. Dimmed: it's a fact about the thread, not a
   control — the choice is made in the new-thread dialog. */
.lookup__lang {
    font-size: 12px;
    font-weight: 550;
    color: var(--accent);
    white-space: nowrap;
}

.lookup__close {
    flex-shrink: 0;
    width: 30px;
    height: 30px;
    font: inherit;
    font-size: 14px;
    color: var(--text-dim);
    cursor: pointer;
    background: none;
    border: 0;
    border-radius: 8px;
}

.lookup__close:hover {
    color: var(--text);
    background: var(--surface);
}

.lookup__controls {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 14px 16px;
    border-bottom: 1px solid var(--border);
}

/* The master on/off switch. The checkbox stays in the DOM (focusable, keyboard
   operable) and the track is painted from its :checked state. */
.switch {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    cursor: pointer;
}

.switch input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
}

.switch__track {
    position: relative;
    flex-shrink: 0;
    width: 38px;
    height: 22px;
    margin-top: 1px;
    background: var(--border);
    border-radius: 999px;
    transition:
        background 0.16s ease,
        box-shadow 0.16s ease;
}

.switch__track::after {
    position: absolute;
    top: 3px;
    left: 3px;
    width: 16px;
    height: 16px;
    content: '';
    background: var(--surface);
    border-radius: 50%;
    box-shadow: 0 1px 3px rgb(0 0 0 / 25%);
    transition: transform 0.16s ease;
}

.switch input:checked + .switch__track {
    background: var(--accent);
}

.switch input:checked + .switch__track::after {
    transform: translateX(16px);
}

.switch input:focus-visible + .switch__track {
    box-shadow: 0 0 0 3px var(--accent-soft);
}

.switch__text {
    min-width: 0;
}

.switch__text strong {
    display: block;
    font-size: 13.5px;
    font-weight: 600;
}

.switch__text em {
    display: block;
    font-size: 11.5px;
    font-style: normal;
    line-height: 1.45;
    color: var(--text-dim);
}

.lookup__modes {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 14px;
    padding-left: 48px;
}

.check {
    display: flex;
    gap: 6px;
    align-items: center;
    font-size: 12.5px;
    color: var(--text-dim);
    cursor: pointer;
}

.check input {
    accent-color: var(--accent);
    margin: 0;
}

.check input:disabled {
    cursor: not-allowed;
}

.check:has(input:disabled) {
    opacity: 0.5;
    cursor: not-allowed;
}

.lookup__status {
    margin: 0;
    font-size: 11.5px;
    color: var(--text-dim);
}

.lookup__status.is-error {
    color: var(--danger);
}

.lookup__results {
    flex: 1;
    padding: 12px;
    overflow-y: auto;
    overscroll-behavior: contain;
}

.lookup__empty {
    margin: 0;
    padding: 10px 4px;
    font-size: 12.5px;
    color: var(--text-dim);
}

.lookup-card {
    padding: 12px 13px;
    margin-bottom: 10px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.lookup-card.is-pending {
    opacity: 0.7;
}

.lookup-card.is-failed {
    border-color: var(--danger);
}

.lookup-card__term {
    margin: 0 0 2px;
    font-size: 15px;
    font-weight: 650;
    overflow-wrap: anywhere;
}

.lookup-card__ipa {
    font-family: var(--mono);
    font-size: 11.5px;
    font-weight: 400;
    color: var(--text-dim);
    white-space: nowrap;
}

.lookup-card__pos {
    margin: 0 0 6px;
    font-size: 11px;
    font-weight: 600;
    color: var(--accent);
    text-transform: lowercase;
}

.lookup-card__meaning {
    margin: 0;
    font-size: 13.5px;
    line-height: 1.5;
}

.lookup-card.is-failed .lookup-card__meaning {
    color: var(--danger);
}

.lookup-card__note {
    margin: 6px 0 0;
    font-size: 12px;
    line-height: 1.5;
    color: var(--text-dim);
}

.lookup-card__example {
    margin-top: 9px;
    padding-left: 9px;
    border-left: 2px solid var(--accent-soft);
}

.lookup-card__example-en {
    margin: 0;
    font-size: 12.5px;
    font-style: italic;
}

.lookup-card__example-vi {
    margin: 2px 0 0;
    font-size: 12px;
    color: var(--text-dim);
}

/* The only affordance available: individual words can't be given a hover state
   without wrapping each one, which the streaming re-render would undo. */
.messages.is-lookup .msg__body {
    cursor: pointer;
}

/* ----------------------------------------------------- settings block -- */

.settings-block {
    padding: 16px 18px;
    margin-bottom: 18px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.settings-block__title {
    margin: 0 0 12px;
    font-size: 14px;
    font-weight: 650;
}

.settings-block__status {
    margin: 10px 0 0;
    font-size: 12.5px;
    color: var(--text-dim);
}

.settings-block__status.is-error {
    color: var(--danger);
}

/* ------------------------------------------------ selection action menu -- */

/* Floats over the highlighted phrase. Fixed, not absolute: the coordinates come
   straight from Range.getBoundingClientRect(), which is viewport-relative.
   Above the drawer (40) so it stays reachable, below the toasts (60). */
.sel-menu {
    position: fixed;
    z-index: 50;
    display: flex;
    gap: 2px;
    padding: 3px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 999px;
    box-shadow: 0 8px 26px rgb(0 0 0 / 16%);
}

@media (prefers-reduced-motion: no-preference) {
    .sel-menu {
        animation: sel-menu-in 0.12s ease-out;
    }
}

@keyframes sel-menu-in {
    from {
        opacity: 0;
        transform: translateY(4px);
    }
}

.sel-menu__btn {
    display: flex;
    gap: 6px;
    align-items: center;
    padding: 6px 14px;
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    color: var(--text);
    cursor: pointer;
    background: none;
    border: 0;
    border-radius: 999px;
    /* The press must not steal the selection the menu is about to act on. */
    -webkit-user-select: none;
    user-select: none;
    white-space: nowrap;
}

.sel-menu__btn:hover {
    color: var(--accent);
    background: var(--accent-soft);
}

/* Both panel triggers (desktop header button, mobile topbar icon) light up. */
.chat-head__actions .btn.is-active,
.topbar__icon.is-active {
    color: var(--accent);
    background: var(--accent-soft);
    border-color: var(--accent);
}

/* ----------------------------------------------------------- responsive */

/* Tablet — the sidebar still fits, just narrower, and so does the lookup panel.
   Keeping it a real column rather than an overlay matters: the whole point is to
   read the message and the meaning side by side. */
@media (max-width: 1100px) {
    :root {
        --sidebar-w: 240px;
        --lookup-w: 280px;
    }

    .btn__label {
        display: none;
    }
}

/* Phone / small tablet — the sidebar turns into an off-canvas drawer.
   It used to be `display: none` here, which left no way to switch threads
   or reach the prompt settings on a phone at all. */
@media (max-width: 900px) {
    .app,
    /* Beats the two-class `.app.has-lookup` rule in the 1100px block, which
       would otherwise reinstate the sidebar column here. */
    .app.has-lookup {
        /* minmax(0, …), not a bare 1fr: `1fr` floors the track at the widest
           item's min-content, so the topbar's row of controls could push the
           whole grid past the viewport and scroll the page sideways. */
        grid-template-columns: minmax(0, 1fr);
        grid-template-rows: auto 1fr;
    }

    .topbar {
        display: flex;
    }

    .sidebar {
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        z-index: 40;
        width: min(84vw, 320px);
        padding-top: env(safe-area-inset-top);
        border-right: 1px solid var(--border);
        box-shadow: 2px 0 24px rgb(0 0 0 / 18%);
        transform: translateX(-100%);
        transition: transform 0.22s ease;
    }

    .sidebar.is-open {
        transform: none;
    }

    /* No room for a third column on a phone — the panel mirrors the sidebar and
       becomes an off-canvas drawer, dismissed by its own scrim. */
    .lookup {
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        z-index: 40;
        width: min(88vw, 340px);
        padding-top: env(safe-area-inset-top);
        padding-bottom: env(safe-area-inset-bottom);
        box-shadow: -2px 0 24px rgb(0 0 0 / 18%);
    }

    @media (prefers-reduced-motion: no-preference) {
        .app.has-lookup .lookup {
            animation: lookup-in 0.22s ease-out;
        }
    }

    @keyframes lookup-in {
        from {
            transform: translateX(100%);
        }
    }

    /* Row actions are hover-revealed on desktop; there is no hover on touch. */
    .thread__actions {
        display: flex;
    }

    .thread__link {
        padding: 12px 10px;
    }

    /* The whole header moves into the topbar + ⋮ menu — it was spending ~200px
       of an 844px screen on a duplicated title and two rare actions. */
    .chat-head {
        display: none;
    }

    .messages {
        padding: 16px 12px 4px;
    }

    .msg {
        margin-bottom: 18px;
    }

    .msg--user .msg__body {
        max-width: 85%;
    }

    .composer {
        gap: 6px;
        padding: 8px 12px;
        padding-bottom: calc(10px + env(safe-area-inset-bottom));
        background: var(--bg);
        border-top: 1px solid var(--border);
    }

    /* Icon-only circle: the label costs width the textarea needs more. */
    .composer__action {
        width: 44px;
        padding: 0;
        border-radius: 50%;
    }

    .composer__action-label {
        display: none;
    }

    .composer__meta {
        gap: 8px;
        min-height: 0;
    }

    .model-picker__label {
        display: none;
    }

    /* Let the select take the whole row instead of a fixed cap — a name like
       "Mặc định (claude-haiku-4-5)" gets clipped mid-word otherwise. */
    .model-picker {
        flex: 1;
        min-width: 0;
    }

    .model-picker select {
        flex: 1;
        min-width: 0;
        padding: 4px 10px;
        border-radius: 999px;
    }

    /* iOS zooms the whole page when focusing a control under 16px.
       The selectors must mirror the base rule's `.field input[type='…']` shape:
       a plain `.field input` here loses on specificity to it, and the 16px
       silently never applied. */
    .composer textarea,
    .model-picker select,
    .field input[type='text'],
    .field input[type='password'],
    .field textarea,
    .field select {
        font-size: 16px;
    }

    .welcome {
        padding-top: 6vh;
    }

    .prompt-manager {
        grid-template-columns: 1fr;
        padding: 16px;
    }

    .prompt-form {
        position: static;
    }

    /* rows="16" is ~380px — a whole phone screen for one field, pushing the
       prompt list below the fold. */
    #prompt-content {
        height: 180px;
    }

    /* Side-by-side these wrap to two lines each; stacked they read cleanly. */
    .welcome__actions {
        flex-direction: column;
    }

    .welcome__actions .btn {
        text-align: center;
    }

    .dialog__head {
        padding: 16px 16px 12px;
    }

    .dialog__content {
        padding: 16px;
    }

    .dialog__foot {
        padding: 12px 16px;
        padding-bottom: calc(12px + env(safe-area-inset-bottom));
    }

    /* Full-width, finger-sized buttons — the desktop 8px-tall pills are a poor
       target, and a destructive action is the worst place to mis-tap. */
    .dialog__actions {
        gap: 8px;
    }

    .dialog__actions .btn {
        flex: 1;
        padding: 13px 14px;
        font-size: 15px;
    }

    /* 140 chars of prompt preview per option makes the picker a scroll marathon
       on a phone; the name and description already identify it. */
    .prompt-option__body code {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 2;
        line-clamp: 2;
        overflow: hidden;
    }
}

@media (max-width: 620px) {
    .msg--user .msg__body {
        max-width: 88%;
    }

    .chat-empty {
        margin-top: 8vh;
    }

    .suggestions {
        flex-direction: column;
        align-items: stretch;
    }

    .chip {
        padding: 11px 14px;
        text-align: center;
    }
}

/* Honour the OS "reduce motion" setting for the drawer and caret. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
    }
}
