/* =========================================================
   AI Chat — modern Apple/iOS-inspired UI
   Liquid glass surfaces, soft shadows, spring transitions.
   Adapts to system light/dark mode.
   ========================================================= */

/* ---------- Self-hosted document fonts ----------
   Curated Google-Fonts subset (latin, 400+700) used by the Document
   Collaborator's font picker. Self-hosted (not via the Google CDN) so
   they're available offline AND actually embedded when html2canvas
   rasterizes a doc for PDF export. Web-safe fonts (Georgia, Times New
   Roman, Arial, Courier New) need no @font-face. Italics synthesize. */
@font-face { font-family: 'Lora'; font-weight: 400; font-display: swap; src: url('../fonts/lora-400.woff2') format('woff2'); }
@font-face { font-family: 'Lora'; font-weight: 700; font-display: swap; src: url('../fonts/lora-700.woff2') format('woff2'); }
@font-face { font-family: 'Merriweather'; font-weight: 400; font-display: swap; src: url('../fonts/merriweather-400.woff2') format('woff2'); }
@font-face { font-family: 'Merriweather'; font-weight: 700; font-display: swap; src: url('../fonts/merriweather-700.woff2') format('woff2'); }
@font-face { font-family: 'Roboto Slab'; font-weight: 400; font-display: swap; src: url('../fonts/roboto-slab-400.woff2') format('woff2'); }
@font-face { font-family: 'Roboto Slab'; font-weight: 700; font-display: swap; src: url('../fonts/roboto-slab-700.woff2') format('woff2'); }
@font-face { font-family: 'Playfair Display'; font-weight: 400; font-display: swap; src: url('../fonts/playfair-display-400.woff2') format('woff2'); }
@font-face { font-family: 'Playfair Display'; font-weight: 700; font-display: swap; src: url('../fonts/playfair-display-700.woff2') format('woff2'); }
@font-face { font-family: 'Source Sans 3'; font-weight: 400; font-display: swap; src: url('../fonts/source-sans-3-400.woff2') format('woff2'); }
@font-face { font-family: 'Source Sans 3'; font-weight: 700; font-display: swap; src: url('../fonts/source-sans-3-700.woff2') format('woff2'); }
@font-face { font-family: 'EB Garamond'; font-weight: 400; font-display: swap; src: url('../fonts/eb-garamond-400.woff2') format('woff2'); }
@font-face { font-family: 'EB Garamond'; font-weight: 700; font-display: swap; src: url('../fonts/eb-garamond-700.woff2') format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-weight: 400; font-display: swap; src: url('../fonts/jetbrains-mono-400.woff2') format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-weight: 700; font-display: swap; src: url('../fonts/jetbrains-mono-700.woff2') format('woff2'); }

/* ---------- Design tokens (dark = default) ---------- */
:root {
    color-scheme: light dark;

    --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'SF Pro Display',
                 'Segoe UI', Roboto, system-ui, sans-serif;
    --font-mono: ui-monospace, 'SF Mono', 'JetBrains Mono', 'Menlo', 'Consolas', monospace;

    /* Surfaces */
    --bg-base: #0a0a0f;
    --bg-canvas: rgba(20, 20, 28, 0.55);
    --glass: rgba(255, 255, 255, 0.06);
    --glass-strong: rgba(255, 255, 255, 0.10);
    --glass-stronger: rgba(255, 255, 255, 0.16);
    --glass-elev: rgba(28, 28, 38, 0.78);
    --hairline: rgba(255, 255, 255, 0.10);
    --hairline-soft: rgba(255, 255, 255, 0.06);
    --hairline-strong: rgba(255, 255, 255, 0.18);

    /* Text */
    --text: #f4f4f8;
    --text-2: rgba(244, 244, 248, 0.68);
    --text-3: rgba(244, 244, 248, 0.42);

    /* Accent — iOS-leaning blue/violet */
    --accent: #7d6cf5;
    --accent-2: #a08bff;
    --accent-soft: rgba(125, 108, 245, 0.22);
    --accent-glow: rgba(125, 108, 245, 0.45);
    --accent-fg: #ffffff;

    /* Status */
    --danger: #ff5560;
    --danger-soft: rgba(255, 85, 96, 0.16);
    --success: #34d399;
    --success-soft: rgba(52, 211, 153, 0.16);
    --warning: #fbbf24;

    /* Geometry */
    --radius-xs: 8px;
    --radius-sm: 12px;
    --radius: 16px;
    --radius-lg: 22px;
    --radius-xl: 28px;
    --radius-xl-mobile: 48px;
    --radius-pill: 999px;

    /* Shadows */
    --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.18), 0 6px 18px rgba(0, 0, 0, 0.22);
    --shadow-2: 0 2px 6px rgba(0, 0, 0, 0.20), 0 16px 40px rgba(0, 0, 0, 0.32);
    --shadow-float: 0 1px 1px rgba(0, 0, 0, 0.18), 0 8px 28px rgba(0, 0, 0, 0.36);

    /* Motion */
    --ease: cubic-bezier(0.32, 0.72, 0, 1);          /* iOS spring-ish */
    --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
    --t-fast: 140ms;
    --t: 220ms;
    --t-slow: 380ms;

    /* Layout */
    --sidebar-w: 296px;
    --header-h: 58px;
    --gutter: 12px;
    --safe-top: env(safe-area-inset-top, 0px);
    --safe-bottom: env(safe-area-inset-bottom, 0px);
}

/* ---------- Light mode ---------- */
@media (prefers-color-scheme: light) {
    :root {
        --bg-base: #f3f3f7;
        --bg-canvas: rgba(255, 255, 255, 0.62);
        --glass: rgba(255, 255, 255, 0.62);
        --glass-strong: rgba(255, 255, 255, 0.78);
        --glass-stronger: rgba(255, 255, 255, 0.92);
        --glass-elev: rgba(255, 255, 255, 0.88);
        --hairline: rgba(20, 20, 30, 0.10);
        --hairline-soft: rgba(20, 20, 30, 0.06);
        --hairline-strong: rgba(20, 20, 30, 0.16);

        --text: #14141d;
        --text-2: rgba(20, 20, 29, 0.62);
        --text-3: rgba(20, 20, 29, 0.40);

        --accent: #6d5ee7;
        --accent-2: #8a7af0;
        --accent-soft: rgba(109, 94, 231, 0.16);
        --accent-glow: rgba(109, 94, 231, 0.30);

        --shadow-1: 0 1px 2px rgba(20, 20, 40, 0.06), 0 8px 24px rgba(20, 20, 40, 0.10);
        --shadow-2: 0 2px 4px rgba(20, 20, 40, 0.06), 0 18px 48px rgba(20, 20, 40, 0.14);
        --shadow-float: 0 1px 1px rgba(20, 20, 40, 0.06), 0 14px 36px rgba(20, 20, 40, 0.16);
    }
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html, body { height: 100%; }
html, body { background: var(--bg-base); }
/* Block the browser's horizontal-overscroll-back-navigation gesture so
   the sidebar swipe can't accidentally trigger it (Chrome/Firefox/
   Edge). iOS Safari ignores this — it's blocked separately by
   preventDefault() inside the touchmove handler in app.js. */
html, body { overscroll-behavior-x: none; }
body {
    font-family: var(--font-sans);
    font-size: 15px;
    line-height: 1.5;
    color: var(--text);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    overflow: hidden;
    overscroll-behavior: none;
    text-rendering: optimizeLegibility;
}
input, textarea, select, button { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-2); }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { transition-duration: 0.001ms !important; animation-duration: 0.001ms !important; }
}

/* ---------- App shell ---------- */
#app {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    /* No explicit height on desktop: with inset:0 the shell already
       fills the visible viewport. Adding height:100dvh on top would
       cause the bottom edge to clip slightly when the window is
       resized small (dvh > available height). The mobile media query
       below adds dvh back specifically because iOS PWA standalone
       resolves inset:0 to the layout viewport and needs the kicker. */
    z-index: 1;
    display: flex;
    padding: var(--gutter);
    padding-top: calc(var(--gutter) + var(--safe-top));
    padding-bottom: calc(var(--gutter) + var(--safe-bottom));
    gap: var(--gutter);
    overflow: hidden;
}

/* ---------- Auth / centered cards ---------- */
.auth-view {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}

.auth-card {
    width: 100%;
    max-width: 420px;
    padding: 36px 32px;
    background: var(--glass);
    backdrop-filter: blur(40px) saturate(180%);
    -webkit-backdrop-filter: blur(40px) saturate(180%);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-2);
    animation: rise 420ms var(--ease) both;
}
.auth-card h1 {
    font-size: 28px;
    font-weight: 700;
    letter-spacing: -0.02em;
    margin-bottom: 6px;
}
.auth-card .subtitle {
    color: var(--text-2);
    font-size: 14px;
    margin-bottom: 28px;
}

@keyframes rise {
    from { opacity: 0; transform: translateY(14px) scale(0.98); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* ---------- Forms ---------- */
.form-group { margin-bottom: 14px; }
.form-group label {
    display: block;
    font-size: 12px;
    font-weight: 600;
    color: var(--text-2);
    margin-bottom: 6px;
    letter-spacing: 0.01em;
}

.form-input {
    width: 100%;
    padding: 12px 14px;
    font-size: 15px;
    color: var(--text);
    background: var(--glass);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-sm);
    outline: none;
    transition: border-color var(--t) var(--ease), background var(--t) var(--ease), box-shadow var(--t) var(--ease);
    -webkit-appearance: none;
    appearance: none;
}
.form-input::placeholder { color: var(--text-3); }
.form-input:hover { background: var(--glass-strong); }
.form-input:focus {
    border-color: var(--accent);
    background: var(--glass-strong);
    box-shadow: 0 0 0 4px var(--accent-soft);
}

textarea.form-input { resize: vertical; min-height: 110px; line-height: 1.5; }

/* ---------- Buttons ---------- */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 11px 20px;
    font-size: 14px;
    font-weight: 600;
    letter-spacing: 0.005em;
    border-radius: var(--radius-pill);
    cursor: pointer;
    transition: transform var(--t-fast) var(--ease), background var(--t) var(--ease),
                box-shadow var(--t) var(--ease), opacity var(--t) var(--ease);
    white-space: nowrap;
    -webkit-tap-highlight-color: transparent;
}
.btn:active { transform: scale(0.97); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }

.btn-primary {
    background: linear-gradient(180deg, var(--accent-2), var(--accent));
    color: var(--accent-fg);
    box-shadow: 0 1px 0 rgba(255, 255, 255, 0.18) inset, 0 6px 18px var(--accent-glow);
}
.btn-primary:hover { filter: brightness(1.05); }

.btn-secondary {
    background: var(--glass-strong);
    color: var(--text);
    border: 1px solid var(--hairline);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
}
.btn-secondary:hover { background: var(--glass-stronger); }

.btn-danger {
    background: var(--danger-soft);
    color: var(--danger);
    border: 1px solid color-mix(in srgb, var(--danger) 28%, transparent);
}
.btn-danger:hover { background: color-mix(in srgb, var(--danger) 22%, transparent); }

.btn-sm { padding: 7px 14px; font-size: 13px; }
.btn-full { width: 100%; }

/* ---------- Sidebar ---------- */
.sidebar {
    width: var(--sidebar-w);
    height: 100%;
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    background: var(--bg-canvas);
    backdrop-filter: blur(40px) saturate(180%);
    -webkit-backdrop-filter: blur(40px) saturate(180%);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-1);
    overflow: hidden;
    transition: transform var(--t) var(--ease);
}

.sidebar-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    /* Desktop: collapse to zero — there's no close button here, and the
       brand mark/title were removed. The mobile media query below
       restores the 56px row so the close button has a target. */
    padding: 0 18px;
    min-height: 0;
}
.sidebar-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    font-weight: 700;
    font-size: 15px;
    letter-spacing: -0.01em;
}
.sidebar-brand-mark {
    width: 28px; height: 28px;
    border-radius: 8px;
    background: linear-gradient(135deg, var(--accent), #e94090);
    display: grid; place-items: center;
    color: white;
    font-weight: 800;
    font-size: 13px;
    box-shadow: 0 4px 14px var(--accent-glow);
}
/* Higher specificity than `.icon-btn` so the close button stays hidden on
   desktop — the mobile media query re-shows it. */
.sidebar-header .sidebar-close {
    display: none;
    width: 32px; height: 32px;
    border-radius: var(--radius-pill);
    align-items: center; justify-content: center;
    color: var(--text-2);
    background: var(--glass);
    border: 1px solid var(--hairline);
    transition: background var(--t) var(--ease), color var(--t) var(--ease);
}
.sidebar-header .sidebar-close:hover { background: var(--glass-strong); color: var(--text); }

.new-chat-btn {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 12px 12px 10px;
    padding: 11px 14px;
    background: linear-gradient(180deg, var(--accent-2), var(--accent));
    color: white;
    border-radius: var(--radius);
    font-size: 14px;
    font-weight: 600;
    transition: transform var(--t-fast) var(--ease), filter var(--t) var(--ease), box-shadow var(--t) var(--ease);
    box-shadow: 0 1px 0 rgba(255,255,255,0.18) inset, 0 6px 18px var(--accent-glow);
}
.new-chat-btn:hover { filter: brightness(1.05); }
.new-chat-btn:active { transform: scale(0.98); }
.new-chat-btn svg { width: 16px; height: 16px; }

.chat-list {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 4px 8px 8px;
    -webkit-overflow-scrolling: touch;
    scrollbar-gutter: stable;
}

.chat-list-empty {
    padding: 32px 16px;
    text-align: center;
    font-size: 13px;
    color: var(--text-3);
}

.chat-list-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border-radius: var(--radius-sm);
    cursor: pointer;
    margin-bottom: 2px;
    transition: background var(--t) var(--ease);
    color: var(--text);
}
/* Hover styles gated to hover-capable pointers (mouse/trackpad). Touch
   devices skip these so a single tap doesn't first paint a hover state
   that the user mistakes for a no-op, then require a second tap. */
@media (hover: hover) {
    .chat-list-item:hover { background: var(--glass); }
    .chat-list-item:hover .chat-actions { opacity: 1; width: auto; overflow: visible; }
}
.chat-list-item.active {
    background: var(--accent-soft);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent);
}

.chat-list-item .chat-title {
    flex: 1;
    font-size: 13.5px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.chat-list-item .chat-actions {
    display: flex;
    gap: 2px;
    opacity: 0;
    /* Collapse to zero width while hidden so the invisible edit/delete
       buttons don't reserve space and truncate the title early — that
       reserved gap was the empty strip on every non-active row. Width is
       restored when the row is active (buttons always shown) or hovered. */
    width: 0;
    overflow: hidden;
    transition: opacity var(--t) var(--ease);
}
.chat-list-item.active .chat-actions {
    opacity: 1;
    width: auto;
    overflow: visible;
}

.chat-action-btn {
    width: 28px; height: 28px;
    border-radius: var(--radius-pill);
    display: inline-flex; align-items: center; justify-content: center;
    color: var(--text-3);
    transition: background var(--t) var(--ease), color var(--t) var(--ease);
}
.chat-action-btn:hover { background: var(--glass); color: var(--text); }
.chat-action-btn.delete:hover { background: var(--danger-soft); color: var(--danger); }
.chat-action-btn svg { width: 14px; height: 14px; }

.sidebar-tools {
    padding: 6px 10px;
    border-top: 1px solid var(--hairline-soft);
}

.prompt-builder-link {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border-radius: var(--radius-sm);
    color: var(--text-2);
    font-size: 13.5px;
    font-weight: 500;
    transition: background var(--t) var(--ease), color var(--t) var(--ease);
    /* Reset <button> defaults so the rule works for both <a> and
       <button>. Without these, buttons render with a browser-supplied
       background/border and don't stretch to fill the sidebar column. */
    width: 100%;
    background: none;
    border: none;
    cursor: pointer;
    text-align: left;
    font-family: inherit;
    text-decoration: none;
}
.prompt-builder-link:hover { background: var(--glass); color: var(--text); }
.prompt-builder-link svg { width: 16px; height: 16px; opacity: 0.7; flex-shrink: 0; }
.prompt-builder-link:hover svg { opacity: 1; }

/* Tools popup — groups Automations / Prompt Builder / Image Studio.
   Anchors to the Tools button and opens upward, like the user menu. */
.tools-menu { position: relative; }
.tools-menu-btn .chev {
    margin-left: auto;
    width: 14px;
    height: 14px;
    color: var(--text-3);
}
.tools-menu-dropdown {
    position: absolute;
    bottom: calc(100% + 4px);
    left: 0;
    right: 0;
    padding: 6px;
    background: var(--glass-elev);
    backdrop-filter: blur(40px) saturate(180%);
    -webkit-backdrop-filter: blur(40px) saturate(180%);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    box-shadow: var(--shadow-2);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--t) var(--ease);
    z-index: 50;
}
.tools-menu-dropdown.show {
    opacity: 1;
    pointer-events: auto;
}
.tools-menu-dropdown .menu-item { font-size: 13.5px; }

.sidebar-footer {
    position: relative;
    padding: 10px;
    border-top: 1px solid var(--hairline-soft);
}

.user-menu-btn {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px 10px;
    border-radius: var(--radius);
    color: var(--text);
    transition: background var(--t) var(--ease);
}
.user-menu-btn:hover { background: var(--glass); }
.user-menu-btn .username {
    flex: 1;
    text-align: left;
    font-size: 14px;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.user-menu-btn .chev { color: var(--text-3); width: 14px; height: 14px; }

.user-avatar {
    width: 34px; height: 34px;
    border-radius: var(--radius-pill);
    background: linear-gradient(135deg, var(--accent-2), #e94090);
    display: grid; place-items: center;
    color: white;
    font-size: 14px;
    font-weight: 700;
    flex-shrink: 0;
    box-shadow: 0 2px 10px var(--accent-glow);
}

.user-menu-dropdown {
    position: absolute;
    bottom: calc(100% - 4px);
    left: 10px;
    right: 10px;
    padding: 6px;
    background: var(--glass-elev);
    backdrop-filter: blur(40px) saturate(180%);
    -webkit-backdrop-filter: blur(40px) saturate(180%);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    box-shadow: var(--shadow-2);
    opacity: 0;
    pointer-events: none;
    /* Opacity-only fade — NO transform animation. A previous version
       used `transform: translateY(8px) scale(0.96)` for a scoot-up
       entry, but on mobile the 220ms `--t` window was long enough
       that users tapping "Settings" quickly hit the dropdown while
       its hit-target was still 5-8px below its final position. The
       touch fell through to the Projects button directly behind in
       `.sidebar-tools`. Removing the transform makes the dropdown's
       hit area match its visual area from frame 1. */
    transition: opacity var(--t) var(--ease);
    z-index: 50;
}
.user-menu-dropdown.show {
    opacity: 1;
    pointer-events: auto;
}

.menu-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 10px 12px;
    border-radius: var(--radius-xs);
    text-align: left;
    color: var(--text);
    font-size: 14px;
    text-decoration: none; /* also used for <a> items in the Tools popup */
    transition: background var(--t-fast) var(--ease);
}
.menu-item:hover { background: var(--glass); }
.menu-item svg { width: 16px; height: 16px; opacity: 0.75; }
.menu-item.danger { color: var(--danger); }
.menu-divider { height: 1px; background: var(--hairline-soft); margin: 4px 0; }

/* ---------- Main column ---------- */
.main-content {
    flex: 1 1 auto;
    min-width: 0;
    height: 100%;
    display: flex;
    flex-direction: column;
    background: var(--bg-canvas);
    backdrop-filter: blur(40px) saturate(180%);
    -webkit-backdrop-filter: blur(40px) saturate(180%);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-1);
    overflow: hidden;
    position: relative;
}

.main-header {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 16px;
    min-height: var(--header-h);
    border-bottom: 1px solid var(--hairline-soft);
    background: transparent;
    flex-shrink: 0;
    z-index: 5;
}

.icon-btn {
    width: 36px; height: 36px;
    display: inline-flex;
    align-items: center; justify-content: center;
    border-radius: var(--radius-pill);
    color: var(--text-2);
    transition: background var(--t) var(--ease), color var(--t) var(--ease), transform var(--t-fast) var(--ease);
    -webkit-tap-highlight-color: transparent;
}
.icon-btn:hover { background: var(--glass); color: var(--text); }
.icon-btn:active { transform: scale(0.94); }
.icon-btn svg { width: 18px; height: 18px; }

.hamburger-btn { display: none; }

.header-title {
    font-size: 15px;
    font-weight: 600;
    letter-spacing: -0.01em;
}

.model-selector {
    appearance: none;
    -webkit-appearance: none;
    padding: 7px 32px 7px 14px;
    background: var(--glass);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-pill);
    color: var(--text);
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    outline: none;
    transition: background var(--t) var(--ease), border-color var(--t) var(--ease);
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%239e9eb0' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
    background-repeat: no-repeat;
    background-position: right 12px center;
    background-size: 12px 12px;
}
.model-selector:hover { background-color: var(--glass-strong); }
.model-selector:focus { border-color: var(--accent); box-shadow: 0 0 0 4px var(--accent-soft); }
.model-selector option { background: #1c1c24; color: var(--text); }
@media (prefers-color-scheme: light) {
    .model-selector option { background: white; color: var(--text); }
}

.header-spacer { flex: 1; }

/* Local-model reachability badge — sits next to the model selector when
 * the chat's current model is a local-provider one. The model dropdown
 * also renders a 🟢/⚪ dot per local option, but the explicit badge
 * makes the active model's status unmissable. */
.local-status-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 4px 10px;
    border-radius: var(--radius-pill);
    background: var(--glass);
    border: 1px solid var(--hairline);
    color: var(--text-2);
    font-size: 12px;
    line-height: 1;
    user-select: none;
}
.local-status-badge.is-down { color: var(--text-3); }

/* Local-model endpoint card — Settings → API. Shares the panel-card
 * shell but adds a few specifics for the detected-models list and the
 * inline reachability indicator. */
.local-endpoint-status {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-left: 10px;
    font-size: 12px;
    color: var(--text-2);
}
.local-detected-models {
    margin-top: 12px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.local-detected-models .detected-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 10px;
    border-radius: var(--radius-xs);
    background: var(--glass);
}
.local-detected-models .detected-row .detected-id {
    color: var(--text-3);
    font-size: 12px;
    font-family: ui-monospace, SFMono-Regular, monospace;
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.local-cors-hint {
    margin-top: 14px;
    padding: 10px 12px;
    border-radius: var(--radius-xs);
    background: var(--glass);
    border: 1px solid var(--hairline);
    color: var(--text-2);
    font-size: 12px;
    line-height: 1.55;
}
.local-cors-hint code {
    background: rgba(0, 0, 0, 0.18);
    padding: 1px 6px;
    border-radius: 4px;
    font-family: ui-monospace, SFMono-Regular, monospace;
    font-size: 11.5px;
}

/* ---------- Chat container ---------- */
.chat-container {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 22px 16px 28px;
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;
}

.chat-empty {
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    color: var(--text-3);
    padding: 40px 20px;
    gap: 6px;
}
.chat-empty .empty-icon {
    width: 72px; height: 72px;
    border-radius: var(--radius-xl);
    background: linear-gradient(135deg, var(--accent-2), #e94090);
    display: grid; place-items: center;
    color: white;
    margin-bottom: 16px;
    box-shadow: 0 12px 32px var(--accent-glow);
}
.chat-empty .empty-icon svg { width: 32px; height: 32px; }
/* Image Studio: gentle pulse while an image is generating in the chat view. */
.chat-empty .empty-icon.generating { animation: empty-pulse 1.5s ease-in-out infinite; }
@keyframes empty-pulse {
    0%, 100% { transform: scale(1);    box-shadow: 0 12px 32px var(--accent-glow); }
    50%      { transform: scale(1.06); box-shadow: 0 16px 44px var(--accent-glow); }
}
.chat-empty h2 {
    font-size: 22px;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--text);
}
.chat-empty p { font-size: 14px; color: var(--text-2); max-width: 360px; }

/* ---------- Messages ---------- */
.message {
    max-width: 820px;
    margin: 0 auto 14px;
    animation: msgIn 320ms var(--ease) both;
}
@keyframes msgIn {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* Layout: 2-column grid (avatar + bubble) with the actions row
   spanning both columns underneath. Earlier this was a flex-wrap
   container with `.message-actions { flex-basis: 100% }` to force the
   actions onto their own line — but flex's line-break decision uses
   each child's flex-basis (auto → content max-content for the bubble),
   not its max-width. On a long response whose intrinsic max-content
   exceeded "container minus avatar", flex wrap would push the bubble
   to a new row UNDER the avatar (rendering full-bleed) instead of
   alongside it. Grid sizes the bubble against its grid cell directly,
   so max-width: 70%/85% on the bubble actually clamps placement, not
   just rendered width. */
.message-user,
.message-assistant {
    display: grid;
    column-gap: 8px;
    align-items: end;
}
.message-assistant {
    grid-template-columns: auto 1fr;
    grid-template-areas:
        "avatar bubble"
        "actions actions";
}
.message-user {
    grid-template-columns: 1fr auto;
    grid-template-areas:
        "bubble avatar"
        "actions actions";
}
.message-avatar  { grid-area: avatar; }
.message-bubble  { grid-area: bubble; }
.message-actions { grid-area: actions; }
/* Keep the bubble at content size inside its 1fr cell — without
   justify-self the bubble would stretch to fill the cell, losing the
   chat-bubble feel. max-width on the bubble still caps the upper
   bound; this just keeps short bubbles short. */
.message-assistant .message-bubble { justify-self: start; }
.message-user      .message-bubble { justify-self: end;   }

/* Per-bubble action row (copy / export / delete). Quiet by default,
   brightens on hover/focus of the parent message — same pattern as
   ChatGPT / Claude / iMessage. The 40px inline padding lines the
   actions up with the bubble's outer edge (skipping the 32px avatar
   + 8px gap on the avatar side). */
.message-actions {
    display: flex;
    gap: 2px;
    margin-top: 4px;
    opacity: 0;
    transition: opacity 160ms var(--ease);
}
.message-actions-user      { justify-content: flex-end;   padding-right: 40px; }
.message-actions-assistant { justify-content: flex-start; padding-left:  40px; }
.message:hover .message-actions,
.message:focus-within .message-actions { opacity: 1; }
/* Touch devices: keep the actions discoverable without hover. We
   land on a quiet-but-visible default that brightens on tap-focus. */
@media (hover: none) {
    .message-actions { opacity: 0.55; }
    .message:focus-within .message-actions { opacity: 1; }
}

.message-action-btn {
    width: 28px; height: 28px;
    border-radius: var(--radius-pill);
    color: var(--text-3);
    background: transparent;
    display: inline-flex; align-items: center; justify-content: center;
    transition: background var(--t) var(--ease), color var(--t) var(--ease),
                transform var(--t-fast) var(--ease);
}
.message-action-btn:hover { background: var(--glass-strong); color: var(--text); }
.message-action-btn:active { transform: scale(0.92); }
.message-action-btn svg { width: 14px; height: 14px; }
.message-action-btn.message-action-danger:hover {
    background: var(--danger-soft);
    color: var(--danger);
}
.message-action-btn.is-success { color: var(--success); }

.message-avatar {
    width: 32px; height: 32px;
    border-radius: var(--radius-pill);
    display: grid; place-items: center;
    font-size: 18px; line-height: 1;
    flex-shrink: 0;
    box-shadow: 0 2px 8px rgba(0,0,0,0.18);
    user-select: none;
}

.message-user .message-bubble {
    background: linear-gradient(180deg, var(--accent-2), var(--accent));
    color: white;
    border-radius: var(--radius-lg) var(--radius-lg) 6px var(--radius-lg);
    padding: 10px 14px;
    max-width: 70%;
    /* Flex items default to min-width:auto, which is "as wide as the
       intrinsic content size". An intrinsic 1024×1024 generated image
       (or a long unbroken token in a code block) will then refuse to
       shrink below that — pushing the bubble past max-width and out
       of the viewport on mobile. min-width:0 releases that floor so
       max-width actually clamps. */
    min-width: 0;
    font-size: 14.5px;
    line-height: 1.5;
    word-break: break-word;
    box-shadow: 0 1px 0 rgba(255,255,255,0.16) inset, 0 6px 18px var(--accent-glow);
}
.message-user .message-bubble div { white-space: pre-wrap; }

.message-assistant .message-bubble {
    background: var(--glass);
    border: 1px solid var(--hairline-soft);
    border-radius: var(--radius-lg) var(--radius-lg) var(--radius-lg) 6px;
    padding: 14px 18px;
    max-width: 85%;
    /* See min-width:0 note on .message-user .message-bubble — same
       reasoning: lets the intrinsic size of a generated image / wide
       code block be clamped by max-width instead of pushing the
       bubble past the viewport edge. */
    min-width: 0;
    font-size: 14.5px;
    line-height: 1.65;
    color: var(--text);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
}

/* Markdown */
.message-assistant .message-bubble h1,
.message-assistant .message-bubble h2,
.message-assistant .message-bubble h3,
.message-assistant .message-bubble h4,
.message-assistant .message-bubble h5,
.message-assistant .message-bubble h6 {
    margin: 16px 0 8px;
    font-weight: 700;
    letter-spacing: -0.01em;
    line-height: 1.25;
}
.message-assistant .message-bubble h1 { font-size: 1.35em; }
.message-assistant .message-bubble h2 { font-size: 1.2em; }
.message-assistant .message-bubble h3 { font-size: 1.08em; }
.message-assistant .message-bubble h4 { font-size: 1em; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-2); }
.message-assistant .message-bubble h5 { font-size: 0.92em; color: var(--text-2); }
.message-assistant .message-bubble h6 { font-size: 0.85em; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.06em; }
.message-assistant .message-bubble > h1:first-child,
.message-assistant .message-bubble > h2:first-child,
.message-assistant .message-bubble > h3:first-child,
.message-assistant .message-bubble > h4:first-child,
.message-assistant .message-bubble > h5:first-child,
.message-assistant .message-bubble > h6:first-child { margin-top: 0; }

.message-assistant .message-bubble p { margin: 0 0 10px; }
.message-assistant .message-bubble p:last-child { margin-bottom: 0; }

.message-assistant .message-bubble ul,
.message-assistant .message-bubble ol { margin: 6px 0 10px; padding-left: 22px; }
.message-assistant .message-bubble li { margin-bottom: 4px; }
.message-assistant .message-bubble li > p { margin: 0; }
.message-assistant .message-bubble li::marker { color: var(--text-3); }

.message-assistant .message-bubble hr {
    border: 0;
    height: 1px;
    background: var(--hairline);
    margin: 18px 0;
}

.message-assistant .message-bubble code {
    font-family: var(--font-mono);
    font-size: 0.88em;
    background: var(--glass-strong);
    padding: 2px 6px;
    border-radius: 6px;
    border: 1px solid var(--hairline-soft);
}
.message-assistant .message-bubble pre {
    background: rgba(0, 0, 0, 0.32);
    border: 1px solid var(--hairline-soft);
    border-radius: var(--radius-sm);
    padding: 14px 16px;
    margin: 10px 0;
    overflow-x: auto;
}
.message-assistant .message-bubble pre code {
    background: none;
    padding: 0;
    border: 0;
    font-size: 13px;
    line-height: 1.55;
    white-space: pre;
}
@media (prefers-color-scheme: light) {
    .message-assistant .message-bubble pre { background: rgba(20, 20, 30, 0.05); }
}

.message-assistant .message-bubble blockquote {
    border-left: 3px solid var(--accent);
    padding: 6px 14px;
    margin: 10px 0;
    color: var(--text-2);
    background: var(--glass);
    border-radius: 8px;
}
.message-assistant .message-bubble blockquote > p:last-child { margin-bottom: 0; }

.message-assistant .message-bubble a { color: var(--accent-2); border-bottom: 1px solid var(--accent-soft); }
.message-assistant .message-bubble a:hover { border-bottom-color: var(--accent-2); }

.message-assistant .message-bubble strong { font-weight: 700; }
.message-assistant .message-bubble em { font-style: italic; }
.message-assistant .message-bubble del { color: var(--text-3); }

/* Tables — wrapped for horizontal overflow on narrow screens */
.message-assistant .message-bubble .md-table-wrap {
    margin: 12px 0;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    border: 1px solid var(--hairline-soft);
    border-radius: var(--radius-sm);
    background: var(--glass);
}
.message-assistant .message-bubble .md-table-wrap table {
    margin: 0;
    border: 0;
    border-collapse: separate;
    border-spacing: 0;
    width: 100%;
    font-size: 13px;
}
.message-assistant .message-bubble .md-table-wrap th,
.message-assistant .message-bubble .md-table-wrap td {
    border: 0;
    border-bottom: 1px solid var(--hairline-soft);
    border-right: 1px solid var(--hairline-soft);
    padding: 9px 12px;
    text-align: left;
    vertical-align: top;
    line-height: 1.45;
}
.message-assistant .message-bubble .md-table-wrap th:last-child,
.message-assistant .message-bubble .md-table-wrap td:last-child { border-right: 0; }
.message-assistant .message-bubble .md-table-wrap tr:last-child td { border-bottom: 0; }
.message-assistant .message-bubble .md-table-wrap th {
    background: var(--glass-strong);
    font-weight: 600;
    color: var(--text);
    font-size: 12.5px;
    letter-spacing: 0.01em;
}
.message-assistant .message-bubble .md-table-wrap tbody tr:nth-child(even) { background: var(--glass); }
.message-assistant .message-bubble .md-table-wrap code { font-size: 0.85em; }

/* Attachment chips */
.message-attachments {
    display: flex; flex-wrap: wrap; gap: 8px;
    margin-top: 10px;
}
.attachment-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 6px 12px;
    background: rgba(255,255,255,0.18);
    border: 1px solid rgba(255,255,255,0.22);
    border-radius: var(--radius-pill);
    font-size: 12.5px;
}
.message-assistant .attachment-badge {
    background: var(--glass);
    border-color: var(--hairline);
    color: var(--text-2);
}
.attachment-preview {
    max-width: 240px;
    max-height: 180px;
    border-radius: var(--radius);
    margin-top: 8px;
    box-shadow: var(--shadow-1);
}

/* ---------- Citations / Sources ---------- */

/* Inline [N] reference, post-processed from markdown into a clickable
   superscript that points at the matching source. */
.citation-ref {
    font-size: 0.72em;
    line-height: 1;
    margin: 0 1px;
    vertical-align: super;
}
.citation-ref a {
    display: inline-block;
    min-width: 16px;
    padding: 1px 5px;
    border-radius: 6px;
    background: var(--accent-soft);
    color: var(--accent);
    font-weight: 600;
    text-decoration: none;
    transition: background var(--t) var(--ease), color var(--t) var(--ease);
}
.citation-ref a:hover {
    background: var(--accent);
    color: var(--accent-fg);
}

/* Sources footer attached to assistant messages that drew on web
   results. Sits below the markdown body and any attachments. */
.message-sources {
    margin-top: 14px;
    padding-top: 10px;
    border-top: 1px solid var(--hairline-soft);
}
.message-sources-label {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-3);
    margin-bottom: 6px;
}
.message-sources-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.message-sources-list li {
    display: flex;
    align-items: baseline;
    gap: 8px;
    font-size: 12.5px;
    line-height: 1.4;
}
.message-sources-list .citation-num {
    flex-shrink: 0;
    min-width: 18px;
    padding: 1px 5px;
    border-radius: 5px;
    background: var(--accent-soft);
    color: var(--accent);
    font-size: 11px;
    font-weight: 600;
    text-align: center;
}
.message-sources-list .citation-link {
    color: var(--text-2);
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.message-sources-list .citation-link:hover .citation-title { color: var(--accent); }
.message-sources-list .citation-title { color: var(--text); }
.message-sources-list .citation-host {
    color: var(--text-3);
    margin-left: 6px;
    font-size: 11.5px;
}

/* "Retry with web search" call-to-action attached to assistant
   messages where the model raised the suggest_web_search flag.
   Sits at the bottom of the bubble below any sources, in its own
   row so it reads as an action rather than continuation text. */
.retry-search-cta {
    margin-top: 12px;
    padding-top: 10px;
    border-top: 1px solid var(--hairline-soft);
}
.retry-search-btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    border-radius: var(--radius-pill);
    background: var(--accent-soft);
    color: var(--accent);
    font-size: 13px;
    font-weight: 600;
    border: 1px solid color-mix(in srgb, var(--accent) 24%, transparent);
    transition: background var(--t) var(--ease), color var(--t) var(--ease),
                transform var(--t-fast) var(--ease);
}
.retry-search-btn:hover:not(:disabled) {
    background: var(--accent);
    color: var(--accent-fg);
}
.retry-search-btn:active:not(:disabled) { transform: scale(0.97); }
.retry-search-btn:disabled { opacity: 0.55; cursor: default; }
.retry-search-btn svg { width: 14px; height: 14px; }

/* Pre-stream status line (e.g. "Searching the web…") shown inside the
   assistant bubble before the model's first token arrives. Italic +
   muted so it reads as a transient system note rather than content.
   `display: flex` would otherwise win against `[hidden]`'s default
   `display: none`, so we explicitly opt back into hiding here. */
.message-status {
    font-size: 12.5px;
    color: var(--text-3);
    font-style: italic;
    padding: 2px 0 8px;
    display: flex;
    align-items: center;
    gap: 6px;
}
.message-status[hidden] { display: none; }
.message-status::before {
    content: '';
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--accent);
    animation: think 1.3s ease-in-out infinite;
}

/* Thinking */
.thinking-indicator {
    max-width: 820px;
    margin: 0 auto 14px;
    animation: msgIn 220ms var(--ease) both;
}
.thinking-dots {
    display: inline-flex;
    gap: 6px;
    padding: 14px 18px;
    background: var(--glass);
    border: 1px solid var(--hairline-soft);
    border-radius: var(--radius-lg) var(--radius-lg) var(--radius-lg) 6px;
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
}
.thinking-dots span {
    width: 8px; height: 8px;
    background: var(--accent);
    border-radius: 50%;
    animation: think 1.3s ease-in-out infinite;
}
.thinking-dots span:nth-child(2) { animation-delay: 0.16s; }
.thinking-dots span:nth-child(3) { animation-delay: 0.32s; }
@keyframes think {
    0%, 80%, 100% { transform: scale(0.55); opacity: 0.4; }
    40%           { transform: scale(1);    opacity: 1;   }
}

/* ---------- Composer ---------- */
.chat-input-area {
    flex-shrink: 0;
    /* Desktop: extra bottom breathing room so the input never feels clipped
       against the browser's window edge or OS chrome (Dock/taskbar).
       The mobile media query overrides this with a tighter, safe-area-aware
       value that matches native iOS app spacing. */
    padding: 12px 14px;
    border-top: 1px solid var(--hairline-soft);
    background: transparent;
}

.upload-preview-area {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    max-width: 820px;
    margin: 0 auto 10px;
}
.upload-preview-area:empty { display: none; }

.upload-preview-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px 6px 12px;
    background: var(--glass);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-pill);
    font-size: 12.5px;
    color: var(--text-2);
    animation: rise 220ms var(--ease) both;
}
.upload-preview-item .remove-upload {
    width: 22px; height: 22px;
    border-radius: 50%;
    color: var(--text-3);
    background: var(--glass-strong);
    display: inline-flex; align-items: center; justify-content: center;
    transition: background var(--t) var(--ease), color var(--t) var(--ease);
}
.upload-preview-item .remove-upload:hover { background: var(--danger-soft); color: var(--danger); }
.upload-preview-item svg { width: 14px; height: 14px; opacity: 0.7; }

.chat-input-row {
    display: flex;
    align-items: flex-end;
    gap: 10px;
    max-width: 820px;
    margin: 0 auto;
}

.chat-input-wrapper {
    flex: 1;
    position: relative;
    background: var(--glass-strong);
    border: 1px solid var(--hairline);
    border-radius: 24px;
    transition: border-color var(--t) var(--ease), box-shadow var(--t) var(--ease), background var(--t) var(--ease);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
}
.chat-input-wrapper:focus-within {
    border-color: var(--accent);
    box-shadow: 0 0 0 4px var(--accent-soft);
    background: var(--glass-stronger);
}

/* Wrapper outer height = #chat-input min-height (44) + wrapper border (2)
   = 46px, matching .send-btn so they align flush in the row.
   Right padding makes room for the .composer-tools cluster (four 34px
   buttons + 3×2px gaps = 142px, plus the 6px right offset + ~8px
   breathing margin so text wraps before sliding under the icons). When
   you add or remove a composer tool button, bump this AND the mobile
   override below. */
#chat-input {
    width: 100%;
    padding: 10px 156px 10px 18px;
    min-height: 44px;
    max-height: 220px;
    background: transparent;
    border: 0;
    color: var(--text);
    font-size: 15px;
    outline: none;
    resize: none;
    line-height: 1.45;
    overflow-y: auto;
}
#chat-input::placeholder { color: var(--text-3); }

/* Cluster of in-composer tool buttons (web search + paperclip). Pinned
   to the bottom-right of the input wrapper so they sit alongside the
   last line of input regardless of how tall the textarea grows. */
.composer-tools {
    position: absolute;
    right: 6px;
    bottom: 5px;
    display: flex;
    align-items: center;
    gap: 2px;
}

.composer-tool-btn {
    width: 34px; height: 34px;
    border-radius: var(--radius-pill);
    color: var(--text-2);
    background: transparent;
    display: inline-flex; align-items: center; justify-content: center;
    transition: background var(--t) var(--ease), color var(--t) var(--ease),
                box-shadow var(--t) var(--ease);
}
.composer-tool-btn:hover { background: var(--glass); color: var(--text); }
.composer-tool-btn svg { width: 16px; height: 16px; }
/* Active web-search toggle: tinted background + accent foreground so
   it's unmistakably "on" without being garish. */
.composer-tool-btn.is-active {
    background: var(--accent-soft);
    color: var(--accent);
    box-shadow: inset 0 0 0 1px var(--accent-soft);
}
.composer-tool-btn.is-active:hover {
    background: var(--accent-soft);
    color: var(--accent-2);
}

/* Backwards-compat shim: existing layouts and offline rules still
   reference `.upload-btn`. Reset position/size now that the button is
   inside `.composer-tools` instead of being absolutely positioned. */
.upload-btn { position: static; right: auto; bottom: auto; }

.send-btn {
    width: 46px; height: 46px;
    flex-shrink: 0;
    border-radius: var(--radius-pill);
    color: white;
    background: linear-gradient(180deg, var(--accent-2), var(--accent));
    box-shadow: 0 1px 0 rgba(255,255,255,0.18) inset, 0 8px 22px var(--accent-glow);
    display: inline-flex; align-items: center; justify-content: center;
    transition: transform var(--t-fast) var(--ease), filter var(--t) var(--ease), opacity var(--t) var(--ease);
    -webkit-tap-highlight-color: transparent;
}
.send-btn:hover { filter: brightness(1.06); }
.send-btn:active { transform: scale(0.94); }
.send-btn:disabled { opacity: 0.4; transform: none; }
.send-btn svg { width: 18px; height: 18px; }

/* ---------- Offline banner ---------- */
.offline-banner {
    display: none;
    align-items: center; justify-content: center;
    gap: 8px;
    padding: 10px 14px;
    margin: 10px 14px 0;
    background: var(--danger-soft);
    border: 1px solid color-mix(in srgb, var(--danger) 26%, transparent);
    color: var(--danger);
    font-size: 13px;
    font-weight: 500;
    border-radius: var(--radius);
}
.offline-banner svg { width: 16px; height: 16px; }
body.offline .offline-banner { display: flex; }
body.offline #chat-input { pointer-events: none; opacity: 0.5; }
body.offline .send-btn,
body.offline .upload-btn { pointer-events: none; opacity: 0.4; }

/* ---------- Settings & Admin panels ---------- */
.panel-view {
    flex: 1;
    overflow-y: auto;
    padding: 24px 20px 32px;
    -webkit-overflow-scrolling: touch;
}

.panel-card {
    max-width: 680px;
    margin: 0 auto 18px;
    padding: 22px 22px;
    background: var(--glass);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-lg);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    animation: rise 320ms var(--ease) both;
}

.panel-card h2 {
    font-size: 17px;
    font-weight: 700;
    letter-spacing: -0.01em;
    margin-bottom: 4px;
}

.panel-card h3 {
    font-size: 15px;
    margin-bottom: 12px;
    color: var(--text-2);
    font-weight: 600;
}

.panel-card .description {
    font-size: 13px;
    color: var(--text-2);
    margin-bottom: 16px;
    line-height: 1.5;
}

/* ---------- Toggle ---------- */
.toggle-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    padding: 6px 0;
    font-size: 14px;
}

.toggle-switch {
    position: relative;
    width: 50px;
    height: 30px;
    flex-shrink: 0;
}
.toggle-switch input { opacity: 0; width: 0; height: 0; }
.toggle-slider {
    position: absolute;
    inset: 0;
    background: rgba(120, 120, 128, 0.28);
    border-radius: var(--radius-pill);
    cursor: pointer;
    transition: background var(--t) var(--ease);
}
.toggle-slider::before {
    content: '';
    position: absolute;
    width: 26px; height: 26px;
    left: 2px; top: 2px;
    background: white;
    border-radius: 50%;
    box-shadow: 0 2px 4px rgba(0,0,0,0.18);
    transition: transform var(--t) var(--ease);
}
.toggle-switch input:checked + .toggle-slider { background: #34c759; }
.toggle-switch input:checked + .toggle-slider::before { transform: translateX(20px); }

/* ---------- Models management ---------- */
.add-model-form {
    margin: 8px 0 16px;
    padding: 16px;
    background: var(--glass);
    border: 1px solid var(--hairline-soft);
    border-radius: var(--radius);
    animation: rise 220ms var(--ease) both;
}
.add-model-form code {
    font-family: var(--font-mono);
    font-size: 0.92em;
    background: var(--glass-strong);
    padding: 1px 6px;
    border-radius: 5px;
}

.model-list {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.model-group-label {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-3);
    margin-bottom: 6px;
    padding-left: 4px;
}

.model-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    background: var(--glass);
    border: 1px solid var(--hairline-soft);
    border-radius: var(--radius);
    transition: background var(--t) var(--ease), border-color var(--t) var(--ease);
    flex-wrap: wrap;
}
.model-row:hover { background: var(--glass-strong); border-color: var(--hairline); }

.model-toggle {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 1;
    min-width: 200px;
    cursor: pointer;
}
.model-toggle input[type="checkbox"] {
    accent-color: var(--accent);
    width: 18px; height: 18px;
    cursor: pointer;
    flex-shrink: 0;
}
.model-toggle .model-name {
    font-size: 14px;
    font-weight: 600;
}
.model-toggle .model-id {
    font-family: var(--font-mono);
    font-size: 11.5px;
    color: var(--text-3);
    padding: 2px 8px;
    background: var(--glass);
    border-radius: var(--radius-pill);
    border: 1px solid var(--hairline-soft);
}

.default-radio {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    color: var(--text-2);
    cursor: pointer;
    padding: 4px 10px;
    border-radius: var(--radius-pill);
    transition: background var(--t) var(--ease);
}
.default-radio:hover { background: var(--glass-strong); }
.default-radio input { accent-color: var(--accent); }
.default-radio:has(input:checked) {
    background: var(--accent-soft);
    color: var(--accent);
}

.model-actions {
    display: flex;
    align-items: center;
    gap: 6px;
}

@media (max-width: 540px) {
    .model-toggle .model-id { display: none; }
    .model-row { gap: 8px; }
}

/* ---------- Invite list ---------- */
.invite-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 0;
    border-bottom: 1px solid var(--hairline-soft);
    font-size: 13px;
}
.invite-item:last-child { border-bottom: none; }
.invite-link-text {
    flex: 1;
    color: var(--accent);
    font-family: var(--font-mono);
    font-size: 12px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.invite-status {
    font-size: 11px;
    padding: 3px 10px;
    border-radius: var(--radius-pill);
    font-weight: 600;
    letter-spacing: 0.02em;
}
.invite-status.used    { background: var(--success-soft); color: var(--success); }
.invite-status.expired { background: var(--danger-soft);  color: var(--danger); }
.invite-status.active  { background: var(--accent-soft);  color: var(--accent); }

/* ---------- Users table ---------- */
.users-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}
.users-table th,
.users-table td {
    padding: 12px 12px;
    text-align: left;
    border-bottom: 1px solid var(--hairline-soft);
}
.users-table th {
    color: var(--text-3);
    font-weight: 600;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.users-table tr:last-child td { border-bottom: 0; }

.badge {
    font-size: 10.5px;
    padding: 3px 8px;
    border-radius: var(--radius-pill);
    font-weight: 600;
    letter-spacing: 0.02em;
    margin-left: 6px;
    vertical-align: middle;
}
.badge-admin     { background: var(--accent-soft); color: var(--accent); }
.badge-suspended { background: var(--danger-soft); color: var(--danger); }

/* Sessions */
.session-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 0;
    border-bottom: 1px solid var(--hairline-soft);
}
.session-item:last-child { border-bottom: none; }
.session-info { flex: 1; min-width: 0; }
.session-device { font-size: 14px; font-weight: 500; margin-bottom: 2px; }
.session-meta { font-size: 12px; color: var(--text-3); }
.session-current {
    font-size: 11px;
    padding: 3px 10px;
    border-radius: var(--radius-pill);
    background: var(--success-soft);
    color: var(--success);
    font-weight: 600;
}

/* API key rows */
.api-key-row {
    display: flex;
    gap: 10px;
    margin-bottom: 10px;
    align-items: center;
}
.api-key-row .form-input { flex: 1; font-family: var(--font-mono); font-size: 13px; }
.api-key-row .provider-label {
    width: 90px;
    font-size: 13px;
    font-weight: 600;
    text-transform: capitalize;
    flex-shrink: 0;
}
@media (max-width: 480px) {
    .api-key-row { flex-wrap: wrap; }
    .api-key-row .provider-label { width: 100%; }
}

/* ---------- 2FA card + setup dialog ---------- */
.totp-state {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 8px 0 14px;
}
.totp-pill {
    display: inline-flex;
    align-items: center;
    padding: 3px 10px;
    border-radius: var(--radius-pill);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
.totp-pill.is-on  { background: var(--success-soft); color: var(--success); }
.totp-pill.is-off { background: var(--danger-soft);  color: var(--danger);  }
.totp-meta { color: var(--text-3); font-size: 13px; }
.totp-warning {
    margin: 8px 0 14px;
    padding: 10px 12px;
    background: rgba(251, 191, 36, 0.10);
    border: 1px solid rgba(251, 191, 36, 0.28);
    border-radius: var(--radius-sm);
    color: var(--text);
    font-size: 12.5px;
    line-height: 1.5;
}
.totp-actions { display: flex; flex-wrap: wrap; gap: 10px; }

/* TOTP code input (login + setup confirm) — large, monospace, tracked,
   reads as a code-entry rather than ordinary text input. */
.totp-input {
    font-family: var(--font-mono);
    font-size: 22px;
    letter-spacing: 0.18em;
    text-align: center;
}

/* Setup dialog: ordered list with numbered steps, secret + backup-codes
   blocks. Caps width so the steps don't sprawl on desktop. */
.totp-dialog {
    max-width: 460px;
    width: 100%;
    max-height: 88vh;
    overflow-y: auto;
}
.totp-steps {
    margin: 0 0 16px;
    padding-left: 24px;
}
.totp-steps li { margin-bottom: 16px; line-height: 1.5; font-size: 14px; }
.totp-steps li:last-child { margin-bottom: 0; }
.totp-steps strong { font-weight: 600; }

/* QR block in the setup dialog. White background even in dark mode —
   authenticator apps need high contrast on a stable colour to scan
   reliably, and the surrounding dialog already provides the visual
   chrome. Clamp size so the QR doesn't dwarf the rest of the dialog
   on narrow screens. */
.totp-qr {
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 12px 0 8px;
    padding: 12px;
    background: #fff;
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
}
.totp-qr svg {
    display: block;
    width: 100%;
    max-width: 240px;
    height: auto;
    aspect-ratio: 1 / 1;
}

.totp-link-row { margin: 8px 0 6px; }
.totp-link {
    display: inline-block;
    padding: 7px 12px;
    border-radius: var(--radius-pill);
    background: var(--accent-soft);
    color: var(--accent);
    font-weight: 600;
    font-size: 13px;
    border: 1px solid color-mix(in srgb, var(--accent) 24%, transparent);
}
.totp-link:hover { background: var(--accent); color: var(--accent-fg); }
.totp-or {
    color: var(--text-3);
    font-size: 12.5px;
    margin: 6px 0 4px;
}

.totp-secret-row {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}
.totp-secret {
    flex: 1;
    min-width: 0;
    padding: 8px 10px;
    background: var(--glass);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-sm);
    font-family: var(--font-mono);
    font-size: 13px;
    letter-spacing: 0.04em;
    word-break: break-all;
    user-select: all;
}

.totp-backup-codes {
    margin-top: 6px;
    padding: 10px 12px;
    background: var(--glass);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-sm);
    font-family: var(--font-mono);
    font-size: 13px;
    line-height: 1.55;
    white-space: pre-wrap;
    word-break: break-all;
    user-select: all;
}

/* Encryption info card — bullet list of what's covered. */
.encryption-list {
    list-style: none;
    margin: 8px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.encryption-list li {
    padding: 8px 12px;
    background: var(--glass);
    border: 1px solid var(--hairline-soft);
    border-radius: var(--radius-sm);
    font-size: 13px;
    line-height: 1.5;
    color: var(--text-2);
}
.encryption-list strong { color: var(--text); margin-right: 4px; font-weight: 600; }

.checkbox-row {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--text-2);
}

/* ---------- Automations ---------- */

/* List view: card per automation with quick actions. */
.automation-card {
    /* Tighter than a generic panel-card so the list reads as a
       scannable feed rather than walls of chrome. */
    padding: 18px 20px;
}
.automation-card-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
}
.automation-name {
    margin: 0 0 4px;
    font-size: 16px;
    font-weight: 600;
}
.automation-trigger {
    margin: 0;
    color: var(--text-2);
    font-size: 13px;
}
.automation-meta {
    display: flex;
    gap: 18px;
    flex-wrap: wrap;
    color: var(--text-3);
    font-size: 12.5px;
    margin: 10px 0 14px;
}
.automation-meta strong { color: var(--text-2); font-weight: 600; }
.automation-actions {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

/* Builder header — name doubles as the page title. */
.automation-name-input {
    flex: 1;
    max-width: 360px;
    font-size: 15px;
    font-weight: 600;
}
.automation-builder { padding-bottom: 60px; }

/* Vertical chain of nodes with a connector line between them. */
.chain-editor {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 8px;
}

.chain-node {
    background: var(--glass);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    padding: 14px 16px;
    transition: border-color var(--t) var(--ease), background var(--t) var(--ease);
}
.chain-node:focus-within {
    border-color: var(--accent);
    background: var(--glass-stronger);
}
.chain-node-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
}
.chain-node-icon {
    font-size: 13px;
    font-weight: 700;
    color: var(--accent);
    width: 18px;
    text-align: center;
}
.chain-node-title {
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-2);
    flex: 1;
}
.chain-node-delete {
    width: 26px; height: 26px;
    border-radius: var(--radius-pill);
    color: var(--text-3);
    background: transparent;
    display: inline-flex; align-items: center; justify-content: center;
}
.chain-node-delete:hover { background: var(--danger-soft); color: var(--danger); }
.chain-node-delete svg { width: 12px; height: 12px; }

.chain-node textarea { resize: vertical; }
.chain-node-row {
    display: flex;
    gap: 8px;
    margin-top: 8px;
    flex-wrap: wrap;
}
.chain-node-row .form-input { flex: 1; min-width: 140px; }

/* Per-node web-search toggle. Renders as a quiet pill that lights up
   in accent colours when active — same visual cue as the chat
   composer's globe toggle so the connection is obvious. */
.chain-node-toggle {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-top: 10px;
    padding: 6px 12px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--hairline);
    background: transparent;
    color: var(--text-2);
    font-size: 12.5px;
    cursor: pointer;
    user-select: none;
    transition: background var(--t) var(--ease), color var(--t) var(--ease),
                border-color var(--t) var(--ease);
}
.chain-node-toggle input { /* offscreen — the label IS the control */
    position: absolute;
    opacity: 0;
    pointer-events: none;
}
.chain-node-toggle:hover { color: var(--text); }
.chain-node-toggle svg { width: 13px; height: 13px; }
.chain-node-toggle.is-on {
    background: var(--accent-soft);
    border-color: color-mix(in srgb, var(--accent) 30%, transparent);
    color: var(--accent);
}
.chain-node-toggle.is-on:hover { color: var(--accent-2); }

/* End nodes are visually quieter — they're terminators, not editors. */
.chain-node-end {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    background: var(--glass-strong);
    border: 1px dashed var(--hairline-strong);
    color: var(--text-3);
    font-size: 12.5px;
    width: max-content;
    border-radius: var(--radius-pill);
}

/* If-nodes spawn two indented sub-chains. The branch-group is a
   horizontal layout on desktop, stacks on mobile. */
.branch-group {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
    border-left: 2px dashed var(--accent-soft);
    padding: 8px 0 8px 12px;
    margin-top: 4px;
}
.branch-column {
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-width: 0;
}
.branch-label {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.branch-true  { color: var(--success); }
.branch-false { color: var(--danger);  }
@media (max-width: 720px) {
    .branch-group { grid-template-columns: 1fr; }
}

/* "Add step" affordance between nodes — quiet by default, brightens
   on hover. */
.chain-insert {
    display: flex;
    justify-content: center;
    padding: 2px 0;
}
.chain-insert-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    border-radius: var(--radius-pill);
    color: var(--text-3);
    font-size: 12px;
    font-weight: 500;
    background: transparent;
    transition: background var(--t) var(--ease), color var(--t) var(--ease);
}
.chain-insert-btn:hover {
    background: var(--accent-soft);
    color: var(--accent);
}
.chain-insert-btn svg { width: 12px; height: 12px; }

/* Tiny floating menu shown when the user hits "Add step". */
.insert-menu {
    position: fixed;
    z-index: 1100;
    background: var(--glass-elev);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-2);
    padding: 4px;
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    display: flex;
    flex-direction: column;
    min-width: 160px;
}
.insert-menu button {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    border-radius: var(--radius-xs);
    color: var(--text);
    font-size: 13px;
    text-align: left;
    background: transparent;
}
.insert-menu button:hover { background: var(--accent-soft); }
.insert-menu button svg { width: 14px; height: 14px; }

/* iOS-style toggle switch used on the automations list. */
.toggle {
    position: relative;
    display: inline-block;
    flex-shrink: 0;
}
.toggle input {
    opacity: 0;
    width: 0; height: 0;
    position: absolute;
}
.toggle-track {
    display: block;
    width: 44px;
    height: 26px;
    background: var(--hairline-strong);
    border-radius: var(--radius-pill);
    transition: background var(--t) var(--ease);
    cursor: pointer;
}
.toggle-thumb {
    display: block;
    position: absolute;
    top: 2px; left: 2px;
    width: 22px; height: 22px;
    background: white;
    border-radius: 50%;
    box-shadow: var(--shadow-1);
    transition: transform var(--t) var(--ease);
}
.toggle input:checked + .toggle-track { background: var(--accent); }
.toggle input:checked + .toggle-track .toggle-thumb { transform: translateX(18px); }

/* Automation-output badge inside chat bubbles. Subtle pill at the
   top-right of an assistant bubble that the cron wrote. */
.message-bubble.is-automation-output {
    border-color: var(--accent-soft);
    box-shadow: inset 0 0 0 1px var(--accent-soft);
}
.automation-output-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 8px;
    border-radius: var(--radius-pill);
    background: var(--accent-soft);
    color: var(--accent);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-bottom: 8px;
}
.automation-output-badge svg { width: 12px; height: 12px; }

/* Quiet pill shown when the AI writes to memory or rewrites its persona
   on a turn. Multiple can stack in .message-notice-host. */
.message-notice-host { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
.message-notice:first-child { margin-top: 8px; }
.message-notice {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border-radius: var(--radius-pill);
    background: var(--accent-soft);
    color: var(--text-2);
    font-size: 12px;
    line-height: 1.3;
}
.message-notice svg { width: 13px; height: 13px; color: var(--accent); flex-shrink: 0; }
.message-notice a { color: var(--accent); text-decoration: none; font-weight: 600; }
.message-notice a:hover { text-decoration: underline; }

/* Run history list inside the builder. */
.run-history-list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.run-history-list li {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    padding: 10px 0;
    border-top: 1px solid var(--hairline-soft);
    font-size: 13px;
}
.run-history-list li:first-child { border-top: 0; }
.run-status {
    display: inline-flex;
    align-items: center;
    padding: 2px 9px;
    border-radius: var(--radius-pill);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.run-status-success { background: var(--success-soft); color: var(--success); }
.run-status-failed  { background: var(--danger-soft);  color: var(--danger); }
.run-status-running { background: var(--accent-soft);  color: var(--accent); }
.run-status-skipped { background: var(--glass-strong); color: var(--text-3); }
.run-time { color: var(--text-2); }
.run-source { color: var(--text-3); font-size: 11px; }
.run-error {
    flex-basis: 100%;
    color: var(--danger);
    background: var(--danger-soft);
    padding: 8px 10px;
    border-radius: var(--radius-sm);
    font-size: 12px;
    font-family: var(--font-mono);
}

/* Admin cron-setup panel. Three blocks (HTTP / CLI / token) with
   monospace boxes for the values. */
.automations-admin-stats {
    display: flex;
    flex-wrap: wrap;
    gap: 18px;
    padding: 12px 14px;
    margin-top: 12px;
    background: var(--glass);
    border: 1px solid var(--hairline-soft);
    border-radius: var(--radius-sm);
    font-size: 13px;
    color: var(--text-2);
}
.automations-admin-stats strong { color: var(--text); margin-right: 4px; }

.cron-setup {
    margin-top: 18px;
    padding-top: 14px;
    border-top: 1px solid var(--hairline-soft);
}
.cron-setup h4 {
    font-size: 14px;
    margin-bottom: 6px;
}
.cron-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 8px 0;
    flex-wrap: wrap;
}
.cron-row > label {
    width: 80px;
    flex-shrink: 0;
    font-size: 12px;
    font-weight: 600;
    color: var(--text-2);
}
.cron-row .form-input { flex: 1; font-family: var(--font-mono); font-size: 12.5px; }
.cron-code {
    font-family: var(--font-mono);
    font-size: 12.5px;
    padding: 6px 10px;
    background: var(--glass);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-xs);
    color: var(--text);
}
.cron-code-block {
    flex: 1;
    word-break: break-all;
    user-select: all;
}

/* ---------- Toasts ---------- */
.toast-container {
    position: fixed;
    top: calc(20px + var(--safe-top));
    left: 50%;
    transform: translateX(-50%);
    z-index: 1000;
    display: flex;
    flex-direction: column;
    gap: 10px;
    pointer-events: none;
}
@media (min-width: 640px) {
    .toast-container { top: calc(24px + var(--safe-top)); left: auto; right: 24px; transform: none; }
}

.toast {
    pointer-events: auto;
    padding: 12px 18px;
    border-radius: var(--radius-pill);
    font-size: 13.5px;
    font-weight: 500;
    background: var(--glass-elev);
    backdrop-filter: blur(30px) saturate(180%);
    -webkit-backdrop-filter: blur(30px) saturate(180%);
    border: 1px solid var(--hairline);
    box-shadow: var(--shadow-2);
    color: var(--text);
    max-width: 380px;
    animation: toastIn 280ms var(--ease) both, toastOut 280ms var(--ease) 2.7s forwards;
    display: inline-flex;
    align-items: center;
    gap: 10px;
}
.toast::before {
    content: '';
    width: 8px; height: 8px;
    border-radius: 50%;
    background: var(--accent);
    flex-shrink: 0;
}
.toast-success { color: var(--success); }
.toast-success::before { background: var(--success); }
.toast-error   { color: var(--danger);  }
.toast-error::before   { background: var(--danger); }
.toast-info::before    { background: var(--accent); }

@keyframes toastIn {
    from { opacity: 0; transform: translateY(-12px) scale(0.96); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes toastOut {
    to { opacity: 0; transform: translateY(-8px) scale(0.96); }
}

/* ---------- Dialog ---------- */
.dialog-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    z-index: 500;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    animation: fadeIn 200ms var(--ease);
}
@keyframes fadeIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}

.dialog {
    background: var(--glass-elev);
    backdrop-filter: blur(40px) saturate(180%);
    -webkit-backdrop-filter: blur(40px) saturate(180%);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-2);
    padding: 24px;
    max-width: 420px;
    width: 100%;
    animation: rise 260ms var(--ease) both;
}
.dialog h3 {
    font-size: 17px;
    font-weight: 700;
    letter-spacing: -0.01em;
    margin-bottom: 6px;
}
.dialog p {
    font-size: 14px;
    color: var(--text-2);
    margin-bottom: 18px;
    line-height: 1.5;
}
.dialog-actions { display: flex; gap: 10px; justify-content: flex-end; }

/* ---------- Sidebar overlay (mobile) ---------- */
.sidebar-overlay {
    display: none;
    position: fixed;
    inset: 0;
    /* Blur-only veil: no colour tint, so the underlying page keeps its
       exact colours when the sidebar opens — just softened. A transparent
       background still lets backdrop-filter blur what's behind it. */
    background: transparent;
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    z-index: 199;
    opacity: 0;
    transition: opacity var(--t) var(--ease);
}
.sidebar.open ~ .sidebar-overlay { display: block; opacity: 1; }

/* ---------- Scrollbars ---------- */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
    background: rgba(150, 150, 170, 0.28);
    border-radius: 4px;
    border: 2px solid transparent;
    background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: rgba(150, 150, 170, 0.44); background-clip: content-box; }

/* ---------- Responsive ---------- */
@media (max-width: 768px) {
    :root { --gutter: 8px; }

    /* Full-bleed shell on phones — main panel meets the safe-area edges,
       individual sections pad themselves so nothing sits under the
       status bar or home indicator.

       The explicit dvh height matters on iOS PWA standalone: when added
       to the home screen, `position: fixed; inset: 0` alone can resolve
       to the layout viewport (which excludes the area beneath the home
       indicator), leaving a visible gap below the composer. dvh extends
       the shell to the visual viewport so the composer's safe-area
       padding is what actually clears the indicator. */
    #app {
        padding: 0;
        height: 100vh;
        height: 100dvh;
    }

    .main-content {
        border: 0;
        border-radius: 0;
        box-shadow: none;
        /* Drop the canvas tint on mobile — the body gradient is the
           canvas, no glass tint should paint over part of the screen
           and not the rest. */
        background: transparent;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        /* Reserve vertical space for the fixed header below. The header
           is taken out of the flex flow by `position: fixed`, so without
           this padding the chat-container / settings-tabs / panel-view
           would jump up and sit underneath it. The 60px is a safe lower
           bound on the rendered header height — min-height is 58px but
           the 36px icon buttons + 12+12 padding push it to ~60px. */
        padding-top: calc(60px + var(--safe-top));
    }

    /* Pin the header to the top of the visual viewport so opening the
       on-screen keyboard doesn't drag it out of view. iOS Safari in a
       regular browser tab scrolls the layout viewport upwards when an
       input is focused to keep that input above the keyboard — anything
       in normal flow gets pulled with it. `position: fixed; top: 0`
       anchors to the visual viewport instead, matching what the
       composer already does at the bottom via the inline style in
       index.php. Solid background so chat content can scroll behind it
       without bleeding through. */
    .main-header {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        z-index: 10;
        background: var(--bg-base);
        border-bottom: 1px solid var(--hairline-soft);
        padding-top: calc(12px + var(--safe-top));
        padding-left: calc(16px + env(safe-area-inset-left, 0px));
        padding-right: calc(16px + env(safe-area-inset-right, 0px));
    }

    /* iOS 26 introduces floating window controls on iPad that overlap the
       top-left corner of windowed apps. Indent the header enough to clear
       them. Only applies when the viewport is narrow enough to show the
       hamburger (≤768px), which is the split-screen / Stage Manager case
       where the controls are present. */
    html.is-ipad .main-header {
        padding-left: 80px;
    }

    .panel-view {
        padding-left: calc(14px + env(safe-area-inset-left, 0px));
        padding-right: calc(14px + env(safe-area-inset-right, 0px));
        padding-bottom: calc(28px + var(--safe-bottom));
    }

    .chat-container {
        padding-left: calc(14px + env(safe-area-inset-left, 0px));
        padding-right: calc(14px + env(safe-area-inset-right, 0px));
    }

    .chat-input-area {
        /* env() inline (not via CSS variable) — some iOS PWA contexts
           don't propagate env() through custom properties reliably.
           On iPhones with a home indicator the inset is ~34px, which
           is much more than native iOS apps actually leave below their
           composer (Messages/ChatGPT use ~6-10px above the indicator).
           Subtract 24px so the input hugs the bottom like a native app
           while still clearing the indicator's gesture area. Clamp to
           a min of 8px for devices without an inset. */
        padding-top: 8px;
        padding-bottom: max(8px, calc(env(safe-area-inset-bottom, 0px) - 24px));
        padding-left: calc(14px + env(safe-area-inset-left, 0px));
        padding-right: calc(14px + env(safe-area-inset-right, 0px));
    }

    .offline-banner {
        margin-left: calc(10px + env(safe-area-inset-left, 0px));
        margin-right: calc(10px + env(safe-area-inset-right, 0px));
    }

    /* Auth view should also clear status bar / home indicator */
    .auth-view {
        padding-top: calc(24px + var(--safe-top));
        padding-bottom: calc(24px + var(--safe-bottom));
        padding-left: calc(24px + env(safe-area-inset-left, 0px));
        padding-right: calc(24px + env(safe-area-inset-right, 0px));
    }

    /* Sidebar remains a floating overlay, but inset by safe-area on top + bottom.
       `height: auto` is required to override the base `height: 100%`, which would
       otherwise win against `top`/`bottom` and erase the bottom margin. */
    .sidebar {
        position: fixed;
        top: calc(var(--gutter) + var(--safe-top));
        bottom: calc(var(--gutter) + var(--safe-bottom));
        left: calc(var(--gutter) + env(safe-area-inset-left, 0px));
        height: auto;
        width: min(86vw, 320px);
        z-index: 200;
        transform: translateX(calc(-100% - var(--gutter) * 2 - env(safe-area-inset-left, 0px)));
        transition: transform 320ms var(--ease);
        border-radius: var(--radius-xl-mobile);
    }
    .sidebar.open { transform: translateX(0); }
    .sidebar-header {
        padding: 16px 18px 12px;
        min-height: 56px;
    }
    .sidebar-header .sidebar-close { display: inline-flex; }
    .hamburger-btn { display: inline-flex; }

    .message-user .message-bubble { max-width: 88%; }
    .message-assistant .message-bubble { max-width: 96%; }

    .auth-card { padding: 28px 24px; }
    .panel-card { padding: 18px; border-radius: var(--radius); }
    .users-table { font-size: 12px; }
    .users-table th, .users-table td { padding: 10px 8px; }
}

@media (max-width: 480px) {
    .chat-input-row { gap: 8px; }
    /* Keep wrapper outer (42 + 2 border) = send-btn (44) for alignment.
       Right padding clears the four 32px tool buttons + 3×2px gaps
       (134px) + 5px right offset + breathing so text wraps before
       overlapping the icons. */
    #chat-input { min-height: 42px; padding: 10px 146px 10px 16px; font-size: 14.5px; }
    .send-btn { width: 44px; height: 44px; }
    .composer-tools { right: 5px; bottom: 5px; gap: 2px; }
    .composer-tool-btn { width: 32px; height: 32px; }
    .header-title { display: none; }
}

/* ---------- Encryption notice on registration ---------- */
.encryption-notice {
    background: rgba(251, 191, 36, 0.10);
    border: 1px solid rgba(251, 191, 36, 0.28);
    color: var(--text);
    padding: 12px 14px;
    border-radius: var(--radius-sm);
    font-size: 12.5px;
    line-height: 1.55;
    margin-top: 14px;
}
.encryption-notice strong {
    display: block;
    margin-bottom: 4px;
    color: var(--warning);
    font-weight: 600;
}

/* ---------- Skills list (Settings) ---------- */
.skills-grid {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.skill-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    background: var(--glass);
    border: 1px solid var(--hairline-soft);
    border-radius: var(--radius-sm);
    transition: opacity var(--t-fast) var(--ease);
}
.skill-row.is-disabled { opacity: 0.55; }
.skill-body { flex: 1; min-width: 0; }
.skill-name {
    font-size: 14.5px;
    font-weight: 600;
    margin-bottom: 2px;
    color: var(--text);
}
.skill-desc {
    font-size: 13px;
    color: var(--text-2);
    line-height: 1.45;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}
.skill-actions {
    display: flex;
    gap: 6px;
    flex-shrink: 0;
}

/* ---------- Tool-call cards (chat body) ---------- */
.text-segment + .text-segment { margin-top: 8px; }
.text-segment + .tool-call-card,
.tool-call-card + .text-segment { margin-top: 10px; }
.tool-call-card {
    margin: 6px 0;
    background: var(--glass);
    border: 1px solid var(--hairline-soft);
    border-radius: var(--radius-sm);
    padding: 8px 10px;
    font-size: 13px;
}
.tool-call-card.is-pending {
    border-color: var(--hairline);
    background: rgba(125, 108, 245, 0.06);
}
.tool-call-card.is-success { border-left: 3px solid var(--success, #34d399); }
.tool-call-card.is-error   { border-left: 3px solid var(--danger); }
.tool-call-header {
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--text-2);
}
.tool-call-header svg { width: 14px; height: 14px; flex-shrink: 0; color: var(--text-3); }
.tool-call-name {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 6px;
    font-family: var(--font-mono);
    font-size: 12.5px;
    color: var(--text);
}
.tool-call-slug {
    color: var(--text-3);
    font-weight: 500;
}
.tool-call-slug::after {
    content: '/';
    margin: 0 2px;
    color: var(--text-3);
}
.tool-call-tool { font-weight: 600; }
.tool-call-status {
    font-size: 12px;
    color: var(--text-3);
    flex-shrink: 0;
}
.tool-call-status .thinking-dots { transform: scale(0.7); }
.tool-call-ok { color: var(--success, #34d399); font-weight: 600; }
.tool-call-error { color: var(--danger); font-weight: 600; }
.tool-call-detail {
    margin-top: 6px;
    border-top: 1px solid var(--hairline-soft);
    padding-top: 6px;
    font-size: 12px;
}
.tool-call-detail summary {
    cursor: pointer;
    color: var(--text-3);
    font-weight: 500;
    user-select: none;
    padding: 2px 0;
}
.tool-call-detail summary:hover { color: var(--text-2); }
.tool-call-detail pre {
    margin: 6px 0 2px;
    padding: 8px 10px;
    background: var(--bg-deep, rgba(0,0,0,0.18));
    border-radius: 6px;
    font-size: 11.5px;
    line-height: 1.45;
    max-height: 240px;
    overflow: auto;
    white-space: pre-wrap;
    word-break: break-word;
}

/* ---------- MCP plugins list (Settings) ---------- */
.plugins-grid {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.plugin-row {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 12px 14px;
    background: var(--glass);
    border: 1px solid var(--hairline-soft);
    border-radius: var(--radius-sm);
    transition: opacity var(--t-fast) var(--ease);
}
.plugin-row.is-disabled { opacity: 0.55; }
.plugin-body {
    flex: 1;
    min-width: 0;
}
.plugin-name-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 2px;
}
.plugin-name {
    font-size: 14.5px;
    font-weight: 600;
    color: var(--text);
}
.plugin-flag {
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-3);
    background: var(--hairline-soft);
    padding: 2px 6px;
    border-radius: 999px;
    font-weight: 600;
}
.plugin-flag.warn {
    color: var(--warning, #fbbf24);
    background: rgba(251, 191, 36, 0.12);
}
.plugin-meta {
    font-size: 12.5px;
    color: var(--text-2);
    line-height: 1.4;
    word-break: break-all;
    font-family: var(--font-mono);
}
.plugin-status-row {
    margin-top: 6px;
    font-size: 12px;
}
.plugin-status {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: var(--text-3);
}
.plugin-status svg { width: 13px; height: 13px; }
.plugin-status.ok { color: var(--success, #34d399); }
.plugin-status.err { color: var(--danger); }
.plugin-status.pending { color: var(--text-3); font-style: italic; }
.plugin-actions {
    display: flex;
    gap: 6px;
    flex-shrink: 0;
    flex-wrap: wrap;
    justify-content: flex-end;
}
.plugin-oauth-row {
    margin-top: 8px;
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}
@media (max-width: 600px) {
    .plugin-row { flex-direction: column; }
    .plugin-actions { justify-content: flex-start; }
}

/* ---------- Chat header controls (project picker + favourite) ---------- */
.chat-header-controls {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-left: 4px;
}
.chat-header-controls:empty { display: none; }
.header-project-select {
    height: 32px;
    border-radius: var(--radius-sm);
    background: var(--glass);
    border: 1px solid var(--hairline-soft);
    color: var(--text);
    font-size: 13px;
    padding: 0 26px 0 10px;
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%238a8a96' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
    background-repeat: no-repeat;
    background-position: right 8px center;
    cursor: pointer;
    max-width: 180px;
    text-overflow: ellipsis;
}
.header-project-select:hover { border-color: var(--hairline); }
.header-fav-btn {
    width: 32px; height: 32px;
    color: var(--text-3);
}
.header-fav-btn svg { width: 18px; height: 18px; }
.header-fav-btn.is-active { color: var(--accent-2); }

/* The cog button replaces the inline model/project selectors below the
   mobile breakpoint — keeping the header from squashing those down to
   nothing while still leaving room for the favourite toggle. */
.header-cog-btn {
    display: none;
    width: 32px; height: 32px;
    color: var(--text-2);
}
.header-cog-btn svg { width: 18px; height: 18px; }

@media (max-width: 600px) {
    .model-selector,
    .header-project-select { display: none; }
    .header-cog-btn { display: inline-flex; }
    /* flex-grow runs BEFORE auto-margin in the flex algorithm, so the
       trailing .header-spacer's flex:1 was eating all the free space
       and pinning the controls to the left. Hide just the spacer that
       directly follows the chat header controls (other pages' spacers
       are untouched), then margin-left:auto on the controls works. */
    .chat-header-controls + .header-spacer { display: none; }
    .chat-header-controls { margin-left: auto; }
}

/* ---------- Sidebar search ---------- */
.sidebar-search {
    position: relative;
    margin: 6px 10px 4px;
}
.sidebar-search-icon {
    position: absolute;
    left: 10px; top: 50%; transform: translateY(-50%);
    color: var(--text-3);
    pointer-events: none;
    display: flex;
}
.sidebar-search-icon svg { width: 14px; height: 14px; }
.sidebar-search-input {
    width: 100%;
    height: 34px;
    padding: 0 30px 0 32px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--hairline-soft);
    background: var(--glass);
    color: var(--text);
    font-size: 13.5px;
    font-family: inherit;
    outline: none;
    transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.sidebar-search-input::placeholder { color: var(--text-3); }
.sidebar-search-input:focus {
    border-color: var(--accent-soft);
    background: var(--glass-strong);
}
.sidebar-search-clear {
    position: absolute;
    right: 6px; top: 50%; transform: translateY(-50%);
    background: none;
    border: none;
    color: var(--text-3);
    cursor: pointer;
    padding: 4px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
}
.sidebar-search-clear:hover { background: var(--glass-strong); color: var(--text); }
.sidebar-search-clear svg { width: 12px; height: 12px; }

.chat-list-item-body {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
}
.chat-snippet {
    font-size: 12px;
    color: var(--text-3);
    line-height: 1.4;
    margin-top: 2px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
mark {
    background: var(--accent-soft);
    color: var(--text);
    border-radius: 3px;
    padding: 0 2px;
}

/* ---------- Offline pill (sidebar header) ---------- */
.offline-pill {
    display: none;
    align-items: center;
    gap: 5px;
    padding: 3px 8px 3px 6px;
    border-radius: var(--radius-pill);
    background: var(--danger-soft);
    color: var(--danger);
    font-size: 11px;
    font-weight: 600;
    border: 1px solid color-mix(in srgb, var(--danger) 26%, transparent);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.offline-pill svg { width: 12px; height: 12px; }
body.offline .offline-pill { display: inline-flex; }

/* ---------- Sidebar favourites ---------- */
.favorites-section {
    padding: 6px 10px 0;
}
.sidebar-section-label {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-3);
    padding: 8px 10px 6px;
    font-weight: 600;
}
.favorites-list {
    display: flex;
    flex-direction: column;
    gap: 1px;
    margin-bottom: 8px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--hairline-soft);
}
.favorite-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 10px;
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background var(--t-fast) var(--ease);
    color: var(--text-2);
    font-size: 13.5px;
}
.favorite-item:hover { background: var(--glass-strong); color: var(--text); }
.favorite-item.active {
    background: var(--accent-soft);
    color: var(--text);
}
.favorite-icon {
    flex-shrink: 0;
    width: 14px; height: 14px;
    color: var(--accent-2);
    display: grid; place-items: center;
}
.favorite-icon svg { width: 14px; height: 14px; }
.favorite-title {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 1;
}

/* ---------- Projects ---------- */
.project-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 14px;
}
.project-card {
    background: var(--glass);
    border: 1px solid var(--hairline-soft);
    border-radius: var(--radius);
    padding: 16px;
    cursor: pointer;
    transition: transform var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.project-card:hover {
    transform: translateY(-1px);
    border-color: var(--hairline);
    background: var(--glass-strong);
}
.project-card.is-favorite { border-color: var(--accent-soft); }
.project-card-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 8px;
}
.project-card-name {
    font-size: 16px;
    font-weight: 600;
    margin: 0;
    line-height: 1.3;
    letter-spacing: -0.01em;
}
.project-card-desc {
    font-size: 13.5px;
    color: var(--text-2);
    line-height: 1.5;
    margin: 0 0 10px;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.project-card-meta {
    font-size: 12px;
    color: var(--text-3);
}
.project-fav-btn {
    width: 32px; height: 32px;
    color: var(--text-3);
    flex-shrink: 0;
}
.project-fav-btn svg { width: 18px; height: 18px; }
.project-card.is-favorite .project-fav-btn,
.is-favorite .project-fav-btn {
    color: var(--accent-2);
}
.project-instructions { margin-top: 16px; }
.project-instructions h3 {
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-3);
    margin: 0 0 8px;
    font-weight: 600;
}
.project-instructions-text {
    background: var(--glass);
    border: 1px solid var(--hairline-soft);
    border-radius: var(--radius-sm);
    padding: 12px 14px;
    font-family: var(--font-mono);
    font-size: 13px;
    line-height: 1.6;
    color: var(--text);
    white-space: pre-wrap;
    word-break: break-word;
    margin: 0;
}
.project-chat-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.project-chat-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 10px 12px;
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background var(--t-fast) var(--ease);
    gap: 12px;
}
.project-chat-item:hover { background: var(--glass-strong); }
.project-chat-title {
    font-size: 14px;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.project-chat-meta {
    font-size: 12px;
    color: var(--text-3);
    flex-shrink: 0;
}
.dialog-wide { max-width: 560px; }

/* ---------- Settings tabs (in-shell, sit under the main-header) ---------- */
.settings-tabs {
    display: flex;
    gap: 2px;
    padding: 0 16px;
    overflow-x: auto;
    border-bottom: 1px solid var(--hairline-soft);
    background: var(--glass);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    flex-shrink: 0;
    scrollbar-width: none;
}
.settings-tabs::-webkit-scrollbar { display: none; }
.settings-tab {
    background: transparent;
    border: none;
    color: var(--text-2);
    font-size: 14px;
    font-weight: 500;
    padding: 12px 16px;
    cursor: pointer;
    position: relative;
    white-space: nowrap;
    font-family: inherit;
    transition: color var(--t-fast) var(--ease);
}
.settings-tab:hover { color: var(--text); }
.settings-tab.active { color: var(--text); }
.settings-tab.active::after {
    content: '';
    position: absolute;
    left: 14px; right: 14px;
    bottom: -1px;
    height: 2px;
    background: var(--accent);
    border-radius: 2px 2px 0 0;
}
@media (max-width: 600px) {
    .settings-tabs { padding: 0 8px; }
    .settings-tab { padding: 11px 12px; font-size: 13.5px; }
}

/* ---------- Profile / avatar builder (Settings) ---------- */
.avatar-builders {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 18px;
    margin-top: 12px;
}
.avatar-builder {
    background: var(--glass);
    border: 1px solid var(--hairline-soft);
    border-radius: var(--radius);
    padding: 16px;
}
.avatar-builder-label {
    font-size: 13px;
    color: var(--text-2);
    margin-bottom: 10px;
    font-weight: 500;
}
.avatar-section-label {
    font-size: 11px;
    color: var(--text-3);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin: 14px 0 6px;
    font-weight: 600;
}
.avatar-preview {
    width: 64px; height: 64px;
    border-radius: var(--radius-pill);
    display: grid; place-items: center;
    font-size: 32px; line-height: 1;
    margin: 4px auto 4px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.22);
}
.color-grid {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 6px;
}
.color-swatch {
    aspect-ratio: 1 / 1;
    border-radius: var(--radius-pill);
    border: 2px solid transparent;
    cursor: pointer;
    padding: 0;
    transition: transform var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.color-swatch:hover { transform: scale(1.08); }
.color-swatch.selected {
    border-color: var(--text);
    box-shadow: 0 0 0 2px var(--bg-base), 0 0 0 4px var(--text);
}
.emoji-grid {
    display: grid;
    /* auto-fit picks however many ~38px cells fit the column width;
       narrower avatar-builder cards get fewer columns + more rows
       instead of squeezing emojis into too-tight cells. */
    grid-template-columns: repeat(auto-fit, minmax(38px, 1fr));
    gap: 6px;
}
.emoji-swatch {
    aspect-ratio: 1 / 1;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-size: 18px;
    line-height: 1;
    padding: 0;
    color: inherit;
    /* Center the glyph and give the emoji breathing room inside the
       cell so wide emojis (flags, ZWJ sequences) don't clip the edges. */
    display: grid;
    place-items: center;
    overflow: hidden;
    transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.emoji-swatch:hover {
    background: var(--glass-strong);
    transform: scale(1.08);
}
.emoji-swatch.selected {
    background: var(--accent-soft);
    border-color: var(--accent);
}

/* ---------- Theme picker (Settings → Profile) ---------- */
.theme-picker-section {
    margin-top: 18px;
    padding-top: 18px;
    border-top: 1px solid var(--hairline-soft);
}
.theme-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 10px;
}
.theme-card {
    display: flex;
    flex-direction: column;
    text-align: left;
    padding: 10px;
    background: var(--glass);
    border: 1px solid var(--hairline-soft);
    border-radius: var(--radius);
    cursor: pointer;
    transition: transform var(--t-fast) var(--ease),
                border-color var(--t-fast) var(--ease),
                background var(--t-fast) var(--ease);
}
.theme-card:hover { transform: translateY(-1px); background: var(--glass-strong); }
.theme-card.selected {
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.theme-card-preview {
    position: relative;
    display: block;
    height: 64px;
    border-radius: var(--radius-sm);
    overflow: hidden;
    margin-bottom: 8px;
    border: 1px solid var(--hairline-soft);
}
.theme-card-surface {
    position: absolute;
    inset: 8px 8px 28px 8px;
    border-radius: 6px;
}
.theme-card-bubble {
    position: absolute;
    height: 12px;
    border-radius: 6px;
}
.theme-card-bubble-ai {
    left: 14px;
    bottom: 10px;
    width: 38%;
}
.theme-card-bubble-user {
    right: 14px;
    bottom: 10px;
    width: 30%;
}
.theme-card-dot {
    position: absolute;
    top: 6px;
    right: 6px;
    width: 10px;
    height: 10px;
    border-radius: var(--radius-pill);
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.18);
}
.theme-card-label {
    font-size: 13px;
    font-weight: 600;
    color: var(--text);
}
.theme-card-blurb {
    font-size: 11.5px;
    color: var(--text-3);
    margin-top: 2px;
    line-height: 1.4;
}

/* ---------- Utility ---------- */
.hidden { display: none !important; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }
.text-center { text-align: center; }
.mt-8  { margin-top: 8px; }
.mt-12 { margin-top: 12px; }
.mt-16 { margin-top: 16px; }
.mt-20 { margin-top: 20px; }
.mb-8  { margin-bottom: 8px; }
.mb-16 { margin-bottom: 16px; }
.gap-8 { gap: 8px; }
.flex { display: flex; }
.flex-wrap { flex-wrap: wrap; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }

/* =========================================================
   THEMES
   ---------------------------------------------------------
   Themes are applied via `data-theme="<id>"` on the root
   element. Each block re-declares the design tokens; light
   vs dark within a theme nests an extra
   `@media (prefers-color-scheme: light)` block.

   Token overrides cover the bulk of the visual change.
   Where a theme needs to alter geometry (bubble shape,
   shadows, blur) we add a small number of selector-level
   tweaks under the same `:root[data-theme=…]` guard.
   ========================================================= */

/* ----- Cupertino (Apple / iMessage) ----- */
/* Dark = default. Light overrides nested below. */
:root[data-theme="cupertino"] {
    --font-sans: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'SF Pro Display',
                 'Inter', 'Segoe UI', Roboto, system-ui, sans-serif;

    /* iOS dark surfaces — flatter than the Default glassy stack.
       The visual hierarchy comes from solid greys, not translucent
       layers, so message bubbles read like iMessage. */
    --bg-base: #000000;
    --bg-canvas: #1c1c1e;
    --glass: #1c1c1e;
    --glass-strong: #2c2c2e;
    --glass-stronger: #3a3a3c;
    --glass-elev: #2c2c2e;
    --hairline: rgba(255, 255, 255, 0.10);
    --hairline-soft: rgba(255, 255, 255, 0.06);
    --hairline-strong: rgba(255, 255, 255, 0.20);

    --text: #ffffff;
    --text-2: rgba(235, 235, 245, 0.60);
    --text-3: rgba(235, 235, 245, 0.30);

    /* iOS system blue (dark variant). */
    --accent: #0a84ff;
    --accent-2: #409cff;
    --accent-soft: rgba(10, 132, 255, 0.22);
    --accent-glow: rgba(10, 132, 255, 0.32);
    --accent-fg: #ffffff;

    --danger: #ff453a;
    --danger-soft: rgba(255, 69, 58, 0.18);
    --success: #30d158;
    --success-soft: rgba(48, 209, 88, 0.18);
    --warning: #ffd60a;

    --radius-xs: 6px;
    --radius-sm: 10px;
    --radius: 14px;
    --radius-lg: 18px;
    --radius-xl: 22px;

    --shadow-1: 0 1px 0 rgba(255, 255, 255, 0.04) inset, 0 1px 3px rgba(0, 0, 0, 0.4);
    --shadow-2: 0 2px 8px rgba(0, 0, 0, 0.5);
    --shadow-float: 0 8px 24px rgba(0, 0, 0, 0.45);
}
@media (prefers-color-scheme: light) {
    :root[data-theme="cupertino"] {
        --bg-base: #f2f2f7;
        --bg-canvas: #ffffff;
        --glass: #ffffff;
        --glass-strong: #f2f2f7;
        --glass-stronger: #e5e5ea;
        --glass-elev: #ffffff;
        --hairline: rgba(60, 60, 67, 0.18);
        --hairline-soft: rgba(60, 60, 67, 0.10);
        --hairline-strong: rgba(60, 60, 67, 0.29);

        --text: #000000;
        --text-2: rgba(60, 60, 67, 0.60);
        --text-3: rgba(60, 60, 67, 0.30);

        --accent: #007aff;
        --accent-2: #339cff;
        --accent-soft: rgba(0, 122, 255, 0.14);
        --accent-glow: rgba(0, 122, 255, 0.22);

        --danger: #ff3b30;
        --danger-soft: rgba(255, 59, 48, 0.12);
        --success: #34c759;
        --success-soft: rgba(52, 199, 89, 0.14);

        --shadow-1: 0 1px 2px rgba(60, 60, 67, 0.10);
        --shadow-2: 0 6px 18px rgba(60, 60, 67, 0.14);
        --shadow-float: 0 10px 28px rgba(60, 60, 67, 0.18);
    }
}
/* Cupertino: flat surfaces — no backdrop blur, no glow.
   The Default theme leans on `backdrop-filter` for its glassy
   feel; iMessage doesn't have that. Disable the blur so the
   surfaces read as solid Apple-system materials. */
:root[data-theme="cupertino"] .main-content,
:root[data-theme="cupertino"] .sidebar,
:root[data-theme="cupertino"] .auth-card,
:root[data-theme="cupertino"] .panel-card,
:root[data-theme="cupertino"] .message-assistant .message-bubble {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
/* iMessage-style chat bubbles: solid fills, tighter padding,
   tail-side rounded corner kept small (8px) to read as the
   classic "speech bubble" corner. */
:root[data-theme="cupertino"] .message-user .message-bubble {
    background: var(--accent);
    box-shadow: none;
    border-radius: 20px 20px 6px 20px;
}
:root[data-theme="cupertino"] .message-assistant .message-bubble {
    background: var(--glass-stronger);
    border-color: transparent;
    border-radius: 20px 20px 20px 6px;
    color: var(--text);
}

/* ----- Material 3 ----- */
:root[data-theme="material"] {
    --font-sans: 'Roboto', 'Inter', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;

    /* M3 dark scheme — surface tonal layers, primary mauve. */
    --bg-base: #141218;
    --bg-canvas: #1d1b20;
    --glass: #211f26;
    --glass-strong: #2b2930;
    --glass-stronger: #36343b;
    --glass-elev: #2b2930;
    --hairline: rgba(202, 196, 208, 0.16);
    --hairline-soft: rgba(202, 196, 208, 0.10);
    --hairline-strong: rgba(202, 196, 208, 0.28);

    --text: #e6e0e9;
    --text-2: #cac4d0;
    --text-3: rgba(202, 196, 208, 0.62);

    --accent: #d0bcff;          /* M3 primary on dark */
    --accent-2: #b69df8;
    --accent-soft: rgba(208, 188, 255, 0.16);
    --accent-glow: rgba(208, 188, 255, 0.20);
    --accent-fg: #381e72;        /* on-primary on dark */

    --danger: #f2b8b5;
    --danger-soft: rgba(242, 184, 181, 0.16);
    --success: #7dd9a7;
    --success-soft: rgba(125, 217, 167, 0.16);
    --warning: #f5cf78;

    /* M3 leans squarer than Default. */
    --radius-xs: 4px;
    --radius-sm: 8px;
    --radius: 12px;
    --radius-lg: 16px;
    --radius-xl: 28px;

    /* M3 elevation 1-3 ish — softer, smaller than iOS shadows. */
    --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.30), 0 1px 3px 1px rgba(0, 0, 0, 0.15);
    --shadow-2: 0 1px 2px rgba(0, 0, 0, 0.30), 0 2px 6px 2px rgba(0, 0, 0, 0.15);
    --shadow-float: 0 4px 8px 3px rgba(0, 0, 0, 0.15), 0 1px 3px rgba(0, 0, 0, 0.30);
}
@media (prefers-color-scheme: light) {
    :root[data-theme="material"] {
        --bg-base: #fef7ff;
        --bg-canvas: #fffbff;
        --glass: #f7f2fa;
        --glass-strong: #eee8f3;
        --glass-stronger: #e6dff0;
        --glass-elev: #fffbff;
        --hairline: rgba(73, 69, 79, 0.18);
        --hairline-soft: rgba(73, 69, 79, 0.10);
        --hairline-strong: rgba(73, 69, 79, 0.28);

        --text: #1d1b20;
        --text-2: #49454f;
        --text-3: rgba(73, 69, 79, 0.62);

        --accent: #6750a4;
        --accent-2: #7f67be;
        --accent-soft: rgba(103, 80, 164, 0.12);
        --accent-glow: rgba(103, 80, 164, 0.18);
        --accent-fg: #ffffff;

        --danger: #b3261e;
        --danger-soft: rgba(179, 38, 30, 0.10);
        --success: #146c2e;
        --success-soft: rgba(20, 108, 46, 0.12);

        --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.10), 0 1px 3px 1px rgba(0, 0, 0, 0.06);
        --shadow-2: 0 1px 2px rgba(0, 0, 0, 0.10), 0 2px 6px 2px rgba(0, 0, 0, 0.08);
        --shadow-float: 0 4px 8px 3px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.10);
    }
}
/* Material 3: no glass blur — surfaces are tonal, not translucent.
   User bubble uses primary container (filled tonal), assistant
   bubble uses surface variant — both with the M3 large-corner
   shape that's smaller on the "tail" side. */
:root[data-theme="material"] .main-content,
:root[data-theme="material"] .sidebar,
:root[data-theme="material"] .auth-card,
:root[data-theme="material"] .panel-card,
:root[data-theme="material"] .message-assistant .message-bubble {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
:root[data-theme="material"] .message-user .message-bubble {
    background: var(--accent);
    color: var(--accent-fg);
    box-shadow: var(--shadow-1);
    border-radius: 20px 20px 4px 20px;
}
:root[data-theme="material"] .message-assistant .message-bubble {
    background: var(--glass-strong);
    border-color: transparent;
    color: var(--text);
    border-radius: 20px 20px 20px 4px;
    box-shadow: var(--shadow-1);
}

/* ----- Catppuccin (Latte light / Macchiato dark) ----- */
/* Default is dark, so Macchiato is the base. Latte overrides in
   the light-mode media query below. */
:root[data-theme="catppuccin"] {
    /* Macchiato palette https://catppuccin.com/palette/ */
    --bg-base: #24273a;       /* base */
    --bg-canvas: #1e2030;     /* mantle */
    --glass: #363a4f;         /* surface0 */
    --glass-strong: #494d64;  /* surface1 */
    --glass-stronger: #5b6078;/* surface2 */
    --glass-elev: #363a4f;
    --hairline: rgba(202, 211, 245, 0.10);
    --hairline-soft: rgba(202, 211, 245, 0.06);
    --hairline-strong: rgba(202, 211, 245, 0.18);

    --text: #cad3f5;          /* text */
    --text-2: #b8c0e0;        /* subtext1 */
    --text-3: #a5adcb;        /* subtext0 */

    --accent: #c6a0f6;        /* mauve */
    --accent-2: #f5bde6;      /* pink */
    --accent-soft: rgba(198, 160, 246, 0.18);
    --accent-glow: rgba(198, 160, 246, 0.28);
    --accent-fg: #24273a;

    --danger: #ed8796;        /* red */
    --danger-soft: rgba(237, 135, 150, 0.18);
    --success: #a6da95;       /* green */
    --success-soft: rgba(166, 218, 149, 0.18);
    --warning: #eed49f;       /* yellow */
}
@media (prefers-color-scheme: light) {
    :root[data-theme="catppuccin"] {
        /* Latte palette — surface0/1/2 are too dark to use as primary
           UI fills in a light theme; they're meant for subtle accents.
           Map the bright base/mantle/crust tones onto the design's
           "glass" layers instead, with #ffffff for cards/bubbles so
           text reads cleanly. surface0 stays available as the
           strongest emphasis level. */
        --bg-base: #eff1f5;       /* base */
        --bg-canvas: #e6e9ef;     /* mantle */
        --glass: #ffffff;         /* cards / sidebar / assistant bubble */
        --glass-strong: #e6e9ef;  /* mantle — hover/elevated state */
        --glass-stronger: #dce0e8;/* crust — strongest emphasis */
        --glass-elev: #ffffff;
        --hairline: rgba(76, 79, 105, 0.18);
        --hairline-soft: rgba(76, 79, 105, 0.10);
        --hairline-strong: rgba(76, 79, 105, 0.28);

        --text: #4c4f69;          /* text */
        --text-2: #5c5f77;        /* subtext1 */
        --text-3: #6c6f85;        /* subtext0 */

        --accent: #8839ef;        /* mauve */
        --accent-2: #ea76cb;      /* pink */
        --accent-soft: rgba(136, 57, 239, 0.14);
        --accent-glow: rgba(136, 57, 239, 0.22);
        --accent-fg: #eff1f5;

        --danger: #d20f39;        /* red */
        --danger-soft: rgba(210, 15, 57, 0.12);
        --success: #40a02b;       /* green */
        --success-soft: rgba(64, 160, 43, 0.14);
        --warning: #df8e1d;       /* yellow */

        --shadow-1: 0 1px 2px rgba(76, 79, 105, 0.10), 0 6px 18px rgba(76, 79, 105, 0.12);
        --shadow-2: 0 2px 6px rgba(76, 79, 105, 0.10), 0 14px 36px rgba(76, 79, 105, 0.16);
        --shadow-float: 0 1px 1px rgba(76, 79, 105, 0.08), 0 12px 32px rgba(76, 79, 105, 0.16);
    }
}
/* Catppuccin: solid surfaces, no glass blur — the palette is
   designed to read with flat fills. Strip every gradient the
   Default theme applies (primary buttons, brand marks, send
   button, user bubble) and replace with solid mauve so the
   Catppuccin colour story stays cohesive. */
:root[data-theme="catppuccin"] .main-content,
:root[data-theme="catppuccin"] .sidebar,
:root[data-theme="catppuccin"] .auth-card,
:root[data-theme="catppuccin"] .panel-card,
:root[data-theme="catppuccin"] .message-assistant .message-bubble {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
/* Solid mauve fills — covers .btn-primary, .new-chat-btn, the
   user-bubble background, the round send button, and any other
   surface the Default theme paints with the accent gradient. */
:root[data-theme="catppuccin"] .btn-primary,
:root[data-theme="catppuccin"] .new-chat-btn,
:root[data-theme="catppuccin"] .message-user .message-bubble,
:root[data-theme="catppuccin"] .send-btn {
    background: var(--accent);
    color: var(--accent-fg);
    box-shadow: 0 4px 14px var(--accent-glow);
}
/* The pink-accented brand mark, sidebar user avatar, and empty-state
   icon use a mauve→pink gradient by default. In Catppuccin we keep
   them tied to the palette but flat — pure mauve reads as part of the
   theme rather than a leftover from Default. */
:root[data-theme="catppuccin"] .sidebar-brand-mark,
:root[data-theme="catppuccin"] .user-menu-btn .user-avatar,
:root[data-theme="catppuccin"] .chat-empty .empty-icon {
    background: var(--accent);
    color: var(--accent-fg);
}
:root[data-theme="catppuccin"] .message-assistant .message-bubble {
    background: var(--glass);
    border-color: var(--hairline-soft);
}

/* =========================================================
   Media generation — composer popover, modals, attachments
   ========================================================= */

/* Popover anchored to the composer's media button. Positioned in JS
   so it always sits above (or below if there's no room) the trigger. */
.media-picker {
    position: fixed;
    z-index: 1000;
    min-width: 260px;
    max-width: 320px;
    background: var(--bg-panel, var(--bg-base));
    border: 1px solid var(--hairline);
    border-radius: 14px;
    box-shadow: var(--shadow-float);
    padding: 6px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    animation: media-picker-in 120ms ease-out;
}
@keyframes media-picker-in {
    from { opacity: 0; transform: translateY(4px) scale(0.98); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}
.media-picker-arrow { display: none; }
.media-picker-item {
    display: flex;
    align-items: center;
    gap: 10px;
    /* Buttons default to width:auto and don't stretch in column flex
       parents in every browser despite the parent's align-items:stretch,
       which is why the rows previously sat at content-width with a gap
       on the right. Force the full container width here. */
    width: 100%;
    box-sizing: border-box;
    padding: 9px 10px;
    border: none;
    border-radius: 10px;
    background: transparent;
    color: var(--text-1, var(--text));
    cursor: pointer;
    text-align: left;
    font: inherit;
    transition: background 120ms ease;
}
.media-picker-item:hover,
.media-picker-item:focus-visible {
    background: var(--glass);
    outline: none;
}
.media-picker-item.is-disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.media-picker-item.is-disabled:hover { background: transparent; }
.media-picker-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 8px;
    background: var(--glass);
    color: var(--text-1, var(--text));
    flex-shrink: 0;
}
.media-picker-icon svg { width: 16px; height: 16px; }
.media-picker-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}
.media-picker-label {
    font-size: 13.5px;
    font-weight: 500;
}
.media-picker-sub {
    font-size: 11.5px;
    color: var(--text-3);
}

/* Composer Tools menu — reuses .media-picker chrome. Toggle rows show an
   active state + a check on the right; a separator splits the on/off
   toggles from the one-shot context actions. */
.tools-picker { min-width: 280px; }
.tools-toggle { position: relative; }
.tools-toggle.is-on .media-picker-icon {
    background: var(--accent);
    color: #fff;
}
.tools-toggle-state {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    color: var(--accent);
    flex-shrink: 0;
}
.tools-toggle-state svg { width: 16px; height: 16px; }
.tools-picker-sep {
    height: 1px;
    margin: 4px 8px;
    background: var(--hairline);
}

/* Reasoning-effort segmented control inside the Tools popover. Only
   rendered for reasoning-capable models. Icon + label on one row, the
   Auto/Low/Medium/High segments wrap beneath on narrow widths. */
.tools-reasoning {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    padding: 8px 12px;
}
.tools-reasoning-label {
    font-size: 13px;
    font-weight: 500;
    color: var(--text);
}
.tools-reasoning-seg {
    display: inline-flex;
    margin-left: auto;
    border: 1px solid var(--hairline);
    border-radius: var(--radius-sm, 8px);
    overflow: hidden;
}
.tools-reasoning-opt {
    appearance: none;
    border: 0;
    background: transparent;
    color: var(--text-2);
    font: inherit;
    font-size: 12px;
    padding: 4px 9px;
    cursor: pointer;
    border-left: 1px solid var(--hairline);
}
.tools-reasoning-opt:first-child { border-left: 0; }
.tools-reasoning-opt:hover { background: var(--glass); color: var(--text); }
.tools-reasoning-opt.is-on {
    background: var(--accent);
    color: #fff;
}

/* Context-boundary dividers (Condense / Clear Session Context). A quiet
   horizontal rule with a centred label so the user can see exactly where
   the AI's working context begins. */
.context-marker {
    margin: 18px 0;
    font-size: 12px;
    color: var(--text-3);
}
.context-marker-line {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    text-align: center;
}
.context-marker-line svg { width: 14px; height: 14px; flex-shrink: 0; }
/* Flanking rules drawn with pseudo-elements so the label sits centred. */
.context-marker-line::before,
.context-marker-line::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--hairline);
    max-width: 120px;
}
.context-marker-condense details > summary {
    list-style: none;
    cursor: pointer;
}
.context-marker-condense details > summary::-webkit-details-marker { display: none; }
.context-marker-condense summary:hover { color: var(--text); }
.context-marker-summary {
    margin: 10px auto 0;
    max-width: 640px;
    padding: 12px 14px;
    background: var(--glass);
    border: 1px solid var(--hairline);
    border-radius: 12px;
    font-size: 13px;
    color: var(--text);
    line-height: 1.5;
}
.context-marker-summary > :first-child { margin-top: 0; }
.context-marker-summary > :last-child { margin-bottom: 0; }
/* Lists need explicit indent — the global reset zeroes ul/ol padding, which
   pushes the bullet markers outside the summary box. Match the in-bubble
   list indent so they sit inside. */
.context-marker-summary ul,
.context-marker-summary ol { margin: 6px 0; padding-left: 22px; }
.context-marker-summary li { margin: 2px 0; }

/* Transient "Condensing…" row shown while the summary is generated (the
   blocking/cloud call or the in-browser local summary can take a while). */
.context-marker-progress .context-marker-line { color: var(--text-3); }
.context-marker-progress .thinking-dots {
    padding: 0;
    background: none;
    border: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    gap: 4px;
}
.context-marker-progress .thinking-dots span { width: 6px; height: 6px; }

/* Modal — reuses .dialog-overlay/.dialog structure with media-specific
   spacing/layout tweaks. */
.media-modal-overlay .media-modal {
    max-width: 520px;
    width: 92vw;
}
.media-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}
.media-modal-header h3 { margin: 0; }
.media-modal-header .icon-btn {
    width: 32px;
    height: 32px;
    border-radius: 8px;
    background: transparent;
    border: none;
    color: var(--text-2, var(--text-3));
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.media-modal-header .icon-btn:hover { background: var(--glass); color: var(--text-1, var(--text)); }
.media-form .form-input { width: 100%; }
.media-label {
    display: block;
    font-size: 12.5px;
    font-weight: 500;
    color: var(--text-2, var(--text-3));
    margin: 0 0 6px;
}
.media-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 10px;
    margin-top: 10px;
}
.media-field { display: flex; flex-direction: column; }
.media-option-fields { margin-top: 4px; }
.media-modal-actions { margin-top: 16px; }
.media-hint {
    font-size: 11.5px;
    color: var(--text-3);
    margin: 6px 0 0;
}
.media-status { min-height: 1em; }

/* =========================================================
   Attachment rendering — image / audio / video / file badge
   ========================================================= */

.message-attachments {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 10px;
    /* Belt-and-braces against intrinsic-content overflow on mobile:
       cap to parent width, release the flex min-width floor, and any
       last-resort overflow gets clipped instead of forcing horizontal
       scroll on the whole viewport. */
    max-width: 100%;
    min-width: 0;
    overflow: hidden;
}
.attachment-figure {
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
    max-width: min(480px, 100%);
    min-width: 0;
}
.attachment-figure img.attachment-preview {
    /* `max-width: 100%` is the standard responsive-image clamp; pairing
       it with `width: auto; height: auto` keeps aspect ratio intact at
       any container width. Together with the bubble's min-width:0, a
       1024×1024 generated image now scales down cleanly on a 320px
       viewport instead of pushing the bubble out. */
    max-width: 100%;
    width: auto;
    height: auto;
    max-height: 520px;
    border-radius: 10px;
    border: 1px solid var(--hairline);
    display: block;
}
.attachment-figure audio,
.attachment-figure video {
    width: 100%;
    max-width: 100%;
    border-radius: 10px;
    display: block;
}
.attachment-figure video {
    max-height: 480px;
    background: #000;
}
.attachment-caption {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    color: var(--text-3);
}
.attachment-caption span:first-of-type:not(.attachment-icon),
.attachment-caption > span:not(.attachment-icon) {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.attachment-icon {
    display: inline-flex;
    align-items: center;
    color: var(--text-3);
    flex-shrink: 0;
}
.attachment-icon svg { width: 14px; height: 14px; }
.attachment-download {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border-radius: 7px;
    color: var(--text-3);
    text-decoration: none;
    transition: background 120ms ease, color 120ms ease;
}
.attachment-download:hover {
    background: var(--glass);
    color: var(--text-1, var(--text));
}
.attachment-download svg { width: 14px; height: 14px; }

/* The fallback badge for non-image / non-audio / non-video files — PDFs,
   code dumps, anything else. Whole row is clickable for download. */
.attachment-badge.attachment-file {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    border: 1px solid var(--hairline);
    border-radius: 10px;
    color: var(--text-1, var(--text));
    text-decoration: none;
    font-size: 13px;
    background: var(--glass);
    max-width: fit-content;
    transition: background 120ms ease;
}
.attachment-badge.attachment-file:hover {
    background: var(--hairline-soft, var(--glass));
}
.attachment-badge.attachment-file svg:first-child { width: 16px; height: 16px; flex-shrink: 0; }
.attachment-badge.attachment-file svg:last-child  { width: 14px; height: 14px; color: var(--text-3); margin-left: auto; }
.attachment-badge.attachment-file > span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 320px;
}

/* Floating popover that lists each attachment when a message has more
   than one — pick which file to download. */
.attachment-download-menu {
    position: fixed;
    z-index: 1100;
    min-width: 220px;
    max-width: 320px;
    background: var(--bg-panel, var(--bg-base));
    border: 1px solid var(--hairline);
    border-radius: 12px;
    box-shadow: var(--shadow-float);
    padding: 6px;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.attachment-download-item {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 8px 10px;
    background: transparent;
    border: none;
    border-radius: 8px;
    color: var(--text-1, var(--text));
    font: inherit;
    font-size: 13px;
    cursor: pointer;
    text-align: left;
}
.attachment-download-item:hover { background: var(--glass); }
.attachment-download-item svg { width: 14px; height: 14px; flex-shrink: 0; color: var(--text-3); }
.attachment-download-item span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* =========================================================
   Code block — copy + download affordances
   ========================================================= */

.code-block-wrap {
    position: relative;
    margin: 10px 0;
}
.code-block-wrap pre {
    margin: 0;
}
.code-block-actions {
    position: absolute;
    top: 8px;
    right: 8px;
    display: flex;
    gap: 4px;
    opacity: 0;
    transition: opacity 120ms ease;
}
.code-block-wrap:hover .code-block-actions,
.code-block-wrap:focus-within .code-block-actions {
    opacity: 1;
}
.code-block-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 7px;
    background: var(--bg-panel, var(--bg-base));
    border: 1px solid var(--hairline);
    color: var(--text-2, var(--text-3));
    cursor: pointer;
    transition: background 120ms ease, color 120ms ease;
}
.code-block-btn:hover {
    background: var(--glass);
    color: var(--text-1, var(--text));
}
.code-block-btn svg { width: 14px; height: 14px; }

/* On touch devices the hover gate means the buttons are invisible —
   always show them there. */
@media (hover: none) {
    .code-block-actions { opacity: 1; }
}

/* Settings sub-tabs — a quieter, smaller-radius row of pills used to
   split the outer "API & Models" tab into "API" + "Models" panes.
   Visually distinct from the top-level settings-tabs so the hierarchy
   reads correctly: one underlined row, one pill row. */
.settings-subtabs {
    display: flex;
    gap: 6px;
    margin: 4px 0 14px;
    flex-wrap: wrap;
}
.settings-subtab {
    background: var(--glass);
    border: 1px solid var(--hairline-soft);
    color: var(--text-2, var(--text-3));
    font-size: 13px;
    font-weight: 500;
    padding: 7px 14px;
    border-radius: var(--radius-pill);
    cursor: pointer;
    font-family: inherit;
    transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.settings-subtab:hover { color: var(--text-1, var(--text)); }
.settings-subtab.active {
    background: var(--accent-soft);
    color: var(--accent);
    border-color: var(--accent-soft);
}

/* Filter chips — used in Settings → Models to narrow the catalogue
   by provider and capability. Multi-select; "All" clears the row. */
.model-filter-block {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 12px;
    margin: 0 0 14px;
    background: var(--glass);
    border: 1px solid var(--hairline-soft);
    border-radius: var(--radius-sm, 10px);
}
.filter-row {
    display: flex;
    gap: 10px;
    align-items: center;
    flex-wrap: wrap;
}
.filter-row-label {
    font-size: 11.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-3);
    min-width: 80px;
}
.filter-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    flex: 1;
    min-width: 0;
}
.filter-chip {
    background: transparent;
    border: 1px solid var(--hairline);
    color: var(--text-2, var(--text-3));
    font-size: 12.5px;
    font-weight: 500;
    padding: 5px 11px;
    border-radius: var(--radius-pill);
    cursor: pointer;
    font-family: inherit;
    transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.filter-chip:hover { color: var(--text-1, var(--text)); border-color: var(--hairline); }
.filter-chip.is-on {
    background: var(--accent);
    color: var(--accent-fg, white);
    border-color: var(--accent);
}
.filter-chip-all { font-weight: 600; }

@media (max-width: 540px) {
    .filter-row { flex-direction: column; align-items: flex-start; gap: 6px; }
    .filter-row-label { min-width: 0; }
}

/* Settings → Models list — grouped by kind heading. Chat / Image /
   TTS / Transcription each get their own subsection so the list reads
   as four catalogues rather than one undifferentiated pile. */
.model-kind-section {
    margin-bottom: 18px;
}
.model-kind-section:last-child { margin-bottom: 0; }
.model-kind-heading {
    margin: 14px 0 6px;
    font-size: 12.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-3);
}
.model-kind-section:first-child .model-kind-heading {
    margin-top: 4px;
}

/* ----- Bang (neubrutalist) -----
   Loud, playful, sticker-like. The signature moves:
     - thick black borders (--hairline overloaded to solid black)
     - hard-offset black drop shadows, zero blur (--shadow-1/2/float)
     - flat saturated fills, no gradients, no backdrop-filter
     - small or zero corner radii — squares read as posters
   Default is the light variant (neubrutalism is a daylight aesthetic);
   a dark variant lives in the prefers-color-scheme block below with
   white shadows so the "sticker" effect survives on a dark canvas. */
:root[data-theme="bang"] {
    /* Space Grotesk is the closest free-stack approximation of the
       chunky display fonts (Archivo Black, Druk) that define the look.
       Falls through to Inter on machines that don't have it. */
    --font-sans: 'Space Grotesk', 'Archivo', 'Inter', system-ui,
                 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

    --bg-base: #fef6e4;       /* warm cream — the canonical neubrutalist canvas */
    --bg-canvas: #fff8ec;
    --glass: #ffffff;
    --glass-strong: #fef6e4;
    --glass-stronger: #fde74c; /* electric yellow as an emphasis surface */
    --glass-elev: #ffffff;

    /* Hairlines overloaded to solid black so every existing
       `border: 1px solid var(--hairline)` in the chrome reads as
       a chunky stroke. -strong bumps to 2px-equivalent presence by
       being fully opaque; selector tweaks below push the loudest
       elements to literal 2-3px borders. */
    --hairline: #000000;
    --hairline-soft: #000000;
    --hairline-strong: #000000;

    --text: #000000;
    --text-2: #1a1a1a;
    --text-3: #555555;

    --accent: #ff5470;         /* hot pink — primary action */
    --accent-2: #ff8fa3;
    --accent-soft: rgba(255, 84, 112, 0.18);
    --accent-glow: rgba(0, 0, 0, 0.20); /* "glow" repurposed as shadow tint */
    --accent-fg: #000000;       /* black-on-pink for that poster look */

    --danger: #ff3333;
    --danger-soft: rgba(255, 51, 51, 0.18);
    --success: #00c853;
    --success-soft: rgba(0, 200, 83, 0.18);
    --warning: #fde74c;

    /* Sharper geometry — neubrutalism leans rectangular. */
    --radius-xs: 0px;
    --radius-sm: 4px;
    --radius: 6px;
    --radius-lg: 10px;
    --radius-xl: 14px;

    /* Hard-offset shadows. No blur, single solid black layer —
       this is the move that makes the whole theme work. */
    --shadow-1: 3px 3px 0 #000000;
    --shadow-2: 5px 5px 0 #000000;
    --shadow-float: 6px 6px 0 #000000;
}
@media (prefers-color-scheme: dark) {
    :root[data-theme="bang"] {
        /* Dark Bang — keep the loud colours, swap the canvas to deep
           purple-black and flip shadows to white so the sticker effect
           survives. Hairlines stay solid white for the same reason. */
        --bg-base: #1a1625;
        --bg-canvas: #221d30;
        --glass: #2d2438;
        --glass-strong: #3a2e4a;
        --glass-stronger: #fde74c; /* yellow still pops on dark */
        --glass-elev: #2d2438;

        --hairline: #ffffff;
        --hairline-soft: #ffffff;
        --hairline-strong: #ffffff;

        --text: #ffffff;
        --text-2: #e5e5e5;
        --text-3: #b0b0b0;

        --accent: #ff5470;
        --accent-2: #ff8fa3;
        --accent-soft: rgba(255, 84, 112, 0.28);
        --accent-glow: rgba(255, 255, 255, 0.25);
        --accent-fg: #000000;

        --danger: #ff5555;
        --danger-soft: rgba(255, 85, 85, 0.22);
        --success: #00e676;
        --success-soft: rgba(0, 230, 118, 0.22);
        --warning: #fde74c;

        --shadow-1: 3px 3px 0 #ffffff;
        --shadow-2: 5px 5px 0 #ffffff;
        --shadow-float: 6px 6px 0 #ffffff;
    }
}
/* Bang: kill every backdrop blur — neubrutalism is the opposite of
   glassmorphism. Surfaces are flat fills, full stop. */
:root[data-theme="bang"] .main-content,
:root[data-theme="bang"] .sidebar,
:root[data-theme="bang"] .auth-card,
:root[data-theme="bang"] .panel-card,
:root[data-theme="bang"] .message-assistant .message-bubble,
:root[data-theme="bang"] .modal,
:root[data-theme="bang"] .menu,
:root[data-theme="bang"] .composer {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
/* Thicken the borders on the loudest chrome — token overrides only
   get us 1px; the neubrutalist look wants 2-3px on hero elements. */
:root[data-theme="bang"] .auth-card,
:root[data-theme="bang"] .panel-card,
:root[data-theme="bang"] .modal,
:root[data-theme="bang"] .menu {
    border-width: 3px;
}
:root[data-theme="bang"] .btn-primary,
:root[data-theme="bang"] .new-chat-btn,
:root[data-theme="bang"] .send-btn {
    background: var(--accent);
    color: var(--accent-fg);
    border: 2px solid #000000;
    box-shadow: var(--shadow-1);
    font-weight: 700;
}
:root[data-theme="bang"] .btn-primary:hover,
:root[data-theme="bang"] .new-chat-btn:hover,
:root[data-theme="bang"] .send-btn:hover {
    /* "Press into the shadow" — classic neubrutalist hover. The
       button moves down-right by the shadow's offset, the shadow
       collapses, so the button looks like it's been pushed. */
    transform: translate(3px, 3px);
    box-shadow: 0 0 0 #000000;
    filter: none;
}
@media (prefers-color-scheme: dark) {
    :root[data-theme="bang"] .btn-primary,
    :root[data-theme="bang"] .new-chat-btn,
    :root[data-theme="bang"] .send-btn {
        border-color: #ffffff;
    }
    :root[data-theme="bang"] .btn-primary:hover,
    :root[data-theme="bang"] .new-chat-btn:hover,
    :root[data-theme="bang"] .send-btn:hover {
        box-shadow: 0 0 0 #ffffff;
    }
}
/* Chat bubbles — flat, blocky, with a hard offset shadow so each
   message reads like a stuck-on sticker. Asymmetric corners are
   dropped (neubrutalism prefers rectangles) but we keep a small
   radius so they're not jarring against the surrounding chrome. */
:root[data-theme="bang"] .message-user .message-bubble {
    background: var(--accent);
    color: var(--accent-fg);
    border: 2px solid #000000;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-1);
    font-weight: 500;
}
:root[data-theme="bang"] .message-assistant .message-bubble {
    background: #ffffff;
    color: #000000;
    border: 2px solid #000000;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-1);
}
@media (prefers-color-scheme: dark) {
    :root[data-theme="bang"] .message-user .message-bubble,
    :root[data-theme="bang"] .message-assistant .message-bubble {
        border-color: #ffffff;
    }
    :root[data-theme="bang"] .message-assistant .message-bubble {
        background: var(--glass);
        color: var(--text);
    }
}
/* Brand mark, sidebar user avatar, and empty-state icon — these
   are gradient-filled in Default. Replace with flat yellow on
   black border for the poster effect. */
:root[data-theme="bang"] .sidebar-brand-mark,
:root[data-theme="bang"] .user-menu-btn .user-avatar,
:root[data-theme="bang"] .chat-empty .empty-icon {
    background: #fde74c;
    color: #000000;
    border: 2px solid #000000;
    box-shadow: var(--shadow-1);
}
@media (prefers-color-scheme: dark) {
    :root[data-theme="bang"] .sidebar-brand-mark,
    :root[data-theme="bang"] .user-menu-btn .user-avatar,
    :root[data-theme="bang"] .chat-empty .empty-icon {
        border-color: #ffffff;
    }
}

/* ----- Terminal (monospace CRT) -----
   Mimics a classic terminal emulator. The signature moves:
     - everything monospace (no proportional text anywhere)
     - off-white text on near-black canvas (dark = default)
     - zero corner radius — terminals are pure rectangles
     - no shadows, no glass blur, no gradients — all flat
     - hairlines as low-alpha white so panel boundaries read
       like ncurses box-drawing
   The light variant flips to "paper terminal" — bone-white
   background with near-black ink — for daylight use. */
:root[data-theme="terminal"] {
    /* ui-monospace lands on SF Mono / Menlo on Apple, Cascadia/Consolas
       on Windows, DejaVu on Linux. No external font load. */
    --font-sans: ui-monospace, 'SF Mono', Menlo, Monaco, Consolas,
                 'Cascadia Code', 'DejaVu Sans Mono', 'Liberation Mono',
                 monospace;
    --font-mono: ui-monospace, 'SF Mono', Menlo, Monaco, Consolas,
                 'Cascadia Code', 'DejaVu Sans Mono', 'Liberation Mono',
                 monospace;

    /* Near-black canvas — not pure #000 so OLED-style burn-in is
       avoided, and the small step between -base and -canvas keeps
       the chat scroll area visually distinct from the sidebar. */
    --bg-base: #0c0c0c;
    --bg-canvas: #111111;
    --glass: #161616;
    --glass-strong: #1c1c1c;
    --glass-stronger: #262626;
    --glass-elev: #161616;

    /* Hairlines as low-alpha white — panel borders read as
       ncurses-style box-drawing lines rather than UI chrome. */
    --hairline: rgba(255, 255, 255, 0.18);
    --hairline-soft: rgba(255, 255, 255, 0.08);
    --hairline-strong: rgba(255, 255, 255, 0.40);

    /* Soft off-white text — not pure #fff so long sessions don't
       burn. -2 / -3 step down to dim greys, mimicking dim ANSI
       rather than tinted hues. */
    --text: #e8e8e8;
    --text-2: #b4b4b4;
    --text-3: #7a7a7a;

    --accent: #e8e8e8;          /* off-white — same as text */
    --accent-2: #ffffff;
    --accent-soft: rgba(255, 255, 255, 0.10);
    --accent-glow: rgba(255, 255, 255, 0.25);
    --accent-fg: #0c0c0c;       /* black text on the accent fill */

    /* Semantic colours kept neutral-leaning. Pure greyscale would
       lose meaning on danger/success states; very-muted ANSI hints
       keep the B&W feel while preserving signal. */
    --danger: #d4d4d4;
    --danger-soft: rgba(255, 255, 255, 0.10);
    --success: #e8e8e8;
    --success-soft: rgba(255, 255, 255, 0.10);
    --warning: #d4d4d4;

    /* Pure rectangles. A 2px nod on -lg/-xl keeps focus rings and
       the composer from looking like they have raw 90-degree
       corners chewing into adjacent chrome. */
    --radius-xs: 0px;
    --radius-sm: 0px;
    --radius: 0px;
    --radius-lg: 2px;
    --radius-xl: 2px;

    /* No drop shadows — terminals are 2D. Keep the variable defined
       (some components read it directly) but as `none` so nothing
       lifts off the canvas. */
    --shadow-1: none;
    --shadow-2: none;
    --shadow-float: 0 0 0 1px rgba(255, 255, 255, 0.30); /* halo ring for floating menus */
}
@media (prefers-color-scheme: light) {
    :root[data-theme="terminal"] {
        /* Paper terminal — bone-white with near-black ink. Same
           flat geometry and monospace; only the temperature flips.
           The slight off-white -base (#f5f5f0) keeps it from
           looking like a stark word processor blank canvas. */
        --bg-base: #f5f5f0;
        --bg-canvas: #fafaf5;
        --glass: #ffffff;
        --glass-strong: #efefe8;
        --glass-stronger: #e4e4dc;
        --glass-elev: #ffffff;

        --hairline: rgba(0, 0, 0, 0.22);
        --hairline-soft: rgba(0, 0, 0, 0.10);
        --hairline-strong: rgba(0, 0, 0, 0.45);

        --text: #1a1a1a;
        --text-2: #4a4a4a;
        --text-3: #7a7a7a;

        --accent: #1a1a1a;
        --accent-2: #000000;
        --accent-soft: rgba(0, 0, 0, 0.08);
        --accent-glow: rgba(0, 0, 0, 0.20);
        --accent-fg: #fafaf5;

        --danger: #2a2a2a;
        --danger-soft: rgba(0, 0, 0, 0.06);
        --success: #1a1a1a;
        --success-soft: rgba(0, 0, 0, 0.06);
        --warning: #2a2a2a;

        --shadow-1: none;
        --shadow-2: none;
        --shadow-float: 0 0 0 1px rgba(0, 0, 0, 0.30);
    }
}
/* Terminal: kill backdrop blur everywhere — CRTs don't have
   frosted glass. Surfaces are flat greyscale fills. */
:root[data-theme="terminal"] .main-content,
:root[data-theme="terminal"] .sidebar,
:root[data-theme="terminal"] .auth-card,
:root[data-theme="terminal"] .panel-card,
:root[data-theme="terminal"] .message-assistant .message-bubble,
:root[data-theme="terminal"] .modal,
:root[data-theme="terminal"] .menu,
:root[data-theme="terminal"] .composer {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
/* Force monospace everywhere — including inputs and buttons that
   sometimes get their own font-family declarations in the base
   stylesheet. Without this the inputs render in Inter against a
   monospace shell, which breaks the illusion. */
:root[data-theme="terminal"],
:root[data-theme="terminal"] input,
:root[data-theme="terminal"] textarea,
:root[data-theme="terminal"] select,
:root[data-theme="terminal"] button {
    font-family: var(--font-mono);
}
/* Primary actions read as inverted prompt blocks — solid
   off-white fill, black text. No shadows, no hover brighten
   (terminals don't animate). A subtle inverse-on-hover reads
   like a focused cursor. */
:root[data-theme="terminal"] .btn-primary,
:root[data-theme="terminal"] .new-chat-btn,
:root[data-theme="terminal"] .send-btn {
    background: var(--accent);
    color: var(--accent-fg);
    border: 1px solid var(--accent);
    box-shadow: none;
    font-weight: 500;
    letter-spacing: 0.02em;
}
:root[data-theme="terminal"] .btn-primary:hover,
:root[data-theme="terminal"] .new-chat-btn:hover,
:root[data-theme="terminal"] .send-btn:hover {
    background: var(--accent-2);
    filter: none;
    transform: none;
}
/* Chat bubbles as terminal output blocks — flat fills, square
   corners, hairline borders. User turn inverts colours (filled
   off-white with black text — reads as a typed prompt). Assistant
   turn is plain text on canvas with a left-border indent so it
   reads like the program's stdout response. */
:root[data-theme="terminal"] .message-user .message-bubble {
    background: var(--accent);
    color: var(--accent-fg);
    border: 1px solid var(--accent);
    border-radius: var(--radius-lg);
    box-shadow: none;
}
:root[data-theme="terminal"] .message-assistant .message-bubble {
    background: transparent;
    color: var(--text);
    border: 1px solid var(--hairline);
    border-left: 2px solid var(--accent); /* "stdout" gutter */
    border-radius: var(--radius-lg);
    box-shadow: none;
}
/* Brand mark, user avatar, empty-state icon — flat greyscale
   tiles with hairline borders. No gradients, no shadows. */
:root[data-theme="terminal"] .sidebar-brand-mark,
:root[data-theme="terminal"] .user-menu-btn .user-avatar,
:root[data-theme="terminal"] .chat-empty .empty-icon {
    background: var(--accent);
    color: var(--accent-fg);
    border: 1px solid var(--accent);
    box-shadow: none;
}
/* Inline code + fenced code blocks already lean monospace, but
   the Default theme tints them with --glass-strong. In Terminal
   that reads as "code inside code"; bump the surrounding
   container to a slightly darker shade so fenced blocks remain
   visually distinct from the assistant turn around them. */
:root[data-theme="terminal"] pre,
:root[data-theme="terminal"] .code-block-wrap pre {
    background: var(--bg-base);
    border: 1px solid var(--hairline-soft);
}

/* ----- Clouse (Anthropic's Claude) -----
   Emulates the Claude app: warm paper/cream surfaces (never neutral
   grey), the clay / terracotta accent that is Anthropic's signature,
   and Claude's bubble-light chat layout — the assistant turn sits
   flat on the canvas with no bubble, only the user's turn gets a
   soft warm container. Dark is the base (warm charcoal); the light
   "paper" variant nests in the media query below. */
:root[data-theme="clouse"] {
    --font-sans: 'Inter', ui-sans-serif, -apple-system, BlinkMacSystemFont,
                 'Segoe UI', Roboto, system-ui, sans-serif;

    /* Claude dark — warm charcoal with a brown cast, not cool grey. */
    --bg-base: #1a1915;
    --bg-canvas: #262624;
    --glass: #2f2e2b;
    --glass-strong: #3a3833;
    --glass-stronger: #45433d;
    --glass-elev: #2f2e2b;
    --hairline: rgba(245, 244, 238, 0.10);
    --hairline-soft: rgba(245, 244, 238, 0.06);
    --hairline-strong: rgba(245, 244, 238, 0.18);

    --text: #f5f4ee;          /* warm Claude off-white */
    --text-2: rgba(245, 244, 238, 0.66);
    --text-3: rgba(245, 244, 238, 0.42);

    --accent: #d97757;        /* clay / terracotta — Anthropic's mark */
    --accent-2: #e0906f;
    --accent-soft: rgba(217, 119, 87, 0.20);
    --accent-glow: rgba(217, 119, 87, 0.28);
    --accent-fg: #ffffff;

    --danger: #e0685a;
    --danger-soft: rgba(224, 104, 90, 0.18);
    --success: #8aab7a;
    --success-soft: rgba(138, 171, 122, 0.18);
    --warning: #d6a960;

    /* Claude rounds gently — softer than Default's large radii. */
    --radius-xs: 6px;
    --radius-sm: 10px;
    --radius: 12px;
    --radius-lg: 16px;
    --radius-xl: 20px;

    --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.25);
    --shadow-2: 0 2px 8px rgba(0, 0, 0, 0.30);
    --shadow-float: 0 8px 28px rgba(0, 0, 0, 0.38);
}
@media (prefers-color-scheme: light) {
    :root[data-theme="clouse"] {
        /* Claude "paper" — warm cream, never pure white. */
        --bg-base: #f0eee6;
        --bg-canvas: #faf9f5;
        --glass: #faf9f5;
        --glass-strong: #f0ece2;
        --glass-stronger: #e8e3d7;
        --glass-elev: #ffffff;
        --hairline: rgba(60, 53, 40, 0.12);
        --hairline-soft: rgba(60, 53, 40, 0.07);
        --hairline-strong: rgba(60, 53, 40, 0.20);

        --text: #2a2722;          /* warm near-black */
        --text-2: rgba(42, 39, 34, 0.66);
        --text-3: rgba(42, 39, 34, 0.42);

        --accent: #c15f3c;        /* deeper clay for contrast on cream */
        --accent-2: #d97757;
        --accent-soft: rgba(193, 95, 60, 0.14);
        --accent-glow: rgba(193, 95, 60, 0.20);
        --accent-fg: #ffffff;

        --danger: #c0392b;
        --danger-soft: rgba(192, 57, 43, 0.12);
        --success: #5a7d4f;
        --success-soft: rgba(90, 125, 79, 0.14);
        --warning: #b07d2a;

        --shadow-1: 0 1px 2px rgba(60, 53, 40, 0.08), 0 6px 16px rgba(60, 53, 40, 0.08);
        --shadow-2: 0 2px 6px rgba(60, 53, 40, 0.08), 0 14px 34px rgba(60, 53, 40, 0.12);
        --shadow-float: 0 1px 1px rgba(60, 53, 40, 0.06), 0 12px 30px rgba(60, 53, 40, 0.14);
    }
}
/* Claude's surfaces are flat — no glassy backdrop blur. */
:root[data-theme="clouse"] .main-content,
:root[data-theme="clouse"] .sidebar,
:root[data-theme="clouse"] .auth-card,
:root[data-theme="clouse"] .panel-card,
:root[data-theme="clouse"] .composer,
:root[data-theme="clouse"] .message-assistant .message-bubble {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
/* Claude chat layout: the assistant turn has NO bubble — it sits
   directly on the canvas. Only the user's turn gets a soft warm
   container (a quiet grey, not the loud accent). */
:root[data-theme="clouse"] .message-assistant .message-bubble {
    background: transparent;
    border-color: transparent;
    box-shadow: none;
    color: var(--text);
    padding: 2px 0;
    max-width: 100%;
}
:root[data-theme="clouse"] .message-user .message-bubble {
    background: var(--glass-strong);
    border: 1px solid var(--hairline-soft);
    color: var(--text);
    border-radius: var(--radius-lg);
    box-shadow: none;
}
/* The clay accent is the single loudest thing in the Claude UI —
   reserve it for the primary CTAs and the round send button. */
:root[data-theme="clouse"] .btn-primary,
:root[data-theme="clouse"] .new-chat-btn,
:root[data-theme="clouse"] .send-btn {
    background: var(--accent);
    color: var(--accent-fg);
    box-shadow: none;
}
:root[data-theme="clouse"] .btn-primary:hover,
:root[data-theme="clouse"] .new-chat-btn:hover,
:root[data-theme="clouse"] .send-btn:hover {
    background: var(--accent-2);
    filter: none;
}
/* Brand mark, user avatar, empty-state icon: solid clay. The Default
   theme paints these with a mauve→pink gradient (a hardcoded #e94090
   in the brand mark) that would break the Claude palette entirely. */
:root[data-theme="clouse"] .sidebar-brand-mark,
:root[data-theme="clouse"] .user-menu-btn .user-avatar,
:root[data-theme="clouse"] .chat-empty .empty-icon {
    background: var(--accent);
    color: var(--accent-fg);
    box-shadow: none;
}

/* =========================================================
   Document Collaborator
   List page (#documents), the WYSIWYG editor (#document?id=N)
   with its chat sidebar, wiki autocomplete, export menu, chips.
   ========================================================= */

/* ---- List page ---- */
.empty-panel {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    text-align: center; padding: 60px 20px; color: var(--text-3); gap: 6px;
}
.empty-panel .empty-icon { width: 44px; height: 44px; opacity: .5; margin-bottom: 6px; }
.empty-panel h3 { color: var(--text); font-size: 17px; }
.doc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; padding: 16px; }
.doc-card {
    position: relative; display: flex; align-items: center; gap: 12px;
    padding: 14px; border-radius: var(--radius); border: 1px solid var(--hairline);
    background: var(--glass); cursor: pointer; transition: border-color .15s, transform .15s;
}
.doc-card:hover { border-color: var(--accent); transform: translateY(-1px); }
.doc-card-icon { width: 30px; height: 30px; color: var(--accent); flex: 0 0 auto; }
.doc-card-icon svg { width: 30px; height: 30px; }
.doc-card-body { min-width: 0; flex: 1; }
.doc-card-title { font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.doc-card-meta { font-size: 12px; color: var(--text-3); margin-top: 3px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.doc-badge { font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: 99px; background: var(--accent-soft, rgba(125,108,245,.18)); color: var(--accent); }
.doc-badge-ai { background: rgba(80,200,120,.18); color: #36b46a; }
.doc-card-del { opacity: 0; flex: 0 0 auto; }
.doc-card:hover .doc-card-del { opacity: .7; }
.doc-card-del:hover { opacity: 1; color: var(--danger, #e5484d); }

/* ---- Editor shell ----
   The editor is a full-bleed takeover. #app is normally a padded flex row
   (sidebar + main) with overflow:hidden — leaving the editor as a flex
   child means its default min-width:auto refuses to shrink below the Toast
   editor's intrinsic width, so it overflows and gets CLIPPED instead of
   reflowing (this is why it looked "not responsive"). `app-doc-mode`
   (added by documents.js, removed by renderChatShell) strips that chrome
   so the view fills #app and its own responsive rules can take over. */
#app.app-doc-mode { display: block; padding: 0; gap: 0; }
.doc-loading { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; gap: 10px; color: var(--text-3); }
.doc-editor-view { display: flex; flex-direction: column; width: 100%; height: 100%; min-width: 0; background: var(--bg-base); }
.doc-editor-header {
    display: flex; align-items: center; gap: 8px; padding: 8px 12px;
    border-bottom: 1px solid var(--hairline); background: var(--bg-elevated, var(--glass)); flex: 0 0 auto;
}
.doc-title-input {
    flex: 1; min-width: 60px; border: none; background: transparent; color: var(--text);
    font-size: 17px; font-weight: 600; padding: 6px 8px; border-radius: 8px; outline: none;
}
.doc-title-input:focus { background: var(--glass); }
.doc-header-tools { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.doc-mode-toggle { display: inline-flex; border: 1px solid var(--hairline); border-radius: 8px; overflow: hidden; }
.doc-mode-btn { border: none; background: transparent; color: var(--text-3); font-size: 12px; font-weight: 600; padding: 5px 10px; cursor: pointer; }
.doc-mode-btn.is-active { background: var(--accent); color: #fff; }
.doc-font-select { border: 1px solid var(--hairline); background: var(--glass); color: var(--text); border-radius: 8px; padding: 5px 8px; font-size: 12px; max-width: 150px; }
.doc-save-state { font-size: 11px; color: var(--text-3); min-width: 56px; text-align: right; }
.doc-save-error { color: var(--danger, #e5484d); }

.doc-editor-body { flex: 1; display: flex; min-height: 0; min-width: 0; }
.doc-editor-main { flex: 1; min-width: 0; overflow: auto; background: #fff; }
/* Toast UI is light by default; pin the writing surface to paper-white in
   every theme so a document reads like a document, not app chrome. */
.doc-editor-main .toastui-editor-defaultUI { border: none; height: 100%; }
.doc-editor-main .toastui-editor-contents { font-size: 16px; }

/* Compact the (otherwise chunky 45px / 32px-button / 25px-pad) Toast
   toolbar, and let it scroll horizontally instead of overflowing on
   narrow viewports — the default toolbar is wider than a phone screen. */
.doc-editor-main .toastui-editor-defaultUI-toolbar {
    height: auto; min-height: 38px; padding: 2px 6px;
    flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; scrollbar-width: thin;
}
.doc-editor-main .toastui-editor-toolbar { height: auto; }
.doc-editor-main .toastui-editor-defaultUI-toolbar button { height: 28px; width: 28px; margin: 4px 2px; }
.doc-editor-main .toastui-editor-toolbar-group { flex: 0 0 auto; }
/* Hide the thin group divider's big margins so groups pack tighter. */
.doc-editor-main .toastui-editor-toolbar-divider { margin: 8px 3px; }

.doc-chat-sidebar {
    flex: 0 0 340px; width: 340px; display: flex; flex-direction: column;
    border-left: 1px solid var(--hairline); background: var(--bg-base); min-height: 0;
}
.doc-chat-shell { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.doc-chat-head { padding: 8px 10px; border-bottom: 1px solid var(--hairline); display: flex; align-items: center; gap: 8px; }
.doc-chat-head .model-selector { flex: 1; min-width: 0; max-width: 100%; }
/* The close button only matters when the sidebar is an overlay (it covers
   the header toggle); on the desktop two-pane layout the sidebar is always
   present so there's nothing to close. Shown in the ≤1024px block below. */
.doc-chat-close { display: none; flex: 0 0 auto; margin-left: auto; }
.doc-chat-sidebar #chat-input::placeholder { font-size: 13px; opacity: .7; }
/* The main composer reserves ~156px of right padding for its absolutely-
   positioned icon cluster — fine at 820px, but in the narrow sidebar it
   squeezes the text into a sliver. Stack instead: full-width textarea on
   top, the tool icons in their own row beneath. */
.doc-chat-sidebar .chat-input-wrapper { display: flex; flex-direction: column; }
.doc-chat-sidebar #chat-input { padding: 10px 14px; min-height: 40px; font-size: 14px; }
.doc-chat-sidebar .composer-tools {
    position: static; right: auto; bottom: auto;
    padding: 0 6px 6px; gap: 4px; justify-content: flex-start;
}
.doc-chat-container { flex: 1; overflow-y: auto; padding: 12px; }
/* Beat the iOS-PWA inline override in index.php (#chat-input-area.chat-input-area,
   position:fixed) so the sidebar composer stays inside the pane on mobile —
   needs an id in the selector to out-specify it. */
.doc-chat-sidebar #chat-input-area.chat-input-area {
    position: static !important; margin: 0 !important;
    left: auto !important; right: auto !important; bottom: auto !important;
    border: none !important; border-top: 1px solid var(--hairline) !important;
    border-radius: 0 !important; box-shadow: none !important;
    padding: 8px !important; background: var(--bg-base) !important;
}
.doc-chat-toggle { display: none; }

/* ---- Wiki [[ ]] autocomplete ---- */
.wiki-menu {
    position: fixed; z-index: 9999; display: none; min-width: 220px; max-width: 320px;
    max-height: 260px; overflow-y: auto; background: var(--bg-elevated, var(--bg-base));
    border: 1px solid var(--hairline); border-radius: 10px; box-shadow: var(--shadow-float); padding: 4px;
}
.wiki-item { padding: 7px 10px; border-radius: 7px; cursor: pointer; font-size: 14px; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wiki-item.is-active, .wiki-item:hover { background: var(--accent); color: #fff; }
.wiki-menu-empty { padding: 8px 10px; font-size: 12.5px; color: var(--text-3); }
.toastui-editor-contents a[data-wikilink], .doc-editor-main a[href^="#document"] { color: var(--accent); }

/* ---- Export menu ---- */
.doc-export-menu {
    position: fixed; z-index: 9999; background: var(--bg-elevated, var(--bg-base));
    border: 1px solid var(--hairline); border-radius: 10px; box-shadow: var(--shadow-float); padding: 4px; min-width: 220px;
}
.doc-export-menu button {
    display: flex; align-items: center; gap: 10px; width: 100%; border: none; background: transparent;
    color: var(--text); font-size: 14px; padding: 9px 10px; border-radius: 7px; cursor: pointer; text-align: left;
}
.doc-export-menu button:hover { background: var(--glass); }
.doc-export-menu svg { width: 16px; height: 16px; opacity: .7; }

/* ---- Settings modal rows ---- */
.doc-setting-row { display: flex; gap: 10px; align-items: flex-start; padding: 10px 0; border-bottom: 1px solid var(--hairline-soft, var(--hairline)); cursor: pointer; }
.doc-setting-row input[type=checkbox] { margin-top: 3px; flex: 0 0 auto; }
.doc-setting-row small { color: var(--text-3); }

/* ---- Chat-side doc chips / edit notices ---- */
.doc-chip {
    display: inline-flex; align-items: center; gap: 6px; margin: 4px 6px 0 0; padding: 4px 10px;
    border-radius: 99px; font-size: 12px; text-decoration: none; border: 1px solid var(--hairline);
    background: var(--glass); color: var(--text);
}
.doc-chip svg { width: 13px; height: 13px; opacity: .7; }
.doc-chip:hover { border-color: var(--accent); color: var(--accent); }
.doc-chip-created { background: rgba(80,200,120,.14); }
.doc-edit-notice {
    display: flex; align-items: center; gap: 8px; margin-top: 6px; padding: 8px 10px;
    border-radius: 10px; background: var(--glass); border: 1px solid var(--hairline); font-size: 13px; color: var(--text);
}
.doc-edit-notice svg { width: 15px; height: 15px; opacity: .7; flex: 0 0 auto; }
.doc-edit-proposed { border-color: var(--accent); }
.doc-undo-btn { margin-left: auto; border: none; background: transparent; color: var(--accent); font-size: 12px; cursor: pointer; }
/* "Updating the document…" indicator shown while the AI streams a (hidden)
   edit block, so the long edit doesn't read as idle. */
.doc-edit-progress {
    display: flex; align-items: center; gap: 8px; margin-top: 6px;
    font-size: 13px; color: var(--text-3);
}
.doc-edit-progress .thinking-dots { display: inline-flex; }

/* ---- Narrow viewports: chat sidebar becomes a slide-over ----
   Below 1024px the two-pane layout gets cramped, so the editor takes the
   full width and the chat opens as an overlay via the header toggle. */
@media (max-width: 1024px) {
    .doc-chat-toggle { display: inline-flex; }
    .doc-chat-close { display: inline-flex; }
    .doc-chat-sidebar {
        position: fixed; top: 0; right: 0; bottom: 0; width: min(92vw, 380px); flex-basis: auto;
        transform: translateX(100%); transition: transform .25s ease; z-index: 60; box-shadow: var(--shadow-float);
    }
    .doc-editor-view.chat-open .doc-chat-sidebar { transform: translateX(0); }
}

/* ---- Phones / small windows: wrap the header, shrink controls ----
   The formatting tools (mode toggle, font, settings, export) wrap to a
   second row; the title, save state, and chat toggle stay on row one. */
@media (max-width: 680px) {
    .doc-editor-header { flex-wrap: wrap; gap: 6px; padding: 6px 8px; }
    .doc-editor-header > .icon-btn:first-child { order: 1; }
    .doc-title-input { order: 2; flex: 1 1 100px; font-size: 16px; padding: 4px 6px; }
    .doc-save-state { order: 3; min-width: 0; }
    .doc-chat-toggle { order: 4; }
    .doc-header-tools { order: 5; flex: 1 1 100%; gap: 6px; overflow-x: auto; padding-bottom: 2px; }
    .doc-mode-btn { padding: 4px 8px; font-size: 11px; }
    .doc-font-select { flex: 1 1 auto; min-width: 0; max-width: none; }
    /* Tighten the editor's own content gutters on small screens. */
    .doc-editor-main .toastui-editor-contents { font-size: 15px; padding: 12px 14px; }
    .doc-editor-main .toastui-editor-md-container .toastui-editor-md-preview,
    .doc-editor-main .toastui-editor-ww-container { padding: 0; }
}

/* ---- iPad: indent the header past the OS window controls ----
   The editor is full-bleed (app-doc-mode strips #app's --gutter inset), so
   on iPadOS windowed mode the back button + title sit flush under the
   top-left window controls. The main app and the Prompt Builder / Image
   Studio avoid this because they float inset by --gutter. Restore a leading
   inset here on iPad-class devices (any coarse pointer, tablet+ width),
   preferring the OS-reserved safe-area inset when it's reported. Desktop and
   phones are unaffected. */
@media (any-pointer: coarse) and (min-width: 768px) {
    .doc-editor-header { padding-left: max(calc(var(--gutter) + 16px), env(safe-area-inset-left, 0px)); }
}
