/* ============================================ */ /* Odysseus UI — Consolidated Stylesheet */ /* ============================================ */ /* ── Variables ── * * Theme-public variables (override via theme.js or custom themes): * Core: --bg, --fg, --panel, --border, --red * Syntax: --hl-keyword, --hl-string, --hl-comment, --hl-function, * --hl-number, --hl-builtin, --hl-variable, --hl-params, * --hl-bg, --hl-fg * Accents: --accent-primary, --accent-error (set by theme.js) * Semantic: --color-error, --color-success, --color-warning, --color-danger, * --color-accent, --color-muted, --color-muted-alt */ :root { /* Core palette */ --bg: #282c34; --fg: #9cdef2; --panel: #111; --border: #355a66; --red: #e06c75; /* Were `var(--green)` / `var(--warn)` — self-referential, so they resolved to invalid and every site fell back to its own literal (or, for sites with no fallback, painted as transparent/inherit). Anchor them to real hex so the token layer actually works. */ --green: #50fa7b; --warn: #f0ad4e; /* Syntax highlighting */ --hl-bg: #1e2228; --hl-fg: #9cdef2; --hl-keyword: #c678dd; --hl-string: #e5c07b; --hl-comment: #828997; --hl-function: #61afef; --hl-number: #d19a66; --hl-builtin: #56b6c2; --hl-variable: #abb2bf; --hl-params: #a8c0d4; /* Semantic colors */ --color-error: #ff4444; --color-error-light: #ff6666; --color-success: #4caf50; --color-warning: #f0ad4e; --color-danger: #c0392b; --color-recording: #ff3b30; --color-recording-hover: #d63031; --color-muted: #888; --color-muted-alt: #6b7280; --color-accent: #00aaff; --color-agent-active: #00ff00; --color-brand-blue: #3b82f6; --color-blind-orange: #ff9800; --color-save-green: var(--color-success); --color-link-hover: #66c7ff; --color-subheader: #6b8a94; --select-bg: var(--bg); --select-fg: var(--fg); --select-option-bg: color-mix(in srgb, var(--panel) 74%, var(--bg)); --select-option-fg: var(--fg); --select-option-active-bg: color-mix(in srgb, var(--accent, var(--red)) 24%, var(--panel)); /* Warm accent — used by the Goals/Today UI in Notes. Lives as a token so themes can override without touching the goal CSS. */ --accent-warm: #d19a66; } :root.light { --bg: #f5f5f5; --fg: #2b2b2b; --panel: #fff; --border: #bbb; --hl-bg: #f9f9f9; --hl-fg: #2b2b2b; --hl-keyword: #7928a1; --hl-string: #986801; --hl-comment: #6a737d; --hl-function: #005cc5; --hl-number: #986801; --hl-builtin: #0070a0; --hl-variable: #383a42; --hl-params: #4a4f5c; --select-bg: #eaeaea; --select-fg: var(--fg); --select-option-bg: var(--panel); --select-option-fg: var(--fg); --select-option-active-bg: color-mix(in srgb, var(--red) 16%, var(--panel)); } /* ── Reset & Base ── */ * { box-sizing: border-box; } html, body { overflow-x: hidden; height: 100%; margin: 0; overscroll-behavior: none; } body { background-color: var(--bg); color: var(--fg); /* Animate the dock push BOTH ways. Keeping the transition on the base body (not on .right/left-dock-active) means removing the class on undock also animates padding back to 0 — otherwise the chat snapped back instantly. */ transition: padding-left 160ms cubic-bezier(0.22, 0.61, 0.36, 1), padding-right 160ms cubic-bezier(0.22, 0.61, 0.36, 1); font-family: var(--font-family, 'Fira Code', monospace); display: flex; height: 100%; height: 100dvh; /* dynamic viewport height — adapts when mobile keyboard opens */ overflow: hidden; } /* Self-hosted Fira Code font */ @font-face { font-family: 'Fira Code'; font-weight: 300; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-Light.woff2') format('woff2'); } @font-face { font-family: 'Fira Code'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-Regular.woff2') format('woff2'); } @font-face { font-family: 'Fira Code'; font-weight: 600; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-SemiBold.woff2') format('woff2'); } /* Code block baseline */ pre, code, .hljs { font-size: 0.95em; line-height: 1.5; } /* Scrollbar styling */ @supports selector(::-webkit-scrollbar) { ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-track { background: var(--panel); } ::-webkit-scrollbar-thumb { background-color: var(--red); border-radius: 4px; border: 2px solid var(--panel); } ::-webkit-scrollbar-thumb:hover { background-color: color-mix(in srgb, var(--red) 80%, white); } } html { scrollbar-color: var(--red) var(--panel); scrollbar-width: thin; } /* Utility */ .red-text { color: var(--red); } /* ── Density Overrides ── */ :root.density-compact { font-size: 13px; } :root.density-compact .msg { padding: 6px 10px; margin-bottom: 4px; } :root.density-compact .list-item { padding: 4px 8px; } :root.density-compact .sidebar .section { padding: 0; } :root.density-spacious { font-size: 16px; } :root.density-spacious .msg { padding: 14px 18px; margin-bottom: 12px; } :root.density-spacious .list-item { padding: 8px 12px; } :root.density-spacious .sidebar .section { padding: 0; } /* ── Background Patterns ── */ :root { --bg-effect-intensity: 1; } /* Canvas-based effects — single source of truth for intensity */ #synapse-canvas, #rain-canvas, #constellations-canvas, #perlin-flow-canvas, #petals-canvas, #sparkles-canvas, #embers-canvas { opacity: var(--bg-effect-intensity, 1); } body.bg-pattern-dots { background-image: radial-gradient(color-mix(in srgb, var(--bg-effect-color, var(--fg)) calc(5% * var(--bg-effect-intensity, 1)), transparent) 1px, transparent 1px); background-size: 20px 20px; background-attachment: fixed; } body.bg-pattern-synapse { /* CSS grid as base, canvas pulses overlay */ background-image: linear-gradient(color-mix(in srgb, var(--bg-effect-color, var(--fg)) calc(3.5% * var(--bg-effect-intensity, 1)), transparent) 1px, transparent 1px), linear-gradient(90deg, color-mix(in srgb, var(--bg-effect-color, var(--fg)) calc(3.5% * var(--bg-effect-intensity, 1)), transparent) 1px, transparent 1px); background-size: 24px 24px; background-attachment: fixed; } body.bg-pattern-perlin-flow, body.bg-pattern-petals, body.bg-pattern-sparkles { /* canvas-only backgrounds */ } /* ── Layout ── */ /* Top bar — session meta + incognito, single row */ .chat-top-bar { display: flex; align-items: center; justify-content: center; flex-shrink: 0; position: relative; z-index: 2; padding: 5px 0 0; min-height: 25px; box-sizing: border-box; } .chat-top-bar::after { content: none; } body.sidebar-collapsed.hamburger-left .chat-top-bar { padding-left: 38px; } body.sidebar-collapsed.hamburger-right .chat-top-bar { padding-right: 38px; } .incognito-indicator { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); background: color-mix(in srgb, var(--accent) 12%, transparent); border: 1px solid var(--accent); color: var(--accent); cursor: pointer; padding: 4px; border-radius: 6px; display: flex; align-items: center; justify-content: center; transition: opacity 0.15s, background 0.15s, left 0.15s; z-index: 1; opacity: 0.7; } body.sidebar-collapsed.hamburger-left .incognito-indicator { left: 12px; } .incognito-indicator:hover { opacity: 1; background: color-mix(in srgb, var(--accent) 20%, transparent); } .chat-new-btn { position: absolute; right: 7px; top: 50%; transform: translateY(-50%); background: none; border: none; color: var(--fg); opacity: 0.6; cursor: pointer; padding: 4px; border-radius: 6px; display: flex; align-items: center; justify-content: center; transition: opacity 0.08s, background 0.08s, left 0.15s, right 0.15s; flex-shrink: 0; z-index: 1; } .chat-new-btn:hover { opacity: 1; color: var(--accent); } /* Flip new-chat and incognito when sidebar is on the right */ body:has(.sidebar.right-side) .chat-new-btn { right: auto; left: 12px; } body:has(.sidebar.right-side) .incognito-indicator { left: auto; right: 12px; } body.sidebar-collapsed.hamburger-right .incognito-indicator { right: 42px; } /* Session meta — sits at top of chat area, scrolls with content */ .chat-meta-overlay { position: absolute; left: 0; right: 0; top: 50%; transform: translateY(calc(-50% - 2px)); font-size: 0.75em; line-height: 1; /* 70% mix keeps the chat title clearly above the WCAG AA 4.5:1 contrast threshold (40% only reached ~2.8:1). */ color: color-mix(in srgb, var(--fg) 70%, transparent); white-space: nowrap; display: flex; align-items: center; justify-content: center; gap: 2px; pointer-events: none; } .chat-meta-overlay > * { pointer-events: auto; } .chat-meta-overlay #current-meta { overflow: hidden; text-overflow: ellipsis; cursor: pointer; } .chat-meta-overlay:hover { color: color-mix(in srgb, var(--fg) 75%, transparent); } /* Offline model rows */ .models-row-offline { opacity: 0.4; pointer-events: none; } .models-row-offline .model-chat-btn { pointer-events: auto; } /* Offline badge next to endpoint name */ .endpoint-offline-badge { color: var(--danger, #f44); font-size: 0.8em; margin-left: 4px; opacity: 0.7; } .chat-meta-overlay:empty, .chat-meta-overlay:not(:has(#current-meta:not(:empty))) { display: none; } .export-dropdown-wrap { position: relative; display: inline-flex; flex-shrink: 0; margin-left: -4px; margin-right: -20px; } .export-dl-btn { background: none; border: none; color: inherit; cursor: pointer; padding: 4px 5px; border-radius: 4px; display: flex; align-items: center; transition: color 0.08s, background 0.08s; } .export-dl-btn:hover { color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); } .export-dropdown-menu { display: none; position: fixed; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 4px; min-width: 120px; box-shadow: 0 4px 16px rgba(0,0,0,0.4); z-index: 300; color: var(--fg); } .export-dropdown-menu.open { display: block; } .export-dropdown-item { padding: 6px 8px; font-size: 11px; cursor: pointer; border-radius: 6px; color: var(--fg); white-space: nowrap; display: flex; align-items: center; gap: 10px; transition: background 0.1s; } .export-dropdown-item:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); } .export-dropdown-item .dropdown-icon { width: 14px; height: 14px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; opacity: 0.5; } /* On mobile the chat-header export dropdown was cramped — items were 11px font with 6px padding. Bump to readable touch targets: larger min-width, taller rows, bigger icons + text. */ @media (max-width: 768px) { .export-dropdown-menu { min-width: 200px; padding: 6px; border-radius: 10px; } .export-dropdown-item { padding: 12px 14px; font-size: 14px; gap: 12px; min-height: 44px; } .export-dropdown-item .dropdown-icon { width: 18px; height: 18px; opacity: 0.7; } .export-dropdown-item .dropdown-icon svg { width: 18px; height: 18px; } } .sidebar { width: 240px; background: var(--sidebar-bg, var(--panel)); border-right: 1px solid var(--border); transition: width 0.25s ease, opacity 0.2s ease, padding 0.25s ease; display: flex; flex-direction: column; flex-shrink: 0; overflow: hidden; min-height: 0; margin: 0; padding: 0; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); backdrop-filter: blur(10px); border: 1px solid color-mix(in srgb, var(--fg) 11%, transparent); position: relative; } .sidebar-resize-handle { position: absolute; top: 0; right: -3px; width: 6px; height: 100%; cursor: col-resize; z-index: 10; background: transparent; } .sidebar-resize-handle:hover, .sidebar-resize-handle.dragging { background: var(--accent); opacity: 0.6; } .sidebar.right-side .sidebar-resize-handle { right: auto; left: -3px; } .sidebar.resizing { transition: none; user-select: none; } .sidebar.right-side { order: 2; margin: 0; border-right: none; border-left: 1px solid var(--border); } .sidebar.hidden { /* !important so it beats the inline width init.js restores from storage — otherwise the width never changes and only opacity animates, making the collapse look instant. With this, width animates from the inline value down to 0 via the .sidebar width transition. */ width: 0 !important; padding: 0 !important; border: none; overflow: hidden; opacity: 0; } /* ===== Sidebar User Bar ===== */ .sidebar-user-bar { display: flex; align-items: center; justify-content: space-between; padding: 12px 12px; flex-shrink: 0; gap: 4px; min-height: 48px; } .user-bar-left { display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0; cursor: pointer; padding: 6px 8px; border-radius: 8px; transition: background 0.15s; } .user-bar-left:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); } .user-bar-avatar { width: 24px; height: 24px; border-radius: 50%; background: color-mix(in srgb, var(--fg) 12%, transparent); display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 600; color: var(--fg); opacity: 0.7; flex-shrink: 0; text-transform: uppercase; } .user-bar-name { font-size: 9.75px; font-weight: 500; color: var(--fg); opacity: 0.8; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .user-bar-actions { display: flex; gap: 1px; flex-shrink: 0; } .user-bar-btn { background: none; border: none; color: var(--fg); opacity: 0.35; cursor: pointer; padding: 6px; border-radius: 6px; display: flex; align-items: center; justify-content: center; transition: opacity 0.12s, background 0.12s; } .user-bar-btn:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 8%, transparent); } .sidebar.hidden .sidebar-user-bar { display: none; } /* Sticky sidebar header — logo, never scrolls */ .sidebar-header { display: flex; align-items: center; justify-content: flex-end; /* right-align when sidebar is on left */ gap: 8px; padding: 15px 10px 0 40px; /* top padding aligns logo with fixed hamburger */ flex-shrink: 0; min-height: 40px; border: none !important; box-shadow: none !important; position: relative; z-index: 3; background: var(--sidebar-bg, var(--panel)); } .sidebar-hamburger { display: none !important; /* external #hamburger-btn is the only toggle */ } .sidebar-inner { flex: 1; overflow-y: auto; overflow-x: hidden; overscroll-behavior-y: none; scrollbar-width: none; display: flex; flex-direction: column; gap: 0; padding: 10px 8px 8px; min-height: 0; transition: transform 0.3s cubic-bezier(0.25, 1, 0.5, 1); } .sidebar-brand { display: flex; align-items: center; flex-shrink: 0; min-height: 24px; } .sidebar-brand-title { font-size: 1rem; font-weight: 600; line-height: 1.35; color: var(--brand-color, var(--red)); white-space: nowrap; user-select: none; position: relative; top: 0; left: -10px; } .sidebar-sep { display: none; } #sidebar-search-btn, #sidebar-new-chat-btn { margin: 0; padding: 8px 8px; } #sessions-section { margin-top: -1px; } #tools-section { margin-top: 1px; } #tools-section .list-item { padding: 8px 8px; } .sidebar.right-side .sidebar-header { justify-content: flex-start; padding-left: 10px; padding-right: 40px; } .sidebar.right-side .sidebar-inner { padding: 8px; } .sidebar.right-side .sidebar-brand { justify-content: flex-start; padding: 2px 30px 4px 4px; } .sidebar.right-side .sidebar-brand-title { margin-left: 10px; } /* Fixed hamburger — always visible, toggles sidebar */ .hamburger-btn { position: fixed; top: 12px; left: 9px; right: auto; z-index: 210; width: 30px; height: 30px; align-items: center; justify-content: center; background: none; border: none; color: var(--hamburger-color, var(--fg)); cursor: pointer; opacity: 0.5; -webkit-tap-highlight-color: transparent; outline: none; transition: opacity 0.15s; padding: 0; display: flex; } body.hamburger-right .hamburger-btn { left: auto; right: 9px; } .mobile-new-chat-btn { display: none; } .hamburger-btn:hover { opacity: 1; } /* Icon rail — mini sidebar that replaces the wide .sidebar when it's hidden (mutually exclusive — see sidebar-layout.js:57). Fullscreen panels reserve this strip of width via `left: var(--icon-rail-w)` so the rail stays visible without needing a z-index hack (which used to cover the fixed-position hamburger button). */ .icon-rail { width: 48px; flex-shrink: 0; background: var(--panel); border-right: 1px solid var(--border); display: flex; flex-direction: column; align-items: center; padding: 48px 4px 8px 4px; gap: 4px; margin: 0; position: relative; box-sizing: border-box; /* Allow hover labels (e.g. the rail-new-chat "New" tooltip) to extend outside the 48px column. overflow:hidden was clipping them. */ overflow: visible; } .rail-resize-handle { position: absolute; top: 0; right: -3px; width: 6px; height: 100%; cursor: col-resize; z-index: 10; background: transparent; } .rail-resize-handle:hover, .rail-resize-handle.dragging { background: var(--accent); opacity: 0.6; } .icon-rail.right-side .rail-resize-handle { right: auto; left: -3px; } .icon-rail.right-side { order: 2; margin: 0; border-right: none; border-left: 1px solid var(--border); } .icon-rail-divider { width: 24px; height: 1px; background: var(--border); margin: 4px 0; } .icon-rail-btn { position: relative; width: 34px; height: 34px; border: none; background: transparent; color: var(--accent, var(--red)); font-size: 16px; border-radius: 6px; cursor: pointer; display: flex; align-items: center; justify-content: center; opacity: 0.5; transition: opacity 0.08s, background 0.08s; } .rail-notes-badge { position: absolute; top: 1px; right: 1px; min-width: 14px; height: 14px; padding: 0 3px; border-radius: 7px; background: color-mix(in srgb, var(--accent) 85%, var(--bg)); color: var(--bg); font-size: 9px; font-weight: 700; line-height: 14px; text-align: center; box-sizing: border-box; pointer-events: none; box-shadow: 0 0 0 1px var(--bg); } .rail-notes-badge.fired { background: var(--red); animation: rail-notes-pulse 1.6s ease-in-out infinite; } @keyframes rail-notes-pulse { 0%, 100% { box-shadow: 0 0 0 1px var(--bg), 0 0 0 0 color-mix(in srgb, var(--red) 50%, transparent); } 50% { box-shadow: 0 0 0 1px var(--bg), 0 0 0 4px color-mix(in srgb, var(--red) 15%, transparent); } } /* Main sidebar notes button — dot when a reminder has fired */ .tool-notes-dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--red); /* Match the Deep Research badge: right-aligned (auto) with the same 4px left nudge, so both sidebar buttons' dots line up identically. */ margin-left: auto; position: relative; left: -4px; flex-shrink: 0; align-self: center; animation: rail-notes-pulse 1.6s ease-in-out infinite; pointer-events: none; } /* Individual card — subtle accent border tint when a reminder has fired */ .note-card.note-card-reminder-due .note-card-reminder { background: color-mix(in srgb, var(--red) 22%, transparent); color: var(--red); font-weight: 600; } .icon-rail-btn:hover { opacity: 1; background: color-mix(in srgb, var(--accent) 12%, transparent); } .icon-rail-btn.active-section { opacity: 1; background: color-mix(in srgb, var(--color-accent) 15%, transparent); } /* Unified "minimized" indicator for any rail/sidebar button whose modal is held open */ .rail-minimized { position: relative; } .rail-minimized::after { content: ''; position: absolute; /* Default for inline spans like #email-section-title — sit just to the right of the element so it never overlaps the icon. */ top: 50%; right: -10px; transform: translateY(-50%); width: 6px; height: 6px; border-radius: 50%; background: var(--accent, var(--red)); box-shadow: 0 0 0 2px var(--bg); pointer-events: none; animation: rail-min-pulse 2s ease-in-out infinite; } /* Compact icon-rail buttons: top-right corner of the icon */ .icon-rail-btn.rail-minimized::after { top: 4px; right: 4px; transform: none; } /* Sidebar list-items are wider — anchor right-aligned vertically centered */ .list-item.rail-minimized::after { top: 50%; right: 8px; transform: translateY(-50%); } #tool-memory-btn.rail-minimized::after { right: 12px; } /* Per-user nudge: the listed tools' minimized dot sits 2px further in from the right edge so it doesn't look glued to the border. */ #tool-theme-btn.rail-minimized::after, #tool-tasks-btn.rail-minimized::after, #tool-notes-btn.rail-minimized::after, #tool-library-btn.rail-minimized::after, #tool-gallery-btn.rail-minimized::after, #tool-compare-btn.rail-minimized::after, #tool-calendar-btn.rail-minimized::after { right: 12px; } /* Cookbook already shows its own running/served-status dot (#cookbook-notif-dot, toggled with .cookbook-notif-active on the button). Don't stack the tabbed-down pulse on top of it — the two dots overlap. Suppress the minimized dot while the status dot is up. */ .list-item.rail-minimized.cookbook-notif-active::after { display: none; } @media (max-width: 768px) { /* On mobile the list-items are taller (touch-sized), so the tabbed-down pulsing dot reads a hair low and right. Email uses its own dot and is already aligned — only the tool list-item dots need the nudge: 4px left (right 8 → 12) and 2px up. */ .list-item.rail-minimized::after { right: 13px; transform: translateY(calc(-50% - 2px)); } #tool-memory-btn.rail-minimized::after { right: 17px; } } @keyframes rail-min-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } } /* Compact `_` minimize button for modal headers — matches the close button's bordered square so the two read as a paired control. The header's h4 carries margin-right:auto, which groups minimize + close on the right; this button just needs a small gap before close. */ .modal-minimize-btn { background: var(--bg); color: var(--fg); border: 1px solid var(--fg); cursor: pointer; width: 24px; height: 24px; padding: 0; margin-left: 0; margin-right: 4px; line-height: 1; display: inline-flex; align-items: center; justify-content: center; border-radius: 4px; flex-shrink: 0; transition: background 0.15s, color 0.15s; } .modal-minimize-btn:hover { background: var(--fg); color: var(--bg); } .modal.modal-minimized { display: none !important; } /* Window tile snap ghost (desktop only) */ #tile-ghost { position: fixed; pointer-events: none; z-index: 9000; background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); border: 2px solid color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); border-radius: 8px; opacity: 0; transform: scale(0.96); transition: left 0.12s ease, top 0.12s ease, width 0.12s ease, height 0.12s ease, opacity 0.12s, transform 0.12s; } #tile-ghost.visible { opacity: 1; transform: scale(1); } /* Bottom dock — chip per minimized modal */ #minimized-dock { position: fixed; bottom: var(--composer-clearance, 12px); left: 50%; transform: translateX(-50%); display: flex; gap: 6px; flex-wrap: wrap; max-width: calc(100vw - 24px); padding: 4px; z-index: 10020; pointer-events: none; } .minimized-dock-chip { pointer-events: auto; display: inline-flex; align-items: center; gap: 6px; padding: 6px 8px 6px 10px; background: var(--panel, var(--bg)); border: 1px solid var(--border); border-radius: 999px; color: var(--fg); font-family: inherit; font-size: 12px; cursor: grab; touch-action: none; user-select: none; box-shadow: 0 4px 14px rgba(0,0,0,0.35); transition: transform 0.28s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.15s, border-color 0.15s; animation: dock-chip-in 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) both; } .minimized-dock-chip:hover { background: color-mix(in srgb, var(--accent, var(--red)) 12%, var(--panel, var(--bg))); border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, var(--border)); } .minimized-dock-chip:active { cursor: grabbing; } .minimized-dock-chip.dragging { cursor: grabbing; z-index: 1000; box-shadow: 0 8px 24px rgba(0,0,0,0.55); transition: none; opacity: 0.95; } /* Whole-dock drag (grabbed an edge chip) */ #minimized-dock.dock-dragging { cursor: grabbing; } #minimized-dock.dock-dragging .minimized-dock-chip { transition: none; box-shadow: 0 8px 24px rgba(0,0,0,0.45); } /* Subtle visual cue that edge chips drag the whole dock */ #minimized-dock .minimized-dock-chip:first-child:not(:only-child), #minimized-dock .minimized-dock-chip:last-child:not(:only-child) { box-shadow: 0 4px 14px rgba(0,0,0,0.35), inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); } .minimized-dock-chip svg { opacity: 0.7; flex-shrink: 0; } .minimized-dock-label { white-space: nowrap; } /* Mobile: chips are icon-only round pills. Tap to restore, drag toward the trash zone at top-center to close. touch-action:none lets the pointermove listener claim the gesture instead of the page scroll. */ @media (max-width: 768px) { .minimized-dock-chip { width: 40px; height: 40px; padding: 0 !important; border-radius: 50% !important; justify-content: center; position: relative; overflow: visible; touch-action: none; } .minimized-dock-chip svg { width: 18px; height: 18px; opacity: 0.9; } .minimized-dock-label, .minimized-dock-x { display: none !important; } .minimized-dock-chip.chip-free-drag { box-shadow: 0 8px 22px rgba(0,0,0,0.55), 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 50%, transparent) !important; } /* Long-press hint — chip swells and settles while the detach timer counts down so the user feels feedback before the bubble peels out of the chain. Returns to scale(1) at the end so there's no visual jump when the timer fires and the chip becomes a free-drag puck. */ .minimized-dock-chip.chip-long-press { background: radial-gradient(circle at 30% 25%, color-mix(in srgb, #fff 28%, transparent), transparent 36%), linear-gradient(135deg, color-mix(in srgb, var(--accent, var(--red)) 34%, var(--panel, var(--bg))), color-mix(in srgb, #7dd3fc 26%, var(--panel, var(--bg))) 52%, color-mix(in srgb, #f0abfc 22%, var(--panel, var(--bg)))); border-color: color-mix(in srgb, var(--accent, var(--red)) 72%, #fff 12%) !important; animation: chip-long-press-pulse 0.82s ease-in-out infinite; z-index: 10030; } .minimized-dock-chip.chip-long-press::before { content: ''; position: absolute; inset: -96px; border-radius: inherit; background: radial-gradient(circle, color-mix(in srgb, var(--accent, var(--red)) 42%, transparent) 0 18%, color-mix(in srgb, #7dd3fc 34%, transparent) 34%, color-mix(in srgb, #f0abfc 30%, transparent) 50%, transparent 72%); pointer-events: none; z-index: -1; animation: chip-long-press-ripple 0.82s ease-out infinite; } @keyframes chip-long-press-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.42); box-shadow: 0 14px 42px rgba(0,0,0,0.66), 0 0 0 16px color-mix(in srgb, var(--accent, var(--red)) 42%, transparent), 0 0 72px color-mix(in srgb, #7dd3fc 44%, transparent), 0 0 118px color-mix(in srgb, #f0abfc 32%, transparent); } } @keyframes chip-long-press-ripple { 0% { opacity: 0.92; transform: scale(0.08); } 72% { opacity: 0.28; transform: scale(3.6); } 100% { opacity: 0; transform: scale(5.4); } } /* Chip whose modal is currently open — accent ring so the user can tell at a glance which floating bubble belongs to the visible modal. Tap it to minimize. */ .minimized-dock-chip.chip-active { border-color: var(--accent, var(--red)) !important; box-shadow: 0 4px 14px rgba(0,0,0,0.35), 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); } .minimized-dock-chip.chip-active svg { opacity: 1; } } /* Magnetic close zone — slides in from the side opposite the chip's starting position so it never overlaps the dock. Always accent color, larger than the chips, snappy spring transition. */ #dock-trash-zone { position: fixed; left: 50%; width: 88px; height: 88px; border-radius: 50%; background: var(--accent, var(--red, #e53935)); color: #fff; display: flex; align-items: center; justify-content: center; pointer-events: none; opacity: 0; z-index: 9000; box-shadow: 0 8px 28px color-mix(in srgb, var(--accent, var(--red, #e53935)) 55%, transparent); transition: opacity 0.18s ease, transform 0.26s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.18s ease; } /* Off-screen start position depends on which side the chip is on so the X slides in from the opposite edge with a snappy overshoot. */ #dock-trash-zone[data-side="top"] { transform: translateX(-50%) translateY(-180%) scale(0.7); } #dock-trash-zone[data-side="bottom"] { transform: translateX(-50%) translateY(180%) scale(0.7); } #dock-trash-zone.visible { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); } #dock-trash-zone.engaged { transform: translateX(-50%) translateY(0) scale(1.22); box-shadow: 0 0 0 14px color-mix(in srgb, var(--accent, var(--red, #e53935)) 22%, transparent), 0 12px 36px color-mix(in srgb, var(--accent, var(--red, #e53935)) 60%, transparent); } /* Whirlpool ring — fades in when chip is in capture range and spins continuously, then bursts on drop. */ #dock-trash-zone .whirlpool { position: absolute; inset: -8px; border-radius: 50%; border: 2px solid transparent; border-top-color: rgba(255,255,255,0.9); border-right-color: rgba(255,255,255,0.55); border-bottom-color: rgba(255,255,255,0.25); opacity: 0; pointer-events: none; animation: whirlpool-spin 0.85s linear infinite; transition: opacity 0.15s ease; } #dock-trash-zone.engaged .whirlpool { opacity: 1; } #dock-trash-zone.dropping .whirlpool { opacity: 1; animation: whirlpool-burst 0.36s cubic-bezier(0.4, 0, 0.2, 1) forwards; } @keyframes whirlpool-spin { to { transform: rotate(360deg); } } @keyframes whirlpool-burst { 0% { transform: rotate(0deg) scale(1); opacity: 1; } 60% { transform: rotate(540deg) scale(1.5); opacity: 0.6; } 100% { transform: rotate(900deg) scale(2.2); opacity: 0; } } /* Email chip badges: - email-lib-modal chip: "1" badge when an email is expanded inside (JS sets data-has-expanded). - email-reader-* chips: auto-numbered 1, 2, 3 … via CSS counter, so multiple opened-email windows are visually distinguishable. */ .minimized-dock-chip[data-modal-id="email-lib-modal"], .minimized-dock-chip[data-modal-id^="email-reader-"] { position: relative; } .minimized-dock-chip[data-modal-id^="email-reader-"][data-tab-num]::after { content: attr(data-tab-num); position: absolute; top: -4px; right: -4px; min-width: 16px; height: 16px; padding: 0 4px; background: var(--accent, var(--red)); color: #fff; font-size: 9px; font-weight: 700; line-height: 16px; text-align: center; border-radius: 8px; box-shadow: 0 0 0 2px var(--bg); pointer-events: none; } .minimized-dock-chip[data-modal-id="email-lib-modal"][data-email-unread-label]::after { content: attr(data-email-unread-label); position: absolute; top: -6px; right: 10px; height: 16px; padding: 0 6px; background: var(--accent, var(--red)); color: #fff; font-size: 9px; font-weight: 700; line-height: 16px; white-space: nowrap; border-radius: 8px; box-shadow: 0 0 0 2px var(--bg); pointer-events: none; } .minimized-dock-x { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 50%; font-size: 14px; line-height: 1; opacity: 0.4; margin-left: 3px; } .minimized-dock-x:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 12%, transparent); } @keyframes dock-chip-in { from { opacity: 0; transform: translateY(20px) scale(0.85); } to { opacity: 1; transform: translateY(0) scale(1); } } .rail-new-chat svg { transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1); } .rail-new-chat:hover svg { transform: rotate(90deg); } /* A "New" label slides out from the right of the + as it spins, so users discover what the icon does without needing the tooltip. */ .rail-new-chat { position: relative; } .rail-new-chat::after { content: 'New'; position: absolute; left: 100%; top: 50%; margin-left: 6px; transform: translateY(-50%) translateX(-6px); opacity: 0; pointer-events: none; white-space: nowrap; font-size: 11px; font-weight: 600; color: var(--fg); background: var(--panel); padding: 2px 6px; border-radius: 4px; box-shadow: 0 2px 6px rgba(0,0,0,0.25); z-index: 20; transition: opacity 0.25s ease, transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1); } .rail-new-chat:hover::after { opacity: 1; transform: translateY(-50%) translateX(0); } #rail-admin, #rail-settings { color: var(--fg); } .rail-separator { width: 20px; height: 1px; background: var(--border); margin: 4px auto; } .rail-dynamic { position: relative; } .rail-dynamic::after { content: ''; position: absolute; bottom: 2px; left: 50%; transform: translateX(-50%); width: 4px; height: 4px; border-radius: 50%; background: var(--accent, var(--red)); opacity: 0.7; } /* ── Sidebar sections (flat layout) ── */ .section { padding: 0; border: none; background: none; border-radius: 0; box-shadow: none; margin: 0; } /* Section header row — identical sizing to .list-item */ .section-header-flex { display: flex; align-items: center; gap: 6px; padding: 8px 8px; margin: 1px 0; border-radius: 4px; background: transparent; cursor: pointer; transition: background 0.08s; height: 29px; box-sizing: border-box; } .section-header-flex:hover { background: color-mix(in srgb, var(--red) 8%, transparent); } .section-header-flex::after { content: none; } .section-header-flex h4::before { content: none; } /* Section title text — same font as .list-item .grow */ .section-header-flex h4, .section-header-flex .section-title { flex: 1; cursor: pointer; user-select: none; display: flex; align-items: center; gap: 6px; margin: 0; padding: 0; border: none; font-size: 10px; font-weight: 400; font-family: inherit; line-height: 1; letter-spacing: 0; text-transform: none; color: var(--fg); } .section-icon, .sidebar-action-icon { flex-shrink: 0; stroke: var(--accent, var(--red)); position: relative; left: -1px; color: var(--accent, var(--red)); } /* Shared notification dot for sidebar section titles. Single source of truth so chats / email / assistant / future-section dots all sit at the same offset from their label. */ .sidebar-notif-dot { display: inline-block; width: 6px; height: 6px; /* Push to the right edge of the flex section-title so chats / email / assistant dots all line up vertically in the same column instead of trailing right after each (differently-sized) label. */ margin-left: auto; border-radius: 50%; background: var(--accent, var(--red)); flex-shrink: 0; vertical-align: middle; } /* The email notification gets a soft breathing glow so new-mail catches the eye without being shouty. Vertical alignment stays with the inherited .sidebar-notif-dot rule so it lines up with chats/assistant. */ #email-unread-dot.sidebar-notif-dot { animation: email-notif-breathe 2.2s ease-in-out infinite; /* Nudge in from the far-right edge so it doesn't crowd the corner. */ margin-right: 4px; /* Tiny vertical nudge to center with the email label. */ position: relative; top: 0.1px; } @keyframes email-notif-breathe { 0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent, var(--red)) 60%, transparent); opacity: 0.85; } 50% { box-shadow: 0 0 6px 2px color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); opacity: 1; } } @media (prefers-reduced-motion: reduce) { #email-unread-dot.sidebar-notif-dot { animation: none; } } @media (max-width: 768px) { /* Nudge the sidebar notification dot up 1px on mobile so it lines up with the bigger section titles. vertical-align:middle drifts a hair low against the larger touch-friendly text. */ .sidebar-notif-dot { position: relative; top: -1px; } /* Cookbook's sidebar status dot carries an inline top:-1px, so override with the ID + !important to nudge it 2px left / 1px up on mobile. */ #cookbook-notif-dot { left: -1px !important; top: -2px !important; } } #sidebar-new-chat-btn svg { transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1); } #sidebar-new-chat-btn:hover svg { transform: rotate(90deg); } /* Sort/select buttons in header — compact */ .section-header-flex > div { display: flex; align-items: center; gap: 2px; } .section-header-btn { all: unset; cursor: pointer; opacity: 0.4; padding: 1px 3px; border-radius: 4px; display: inline-flex; align-items: center; justify-content: center; transition: opacity 0.08s, background 0.08s; } .section-header-btn:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 7%, transparent); } .section-header-btn.active { opacity: 0.9; color: var(--accent); } .section-header-btn svg { width: 12px; height: 12px; } /* Chats library — grid icon, hover-reveal so the header only toggles collapse */ #sessions-section .chats-manage-btn { opacity: 0; transition: opacity 0.12s, background 0.08s; } #sessions-section .section-header-flex:hover .chats-manage-btn, #sessions-section .chats-manage-btn:hover, #sessions-section .chats-manage-btn:focus-visible { opacity: 0.45; } #sessions-section .chats-manage-btn:hover, #sessions-section .chats-manage-btn:focus-visible { opacity: 1; } @media (hover: none) { #sessions-section .chats-manage-btn { opacity: 0.35; } #sessions-section .chats-manage-btn:active { opacity: 1; } } /* Collapse chevron */ .section-collapse-btn { all: unset; cursor: pointer; display: inline-flex; align-items: center; padding: 0 2px; border-radius: 4px; } .section-collapse-btn:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); } .section-collapse-chevron { display: inline-flex; opacity: 0.3; transition: transform 0.2s, opacity 0.15s; } .section-collapse-btn:hover .section-collapse-chevron { opacity: 0.6; } .section.collapsed .section-collapse-chevron { transform: rotate(-90deg); opacity: 0.5; } .section-header-flex:has(.section-header-btn) .section-collapse-btn { display: none; } .section.collapsed .section-collapse-btn { display: inline-flex !important; } /* Collapsed state */ .section.collapsed > *:not(h4):not(.section-title):not(.section-header-flex) { display: none !important; } .section.collapsed .section-header-flex { margin-bottom: 0; } .section.collapsed .section-header-btn { display: none; } .section.collapsed { cursor: pointer; } /* Domino expand: every time a section goes from .collapsed → open (toggleCollapse in section-management.js adds .section-just-expanded for ~700ms), the .list-item children cascade in one after another, same feel as the chat input's tools menu. Each row springs in from a tiny offset below + scaled-down, staggered by nth-child. */ .section.section-just-expanded :is(.list-item, .models-row) { animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(1) { animation-delay: 0.04s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(2) { animation-delay: 0.08s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(3) { animation-delay: 0.12s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(4) { animation-delay: 0.16s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(5) { animation-delay: 0.20s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(6) { animation-delay: 0.24s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(7) { animation-delay: 0.28s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(8) { animation-delay: 0.32s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(9) { animation-delay: 0.36s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(10) { animation-delay: 0.40s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(11) { animation-delay: 0.44s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(12) { animation-delay: 0.48s; } @keyframes section-domino-in { 0% { opacity: 0; transform: translateY(8px) translateX(-4px) scale(0.92); } 60% { opacity: 1; } 100% { opacity: 1; transform: translateY(0) translateX(0) scale(1); } } /* Domino collapse: when toggleCollapse goes open → closed, JS adds .section-just-collapsing for ~530ms before flipping in .collapsed. During that window the items fade/slide DOWN one after another so you see them peel off instead of vanishing as a block. Uses nth-last-child so the BOTTOM item leaves first and the cascade rolls upward — mirrors the "stacked deck" feeling of the open animation reversed. */ .section.section-just-collapsing :is(.list-item, .models-row) { animation: section-domino-out 0.22s ease-in forwards; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(1) { animation-delay: 0.00s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(2) { animation-delay: 0.025s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(3) { animation-delay: 0.05s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(4) { animation-delay: 0.075s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(5) { animation-delay: 0.10s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(6) { animation-delay: 0.125s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(7) { animation-delay: 0.15s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(8) { animation-delay: 0.175s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(9) { animation-delay: 0.20s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(10) { animation-delay: 0.225s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(11) { animation-delay: 0.25s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(12) { animation-delay: 0.275s; } @keyframes section-domino-out { 0% { opacity: 1; transform: translateY(0) translateX(0) scale(1); } 100% { opacity: 0; transform: translateY(6px) translateX(-3px) scale(0.94); } } @keyframes spin { to { transform: rotate(360deg); } } .row { display:flex; gap:6px; align-items:center; } .list-item:hover, .models-row:hover { background: color-mix(in srgb, var(--red) 8%, transparent); border-color: var(--red); } /* Disabled tool — dimmed to signal its feature is turned off globally */ .list-item.tool-disabled { opacity: 0.4; } .list-item.tool-disabled:hover { opacity: 0.7; } /* Session bulk select mode */ .session-bulk-bar { display: flex; align-items: center; gap: 6px; padding: 4px 8px; border-bottom: 1px solid var(--border); background: var(--sidebar-bg, var(--panel)); position: sticky; top: 0; z-index: 3; font-size: 11px; } .session-bulk-bar.hidden { display: none; } #session-select-all-dot { display: inline-block; position: relative; top: -2px; } .session-bulk-btn { background: none; border: none; border-radius: 4px; color: var(--fg); padding: 4px; font-family: inherit; cursor: pointer; opacity: 0.4; transition: opacity 0.1s; display: inline-flex; align-items: center; justify-content: center; } .session-bulk-btn:hover { opacity: 1; } .session-bulk-btn-danger { color: var(--red); opacity: 0.5; } .session-bulk-btn-danger:hover { opacity: 1; } /* Checkbox in select mode */ .session-select-cb { accent-color: var(--accent, var(--red)); margin: 0 4px 0 0; flex-shrink: 0; cursor: pointer; } .session-icon, .model-icon { flex-shrink: 0; opacity: 0.35; display: inline-flex; align-items: center; } .session-icon.has-docs { color: inherit; opacity: 0.5; } .session-star { width: 10px; height: 10px; border-radius: 50%; border: 1.5px solid color-mix(in srgb, var(--fg) 22%, transparent); flex-shrink: 0; position: relative; } .session-star.processing { animation: research-pulse 1.5s ease-in-out infinite; } .session-star.notify { animation: research-done-pulse 1.2s ease-in-out 5; opacity: 1; /* Bigger, solid status dot so "research done" reads clearly. Use the defined accent (bare --accent is undefined here → no colour). */ width: 14px; height: 14px; background: var(--accent-primary, var(--red)); border-color: var(--accent-primary, var(--red)); } /* The dot doubles as the provider-logo holder; hide that logo in the done state so the solid notif dot doesn't collide with the SVG behind it. */ .session-star.notify svg { display: none; } @keyframes research-done-pulse { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.5); opacity: 0.7; } } .session-fav { flex-shrink: 0; cursor: pointer; color: var(--red); opacity: 0.6; display: inline-flex; align-items: center; transition: opacity 0.1s; } .session-fav:hover { opacity: 1; } .list-item.active-session { background: color-mix(in srgb, var(--red) 10%, transparent); border-color: var(--red); border-left-color: var(--red); } .list-item .provider-logo { opacity: 0.4 !important; } .list-item:has(.session-star.processing) .provider-logo { opacity: 1 !important; } .list-item.stream-complete .provider-logo { opacity: 1 !important; } .list-item.active { background: color-mix(in srgb, var(--red) 10%, transparent); border-color: var(--red); border-left-color: var(--red); } /* Only show the red focus ring for keyboard navigation (Tab). Mouse clicks shouldn't leave a sticky red outline on a sidebar item. */ .list-item:focus { outline: none; } .list-item:focus-visible { outline: none; border-color: var(--red, var(--color-error)); box-shadow: 0 0 0 1px var(--red, var(--color-error)); } /* Drag handles hidden by default, shown via body.rearrange-mode */ .drag-handle, .item-drag-handle, .folder-drag-handle { display: none; } body.rearrange-mode .drag-handle, body.rearrange-mode .item-drag-handle, body.rearrange-mode .folder-drag-handle { display: inline; } /* Drag sorting styles for list items */ .item-drag-handle { cursor: grab; opacity: 0.4; font-size: 10px; padding: 0 4px; user-select: none; letter-spacing: -2px; transition: opacity 0.15s; } .item-drag-handle:hover { opacity: 1; color: var(--red); } .item-drag-handle:active { cursor: grabbing; } .list-item.dragging, .models-row.dragging, .session-folder.dragging { opacity: 0.95; box-shadow: 0 8px 24px color-mix(in srgb, var(--red) 30%, transparent); border-color: var(--red); background: var(--panel); cursor: grabbing; } .list-item.dragging .item-drag-handle, .models-row.dragging .item-drag-handle, .session-folder.dragging .folder-drag-handle { opacity: 1; color: var(--red); } /* ── Model category grouping ── */ .models-category-header { display: flex; align-items: center; gap: 6px; padding: 4px 8px; cursor: pointer; font-size: 0.85em; font-weight: 600; color: color-mix(in srgb, var(--fg) 55%, transparent); border-radius: 4px; user-select: none; margin-top: 4px; transition: opacity 0.08s, background 0.08s, color 0.08s; } .models-category-header:hover { color: var(--fg); background: color-mix(in srgb, var(--fg) 4%, transparent); } .models-category-header .folder-count { font-weight: 400; opacity: 0.5; font-size: 0.9em; } .models-endpoint-label { display: flex; align-items: center; gap: 6px; padding: 4px 8px; cursor: pointer; font-size: 0.85em; font-weight: 600; color: color-mix(in srgb, var(--fg) 55%, transparent); border-radius: 4px; user-select: none; transition: opacity 0.08s, background 0.08s; } .models-endpoint-label:hover { color: var(--fg); background: color-mix(in srgb, var(--fg) 4%, transparent); } .models-endpoint-label .folder-count { font-weight: 400; opacity: 0.5; } .models-group-content.indented { padding-left: 4px; } .models-group-content.indented .models-row { border-bottom: 1px solid color-mix(in srgb, var(--fg) 7%, transparent); } .models-group-content.indented .models-row:last-child { border-bottom: none; } /* ── Session folders ── */ .session-folder { margin: 2px 0; } .session-folder-header { display: flex; align-items: center; gap: 6px; padding: 4px 8px; cursor: pointer; font-size: 0.88em; font-weight: 600; color: color-mix(in srgb, var(--fg) 55%, transparent); border-radius: 4px; user-select: none; transition: opacity 0.08s, background 0.08s; } .session-folder-header:hover { color: var(--fg); background: color-mix(in srgb, var(--fg) 4%, transparent); } .session-folder-header .folder-count { font-weight: 400; opacity: 0.5; } .session-folder-header.drag-over { outline: 2px dashed var(--red); background: color-mix(in srgb, var(--red) 13%, transparent); opacity: 1; } .session-folder.dragging-folder { opacity: 0.4; } .folder-toggle { font-size: 0.7em; width: 10px; text-align: center; } .folder-name { font-weight: inherit; flex: 1; } .folder-count { font-size: 0.85em; opacity: 0.5; } .folder-drag-handle { cursor: grab; opacity: 0.3; font-size: 0.8em; padding: 0 2px; transition: opacity 0.08s; } .session-folder-header:hover .folder-drag-handle { opacity: 0.7; } .folder-delete-btn { background: none; border: none; color: var(--fg); opacity: 0; cursor: pointer; font-size: 1.1em; padding: 0 2px; line-height: 1; min-height: 0; height: auto; transition: opacity 0.08s, color 0.08s; } .session-folder-header:hover .folder-delete-btn { opacity: 0.5; } .folder-delete-btn:hover { opacity: 1 !important; color: var(--color-error); } .session-folder-content { padding-left: 4px; } .session-folder-content .list-item { border-bottom: 1px solid color-mix(in srgb, var(--fg) 7%, transparent); } .session-folder-content .list-item:last-child { border-bottom: none; } .session-show-more-btn { display: block; width: 100%; background: none; border: none; color: var(--fg); opacity: 0.4; font-size: 0.8em; padding: 6px 8px; cursor: pointer; text-align: center; transition: opacity 0.15s; } .session-show-more-btn:hover { opacity: 0.8; } .drag-folder-placeholder { height: 4px; margin: 2px 0; border-radius: 2px; background: var(--red); opacity: 0.6; } .unfiled-drop-zone { min-height: 8px; border-radius: 4px; transition: all 0.15s; } .unfiled-drop-zone.drag-over { min-height: 24px; outline: 2px dashed var(--red); background: color-mix(in srgb, var(--red) 9%, transparent); } /* Mobile swipe-to-delete */ .swipe-delete-action { position: absolute; right: 0; top: 0; bottom: 0; width: 60px; background: color-mix(in srgb, var(--color-error) 12%, transparent); color: var(--color-error); display: flex; align-items: center; justify-content: center; border-radius: 0 4px 4px 0; opacity: 0; pointer-events: none; transition: opacity 0.15s; cursor: pointer; z-index: 1; } .swipe-delete-action::before { content: ''; display: block; width: 18px; height: 18px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%23ff4444' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='3 6 5 6 21 6'/%3E%3Cpath d='M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2'/%3E%3C/svg%3E"); background-size: contain; background-repeat: no-repeat; } .swipe-delete-action:active { background: color-mix(in srgb, var(--color-error) 22%, transparent); } .drag-placeholder { background: color-mix(in srgb, var(--red) 10%, transparent); border: 2px dashed color-mix(in srgb, var(--color-accent) 40%, transparent); border-radius: 6px; margin: 4px 0; transition: height 0.15s ease; } .list-item, .models-row { display: flex; gap: 6px; align-items: center; padding: 3px 8px; margin: 0; border-radius: 4px; border: none; line-height: 1.3; font-size: 13px; background: transparent; transition: background 0.08s; cursor: pointer; touch-action: pan-y; } .list-item button { margin: 0; height: 24px; padding: 0 8px; font-size: 9px; } /* Inline "+" action on a tool/section row (Library → new document, Email → compose). Sizing forced + min-* zeroed so the mobile touch-target rule (44px) can't inflate it. Selector intentionally NOT scoped to `.list-item` so the same class also styles buttons sitting in `.section-header-flex` (e.g. #email-compose-btn). */ .list-item-plus-btn { all: unset; box-sizing: border-box; flex-shrink: 0; position: relative; left: 4px; display: inline-flex !important; align-items: center; justify-content: center; height: 14px !important; min-height: 0 !important; width: auto !important; min-width: 0 !important; padding: 0 5px !important; border-radius: 4px; color: var(--fg); /* Always visible at rest. On hover (devices that have it) the + spins 90° and "new" reveals to its right — neat expand affordance. */ opacity: 1 !important; cursor: pointer; gap: 0; overflow: visible; z-index: 1; transition: background 0.12s, color 0.12s, gap 0.18s ease; } .list-item-plus-btn svg.list-item-plus-icon { width: 13px; height: 13px; transition: transform 0.22s ease; } /* Legacy fallback for plus-btns without the `.list-item-plus-icon` class. */ .list-item-plus-btn svg:not(.list-item-plus-icon) { width: 13px; height: 13px; } /* Label is absolutely positioned to the LEFT of the icon so the + stays put — it just appears beside it on hover instead of pushing it. The button's intrinsic width remains the icon, so neighbours don't shift. */ .list-item-plus-label { position: absolute; right: 100%; top: 50%; transform: translateY(-50%); padding-right: 5px; opacity: 0; pointer-events: none; white-space: nowrap; font-size: 9.5px; line-height: 1; letter-spacing: 0.02em; transition: opacity 0.18s ease, transform 0.22s ease; } @media (hover: hover) { .list-item-plus-btn:hover .list-item-plus-icon { transform: rotate(90deg); } .list-item-plus-btn:hover .list-item-plus-label { opacity: 1; transform: translateY(-50%) translateX(0); pointer-events: auto; } .list-item-plus-btn .list-item-plus-label { transform: translateY(-50%) translateX(6px); } } .list-item-plus-btn:hover { background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); color: var(--accent, var(--red)); } .list-item span { color: var(--fg); font-size: 9.75px; } .grow { flex: 1; overflow: hidden; text-overflow: clip; white-space: nowrap; } input, textarea, button, select { background:var(--bg); color:var(--fg); border:1px solid var(--border); font-family:inherit; padding:6.9px; border-radius: 4px; transition: background 0.08s, border-color 0.08s, color 0.08s, opacity 0.08s; } input:hover, textarea:hover, button:hover, select:hover { border-color: var(--fg); background-color: var(--panel); } :root.light input, :root.light textarea, :root.light button, :root.light select { background:#eaeaea; color-scheme: light; } input[type="text"] { height:32px; width:100%; } textarea { width:100%; min-height:32px; height:auto; max-height:30lh; overflow-y:auto; resize:none; } button { height:32px; padding:0 10px; } #chat-form button[type="submit"] { height:38px; } select { height:32px; color-scheme: dark; background-color: var(--select-bg); color: var(--select-fg); } select option, select optgroup { background-color: var(--select-option-bg); color: var(--select-option-fg); } select option:checked { background-color: var(--select-option-active-bg); color: var(--select-option-fg); } :root.light select { color-scheme: light; } .chat-container { flex:1; display:flex; flex-direction:column; padding:0 16px; overflow:hidden; position:relative; min-height:0; min-width:0; margin-top:8px; margin-bottom: 0; } .chat-meta { font-size:12px; color:color-mix(in srgb, var(--fg) 60%, transparent); margin-bottom:6px; } .chat-history { flex:1; overflow-y:auto; overflow-x:hidden; overscroll-behavior-y: none; margin-bottom:8px; white-space:normal; min-height:0; --chat-max: 800px; padding-left: max(0px, calc((100% - var(--chat-max)) / 2)); padding-right: max(12px, calc((100% - var(--chat-max)) / 2 + 12px)); } /* Sortable Cookbook column headers had no visual cue, so users couldn't tell a header was clickable (the Newest sort on the Model column was invisible). Show a pointer + hover highlight, and underline the active sort column. */ .hwfit-header .hwfit-sortable { cursor: pointer; transition: color .12s; } .hwfit-header .hwfit-sortable:hover { color: var(--fg); text-decoration: underline dotted; } .hwfit-header .hwfit-sort-active { color: var(--fg); font-weight: 600; } /* Welcome screen — centered in available space above input bar */ #welcome-screen { position:absolute; top:40%; left:50%; transform:translate(-50%,-50%); display:flex; flex-direction:column; align-items:center; text-align:center; pointer-events:none; animation: welcome-enter 0.4s ease-out both; transition: top 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s ease, transform 0.3s ease; } #welcome-screen .welcome-tip, #welcome-screen .welcome-sub, #welcome-screen .welcome-version { transition: opacity 0.25s ease, max-height 0.25s ease, margin 0.25s ease; max-height: 60px; overflow: hidden; } @media (max-height: 650px) { #welcome-screen { top: 28%; } #welcome-screen .welcome-tip { opacity: 0; max-height: 0; margin: 0; } #welcome-screen .welcome-version { margin-top: 6px; } .incognito-btn { margin-top: 8px; } } @media (max-height: 500px) { #welcome-screen { top: 22%; } #welcome-screen .welcome-name { font-size: 1.4rem; margin-bottom: 0; } #welcome-screen .welcome-sub { opacity: 0; max-height: 0; margin: 0; } .incognito-btn { margin-top: 4px !important; } } @media (max-height: 380px) { #welcome-screen { opacity: 0; pointer-events: none; } } #welcome-screen.hidden { display:none; } #welcome-screen.kb-hidden { opacity: 0; pointer-events: none; transform: translate(-50%, -50%) scale(0.95); } @media (max-width: 768px) { #welcome-screen { top: 42%; } #welcome-screen .welcome-name { margin-bottom: 2px; } #welcome-screen .welcome-sub { margin-bottom: 0; } #welcome-screen .incognito-btn { margin-top: 6px; } } #welcome-screen .welcome-name { font-size:2.2rem; font-weight:700; background: linear-gradient(135deg, var(--brand-color, var(--red)), color-mix(in srgb, var(--brand-color, var(--red)) 60%, var(--fg))); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; letter-spacing:0.03em; margin-bottom:10px; } .welcome-boat { width: 1.8rem; height: 1.8rem; margin-right: 0.4rem; vertical-align: -0.15em; color: var(--brand-color, var(--red)); } #welcome-screen .welcome-sub { font-size:0.85rem; color:color-mix(in srgb, var(--fg) 60%, transparent); opacity:0.6; line-height:1.5; max-width:300px; } #welcome-screen .welcome-version { font-size:0.7rem; opacity:0.25; margin-top:12px; pointer-events:none; user-select:none; } #welcome-screen .welcome-tip { font-size:0.75rem; color:var(--fg); opacity:0.2; margin-top:24px; max-width:320px; line-height:1.4; } /* Incognito toggle — on welcome screen */ .incognito-btn { pointer-events: auto; background: none; border: 1px solid var(--border); color: var(--fg); opacity: 0.25; cursor: pointer; padding: 6px 12px; border-radius: 20px; display: inline-flex; align-items: center; justify-content: center; gap: 6px; transition: all 0.15s; margin-top: 16px; font-family: inherit; font-size: 11px; } .incognito-btn:hover { opacity: 0.6; background: color-mix(in srgb, var(--fg) 6%, transparent); } .incognito-btn.active { opacity: 1; color: var(--accent); border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); } .incognito-btn.active .eye-open { display: none; } .incognito-btn.active .eye-blinded { display: inline !important; } .incognito-label { font-size: 11px; font-weight: 500; letter-spacing: 0.3px; } /* When welcome is active, push input bar up toward center */ .chat-container .chat-input-bar { transition: margin 0.3s ease, max-width 0.3s ease; } .chat-container.welcome-active .chat-input-bar { margin-bottom:30vh; margin-left:auto; margin-right:auto; max-width: 800px; width: 100%; } @media (max-width:768px) { #welcome-screen .welcome-name { font-size:1.8rem; } #welcome-screen .welcome-sub { font-size:0.8rem; } .chat-container.welcome-active .chat-input-bar { margin-bottom:0; margin-left:0; margin-right:0; } } .msg { margin: 8px 0; position: relative; display: flex; flex-direction: column; width: fit-content; max-width: 85%; min-width: 80px; border-radius: 12px; padding: 10px 12px; line-height: 1.4; word-wrap: break-word; overflow-wrap: break-word; overflow: hidden; animation: msg-enter 0.3s ease-out both; } .msg-user { align-items: flex-end; margin-left: auto; margin-right: 8px; background: var(--user-bubble-bg, color-mix(in srgb, var(--fg) 8%, var(--bg))); border: 1px solid var(--bubble-border, var(--border)); border-radius: 18px 18px 0 18px; align-self: flex-end; width: fit-content; max-width: 85%; min-width: 80px; word-wrap: break-word; overflow-wrap: break-word; overflow: hidden; } .msg-ai { align-items: flex-start; margin-right: auto; margin-left: 8px; background: var(--ai-bubble-bg, var(--panel)); border: 1px solid var(--bubble-border, var(--border)); border-radius: 18px 18px 18px 0; align-self: flex-start; width: 85%; max-width: 85%; min-width: 80px; word-wrap: break-word; overflow-wrap: break-word; overflow: hidden; transition: min-height 0.25s ease-out; } .msg .role { font-weight: 600; margin-bottom: 6px; display: flex; align-items: center; gap: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .msg .role::before { content: ''; width: 8px; height: 8px; border-radius: 50%; background: var(--model-dot, color-mix(in srgb, var(--fg) 30%, transparent)); flex-shrink: 0; } .msg .role.has-logo::before { display: none; } .role-provider-logo { display: inline-flex; align-items: center; flex-shrink: 0; } .role-provider-logo svg { width: 12px; height: 12px; } .msg-user .role { color: color-mix(in srgb, var(--fg) 60%, transparent); } .msg-user .role::before { background: color-mix(in srgb, var(--fg) 40%, transparent); } .msg .body { width: 100%; white-space: normal; word-break: break-word; overflow-wrap: anywhere; line-height: 1.5; font-size: 0.95em; margin: 0; overflow: hidden; max-width: 100%; } .msg .body > * { margin-top: 8px; margin-bottom: 8px; } .msg .body > *:first-child { margin-top: 0; } .msg .body > *:last-child { margin-bottom: 0; } .msg .body p:empty { display: none; } .msg-user .body { color: var(--fg); } .msg-ai .body { color: var(--fg); } .rag-sources { margin-top: 12px; border: 1px solid var(--border); border-radius: 6px; padding: 8px; font-size: 12px; } .rag-sources summary { cursor: pointer; color: var(--red); font-weight: bold; font-size: 11px; } .rag-source-item { margin-top: 8px; padding: 6px; background: color-mix(in srgb, var(--fg) 4%, transparent); border-radius: 4px; border-left: 2px solid var(--red); } .rag-similarity { color: color-mix(in srgb, var(--fg) 50%, transparent); font-size: 10px; margin-left: 6px; } .rag-snippet { color: color-mix(in srgb, var(--fg) 65%, transparent); font-size: 11px; margin-top: 4px; white-space: pre-wrap; max-height: 60px; overflow: hidden; } .rag-file-delete { background: none; border: 1px solid var(--border); border-radius: 4px; color: color-mix(in srgb, var(--fg) 50%, transparent); cursor: pointer; font-size: 10px; padding: 1px 5px; } .rag-file-delete:hover { color: var(--fg); border-color: var(--fg); } .rag-file-size { color: color-mix(in srgb, var(--fg) 50%, transparent); font-size: 10px; margin-left: 4px; } .rag-upload-zone { border: 2px dashed var(--border); border-radius: 6px; padding: 12px; text-align: center; color: color-mix(in srgb, var(--fg) 45%, transparent); font-size: 11px; cursor: pointer; transition: border-color 0.2s; } .rag-upload-zone.dragover { border-color: var(--red); color: var(--red); } .msg .timestamp { font-size: 10px; color: color-mix(in srgb, var(--fg) 45%, transparent); margin-top: 4px; text-align: right; opacity: 0.7; } .msg-user .timestamp { color: color-mix(in srgb, var(--fg) 72%, transparent); } pre { overflow: hidden; padding: 6px 4px 2px 4px; border: 1px solid var(--border); background: var(--bg); position: relative; line-height: 1.4; font-size: 0.95em; font-family: 'Fira Code', 'Courier New', monospace; min-height: 38px; max-width: 100%; margin: 8px 0; white-space: pre-wrap; word-break: break-word; border-radius: 4px; min-height: 20px; min-width: 80px; } /* Ensure code block headers are only slightly larger than regular text */ pre h1, pre h2, pre h3, pre h4, pre h5, pre h6 { font-size: 1.1em; font-weight: bold; margin: 8px 0 4px 0; color: var(--fg); } .code-lang { font-size:0.8em; color:color-mix(in srgb, var(--fg) 60%, transparent); display:block; margin-bottom:2px; font-style:italic; } code { font-family:inherit; } .loading { color:var(--red); font-style:italic; } #chat-form { display:none; } /* Unified chat input bar */ .chat-input-bar { background: var(--input-bg, var(--panel)); border: 1px solid var(--input-border, var(--border)); border-radius: 16px; padding: 10px 12px; display: flex; flex-direction: column; gap: 8px; max-width: 800px; margin-left: auto; margin-right: auto; width: 100%; } .chat-input-top { width: 100%; position: relative; } .chat-input-top > .model-picker-wrap { position: absolute; top: 0; right: 0; z-index: 2; transform-origin: top right; transition: opacity 0.22s ease, transform 0.22s ease; will-change: opacity, transform; } .chat-input-top > .model-picker-wrap.model-picker-autohide { opacity: 0; pointer-events: none; transform: translateY(-4px) scale(0.96); } .ghost-text-overlay { position: absolute; top: 0; left: 0; right: 0; pointer-events: none; white-space: pre-wrap; overflow-wrap: break-word; font-family: inherit; font-size: 14px; line-height: 1.5; padding: 0; border: 1px solid transparent; color: transparent; z-index: 1; } .ghost-text-overlay .ghost-suggestion { color: color-mix(in srgb, var(--fg) 30%, transparent); } .chat-input-bar textarea#message { width: 100%; background: transparent; border: none; outline: none; resize: vertical; font-size: 14px; line-height: 1.5; color: var(--fg); min-height: 24px; max-height: min(60vh, 600px); padding: 0; font-family: inherit; transition: height 0.12s ease-out; } .chat-input-bar textarea#message::placeholder { color: color-mix(in srgb, var(--fg) 35%, transparent); } .chat-input-bottom { display: flex; justify-content: space-between; align-items: center; margin-top: 4px; } /* Progressive shrinkage: as the chat input bar gets narrow, sacrifice chrome before the typing area. First the Agent/Chat toggle drops out, then the model picker — textarea + send button always stay. Container queries so it responds to *bar width*, not viewport width (the bar can be in a split pane). */ .chat-input-bar { container-type: inline-size; container-name: chatbar; } @container chatbar (max-width: 340px) { .chat-input-right .mode-toggle { display: none !important; } } @container chatbar (max-width: 260px) { #model-picker-wrap { display: none !important; } } .chat-input-left { display: flex; gap: 4px; align-items: center; flex-wrap: nowrap; overflow: hidden; min-width: 0; flex: 1; } /* Collapsible buttons shrink away; collapsed ones disappear */ .chat-input-left > .input-icon-btn { flex-shrink: 0; } .chat-input-left > .input-icon-btn.toolbar-collapsed { display: none !important; } /* Always keep overflow wrapper visible and leftmost */ .chat-input-left > .overflow-wrapper { flex-shrink: 0; position: relative; z-index: 1; } .input-divider { width: 1px; height: 16px; background: var(--border); opacity: 0.4; margin: 0 2px; flex-shrink: 0; } .chat-input-right { display: flex; gap: 8px; align-items: center; flex-shrink: 0; } .input-icon-btn { background: none; border: none; color: var(--fg); opacity: 0.5; cursor: pointer; padding: 6px; border-radius: 8px; display: flex; align-items: center; justify-content: center; transition: opacity 0.15s, background 0.15s, color 0.15s; position: relative; } .input-icon-btn:hover { opacity: 0.8; background: color-mix(in srgb, var(--accent) 8%, transparent); } .input-icon-btn.active, .input-icon-btn.expanded { opacity: 1; color: var(--fg); background: color-mix(in srgb, var(--fg) 9%, transparent); } /* While the menu is open the chevron stays in its highlighted state — don't run the opacity fade transition so we never flash from 0.5 → hover-1.0 → drop-back. The state holds steady. */ .input-icon-btn.expanded { transition: none; } /* Accent color for Research, Compare toolbar indicators */ #research-toggle-btn.active, .tool-indicator.active { color: var(--red); background: color-mix(in srgb, var(--red) 12%, transparent); } /* Research button glow while actively running */ #research-toggle-btn.research-running { opacity: 1 !important; color: var(--red); animation: research-pulse 2s ease-in-out infinite; } @keyframes research-pulse { 0%, 100% { background: color-mix(in srgb, var(--red) 12%, transparent); } 50% { background: color-mix(in srgb, var(--red) 22%, transparent); } } .tool-indicator-x { margin-left: 2px; opacity: 0.4; flex-shrink: 0; transition: opacity 0.15s; } .tool-indicator:hover .tool-indicator-x { opacity: 1; } /* Character indicator — hide name text below 768px, icon always visible */ @media (max-width: 768px) { #character-indicator-name { display: none !important; } } /* Document indicator — hidden by default, shown when docs exist */ #doc-indicator-btn { display: none !important; } #doc-indicator-btn.visible { display: inline-flex !important; } /* On mobile, the minimized-dock chip replaces this indicator — keep it hidden regardless of `.visible`. */ @media (max-width: 768px) { #doc-indicator-btn, #doc-indicator-btn.visible { display: none !important; } } .doc-indicator-active { color: var(--accent, var(--accent-primary, var(--red))) !important; opacity: 1 !important; } #doc-indicator-btn.active { color: var(--accent, var(--accent-primary, var(--red))) !important; opacity: 1 !important; background: color-mix(in srgb, var(--fg) 9%, transparent) !important; } #overflow-doc-btn.has-docs, #overflow-doc-btn.active { color: var(--accent, var(--red)); opacity: 1; } #overflow-doc-btn.has-docs.active { background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); } .send-btn { /* Prefer the theme accent (--red). A stored custom `--send-btn-bg` override only kicks in if it's set to a non-white value — guards against ChatGPT-style themes where the stored override leaked to white and rendered the send button invisible. */ background: var(--send-btn-bg, var(--red)); color: #fff; border: none; border-radius: 8px; min-width: 32px; width: 32px; height: 32px !important; padding: 0; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.25s, border-color 0.25s, color 0.25s, width 0.3s cubic-bezier(0.34, 1, 0.64, 1), padding 0.3s, border-radius 0.3s, opacity 0.1s; flex-shrink: 0; overflow: hidden; } /* Instant feedback while the send handler is spinning up before streaming begins */ .send-btn.send-pending { opacity: 0.55; pointer-events: none; animation: send-pending-pulse 0.9s ease-in-out infinite; } @keyframes send-pending-pulse { 0%, 100% { opacity: 0.55; } 50% { opacity: 0.85; } } /* Send button icon transitions — send mode forces no animation */ .send-btn[data-mode="send"] svg { animation: none !important; } /* Spin out: + rotates away, then arrow spins in via anim-spin */ .send-btn.anim-spin-swap svg { animation: btn-spin-out 0.15s ease-in forwards; } .send-btn.anim-spin-swap .send-btn-label { opacity: 0; transition: opacity 0.1s; } @keyframes btn-spin-out { 0% { transform: rotate(0deg) scale(1); opacity: 1; } 100% { transform: rotate(90deg) scale(0); opacity: 0; } } .send-btn.anim-spin svg { animation: btn-spin-in 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); } .send-btn.anim-launch svg { animation: btn-launch 0.3s cubic-bezier(0.6, 0, 0.4, 1) forwards; } .send-btn.anim-land svg { animation: btn-land 0.25s cubic-bezier(0.34, 1.56, 0.64, 1); } @keyframes btn-spin-in { 0% { transform: rotate(-180deg) scale(0); opacity: 0; } 100% { transform: rotate(0deg) scale(1); opacity: 1; } } /* Arrow flies UP and OUT of the button before the stop icon lands in. Stays opaque most of the way so it reads as a real launch instead of a fade-in-place. .anim-launch temporarily lifts the button's overflow so the icon escapes the 32px box. */ @keyframes btn-launch { 0% { transform: translateY(0) scale(1); opacity: 1; } 70% { transform: translateY(-30px) scale(0.95); opacity: 1; } 100% { transform: translateY(-58px) scale(0.55); opacity: 0; } } .send-btn.anim-launch { overflow: visible !important; } .send-btn.anim-launch svg { transform-origin: 50% 50%; } @keyframes btn-land { 0% { transform: translateY(10px) scale(0.3); opacity: 0; } 100% { transform: translateY(0) scale(1); opacity: 1; } } /* Stop button — Processing: Siren pulse, Receiving: Quarter turn spin */ .send-btn[data-mode="streaming"][data-phase="processing"] svg { animation: siren-icon 1.5s ease-in-out infinite; } .send-btn[data-mode="streaming"][data-phase="receiving"] svg { animation: quarter-turn 2s cubic-bezier(0.4, 0, 0.2, 1) infinite; } @keyframes siren-icon { 0%, 100% { transform: scale(1); } 50% { transform: scale(0.8); } } @keyframes quarter-turn { 0%, 15% { transform: rotate(0deg); } 25%, 40% { transform: rotate(90deg); } 50%, 65% { transform: rotate(180deg); } 75%, 90% { transform: rotate(270deg); } 100% { transform: rotate(360deg); } } .send-btn:hover { background: var(--send-btn-hover, color-mix(in srgb, var(--red) 80%, white)); color: #fff; } .send-btn.mic-mode, .send-btn.newchat-mode { /* Idle / new-chat / mic states blend the picked Send Btn color into the panel so the user's color choice is visible across every state, not only briefly while typing. */ background: color-mix(in srgb, var(--send-btn-bg, var(--red)) 30%, var(--panel, #2a2a2a)); color: var(--fg); border: 1px solid var(--border); transition: width 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.25s, border-color 0.25s, color 0.25s, border-radius 0.3s; } .send-btn.mic-mode:hover, .send-btn.newchat-mode:hover { background: color-mix(in srgb, var(--send-btn-hover, var(--send-btn-bg, var(--red))) 85%, var(--panel, #2a2a2a)); color: #fff; } /* Hover: just spin the + 90° (no size change). The "New chat" affordance is the tooltip (title="New chat") plus the icon rotation. Gated on data-mode="newchat" so the arrow variant (empty-session state which keeps the newchat-mode class but shows the send arrow) does NOT rotate on hover. */ .send-btn[data-mode="newchat"] svg { transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1); } .send-btn[data-mode="newchat"]:hover svg { transform: rotate(90deg); } /* "New Session" expanding label */ .send-btn-label { width: 0; max-width: 0; overflow: hidden; white-space: nowrap; font-size: 11px; font-weight: 500; letter-spacing: 0.3px; opacity: 0; padding: 0; margin: 0; transition: max-width 0.35s cubic-bezier(0.34, 1.2, 0.64, 1), width 0.35s, opacity 0.25s, margin-left 0.25s; } .send-btn.newchat-expanded .send-btn-label { width: auto; max-width: 50px; opacity: 1; margin-left: 4px; } .send-btn.newchat-expanded { width: 68px; padding: 0 10px 0 8px; } .send-btn.recording { background: var(--red) !important; color: #fff !important; border: none !important; animation: pulse-recording 1.5s infinite; } @keyframes pulse-recording { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } } /* Mode toggle — Agent / Chat */ .mode-toggle { display: flex; flex-shrink: 0; height: 28px; border: 1px solid var(--border); border-radius: 10px; overflow: hidden; position: relative; } .mode-toggle::before { content: ''; position: absolute; top: 0; left: 0; width: 50%; height: 100%; background: color-mix(in srgb, var(--fg) 10%, transparent); border-radius: 9px; transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); z-index: 0; } .mode-toggle.mode-chat::before { transform: translateX(100%); } #mode-agent-btn { border-radius: 10px 0 0 10px; } #mode-chat-btn { border-radius: 0 10px 10px 0; } .mode-toggle-btn { background: none; border: none; color: color-mix(in srgb, var(--fg) 40%, transparent); cursor: pointer; padding: 0 10px; font-size: 11px; font-weight: 500; font-family: inherit; transition: color 0.2s; white-space: nowrap; height: 100%; position: relative; z-index: 1; } .mode-toggle-btn:not(.active):hover { color: color-mix(in srgb, var(--fg) 60%, transparent); } .mode-toggle-btn:hover { background: none !important; border-color: transparent !important; } .mode-toggle-btn.active, .mode-toggle-btn.active:hover, .mode-toggle-btn.active:focus { color: var(--fg) !important; background: none !important; border-color: transparent !important; cursor: default; } .mode-toggle-btn + .mode-toggle-btn { border-left: none; } /* Message count badge in the chat-meta header (next to the title). Auto-hides when empty so a brand-new chat doesn't show "0 msgs". */ .chat-meta-count { font-size: inherit; font-weight: 400; color: color-mix(in srgb, var(--fg) 35%, transparent); white-space: nowrap; user-select: none; margin-left: 6px; } .chat-meta-count:empty { display: none; } /* Session cost indicator (next to chevron in header) */ .session-cost-display { font-size: inherit; font-weight: 400; color: color-mix(in srgb, var(--fg) 35%, transparent); white-space: nowrap; user-select: none; margin-right: 2px; } /* Model picker — input bar drop-up */ .model-picker-wrap { position: relative; flex-shrink: 0; } .model-picker-btn { display: inline-flex; align-items: center; gap: 4px; height: 21px; padding: 0 6px; font-size: 11px; font-weight: 500; font-family: inherit; background: none; border: 1px solid transparent; border-radius: 4px; /* 65% mix lifts the model label above the WCAG AA 4.5:1 threshold against the dark chat-bar (40% only reached ~2.9:1). */ color: color-mix(in srgb, var(--fg) 65%, transparent); cursor: pointer; white-space: nowrap; transition: background 0.15s, color 0.15s, border-color 0.15s; } .model-picker-btn:hover { border-color: var(--border); background: color-mix(in srgb, var(--fg) 8%, transparent); color: var(--fg); } .model-picker-btn #model-picker-label { max-width: 120px; overflow: hidden; text-overflow: ellipsis; } .model-picker-btn svg { flex-shrink: 0; opacity: 0.5; } .model-picker-logo { display: inline-flex; align-items: center; vertical-align: -2px; } .model-picker-logo svg { width: 12px; height: 12px; opacity: 0.7; } .model-picker-menu { position: absolute; bottom: calc(100% + 16px); right: 0; z-index: 300; min-width: 260px; max-width: 360px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 -4px 20px rgba(0,0,0,0.4); padding: 6px; animation: picker-roll-up 0.2s ease-out; transform-origin: bottom right; } .model-picker-menu.closing { animation: picker-roll-down 0.15s ease-in forwards; } .model-picker-menu.hidden { display: none; } .model-picker-search-row { display: grid; grid-template-columns: minmax(0, 1fr) 30px; gap: 4px; align-items: center; margin-bottom: 4px; transition: grid-template-columns 0.18s ease, gap 0.18s ease; } .model-picker-menu.no-models .model-picker-search-row { margin-bottom: 0; } .model-picker-search-row.searching { grid-template-columns: minmax(0, 1fr) 0px; gap: 0; } .model-picker-search-row.searching .model-picker-action-btn { opacity: 0; transform: translateX(10px) scale(0.88); pointer-events: none; } .model-picker-menu input[type="text"] { width: 100%; box-sizing: border-box; padding: 6px 8px; font-size: 0.82em; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); color: var(--fg); font-family: inherit; outline: none; min-width: 0; transition: border-color 0.15s, padding 0.18s ease, background 0.18s ease; } .model-picker-menu input[type="text"]:focus { border-color: var(--red); } .model-picker-menu input[type="text"]::placeholder { color: color-mix(in srgb, var(--fg) 30%, transparent); } .model-picker-action-btn { appearance: none; display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; border: 1px solid var(--border); border-radius: 4px; background: color-mix(in srgb, var(--fg) 4%, transparent); color: color-mix(in srgb, var(--fg) 66%, transparent); cursor: pointer; padding: 0; overflow: hidden; transform: translateX(0) scale(1); transition: opacity 0.16s ease, transform 0.18s ease, border-color 0.15s, color 0.15s, background 0.15s; } .model-picker-action-btn:hover, .model-picker-action-btn:focus-visible { border-color: var(--red); color: var(--fg); background: color-mix(in srgb, var(--red) 10%, var(--panel)); outline: none; } .model-picker-action-btn.primary { color: var(--red); background: color-mix(in srgb, var(--red) 8%, transparent); } .model-picker-action-btn svg { width: 14px; height: 14px; transition: transform 0.28s ease; } .model-picker-action-btn:hover svg, .model-picker-action-btn:focus-visible svg { transform: rotate(90deg); } .model-picker-list { max-height: min(280px, 50dvh); overflow-y: auto; } .model-picker-list.is-empty { max-height: 0; overflow: hidden; } .model-picker-list .model-switch-item { display: flex; align-items: center; padding: 5px 8px; border-radius: 4px; cursor: pointer; font-size: 0.82em; color: var(--fg); gap: 6px; } .model-picker-list .model-switch-item:hover { background: color-mix(in srgb, var(--red) 8%, transparent); } .model-picker-list .model-switch-empty { display: flex; align-items: center; justify-content: center; padding: 5px 8px; color: color-mix(in srgb, var(--fg) 50%, transparent); font-size: 0.82em; } .model-picker-list .mp-section-label { font-size: 0.72em; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.4; padding: 6px 8px 2px; } .model-picker-list .mp-section-label:first-child { padding-top: 2px; } /* Model name takes the slack so the endpoint label + favorite dot sit on the right. */ .model-picker-list .model-switch-item .mp-model-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .model-picker-list .model-switch-item .model-switch-ep { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.9em; opacity: 0.45; } /* Keyboard navigation highlight (Arrow keys in the search box). */ .model-picker-list .model-switch-item.kb-active { background: color-mix(in srgb, var(--red) 14%, transparent); } /* Inline favorite dot — always visible (works on touch), active when on. */ .model-picker-list .mp-fav-dot { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 24px; margin: -5px -8px -5px 8px; padding: 0; border: none; background: transparent; cursor: pointer; color: color-mix(in srgb, var(--fg) 22%, transparent); font-family: inherit; font-size: 13px; line-height: 1; transition: color 0.15s ease, opacity 0.15s ease, transform 0.12s ease; -webkit-tap-highlight-color: transparent; } .model-picker-list .mp-fav-dot:hover { color: color-mix(in srgb, var(--fg) 68%, transparent); } .model-picker-list .mp-fav-dot:focus-visible { outline: none; color: color-mix(in srgb, var(--fg) 68%, transparent); } .model-picker-list .mp-fav-dot.active { color: var(--accent, var(--red)); opacity: 1; } .model-picker-list .mp-fav-dot.active:hover { color: var(--accent, var(--red)); opacity: 0.72; } .model-picker-list .mp-fav-dot.pulse { animation: mpFavPulse 0.32s ease-out; } @keyframes mpFavPulse { 0% { text-shadow: 0 0 0 color-mix(in srgb, var(--accent, var(--red)) 0%, transparent); } 45% { text-shadow: 0 0 10px color-mix(in srgb, var(--accent, var(--red)) 60%, transparent); } 100% { text-shadow: 0 0 0 color-mix(in srgb, var(--accent, var(--red)) 0%, transparent); } } /* First-run hint when a large catalog has no Recent/Favorites yet. */ .model-picker-list .mp-empty-hint { flex-direction: column; gap: 2px; padding: 14px 8px; text-align: center; } .model-picker-list .mp-empty-hint .mp-empty-title { font-size: 1.05em; color: color-mix(in srgb, var(--fg) 70%, transparent); } .model-picker-list .mp-empty-hint .mp-empty-sub { font-size: 0.92em; opacity: 0.7; } /* Provider group headers */ .model-picker-list .mp-provider-header { display: flex; align-items: center; gap: 6px; padding: 5px 8px; cursor: pointer; font-size: 0.78em; font-weight: 500; color: var(--fg); border-radius: 4px; user-select: none; } .model-picker-list .mp-provider-header:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); } .model-picker-list .mp-provider-chevron { display: inline-flex; opacity: 0.4; transition: transform 0.2s, opacity 0.15s; flex-shrink: 0; } .model-picker-list .mp-provider-header:hover .mp-provider-chevron { opacity: 0.7; } .model-picker-list .mp-provider-chevron.collapsed { transform: rotate(-90deg); } .model-picker-list .mp-provider-name { flex: 1; } .model-picker-list .mp-provider-count { font-size: 0.85em; opacity: 0.4; } /* Domino expand (15% faster than sidebar) */ .mp-provider-group.mp-just-expanded .model-switch-item { animation: mp-domino-in 0.31s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; } .mp-provider-group.mp-just-expanded .model-switch-item:nth-child(1) { animation-delay: 0.035s; } .mp-provider-group.mp-just-expanded .model-switch-item:nth-child(2) { animation-delay: 0.07s; } .mp-provider-group.mp-just-expanded .model-switch-item:nth-child(3) { animation-delay: 0.105s; } .mp-provider-group.mp-just-expanded .model-switch-item:nth-child(4) { animation-delay: 0.14s; } .mp-provider-group.mp-just-expanded .model-switch-item:nth-child(5) { animation-delay: 0.175s; } .mp-provider-group.mp-just-expanded .model-switch-item:nth-child(6) { animation-delay: 0.21s; } .mp-provider-group.mp-just-expanded .model-switch-item:nth-child(7) { animation-delay: 0.245s; } .mp-provider-group.mp-just-expanded .model-switch-item:nth-child(8) { animation-delay: 0.28s; } .mp-provider-group.mp-just-expanded .model-switch-item:nth-child(9) { animation-delay: 0.315s; } .mp-provider-group.mp-just-expanded .model-switch-item:nth-child(10) { animation-delay: 0.35s; } @keyframes mp-domino-in { 0% { opacity: 0; transform: translateY(6px) scale(0.94); } 60% { opacity: 1; } 100% { opacity: 1; transform: translateY(0) scale(1); } } /* Comfortable touch targets on phones / narrow screens. */ @media (hover: none) and (pointer: coarse), (max-width: 768px) { .model-picker-list .model-switch-item { padding-top: 8px; padding-bottom: 8px; } .model-picker-list .mp-fav-dot { width: 30px; height: 30px; margin: -7px -8px -7px 8px; } } /* Overflow "+" menu */ .overflow-wrapper { position: relative; display: flex; align-items: center; } .plus-active-dot { position: absolute; top: 2px; right: 2px; width: 6px; height: 6px; background: var(--fg); border-radius: 50%; display: none; } .overflow-plus-btn.has-active .plus-active-dot { display: block; } .overflow-menu { position: fixed; background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 4px; min-width: 170px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); z-index: 1000; /* Container spring-in: the rounded panel scales/grows out of the chevron's position before the menu items domino in on top. */ transform-origin: bottom left; animation: overflow-menu-pop 0.22s cubic-bezier(0.34, 1.56, 0.64, 1); } @keyframes overflow-menu-pop { 0% { transform: scale(0.6) translateY(8px); opacity: 0; } 100% { transform: scale(1) translateY(0); opacity: 1; } } .overflow-menu.hidden { display: none; } /* Closing state: JS adds `.closing` to play the fold-in animation, waits for it to finish, then flips to `.hidden`. Container scales/translates back into the chevron while items peel off from the top down so the menu collapses into its anchor. */ .overflow-menu.closing { animation: overflow-menu-pop-out 0.22s cubic-bezier(0.5, 0, 0.75, 0) forwards; animation-delay: 0.16s; } @keyframes overflow-menu-pop-out { 0% { transform: scale(1) translateY(0); opacity: 1; } 100% { transform: scale(0.6) translateY(8px); opacity: 0; } } .overflow-menu-item { display: flex; align-items: center; gap: 8px; width: 100%; padding: 8px 12px; background: none; border: none; color: var(--fg); opacity: 0.7; cursor: pointer; border-radius: 6px; font-size: 13px; font-family: inherit; transition: background 0.15s, opacity 0.15s, color 0.15s; /* Domino-style cascade: each item slides up + fades in with a tiny delay after the previous one (set via nth-last-child below so the BOTTOM item appears first and the cascade rolls upward — visually feels like the menu is "stacking up" from the chevron). The container itself springs in via .overflow-menu's keyframe. */ animation: overflow-item-in 0.32s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; } .overflow-menu:not(.hidden):not(.closing) .overflow-menu-item:nth-last-child(1) { animation-delay: 0.06s; } .overflow-menu:not(.hidden):not(.closing) .overflow-menu-item:nth-last-child(2) { animation-delay: 0.10s; } .overflow-menu:not(.hidden):not(.closing) .overflow-menu-item:nth-last-child(3) { animation-delay: 0.14s; } .overflow-menu:not(.hidden):not(.closing) .overflow-menu-item:nth-last-child(4) { animation-delay: 0.18s; } .overflow-menu:not(.hidden):not(.closing) .overflow-menu-item:nth-last-child(5) { animation-delay: 0.22s; } .overflow-menu:not(.hidden):not(.closing) .overflow-menu-item:nth-last-child(6) { animation-delay: 0.26s; } .overflow-menu:not(.hidden):not(.closing) .overflow-menu-item:nth-last-child(7) { animation-delay: 0.30s; } .overflow-menu:not(.hidden):not(.closing) .overflow-menu-item:nth-last-child(8) { animation-delay: 0.34s; } .overflow-menu:not(.hidden):not(.closing) .overflow-menu-item:nth-last-child(9) { animation-delay: 0.38s; } .overflow-menu:not(.hidden):not(.closing) .overflow-menu-item:nth-last-child(10) { animation-delay: 0.42s; } @keyframes overflow-item-in { 0% { opacity: 0; transform: translateY(10px) translateX(-6px) scale(0.9); } 60% { opacity: 1; } 100% { opacity: 1; transform: translateY(0) translateX(0) scale(1); } } /* Fold-in: items peel off top-down (mirror of the open's bottom-up cascade) so the menu visibly empties before the container scales back into the chevron. */ .overflow-menu.closing .overflow-menu-item { animation: overflow-item-out 0.20s ease-in forwards; } .overflow-menu.closing .overflow-menu-item:nth-child(1) { animation-delay: 0.00s; } .overflow-menu.closing .overflow-menu-item:nth-child(2) { animation-delay: 0.02s; } .overflow-menu.closing .overflow-menu-item:nth-child(3) { animation-delay: 0.04s; } .overflow-menu.closing .overflow-menu-item:nth-child(4) { animation-delay: 0.06s; } .overflow-menu.closing .overflow-menu-item:nth-child(5) { animation-delay: 0.08s; } .overflow-menu.closing .overflow-menu-item:nth-child(6) { animation-delay: 0.10s; } .overflow-menu.closing .overflow-menu-item:nth-child(7) { animation-delay: 0.12s; } .overflow-menu.closing .overflow-menu-item:nth-child(8) { animation-delay: 0.14s; } .overflow-menu.closing .overflow-menu-item:nth-child(9) { animation-delay: 0.16s; } .overflow-menu.closing .overflow-menu-item:nth-child(10) { animation-delay: 0.18s; } @keyframes overflow-item-out { 0% { opacity: 1; transform: translateY(0) translateX(0) scale(1); } 100% { opacity: 0; transform: translateY(6px) translateX(-3px) scale(0.92); } } .overflow-menu-item:hover { opacity: 1; background: color-mix(in srgb, var(--red) 10%, transparent); } #overflow-attach-btn { position: relative; font-weight: 600; } #overflow-attach-btn svg { transition: transform 0.16s cubic-bezier(0.34, 1.56, 0.64, 1); } .overflow-menu-item.active { opacity: 1; color: var(--fg); } .overflow-active-dot { width: 6px; height: 6px; background: var(--fg); border-radius: 50%; margin-left: auto; display: none; flex-shrink: 0; } .overflow-menu-item.active .overflow-active-dot { display: block; } .attach-strip { display: flex; gap: 6px; flex-wrap: wrap; margin: 0 0 8px; min-height: 0; } .attach-strip:empty { display: none; } .attach-strip { display: flex; gap: 6px; flex-wrap: wrap; margin: 6px 0 0; min-height: 32px; padding: 2px; } .attachment-placeholder { display: flex; align-items: center; color: var(--fg); opacity: 0.7; font-style: italic; padding: 4px 8px; border-radius: 4px; background: color-mix(in srgb, var(--fg) 6%, transparent); } .attach-btn { width:32px; display:grid; place-items:center; } .hidden { display:none; } .toggle { position:relative; display:inline-block; width:30px; height:16px; vertical-align:middle; } .toggle input { opacity:0; width:0; height:0; } .toggle .slider { position:absolute; cursor:pointer; top:0; left:0; right:0; bottom:0; background:color-mix(in srgb, var(--fg) 15%, transparent); transition:background .08s; border-radius:8px; } .toggle .slider:before { position:absolute; content:""; height:12px; width:12px; left:2px; top:2px; background:var(--panel); border-radius:50%; transform:translateX(0); transition:transform .08s; box-shadow:0 1px 2px rgba(0,0,0,0.25); } .toggle input:checked + .slider { background:var(--red); } .toggle input:checked + .slider:before { transform:translateX(14px); } .copy-btn { position:absolute; top:6px; right:6px; background:var(--bg); color:var(--fg); border:1px solid var(--border); border-radius:6px; font-size:12px; height:24px; padding:0 8px; cursor:pointer; opacity:0; transition:.15s; } .msg:hover .copy-btn { opacity:1; } .role-timestamp { font-size:0.7rem; color:var(--color-muted-alt); font-weight:normal; margin-left:6px; } .msg-footer { display:flex; align-items:center; gap:6px; flex-wrap: wrap; margin-top:6px; color:var(--color-muted-alt); font-size:0.75rem; position: relative; } .msg-footer .timestamp { font-size:0.75rem; color:var(--color-muted-alt); margin:0; opacity:1; } .msg-footer .response-metrics { font-size:0.75rem; color:var(--color-muted-alt); transition: color .15s; } .msg-footer .response-metrics:hover { color:var(--fg); } /* Context usage ring — right side of footer */ .ctx-ring { display: inline-flex; align-items: center; gap: 3px; margin-left: auto; line-height: 0; opacity: 0.6; cursor: default; transition: opacity 0.15s; --ctx-stroke: var(--color-muted, #888); } .ctx-ring .ctx-ring-pct { color: var(--color-muted, #888); transition: color 0.1s ease; } .ctx-ring svg circle { transition: stroke 0.1s ease; } .ctx-ring:hover { opacity: 1; --ctx-stroke: var(--ctx-color); } .ctx-ring:hover .ctx-ring-pct { color: var(--ctx-color); } .ctx-ring-pct { font-size: 0.7rem; font-weight: 600; line-height: 1; } /* Context detail popup */ .ctx-detail-popup { position: fixed; z-index: 200; background: var(--panel, var(--bg)); border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px; box-shadow: 0 4px 20px rgba(0,0,0,0.4); min-width: 220px; max-width: 280px; font-size: 0.85rem; color: var(--fg); } .ctx-bar-wrap { width: 100%; height: 6px; background: var(--border); border-radius: 4px; overflow: hidden; } .ctx-bar-fill { height: 100%; border-radius: 4px; transition: width 0.3s; } .ctx-compact-btn { display: block; width: 100%; margin-top: 10px; padding: 6px 0; background: none; border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 0.8rem; cursor: pointer; transition: border-color 0.15s, color 0.15s; } .ctx-compact-btn:hover { border-color: var(--accent, var(--red)); color: var(--accent, var(--red)); } .ctx-compact-btn:disabled { opacity: 0.5; cursor: default; } .compact-wave { display: inline-block; color: var(--accent, var(--red)); letter-spacing: 1px; font-size: 0.9em; } /* Memory-used indicator pill */ .memory-used-pill { display: inline-flex; align-items: center; background: var(--panel); border: 1px solid var(--border); color: var(--fg); font-size: 0.7rem; padding: 2px 8px; border-radius: 10px; cursor: pointer; opacity: 0.6; transition: opacity 0.15s, background 0.15s; white-space: nowrap; position: relative; z-index: 1; } .memory-used-pill:hover { opacity: 1; background: var(--border); } /* Nudge label text 1px down so it visually centers with the icon. */ .memory-used-pill-text { position: relative; top: 1px; display: inline-block; } .memory-used-detail { position: fixed; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 6px 8px; min-width: 200px; max-width: min(360px, calc(100vw - 16px)); max-height: 50vh; overflow-y: auto; z-index: 300; box-shadow: 0 4px 12px rgba(0,0,0,0.3); font-size: 0.75rem; } .memory-used-row { display: flex; align-items: flex-start; gap: 6px; padding: 3px 0; line-height: 1.3; } .memory-used-row + .memory-used-row { border-top: 1px solid color-mix(in srgb, var(--border) 50%, transparent); } .memory-used-badge { flex-shrink: 0; font-size: 0.7rem; width: 16px; text-align: center; } .memory-used-badge.pinned { color: var(--red); } .memory-used-badge.recalled { color: var(--fg); opacity: 0.5; } .memory-used-text { color: var(--fg); opacity: 0.85; } .msg-actions { display:inline-flex; align-items:center; gap:4px; } .footer-copy-btn { background:none; border:none; color:var(--color-muted-alt); cursor:pointer; padding:2px 6px; border-radius:4px; transition: color .15s; line-height:1; display: inline-flex; align-items: center; justify-content: center; } .footer-copy-btn:hover { color:var(--accent); } /* Delete action — same chrome as copy/download/edit but hover reveals the destructive red tint so the user can tell it's not a benign op. */ .footer-delete-btn:hover { color: var(--red); } .regen-btn { background:none; border:none; color:var(--color-muted-alt); font-size:1.1rem; cursor:pointer; padding:2px 6px; border-radius:4px; transition: color .15s; line-height:1; } .regen-btn:hover { color:var(--accent); } .fork-btn { background:none; border:none; color:var(--color-muted-alt); font-size:1.1rem; cursor:pointer; padding:2px 6px; border-radius:4px; transition: color .15s; line-height:1; } .fork-btn:hover { color:var(--accent); } .msg-action-btn { background:none; border:none; color:var(--muted, var(--color-muted-alt)); font-size:1.1rem; cursor:pointer; padding:2px 6px; border-radius:4px; transition: color .15s; line-height:1; } .msg-action-btn:hover { color:var(--accent); } .msg-action-btn[data-action="shorten"] { position:relative; top:1px; font-size:1.25rem; } .msg-delete-btn { position:relative; top:1px; } .msg-delete-btn:hover { color:var(--red); } .msg-more-btn { font-size:0.7rem; letter-spacing:0.5px; } .msg-overflow-menu { position:fixed; background:var(--panel); border:1px solid var(--border); border-radius:6px; padding:4px; box-shadow:0 4px 16px rgba(0,0,0,0.4); z-index:100; min-width:150px; } .msg-overflow-item { display:flex; align-items:center; gap:6px; width:100%; background:none; border:none; border-bottom:1px solid color-mix(in srgb, var(--border) 40%, transparent); color:var(--fg); font-size:0.8rem; padding:5px 8px; border-radius:4px; cursor:pointer; text-align:left; font-family:inherit; transition:background .1s; } .msg-overflow-item:last-child { border-bottom:none; } .msg-overflow-item:hover { background:color-mix(in srgb, var(--red) 8%, transparent); } .overflow-icon { width:16px; text-align:center; flex-shrink:0; font-size:1rem; } .variant-nav { display:inline-flex; align-items:center; gap:0px; margin-left:auto; font-size:0.85em; font-family:inherit; opacity:0.6; } .variant-divider { opacity:0.25; margin:0 4px 0 2px; } .variant-tag { font-size:1.1em; opacity:0.8; margin-right:2px; position:relative; top:-1px; } .variant-tag-scissors { top:-1px; } /* The ✂ "Rewrite shorter" action button glyph sits a touch low against the other footer icons — nudge it up 2px. */ .msg-action-btn[data-action="shorten"] { position: relative; top: -2px; } /* The "?" Explain-simpler glyph also sits slightly low — nudge it up 1px. */ .msg-action-btn[data-action="explain"] { position: relative; top: -1px; } .variant-btn { background:none; border:none; color:var(--fg); cursor:pointer; padding:4px 6px; font-size:1em; font-family:inherit; opacity:0.7; line-height:1; } .variant-btn:hover { opacity:1; color:var(--fg); } .variant-btn:disabled { opacity:0.3; cursor:default; } .variant-num { background:none; border:none; color:var(--fg); cursor:pointer; padding:4px 4px; font-size:1em; font-family:inherit; opacity:0.7; line-height:1; } .variant-num:hover { opacity:1; color:var(--fg); } .variant-num:disabled { opacity:0.5; cursor:default; } .variant-slash { opacity:0.4; font-size:1em; } .continue-separator { display:inline; opacity:0.35; font-size:0.85em; font-style:italic; } .stopped-indicator { color:var(--red); margin-top:8px; font-size:0.85em; opacity:0.8; display:flex; align-items:center; gap:8px; flex-wrap:wrap; } /* Message edit UI */ .msg-edit-textarea { background:var(--bg); color:var(--fg); border:1px solid var(--border); border-radius:6px; padding:10px; font-family:inherit; font-size:inherit; line-height:1.6; resize:vertical; box-sizing:border-box; } .msg-edit-textarea:focus { outline:1px solid var(--hl-function, #61afef); border-color:var(--hl-function, #61afef); } .msg-edit-bar { display:flex; gap:8px; margin-top:6px; } .msg-edit-save, .msg-edit-cancel { background:var(--bg); color:var(--fg); border:1px solid var(--border); border-radius:6px; padding:4px 14px; cursor:pointer; font-size:0.85em; } .msg-edit-save:hover { border-color:var(--color-save-green, #4caf50); color:var(--color-save-green, #4caf50); } .msg-edit-cancel:hover { border-color:var(--red); color:var(--red); } /* Edited indicator — similar to stopped-indicator */ .edited-indicator { color:var(--fg); margin-top:8px; font-size:0.85em; opacity:0.4; font-style:italic; } .continue-btn { background:none; border:none; color:var(--fg); opacity:0.5; cursor:pointer; font-size:2.6em; padding:2px 2px 0; line-height:1; } .continue-btn:hover { opacity:0.8; } .ctx-indicator { display:inline-flex; align-items:center; gap:1px; font-size:0.75rem; } .ctx-popup { position:fixed; z-index:250; background:var(--panel); border:1px solid var(--border); border-radius:8px; padding:10px 14px; font-size:0.8rem; color:var(--fg); box-shadow:0 8px 24px rgba(0,0,0,0.4); min-width:180px; line-height:1.7; } .ctx-label { display:inline-block; width:60px; color:var(--color-muted-alt); font-size:0.75rem; } .edit-btn { background:none; border:none; color:var(--color-muted-alt); font-size:1.1rem; cursor:pointer; padding:2px 6px; border-radius:4px; transition: color .15s; line-height:1; } .edit-btn:hover { color:var(--accent); } .edit-textarea { width:100%; background:var(--bg); color:var(--fg); border:1px solid var(--border); border-radius:6px; padding:8px; font-family:inherit; font-size:0.95rem; resize:vertical; outline:none; min-height:80px; } .edit-textarea:focus { border-color:var(--red); } .edit-save-btn, .edit-cancel-btn { background:var(--bg); color:var(--fg); border:1px solid var(--border); border-radius:6px; padding:4px 12px; cursor:pointer; font-size:0.8rem; } .edit-save-btn:hover { background:var(--panel); } .edit-cancel-btn:hover { background:var(--panel); } pre .copy-code { position:absolute; right:6px; background:var(--bg); color:var(--fg); border:1px solid var(--border); border-radius:6px; width:28px; height:28px; padding:0; cursor:pointer; opacity:0; transition: opacity .15s, color .15s, border-color .15s; display:flex; align-items:center; justify-content:center; } pre .copy-code { top:6px; } pre .copy-code.bottom { top:auto; bottom:6px; } pre:hover .copy-code { opacity:0.7; } pre .copy-code:hover { opacity:1; } pre .copy-code.copied { opacity: 1; color: var(--color-save-green, #4caf50); border-color: var(--color-save-green, #4caf50); background: color-mix(in srgb, var(--color-save-green, #4caf50) 18%, var(--bg)); animation: code-copy-pulse 0.36s cubic-bezier(0.34, 1.56, 0.64, 1); } @keyframes code-copy-pulse { 0% { transform: scale(1); } 50% { transform: scale(1.15); } 100% { transform: scale(1); } } /* Slim text-button variant: swap "Copy" → "✓ Copied" via the ::before content while still inheriting the green flash + pulse. */ pre.pre-compact .copy-code.copied::before { content: '✓ Copied'; } /* Edit code button — positioned left of copy button */ pre .edit-code { position:absolute; right:42px; top:6px; background:var(--bg); color:var(--fg); border:1px solid var(--border); border-radius:6px; width:28px; height:28px; padding:0; cursor:pointer; opacity:0; transition: opacity .15s, color .15s, border-color .15s; display:flex; align-items:center; justify-content:center; } pre .edit-code.bottom { top:auto; bottom:6px; } pre:hover .edit-code { opacity:0.7; } pre .edit-code:hover { opacity:1; } /* When the edit-code button is in "save" mode (checkmark), use the theme accent so it matches the EDITING outline + label that are also accent-coloured — clearer that this is the confirm action. */ pre .edit-code.active { opacity: 1; color: var(--accent-primary, var(--red)); border-color: var(--accent-primary, var(--red)); background: color-mix(in srgb, var(--accent-primary, var(--red)) 12%, var(--bg)); } pre .use-code { position:absolute; right:42px; top:6px; background:var(--bg); color:var(--fg); border:1px solid var(--border); border-radius:6px; width:28px; height:28px; padding:0; cursor:pointer; opacity:0; transition: opacity .15s, color .15s, border-color .15s; display:flex; align-items:center; justify-content:center; } pre .use-code.bottom { top:auto; bottom:6px; } pre:hover .use-code { opacity:0.7; } pre .use-code:hover { opacity:1; } pre .use-code.used { opacity: 1; color: var(--color-save-green, #4caf50); border-color: var(--color-save-green, #4caf50); background: color-mix(in srgb, var(--color-save-green, #4caf50) 18%, var(--bg)); animation: code-copy-pulse 0.36s cubic-bezier(0.34, 1.56, 0.64, 1); } .setup-trigger-link, .setup-clickable-provider, .setup-clickable-code { transition: color 0.15s ease, opacity 0.15s ease; } .setup-trigger-link:hover, .setup-clickable-provider:hover, .setup-clickable-code:hover { color: var(--accent, var(--red)) !important; opacity: 0.9; } /* Tapping the code body (not a button) toggles the overlay buttons off so they stop covering the text on touch screens. Tap again to bring back. */ pre.buttons-hidden .copy-code, pre.buttons-hidden .edit-code, pre.buttons-hidden .run-code { opacity:0 !important; pointer-events:none !important; } /* Editing state — subtle border on the code block */ /* Editing state: was a 1px subtle outline that was almost invisible on mobile, so users couldn't tell their tap-to-edit had actually engaged. Use the accent colour + a tinted background so it reads at a glance. */ pre.editing { outline: 2px solid var(--accent-primary, var(--red)); outline-offset: -2px; background: color-mix(in srgb, var(--accent-primary, var(--red)) 6%, var(--bg)) !important; } pre.editing code.editing { outline:none; cursor:text; } pre.editing::before { content: 'EDITING'; position: absolute; top: 0; left: 0; padding: 2px 8px; font-size: 9px; font-weight: 700; letter-spacing: 0.5px; background: var(--accent-primary, var(--red)); color: #fff; border-radius: 0 0 4px 0; z-index: 2; pointer-events: none; } /* Run code button — positioned left of edit button */ pre .run-code { position:absolute; right:78px; top:6px; background:var(--bg); color:var(--fg); border:1px solid var(--border); border-radius:6px; width:28px; height:28px; padding:0; cursor:pointer; opacity:0; transition: opacity .15s, color .15s, border-color .15s; display:flex; align-items:center; justify-content:center; } pre .run-code.bottom { top:auto; bottom:6px; } pre:hover .run-code { opacity:0.7; } pre .run-code:hover { opacity:1; color:var(--hl-function, #61afef); border-color:var(--hl-function, #61afef); } /* Compact (single-line) code blocks: slim buttons so the row doesn't double the height of a 1-line bash. Copy is text ("Copy" → "✓ Copied"), Run and Edit keep their icons but at smaller sizes. Edit swaps to a "Save" text label when its .active state is on. */ pre.pre-compact { padding-right: 200px; min-height: 0; } pre.pre-compact .copy-code, pre.pre-compact .edit-code, pre.pre-compact .run-code { height: 20px; padding: 0; font-size: 10px; font-weight: 500; line-height: 20px; top: 3px; } /* Copy: text-only, hide SVG */ pre.pre-compact .copy-code { width: auto; padding: 0 8px; right: 6px; gap: 0; } pre.pre-compact .copy-code svg { display: none; } pre.pre-compact .copy-code::before { content: 'Copy'; } pre.pre-compact .copy-code.copied::before { content: '✓ Copied'; } /* Edit: icon + "Edit" label, swap to "Save" when editing */ pre.pre-compact .edit-code { width: auto; padding: 0 8px 0 6px; gap: 3px; right: 64px; } pre.pre-compact .edit-code svg { width: 12px; height: 12px; } pre.pre-compact .edit-code::after { content: 'Edit'; } pre.pre-compact .edit-code.active::after { content: 'Save'; } /* Run: icon + "Run" label */ pre.pre-compact .run-code { width: auto; padding: 0 8px 0 6px; gap: 3px; right: 126px; } pre.pre-compact .run-code svg { width: 12px; height: 12px; } pre.pre-compact .run-code::after { content: 'Run'; } /* Bottom-positioned slim buttons (when pre is near the top of the viewport, the existing JS toggles .bottom to flip them down). */ pre.pre-compact .copy-code.bottom, pre.pre-compact .edit-code.bottom, pre.pre-compact .run-code.bottom { top: auto; bottom: 3px; } /* Touch devices: no hover, so always show copy/run/edit buttons */ @media (hover: none) { pre .copy-code { opacity:0.7; } pre .edit-code { opacity:0.7; } pre .run-code { opacity:0.7; } } /* Code runner output panel */ .code-runner-output { position:relative; border:1px solid var(--border); border-top:2px solid var(--hl-function, #61afef); border-radius:0 0 4px 4px; background:var(--bg); margin:-4px 0 8px 0; padding:8px 12px; max-height:400px; overflow:auto; } .code-runner-pre { margin:0; padding:0; font-family:'Fira Code', 'Courier New', monospace; font-size:0.9em; line-height:1.5; white-space:pre-wrap; word-break:break-word; color:var(--fg); background:none !important; border:none !important; } .code-runner-error { color:var(--red); } .code-runner-loading { font-style:italic; color:var(--red); padding:4px 0; } .code-runner-close { position:absolute; top:4px; right:4px; background:none; border:none; color:var(--fg); cursor:pointer; opacity:0.5; font-size:14px; padding:2px 6px; } .code-runner-close:hover { opacity:1; } /* Labeled copy pill — pinned top-right INSIDE the run-output panel, not in a separate footer. Panel is position:relative so absolute works. */ .code-runner-copy-inline { position: absolute; top: 6px; right: 32px; /* sits LEFT of the X close (top:4 right:4 ~24px wide) */ z-index: 2; background: var(--panel); color: var(--fg); border: 1px solid var(--border); border-radius: 6px; padding: 3px 10px; font-size: 11px; cursor: pointer; display: inline-flex; align-items: center; transition: border-color 0.15s, color 0.15s, background 0.15s; } .code-runner-copy-inline:hover { border-color: var(--accent-primary, var(--red)); color: var(--accent-primary, var(--red)); } /* Reserve room on the right so the output text doesn't slide under either the Copy pill or the Close X. Applies to both the chat run panel AND the document panel (doc-run-output reuses the same children). */ .code-runner-output, .doc-run-output { padding-right: 110px; } .toast { position:fixed; top: 16px; right: 16px; left: auto; bottom: auto; background:var(--panel); color:var(--fg); border:1px solid color-mix(in srgb, var(--accent) 30%, transparent); border-left: 3px solid var(--accent); padding:8px 12px; border-radius:6px; font-size:12px; opacity:0; /* Off-screen to the right by default; .show slides to 0; removing .show transitions to -120% (off-screen left). */ transform: translateX(120%); transition: opacity .35s cubic-bezier(0.22, 1, 0.36, 1), transform .45s cubic-bezier(0.22, 1, 0.36, 1); z-index: 9999; pointer-events: none; box-shadow: 0 4px 12px rgba(0,0,0,0.2); backdrop-filter: blur(12px); max-width: min(360px, calc(100vw - 32px)); min-width: min(220px, calc(100vw - 32px)); min-height: 34px; display: inline-flex; align-items: center; box-sizing: border-box; } .toast.show { opacity:1; transform: translateX(0); } .toast .toast-checkmark { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; margin-right: 7px; color: var(--green, #50fa7b); vertical-align: -3px; transform: scale(0.65); opacity: 0; animation: toastCheckPop 360ms cubic-bezier(0.2, 0.9, 0.25, 1.25) forwards; } .toast .toast-checkmark svg polyline { stroke-dasharray: 24; stroke-dashoffset: 24; animation: toastCheckDraw 420ms ease-out 120ms forwards; } @keyframes toastCheckPop { 0% { opacity: 0; transform: scale(0.65); } 65% { opacity: 1; transform: scale(1.16); } 100% { opacity: 1; transform: scale(1); } } @keyframes toastCheckDraw { to { stroke-dashoffset: 0; } } .toast.exiting { opacity: 0; transform: translateX(-120%); } .toast.error { border-color: color-mix(in srgb, var(--color-error) 40%, transparent); border-left-color: var(--color-error); color: var(--color-error); } /* When the notes panel is docked to the right, the default top-right toast sits directly over the Archive / View-toggle buttons in the panel header. Flip it to the top-left so you can still reach the header after an archive action. Mirror the slide direction too (enter from left, exit to right) so the motion still reads naturally. */ body:has(.notes-pane.modal-right-docked) .toast { right: auto; left: 16px; transform: translateX(-120%); } body:has(.notes-pane.modal-right-docked) .toast.show { transform: translateX(0); } body:has(.notes-pane.modal-right-docked) .toast.exiting { transform: translateX(120%); } @media (max-width: 768px) { .toast { top: 12px; right: 12px; max-width: calc(100vw - 24px); /* Receive touches so the swipe-to-dismiss gesture works (desktop keeps pointer-events:none so the toast never blocks clicks). Horizontal pan only, so it doesn't fight page scroll. */ pointer-events: auto; touch-action: pan-x; } } .stop-btn { position:absolute; top:2px; right:2px; background:var(--panel); color:var(--fg); border:1px solid var(--fg); font-family:inherit; font-size:1em; line-height:1; padding:2px 5px; cursor:pointer; } .small-note { font-size:12px; color:color-mix(in srgb, var(--fg) 60%, transparent); margin-top:4px; } .row-end { justify-content:flex-end; } .model-chat-btn { height:32px; padding:0 10px; margin-left:auto; } /* Nudge the "+ Chat" label down 1px to sit centered in the button. */ .model-chat-btn-label { position: relative; top: 1px; } .openai-row { display:flex; align-items:center; gap:6px; } .models-row { display:flex; align-items:center; gap:6px; border:1px solid var(--border); padding:4px; margin:4px 0; border-radius: 4px; } .models-row .grow, .models-row select { flex:1; display:flex; align-items:center; font-size: 9.75px; } .model-fav-btn { width: 8px; height: 8px; border-radius: 50%; border: 1.5px solid color-mix(in srgb, var(--fg) 22%, transparent); flex-shrink: 0; cursor: pointer; transition: all 0.15s; position: relative; margin-left: 4px; } .model-fav-btn::before { content: ''; position: absolute; top: -10px; left: -10px; right: -10px; bottom: -10px; } .model-fav-btn:hover { border-color: var(--fg); background: color-mix(in srgb, var(--fg) 27%, transparent); transform: scale(1.3); } .model-fav-btn.active { background: var(--fg); border-color: var(--fg); } .model-fav-btn.active:hover { opacity: 0.6; } .model-search-input { width: 100%; padding: 6px 10px; margin-bottom: 4px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--fg); font-family: inherit; font-size: 0.8rem; outline: none; box-sizing: border-box; transition: border-color 0.15s; } .model-search-input:focus { border-color: var(--red); } .model-search-input::placeholder { color: color-mix(in srgb, var(--fg) 30%, transparent); } .models-row button { font-size: 9px; height: 24px; padding: 0 8px; } @media (max-width:768px){ .box { max-height:none; } .chat-container { padding:10px; flex:1; margin-top:0; padding-top:42px; min-height:0; } .scroll-nav-btn { width:44px; height:44px; font-size:14px; margin-bottom:0; } .send-btn { width:48px; height:48px !important; border-radius:12px; } .send-btn svg { width:22px; height:22px; } #compare-toggle-btn { display:none !important; } .section[draggable] { -webkit-user-drag:none; } .drag-handle, .item-drag-handle, .folder-drag-handle { display:none !important; } /* Sidebar overlays chat on mobile */ .sidebar { position: fixed !important; top: 0; bottom: 0; left: 0; z-index: 200; width: 80% !important; max-width: 340px; box-shadow: 4px 0 20px rgba(0,0,0,0.5); transition: transform 0.25s ease, opacity 0.25s ease !important; opacity: 1 !important; overflow: visible !important; } .sidebar.hidden { width: 80% !important; transform: translateX(-100%); pointer-events: none; overflow: hidden !important; } .sidebar.right-side.hidden { transform: translateX(100%); } .sidebar.right-side { left: auto; right: 0; box-shadow: -4px 0 20px rgba(0,0,0,0.5); } /* Backdrop behind sidebar */ #sidebar-backdrop { position: fixed; inset: 0; z-index: 199; background: rgba(0,0,0,0.4); opacity: 0; pointer-events: none; transition: opacity 0.35s ease; } #sidebar-backdrop.visible { opacity: 1; pointer-events: auto; } /* Elastic overscroll on sidebar */ .sidebar-inner { -webkit-overflow-scrolling: touch; overscroll-behavior-y: auto; overflow-y: scroll !important; padding-bottom: 40px; } .sidebar:not(.hidden) { overscroll-behavior: auto; } /* Sidebar header — room for hamburger */ .sidebar-header { padding: 18px 12px 8px 12px; min-height: 44px; } .sidebar-brand-title { font-size: 1.2rem; left: 0 !important; } /* Sidebar inner — bigger spacing */ .sidebar-inner { padding: 12px 10px 12px !important; gap: 4px !important; } /* Section headers — match list item sizing */ .section-header-flex { padding: 12px 10px !important; height: 48px !important; border-radius: 8px; box-sizing: border-box; } .section-header-flex h4, .section-header-flex .section-title { font-size: 14px !important; gap: 11px !important; } .section-icon, .sidebar-action-icon { width: 16px !important; height: 16px !important; left: 0 !important; } /* List items — bigger touch targets, bigger text */ .list-item { padding: 12px 10px !important; min-height: 48px; font-size: 14px; border-radius: 8px; gap: 10px !important; } .list-item .grow { font-size: 14px !important; } /* Search, New Chat & Assistant */ #sidebar-search-btn, #sidebar-new-chat-btn, .sidebar-assistant-entry { padding: 12px 10px !important; min-height: 48px !important; } #sidebar-search-btn .grow, #sidebar-new-chat-btn .grow, .sidebar-assistant-entry .grow { font-size: 14px !important; left: 0 !important; } /* Section separator — more breathing room */ .section { margin-top: 4px; border-radius: 8px; } /* Wave accent — slightly bigger on mobile */ .section-header-flex h4::before { height: 18px; } /* Compact top bar on mobile — align with sidebar header */ .chat-top-bar { padding: 1px 8px; min-height: 14px; margin-top: -31px; padding-top: 31px; } .chat-top-bar .chat-new-btn { display: none; } .chat-meta-overlay { font-size: 0.65em; max-width: 55%; overflow: visible; left: 50%; top: 50%; transform: translate(-50%, -50%); position: absolute; } .chat-meta-overlay .export-dl-btn { display: inline-flex; } /* Incognito — smaller on mobile */ .incognito-btn { padding: 4px 10px; font-size: 10px; } /* Incognito indicator — right side next to hamburger on mobile */ .incognito-indicator { position: fixed; top: 12px; left: auto !important; right: 48px !important; transform: none; width: 32px; height: 32px; z-index: 210; opacity: 0.8; } /* Icon rail on mobile — hidden by default, shown in mini-sidebar state */ .icon-rail { display: none !important; } .icon-rail.mobile-mini { display: flex !important; position: fixed; top: 0; bottom: 0; left: 0; z-index: 200; width: 48px; box-shadow: 2px 0 12px rgba(0,0,0,0.4); } .icon-rail.mobile-mini.right-side { left: auto; right: 0; box-shadow: -2px 0 12px rgba(0,0,0,0.4); } /* Chat bubbles — AI stretches full width, user stays compact */ .msg { font-size: 0.85em; } .msg .body { font-size: 0.9em; } .msg-user { max-width: 90% !important; margin-left: auto; margin-right: 4px; } .msg-ai, .agent-thread { width: 100% !important; max-width: 100% !important; margin: 8px 0; } /* Prevent inner scrollable elements from trapping vertical scroll */ .msg pre, .agent-tool-output pre, .agent-thread-cmd, .msg details { overflow-y: hidden !important; max-height: none !important; overflow-x: auto; -webkit-overflow-scrolling: touch; touch-action: pan-y pan-x; } /* Models row — match list items */ .models-row { padding: 12px 10px; min-height: 48px; } /* Input icon buttons — bigger touch targets */ .input-icon-btn { padding: 10px; min-width: 44px; min-height: 44px; } /* Tool indicators — icon only on mobile (hide text, keep x) */ .tool-indicator > span { display: none !important; } .tool-indicator .tool-indicator-x { display: inline-block !important; opacity: 0.6; } /* Hamburger — always right on mobile */ .hamburger-btn { width: 44px; height: 44px; top: 6px; left: auto !important; right: 4px !important; -webkit-tap-highlight-color: transparent; } .hamburger-btn:hover, .hamburger-btn:active { background: none !important; border: none !important; box-shadow: none !important; } /* New chat — always left on mobile */ .mobile-new-chat-btn { display: flex; position: fixed; top: 12px; left: 8px; z-index: 210; width: 32px; height: 32px; align-items: center; justify-content: center; background: none; border: none; color: var(--fg); cursor: pointer; opacity: 0.5; padding: 0; } /* Modal close button — bigger on mobile */ .close-btn, .modal-close { min-width: 44px; min-height: 44px; width: 44px; height: 44px; font-size: 14px; } /* Code block buttons — slightly bigger touch targets */ pre .copy-code, pre .edit-code, pre .run-code { width: 44px; height: 44px; } /* Touch-friendly targets for small buttons */ .export-dl-btn { min-width: 44px; min-height: 44px; } .section-header-btn { min-width: 44px; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; } /* 44×44 touch buttons starting from right:6 ends at 50. ~8px gap is enough to be distinguishable without spreading them too far apart. */ pre .edit-code { right: 58px; } pre .run-code { right: 110px; } /* Dropdowns — bigger touch targets on mobile */ .dropdown-item-compact { padding: 12px 12px !important; font-size: 14px !important; min-height: 44px; gap: 10px !important; } .dropdown-item-compact .dropdown-icon { width: 18px !important; height: 18px !important; } .dropdown-item-compact .dropdown-icon svg { width: 16px !important; height: 16px !important; } .dropdown, .session-dropdown { padding: 6px !important; border-radius: 12px !important; } /* Safe area padding for notched devices */ .chat-input-bar { padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px)); } /* Mode toggle — larger touch targets */ .mode-toggle { height: 34px; border-radius: 12px; } .mode-toggle::before { border-radius: 11px; } .mode-toggle-btn { padding: 0 14px; font-size: 12px; min-height: 34px; } #mode-agent-btn { border-radius: 12px 0 0 12px; } #mode-chat-btn { border-radius: 0 12px 12px 0; } /* Diff mode — stack toolbar, bigger buttons */ .diff-toolbar { padding: 8px 12px; gap: 6px; flex-wrap: wrap; } .diff-toolbar-btn { padding: 6px 12px; font-size: 12px; min-height: 36px; } .diff-chunk-btn { width: 28px; height: 28px; font-size: 14px; } /* Document/email/gallery/research library — full-height bottom-sheet on mobile. Keep the rounded top corners + border-top so they match the cookbook/calendar/compare look instead of looking like raw full-bleed panels. */ .doclib-modal-content, .gallery-modal-content { width: 100vw !important; max-width: 100vw !important; /* vh fallback, dvh override so the modal adapts to mobile URL-bar show/hide. Order matters: later same-specificity rule wins, so dvh must come after vh. */ max-height: 100vh !important; max-height: 100dvh !important; height: 100vh; height: 100dvh; border-radius: 14px 14px 0 0 !important; border: none !important; border-top: 1px solid var(--border) !important; box-shadow: none !important; padding: 6px !important; padding-bottom: env(safe-area-inset-bottom, 6px) !important; margin: 0 !important; overflow-y: auto; -webkit-overflow-scrolling: touch; } #email-lib-modal .doclib-grid { max-height: none; flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; } /* Library modal layout: pin the header + tab strip, give the active panel (admin-card) the remaining height, and let the grid scroll internally. The load-more button sits below the grid as a sibling inside admin-card so it stays visible at the bottom of the panel without competing with the grid for scroll. */ #doclib-modal .doclib-modal-content { display: flex !important; flex-direction: column !important; overflow: hidden !important; } #doclib-modal .modal-header, #doclib-modal .lib-tabs { flex: 0 0 auto !important; } #doclib-modal .modal-body { flex: 1 1 0 !important; min-height: 0 !important; overflow: hidden !important; } #doclib-modal .admin-card { flex: 1 1 0 !important; min-height: 0 !important; } /* :not(:has(.doclib-card-expanded)) scopes these to the normal list state — when a document card is expanded, the existing expand-state rules take over (grid claims flex:1 with overflow:hidden so the expanded card can scroll itself). */ #doclib-modal .doclib-grid:not(:has(.doclib-card-expanded)) { flex: 1 1 0 !important; min-height: 0 !important; max-height: none !important; overflow-y: auto !important; -webkit-overflow-scrolling: touch; } #doclib-modal .doclib-load-more { margin: 8px auto 12px !important; } /* Squeeze a little more height for the preview content by tightening the spacing inside an expanded doc card on mobile. */ #doclib-modal .doclib-card.doclib-card-expanded, #email-lib-modal .doclib-card.doclib-card-expanded, #memory-modal .doclib-card.doclib-card-expanded { gap: 2px !important; padding: 4px 6px !important; height: 100% !important; } /* Skills preview kept stopping at partial height because the flex chain (modal → tabs → panel → admin-card → grid → card) wouldn't reliably hand the card a full-height parent. But the skills LIST already scrolls correctly inside #skills-list, so that grid box already has the right bounded height (the area below the tabs). Anchor the expanded card to THAT box with position:absolute so it fills the list area exactly — header + tabs stay visible. NOTE: position:relative here is UNCONDITIONAL (not gated behind :has(.doclib-card-expanded)). Firefox mobile builds without :has() support never applied the gated rule, so the absolute card lost its anchor and only filled ~50% — while Chromium/desktop-narrow worked. Relative-when-collapsed is harmless (no abs children then). */ #memory-modal #skills-list.doclib-grid { position: relative !important; } #memory-modal .doclib-card.skill-card.doclib-card-expanded { position: absolute !important; inset: 0 !important; /* Height is set in JS (skills.js _fillSkillCardHeight) as an explicit px value — Firefox does NOT treat inset:0 stretch OR height:100% (against the flex-sized grid) as a definite height, so grid/flex children never filled. An explicit px height is unambiguous. */ margin: 0 !important; padding: 8px 10px calc(8px + env(safe-area-inset-bottom, 0px)) !important; background: var(--bg) !important; /* Flex column. JS (_fillSkillCardHeight) sets EXPLICIT px heights on the preview +
; in a flex column an explicit-height item
           (flex:0 0 auto + height) is honoured. (Grid was worse here — the
           collapsed 1fr track overrode the preview's explicit height.) */
        display: flex !important;
        flex-direction: column !important;
        overflow: hidden !important;
        border: none !important;
        border-radius: 0 !important;
        box-sizing: border-box !important;
      }
      /* Preview is the 1fr grid track — give it a definite-height flex column
         so the 
 (flex:1) fills and the footer pins at the bottom. */
      #memory-modal .doclib-card.skill-card.doclib-card-expanded > .doclib-card-preview {
        display: flex !important;
        flex-direction: column !important;
        min-height: 0 !important;
        overflow: hidden !important;
      }
      /* The card now fills the screen, but a base rule (.skill-card...
         .doclib-card-preview { flex: 0 1 auto }) sizes the preview to its
         CONTENT — so a medium SKILL.md left the preview at ~half the card
         (the "only 50%" the debug confirmed: card was full, content wasn't).
         Force the preview AND the 
 to FILL the card. Extra .skill-card
         in the selector out-specifies both the base and the generic mobile
         rule so this wins without ambiguity. */
      #memory-modal .doclib-card.skill-card.doclib-card-expanded > .doclib-card-preview {
        flex: 1 1 auto !important;
        min-height: 0 !important;
      }
      #memory-modal .doclib-card.skill-card.doclib-card-expanded .skill-md-pre {
        flex: 1 1 auto !important;
        min-height: 0 !important;
      }
      /* Flatten the expanded doc/email card on mobile — drop the inner
         border + background so it doesn't read as a "subwindow" inside the
         modal (the chat preview doesn't have that nested-card look). The
         body prefix beats the desktop email rule later in the file that
         paints a 2px accent border + box-shadow with !important. */
      body #doclib-modal .doclib-card.doclib-card-expanded,
      body #email-lib-modal .doclib-card.doclib-card-expanded,
      body #memory-modal .doclib-card.doclib-card-expanded {
        background: transparent !important;
        border: none !important;
        border-radius: 0 !important;
        box-shadow: none !important;
      }
      /* Same layout pattern the chat preview uses: preview itself clips,
         the 
 (or PDF iframe) inside owns the scroll, and the action
         bar pins to the bottom with extra bottom padding so it floats
         above the iOS safe-area / home-indicator strip. */
      #doclib-modal .doclib-card.doclib-card-expanded .doclib-card-preview,
      #email-lib-modal .doclib-card.doclib-card-expanded .doclib-card-preview,
      #memory-modal .doclib-card.doclib-card-expanded .doclib-card-preview {
        padding: 4px 6px 20px !important;
        flex: 1 1 auto !important;
        min-height: 82dvh !important;
        overflow: hidden !important;
        display: flex !important;
        flex-direction: column !important;
        border-top: none !important;
      }
      /* The "Load more" button is flex-shrink:0 and sits in the panel AFTER the
         grid, so even when a card is expanded it reserves ~40px at the panel
         bottom — shrinking the visible grid and clipping the action bar by that
         much (the "black bar"). The global collapse rule's max-height:0 can't
         remove a flex-shrink:0 item, so hide it outright on expand. */
      #doclib-modal [data-doclib-panel="documents"]:has(.doclib-card-expanded) .doclib-load-more,
      #doclib-modal [data-doclib-panel="documents"]:has(.doclib-card-expanded) .doclib-inline-load-more {
        display: none !important;
      }
      /* Small bottom padding now that the load-more no longer steals space —
         the action bar sits low, just clearing the home-indicator safe area. */
      #doclib-modal [data-doclib-panel="documents"] .doclib-card.doclib-card-expanded .doclib-card-preview {
        padding-bottom: calc(18px + env(safe-area-inset-bottom, 0px)) !important;
      }
      #doclib-modal .doclib-card.doclib-card-expanded .doclib-card-preview pre,
      #doclib-modal .doclib-card.doclib-card-expanded .doclib-card-preview .doclib-card-pdf-frame,
      #email-lib-modal .doclib-card.doclib-card-expanded .doclib-card-preview pre,
      #email-lib-modal .doclib-card.doclib-card-expanded .doclib-card-preview .email-reader-body,
      #memory-modal .doclib-card.doclib-card-expanded .doclib-card-preview pre,
      #memory-modal .doclib-card.doclib-card-expanded .doclib-card-preview .skill-md-editor {
        flex: 1 1 auto !important;
        min-height: 0 !important;
        overflow-y: auto !important;
        -webkit-overflow-scrolling: touch;
        /* Strip the code-box visual treatment so the 
 / reader body
           doesn't read as a "sub-window" inside the modal. */
        background: transparent !important;
        border: none !important;
        box-shadow: none !important;
        padding: 0 !important;
        margin: 0 !important;
        border-radius: 0 !important;
      }
      /* The skill editor textarea keeps a light frame on mobile (it's an
         input, not read-only text) — override the strip-down above. */
      #memory-modal .doclib-card.doclib-card-expanded .doclib-card-preview .skill-md-editor {
        background: var(--bg) !important;
        border: 1px solid var(--border) !important;
        padding: 8px !important;
      }
      #doclib-modal .doclib-card.doclib-card-expanded .doclib-card-preview pre code,
      #doclib-modal .doclib-card.doclib-card-expanded .doclib-card-preview code.hljs,
      #email-lib-modal .doclib-card.doclib-card-expanded .doclib-card-preview pre code,
      #email-lib-modal .doclib-card.doclib-card-expanded .doclib-card-preview code.hljs,
      #memory-modal .doclib-card.doclib-card-expanded .doclib-card-preview pre code {
        background: transparent !important;
        border: none !important;
        padding: 0 !important;
        box-shadow: none !important;
      }
      #doclib-modal .doclib-card.doclib-card-expanded .doclib-card-expanded-actions,
      #email-lib-modal .doclib-card.doclib-card-expanded .doclib-card-expanded-actions,
      #memory-modal .doclib-card.doclib-card-expanded .doclib-card-expanded-actions {
        padding: 6px 4px 0 !important;
        margin-top: 4px !important;
        flex-shrink: 0 !important;
      }
      /* Email reader on mobile inherits the library modal's horizontal
         padding (the .doclib-modal-content default — 6px). No extra
         overrides for the modal-content / modal-body / admin-card / grid
         chain; we just clean up the inner email reader header/body
         padding so the From / To lines align with the email subject. */
      #email-lib-modal .doclib-card.doclib-card-expanded .doclib-card-preview .email-reader-header,
      #email-lib-modal .doclib-card.doclib-card-expanded .doclib-card-preview .email-reader-atts,
      #email-lib-modal .doclib-card.doclib-card-expanded .doclib-card-preview .email-reader-body {
        padding-left: 6px !important;
        padding-right: 6px !important;
      }
      /* Mobile email-reader header: meta on the left, actions on the right
         (two stacked rows). The two .email-reader-actions-row siblings
         render as their own flex-row strips so primary (reply/reply-all/
         forward) sits above secondary (AI/summary/more), instead of
         flattening into one line that collided with the recipient chips. */
      #email-lib-modal .email-reader-header,
      .email-reader-tab-modal .email-reader-header,
      .email-window-modal .email-reader-header {
        padding: 8px 8px !important;
        gap: 6px !important;
        flex-direction: row !important;
        align-items: flex-start !important;
      }
      #email-lib-modal .email-reader-actions,
      .email-reader-tab-modal .email-reader-actions,
      .email-window-modal .email-reader-actions {
        display: flex !important;
        flex-direction: column !important;
        align-items: flex-end !important;
        gap: 4px !important;
        margin-left: auto !important;
        flex-shrink: 0 !important;
        position: relative !important;
        top: -3px !important; /* lift the reply/forward/etc. action buttons up on mobile */
      }
      #email-lib-modal .email-reader-actions-row,
      .email-reader-tab-modal .email-reader-actions-row,
      .email-window-modal .email-reader-actions-row {
        display: flex !important;
        flex-direction: row !important;
        flex-wrap: nowrap !important;
        align-items: center !important;
        justify-content: flex-end !important;
        gap: 4px !important;
      }
      #email-lib-modal .email-reader-actions .memory-toolbar-btn.reader-icon-btn,
      .email-reader-tab-modal .email-reader-actions .memory-toolbar-btn.reader-icon-btn,
      .email-window-modal .email-reader-actions .memory-toolbar-btn.reader-icon-btn {
        width: 44px !important;
        height: 44px !important;
        flex: 0 0 auto !important;
        display: inline-flex !important;
        flex-direction: column !important;
        align-items: center !important;
        justify-content: center !important;
        gap: 3px !important;
        padding: 4px 2px !important;
      }
      #email-lib-modal .email-reader-actions .memory-toolbar-btn.reader-icon-btn svg,
      .email-reader-tab-modal .email-reader-actions .memory-toolbar-btn.reader-icon-btn svg,
      .email-window-modal .email-reader-actions .memory-toolbar-btn.reader-icon-btn svg {
        width: 16px !important;
        height: 16px !important;
      }
      /* Gallery-style label under each button — only on mobile. The
         global rule below the @media block hides them on desktop. */
      #email-lib-modal .reader-btn-label,
      .email-reader-tab-modal .reader-btn-label,
      .email-window-modal .reader-btn-label {
        display: inline-block !important;
        font-size: 8.5px !important;
        font-weight: 500 !important;
        line-height: 1 !important;
        letter-spacing: 0.02em !important;
        opacity: 0.75 !important;
        white-space: nowrap !important;
      }
      /* List-view cards keep the framed look from their own .doclib-card /
         .memory-item base styles — no extra grid padding here, so the
         spacing matches the documents/library tab exactly. */
      /* Tighten the top of the email sheet — modal header + description +
         account row + toolbar were stacking with full desktop spacing and
         pushed the email subjects way down. */
      #email-lib-modal .modal-header {
        padding: 4px 8px !important;
        min-height: 0 !important;
      }
      #email-lib-modal .modal-header h4 {
        /* Match the other tool headers (base .modal-header h4 = 1rem); was
           13px, which read noticeably smaller than Calendar/Tasks/etc. */
        font-size: 1rem !important;
        line-height: 1.2 !important;
      }
      #email-lib-modal .modal-body {
        gap: 4px !important;
      }
      #email-lib-modal .admin-card {
        gap: 4px !important;
      }
      #email-lib-modal .admin-card > .doclib-desc {
        display: none !important;
      }
      /* When an email is expanded the list-mode toolbar siblings are
         already hidden by the existing :has rule. Hide the modal-header
         entirely AND zero out the modal-content top padding so the email
         reader claims the full sheet height. The swipe-down gesture (and
         the dock chip) still dismiss the modal. */
      #email-lib-modal:has(.doclib-card-expanded) .modal-header,
      #email-lib-modal.email-reading .modal-header {
        display: none !important;
      }
      #email-lib-modal:has(.doclib-card-expanded) .doclib-modal-content,
      #email-lib-modal.email-reading .doclib-modal-content {
        padding-top: 0 !important;
      }
      #email-lib-modal:has(.doclib-card-expanded) .modal-body,
      #email-lib-modal.email-reading .modal-body {
        gap: 0 !important;
      }
      /* Flatten the From / To bar so it doesn't read as a cut-off framed
         strip with a hard background change. Remove the background, drop
         the border-bottom to a faint divider, and let it blend with the
         sheet. */
      #email-lib-modal .email-reader-header {
        background: transparent !important;
        border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent) !important;
      }
      #email-lib-modal .email-card-nav-btn {
        padding: 6px 10px !important;
        min-width: 40px !important;
        height: 38px !important;
      }
      #email-lib-modal .email-card-nav-btn svg {
        width: 18px !important;
        height: 18px !important;
      }
      /* Nudge the prev/next arrow cluster ~4px to the right so it sits
         comfortably out at the edge instead of crowding the subject text. */
      #email-lib-modal .email-card-nav-arrows {
        transform: translateX(4px);
      }
      /* Done check — extend the actual TAP area via padding + negative
         margin (a transparent ::before doesn't change the parent's hit
         box). Layout stays the same height as library cards because the
         margin cancels the padding visually, but the clickable region is
         ~37px square. */
      #email-lib-modal .email-card-done {
        position: relative !important;
        z-index: 5 !important;
        pointer-events: auto !important;
        touch-action: manipulation !important;
        padding: 12px !important;
        margin: -12px !important;
      }
      #email-lib-modal .email-card-done svg {
        width: 13px !important;
        height: 13px !important;
      }
      /* Make sure no overlay or pseudo intercepts the tap. */
      #email-lib-modal .email-card-done * { pointer-events: none; }
      /* Tighten the From / To meta bar and the email body — they had
         desktop padding (10–14px) that wasted real estate on phones. */
      #email-lib-modal .email-reader-header {
        padding: 6px 4px !important;
        gap: 4px !important;
      }
      #email-lib-modal .email-reader-meta {
        font-size: 11px !important;
      }
      #email-lib-modal .email-reader-meta-row strong {
        min-width: 28px !important;
      }
      #email-lib-modal .email-reader-atts {
        padding: 6px 4px !important;
      }
      #email-lib-modal .email-reader-body {
        padding: 8px 4px !important;
      }
      /* Make sure the grid claims the full admin-card height when a doc
         is expanded — the existing rule sets flex:1, but on mobile we also
         need a hard height fallback because the parent uses dvh which
         desktop-tuned selectors don't always trickle through. */
      #doclib-modal .admin-card:has(.doclib-card-expanded) > .doclib-grid,
      #memory-modal .admin-card:has(.doclib-card-expanded) > .doclib-grid {
        height: 100% !important;
      }
      /* Skills modal: keep the header + tab strip visible; the expanded
         card fills the skills-list area below them via position:absolute
         (see the #skills-list rule above). Just make sure the tab-panel +
         its card give the list its full height. */
      #memory-modal .memory-tab-panel[data-memory-panel="skills"] > .admin-card:has(.doclib-card-expanded) {
        flex: 1 1 auto !important;
        min-height: 0 !important;
      }
      .doclib-card-header {
        padding: 10px 8px;
        gap: 4px;
      }
      .doclib-card-session,
      .doclib-card-time {
        display: none;
      }
      .doclib-card-expanded-actions {
        flex-wrap: wrap;
      }
      /* Keep the footer buttons identical to the chat/research footers on
         mobile too — those have no mobile enlargement, so neither should
         these (otherwise the doc footer reads in a larger/different font). */
      .doclib-card-action-btn {
        font-size: 10px;
        padding: 3px 8px;
      }
      /* Chat top bar — adjusted for reduced height */
      /* Suggestion nav — bigger touch targets */
      .doc-suggestion-nav-btn {
        padding: 6px 8px;
        font-size: 18px;
      }
      .doc-suggestion-close {
        padding: 10px 12px;
        margin: -10px -12px;
      }
    }
    #mobile-backdrop, #mobile-menu-btn { display:none !important; }
    #sidebar-backdrop { display:none !important; }
    /* ----- Loading spinner ----- */
    @keyframes spin {
      to { transform: rotate(360deg); }
    }
    .spinner {
      width: 24px;
      height: 24px;
      margin: 8px auto;
      border: 3px solid var(--border);
      border-top-color: var(--red);
      border-radius: 50%;
      animation: spin 0.9s linear infinite;
    }
    /* Inline spinner for buttons */
    .btn-spinner {
      display: inline-block;
      width: 12px;
      height: 12px;
      border: 2px solid transparent;
      border-top-color: currentColor;
      border-radius: 50%;
      animation: spin 1s linear infinite;
      margin-right: 6px;
    }
    .search-status {
      font-size: 0.85em;
      color: var(--red);
      margin-top: 4px;
      padding: 4px;
      border-left: 2px solid var(--red);
      background: color-mix(in srgb, var(--red) 5%, transparent);
    } 
    /* Loading indicator for messages */
    .loading-indicator {
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 10px;
    }
    .loading-dots {
      display: flex;
      gap: 4px;
    }
    .loading-dot {
      width: 6px;
      height: 6px;
      background-color: var(--red);
      border-radius: 50%;
    }
    .loading-dot:nth-child(1) {
      animation: loading-bounce 1.4s infinite ease-in-out both;
    }
    .loading-dot:nth-child(2) {
      animation: loading-bounce 1.4s infinite ease-in-out both;
      animation-delay: -0.32s;
    }
    .loading-dot:nth-child(3) {
      animation: loading-bounce 1.4s infinite ease-in-out both;
      animation-delay: -0.64s;
    }
    @keyframes loading-bounce {
      0%, 80%, 100% {
        transform: scale(0);
      }
      40% {
        transform: scale(1);
      }
    }
    /* Modal styling */
    .modal {
      position:fixed;
      top:0; left:0; width:100%; height:100%;
      background:none;
      display:flex; align-items:center; justify-content:center;
      z-index:250;
      backdrop-filter:none;
      pointer-events:none;
    }
    /* Cookbook always sits above Gallery so the "Serve a model in
       Cookbook…" flow (and any other "open Cookbook from inside another
       modal") is visible no matter which modal was opened first. */
    #cookbook-modal { z-index: 260; }
    .modal.hidden { display:none; }

    /* Tool windows open centered in the CHAT AREA (the space right of the
       sidebar + icon rail), rather than the full viewport.
       We narrow the overlay to the chat area so its flex-centering lands the
       window there; fullscreen / docked states use position:fixed so they
       escape this narrowed overlay and still fill the screen. The
       --sidebar-w / --icon-rail-w vars track collapse state live, so when a
       window (e.g. Cookbook) hides the sidebar this naturally re-centers.
       Desktop only — on mobile these are full-screen sheets. */
    @media (min-width: 769px) {
      #calendar-modal,
      #gallery-modal,
      #tasks-modal,
      #memory-modal,
      #doclib-modal,
      #compare-model-overlay,
      #research-overlay,
      #theme-modal,
      #settings-modal,
      #email-lib-modal {
        left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px));
        width: calc(100% - (var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)));
        box-sizing: border-box;
        /* Slide in sync with the sidebar's 0.25s collapse/expand so the
           centered window glides instead of jumping when the nav toggles. */
        transition: left 0.25s ease, width 0.25s ease;
      }
    }
    .modal-content {
      background:var(--panel);
      border:1px solid var(--border);
      width:min(520px, 92vw); max-height:85vh; padding:10px;
      box-sizing:border-box; font-size:14px;
      font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      letter-spacing: -0.015em;
      display:flex; flex-direction:column;
      position:relative;
      overflow-y:auto;
      border-radius:10px;
      box-shadow:0 8px 32px rgba(0,0,0,0.45);
      pointer-events:auto;
      animation: modal-enter 0.25s ease-out both;
    }
    .modal-header {
      display:flex; justify-content:space-between; align-items:center; margin-bottom:6px;
      cursor:grab; user-select:none;
      /* Pin the header (with its close button) to the top of the
         scrollable modal-content so users can always dismiss the modal
         even after scrolling far down — especially important on mobile
         where the modal can be taller than the viewport. */
      position: sticky;
      top: 0;
      z-index: 5;
      /* Inherit the modal-content's background so the header matches the
         panel body. Many tool modals set their content to var(--bg) inline
         while this header was hard-coded to var(--panel) — making the title
         strip a different shade in every theme. `inherit` tracks whatever
         the content uses (var(--bg) or the default var(--panel)) and stays
         opaque, so the sticky header still masks scrolled content. */
      background-color: inherit;
    }
    .modal-header:active { cursor:grabbing; }
    /* Edge/corner window resize (windowResize.js). While a resize is in
       progress, suppress text selection and force the active resize cursor
       across the whole document so it does not flicker as the pointer passes
       over child elements mid-drag. */
    body.window-resizing-active { user-select:none !important; }
    body.window-resizing-active * { cursor:inherit !important; }
    /* Suppress only TRANSITIONS while resizing so the edge tracks the cursor
       crisply. We deliberately do NOT toggle `animation` here: toggling
       animation off→on re-triggers the modal open-animation (a scale-in) on
       mouseup, which both mis-measures the final size and visibly "pops" the
       window. windowResize.js instead kills the one-shot open animation inline
       once, in begin(). */
    .window-resizing {
      transition:none !important;
    }
    /* Cookbook's modal-content is var(--bg) (inline) instead of the default
       var(--panel), so its sticky header — which defaults to var(--panel) —
       read as a different-coloured band. Match the header to the cookbook
       body so the panel is one uniform colour. */
    #cookbook-modal .modal-header {
      background: var(--bg);
      /* Cookbook opts out of the sticky header on mobile; the
         title bar scrolls away with the content instead of following. */
      position: static;
      top: auto;
    }
    .modal-header h4 {
      margin:0;
      /* Push every header control (opacity slider, minimize, close) into one
         group on the right — works whether or not optional controls like the
         opacity slider are present, so the minimize button never floats to
         the centre when a sibling is hidden. */
      margin-right:auto;
      font-size:1rem;
      font-weight:600;
      letter-spacing:-0.03em;
      color:var(--red);
    }
    .close-btn,
    .modal-close {
      background:var(--bg);
      color:var(--fg);
      border:1px solid var(--fg);
      font-size:12px;
      width:24px;
      height:24px;
      padding:0;
      display:inline-flex;
      align-items:center;
      justify-content:center;
      line-height:1;
      text-indent:0;
      cursor:pointer;
      border-radius:4px;
      line-height:1;
      flex-shrink:0;
    }
    .close-btn:hover,
    .modal-close:hover {
      background:var(--fg);
      color:var(--bg);
    }
    /* Minimize button — sits beside the close button on every modal */
    .minimize-btn {
      background:var(--bg);
      color:var(--fg);
      border:1px solid var(--fg);
      font-size:14px;
      font-weight:700;
      width:24px;
      height:24px;
      padding:0 0 6px 0; /* nudge the underscore visually toward the middle */
      display:inline-flex;
      align-items:center;
      justify-content:center;
      line-height:1;
      cursor:pointer;
      border-radius:4px;
      flex-shrink:0;
      margin-left:auto; /* push to the right so it docks next to .close-btn */
      margin-right:4px;
    }
    .minimize-btn:hover {
      background:var(--fg);
      color:var(--bg);
    }
    @media (max-width: 768px) {
      .minimize-btn { display: none !important; }
      #modal-dock { display: none !important; }
    }
    /* Minimized modals are hidden but stay in the DOM with their state intact */
    .modal.minimized { display:none !important; }
    /* Bottom dock for minimized modals */
    #modal-dock {
      position:fixed;
      bottom:var(--composer-clearance, 0px);
      left:0;
      right:0;
      display:flex;
      flex-wrap:wrap;
      gap:4px;
      padding:4px 8px;
      z-index:240;
      pointer-events:none;
      justify-content:flex-start;
    }
    #modal-dock:empty { display:none; }
    .modal-dock-item {
      background:var(--panel);
      border:1px solid var(--border);
      border-bottom:none;
      border-radius:6px 6px 0 0;
      padding:4px 4px 4px 10px;
      display:inline-flex;
      align-items:center;
      gap:6px;
      cursor:pointer;
      pointer-events:auto;
      font-size:12px;
      color:var(--fg);
      max-width:220px;
      box-shadow:0 -2px 8px rgba(0,0,0,0.25);
      transition:background 0.15s;
    }
    .modal-dock-item:hover { background:var(--bg); }
    .modal-dock-label {
      white-space:nowrap;
      overflow:hidden;
      text-overflow:ellipsis;
      max-width:180px;
    }
    .modal-dock-close {
      background:transparent;
      border:none;
      color:var(--fg);
      cursor:pointer;
      font-size:14px;
      padding:0 4px;
      line-height:1;
      opacity:0.6;
    }
    .modal-dock-close:hover { color:var(--red); opacity:1; }
    .modal-body { flex:1; overflow-y:auto; }
    .modal-body button { margin-top:6px; }
    /* Styled confirm dialog — keeps backdrop */
    #styled-confirm-overlay {
      background:rgba(0,0,0,0.5);
      backdrop-filter:blur(4px);
      pointer-events:auto !important;
      z-index: 99999 !important;
      position: fixed !important;
      top: 0 !important;
      left: 0 !important;
      width: 100% !important;
      height: 100% !important;
      top: 0; left: 0; width: 100%; height: 100%;
    }
    #styled-confirm-overlay .modal-content {
      position: relative;
      z-index: 10001;
    }
    .styled-confirm-box {
      width:360px; max-width:90vw;
      max-height:none; /* override modal-content's 85vh */
      padding:14px 18px;
    }
    .styled-confirm-box .modal-header { margin-bottom:4px; }
    .styled-confirm-box .modal-body p {
      margin:8px 0 12px; color:var(--fg); font-size:0.92rem; line-height:1.45;
      white-space:pre-line;
    }
    .styled-confirm-box .modal-footer {
      display:flex; justify-content:flex-end; gap:8px; padding-top:6px;
      border-top:1px solid var(--border); margin-top:4px;
    }
    @media (max-width:768px) {
      .styled-confirm-box {
        width: 85vw;
        padding: 12px 16px;
        font-size: 0.88rem;
        border-radius: 12px;
      }
      .styled-confirm-box .modal-header h4 { font-size: 0.9rem; }
      .styled-confirm-box .modal-body p { font-size: 0.85rem; margin: 6px 0 10px; }
      .styled-confirm-box .modal-footer { gap: 10px; }
      .styled-confirm-box .confirm-btn {
        flex: 1;
        /* More bottom than top padding nudges the label up ~2px so it isn't
           sitting low in the taller mobile buttons. */
        padding: 8px 12px 12px;
        font-size: 0.85rem;
        border-radius: 8px;
        text-align: center;
      }
    }
    .confirm-btn {
      /* Asymmetric padding nudges the label UP ~2px from where it was (more
         bottom than top padding), so the confirm-dialog text isn't sitting low. */
      padding:3px 16px 5px; border-radius:4px; font-size:0.85rem;
      cursor:pointer; border:1px solid var(--border);
      font-family:inherit;
    }
    @media (max-width: 820px) {
      /* Mobile: flip the asymmetry to shift the text 1 px UP from
         centre instead (the bigger touch targets on mobile read better
         with the label sitting slightly high). */
      .confirm-btn { padding:2px 16px 4px; }
    }
    .confirm-btn-secondary { background:var(--bg); color:var(--fg); }
    .confirm-btn-secondary:hover { background:var(--border); }
    .confirm-btn-primary { background:var(--accent-primary, var(--red, #4a9eff)); color:#fff; border-color:transparent; }
    .confirm-btn-primary:hover { filter:brightness(1.15); }
    .confirm-btn-danger { background:var(--color-danger); color:#fff; border-color:transparent; }
    .confirm-btn-danger:hover { background:var(--color-error); }
    /* Styled prompt — text-input dialog (used in place of window.prompt) */
    #styled-prompt-overlay {
      background:rgba(0,0,0,0.5);
      backdrop-filter:blur(4px);
      pointer-events:auto !important;
      z-index: 99999 !important;
      position: fixed !important;
      top: 0 !important;
      left: 0 !important;
      width: 100% !important;
      height: 100% !important;
    }
    #styled-prompt-overlay .modal-content {
      position: relative;
      z-index: 10001;
    }
    .styled-prompt-box { width: min(400px, 92vw); max-width: 100%; box-sizing: border-box; }
    .styled-prompt-box .modal-body { padding-top: 4px; }
    .styled-prompt-input {
      width:100%;
      box-sizing:border-box;
      margin-top:8px;
      padding:9px 12px;
      border:1px solid var(--border);
      border-radius:6px;
      background:var(--bg);
      color:var(--fg);
      font:inherit;
      font-size:0.95rem;
      outline:none;
      transition: border-color 0.15s, box-shadow 0.15s;
    }
    .styled-prompt-input:focus {
      border-color: var(--accent-primary, var(--red, #4a9eff));
      box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-primary, var(--red, #4a9eff)) 25%, transparent);
    }
    @media (max-width:768px) {
      .styled-prompt-box { width: 85vw; }
      .styled-prompt-input { font-size: 0.9rem; padding: 10px 12px; }
    }
    /* Scroll navigation buttons */
    .scroll-nav-btn {
      position:fixed;
      background:var(--panel);
      color: var(--accent);
      border:none;
      border-radius:10px;
      width:38px; height:38px;
      padding:0;
      display:flex; align-items:center; justify-content:center;
      font-size:14px;
      line-height:1;
      font-family:inherit;
      cursor:pointer;
      opacity:0;
      pointer-events:none;
      transition: opacity .2s, transform .3s cubic-bezier(0.25, 1, 0.5, 1), background .15s;
      z-index:100;
      transform: translateY(0);
    }
    .scroll-nav-btn::before {
      content: '';
      position: absolute;
      inset: 0;
      border-radius: 10px;
      padding: 1px;
      background: linear-gradient(to bottom, var(--border), color-mix(in srgb, var(--border) 30%, transparent));
      -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
      mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
      -webkit-mask-composite: xor;
      mask-composite: exclude;
      pointer-events: none;
    }
    #scroll-bottom-btn.show { opacity:1; pointer-events:auto; }
    @media (hover: hover) and (pointer: fine) {
      #scroll-bottom-btn.show:hover {
        border-color: color-mix(in srgb, var(--fg) 25%, transparent);
      }
    }
    #scroll-bottom-btn.slide-out {
      transform: translateY(20px);
      opacity: 0 !important;
      pointer-events: none;
    }
    /* Focus outline for accessibility */
    :focus-visible {
      outline: 2px solid var(--red);
      outline-offset: 2px;
    }
    /* Hamburger menu button */
    .hamburger {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      width: 32px;
      height: 32px;
      background: none;
      border: 1px solid transparent;
      border-radius: 6px;
      padding: 0;
      cursor: pointer;
      transition: background 0.15s, border-color 0.15s;
    }
    .hamburger:hover {
      background: color-mix(in srgb, var(--fg) 7%, transparent);
      border-color: var(--border);
    }
    .hamburger span {
      display: block;
      width: 16px;
      height: 2px;
      background: var(--fg);
      border-radius: 1px;
      transition: transform 0.2s, opacity 0.2s;
    }
    .hamburger span + span { margin-top: 3px; }
    /* Agent indicator */
    #agent-indicator {
      position: fixed;
      top: 20px;
      right: 20px;
      background: var(--bg);
      color: var(--fg);
      border: 1px solid var(--border);
      padding: 6px 12px;
      border-radius: 6px;
      font-size: 12px;
      display: none;
      z-index: 100;
      cursor: pointer;
      transition: all 0.2s ease;
    }
    #agent-indicator.active {
      display: block;
      border-color: var(--color-agent-active);
      box-shadow: 0 0 10px rgba(0, 255, 0, 0.3);
    }
    #agent-indicator:hover {
      border-color: var(--color-agent-active);
      background: var(--panel);
    }
    #research-toggle-btn:disabled {
      opacity: 0.6;
      cursor: not-allowed;
    }
    
/* ── Drag & Drop ── */

/* ---------- Color palette (dark / light) ---------- */

    /* Drag and drop styling */
    .section[dnd-active="true"] {
      background: color-mix(in srgb, var(--red) 10%, transparent) !important;
      border-color: var(--red) !important;
    }
    
    .section[dnd-over="true"] {
      background: color-mix(in srgb, var(--red) 20%, transparent) !important;
      border-color: var(--red) !important;
      transform: scale(1.02);
    }
    
    .drag-handle {
      cursor: grab;
      opacity: 0.5;
      padding: 0 6px;
      user-select: none;
    }
    
    .drag-handle:hover {
      opacity: 0.8;
    }
    
    .drag-handle:active {
      cursor: grabbing;
    }
    
/* ── UI Controls (Radio, Presets, Toolbar, Settings) ── */


    /* Custom radio button styling */
    .radio-option {
      display: flex;
      align-items: center;
      gap: 10px;
      padding: 8px;
      border: 1px solid var(--border);
      border-radius: 6px;
      background: color-mix(in srgb, var(--fg) 6%, transparent);
      cursor: pointer;
      transition: all 0.2s ease;
    }
    
    .radio-option:hover {
      background: color-mix(in srgb, var(--fg) 9%, transparent);
      border-color: var(--fg);
    }
    
    .radio-option input[type="radio"] {
      appearance: none;
      -webkit-appearance: none;
      width: 18px;
      height: 18px;
      border: 2px solid var(--border);
      border-radius: 50%;
      outline: none;
      margin: 0;
      background: var(--bg);
      transition: all 0.2s ease;
      position: relative;
      flex-shrink: 0;
    }
    
    .radio-option input[type="radio"]:checked {
      border-color: var(--red);
      background: var(--red);
    }
    
    .radio-option input[type="radio"]:focus {
      box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 30%, transparent);
    }
    
    .radio-label {
      color: var(--fg);
      font-size: 14px;
      user-select: none;
    }
    
    /* Preset buttons */
    .preset-btn {
      height: 27.2px; /* 15% smaller than 32px */
      padding: 0 8.5px; /* 15% smaller than 10px */
      margin-left: 4px;
      border: 1px solid var(--border);
      border-radius: 4px;
      background: var(--bg);
      color: var(--fg);
      font-family: 'Fira Code', monospace;
      font-size: 10.2px; /* 15% smaller than 12px */
      cursor: pointer;
      transition: all 0.2s ease;
    }
    
    .preset-btn:hover {
      background: var(--panel);
      border-color: var(--fg);
    }
    
    .preset-btn.active {
      background: var(--panel);
      border-color: var(--fg);
      box-shadow: 0 0 0 1px var(--fg), 0 0 8px color-mix(in srgb, var(--fg) 16%, transparent);
      font-weight: 600;
    }
    
    /* All preset buttons use the same blue color */
    .preset-btn {
      border-color: var(--red); /* Blue color */
    }
    
    .preset-btn.active {
      border-color: var(--red); /* Blue color when active */
      box-shadow: 0 0 0 1px var(--red), 0 0 8px color-mix(in srgb, var(--red) 30%, transparent);
    }
    
    /* Custom preset modal: the base .preset-modal-content sets overflow:hidden
       (for desktop rounded-corner clipping), which on the mobile sheet clipped
       the footer (Start/Cancel) off the bottom with no way to reach it. Let the
       whole sheet scroll — same as every other mobile modal (.modal-content is
       overflow-y:auto on mobile) — so the footer is always reachable. No flex
       changes, so the body can't collapse. */
    #custom-preset-modal .preset-modal-content {
      overflow-y: auto !important;
    }
    #custom-preset-modal .modal-body label {
      font-size: 13px;
      font-weight: 500;
      color: var(--fg);
      margin-top: 8px;
      margin-bottom: 4px;
      display: block;
    }

    #custom-preset-modal .modal-body input,
    #custom-preset-modal .modal-body textarea {
      width: 100%;
      margin-bottom: 8px;
      box-sizing: border-box;
    }
    #custom-preset-modal .modal-body textarea,
    #custom-preset-modal .modal-body input[type="text"] {
      background: var(--panel);
      border: 1px solid var(--border);
      border-radius: 6px;
      color: var(--fg);
      padding: 8px 10px;
      font-size: 13px;
      font-family: inherit;
      transition: border-color 0.15s;
    }
    #custom-preset-modal .modal-body textarea {
      resize: vertical;
    }
    #custom-preset-modal .modal-body textarea:focus,
    #custom-preset-modal .modal-body input[type="text"]:focus {
      outline: none;
      border-color: var(--red);
    }

    #custom-preset-modal .modal-footer {
      display: flex;
      justify-content: flex-end;
      gap: 8px;
      margin-top: 12px;
      padding-top: 10px;
      border-top: 1px solid var(--border);
    }

    #custom-preset-modal .modal-footer button {
      padding: 7px 14px;
      border-radius: 6px;
      font-size: 12px;
      font-weight: 500;
      border: 1px solid var(--border);
      background: none;
      color: var(--fg);
      cursor: pointer;
      transition: all 0.15s;
    }
    #custom-preset-modal .modal-footer button:hover {
      background: color-mix(in srgb, var(--fg) 8%, transparent);
    }

    #custom-preset-modal .modal-footer button#save-custom-preset {
      /* The theme's accent is stored in --red (theme.js sets --red = accentHex)
         and --accent is undefined, so the canonical accent expression is
         var(--accent, var(--red)). The old bare var(--accent) resolved to nothing
         which, with color:var(--bg), made the button invisible. */
      background: var(--accent, var(--red));
      color: var(--bg);
      border-color: var(--accent, var(--red));
    }
    #custom-preset-modal .modal-footer button#save-custom-preset:hover {
      opacity: 0.85;
    }
    /* Toolbar visibility tab */
    .toolbar-hint {
      font-size: 12px;
      color: color-mix(in srgb, var(--fg) 55%, transparent);
      margin-bottom: 12px;
    }
    /* ── Appearance visibility toggles ── */
    .vis-toggles {
      display: flex;
      flex-direction: column;
    }
    .vis-row {
      display: flex;
      align-items: center;
      gap: 8px;
      cursor: pointer;
      padding: 5px 6px;
      border-radius: 4px;
      transition: background 0.12s;
    }
    .vis-row:hover {
      background: color-mix(in srgb, var(--fg) 5%, transparent);
    }
    .vis-row input[type="checkbox"] {
      display: none;
    }
    .vis-icon {
      width: 20px;
      height: 20px;
      display: flex;
      align-items: center;
      justify-content: center;
      flex-shrink: 0;
      color: color-mix(in srgb, var(--fg) 40%, transparent);
    }
    .vis-icon-text {
      font-size: 10px;
      font-weight: 700;
      font-family: inherit;
      letter-spacing: -0.5px;
    }
    .vis-label {
      flex: 1;
      font-size: 12px;
      color: var(--fg);
      user-select: none;
    }
    .vis-switch {
      position: relative;
      width: 30px;
      height: 16px;
      background: color-mix(in srgb, var(--fg) 15%, transparent);
      border-radius: 8px;
      transition: background 0.2s;
      flex-shrink: 0;
    }
    .vis-switch::after {
      content: '';
      position: absolute;
      top: 2px;
      left: 2px;
      width: 12px;
      height: 12px;
      background: var(--panel);
      border-radius: 50%;
      transition: transform 0.2s;
      box-shadow: 0 1px 2px rgba(0,0,0,0.25);
    }
    .vis-row input:checked + .vis-switch {
      background: var(--red);
    }
    .vis-row input:checked + .vis-switch::after {
      transform: translateX(14px);
    }
    .vis-row input:checked ~ .vis-icon,
    .vis-row:has(input:checked) .vis-icon {
      color: var(--fg);
    }
    /* Compare model selector — match the calendar modal's clean header
       (no border underline). */
    #compare-model-overlay .modal-header h4 {
      pointer-events: none;
    }
    /* Compare model selector: keep manually-resized/tiny windows contained.
       Picker dropdowns are appended to document.body, so the card itself can
       clip and scroll without cropping the dropdown list. */
    #compare-model-overlay .modal-content {
      display: flex;
      flex-direction: column;
      max-height: min(720px, calc(100dvh - 48px));
      overflow: hidden;
      min-height: 180px;
    }
    #compare-model-overlay .modal-body {
      overflow: auto;
      flex: 1 1 auto;
      min-height: 0;
    }
    .vis-hint {
      font-size: 10px;
      color: color-mix(in srgb, var(--fg) 30%, transparent);
      font-weight: 400;
      margin-left: 2px;
    }
    /* Settings toggle — admin-only lock indicator */
    .ui-vis-lock {
      display: none;
    }
    /* (legacy toolbar-toggle styles removed — now using .vis-* classes) */

    /* Demo highlight pulse */
    .odysseus-highlight {
      outline: 2px solid var(--accent, var(--red)) !important;
      outline-offset: 1px;
      box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent);
      animation: ody-pulse 1.5s ease-in-out infinite;
      z-index: 100;
      position: relative;
    }
    @keyframes ody-pulse {
      0%, 100% { outline-color: var(--accent, var(--red)); box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); }
      50%       { outline-color: color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); }
    }
    /* Floating breathing halo. Rendered as a body-level div positioned over
       the target, so we don't fight the target's own outline / box-shadow /
       overflow chain. JS keeps it in sync with the target's bounding rect. */
    .tour-halo {
      position: fixed;
      pointer-events: none;
      border: 3px solid var(--accent, var(--red));
      border-radius: 10px;
      z-index: 10000;
      animation: ody-breathe 1.4s ease-in-out infinite;
      opacity: 0;
      transform: scale(0.94);
      transition: opacity 0.35s ease-out, transform 0.35s ease-out;
    }
    .tour-halo.tour-fade-in {
      opacity: 1;
      transform: scale(1);
    }
    @keyframes ody-breathe {
      0%, 100% {
        box-shadow: 0 0 0 3px  color-mix(in srgb, var(--accent, var(--red)) 55%, transparent),
                    0 0 22px 4px color-mix(in srgb, var(--accent, var(--red)) 40%, transparent);
        border-color: var(--accent, var(--red));
      }
      50% {
        box-shadow: 0 0 0 6px  color-mix(in srgb, var(--accent, var(--red)) 30%, transparent),
                    0 0 40px 14px color-mix(in srgb, var(--accent, var(--red)) 70%, transparent);
        border-color: color-mix(in srgb, var(--accent, var(--red)) 80%, transparent);
      }
    }
    /* While the tour is active, lift overflow:hidden on common clipping
       ancestors so the halo around a highlighted child isn't cropped. */
    body.tour-active .sidebar,
    body.tour-active .sidebar-inner,
    body.tour-active .chat-input-bar,
    body.tour-active .chat-input-top,
    body.tour-active .chat-input-wrap,
    body.tour-active .chat-input-right,
    body.tour-active .mode-toggle,
    body.tour-active .model-picker-wrap {
      overflow: visible !important;
    }

    /* ── Secret tour hint (drag-to-snap on first modal open) ── */
    .tour-hint {
      position: fixed;
      z-index: 10002;
      background: var(--bg);
      color: var(--fg);
      border: 1px solid var(--border);
      border-radius: 10px;
      padding: 12px 14px 10px;
      width: 240px;
      font-size: 0.78rem;
      line-height: 1.5;
      box-shadow: 0 6px 20px rgba(0, 0, 0, 0.32);
      opacity: 0;
      transform: translateY(-4px);
      transition: opacity 0.28s ease-out, transform 0.28s ease-out;
      pointer-events: auto;
    }
    .tour-hint.tour-hint-in  { opacity: 1; transform: translateY(0); }
    .tour-hint.tour-hint-out { opacity: 0; transform: translateY(-4px); }
    .tour-hint-visual {
      display: flex;
      justify-content: center;
      margin-bottom: 8px;
      color: var(--accent, var(--red));
    }
    .tour-hint-visual svg { display: block; }
    .tour-hint-text { margin-bottom: 10px; opacity: 0.92; }
    .tour-hint-text b { color: var(--accent, var(--red)); font-weight: 600; }
    .tour-hint-dismiss {
      display: block;
      margin: 0 0 0 auto;
      background: none;
      border: 1px solid var(--border);
      color: var(--fg);
      border-radius: 6px;
      padding: 3px 12px;
      cursor: pointer;
      font-family: inherit;
      font-size: 0.72rem;
      opacity: 0.85;
      transition: opacity 0.15s, background 0.15s;
    }
    .tour-hint-dismiss:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 8%, transparent); }

    /* SVG dance: cursor approaches title bar, drags right, modal snaps to a
       right-half zone, holds, returns. 3.2s loop. */
    .th-cursor { animation: th-cursor 3.2s ease-in-out infinite; transform-origin: 0 0; }
    .th-modal-group { animation: th-modal-slide 3.2s ease-in-out infinite; transform-origin: 39px 31px; }
    .th-zone { animation: th-zone-flash 3.2s ease-in-out infinite; }
    @keyframes th-cursor {
      0%, 5%    { transform: translate(35px, 32px); }
      18%       { transform: translate(35px, 22px); }
      48%       { transform: translate(80px, 22px); }
      72%       { transform: translate(80px, 22px); }
      90%, 100% { transform: translate(35px, 32px); }
    }
    @keyframes th-modal-slide {
      0%, 18%   { transform: translate(0, 0) scale(1, 1); }
      48%       { transform: translate(45px, 0) scale(1, 1); }
      58%       { transform: translate(30px, -19px) scale(1.4, 2.4); }
      72%       { transform: translate(30px, -19px) scale(1.4, 2.4); }
      90%, 100% { transform: translate(0, 0) scale(1, 1); }
    }
    @keyframes th-zone-flash {
      0%, 38%   { opacity: 0; }
      48%       { opacity: 0.22; }
      58%, 72%  { opacity: 0; }
      100%      { opacity: 0; }
    }
    @media (prefers-reduced-motion: reduce) {
      .th-cursor, .th-modal-group, .th-zone { animation: none !important; }
    }
    .odysseus-hl-label {
      position: absolute;
      top: -22px;
      left: 8px;
      background: var(--red);
      color: var(--bg);
      font-size: 0.7rem;
      padding: 2px 8px;
      border-radius: 4px;
      white-space: nowrap;
      z-index: 101;
      pointer-events: none;
    }

    /* Generated images inside chat bubbles */
    .msg.generated-image-wrap .body { text-align: center; }
    .generated-image {
      max-width: 100%;
      max-height: 512px;
      border-radius: 8px;
      cursor: pointer;
      transition: transform 0.2s;
      display: inline-block;
      margin: 0 auto;
    }
    .generated-image:hover { transform: scale(1.02); }
    .generated-image-caption {
      font-size: 0.8rem;
      opacity: 0.5;
      margin-top: 6px;
      font-style: italic;
      text-align: center;
    }

    /* Setup wizard */
    .setup-wizard { padding: 16px; max-width: 500px; }
    .setup-wizard .setup-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 12px; color: var(--fg); }
    .setup-wizard .setup-label { font-size: 0.85rem; color: var(--fg); opacity: 0.7; margin-bottom: 8px; }
    .setup-wizard .setup-presets { display: flex; flex-wrap: wrap; gap: 8px; }
    .setup-wizard .setup-preset-btn {
      padding: 8px 14px; border: 1px solid var(--border); border-radius: 6px;
      background: var(--panel); color: var(--fg); cursor: pointer;
      font-size: 0.85rem; transition: all 0.2s;
    }
    .setup-wizard .setup-preset-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); }
    .setup-wizard .setup-input {
      display: block; width: 100%; padding: 8px 12px; margin-bottom: 8px;
      background: var(--panel); border: 1px solid var(--border);
      border-radius: 6px; color: var(--fg); font-size: 0.85rem; box-sizing: border-box;
    }
    .setup-wizard .setup-input:focus { border-color: var(--red); outline: none; }
    .setup-wizard .setup-connect-btn {
      padding: 8px 20px; background: var(--red); color: var(--bg);
      border: none; border-radius: 6px; cursor: pointer; font-weight: 600; margin-top: 4px;
    }
    .setup-wizard .setup-connect-btn:hover { opacity: 0.9; }
    .setup-wizard .setup-status { font-size: 0.8rem; margin-top: 8px; color: var(--fg); opacity: 0.7; }
    .setup-wizard .setup-model-list { display: flex; flex-wrap: wrap; gap: 8px; }
    .setup-wizard .setup-model-btn {
      padding: 8px 14px; border: 1px solid var(--border); border-radius: 6px;
      background: var(--panel); color: var(--fg); cursor: pointer;
      font-size: 0.85rem; transition: all 0.2s;
    }
    .setup-wizard .setup-model-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); }
    .setup-wizard .setup-step.hidden { display: none; }

    /* Dropdown menu styles */
    .dropdown {
      position: absolute;
      top: calc(100% + 6px);
      right: 0;
      background: var(--panel);
      border: 1px solid var(--border);
      border-radius: 10px;
      box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3), 0 0 0 1px color-mix(in srgb, var(--fg) 5%, transparent);
      z-index: 1000;
      display: none;
      min-width: 220px;
      padding: 6px;
      backdrop-filter: blur(12px);
    }

    .dropdown.show {
      display: block;
      animation: dropdown-in 0.15s ease-out;
    }
    @keyframes dropdown-in {
      from { opacity:0; transform:translateY(-6px) scale(0.97); }
      to { opacity:1; transform:translateY(0) scale(1); }
    }

    .dropdown-item {
      padding: 8px 10px;
      border-radius: 6px;
      cursor: pointer;
      transition: background 0.12s ease;
      display: flex;
      align-items: center;
      gap: 10px;
      border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent);
    }

    .dropdown-item:last-child {
      border-bottom: none;
    }

    .dropdown-item .menu-icon {
      width: 28px;
      height: 28px;
      border-radius: 6px;
      background: color-mix(in srgb, var(--fg) 5%, transparent);
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 14px;
      flex-shrink: 0;
    }

    .dropdown-item .menu-text h4 {
      margin: 0;
      font-size: 12.5px;
      font-weight: 500;
      color: var(--fg);
      line-height: 1.3;
    }

    .dropdown-item .menu-text p {
      margin: 0;
      font-size: 10.5px;
      color: var(--color-subheader);
      line-height: 1.3;
    }

    .dropdown-item:hover {
      background: color-mix(in srgb, var(--red) 10%, transparent);
    }
    .dropdown-item:hover .menu-icon {
      background: color-mix(in srgb, var(--red) 15%, transparent);
    }

    /* Compact dropdown items (session/model context menus) */
    .dropdown-item-compact {
      cursor: pointer;
      padding: 6px 8px;
      font-size: 11px;
      border-radius: 8px;
      display: flex;
      align-items: center;
      gap: 10px;
      color: var(--fg);
      transition: background 0.1s;
    }
    .dropdown-item-compact:hover {
      background: color-mix(in srgb, var(--accent) 10%, transparent);
    }
    .dropdown-item-compact .dropdown-icon {
      width: 14px;
      height: 14px;
      display: flex;
      align-items: center;
      justify-content: center;
      flex-shrink: 0;
      opacity: 0.5;
    }
    .dropdown-item-compact .dropdown-icon svg {
      width: 14px;
      height: 14px;
    }
    .dropdown-item-compact .dropdown-shortcut {
      margin-left: auto;
      font-size: 9.5px;
      opacity: 0.35;
      font-weight: 400;
    }
    /* Keyboard shortcut hints (⌘+Alt+D etc.) are meaningless on touch —
       hide them in the per-chat actions menu on mobile. */
    @media (max-width: 768px) {
      .session-dropdown .dropdown-shortcut { display: none; }
    }
    .dropdown-item-danger {
      color: var(--red) !important;
    }
    .dropdown-item-danger .dropdown-icon {
      opacity: 0.7;
    }

    /* Inline rename input for sessions */
    .session-rename-input {
      width: 100%;
      background: var(--bg);
      color: var(--fg);
      border: 1px solid var(--accent, var(--accent-primary));
      border-radius: 4px;
      padding: 2px 6px;
      font-family: inherit;
      font-size: 12px;
      line-height: 1.3;
      outline: none;
    }

    /* Session dropdown container */
    .session-dropdown-menu {
      position: fixed;
      z-index: 1000;
      display: none;
      min-width: auto;
      width: max-content;
      padding: 4px;
      animation: dropdown-in 0.15s ease-out;
    }

    /* Folder move submenu */
    .session-folder-submenu {
      position: fixed;
      z-index: 1001;
      display: none;
      min-width: auto;
      width: max-content;
      padding: 4px;
    }

    .dropdown-divider {
      height: 1px;
      background: var(--border);
      margin: 4px 8px;
    }
    
    /* Search toggle styles */
    .search-toggle {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 6px 0;
      border-bottom: 1px solid var(--border);
      margin-bottom: 8px;
    }
    
    .search-provider-label {
      font-size: 14px;
      color: var(--fg);
    }

/* ── Voice, Search, Themes, Comparison, Censor, Print ── */

    /* Voice recording styles */
    #mic-btn {
      color: var(--fg);
      border: 1px solid var(--border);
      border-radius: 4px;
      transition: all 0.2s ease;
    }
    
    #mic-btn:hover {
      background: color-mix(in srgb, var(--fg) 6%, transparent);
      border-color: var(--fg);
    }
    
    #mic-btn.recording {
      background: var(--color-recording);
      border-color: var(--color-recording);
      animation: pulse 1.5s infinite;
    }
    
    @keyframes pulse {
      0% { opacity: 1; }
      50% { opacity: 0.7; }
      100% { opacity: 1; }
    }
    
    #recording-indicator {
      position: fixed;
      top: 10px;
      left: 0;
      right: 0;
      background: rgba(0, 0, 0, 0.8);
      backdrop-filter: blur(10px);
      border: 1px solid var(--border);
      border-radius: 8px;
      padding: 12px;
      margin: 10px;
      z-index: 1000;
      display: flex;
      align-items: center;
      justify-content: space-between;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    }
    
    #recording-indicator.hidden {
      display: none !important;
    }
    
    .recording-content {
      display: flex;
      align-items: center;
      gap: 12px;
      color: white;
    }
    
    .recording-icon {
      color: var(--color-recording);
      font-size: 20px;
      animation: pulse 1.5s infinite;
    }
    
    .recording-text {
      font-size: 16px;
      font-weight: 500;
    }
    
    #stop-recording {
      background: var(--color-recording);
      color: white;
      border: none;
      border-radius: 6px;
      padding: 6px 12px;
      font-size: 14px;
      font-weight: 500;
      cursor: pointer;
      transition: background 0.2s ease;
    }
    
    #stop-recording:hover {
      background: var(--color-recording-hover);
    }
    
    /* Error state for recording */
    #recording-indicator.error {
      background: rgba(173, 26, 26, 0.9);
    }
    
    .recording-error {
      color: var(--color-recording);
      font-size: 14px;
      margin-top: 4px;
    }
    
    @media (max-width: 768px) {
      #recording-indicator {
        margin: 8px;
        padding: 10px;
      }
      
      .recording-text {
        font-size: 14px;
      }
      
      #stop-recording {
        padding: 6px 10px;
        font-size: 12px;
      }
    }
/* SYNTAX HIGHLIGHTING — uses theme vars from style.css, no hardcoded overrides */
.hljs { color: var(--hl-fg, #9cdef2); background: none !important; }
pre { background: var(--code-bg, var(--hl-bg, #282c34)) !important; }

    /* ---------- Search overlay (Ctrl+K command palette) ---------- */
    .search-overlay {
      position: fixed;
      top: 0; left: 0; width: 100%; height: 100%;
      background: rgba(0, 0, 0, 0.6);
      display: flex;
      align-items: flex-start;
      justify-content: center;
      padding-top: 15vh;
      z-index: 300;
      backdrop-filter: blur(6px);
    }
    .search-overlay.hidden { display: none; }
    .search-popup {
      width: 520px;
      max-width: 90vw;
      background: var(--bg);
      border: 1px solid var(--border);
      border-radius: 12px;
      box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5), 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent);
      overflow: hidden;
      display: flex;
      flex-direction: column;
      max-height: 60vh;
    }
    .search-popup input#search-input {
      width: 100%;
      background: transparent;
      border: none;
      border-bottom: 1px solid var(--border);
      outline: none;
      color: var(--fg);
      font-size: 16px;
      font-family: 'Fira Code', monospace;
      padding: 14px 16px;
      box-sizing: border-box;
    }
    .search-popup input#search-input::placeholder {
      color: color-mix(in srgb, var(--fg) 30%, transparent);
    }
    .search-results {
      overflow-y: auto;
      flex: 1;
      padding: 4px;
    }
    .search-results:empty {
      display: none;
    }
    .search-group-header {
      font-size: 10px;
      font-weight: 600;
      color: var(--color-subheader);
      text-transform: uppercase;
      letter-spacing: 0.5px;
      padding: 10px 12px 4px;
    }
    .search-result-item {
      display: flex;
      align-items: baseline;
      gap: 8px;
      padding: 8px 12px;
      border-radius: 6px;
      cursor: pointer;
      transition: background 0.1s;
    }
    .search-result-item:hover,
    .search-result-item.selected {
      background: color-mix(in srgb, var(--red) 10%, transparent);
    }
    .search-result-role {
      font-size: 10px;
      font-weight: 600;
      color: var(--color-subheader);
      flex-shrink: 0;
      min-width: 24px;
    }
    .search-result-snippet {
      flex: 1;
      font-size: 12px;
      color: var(--fg);
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    .search-result-time {
      font-size: 10px;
      color: color-mix(in srgb, var(--fg) 35%, transparent);
      flex-shrink: 0;
      white-space: nowrap;
    }
    mark.search-highlight {
      /* Was orange-on-orange (0.35 alpha bg + orange text) — too low-contrast
         to read. Solid accent fill with bg-colored text reads clearly. */
      background: var(--accent, #e8a830);
      color: var(--bg, #1a1a1a);
      border-radius: 2px;
      padding: 0 2px;
      font-weight: 600;
    }
    /* Document library search-term highlight — clear, high-contrast. */
    mark.doclib-search-hl {
      background: var(--accent, #e8a830);
      color: var(--bg, #1a1a1a);
      border-radius: 2px;
      padding: 0 2px;
      font-weight: 600;
    }
    .search-empty {
      text-align: center;
      color: color-mix(in srgb, var(--fg) 35%, transparent);
      padding: 24px;
      font-size: 13px;
    }

    /* ---------- Theme popup (uses .modal > .modal-content frame) ---------- */
    #theme-modal { z-index: 260; }
    #theme-popup .theme-popup-sub { color: color-mix(in srgb, var(--fg) 50%, transparent); font-size: 11px; margin-bottom: 10px; }
    /* `max-height` instead of a fixed height so the popup shrinks to fit its
       content (avoids the leftover whitespace after the time-based switching
       card was removed). The inner .theme-tab-panel keeps overflow:auto, so
       any taller content still scrolls inside. */
    #theme-popup { overflow-y: hidden; max-height: min(85vh, 600px); }
    #theme-popup .modal-header { flex-shrink: 0; }
    #theme-popup .admin-tabs { margin: 0 -10px 8px; padding: 0 10px; flex-shrink: 0; }
    .theme-tab-panel { overflow-y: auto; min-height: 0; flex: 1; padding-bottom: 10px; }

    .theme-grid {
      display: grid; grid-template-columns: repeat(auto-fill, minmax(66px, 1fr));
      gap: 6px; margin-bottom: 12px;
    }
    .theme-swatch {
      border: 2px solid var(--border); border-radius: 8px; cursor: pointer;
      padding: 5px; text-align: center; font-size: 0.65rem; color: var(--fg);
      transition: border-color 0.15s, transform 0.15s;
    }
    .theme-swatch:hover { transform: scale(1.06); }
    .theme-swatch.active { border-color: var(--red); box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 33%, transparent); }
    .theme-swatch-colors {
      display: flex; justify-content: center; margin-bottom: 3px;
    }
    .theme-swatch-colors span {
      width: 15px; height: 15px; border-radius: 50%;
      margin-left: -5px;
      border: 1.5px solid color-mix(in srgb, var(--fg) 12%, transparent);
    }
    .theme-swatch-colors span:first-child { margin-left: 0; }
    .theme-custom-label { font-size: 11px; font-weight: 600; color: color-mix(in srgb, var(--fg) 50%, transparent); text-transform: uppercase; letter-spacing: 0.06em; margin: 10px 0 6px; }
    .theme-custom { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px; }
    .color-row { display: flex; align-items: center; gap: 4px; }
    .color-row label { font-size: 13px; font-weight: 500; color: var(--fg); opacity: 0.7; flex: 1; }
    .color-row input[type="color"],
    .color-row input.cp-swatch-input {
      width: 24px; height: 24px; border: 1px solid var(--border); border-radius: 50%;
      background: none; cursor: pointer; padding: 0; flex-shrink: 0;
      overflow: hidden;
      -webkit-appearance: none;
      appearance: none;
    }
    .color-row input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
    .color-row input[type="color"]::-webkit-color-swatch { border: none; border-radius: 50%; }
    .color-row input[type="color"]::-moz-color-swatch { border: none; border-radius: 50%; }
    .color-row input.cp-swatch-input {
      color: transparent;
      text-shadow: none;
      caret-color: transparent;
      font-size: 0;
      user-select: none;
    }
    .color-row input.cp-swatch-input::selection { background: transparent; }
    .color-row input.cp-swatch-input:focus { outline: 1px solid var(--red); outline-offset: 1px; }
    .color-reset-btn {
      width: 24px; height: 24px; border: none; background: none; cursor: pointer;
      color: var(--fg); opacity: 0; font-size: 1.15rem; padding: 0; line-height: 1;
      transition: opacity 0.15s, color 0.15s; flex-shrink: 0; pointer-events: none;
    }
    .color-reset-btn.changed { opacity: 0.4; pointer-events: auto; }
    .color-reset-btn.changed:hover { opacity: 1; color: var(--red); }
    .theme-custom-divider {
      grid-column: 1 / -1; font-size: 11px; color: var(--fg); opacity: 0.5;
      text-transform: uppercase; letter-spacing: 0.04em; margin: 6px 0 2px;
      border-top: 1px solid var(--border); padding-top: 6px;
    }
    .theme-swatch[data-custom] { position: relative; overflow: visible; }
    /* Accent-coloured circular X — always visible (mobile-friendly), sits
       INSIDE the swatch's top-right corner so it never crops into a
       neighbouring swatch. */
    .theme-delete-btn {
      position: absolute;
      top: -2px;
      right: -2px;
      width: 20px;
      height: 20px;
      padding: 0;
      border: none;
      border-radius: 50%;
      background: var(--accent, var(--red, #d92534));
      color: #fff;
      cursor: pointer;
      z-index: 2;
      display: flex;
      align-items: center;
      justify-content: center;
      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
      transition: transform 0.12s, background 0.12s;
    }
    .theme-delete-btn:hover {
      background: color-mix(in srgb, var(--accent, var(--red)) 80%, white);
      transform: scale(1.12);
    }
    .theme-delete-btn:active { transform: scale(0.95); }
    .theme-delete-btn svg {
      display: block;
      /* Optical nudge — the X reads off-center inside the circle without it. */
      position: relative;
      left: 1px;
      top: -1px;
    }
    .theme-save-row {
      display: flex; gap: 6px; margin-top: 8px;
    }
    .theme-save-row input {
      flex: 1; padding: 5px 8px; border: 1px solid var(--border); border-radius: 6px;
      background: var(--bg); color: var(--fg); font-size: 12px; font-family: inherit;
    }
    .theme-save-row input:focus { outline: none; border-color: var(--red); }
    .theme-save-row input::placeholder { color: color-mix(in srgb, var(--fg) 35%, transparent); }
    .theme-save-row button {
      padding: 6px 12px; border: 1px solid var(--red); border-radius: 6px;
      background: transparent; color: var(--red); cursor: pointer;
      font-size: 12px; font-family: inherit; white-space: nowrap; transition: all 0.15s;
    }
    .theme-save-row button:hover { background: color-mix(in srgb, var(--red) 11%, transparent); }
    .theme-save-error {
      font-size: 11px; color: var(--red); margin-top: 2px; display: none;
    }
    /* Import/Export */
    .theme-io-row { display: flex; gap: 6px; margin-top: 6px; }
    .theme-io-btn {
      flex: 1; padding: 5px 10px; border: 1px solid var(--border); border-radius: 6px;
      background: transparent; color: var(--fg); cursor: pointer;
      font-size: 12px; opacity: 0.7; transition: all 0.15s; font-family: inherit;
    }
    .theme-io-btn:hover { opacity: 1; border-color: var(--fg); background: color-mix(in srgb, var(--fg) 5%, transparent); }
    .theme-import-area {
      width: 100%; margin-top: 6px; padding: 6px 8px;
      border: 1px solid var(--border); border-radius: 6px;
      background: var(--bg); color: var(--fg); font-size: 0.7rem;
      font-family: inherit; resize: vertical; min-height: 48px;
    }
    .theme-import-area:focus { outline: none; border-color: var(--red); }
    .theme-import-area.hidden { display: none; }
    .theme-import-actions { display: flex; gap: 6px; margin-top: 4px; }
    .theme-import-actions.hidden { display: none; }
    /* Font & Density */
    .theme-fd-row { display: flex; gap: 8px; margin-bottom: 8px; }
    .theme-fd-group { flex: 1; display: flex; flex-direction: column; gap: 3px; }
    .theme-fd-label { font-size: 12px; font-weight: 500; color: var(--fg); opacity: 0.6; }
    .theme-fd-select {
      padding: 5px 8px; border: 1px solid var(--border); border-radius: 6px;
      background: var(--bg); color: var(--fg); font-size: 12px;
      font-family: inherit; cursor: pointer;
    }
    .theme-fd-select:focus { outline: none; border-color: var(--red); }
    .theme-fd-range {
      width: 100%;
      max-width: 100%;
      box-sizing: border-box;
      margin: 0;
      padding: 0;
      height: 24px;
      background: transparent;
      cursor: pointer;
      -webkit-appearance: none;
      appearance: none;
      accent-color: var(--red);
    }
    .theme-fd-range::-webkit-slider-runnable-track {
      height: 4px; background: var(--border); border-radius: 2px;
    }
    .theme-fd-range::-moz-range-track {
      height: 4px; background: var(--border); border-radius: 2px;
    }
    .theme-fd-range::-webkit-slider-thumb {
      -webkit-appearance: none; appearance: none;
      width: 14px; height: 14px; border-radius: 50%;
      background: var(--red); border: none; margin-top: -5px; cursor: pointer;
    }
    .theme-fd-range::-moz-range-thumb {
      width: 14px; height: 14px; border-radius: 50%;
      background: var(--red); border: none; cursor: pointer;
    }
    .theme-fd-range:focus { outline: none; }
    /* Color Harmony Generator */
    .theme-harmony-row { display: flex; gap: 8px; align-items: flex-end; }
    .harmony-generate-btn {
      padding: 5px 14px; border: 1px solid var(--red); border-radius: 6px;
      background: transparent; color: var(--red); cursor: pointer;
      font-size: 12px; white-space: nowrap; transition: all 0.15s;
      font-family: inherit; width: 100%;
    }
    .harmony-generate-btn:hover { background: color-mix(in srgb, var(--red) 11%, transparent); }
    .harmony-preview {
      display: flex; height: 20px; border-radius: 6px; overflow: hidden;
      margin-top: 8px; border: 1px solid var(--border);
    }
    .harmony-preview:empty { display: none; border: none; }
    .harmony-preview span { flex: 1; }
    #theme-reset-btn {
      margin-top: 6px; width: 100%; padding: 6px; border: 1px solid var(--border);
      border-radius: 6px; background: var(--bg); color: var(--fg); cursor: pointer;
      font-size: 12px; font-family: inherit; opacity: 0.7; transition: opacity 0.15s;
    }
    #theme-reset-btn:hover { opacity: 1; }
    .theme-adv-toggle {
      margin-top: 10px; margin-bottom: 10px;
      padding: 6px 8px; cursor: pointer;
      font-size: 11px; color: var(--red); opacity: 0.8;
      border: 1px solid var(--border); border-radius: 6px;
      transition: opacity 0.15s, background 0.15s;
      user-select: none;
    }
    .theme-adv-toggle:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 4%, transparent); }
    .theme-adv-toggle .theme-adv-arrow {
      display: inline-block; transition: transform 0.2s; font-size: 10px;
      margin-right: 4px;
    }
    .theme-adv-toggle.open .theme-adv-arrow { transform: rotate(90deg); }
    .theme-adv-section { margin-top: 8px; margin-bottom: 10px; }
    .theme-adv-section.hidden { display: none; }
    .theme-adv-group { margin-bottom: 8px; }
    .theme-adv-group-label {
      font-size: 10px; color: var(--fg); opacity: 0.5;
      text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 4px;
    }
    .theme-adv-clear-btn {
      margin-top: 6px; width: 100%; padding: 5px; border: 1px solid var(--border);
      border-radius: 6px; background: transparent; color: var(--fg); opacity: 0.5;
      cursor: pointer; font-size: 12px; font-family: inherit; transition: opacity 0.15s;
    }
    .theme-adv-clear-btn:hover { opacity: 1; }


    /* Mobile: bottom sheet modals — slide up from bottom */
    @media (max-width: 768px) {
      .modal {
        align-items: flex-end;
        background: rgba(0,0,0,0.4);
        pointer-events: auto;
        /* Anchor the bottom sheet to the DYNAMIC viewport. The base overlay is
           position:fixed; height:100% = the layout viewport, whose bottom sits
           UNDER Firefox Android's bottom URL bar — so flex-end parked the sheet
           (and its footer / Start button) beneath the bar. dvh excludes the bar
           so the footer lands above it. Extra safe-area pad for iOS home bar. */
        height: 100dvh;
        padding-bottom: env(safe-area-inset-bottom, 0px);
      }
      /* Confirm dialog stays centered, not a bottom sheet */
      #styled-confirm-overlay {
        align-items: center;
      }
      .styled-confirm-box {
        border-radius: 12px !important;
        border: 1px solid var(--border) !important;
        animation: modal-enter 0.25s ease-out both !important;
        max-height: none !important;
      }
      .styled-confirm-box.modal-closing {
        animation: modal-exit 0.18s ease-in both !important;
      }
      .styled-confirm-box::before {
        display: none !important;
      }
      .styled-confirm-box .close-btn {
        display: none !important;
      }
      #theme-popup {
        width: 100% !important;
        height: 65vh !important;
        max-height: 65vh !important;
        top: auto !important; left: 0 !important; right: 0 !important; bottom: 0 !important;
        position: fixed !important;
        border-radius: 14px 14px 0 0;
        border: none;
        border-top: 1px solid var(--border);
        padding: 6px 12px 12px;
        animation: sheet-enter 0.2s ease-out forwards;
        overflow-y: hidden !important;
      }
      #theme-popup .theme-tab-panel {
        touch-action: pan-y;
        overscroll-behavior: contain;
        -webkit-overflow-scrolling: touch;
      }
      #theme-popup.sheet-ready {
        animation: none;
      }
      #theme-popup.modal-closing {
        animation: sheet-exit 0.15s ease-in both !important;
      }
      .modal-content,
      .memory-modal-content,
      .settings-modal-content,
      #compare-model-overlay .modal-content {
        width: 100% !important;
        /* 85dvh leaves comfortable headroom above the sheet so the user can
           still see the chat behind it and the drag handle has breathing
           room. Was 65vh (too short, content clipped) → 90vh (too tall, top
           hugged the status bar). */
        max-height: 85dvh !important;
        max-height: 85vh !important; /* fallback for browsers without dvh */
        height: auto !important;
        border-radius: 14px 14px 0 0;
        border: none;
        border-top: 1px solid var(--border);
        padding-top: 6px !important;
        /* Clip children to the rounded top corners — otherwise the sticky
           modal-header's var(--panel) background paints a darker
           rectangle past the radius and the corners look square again. */
        overflow: hidden;
        animation: sheet-enter 0.2s ease-out forwards;
      }
      /* Tool modals fill the full mobile viewport — top edge to bottom —
         instead of stopping at the 85dvh sheet height and leaving a gap.
         Email's content carries both `modal-content` + `doclib-modal-content`,
         so the generic 85dvh rule above (later in source, equal specificity)
         was capping it; the ID-scoped selectors here win on specificity and
         bring every tool (cookbook / tasks / memory / settings / email /
         library) to the same top edge. */
      #cookbook-modal .modal-content,
      #tasks-modal .modal-content,
      #calendar-modal .modal-content,
      #memory-modal .memory-modal-content,
      #settings-modal .settings-modal-content,
      #email-lib-modal .modal-content,
      #doclib-modal .doclib-modal-content {
        max-height: 100vh !important;
        max-height: 100dvh !important;
        height: 100vh !important;
        height: 100dvh !important;
        /* Reserve the iOS home-indicator / bottom-bar strip so an expanded
           skill card's footer (Delete / Edit / Run) isn't hidden under it.
           dvh already excludes the URL bar; this handles the safe area. */
        padding-bottom: env(safe-area-inset-bottom, 0px) !important;
      }
      /* Grab handle pill on the non-standard content classes too. Memory's
         desktop rule defines a radial-glow ::before later in the file —
         these body-prefixed selectors win the specificity battle on mobile. */
      body .memory-modal-content::before,
      body .settings-modal-content::before {
        content: '';
        display: block;
        position: static;
        inset: auto;
        width: 36px; height: 4px;
        background: var(--fg);
        opacity: 0.25;
        border-radius: 2px;
        margin: 0 auto 4px;
        flex-shrink: 0;
        padding: 0;
        border-top: 10px solid transparent;
        border-bottom: 6px solid transparent;
        background-clip: padding-box;
        animation: none;
      }
      .memory-modal-content .close-btn,
      .memory-modal-content .modal-close,
      .settings-modal-content .close-btn,
      .settings-modal-content .modal-close {
        display: none !important;
      }
      .memory-modal-content,
      .settings-modal-content {
        touch-action: pan-y;
        overscroll-behavior: contain;
      }
      /* Library modals — go full-bleed on mobile so content extends to the
         very bottom (no wasted vh strip below). The parent modal centers
         them; padding-top reset is in the per-modal rules below. */
      #cookbook-modal .modal-content,
      #calendar-modal .modal-content,
      #email-lib-modal .modal-content,
      #doclib-modal .modal-content,
      #gallery-modal .modal-content,
      #tasks-modal .modal-content {
        /* vh-first as fallback for very old browsers, then dvh wins so
           the modal shrinks/grows with the mobile URL bar. Wrong order
           previously made the expanded chat preview extend below the
           visible viewport on Chrome/Safari mobile (URL bar covered the
           action buttons row). */
        max-height: 100vh !important;
        max-height: 100dvh !important;
        height: 100vh !important;
        height: 100dvh !important;
      }
      /* Anchor those modals to the top so the full height is usable */
      #cookbook-modal,
      #calendar-modal,
      #email-lib-modal,
      #doclib-modal,
      #gallery-modal,
      #tasks-modal {
        padding-top: 0 !important;
        align-items: stretch !important;
      }
      /* Deep Research already gets the swipe grab-handle pill from the
         shared `.modal-content::before` rule (the pane carries that class).
         The previous header-level pill was redundant and rendered as a
         SECOND pill stacked above the real one — removed. */
      /* The inner body must flex to fill the new full height, and the
         tasks list inside it must scroll independently — otherwise the
         list crops at whatever pre-mobile height the desktop layout had. */
      #tasks-modal .modal-content {
        display: flex !important;
        flex-direction: column !important;
      }
      #tasks-modal .modal-body {
        flex: 1 1 auto !important;
        min-height: 0 !important;
      }
      /* Memory/Skills: the body lacks flex:1, so on the fixed-height mobile
         sheet (overflow:hidden) it grew past the viewport and clipped the
         bottom of the skills list + its row action buttons with no way to
         scroll there. Bound the body so the inner list scrolls internally. */
      #memory-modal .memory-modal-content {
        display: flex !important;
        flex-direction: column !important;
      }
      /* flex-basis MUST be 0 (not auto) — matches the working #doclib-modal
         .modal-body. With basis:auto, Firefox (incl. mobile) sizes the body
         to its content (~half height) instead of filling, while Chromium
         fills it regardless — which is exactly why the skill expand worked
         on desktop/Chromium but stuck at ~50% on Firefox mobile. */
      #memory-modal .memory-modal-body {
        flex: 1 1 0 !important;
        min-height: 0 !important;
        overflow: hidden !important;
      }
      /* Same basis:0 fix down the rest of the skills chain so every layer
         fills instead of sizing to content under Firefox. */
      #memory-modal .memory-tab-panel[data-memory-panel="skills"] {
        flex: 1 1 0 !important;
        min-height: 0 !important;
      }
      #memory-modal .memory-tab-panel[data-memory-panel="skills"] > .admin-card {
        flex: 1 1 0 !important;
        min-height: 0 !important;
      }
      /* The skills modal carries an extra toolbar row (search/sort/select +
         bulk bar) the doc/email libraries don't, so the shared 82dvh preview
         min-height overflowed here and pushed the expanded footer (Delete /
         Edit / Run) off the bottom of the screen. Let flexbox size it from
         the resolved card height instead, so the footer always pins inside
         the visible area. */
      #memory-modal .doclib-card.doclib-card-expanded .doclib-card-preview {
        min-height: 0 !important;
      }
      /* Once enter animation finishes, clear it so inline transform works for dragging */
      .modal-content.sheet-ready {
        animation: none;
      }
      .modal-content.modal-closing {
        animation: sheet-exit 0.15s ease-in both !important;
      }
      /* Grab handle — large touch target, visible pill */
      #theme-popup::before,
      .modal-content::before {
        content: '';
        display: block;
        width: 36px; height: 4px;
        background: var(--fg); opacity: 0.25;
        border-radius: 2px;
        margin: 0 auto 4px;
        flex-shrink: 0;
        padding: 0;
        /* Expand touch target without changing visual size */
        border-top: 10px solid transparent;
        border-bottom: 6px solid transparent;
        background-clip: padding-box;
      }
      /* Hide close X on mobile — swipe down to dismiss */
      .modal-content .close-btn,
      .modal-content .modal-close,
      #theme-popup .close-btn {
        display: none !important;
      }
      /* Hide the auto-injected minimize (_) button on mobile — the dock
         chip already represents the minimized state, and the swipe-down
         gesture is the canonical minimize action. */
      .modal-minimize-btn,
      .minimize-btn,
      [data-minimize] {
        display: none !important;
      }
      /* Lock modals to vertical touch only, prevent horizontal dragging */
      .modal-content {
        touch-action: pan-y;
        overscroll-behavior: contain;
      }
      .modal-content .cookbook-body,
      .modal-content .modal-body {
        touch-action: pan-y;
        overscroll-behavior: contain;
      }
      .modal-header {
        cursor: default;
        touch-action: none;
      }
      @keyframes sheet-enter {
        from { transform: translateY(100%); }
        to   { transform: translateY(0); }
      }
      @keyframes sheet-exit {
        from { transform: translateY(0); }
        to   { transform: translateY(100%); }
      }
    }

    /* ── Model A/B Comparison ── */

    /* -- Extracted inline-style classes -- */
    .cmp-header-action-btn {
      background: none; border: 1px solid var(--border); color: var(--fg);
      cursor: pointer; padding: 3px 10px; font-size: 11px; font-weight: 600;
      opacity: 0.7; transition: all 0.15s; line-height: 1; border-radius: 4px;
      display: inline-flex; align-items: center; font-family: inherit;
    }
    .cmp-form-control {
      padding: 8px; background: var(--bg); color: var(--fg);
      border: 1px solid var(--border); border-radius: 6px; font-size: 0.85em;
    }
    .cmp-btn-secondary {
      background: transparent; color: var(--fg); border: 1px solid var(--border);
      border-radius: 6px; cursor: pointer;
    }
    .cmp-btn-primary {
      background: var(--fg); color: var(--bg); border: none;
      border-radius: 6px; cursor: pointer; font-weight: 600;
      transition: filter 0.12s, background 0.12s, color 0.12s;
    }
    /* Override global button:hover (which switches bg to var(--panel) =
       very dark) — keep the bright primary look and just brighten slightly. */
    .cmp-btn-primary:hover:not(:disabled) {
      background: var(--fg); color: var(--bg);
      filter: brightness(1.1);
    }
    .cmp-btn-primary:active:not(:disabled) { filter: brightness(0.95); }
    .cmp-btn-secondary:hover:not(:disabled) {
      background: color-mix(in srgb, var(--fg) 8%, transparent);
      border-color: var(--fg); color: var(--fg);
    }
    .cmp-model-row {
      display: flex; align-items: center; gap: 8px; margin-bottom: 8px;
      transition: margin-left 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
    }
    .cmp-row-label {
      color: var(--fg); font-size: 0.82em; font-weight: 600; min-width: 20px;
      opacity: 0.4; text-align: center; flex-shrink: 0;
    }
    .cmp-rm-btn {
      background: none; border: none; color: var(--fg); cursor: pointer;
      min-width: 20px; font-size: 16px; font-weight: 600; opacity: 0.3;
      transition: all 0.15s; padding: 0; line-height: 1; text-align: center;
      position: relative; top: -1px;
    }
    .cmp-prov-select {
      flex: 0 0 auto; width: 120px; font-size: 0.8em;
    }
    /* Eval-prompts picker — only shown during compare; absolute top-right
       inside .chat-input-top, matching .model-picker-wrap's slot. */
    .chat-input-top > .cmp-eval-wrap {
      position: absolute;
      top: 0; right: 0;
      z-index: 2;
    }
    .cmp-eval-btn {
      display: inline-flex; align-items: center; gap: 4px;
      padding: 4px 8px;
      background: transparent;
      border: 1px solid var(--border);
      border-radius: 6px;
      color: var(--fg);
      font-size: 11px; font-weight: 500;
      font-family: inherit;
      cursor: pointer; opacity: 0.75;
      transition: opacity 0.15s, border-color 0.15s;
    }
    .cmp-eval-btn:hover { opacity: 1; border-color: var(--fg); }
    .cmp-eval-caret { opacity: 0.7; transform: rotate(180deg); }
    .cmp-eval-menu {
      position: absolute; bottom: calc(100% + 4px); right: 0;
      min-width: 220px; max-width: 280px;
      max-height: 360px; overflow-y: auto;
      background: var(--panel);
      border: 1px solid var(--border);
      border-radius: 6px;
      box-shadow: 0 -4px 16px rgba(0,0,0,0.3);
      padding: 4px;
      z-index: 1000;
    }
    .cmp-eval-menu.hidden { display: none; }
    .cmp-eval-group-label {
      font-size: 9px; text-transform: uppercase; letter-spacing: 0.5px;
      opacity: 0.45; font-weight: 600;
      padding: 6px 8px 2px;
    }
    .cmp-eval-item {
      display: block; width: 100%;
      text-align: left;
      padding: 5px 8px;
      background: none; border: none;
      color: var(--fg); font-size: 11px;
      font-family: inherit;
      border-radius: 4px;
      cursor: pointer;
    }
    .cmp-eval-item:hover {
      background: color-mix(in srgb, var(--fg) 8%, transparent);
    }
    .cmp-eval-empty {
      padding: 10px; text-align: center;
      font-size: 11px; opacity: 0.5;
    }
    /* Tick on items that ship a known expected answer */
    .cmp-eval-item-tick {
      float: right;
      margin-left: 6px;
      font-size: 10px;
      color: var(--color-success, #4caf50);
      opacity: 0.8;
    }
    /* Expected-answer panel — floats as its own little window above the
       chat-input-bar. Distinct surface, padded, drop-shadow, slightly
       lifted so it reads as a separate UI element, not part of the input. */
    .chat-input-bar:has(.cmp-eval-expected) { position: relative; }
    .cmp-eval-expected {
      position: absolute;
      bottom: calc(100% + 8px);
      right: 0;
      display: inline-flex; align-items: center; gap: 8px;
      padding: 8px 12px;
      font-size: 11px;
      background: var(--panel);
      border: 1px solid color-mix(in srgb, var(--color-success, #4caf50) 50%, transparent);
      border-radius: 8px;
      box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.2);
      color: var(--fg);
      width: fit-content;
      z-index: 5;
      pointer-events: auto;
    }
    .cmp-eval-expected.hidden { display: none; }
    .cmp-eval-expected-label {
      opacity: 0.6;
      text-transform: uppercase;
      font-size: 9px;
      letter-spacing: 0.5px;
      font-weight: 600;
    }
    .cmp-eval-expected-value {
      font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
      font-size: 11px;
    }
    .cmp-eval-expected-close {
      background: none; border: none; color: var(--fg);
      font-size: 14px; line-height: 1; padding: 0 0 0 4px;
      opacity: 0.5; cursor: pointer; font-family: inherit;
    }
    .cmp-eval-expected-close:hover { opacity: 1; }
    /* Auto-grade badge — stamped on a pane after stream completes when an
       eval prompt with a known expected answer was used. */
    .pane-grade-badge {
      display: inline-flex; align-items: center; justify-content: center;
      width: 18px; height: 18px;
      margin: 0 4px;
      font-size: 12px; font-weight: 700;
      border-radius: 50%;
      border: 1px solid currentColor;
      flex-shrink: 0;
    }
    .pane-grade-badge.pass {
      color: var(--color-success, #4caf50);
      background: color-mix(in srgb, var(--color-success, #4caf50) 12%, transparent);
    }
    .pane-grade-badge.fail {
      color: var(--color-error, #e55);
      background: color-mix(in srgb, var(--color-error, #e55) 12%, transparent);
    }

    /* Compare probe overlay */
    .compare-probe-overlay {
      position: fixed; inset: 0; z-index: 300;
      background: rgba(0,0,0,0.5); display: flex;
      align-items: center; justify-content: center;
    }
    .compare-probe-card {
      background: var(--panel); border-radius: 12px; padding: 20px 24px;
      display: flex; flex-direction: column; align-items: center; gap: 12px;
      min-width: 280px; max-width: 90vw; box-shadow: 0 8px 32px rgba(0,0,0,0.3);
      overflow: hidden;
    }
    .compare-probe-title {
      font-size: 13px; font-weight: 600; opacity: 0.7;
    }
    .compare-probe-list {
      display: grid; grid-template-columns: 1fr 1fr; gap: 6px; min-width: 320px; width: 100%;
    }
    .compare-probe-row {
      display: flex; align-items: center; gap: 6px; padding: 6px 10px;
      border-radius: 6px; background: color-mix(in srgb, var(--fg) 4%, transparent);
      font-size: 12px; transition: background 0.2s; overflow: hidden;
    }
    .compare-probe-row.fail {
      background: color-mix(in srgb, var(--color-error, #f44) 8%, transparent);
    }
    .compare-probe-spinner {
      width: 24px; text-align: center; font-size: 10px; flex-shrink: 0;
      letter-spacing: -1px; opacity: 0.6;
    }
    .compare-probe-spinner.ok { color: var(--color-success); animation: none; }
    .compare-probe-spinner.fail { color: var(--color-error, #f44); animation: none; }
    .compare-probe-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .compare-probe-status { font-size: 11px; opacity: 0.5; flex-shrink: 0; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .compare-probe-status.ok { color: var(--color-success); opacity: 1; }
    .compare-probe-status.fail { color: var(--color-error, #f44); opacity: 1; }
    .compare-probe-action-btn {
      padding: 2px 8px; background: transparent; color: var(--fg); border: 1px solid var(--border);
      border-radius: 4px; cursor: pointer; font-size: 10px; font-family: inherit;
      opacity: 0.7; transition: opacity 0.15s, border-color 0.15s; white-space: nowrap; flex-shrink: 0;
    }
    .compare-probe-action-btn:hover { opacity: 1; border-color: var(--accent); }
    @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
    @keyframes pane-shake {
      0%, 100% { transform: translateX(0); }
      15% { transform: translateX(-3px) rotate(-0.5deg); }
      30% { transform: translateX(3px) rotate(0.5deg); }
      45% { transform: translateX(-2px); }
      60% { transform: translateX(2px); }
      75% { transform: translateX(-1px); }
    }

    .chat-container.compare-active {
      display: flex;
      flex-direction: column;
      padding: 0;
      overflow: hidden;
      animation: compare-enter 0.3s ease-out;
    }
    @keyframes compare-enter {
      from { opacity: 0; transform: translateY(8px); }
      to { opacity: 1; transform: translateY(0); }
    }
    .chat-container.compare-active .chat-input-bar {
      margin-bottom: 0;
    }
    .chat-container.compare-active #model-picker-wrap {
      display: none !important;
    }
    .compare-grid {
      display: grid;
      gap: 4px;
      flex: 1 1 0;
      min-height: 0;
      overflow: hidden;
    }
    .compare-grid[data-cols="2"] { grid-template-columns: 1fr 1fr; }
    .compare-grid[data-cols="3"] { grid-template-columns: 1fr 1fr 1fr; }
    .compare-grid[data-cols="4"] { grid-template-columns: repeat(4, 1fr); }
    .compare-grid[data-cols="5"] { grid-template-columns: repeat(4, 1fr); }
    .compare-grid[data-cols="6"] { grid-template-columns: repeat(4, 1fr); }
    .compare-grid[data-cols="7"] { grid-template-columns: repeat(4, 1fr); }
    .compare-grid[data-cols="8"] { grid-template-columns: repeat(4, 1fr); }
    .compare-grid { grid-auto-rows: 1fr; }
    /* Sequential waterfall layout — stacked rows, staggered left, flush right */
    .compare-grid.sequential-layout {
      display: flex !important;
      flex-direction: column !important;
      grid-template-columns: none !important;
      gap: 4px;
      overflow-y: auto;
    }
    .compare-grid.sequential-layout .compare-pane {
      flex-shrink: 0;
      min-height: 200px;
      transition: margin-left 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s ease;
    }
    /* Herringbone diagonal cut on left side of sequential pane headers */
    .compare-grid.sequential-layout .compare-pane .pane-header {
      clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%);
      padding-left: 26px;
    }
    .compare-pane {
      display: flex;
      flex-direction: column;
      border: 1px solid var(--border);
      border-radius: 6px;
      overflow: hidden;
      min-height: 0;
      min-width: 0;
    }
    .compare-pane .pane-header {
      display: flex;
      align-items: center;
      gap: 4px;
      padding: 4px 10px;
      background: color-mix(in srgb, var(--fg) 4%, transparent);
      border-bottom: 1px solid var(--border);
      font-size: 0.82em;
      font-weight: 600;
      color: var(--fg);
      transition: background 0.4s;
      flex-shrink: 0;
      overflow: hidden;
      min-width: 0;
      flex-wrap: wrap;
    }
    .pane-actions {
      display: flex; gap: 4px; align-items: center; margin-left: auto; flex-shrink: 0;
    }
    .compare-pane-footer {
      font-size: 0.72em; opacity: 0.4; padding: 4px 10px;
      border-top: 1px solid color-mix(in srgb, var(--border) 40%, transparent);
      text-align: center; flex-shrink: 0;
    }
    /* Per-pane vote button. Sits at the bottom of each compare pane so
       the action lives right under the response it judges. */
    .pane-vote-footer {
      padding: 6px 8px;
      border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
      background: color-mix(in srgb, var(--fg) 3%, transparent);
      flex-shrink: 0;
    }
    .pane-vote-btn {
      width: 100%;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      gap: 4px;
      background: var(--bg);
      color: var(--fg);
      border: 1px solid var(--border);
      border-radius: 6px;
      padding: 6px 10px;
      font-family: inherit;
      font-size: 12px;
      font-weight: 500;
      cursor: pointer;
      transition: background 0.15s, border-color 0.15s, opacity 0.15s;
    }
    .pane-vote-btn:hover:not(:disabled) {
      background: color-mix(in srgb, var(--accent, var(--fg)) 12%, var(--bg));
      border-color: var(--accent, var(--fg));
    }
    .pane-vote-btn:disabled { cursor: not-allowed; }
    .pane-vote-label {
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      min-width: 0;
    }
    .pane-action-btn {
      background: none; border: none; color: var(--fg); cursor: pointer;
      opacity: 0.3; padding: 2px; border-radius: 4px;
      display: flex; align-items: center; transition: all 0.15s;
    }
    .pane-action-btn:hover { opacity: 0.8; background: color-mix(in srgb, var(--fg) 6%, transparent); }
    /* Pane title as clickable model-swap button */
    .pane-title-btn {
      background: none; border: none; cursor: pointer;
      font-size: 10px; font-weight: 400; font-family: inherit;
      color: var(--fg); padding: 0;
      text-align: left; display: flex; align-items: center; gap: 4px;
      overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
      transition: opacity 0.15s;
      min-width: 0; flex: 1 1 0;
    }
    .pane-title-btn:hover { opacity: 0.7; }
    .pane-title-caret { font-size: 0.6em; opacity: 0.35; flex-shrink: 0; position: relative; top: 2px; }
    .pane-title-btn:hover .pane-title-caret { opacity: 0.7; }
    .compare-pane .pane-close-btn { opacity: 0.3; }
    .compare-pane .pane-close-btn:hover { opacity: 1; color: var(--color-error); }
    /* Model swap dropdown under pane title */
    .pane-model-dropdown {
      position: absolute;
      z-index: 1000;
      min-width: 220px;
      max-height: 300px;
      overflow-y: auto;
      background: var(--bg);
      border: 1px solid var(--border);
      border-radius: 8px;
      box-shadow: 0 4px 16px rgba(0,0,0,0.3);
      padding: 4px;
    }
    .pane-model-item {
      display: block; width: 100%;
      padding: 6px 10px; font-size: 0.7em;
      text-align: left; background: none; border: none; border-radius: 4px;
      color: var(--fg); cursor: pointer; transition: background 0.1s;
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    .pane-model-item:hover { background: color-mix(in srgb, var(--fg) 10%, transparent); }
    .pane-model-item.current { color: var(--red); font-weight: 600; }
    .pane-timer {
      font-size: 10px; font-weight: 400; opacity: 0.45; font-variant-numeric: tabular-nums;
      white-space: nowrap; padding-right: 4px;
    }
    /* When 4+ panes, timer wraps to its own row */
    .compare-grid[data-cols="4"] .pane-timer,
    .compare-grid[data-cols="5"] .pane-timer,
    .compare-grid[data-cols="6"] .pane-timer {
      width: 100%; order: 99; margin-top: -4px; padding-bottom: 2px; padding-left: 2px;
    }
    .pane-finish-badge {
      font-weight: 600; color: var(--red);
    }
    .compare-pane.winner .pane-header {
      background: color-mix(in srgb, var(--red) 12%, transparent);
      border-bottom-color: color-mix(in srgb, var(--red) 30%, var(--border));
    }
    .compare-pane.winner .pane-title {
      color: var(--red);
    }
    .compare-pane.loser .pane-header {
      opacity: 0.5;
    }
    .confetti-piece {
      position: fixed;
      pointer-events: none;
      z-index: 1000000;
    }
    .compare-pane.expanded { grid-column: 1 / -1; }
    .compare-pane .chat-history {
      flex: 1 1 0;
      min-height: 0;
      overflow-y: auto !important;
      overflow-x: hidden;
      padding: 8px;
      display: flex;
      flex-direction: column;
    }
    .compare-pane .chat-history .msg {
      flex-shrink: 0;
    }
    .compare-gen-image {
      max-width: 100%;
      max-height: 100%;
      object-fit: contain;
      border-radius: 4px;
      cursor: pointer;
    }
    .compare-section {
      margin-bottom: 14px;
    }
    .compare-section:last-child {
      margin-bottom: 0;
    }
    .compare-section-label {
      font-size: 9px;
      text-transform: uppercase;
      letter-spacing: 0.6px;
      opacity: 0.5;
      margin-bottom: 5px;
      font-weight: 600;
    }
    /* Active-type/mode readout next to "Type:"/"Mode:" — only needed on mobile
       (where the tab/toggle text labels are hidden); hidden on desktop. */
    .compare-type-current,
    .compare-mode-current { display: none; }
    /* Contextual one-liner under the mode toggles describing what you just
       changed — empty until the first toggle, then a subtle hint. */
    .compare-mode-hint {
      font-size: 11px;
      opacity: 0.55;
      margin-top: 6px;
      min-height: 0;
      line-height: 1.3;
    }
    .compare-mode-hint:empty { display: none; }
    .compare-mode-tabs {
      display: flex;
      gap: 4px;
      flex-wrap: wrap;
      min-width: 0;
    }
    /* Type tabs match Mode toggles 1:1 (same flex column layout, same metrics) */
    .compare-mode-tab {
      display: flex; flex-direction: column; align-items: center; justify-content: center;
      width: 56px; height: auto; flex: 1 1 0;
      padding: 5px 4px 4px; border: 1px solid var(--border); border-radius: 6px;
      cursor: pointer; user-select: none; transition: all 0.15s;
      background: none; color: var(--fg); opacity: 0.35;
      flex-shrink: 0; gap: 0;
      font-family: inherit;
    }
    .compare-mode-tab:hover {
      opacity: 0.7; background: color-mix(in srgb, var(--fg) 6%, transparent);
    }
    .compare-mode-tab.active {
      opacity: 1; border-color: var(--fg);
      background: color-mix(in srgb, var(--fg) 10%, transparent);
    }
    .compare-sources-box {
      display: flex; align-items: center; gap: 6px;
      padding: 6px 10px; margin-bottom: 8px;
      border-radius: 6px; font-size: 0.78em;
      background: color-mix(in srgb, var(--red) 8%, transparent);
      border: 1px solid color-mix(in srgb, var(--red) 20%, transparent);
      color: color-mix(in srgb, var(--fg) 70%, transparent);
      cursor: default;
    }
    .compare-sources-box .sources-label { font-weight: 600; }
    /* Compact tool blocks inside compare panes */
    .compare-pane .agent-thread-node {
      margin: 4px 0; font-size: 0.85em;
    }
    .compare-pane .agent-thread-cmd {
      max-height: 80px; overflow-y: auto;
    }
    .compare-pane .agent-tool-output pre {
      max-height: 120px; overflow-y: auto;
    }
    .compare-vote-bar {
      display: flex; justify-content: center; gap: 8px;
      padding: 8px; border-top: 1px solid var(--border);
      flex-shrink: 0; flex-wrap: wrap;
    }
    .compare-vote-bar.hidden { display: none; }
    .compare-vote-btn {
      padding: 6px 13px; border: 1px solid var(--border); border-radius: 6px;
      background: var(--panel); color: var(--fg); cursor: pointer; font-size: 0.8em;
      transition: all 0.15s; white-space: nowrap;
    }
    .compare-vote-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); }
    .compare-vote-tie { opacity: 0.7; }
    .compare-rematch-btn { display: flex; align-items: center; gap: 6px; margin-left: 8px; border-color: color-mix(in srgb, var(--fg) 20%, transparent); opacity: 0.6; }
    .compare-rematch-btn:hover { opacity: 1; }
    /* Preview button accent in pane header */
    .pane-preview-btn.active { color: var(--red); }
    /* Full-pane iframe for HTML preview */
    .compare-pane-iframe {
      flex: 1;
      width: 100%;
      border: none;
      border-radius: 0 0 8px 8px;
      background: #fff;
    }
    /* ---- Add-pane "+" button in pane header (last pane only) ---- */
    .pane-add-btn { display: none !important; font-size: 16px; font-weight: 600; }
    .compare-pane:last-child .pane-add-btn { display: flex !important; }
    /* Dropdown for adding a pane */
    .add-pane-dropdown {
      position: absolute;
      right: 0;
      z-index: 100;
      background: var(--panel, var(--bg));
      border: 1px solid var(--border);
      border-radius: 6px;
      max-height: 300px;
      overflow-y: auto;
      min-width: 220px;
      box-shadow: 0 4px 12px rgba(0,0,0,0.3);
      padding: 4px;
    }
    .add-pane-search {
      width: 100%;
      padding: 6px 8px;
      border: 1px solid var(--border);
      border-radius: 4px;
      background: var(--bg);
      color: var(--fg);
      font-size: 0.85em;
      box-sizing: border-box;
      margin-bottom: 4px;
      position: sticky;
      top: 0;
      z-index: 1;
    }
    /* Compare toggle buttons — icon + label stacked */
    .compare-toggle-label {
      display: block;
      font-size: 9px;
      line-height: 1;
      margin-top: 2px;
      font-weight: 500;
    }
    .compare-blind-toggle,
    .compare-save-toggle,
    .compare-dice-toggle,
    .compare-parallel-toggle,
    .compare-reset-toggle {
      display: flex; flex-direction: column; align-items: center; justify-content: center;
      width: 56px; height: auto; flex: 1 1 0;
      padding: 5px 4px 4px; border: 1px solid var(--border); border-radius: 6px;
      cursor: pointer; user-select: none; transition: all 0.15s;
      background: none; color: var(--fg); opacity: 0.35;
      flex-shrink: 0; gap: 0;
    }
    .compare-blind-toggle:hover,
    .compare-save-toggle:hover,
    .compare-dice-toggle:hover,
    .compare-parallel-toggle:hover,
    .compare-reset-toggle:hover {
      opacity: 0.7; background: color-mix(in srgb, var(--fg) 6%, transparent);
    }
    .compare-blind-toggle.active {
      opacity: 1; color: var(--color-blind-orange); border-color: var(--color-blind-orange);
      background: rgba(255, 152, 0, 0.1);
    }
    .compare-save-toggle.active {
      opacity: 1; color: var(--color-save-green); border-color: var(--color-save-green);
      background: color-mix(in srgb, var(--red) 10%, transparent);
    }
    .compare-dice-toggle.active {
      opacity: 1; color: var(--red); border-color: var(--red);
      background: color-mix(in srgb, var(--red) 10%, transparent);
    }
    .compare-parallel-toggle {
      opacity: 1; color: #e0a050; border-color: #e0a050;
      background: rgba(224, 160, 80, 0.1);
    }
    .compare-parallel-toggle.active {
      color: #5b8def; border-color: #5b8def;
      background: rgba(91, 141, 239, 0.1);
    }
    @media (max-width: 520px) {
      .compare-toggle-label { display: none; }
      /* Tab text labels are hidden on mobile, so spell out the active type next
         to "Type:" with its icon. */
      .compare-type-current {
        display: inline-flex;
        align-items: center;
        gap: 4px;
        margin-left: 5px;
        font-weight: 600;
        vertical-align: -3px;
      }
      .compare-type-current svg { width: 14px; height: 14px; }
      /* Mode list — comma-separated, each name already coloured inline. */
      .compare-mode-current {
        display: inline;
        margin-left: 5px;
        font-weight: 600;
      }
      .compare-blind-toggle,
      .compare-save-toggle,
      .compare-dice-toggle,
      .compare-parallel-toggle,
      .compare-reset-toggle {
        width: 32px; height: 32px; min-width: 32px; padding: 0;
      }
      /* The Group tab's seq/parallel toggle is a single full-width button (not
         a row of compact icons like the Compare header), so keep its label and
         make it a comfortable touch target with the text beside the icon. */
      #group-mode-btn {
        flex-direction: row !important;
        width: auto !important;
        height: auto !important;
        min-height: 44px;
        padding: 8px 14px !important;
        gap: 8px !important;
        font-size: 13px;
      }
      #group-mode-btn .compare-toggle-label {
        display: inline !important;
        font-size: 13px;
        margin-top: 0;
      }
      #group-mode-btn svg { width: 18px; height: 18px; }
      /* Compare header: hide labels + close button, show icons only */
      #compare-shuffle-btn span {
        display: none;
      }
      #compare-shuffle-btn,
      #compare-check-btn,
      #compare-add-btn {
        padding: 3px 6px;
      }
      /* Save space so the header buttons fit on one row: tighter padding +
         smaller labels (icons kept full size). (Score now lives in the vote bar.) */
      .compare-header-bar button { padding: 3px 5px !important; }
      .compare-header-bar #compare-check-btn > span,
      .compare-header-bar #compare-add-btn > span {
        font-size: 10px; margin-left: 2px;
      }
      /* Compare mobile: keep the close X visible — it's the only way out
         now that the hamburger is hidden during compare mode. */
      .compare-header-bar {
        padding: 14px 8px 10px 8px !important;
        min-height: 44px;
      }
      /* Mode tabs: icons only, centered */
      .compare-mode-tab span { display: none; }
      .compare-mode-tabs { justify-content: center; }
      /* Header action buttons: hide text labels on Export / Shuffle /
         Model on mobile so the close X fits on the right. Score keeps
         its "Score" label because its icon (4-square grid) reads too
         similar to Shuffle's icon without text. */
      .compare-header-bar #compare-export-btn > span,
      .compare-header-bar #compare-shuffle-btn > span {
        display: none;
      }
      /* Override the desktop override: on mobile the model picker overlay
         MUST cap its height to the viewport so the dropdown doesn't run
         past the bottom of the screen. */
      #compare-model-overlay .modal-content {
        max-height: 85dvh !important;
        max-height: 85vh !important;
        overflow: hidden !important;
      }
      #compare-model-overlay .modal-body {
        overflow: auto !important;
        flex: 1 1 auto !important;
        min-height: 0 !important;
      }
    }
    /* Hide number input spinners */
    input[type="number"]::-webkit-inner-spin-button,
    input[type="number"]::-webkit-outer-spin-button {
      -webkit-appearance: none; margin: 0;
    }

    /* ── Sensitive info censor ── */
    .censored-item {
      filter: blur(5px);
      cursor: pointer;
      transition: filter 0.2s;
      border-radius: 2px;
      padding: 0 2px;
      background: rgba(255,100,100,0.08);
      user-select: none;
    }
    .censored-item:hover { filter: blur(3px); background: rgba(255,100,100,0.15); }
    .censored-item.revealed {
      filter: none;
      background: rgba(100,255,100,0.08);
      user-select: auto;
      cursor: text;
    }

    #settings-menu-list .list-item.active {
      background: color-mix(in srgb, var(--accent) 10%, transparent);
      border-color: var(--accent);
    }

    /* ── Print / PDF Export ── */
    @media print {
      body { background: #fff !important; color: #000 !important; }
      #sidebar, .sidebar, #icon-rail, .hamburger-btn, #sidebar-backdrop, .chat-input-bar, .input-bar-wrapper,
      #welcome-screen, .chat-top-bar, .chat-meta-overlay, .msg-footer,
      .modal, .toast, .overflow-wrapper, .mode-toggle, .incognito-btn,
      button, .dropdown, .session-dropdown,
      .agent-tool-spinner, .agent-thread-node.running { display: none !important; }
      main.chat-container { width: 100% !important; margin: 0 !important; padding: 0 !important; max-height: none !important; overflow: visible !important; }
      #chat-history { max-height: none !important; overflow: visible !important; height: auto !important; padding: 0 !important; }
      .msg { break-inside: avoid; page-break-inside: avoid; border: none !important; box-shadow: none !important; }
      .msg-ai { background: #f5f5f5 !important; color: #000 !important; }
      .msg-user { background: #e8e8e8 !important; color: #000 !important; }
      .msg .role { color: #333 !important; font-weight: bold; }
      .msg .body { color: #000 !important; }
      pre, code { background: #f0f0f0 !important; color: #000 !important; border: 1px solid #ccc !important; }
      details { display: block !important; }
      details[open] summary ~ * { display: block !important; }
      details > summary { list-style: none; }
      details > summary::before { content: "" !important; }
      #chat-history::before { content: attr(data-print-title); display: block; font-size: 1.3em; font-weight: bold; margin-bottom: 1em; color: #000; }
      a { color: #000 !important; text-decoration: underline; }
    }


/* ── Components (from style.css) ── */

/* Self-hosted Fira Code font */
@font-face { font-family: 'Fira Code'; font-weight: 300; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-Light.woff2') format('woff2'); }
@font-face { font-family: 'Fira Code'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-Regular.woff2') format('woff2'); }
@font-face { font-family: 'Fira Code'; font-weight: 600; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-SemiBold.woff2') format('woff2'); }

/* Scrollbar styling */

/* Code block styling */
pre, code, .hljs {
  font-size: 0.95em;
  line-height: 1.5;
}

/* WebKit (Chrome, Edge, Safari) */
/* Utility class for red text */
.red-text {
  color: var(--red);
}

/* Internal chat links (search results, session references) */
a.chat-link {
  color: var(--hl-function);
  text-decoration: none;
  border-bottom: 1px dotted var(--hl-function);
  cursor: pointer;
}
a.chat-link:hover {
  opacity: 0.8;
  border-bottom-style: solid;
}

/* Session items */
.session-item { position: relative; }
.text-ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.session-menu-btn { padding: 0 2px !important; min-width: 20px; height: 20px; display: inline-flex !important; align-items: center; justify-content: center; background: none !important; border-color: transparent !important; }
.session-menu-btn:hover { background: none !important; border-color: transparent !important; }
@media (max-width: 768px) {
  .session-menu-btn { display: none !important; }
  .item-drag-handle { display: none !important; }
}
.session-menu-btn svg { transition: transform 0.2s ease; }

/* First-time swipe hint */
.swipe-hint {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 0.7rem;
  color: var(--color-error, #f44);
  opacity: 0.8;
  transition: opacity 0.5s ease;
  pointer-events: none;
  display: flex;
  align-items: center;
  gap: 4px;
  z-index: 2;
}
.swipe-hint-arrow {
  animation: swipe-nudge 1s ease-in-out infinite;
}
@keyframes swipe-nudge {
  0%, 100% { transform: translateX(0); }
  50% { transform: translateX(-6px); }
}

/* Utility classes */
.muted { opacity: 0.5; }
.muted-sm { opacity: 0.35; font-size: 0.8em; }
.accent-link { color: var(--accent-primary, var(--color-accent)); cursor: pointer; font-size: 0.85em; }
.models-empty-state { text-align: center; padding: 12px 8px; line-height: 1.6; }

/* Provider logo inside favorite dot */
.provider-logo {
  border: none !important;
  background: none !important;
  width: 14px !important; height: 14px !important;
  display: inline-flex; align-items: center; justify-content: center;
  transition: opacity 0.15s;
}
.provider-logo svg { width: 14px; height: 14px; display: block; }
.provider-logo:hover { opacity: 1 !important; transform: scale(1.2); }

/* Hide session menu button until hover — use width:0 so it doesn't steal space from text */
.list-item .hamburger { opacity: 0; width: 0; min-width: 0; overflow: hidden; padding: 0 !important; transition: opacity 0.15s, width 0.15s, padding 0.15s; flex-shrink: 0; display: flex; align-items: center; justify-content: center; }
.list-item:hover .hamburger { opacity: 1; width: 24px; min-width: 24px; padding: 0 4px !important; }
@media (max-width: 768px) {
  .list-item .hamburger { opacity: 0.5; width: 28px; min-width: 28px; padding: 0 4px !important; }
  .list-item .hamburger:active { opacity: 1; }
}

/* Hamburger menu button styling (overrides default button appearance) */
button.hamburger {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}

/* ============================================ */
/* HEADER SIZING FOR CHAT MESSAGES */
/* ============================================ */
/* Markdown in chat messages — colorful, scannable */
.msg h1, .msg h2, .msg h3, .msg h4, .msg h5, .msg h6 {
  margin: 0.6em 0 0.3em 0;
  line-height: 1.3;
  border-bottom: none;
  padding-bottom: 0;
}

.msg h1 {
  font-size: 1.15em;
  font-weight: 700;
  color: var(--hl-keyword, #c678dd);
}

.msg h2 {
  font-size: 1.1em;
  font-weight: 600;
  color: var(--hl-function, #5b8def);
}

.msg h3 {
  font-size: 1.05em;
  font-weight: 600;
  color: var(--hl-string, #98c379);
}

.msg h4 {
  font-size: 1.02em;
  font-weight: 600;
  color: var(--hl-builtin, #e5c07b);
}

.msg h5 {
  font-size: 1em;
  font-weight: 600;
  color: var(--hl-variable, #61afef);
}

.msg h6 {
  font-size: 0.95em;
  font-weight: 600;
  color: var(--hl-number, #d19a66);
}

/* Bold text — subtle accent color */
.msg strong, .msg b {
  color: var(--hl-builtin, #e5c07b);
  font-weight: 600;
}

/* Italic — softer highlight */
.msg em, .msg i {
  color: var(--hl-params, #abb2bf);
  font-style: italic;
}

/* Bold + italic */
.msg strong em, .msg em strong,
.msg b i, .msg i b,
.msg b em, .msg em b,
.msg strong i, .msg i strong {
  color: var(--hl-keyword, #c678dd);
}

/* Strikethrough */
.msg del {
  color: color-mix(in srgb, var(--fg) 45%, transparent);
  text-decoration: line-through;
}

/* Inline code */
.msg code:not(pre code) {
  color: var(--hl-string, #98c379);
  background: color-mix(in srgb, var(--fg) 6%, transparent);
  padding: 1px 5px;
  border-radius: 4px;
  font-size: 0.9em;
}

/* Blockquotes */
.msg blockquote {
  border-left: 3px solid var(--hl-function, #5b8def);
  padding: 4px 12px;
  margin: 0.5em 0;
  color: color-mix(in srgb, var(--fg) 75%, transparent);
  background: color-mix(in srgb, var(--fg) 3%, transparent);
  border-radius: 0 6px 6px 0;
}
.msg blockquote p { margin: 0.3em 0; }

/* Horizontal rules */
.msg hr {
  border: none;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--border), transparent);
  margin: 0.8em 0;
}

/* Lists */
.msg ul, .msg ol {
  margin: 0.3em 0 0.3em 1.2em;
  padding: 0;
}
.msg li {
  margin: 0.15em 0;
}
.msg li::marker {
  color: var(--hl-function, #5b8def);
}

/* Links */
.msg a {
  color: var(--hl-function, #5b8def);
  text-decoration: none;
  border-bottom: 1px solid rgba(91, 141, 239, 0.3);
  transition: border-color 0.15s;
}
.msg a:hover {
  border-bottom-color: var(--hl-function, #5b8def);
}

/* Tables */
.msg table {
  border-collapse: collapse;
  margin: 0.5em 0;
  font-size: 0.9em;
  width: auto;
}
.msg th {
  background: color-mix(in srgb, var(--fg) 7%, transparent);
  color: var(--hl-keyword, #c678dd);
  font-weight: 600;
  padding: 6px 12px;
  border: 1px solid var(--border);
  text-align: left;
}
.msg td {
  padding: 5px 12px;
  border: 1px solid var(--border);
}

/* Agent UI Styling */
.agent-controls {
  background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
  border: 1px solid #dee2e6;
  border-radius: 8px;
  padding: 12px;
  margin-bottom: 12px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.agent-toggle label {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-weight: 500;
  margin-bottom: 8px;
}

#workflow-selector {
  margin-top: 8px;
}

#workflow-type {
  width: 100%;
  padding: 6px 12px;
  border: 1px solid #ced4da;
  border-radius: 4px;
  background: white;
  font-size: 14px;
}

.agent-progress {
  background: #ffebee;
  border: 1px solid #ef9a9a;
  border-radius: 6px;
  padding: 12px;
  margin: 8px 0;
  text-align: center;
}

.agent-working {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-style: italic;
  color: var(--red);
}

.loading-dots::after {
  content: '...';
  animation: dots 1.5s infinite;
}

@keyframes dots {
  0%, 20% { opacity: 0; }
  40% { opacity: 0.5; }
  60%, 100% { opacity: 1; }
}

.workflow-info {
  background: #f8f9fa;
  border: 1px solid var(--red);
  border-radius: 6px;
  padding: 8px 12px;
  margin: 4px 0;
  font-size: 0.9em;
  color: var(--red);
  text-align: center;
}

/* Scrollbar uses --red from :root (set at top of file) */

/* Loading spinner */
@keyframes spin {
  to { transform: rotate(360deg); }
}
.spinner {
  width: 24px;
  height: 24px;
  margin: 8px auto;
  border: 3px solid var(--border);
  border-top-color: var(--red);
  border-radius: 50%;
  animation: spin 0.9s linear infinite;
}

/* Inline spinner for buttons */
.btn-spinner {
  display: inline-block;
  width: 12px;
  height: 12px;
  border: 2px solid transparent;
  border-top-color: currentColor;
  border-radius: 50%;
  animation: spin 1s linear infinite;
  margin-right: 6px;
}

/* Loading indicator for messages */
.loading-indicator {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 10px;
}
.loading-dots {
  display: flex;
  gap: 4px;
}
.loading-dot {
  width: 6px;
  height: 6px;
  background-color: var(--fg);
  border-radius: 50%;
  opacity: 0.6;
}
.loading-dot:nth-child(1) {
  animation: loading-bounce 1.4s infinite ease-in-out both;
}
.loading-dot:nth-child(2) {
  animation: loading-bounce 1.4s infinite ease-in-out both;
  animation-delay: -0.32s;
}
.loading-dot:nth-child(3) {
  animation: loading-bounce 1.4s infinite ease-in-out both;
  animation-delay: -0.64s;
}
@keyframes loading-bounce {
  0%, 80%, 100% {
    transform: scale(0);
  }
  40% {
    transform: scale(1);
  }
}

/* Hamburger menu button */
.hamburger {
  display: inline-flex;
  flex-direction: column;
  justify-content: space-between;
  width: 24px;
  height: 18px;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}
.hamburger span {
  display: block;
  width: 100%;
  height: 3px;
  background: var(--fg);
  border-radius: 2px;
}

/* Agent indicator */
#agent-indicator {
  position: fixed;
  top: 20px;
  right: 20px;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  padding: 6px 12px;
  border-radius: 6px;
  font-size: 12px;
  display: none;
  z-index: 100;
  cursor: pointer;
  transition: all 0.2s ease;
}
#agent-indicator.active {
  display: block;
  border-color: var(--color-agent-active);
  box-shadow: 0 0 10px rgba(0, 255, 0, 0.3);
}
#agent-indicator:hover {
  border-color: var(--color-agent-active);
  background: var(--panel);
}

/* Preset buttons */
.preset-btn {
  height: 27.2px;
  padding: 0 8.5px;
  margin-left: 4px;
  border: 1px solid var(--red);
  border-radius: 4px;
  background: var(--bg);
  color: var(--fg);
  font-family: inherit;
  font-size: 10.2px;
  cursor: pointer;
  transition: all 0.2s ease;
}

.preset-btn:hover {
  background: var(--panel);
  border-color: var(--fg);
}

.preset-btn.active {
  background: var(--panel);
  border-color: var(--red);
  box-shadow: 0 0 0 1px var(--red), 0 0 8px color-mix(in srgb, var(--red) 30%, transparent);
  font-weight: 600;
}

/* Custom preset modal — inherits from .modal base class */

/* Unified chat input area */
.chat-input-area {
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px;
  margin-top: 12px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.chat-controls-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}

.chat-controls-left {
  display: flex;
  align-items: center;
  gap: 8px;
}

.chat-controls-right {
  display: flex;
  align-items: center;
  gap: 8px;
}

.control-group {
  display: flex;
  align-items: center;
  gap: 4px;
}

.control-label {
  font-size: 11px;
  color: var(--fg);
  opacity: 0.8;
}

.toggle-switch {
  position: relative;
  display: inline-block;
  width: 30px;
  height: 16px;
}

.toggle-switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

.toggle-slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: color-mix(in srgb, var(--fg) 15%, transparent);
  border-radius: 8px;
  transition: background 0.08s;
}

.toggle-slider:before {
  position: absolute;
  content: "";
  height: 12px;
  width: 12px;
  left: 2px;
  top: 2px;
  background-color: var(--panel);
  border-radius: 50%;
  transition: transform 0.08s;
  box-shadow: 0 1px 2px rgba(0,0,0,0.25);
}

.toggle-switch input:checked + .toggle-slider {
  background-color: var(--toggle-active, var(--red));
}

.toggle-switch input:checked + .toggle-slider:before {
  transform: translateX(14px);
}

.preset-buttons-row {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

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

#message {
  flex: 1;
  min-height: 34px;
  max-height: 120px;
  resize: none;
  font-size: 13px !important;
  overflow-y: auto !important;
  line-height: 1.4 !important;
  font-family: inherit !important;
}

.action-button {
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  margin: 0;
  background: none;
  border: 1px solid var(--border);
  border-radius: 4px;
  cursor: pointer;
  color: var(--fg);
  transition: all 0.2s ease;
}

.action-button:hover {
  background: color-mix(in srgb, var(--fg) 6%, transparent);
  border-color: var(--fg);
}

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

.action-button.recording {
  background: var(--color-recording);
  border-color: var(--color-recording);
  animation: pulse 1.5s infinite;
}

@keyframes pulse {
  0% { opacity: 1; }
  50% { opacity: 0.7; }
  100% { opacity: 1; }
}

#stop-icon {
  display: none;
  width: 14px;
  height: 14px;
  background: var(--color-recording);
  border-radius: 2px;
}

/* Attachment strip — centered + max-width to match the chat-input-bar below,
   otherwise the chip floats flush-left while the input is centered (visible on
   desktop where the chat area is wider than 800px). */
.attach-strip {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  padding: 2px 8px;
  max-width: 800px;
  width: 100%;
  margin-left: auto;
  margin-right: auto;
  box-sizing: border-box;
}
.attach-strip:empty { display: none; }

/* Upload-in-progress feedback: the message bubble shows immediately, so while
   the files are still uploading we put a whirlpool ON each attachment chip and
   dim the chip's content — making it obvious that file is being sent, not stuck. */
.attach-strip.attach-uploading .thumb {
  position: relative;
  pointer-events: none;
}
.attach-strip.attach-uploading .thumb > :not(.thumb-upload-spinner) {
  opacity: 0.4;
}
.thumb-upload-spinner {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  z-index: 3;
}

.thumb {
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--fg) 11%, transparent);
  padding: 3px 6px;
  font-size: 12px;
  display: flex;
  gap: 6px;
  align-items: center;
  border-radius: 4px;
  transition: all 0.2s ease;
  max-width: 180px;
}
.thumb-img {
  max-width: 60px;
  max-height: 40px;
  border-radius: 3px;
  object-fit: cover;
}
.attach-image-preview {
  margin: 4px 0;
}
.attach-image-preview img {
  box-shadow: 0 1px 4px rgba(0,0,0,0.2);
  /* Same border as the chat bubbles. */
  border: 1px solid var(--bubble-border, var(--border));
}
/* Image chips: image fills the chip, X overlays as a corner accent badge.
   Same on desktop and mobile — doc/text chips keep the beside-X layout. */
.thumb.thumb-image {
  position: relative;
  padding: 0;
}
.thumb.thumb-image .thumb-img {
  max-height: 56px;
  display: block;
}
.thumb.thumb-image button {
  position: absolute;
  /* Sit on the top-right corner edge as an accent badge. */
  top: -7px;
  right: -7px;
  width: 24px;
  height: 24px;
  min-width: 0;
  padding: 0;
  border: 2px solid var(--bg);
  border-radius: 50%;
  background: var(--accent-primary, var(--red));
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  line-height: 1;
  z-index: 3;
  transition: transform 0.12s ease, filter 0.12s ease, box-shadow 0.12s ease;
}
.thumb.thumb-image button:hover {
  transform: scale(1.12);
  filter: brightness(1.12);
  box-shadow: 0 2px 8px rgba(0,0,0,0.25);
}
.thumb.thumb-image button:active {
  transform: scale(0.96);
}
@media (max-width: 768px) {
  /* Collapsed "N files" badge: use the same corner-X accent badge as image thumbs. */
  .thumb-collapsed { position: relative; }
  .thumb-collapsed .thumb-collapsed-x {
    position: absolute;
    top: -7px;
    right: -7px;
    width: 24px;
    height: 24px;
    min-width: 0;
    padding: 0;
    border: 2px solid var(--bg);
    border-radius: 50%;
    background: var(--accent-primary, var(--red));
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 15px;
    line-height: 1;
    z-index: 3;
    opacity: 1;
  }
  /* Bigger remove-X tap target for non-image (doc/text) chips on mobile too. */
  .thumb button {
    height: 28px;
    min-width: 28px;
    font-size: 15px;
  }
}
.thumb span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.thumb:hover {
  background: color-mix(in srgb, var(--fg) 16%, transparent);
  border-color: var(--fg);
}

.thumb button {
  height: 24px;
  padding: 0 7px;
  font-size: 13px;
  border-radius: 4px;
  color: var(--accent-primary, var(--red));
}

.thumb-collapsed {
  cursor: pointer;
  color: var(--red);
  border-color: var(--red);
  background: color-mix(in srgb, var(--red) 10%, transparent);
  font-weight: 600;
  gap: 8px;
  border-radius: 999px;   /* pill — rounder than the square file chips */
  padding-left: 12px;
}
.thumb-collapsed:hover {
  background: color-mix(in srgb, var(--red) 20%, transparent);
}
.thumb-collapsed-label { white-space: nowrap; }
.thumb-collapsed-x {
  height: 24px; padding: 0 7px; font-size: 13px; border-radius: 4px;
  color: var(--accent-primary, var(--red));
  background: none; border: none; cursor: pointer; opacity: 0.6;
}
.thumb-collapsed-x:hover { opacity: 1; }

/* Recording indicator */
#recording-indicator {
  position: fixed;
  top: 10px;
  left: 0;
  right: 0;
  background: rgba(0, 0, 0, 0.8);
  backdrop-filter: blur(10px);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px;
  margin: 10px;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: space-between;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

#recording-indicator.hidden {
  display: none !important;
}

.recording-content {
  display: flex;
  align-items: center;
  gap: 12px;
  color: white;
}

.recording-icon {
  color: var(--color-recording);
  font-size: 20px;
  animation: pulse 1.5s infinite;
}

.recording-text {
  font-size: 16px;
  font-weight: 500;
}

.stop-recording-btn {
  background: var(--color-recording);
  color: white;
  border: none;
  border-radius: 6px;
  padding: 6px 12px;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.2s ease;
}

.stop-recording-btn:hover {
  background: var(--color-recording-hover);
}

#recording-indicator.error {
  background: rgba(173, 26, 26, 0.9);
}

.recording-error {
  color: var(--color-recording);
  font-size: 14px;
  margin-top: 4px;
}

/* Mermaid diagram containers */
.mermaid-container {
  margin: 12px 0;
  padding: 16px;
  background: color-mix(in srgb, var(--bg) 95%, var(--fg));
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow-x: auto;
  text-align: center;
}
.mermaid-container svg { max-width: 100%; height: auto; }

/* KaTeX math overrides */
.katex-display { margin: 0.8em 0; overflow-x: auto; overflow-y: hidden; }
.katex { font-size: 1.1em; }

/* Hide thinking sections globally via settings toggle */
body.hide-thinking .thinking-section { display: none !important; }

/* Thinking process styles — colors follow theme accent */
.msg .body .stream-content {
  width: 100%;
}
.thinking-section {
  margin: 12px 0;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  border: 1px solid color-mix(in srgb, var(--red) 30%, transparent);
  border-radius: 8px;
  background: color-mix(in srgb, var(--red) 5%, transparent);
  overflow: hidden;
  transition: all 0.3s ease;
}

.thinking-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 12px;
  cursor: pointer;
  user-select: none;
  background: color-mix(in srgb, var(--red) 8%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--red) 20%, transparent);
  transition: background 0.2s ease;
}

.thinking-header:hover {
  background: color-mix(in srgb, var(--red) 12%, transparent);
}

.thinking-header-left {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.9em;
  color: var(--red);
  font-weight: 500;
  overflow: hidden;
  min-width: 0;
}
.thinking-header-left span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: opacity 0.2s ease;
}

.thinking-icon {
  font-size: 1.1em;
}

.thinking-toggle {
  font-size: 0.9em;
  color: var(--red);
  transition: transform 0.3s ease;
}
.thinking-toggle::after {
  content: '\25BC'; /* ▼ */
}

.thinking-toggle.expanded {
  transform: rotate(180deg);
}

.thinking-content {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease, padding 0.3s ease;
  padding: 0 12px;
}

.thinking-content.expanded {
  max-height: 300px;
  overflow-y: auto;
  padding: 12px;
}

.thinking-content-inner {
  font-size: 0.85em;
  color: var(--fg);
  opacity: 0.9;
  line-height: 1.5;
}
.live-reply-content {
  animation: fadeSlideIn 0.3s ease-out;
}
@keyframes fadeSlideIn {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Thinking indicator animation */
.thinking-indicator {
  display: flex;
  align-items: center;
  gap: 4px;
  color: var(--red);
  font-style: italic;
  padding: 8px 0;
}

.thinking-dots::after {
  content: '...';
  animation: thinking-dots 1.5s infinite;
  display: inline-block;
  width: 20px;
  text-align: left;
}

@keyframes thinking-dots {
  0%, 20% { content: '.'; }
  40% { content: '..'; }
  60%, 100% { content: '...'; }
}

.thinking-complete {
  color: var(--red);
  font-size: 0.9em;
  padding: 4px 0;
  opacity: 0.8;
}

/* ── Sources section — collapsible source citations ── */
.sources-section {
  margin: 8px 0 12px;
  border: 1px solid color-mix(in srgb, var(--red) 30%, transparent);
  border-radius: 8px;
  overflow: hidden;
  transition: all 0.3s ease;
}
.sources-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  cursor: pointer;
  background: color-mix(in srgb, var(--red) 8%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--red) 20%, transparent);
  transition: background 0.2s ease;
  user-select: none;
}
.sources-header:hover {
  background: color-mix(in srgb, var(--red) 12%, transparent);
}
.sources-header-left {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.85em;
  font-weight: 500;
  color: var(--red);
}
.sources-header-left svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  opacity: 0.7;
}
.sources-toggle {
  font-size: 0.8em;
  color: var(--red);
  opacity: 0.7;
  transition: none;
}
.sources-toggle::after {
  content: '\25B6'; /* ▶ right arrow */
}
.sources-toggle[data-arrow="down"]::after {
  content: '\25BC'; /* ▼ down arrow */
}
.sources-content {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease, padding 0.3s ease;
  padding: 0 10px;
}
.sources-content.expanded {
  max-height: 3000px;
  padding: 8px 10px;
}
.sources-content-inner {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.source-link {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 8px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--fg) 4%, transparent);
  text-decoration: none;
  color: var(--fg);
  transition: background 0.15s ease;
}
.source-link:hover {
  background: color-mix(in srgb, var(--fg) 10%, transparent);
}
.source-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--fg) 15%, transparent);
  color: var(--fg);
  font-size: 0.7em;
  font-weight: 600;
  flex-shrink: 0;
}
.source-title {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.82em;
}
.source-domain {
  font-size: 0.72em;
  opacity: 0.45;
  flex-shrink: 0;
}

/* ── Processing pulse animation (reused by session-star) ── */
@keyframes research-pulse {
  0%, 100% { opacity: 0.3; transform: scale(0.8); }
  50% { opacity: 1; transform: scale(1.2); }
}
.ai-spinner {
  color: var(--red);
}
/* Nudge the Tidy button 2px left. */
#memory-tidy-btn { position: relative; left: -2px; }
/* Tidy button's whirlpool nudge — sits 1px lower so it visually centers on
   the Tidy label baseline. */
#memory-tidy-btn .ai-spinner-whirlpool,
#memory-tidy-btn .spinner-whirlpool {
  position: relative;
  top: 1px;
}
.list-item.stream-complete {
  animation: stream-complete-pulse 2s ease-in-out infinite;
}
.cookbook-notif-active svg { opacity: 1 !important; }

/* Rail notification dot — pulsing indicator on icon-rail buttons */
.icon-rail-btn.rail-notify {
  opacity: 1 !important;
  position: relative;
}
.icon-rail-btn.rail-notify::before {
  content: '';
  position: absolute;
  top: 4px;
  right: 4px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent, var(--red));
  animation: rail-notif-pulse 2s ease-in-out infinite;
  z-index: 1;
}
.icon-rail-btn.rail-notify.rail-notify-success::before {
  background: var(--color-success, #4caf50);
}
@keyframes rail-notif-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.4; transform: scale(0.8); }
}
@keyframes stream-complete-pulse {
  0%, 100% { box-shadow: none; }
  50% { box-shadow: inset 0 0 0 1.5px var(--accent); }
}

/* ===== SLASH COMMAND RESPONSES ===== */
.msg.msg-system {
  padding: 6px 12px; margin: 4px auto 4px 8px;
  max-width: 85%; background: none; border-left: 2px solid var(--border);
}
.msg.msg-system .body { padding: 0; }
.msg.msg-system pre { margin: 4px 0; white-space: pre-wrap; font-size: 0.85em; }
.msg.stream-done-toast {
  cursor: pointer;
  border-left-color: var(--accent, var(--border));
  border-radius: 6px;
  background: color-mix(in srgb, var(--accent) 6%, transparent);
  transition: background 0.15s;
}
.msg.stream-done-toast:hover {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.msg.stream-done-toast .body {
  display: flex;
  align-items: center;
  gap: 8px;
}
.stream-done-indicator {
  font-family: monospace;
  font-size: 1.1em;
  line-height: 1;
  color: var(--accent);
  flex-shrink: 0;
  animation: bar-pulse 1.2s ease-in-out infinite;
}
@keyframes bar-pulse {
  0%, 100% { opacity: 0.4; }
  50% { opacity: 1; }
}

/* ===== AGENT MULTI-BUBBLE ===== */
.msg.msg-tool {
  display: none; /* legacy — hidden, replaced by agent-thread */
}
.msg.msg-continuation {
  margin-top: 2px;
}

/* ===== AGENT THREAD TIMELINE ===== */
.agent-thread {
  position: relative;
  margin: 2px 0 2px 28px;
  padding: 4px 0 4px 22px;
  max-width: calc(85% - 20px);
  box-sizing: border-box;
}
.agent-thread::before {
  content: '';
  position: absolute;
  left: 5px;
  top: 14px;
  bottom: 14px;
  width: 2px;
  background: color-mix(in srgb, var(--red) 18%, transparent);
  border-radius: 1px;
}
/* Extend line to connect to chat bubble above/below */
.agent-thread.has-top::before {
  top: -6.5px;
}
.agent-thread.has-bottom::before {
  bottom: -5px;
}
/* Terminating dot at bottom when no bubble below and last node is expanded */
.agent-thread:not(.has-bottom) .agent-thread-node.open:last-child::after {
  content: '';
  position: absolute;
  /* -17px (was -15) nudges the terminating dot 2px further left so it
     sits flush with the thread's left rail when the search node is
     expanded. This is the "big glow dot at the bottom" the user sees
     after a web_search step. */
  left: -17px;
  bottom: 5px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--red);
  box-shadow: 0 0 4px 1px color-mix(in srgb, var(--red) 55%, transparent),
              0 0 0 3px color-mix(in srgb, var(--red) 18%, transparent);
}

/* Synapse pulse — a bright dot traveling down the line */
.agent-thread::after {
  content: '';
  position: absolute;
  left: 4px;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--red);
  box-shadow: 0 0 3px 1px color-mix(in srgb, var(--red) 50%, transparent);
  pointer-events: none;
  top: 0%;
  opacity: 0;
}
.agent-thread.streaming::after {
  animation: synapse-capped-short 0.8s ease-in-out infinite;
}
.agent-thread.streaming.has-top::after {
  animation: synapse-capped 0.8s ease-in-out infinite;
}
.agent-thread.streaming.has-bottom::after {
  animation: synapse-travel-short 0.8s ease-in-out infinite;
}
.agent-thread.streaming.has-top.has-bottom::after {
  animation: synapse-travel 0.8s ease-in-out infinite;
}
@keyframes synapse-travel {
  0% { top: 0%; opacity: 0; }
  5% { opacity: 0.5; }
  85% { opacity: 0.35; }
  100% { top: 100%; opacity: 0; }
}
@keyframes synapse-capped {
  0% { top: 0%; opacity: 0; }
  5% { opacity: 0.5; }
  70% { opacity: 0.35; top: calc(100% - 20px); }
  100% { opacity: 0; top: calc(100% - 20px); }
}
@keyframes synapse-travel-short {
  0% { top: 14px; opacity: 0; }
  5% { opacity: 0.5; }
  85% { opacity: 0.35; }
  100% { top: 100%; opacity: 0; }
}
@keyframes synapse-capped-short {
  0% { top: 14px; opacity: 0; }
  5% { opacity: 0.5; }
  70% { opacity: 0.35; top: calc(100% - 20px); }
  100% { opacity: 0; top: calc(100% - 20px); }
}
.agent-thread-node {
  position: relative;
  padding: 5px 0;
}
.agent-thread-node + .agent-thread-node {
  margin-top: 2px;
}
.agent-thread-dot {
  position: absolute;
  left: -20px;
  top: 10px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--red);
  border: 2px solid var(--bg);
  z-index: 1;
}
.agent-thread-node.running .agent-thread-dot {
  background: var(--red);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--red) 25%, transparent);
  animation: thread-pulse 1.5s ease-in-out infinite;
  top: 10px;
}
@keyframes thread-pulse {
  0%, 100% { box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 20%, transparent); }
  50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--red) 10%, transparent); }
}
.agent-thread-node.error .agent-thread-dot {
  background: var(--color-error);
}
.agent-thread-header {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-size: 0.85em;
  color: color-mix(in srgb, var(--fg) 70%, transparent);
  user-select: none;
  padding: 2px 0;
}
.agent-thread-header:hover {
  color: var(--fg);
}
.agent-thread-icon {
  font-size: 0.9em;
  color: var(--red);
}
.agent-thread-node.error .agent-thread-icon {
  color: var(--color-error);
}
.agent-thread-tool {
  font-weight: 600;
  color: var(--red);
  text-transform: uppercase;
  letter-spacing: 0.3px;
  font-size: 0.9em;
}
.agent-thread-status {
  font-size: 0.85em;
  opacity: 0.5;
}
.agent-thread-chevron {
  font-size: 0.7em;
  transition: transform 0.2s ease;
  opacity: 0.4;
}
.agent-thread-node.open .agent-thread-chevron {
  transform: rotate(90deg);
}
.agent-thread-wave {
  font-family: monospace;
  font-size: 0.85em;
  color: var(--red);
  letter-spacing: -1px;
}
/* Live "cooking" timer on a running tool — prominent (accent, tabular) so a
   long-running command always reads as alive, not frozen. */
.agent-thread-elapsed {
  margin: 0 6px 0 4px;
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--accent, var(--red));
}
/* Stall watchdog banner — shown when the stream has been silent for ~1min. */
.stall-banner {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 8px auto;
  padding: 8px 12px;
  max-width: 90%;
  font-size: 12px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--color-warning, #f0ad4e) 12%, var(--bg));
  border: 1px solid color-mix(in srgb, var(--color-warning, #f0ad4e) 40%, transparent);
}
.stall-banner-txt { flex: 1; opacity: 0.85; }
.stall-banner-btn {
  font-size: 11px;
  font-weight: 600;
  padding: 4px 10px;
  border-radius: 6px;
  border: none;
  background: var(--accent, var(--red));
  color: #fff;
  cursor: pointer;
  flex-shrink: 0;
}
.stall-banner-stop {
  background: none;
  color: var(--fg);
  border: 1px solid var(--border);
}
.agent-thread-content {
  display: none;
  padding: 4px 0 2px 0;
  overflow-x: auto;
  overflow-y: hidden;
}
.agent-thread-node.open .agent-thread-content {
  display: block;
}
.agent-thread-cmd {
  background: color-mix(in srgb, var(--fg) 5%, transparent);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px 12px;
  margin: 4px 0;
  color: var(--fg);
  font-size: 0.85em;
  white-space: pre-wrap;
  word-break: break-word;
  line-height: 1.4;
  overflow-x: auto;
}

/* Mobile: constrain thread content */
@media (max-width: 768px) {
  .agent-thread {
    margin-left: 16px;
    padding-left: 18px;
    max-width: calc(90% - 16px);
  }
  .agent-thread-cmd {
    font-size: 0.78em;
    padding: 6px 8px;
    max-width: 100%;
    overflow-x: auto;
  }
  .agent-thread-content {
    max-width: calc(100vw - 90px);
  }
  .agent-tool-output pre {
    font-size: 0.8em;
    max-width: 100%;
    overflow-x: auto;
  }
  .agent-thread-header {
    font-size: 0.8em;
  }
  .agent-thread-dot {
    left: -16px;
    top: 10px;
  }
}

/* ===== AGENT TOOL OUTPUT (inside thread nodes) ===== */
.agent-tool-output {
  margin-top: 8px;
  background: color-mix(in srgb, var(--red) 5%, transparent);
  border: 1px solid color-mix(in srgb, var(--red) 20%, transparent);
  border-radius: 8px;
  overflow: hidden;
}
.agent-tool-output summary {
  color: var(--red);
  background: color-mix(in srgb, var(--red) 10%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--red) 15%, transparent);
  cursor: pointer;
  font-size: 0.85em;
  user-select: none;
  padding: 6px 10px;
  font-weight: 500;
  /* Chevron on the right (like the thinking section) instead of the default
     left-side disclosure triangle. */
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  list-style: none;
}
.agent-tool-output summary::-webkit-details-marker { display: none; }
/* Suppress the global `summary::before { content: '▶' }` left arrow — this
   section uses a right-side chevron instead. */
.agent-tool-output summary::before { content: none; }
.agent-tool-output summary::after {
  content: '\25BC'; /* ▼ */
  color: var(--red);
  font-size: 0.9em;
  transition: transform 0.3s ease;
}
.agent-tool-output[open] > summary::after {
  transform: rotate(180deg);
}
.agent-tool-output summary:hover {
  background: color-mix(in srgb, var(--red) 15%, transparent);
}
.agent-thinking-dots .ai-spinner {
  font-size: 12px;
  letter-spacing: 0.5px;
}

.agent-tool-output[open] {
  background: color-mix(in srgb, var(--red) 6%, transparent);
}
.agent-tool-output[open] > :not(summary) {
  animation: detail-reveal 0.25s ease-out both;
}
@keyframes detail-reveal {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.agent-tool-output pre {
  background: transparent;
  border: none;
  border-radius: 0;
  padding: 10px 14px;
  margin-top: 0;
  max-height: 300px;
  overflow-y: auto;
  font-size: 0.95em;
  color: var(--fg);
  opacity: 0.85;
  white-space: pre-wrap;
  word-break: break-all;
  line-height: 1.5;
}

/* Mobile responsive styles */
@media (max-width: 768px) {
  #recording-indicator {
    margin: 8px;
    padding: 10px;
  }
  
  .recording-text {
    font-size: 14px;
  }
  
  .stop-recording-btn {
    padding: 6px 10px;
    font-size: 12px;
  }
}
/* ===== PANE STYLES (shared by compare) ===== */

.pane-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 6px 0 4px;
  border-bottom: 1px solid var(--border);
}
.close-split-btn,
.pane-close-btn {
  background: none;
  border: none;
  color: var(--color-error);
  font-size: 18px;
  cursor: pointer;
  padding: 0 4px;
  line-height: 1;
  opacity: 0;
  transition: opacity 0.15s;
}
.pane-header:hover .close-split-btn,
.pane-header:hover .pane-close-btn {
  opacity: 1;
}
.close-split-btn:hover,
.pane-close-btn:hover {
  color: var(--color-error-light);
}


/* ============================================ */
/* RESEARCH DETAILS EXPANDABLE SECTION - UPDATED */
/* ============================================ */

/* Style the details element */
details {
  background: color-mix(in srgb, var(--hl-string) 5%, transparent);
  border: 1px solid color-mix(in srgb, var(--hl-string) 30%, transparent);
  border-radius: 8px;
  margin: 12px 0;
  padding: 0;
  overflow: hidden;
  transition: all 0.3s ease;
}

details[open] {
  background: color-mix(in srgb, var(--hl-string) 8%, transparent);
}
details[open] > :not(summary) {
  animation: detail-reveal 0.25s ease-out both;
}

/* Style the summary (clickable header) - NO CURSIVE, NORMAL SIZE */
summary {
  cursor: pointer;
  padding: 10px 14px;
  background: color-mix(in srgb, var(--hl-string) 10%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--hl-string) 20%, transparent);
  font-weight: 500;
  font-size: 0.95em;
  font-style: normal;
  font-family: inherit;
  color: var(--hl-string);
  user-select: none;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 8px;
  transition: background 0.2s ease;
}

summary:hover {
  background: color-mix(in srgb, var(--hl-string) 15%, transparent);
}

/* Add custom arrow */
summary::before {
  content: '▶';
  display: inline-block;
  transition: transform 0.3s ease;
  font-size: 0.75em;  /* Smaller arrow */
}

details[open] summary::before {
  transform: rotate(90deg);
}

/* Hide default marker in webkit browsers */
summary::-webkit-details-marker {
  display: none;
}

/* Style the content inside details - SMALLER, MORE COMPACT */
details > div,
details > p,
details > ul,
details > ol {
  padding: 12px 14px;  /* Less padding */
  animation: fadeIn 0.3s ease;
  font-size: 0.9em;  /* Smaller text */
  line-height: 1.5;
}

@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Style research findings inside details - SMALLER HEADINGS */
details h3 {
  margin-top: 12px;
  margin-bottom: 6px;
  color: var(--hl-string);
  font-size: 0.95em;
  font-weight: 500;
}

details h4 {
  margin-top: 10px;
  margin-bottom: 5px;
  color: var(--hl-string);
  font-size: 0.9em;
  font-weight: 500;
}

details ul {
  margin-left: 18px;
  margin-bottom: 10px;
}

details li {
  margin-bottom: 5px;
  line-height: 1.4;
  font-size: 0.85em;  /* Smaller list items */
}

details strong {
  color: var(--hl-string);
  font-weight: 500;
}

details a {
  color: var(--accent);
  text-decoration: none;
  transition: color 0.2s ease;
}

details a:hover {
  color: var(--color-link-hover);
  text-decoration: underline;
}

/* Research metadata - SMALLER */
details .research-meta {
  font-size: 0.8em;
  color: var(--fg);
  opacity: 0.8;
  margin-top: 4px;
}

/* Source links - SMALLER */
details .source-link {
  display: inline-block;
  margin-top: 3px;
  padding: 2px 5px;
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  border-radius: 4px;
  font-size: 0.75em;
  color: var(--accent);
}

details .source-link:hover {
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  border-color: var(--accent);
}

/* Research report links - clean and subtle */
details a {
  color: var(--accent);
  text-decoration: none;
  font-weight: normal;  /* Remove bold */
  font-size: 0.85em;
  opacity: 0.9;
  transition: all 0.2s ease;
  word-break: break-all;  /* Break long URLs nicely */
}

details a:hover {
  color: var(--color-link-hover);
  text-decoration: underline;
  opacity: 1;
}

/* ============================================ */
/* CUSTOM SYNTAX HIGHLIGHTING - Theme-Reactive  */
/* ============================================ */
.hljs {
  background: var(--code-bg, var(--hl-bg, var(--panel)));
  color: var(--code-fg, var(--hl-fg, var(--fg)));
  padding: 8px;
  border-radius: 4px;
}

/* Keywords & control flow — purple/magenta */
.hljs-keyword,
.hljs-selector-tag { color: var(--hl-keyword); }

/* Strings & regex — warm yellow/orange */
.hljs-string,
.hljs-regexp,
.hljs-addition { color: var(--hl-string); }

/* Comments & docs — muted. font-style intentionally omitted: italics shift
   glyph widths in the highlight overlay relative to the transparent textarea
   above it, which makes the caret drift away from the visible character. */
.hljs-comment,
.hljs-quote,
.hljs-meta { color: var(--hl-comment); }

/* Functions & method names — blue */
.hljs-function,
.hljs-title,
.hljs-title.function_,
.hljs-section { color: var(--hl-function); }

/* Numbers & constants — distinct from strings */
.hljs-number,
.hljs-literal { color: var(--hl-number, var(--hl-string)); }

/* Built-ins & types — teal/cyan tint */
.hljs-built_in,
.hljs-type,
.hljs-class,
.hljs-title.class_ { color: var(--hl-builtin, var(--hl-function)); }

/* Variables & identifiers — fg with slight distinction */
.hljs-variable,
.hljs-template-variable,
.hljs-attr { color: var(--hl-variable, var(--hl-fg, var(--fg))); }

/* Operators & punctuation — slightly dimmed fg */
.hljs-operator,
.hljs-punctuation { color: var(--hl-fg, var(--fg)); opacity: 0.8; }

/* Parameters */
.hljs-params { color: var(--hl-params, var(--hl-fg, var(--fg))); }

/* Property access, attributes in HTML/XML */
.hljs-property,
.hljs-selector-class,
.hljs-selector-id { color: var(--hl-variable, var(--hl-function)); }

/* Tags (HTML) */
.hljs-tag { color: var(--hl-keyword); }
.hljs-name { color: var(--hl-keyword); }

/* Deletion/diff */
.hljs-deletion { color: var(--red); }

/* Symbol, special */
.hljs-symbol { color: var(--hl-string); }
.hljs-link { color: var(--hl-function); text-decoration: underline; }

/* Emphasis — applied globally (chat messages, rendered markdown in docs
   preview, etc.) but explicitly NEUTRALIZED in the doc editor's overlay so
   the textarea-and-overlay caret alignment stays bit-perfect. Bold / italic
   shift glyph widths and that drifts the click-to-row mapping. */
.hljs-emphasis { font-style: italic; }
.hljs-strong { font-weight: bold; }
.doc-editor-highlight .hljs-emphasis,
.doc-editor-highlight .hljs-strong {
  font-style: normal !important;
  font-weight: inherit !important;
}

/* ===== Markdown highlighting — document editor =====
   IMPORTANT: this is an overlay layered behind a transparent textarea, so
   every styled token must occupy the EXACT same width as the corresponding
   characters in the textarea. Anything that changes glyph metrics
   (font-weight/style, padding, border, letter-spacing) makes the caret drift
   away from the rendered glyph underneath. Color / background / text-
   decoration are safe — they don't change layout width. */
.doc-editor-highlight .language-markdown .hljs-section {
  color: var(--hl-keyword, #c678dd);
}
.doc-editor-highlight .language-markdown .hljs-strong {
  color: var(--hl-number, #d19a66);
}
.doc-editor-highlight .language-markdown .hljs-emphasis {
  color: var(--hl-string, #e5c07b);
}
.doc-editor-highlight .language-markdown .hljs-bullet {
  color: var(--hl-builtin, #56b6c2);
}
.doc-editor-highlight .language-markdown .hljs-code {
  color: var(--hl-builtin, #56b6c2);
  background: color-mix(in srgb, var(--hl-builtin, #56b6c2) 10%, transparent);
  border-radius: 2px;
}
.doc-editor-highlight .language-markdown .hljs-link {
  color: var(--hl-function, #61afef);
  text-decoration: underline;
}
.doc-editor-highlight .language-markdown .hljs-quote {
  color: var(--hl-comment, #828997);
}
.doc-editor-highlight .language-markdown .hljs-symbol {
  color: var(--red);
}
/* Standalone [bracketed text] — scene directions, annotations */
.doc-editor-highlight .language-markdown .md-bracket {
  color: var(--hl-builtin, #56b6c2);
  opacity: 0.85;
}
/* Heading # markers — dimmer than the heading text */
.doc-editor-highlight .language-markdown .md-heading-marker {
  color: var(--hl-comment, #828997);
  font-weight: 400;
}

/* ===== CUSTOM PRESET MODAL ===== */

.preset-modal-content {
  width: min(460px, 90vw);
  border-radius: 12px;
  overflow: hidden;
}
.preset-modal-body {
  display: flex;
  flex-direction: column;
  overflow-x: hidden;
}

/* Footer Start/Cancel buttons get a leading icon. Done via ::before + a masked
   SVG (not an inline  child) because the labels are set with .textContent,
   which would wipe a child element on every tab switch. background:currentColor
   makes the icon follow the button's text color. */
#save-custom-preset,
#cancel-custom-preset {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
#save-custom-preset::before,
#cancel-custom-preset::before {
  content: "";
  width: 13px; height: 13px;
  flex-shrink: 0;
  background-color: currentColor;
  -webkit-mask: var(--_btn-ic) center / contain no-repeat;
  mask: var(--_btn-ic) center / contain no-repeat;
}
#save-custom-preset::before {
  --_btn-ic: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpolygon points='5 3 19 12 5 21 5 3'/%3E%3C/svg%3E");
}
#cancel-custom-preset::before {
  --_btn-ic: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3C/svg%3E");
}

.preset-tabs {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--border);
  margin: 0 -10px 12px;
  padding: 0 10px;
  margin: 0 -16px;
  padding: 0 16px;
  margin-bottom: 12px;
}

.preset-tab {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px 10px;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--color-muted);
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.15s;
}
.preset-tab-icon { flex-shrink: 0; }
/* On narrow widths the icon + label can crowd 4 tabs — drop the labels to
   icon-only so the row stays clean. */
@media (max-width: 460px) {
  .preset-tab span { display: none; }
}

.preset-tab:hover {
  color: var(--fg);
  background: color-mix(in srgb, var(--fg) 4%, transparent);
}

.preset-tab.active {
  color: var(--red);
  border-bottom-color: var(--red);
}

.preset-tab-content {
  overflow: hidden;
}
.preset-tab-content.hidden {
  display: none;
}

.preset-templates-hint {
  font-size: 11px;
  color: var(--color-muted);
  margin: 0 0 8px;
}

.prompt-templates-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 300px;
  overflow-y: auto;
}

.prompt-template-card {
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: color-mix(in srgb, var(--fg) 3%, transparent);
  cursor: pointer;
  transition: all 0.15s;
}

.prompt-template-card:hover {
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  border-color: color-mix(in srgb, var(--accent) 30%, transparent);
}

.prompt-template-card.selected {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}

.prompt-template-name {
  font-size: 12px;
  font-weight: 600;
  color: var(--fg);
  margin-bottom: 4px;
}

.prompt-template-preview {
  font-size: 11px;
  color: var(--color-muted);
  line-height: 1.4;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.preset-slider-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 12px;
  margin-bottom: 6px;
}

.preset-slider-row label {
  font-size: 13px;
  color: var(--fg);
  font-weight: 500;
  margin: 0;
}

.preset-slider-value {
  font-size: 12px;
  color: var(--fg);
  font-weight: 600;
  min-width: 40px;
  text-align: right;
}

.preset-range {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 6px;
  background: var(--border);
  border-radius: 4px;
  outline: none;
  margin-bottom: 4px;
  box-sizing: border-box;
  display: block;
  padding: 0;
  margin-left: 0;
  margin-right: 0;
}

.preset-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--red, var(--fg));
  cursor: pointer;
  border: 2px solid var(--panel);
  box-shadow: 0 1px 4px rgba(0,0,0,0.2);
}

.preset-range::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--red, var(--fg));
  cursor: pointer;
  border: 2px solid var(--panel);
  box-shadow: 0 1px 4px rgba(0,0,0,0.2);
}

.preset-range::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 4px;
}

.preset-range::-moz-range-track {
  height: 6px;
  background: var(--border);
  border-radius: 4px;
}

.preset-temp-hints {
  display: flex;
  font-size: 10px;
  color: var(--color-muted);
  margin-top: -4px;
  margin-bottom: 10px;
  padding: 0 2px;
  opacity: 0.7;
}
.preset-temp-hints span {
  flex: 1;
}
.preset-temp-hints span:nth-child(2) {
  text-align: center;
}
.preset-temp-hints span:last-child {
  text-align: right;
}

.preset-clear-btn {
  padding: 7px 14px;
  background: none;
  border: 1px solid var(--border);
  color: var(--color-muted);
  border-radius: 6px;
  cursor: pointer;
  font-size: 12px;
  font-weight: 500;
  transition: all 0.15s;
}

.preset-clear-btn:hover {
  color: var(--color-error);
  border-color: var(--color-error);
}

.preset-hint-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 1px solid var(--border);
  font-size: 10px;
  font-weight: 600;
  color: var(--color-muted);
  cursor: help;
  vertical-align: middle;
  margin-left: 4px;
  transition: all 0.15s;
}
.preset-hint-icon:hover {
  color: var(--fg);
  border-color: var(--fg);
}

.preset-section-header {
  font-size: 11px;
  font-weight: 600;
  color: var(--color-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 6px;
  padding: 0 2px;
}

.user-template-card {
  position: relative;
}
.user-template-delete {
  background: none;
  border: none;
  color: var(--color-muted);
  font-size: 13px;
  cursor: pointer;
  padding: 0 2px;
  line-height: 1;
  opacity: 0;
  transition: opacity 0.15s, color 0.15s;
}
.user-template-card:hover .user-template-delete {
  opacity: 1;
}
.user-template-delete:hover {
  color: var(--color-error);
}

.preset-save-template-btn {
  padding: 7px 14px;
  border-radius: 6px;
  font-size: 12px;
  font-weight: 500;
  border: 1px solid var(--border);
  background: none;
  color: var(--fg);
  cursor: pointer;
  transition: all 0.15s;
}
.preset-save-template-btn:hover {
  border-color: var(--accent, var(--red));
  color: var(--accent, var(--red));
}

.char-prompt-wrap {
  position: relative;
}
.char-prompt-wrap textarea {
  padding-bottom: 28px;
}
.char-expand-btn {
  position: absolute;
  bottom: 14px;
  right: 6px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--color-muted);
  font-size: 11px;
  padding: 2px 8px;
  cursor: pointer;
  transition: all 0.15s;
  margin: 0;
  z-index: 1;
}
.char-expand-btn:hover {
  color: var(--red);
  border-color: var(--red);
}
.char-expand-btn.expanding {
  opacity: 0.5;
  pointer-events: none;
}

/* Memory scope bar (My Memories / Characters) */
.memory-scope-bar {
  display: flex;
  gap: 0;
  margin: 0 0 8px 0 !important;
  padding: 0 !important;
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
}
.memory-scope-btn {
  flex: 1;
  padding: 8px 12px !important;
  margin: 0 !important;
  font-size: 13px !important;
  font-weight: 600;
  background: none;
  border: none;
  color: var(--fg);
  opacity: 0.5;
  cursor: pointer;
  transition: all 0.15s;
}
.memory-scope-btn + .memory-scope-btn {
  border-left: 1px solid var(--border);
}
.memory-scope-btn.active {
  background: color-mix(in srgb, var(--red) 12%, transparent);
  color: var(--red);
  opacity: 1;
}
.memory-scope-btn:hover:not(.active) {
  background: color-mix(in srgb, var(--fg) 5%, transparent);
}
.memory-char-list {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}
.memory-char-chip {
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 500;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: none;
  color: var(--fg);
  cursor: pointer;
  transition: all 0.15s;
  margin: 0;
}
.memory-char-chip.active {
  background: color-mix(in srgb, var(--red) 12%, transparent);
  border-color: var(--red);
  color: var(--red);
}
.memory-char-chip:hover:not(.active) {
  background: color-mix(in srgb, var(--fg) 6%, transparent);
}

/* Disabled state dims the form */
#char-fields-wrap.disabled {
  opacity: 0.35;
  pointer-events: none;
  filter: grayscale(0.5);
  transition: opacity 0.2s, filter 0.2s;
}
#char-fields-wrap {
  transition: opacity 0.2s, filter 0.2s;
}

/* Name combo: input + delete btn */
.char-name-combo {
  display: flex;
  gap: 4px;
  align-items: center;
  margin-bottom: 8px;
}
.char-name-combo input,
.char-name-combo select {
  flex: 1;
}
.char-template-select {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--fg);
  padding: 6px 8px;
  font-size: 13px;
  font-family: inherit;
  cursor: pointer;
}
.char-template-select:focus {
  outline: none;
  border-color: var(--red);
}
.char-action-btn {
  background: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--color-muted);
  font-size: 11px;
  padding: 4px 8px;
  cursor: pointer;
  transition: all 0.15s;
  flex-shrink: 0;
  margin: 0 !important;
  white-space: nowrap;
  /* Uniform width so the trailing button column matches between the select
     row (+ New) and the name row (Reset) — that keeps the select and the
     name input the same width, since both fields flex:1 into the leftover. */
  min-width: 64px;
  text-align: center;
}
.char-action-btn:hover {
  color: var(--fg);
  border-color: var(--fg);
}
#char-delete-template-btn:hover {
  color: var(--color-error);
  border-color: var(--color-error);
}

/* Character toggle row in preset modal */
.preset-toggle-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 12px;
  font-size: 13px;
  color: var(--fg);
}
.preset-sub-option {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  padding: 8px 10px;
  font-size: 12px;
  color: var(--color-muted);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
}
.preset-mem-choice {
  flex: 1;
  padding: 6px 10px;
  font-size: 12px;
  font-weight: 500;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: none;
  color: var(--fg);
  cursor: pointer;
  transition: all 0.15s;
  margin: 0;
}
.preset-mem-choice.active {
  background: color-mix(in srgb, var(--red) 12%, transparent);
  border-color: var(--red);
  color: var(--red);
}
.preset-mem-choice:hover:not(.active) {
  background: color-mix(in srgb, var(--fg) 6%, transparent);
}


/* ===== MEMORY MODAL ===== */

.memory-modal-content {
  width: min(560px, 90vw);
  max-height: 78vh;
  font-size: 12px;
  overflow: hidden;   /* clip both axes; the inner .memory-modal-body owns scrolling.
                         (overflow-x alone promotes overflow-y to `auto` → a stray vertical scrollbar) */
  /* Subtle synapse-pulse — a soft radial glow that breathes in/out.
     Layered over the existing modal bg so it shows through without
     overpowering content. */
  position: relative;
  isolation: isolate;
}
.memory-modal-content::before { content: none; }
@keyframes memory-synapse-pulse {
  0%, 100% { opacity: 0.35; transform: scale(1); }
  50%      { opacity: 0.65; transform: scale(1.02); }
}
@media (prefers-reduced-motion: reduce) {
  .memory-modal-content::before { animation: none; opacity: 0.4; }
}
.memory-modal-content .modal-header h4 {
  font-size: 1rem;
}

.memory-modal-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  overflow-y: auto;
  overflow-x: hidden;        /* Stop synapse-pulse pseudo-elements from triggering a sideways scrollbar */
  overscroll-behavior: contain;
  min-height: 0;
  /* Fill the modal-content's height so the flex chain (tab-panel → admin-card
     → list → expanded card) is bounded. Without this the chain grows to
     content, so an expanded skill card pushed its footer off-screen; capping
     the preview then left it floating too high. Bounding here lets the
     preview flex to fill and the footer pin to the bottom naturally. */
  flex: 1 1 auto;
}
.memory-tabs {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--border);
  margin: -4px -4px 0;
  padding: 0 4px;
  flex-shrink: 0;
}
.memory-tab {
  background: none;
  border: none;
  color: var(--fg);
  opacity: 0.5;
  font-size: 12px;
  font-family: inherit;
  padding: 8px 14px;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  transition: opacity 0.15s, border-color 0.15s, color 0.15s, background 0.15s;
}
.memory-tab:hover {
  opacity: 0.8;
  background: color-mix(in srgb, var(--fg) 5%, transparent);
}
.memory-tab.active {
  opacity: 1;
  color: var(--red);
  border-bottom-color: var(--red);
}
.memory-tab-panel {
  display: flex;
  flex-direction: column;
  gap: 10px;
  overflow-y: auto;
  /* overflow-y:auto makes the browser compute overflow-x to `auto` too, which
     produced a stray horizontal scrollbar whenever a child was slightly too
     wide (long unbroken memory text, or the skills two-column row). Clip X
     explicitly — inner code blocks keep their own overflow-x:auto. */
  overflow-x: hidden;
  flex: 1;
  min-width: 0;
  min-height: 0;
}
.memory-tab-panel.hidden { display: none; }
/* Browse: bounded flex column so #memory-list gets remaining height (not 0px).
   height:min(78vh,max-content) gives a definite cap when long, natural height
   when short. flex-basis:auto (not 0) on the list avoids collapse in auto-sized
   parents. Toolbar siblings are flex-shrink:0; only #memory-list grows. */
#memory-modal .memory-modal-content:has(
  .memory-tab-panel[data-memory-panel="browse"]:not(.hidden)
) {
  display: flex;
  flex-direction: column;
  max-height: 78vh;
  height: min(78vh, max-content);
  overflow: hidden;
}
#memory-modal .memory-modal-content:has(
  .memory-tab-panel[data-memory-panel="browse"]:not(.hidden)
) .modal-header,
#memory-modal .memory-modal-content:has(
  .memory-tab-panel[data-memory-panel="browse"]:not(.hidden)
) .memory-tabs {
  flex: 0 0 auto;
}
#memory-modal .memory-modal-content:has(
  .memory-tab-panel[data-memory-panel="browse"]:not(.hidden)
) .memory-modal-body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}
#memory-modal .memory-tab-panel[data-memory-panel="browse"] {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}
#memory-modal .memory-tab-panel[data-memory-panel="browse"] > .admin-card {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}
#memory-modal .memory-tab-panel[data-memory-panel="browse"] > .admin-card > *:not(#memory-list):not(#memory-suggestions-body) {
  flex: 0 0 auto;
}
#memory-modal .memory-tab-panel[data-memory-panel="browse"] #memory-list:not(.hidden),
#memory-modal .memory-tab-panel[data-memory-panel="browse"] #memory-suggestions-body:not(.hidden) {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}
#memory-modal .memory-tab-panel[data-memory-panel="browse"] #memory-suggestions-body:not(.hidden) .memory-suggestions-header {
  flex-shrink: 0;
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--bg);
}
#memory-modal .memory-tab-panel[data-memory-panel].hidden {
  display: none;
}
/* Settings cards dim + mute when their toggle is OFF (matches the
   .memory-toolbar-toggle "off" treatment elsewhere). */
#memory-modal .memory-tab-panel[data-memory-panel="settings"] .admin-card {
  transition: opacity 0.15s, border-color 0.15s, background 0.15s;
}
#memory-modal .memory-tab-panel[data-memory-panel="settings"] .admin-card:has(.admin-switch input:not(:checked)) {
  opacity: 0.55;
  border-color: color-mix(in srgb, var(--fg) 8%, transparent);
  background: color-mix(in srgb, var(--fg) 2%, transparent);
}
/* Skills tab — two-column layout: skills list (left, wider) + Add Skill
   form (right, narrower). Collapses to a single column on narrow screens. */
.memory-tab-panel[data-memory-panel="skills"] {
  flex-direction: row;
  align-items: stretch;
  gap: 10px;
}
.memory-tab-panel[data-memory-panel="skills"] > .admin-card:first-of-type {
  flex: 2 1 0;
  min-width: 0;
}
.memory-tab-panel[data-memory-panel="skills"] > .admin-card:nth-of-type(2) {
  flex: 1 1 0;
  margin-top: 0 !important;
  min-width: 220px;
}
@media (max-width: 640px) {
  .memory-tab-panel[data-memory-panel="skills"] { flex-direction: column; }
  .memory-tab-panel[data-memory-panel="skills"] > .admin-card:nth-of-type(2) {
    margin-top: 12px !important;
  }
}
.memory-desc {
  margin: 0;
  font-size: 11px;
  line-height: 1.5;
  /* 65% keeps this description text above WCAG AA 4.5:1 (50% was ~3.9:1). */
  color: color-mix(in srgb, var(--fg) 65%, transparent);
}

.memory-add-row {
  display: flex;
  gap: 6px;
  align-items: center;
  height: 32px;
}

.memory-add-input {
  flex: 1;
  height: 28px;
  padding: 0 10px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--fg);
  font-family: inherit;
  font-size: 12px;
  box-sizing: border-box;
}
/* Textareas need explicit vertical padding — inputs vertically center text
   via line-height/height; textareas would otherwise pin text to the top. */
textarea.memory-add-input {
  height: auto;
  padding: 6px 10px;
  line-height: 1.4;
}
.memory-add-input::placeholder {
  color: color-mix(in srgb, var(--fg) 40%, transparent);
}

.memory-add-input:focus {
  outline: none;
  border-color: var(--red);
}

.memory-add-btn {
  width: 28px;
  height: 28px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--fg);
  font-size: 16px;
  box-sizing: border-box;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.15s;
  flex-shrink: 0;
}

.memory-add-btn:hover {
  background: var(--panel);
  border-color: var(--red);
  color: var(--red);
}

.memory-toolbar {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 4px 0 8px;
}

.memory-toolbar-row {
  display: flex;
  align-items: center;
  gap: 8px;
}



.memory-toolbar-btn {
  background: none;
  border: 1px solid var(--border);
  color: color-mix(in srgb, var(--fg) 60%, transparent);
  font-size: 11px;
  height: 24px;
  padding: 0 8px;
  border-radius: 6px;
  cursor: pointer;
  font-family: inherit;
  transition: all 0.15s;
  white-space: nowrap;
}



.memory-toolbar-btn:hover {
  border-color: var(--fg);
  color: var(--fg);
}

.memory-toolbar-btn.active {
  background: color-mix(in srgb, var(--red) 15%, transparent);
  border-color: color-mix(in srgb, var(--red) 40%, transparent);
  color: var(--red);
}

.memory-toolbar-btn.danger {
  color: var(--color-error);
  border-color: var(--color-error);
}

.memory-toolbar-btn.danger:hover {
  background: color-mix(in srgb, var(--color-error) 10%, transparent);
}

.memory-toolbar-btn:disabled {
  opacity: 1;
  cursor: default;
}
.memory-toolbar-btn.spinning {
  border-color: transparent;
  background: none;
}
.memory-toolbar-toggle {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  height: 24px;
  color: color-mix(in srgb, var(--fg) 60%, transparent);
  cursor: pointer;
  padding: 0 4px;
  user-select: none;
  transition: all 0.15s;
}
.memory-toolbar-toggle:hover {
  color: var(--fg);
}
.memory-toolbar-toggle .admin-switch {
  vertical-align: middle;
}
.memory-toolbar-toggle:has(input:not(:checked)) {
  opacity: 0.7;
}
.memory-toolbar-toggle:has(input:not(:checked)) > span {
  text-decoration: line-through;
  text-decoration-color: color-mix(in srgb, var(--fg) 30%, transparent);
}

/* Bulk action bar */
.memory-bulk-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 2px;
  border: 1px solid color-mix(in srgb, var(--red) 30%, transparent);
  border-radius: 8px;
  background: color-mix(in srgb, var(--red) 5%, transparent);
  font-size: 11px;
}

.memory-bulk-bar.hidden {
  display: none;
}
/* Nudge the bulk-bar action buttons up 2px (and Memory's -2px left) to
   align with the row baseline. Covers both the Memory bulk bar
   (Cancel/Delete) and the Skills bulk bar (Cancel/Approve/Delete) — both
   live inside #memory-modal. */
#memory-modal .memory-bulk-bar #memory-bulk-cancel,
#memory-modal .memory-bulk-bar #memory-bulk-delete {
  position: relative;
  top: -2px;
  left: -2px;
}
#memory-modal .memory-bulk-bar #skills-bulk-cancel,
#memory-modal .memory-bulk-bar #skills-bulk-publish,
#memory-modal .memory-bulk-bar #skills-bulk-audit,
#memory-modal .memory-bulk-bar #skills-bulk-delete-nonpassing,
#memory-modal .memory-bulk-bar #skills-bulk-delete {
  position: relative;
  top: -2px;
}
/* Research bulk bar — right-align the action buttons (keep All + count on
   the left). Cancel now lives on the Select toggle, so Archive anchors. */
#doclib-research-bulk #doclib-research-bulk-archive {
  margin-left: auto;
}
#doclib-research-bulk .memory-toolbar-btn {
  position: relative;
  top: 3px;
  right: 16px;
}
/* Archive bulk buttons — nudge down 1px to match research. */
#doclib-arc-bulk .memory-toolbar-btn {
  position: relative;
  top: 1px;
}
/* Same right-aligned layout for the other bulk bars — Chats, Documents,
   Archive, Skills, Memories. Cancel's auto-margin pushes the action group
   to the right; 8px of extra right padding seats it off the edge (matching
   the research bar's 8px nudge). */
#doclib-chats-bulk #doclib-chats-bulk-archive,
#doclib-bulk-bar #doclib-bulk-archive,
#doclib-arc-bulk #doclib-arc-bulk-restore,
#email-lib-bulk #email-lib-bulk-actions,
#tasks-bulk-bar #tasks-bulk-delete,
#serve-bulk-bar #serve-bulk-delete,
#gallery-bulk-bar #gallery-bulk-actions,
#gallery-editor-drafts-bulk #gallery-editor-drafts-bulk-delete,
#memory-modal .memory-bulk-bar #memory-bulk-delete,
#memory-modal .memory-bulk-bar #skills-bulk-publish {
  margin-left: auto;
  position: relative;
  top: -1px;
}

/* X-icon Cancel button used in every bulk-select bar (Esc target). The bare SVG
   sits slightly too high vs. the adjacent text buttons — nudge it down 2px. */
[id$="-bulk-cancel"] svg {
  position: relative;
  top: 2px;
}
#doclib-chats-bulk,
#doclib-bulk-bar,
#doclib-arc-bulk,
#email-lib-bulk,
#tasks-bulk-bar,
#serve-bulk-bar,
#gallery-bulk-bar,
#gallery-editor-drafts-bulk,
#memory-modal .memory-bulk-bar {
  padding-right: 18px;
}
#email-lib-bulk-delete.email-bulk-loading {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  opacity: 0.9;
  cursor: wait;
}
#email-lib-bulk-delete.email-bulk-loading .email-bulk-whirlpool {
  width: 12px;
  height: 12px;
  margin: 0;
  position: relative;
  top: -1px;
}
#email-lib-bulk-delete.email-bulk-loading .email-bulk-loading-label {
  position: relative;
  top: 0;
}
/* Drafts bulk bar defaults to justify-content:flex-end (whole row hugs the
   right). Reset it so All + count sit on the left and only the action button
   is pushed right — matching every other bulk bar. */
#gallery-editor-drafts-bulk {
  justify-content: flex-start;
}
/* Nudge the whole memory + skills bulk buttons (icon + label together) up. */
#memory-modal #memory-bulk-bar .memory-toolbar-btn,
#memory-modal #skills-bulk-bar .memory-toolbar-btn {
  position: relative;
  top: -2px;
}

.memory-bulk-check-all {
  display: flex;
  align-items: center;
  gap: 5px;
  cursor: pointer;
  color: color-mix(in srgb, var(--fg) 60%, transparent);
  font-size: 10px;
  padding: 4px 8px;
  border-radius: 4px;
  user-select: none;
  position: relative;
  top: 0;
}
.memory-bulk-check-all:hover {
  background: color-mix(in srgb, var(--fg) 6%, transparent);
}

#memory-selected-count {
  color: color-mix(in srgb, var(--fg) 50%, transparent);
  font-size: 10px;
  flex: 1;
}

/* Custom checkbox — toggle dot (shared by select-all and per-item) */
.memory-select-cb,
.memory-bulk-check-all input {
  -webkit-appearance: none;
  appearance: none;
  width: 6px !important;
  height: 6px !important;
  min-width: 6px;
  min-height: 6px;
  max-width: 6px;
  max-height: 6px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
  flex-shrink: 0;
  margin: 0;
  align-self: center;
  position: relative;
  box-sizing: content-box;
  transition: all 0.15s;
}

.memory-select-cb:hover,
.memory-bulk-check-all input:hover {
  border-color: var(--red);
}

.memory-select-cb:checked,
.memory-bulk-check-all input:checked {
  background: var(--red);
  border-color: var(--red);
}

.memory-count {
  font-size: 11px;
  color: var(--color-muted);
}

.memory-search-input {
  height: 24px;
  margin-top: 6px;
  padding: 0 8px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--fg);
  font-family: inherit;
  font-size: 11px;
  width: 100%;
  box-sizing: border-box;
}

.memory-search-input:focus {
  outline: none;
  border-color: var(--red);
}

.memory-list {
  flex: 1;
  min-height: 0;             /* Required so flex:1 inside a flex parent can shrink rather than push its parent past 85vh */
  overflow-y: auto;
  overflow-x: hidden;        /* Stop the synapse sweep from triggering a sideways scrollbar */
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.memory-item.task-paused {
  opacity: 0.45 !important;
  filter: saturate(0.55);
  background: repeating-linear-gradient(
    45deg,
    color-mix(in srgb, var(--fg) 2%, transparent),
    color-mix(in srgb, var(--fg) 2%, transparent) 8px,
    color-mix(in srgb, var(--fg) 5%, transparent) 8px,
    color-mix(in srgb, var(--fg) 5%, transparent) 16px
  ) !important;
}
.memory-item.task-paused:hover {
  opacity: 0.85 !important;
  filter: saturate(0.9);
}
.task-status-badge {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 9px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  padding: 1px 6px;
  border-radius: 3px;
  flex-shrink: 0;
  cursor: pointer;
  border: 1px solid transparent;
  line-height: 16px;
  font-family: 'Fira Code', monospace;
  transition: transform 0.12s ease, border-color 0.12s ease, background 0.12s ease, filter 0.12s ease;
  user-select: none;
}
.task-paused-badge {
  color: var(--orange, #ffb86c);
  background: color-mix(in srgb, var(--orange, #ffb86c) 22%, transparent);
  border-color: color-mix(in srgb, var(--orange, #ffb86c) 35%, transparent);
}
.task-active-badge {
  color: var(--green, #50fa7b);
  background: color-mix(in srgb, var(--green, #50fa7b) 20%, transparent);
  border-color: color-mix(in srgb, var(--green, #50fa7b) 35%, transparent);
}
.task-run-now-badge {
  color: var(--accent, var(--red));
  background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent);
  border-color: color-mix(in srgb, var(--accent, var(--red)) 34%, transparent);
}
.task-card-run-btn {
  appearance: none;
  height: 20px;
  min-height: 0;
  box-sizing: border-box;
  position: relative;
  top: -4px;
}
.task-state-badge svg {
  position: relative;
  top: -1px;
}
.task-status-badge:hover {
  filter: brightness(1.08) saturate(1.15);
}
.task-paused-badge:hover {
  background: color-mix(in srgb, var(--orange, #ffb86c) 30%, transparent);
  border-color: color-mix(in srgb, var(--orange, #ffb86c) 55%, transparent);
}
.task-active-badge:hover {
  background: color-mix(in srgb, var(--green, #50fa7b) 28%, transparent);
  border-color: color-mix(in srgb, var(--green, #50fa7b) 55%, transparent);
}
.task-run-now-badge:hover {
  background: color-mix(in srgb, var(--accent, var(--red)) 24%, transparent);
  border-color: color-mix(in srgb, var(--accent, var(--red)) 52%, transparent);
}

.task-builtin-badge {
  font-size: 9px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--accent, var(--red));
  background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent);
  padding: 1px 6px;
  border-radius: 8px;
  flex-shrink: 0;
  white-space: nowrap;
}
.task-builtin-badge.modified {
  color: var(--orange, #ff9800);
  border-color: color-mix(in srgb, var(--orange, #ff9800) 40%, transparent);
  background: color-mix(in srgb, var(--orange, #ff9800) 12%, transparent);
}
.memory-item {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: color-mix(in srgb, var(--fg) 3%, transparent);
  max-height: 200px;
  flex-shrink: 0;        /* memory-list is a flex column; without this, items get squeezed to fit */
  transition: all 0.15s;
}

.memory-item-title {
  font-size: 12px;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.memory-item:hover {
  background: color-mix(in srgb, var(--fg) 5%, transparent);
  border-color: color-mix(in srgb, var(--fg) 16%, transparent);
}
/* Synapse pulse — a brief horizontal light sweep runs left → right across
   each memory like a signal traversing a neural pathway. Per-item stagger
   via nth-child + varied durations keeps the list shimmering rather than
   pulsing in sync. */
#memory-list .memory-item {
  position: relative;
  overflow: hidden;
}
#memory-list .memory-item::after {
  /* Sweep highlight rides the border ring only — gradient-fill + mask cutout
     keeps the bright pulse on the 1px stroke instead of washing the body. */
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  background: linear-gradient(
    to right,
    transparent 0%,
    transparent calc(var(--sweep, -20%) - 8%),
    color-mix(in srgb, var(--red) 85%, transparent) var(--sweep, -20%),
    transparent calc(var(--sweep, -20%) + 8%),
    transparent 100%
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  animation: memory-synapse-sweep 6.2s linear infinite;
  animation-delay: 0.4s;
}
#memory-list .memory-item:nth-child(2n)::after { animation-duration: 7.4s; animation-delay: 1.6s; }
#memory-list .memory-item:nth-child(3n)::after { animation-duration: 8.8s; animation-delay: 3.2s; }
#memory-list .memory-item:nth-child(5n)::after { animation-duration: 9.3s; animation-delay: 4.7s; }
#memory-list .memory-item:nth-child(7n)::after { animation-duration: 5.5s; animation-delay: 2.3s; }
#memory-list .memory-item:hover::after { animation: none; opacity: 0; }
@property --sweep {
  syntax: '';
  inherits: false;
  initial-value: -20%;
}
@keyframes memory-synapse-sweep {
  /* Sweep traverses left → right in the first ~12% of the cycle (≈0.7s of
     a 6.2s loop), then waits offscreen. */
  0%   { --sweep: -20%; }
  12%  { --sweep: 120%; }
  13%, 100% { --sweep: 120%; }
}
@media (prefers-reduced-motion: reduce) {
  #memory-list .memory-item::after { animation: none; opacity: 0; }
}
.memory-pinned:hover {
  background: color-mix(in srgb, var(--red) 4%, transparent);
  border-color: var(--border);
  border-left-color: var(--red);
}

.memory-item-content {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.memory-item-text {
  font-size: 11px;
  line-height: 1.5;
  word-break: break-word;
  color: var(--fg);
}

.memory-item-edit-input {
  flex: 1;
  padding: 3px 5px;
  border-radius: 4px;
  border: 1px solid var(--red);
  background: var(--bg);
  color: var(--fg);
  font-family: inherit;
  font-size: 11px;
  min-width: 0;
}

.memory-item-edit-input:focus {
  outline: none;
}

/* Edit row: text input + category select side by side */
.memory-edit-row {
  display: flex;
  gap: 4px;
  flex: 1;
  min-width: 0;
}

.memory-edit-cat-select {
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--red);
  border-radius: 4px;
  font-family: inherit;
  font-size: 9px;
  padding: 2px 3px;
  cursor: pointer;
  flex-shrink: 0;
}

.memory-edit-cat-select:focus {
  outline: none;
}

.memory-item-editing {
  border-color: color-mix(in srgb, var(--red) 40%, transparent);
  background: color-mix(in srgb, var(--red) 3%, transparent);
}

.memory-menu-btn {
  background: none;
  border: 1px solid transparent;
  color: var(--color-muted);
  font-size: 18px;
  width: 24px;
  height: 24px;
  line-height: 24px;
  padding: 0;
  border-radius: 6px;
  cursor: pointer;
  flex-shrink: 0;
  opacity: 0;
  transition: opacity 0.15s, background 0.15s, border-color 0.15s;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.memory-item:hover .memory-menu-btn {
  opacity: 1;
}

.memory-menu-btn:hover {
  background: color-mix(in srgb, var(--fg) 7%, transparent);
  border-color: var(--border);
  color: var(--fg);
}

.memory-item-dropdown {
  display: none;
  position: fixed;
  z-index: 1000;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 4px;
  box-shadow: 0 4px 16px rgba(0,0,0,0.3);
  min-width: auto;
  width: max-content;
}

.memory-item-dropdown .dropdown-item-compact {
  padding: 6px 10px;
  font-size: 12px;
  cursor: pointer;
  border-radius: 6px;
  white-space: nowrap;
}

.memory-item-dropdown .dropdown-item-compact:hover {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}

.memory-dropdown-delete:hover {
  color: var(--red) !important;
}

.memory-item-actions {
  display: flex;
  gap: 4px;
  flex-shrink: 0;
  margin-left: auto;
  opacity: 0;
  transition: opacity 0.15s;
}

.memory-item:hover .memory-item-actions {
  opacity: 1;
}

/* Skill rows show actions at a dim opacity by default so view/run/delete are
   always discoverable, then brighten on hover. */
.memory-item.skill-row .memory-item-actions {
  opacity: 0.35;
  position: relative;
  top: -1px;
}
.memory-item.skill-row:hover .memory-item-actions {
  opacity: 1;
}

.memory-item-btn {
  background: none;
  border: 1px solid transparent;
  color: var(--color-muted);
  font-size: 11px;
  height: 22px;
  padding: 0 6px;
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.15s;
  display: flex;
  align-items: center;
}
@media (max-width: 768px) {
  /* Nudge the ••• menu button up on mobile so it visually aligns with the
     title row rather than sitting a hair below it. */
  .memory-item-actions .memory-item-btn { transform: translateY(-3px); }
  /* Email rows sit on a slightly different baseline (extra meta row /
     nav-arrows cluster), so pull the menu button back down 1px. */
  #email-lib-modal .memory-item-actions .memory-item-btn { transform: translateY(-2px); }
  /* Base rule above hides .memory-item-actions until hover. Mobile has no
     hover → the ⋮ button in cookbook serve / library cards was invisible
     and effectively unclickable. Force-show on mobile. */
  .memory-item-actions { opacity: 0.7 !important; }
  .memory-item-actions .memory-item-btn {
    width: 32px;
    height: 32px;
    min-width: 32px;
  }
}

/* Research-preview sub-sections — used by the research-tab expand pattern. */
.doclib-research-section-label {
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.55;
  margin: 8px 0 4px;
}
.doclib-research-sources ol {
  margin: 0;
  padding-left: 18px;
  font-size: 11px;
  line-height: 1.5;
}
.doclib-research-sources a {
  color: var(--accent, var(--red));
  text-decoration: none;
}
.doclib-research-sources a:hover { text-decoration: underline; }
.doclib-research-summary {
  font-size: 11px;
  line-height: 1.5;
}
.doclib-research-summary p { margin: 4px 0; }

.memory-item-btn:hover {
  color: var(--fg);
  border-color: var(--border);
  background: color-mix(in srgb, var(--fg) 6%, transparent);
}

.memory-item-btn.delete:hover {
  color: var(--color-error);
  border-color: var(--color-error);
}

.memory-item-btn.save {
  color: var(--red);
}

.memory-item-btn.save:hover {
  border-color: var(--red);
}

.memory-item-btn.pin {
  padding: 1px 4px;
  opacity: 0.4;
  display: flex;
  align-items: center;
  justify-content: center;
}
.memory-item-btn.pin.active {
  opacity: 1;
}
.memory-pin-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--fg);
  opacity: 0.5;
  transition: all 0.15s;
}
.memory-item-btn.pin.active .memory-pin-dot {
  background: var(--red);
  opacity: 1;
}
.memory-pinned {
  border-left: 3px solid var(--red);
  border-radius: 4px;
  background: color-mix(in srgb, var(--red) 4%, transparent);
}
.memory-pinned .memory-item-actions {
  opacity: 1;
}
.memory-pinned .memory-item-actions .memory-item-btn:not(.pin) {
  opacity: 0;
}
.memory-pinned:hover .memory-item-actions .memory-item-btn:not(.pin) {
  opacity: 1;
}

/* Category filter chips */
.memory-category-filters {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
}

.memory-cat-chip {
  background: none;
  border: 1px solid var(--border);
  color: color-mix(in srgb, var(--fg) 60%, transparent);
  font-size: 10px;
  height: 22px;
  padding: 0 8px;
  display: inline-flex;
  align-items: center;
  border-radius: 10px;
  cursor: pointer;
  font-family: inherit;
  transition: all 0.15s;
  text-transform: lowercase;
}

.memory-cat-chip:hover {
  border-color: var(--red);
  color: var(--red);
}

.memory-cat-chip.active {
  background: color-mix(in srgb, var(--red) 15%, transparent);
  border-color: color-mix(in srgb, var(--red) 40%, transparent);
  color: var(--red);
}

/* Sort select */
.memory-sort-select {
  position: relative;
  top: 3px;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-family: inherit;
  font-size: 11px;
  height: 24px;
  padding: 0 6px;
  cursor: pointer;
}

.memory-sort-select:focus {
  outline: none;
  border-color: var(--red);
}

/* Item metadata row */
.memory-item-meta {
  display: flex;
  gap: 6px;
  align-items: center;
  flex-wrap: wrap;
}

/* Category badge on each item */
.memory-cat-badge {
  font-size: 10px;
  padding: 2px 6px;
  border-radius: 4px;
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  color: color-mix(in srgb, var(--fg) 55%, transparent);
  text-transform: lowercase;
}

.memory-cat-identity {
  background: color-mix(in srgb, var(--hl-function) 15%, transparent);
  color: var(--hl-function);
}
.memory-cat-preference {
  background: color-mix(in srgb, var(--hl-keyword) 15%, transparent);
  color: var(--hl-keyword);
}
.memory-cat-contact {
  background: color-mix(in srgb, #98c379 15%, transparent);
  color: #98c379;
}
.memory-cat-project {
  background: color-mix(in srgb, var(--hl-string) 15%, transparent);
  color: var(--hl-string);
}
.memory-cat-goal {
  background: color-mix(in srgb, var(--red) 15%, transparent);
  color: var(--red);
}
.memory-cat-task {
  background: color-mix(in srgb, #d19a66 15%, transparent);
  color: #d19a66;
}
.memory-cat-pinned {
  background: color-mix(in srgb, var(--red) 15%, transparent);
  color: var(--red);
}

/* Source and time metadata */
.memory-item-source,
.memory-item-time,
.memory-item-uses {
  font-size: 9px;
  color: color-mix(in srgb, var(--fg) 35%, transparent);
}
.memory-item-uses {
  font-family: monospace;
  color: color-mix(in srgb, var(--fg) 55%, transparent);
}

.memory-item-source::before,
.memory-item-time::before {
  content: '\00b7  ';
}

/* Empty state */
.memory-empty {
  padding: 24px 16px;
  color: color-mix(in srgb, var(--fg) 35%, transparent);
  text-align: center;
  font-size: 11px;
  font-style: italic;
}

/* Suggestions area */
.memory-suggestions {
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* Bound the import-review list to the modal like the sibling .memory-list,
     so a long list scrolls internally instead of overflowing the
     overflow:hidden .admin-card — which clipped lower entries and their
     save/discard controls with no usable scroll area. */
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  /* Small gutter so the scrollbar doesn't sit flush against the item cards. */
  padding-right: 4px;
}

.memory-suggestions.hidden {
  display: none;
}

.memory-suggestions-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 11px;
  color: color-mix(in srgb, var(--fg) 70%, transparent);
  padding-bottom: 4px;
  border-bottom: 1px solid var(--border);
  /* Pin the title + save all/back controls to the top of the scrolling
     review list so they stay reachable while the items scroll under them.
     Opaque background masks items passing beneath. */
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--panel);
}
.memory-suggestions-actions,
.memory-suggestion-actions {
  display: flex;
  align-items: center;
  gap: 5px;
  flex-shrink: 0;
}
.memory-suggestions-actions {
  position: relative;
  left: -4px;
}
.memory-suggestions-actions .memory-item-btn,
.memory-suggestion-actions .memory-item-btn {
  background: color-mix(in srgb, var(--fg) 6%, transparent);
  border-color: color-mix(in srgb, var(--border) 85%, transparent);
}
.memory-suggestions-actions .memory-item-btn.save,
.memory-suggestion-actions .memory-item-btn.save {
  background: color-mix(in srgb, var(--red) 9%, transparent);
  border-color: color-mix(in srgb, var(--red) 28%, transparent);
}

.memory-suggestion-item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 6px;
  padding: 5px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: color-mix(in srgb, var(--red) 3%, transparent);
}

/* Tidy animations */
.memory-tidy-editing {
  border-color: color-mix(in srgb, var(--hl-function) 40%, transparent);
  background: color-mix(in srgb, var(--hl-function) 6%, transparent);
  transition: all 0.3s;
}

.memory-tidy-text-old {
  opacity: 0.4;
  text-decoration: line-through;
  transition: opacity 0.25s;
}

.memory-tidy-text-new {
  color: var(--hl-function);
  transition: color 0.4s;
}

.memory-tidy-removing {
  text-decoration: line-through;
  opacity: 0;
  max-height: 0;
  padding-top: 0;
  padding-bottom: 0;
  margin-top: 0;
  margin-bottom: 0;
  border-color: transparent;
  overflow: hidden;
  transition: opacity 0.3s, max-height 0.4s 0.1s, padding 0.4s 0.1s, border-color 0.3s;
}

/* ===== DOCUMENT EDITOR (ARTIFACTS) PANEL ===== */

/* Doc editor is a body-level sibling of chat-container */

/* Divider between chat and doc editor */
.doc-divider {
  width: 1px;
  flex-shrink: 0;
  background: color-mix(in srgb, var(--fg) 11%, transparent);
  cursor: col-resize;
  transition: background 0.15s;
  position: relative;
  z-index: 10;
}
.doc-divider::before {
  content: '';
  position: absolute;
  top: 0; bottom: 0;
  left: -10px;
  width: 21px;
  cursor: col-resize;
}
.doc-divider:hover {
  background: color-mix(in srgb, var(--fg) 30%, transparent);
}
/* Always-visible "›" handle on the drag divider — clickable to collapse the
   panel, and signals the divider is interactive. */
.doc-divider-collapse {
  position: absolute;
  top: 50%;
  left: 2px;
  transform: translateY(-50%);
  width: 20px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: 7px;
  background: var(--panel);
  color: var(--fg);
  font-size: 16px;
  font-weight: 700;
  line-height: 1;
  opacity: 0.6;
  cursor: pointer;
  transition: opacity 0.15s, border-color 0.15s, left 0.22s ease;
  z-index: 12;
}
/* When in fullscreen mode (cursor outside the doc), the flap itself slides
   to the OUTSIDE edge of the divider — visually belonging to the chat side
   the cursor is on, not the doc. The left/right shift pairs with the glyph
   rotation for a single coordinated transition. */
.doc-divider-collapse[data-mode="fullscreen"] {
  left: -22px;
}
.doc-divider:hover .doc-divider-collapse { opacity: 0.92; }
.doc-divider-collapse:hover { opacity: 1 !important; border-color: var(--accent, var(--red)); }
/* The same `›` glyph is in the markup; CSS rotates 180° for the left-pointing
   (fullscreen) state. Smooth transition pairs with the chevron's slide. */
.doc-divider-collapse > span {
  display: inline-block;
  transition: transform 0.22s ease, opacity 0.18s ease;
}
.doc-divider-collapse[data-mode="fullscreen"] > span {
  transform: rotate(180deg);
}
/* Secondary "hide panel" X button in the divider — invisible until the pane
   is fullscreen, then floats just below the unfullscreen chevron so the user
   has a one-tap escape that minimises the pane instead of just exiting
   fullscreen. */
.doc-divider-hide {
  position: absolute;
  top: 50%;
  left: 2px;
  width: 20px;
  height: 20px;
  margin-top: 22px;
  display: none;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--panel);
  color: var(--accent, var(--red));
  cursor: pointer;
  padding: 0;
  z-index: 12;
  opacity: 0.85;
  transition: opacity 0.15s, border-color 0.15s;
}
.doc-divider-hide:hover {
  opacity: 1;
  border-color: var(--accent, var(--red));
}
/* Smooth entrance — slide in from the left + fade up so it doesn't snap into
   place when fullscreen activates. The chevron's vertical centering uses
   translateY(-50%), so the animation has to keep that part. */
@keyframes doc-fs-chevron-in {
  from { opacity: 0; transform: translateY(-50%) translateX(-18px); }
  to   { opacity: 0.85; transform: translateY(-50%) translateX(0); }
}

/* Copy / Export split button — main click copies, the caret opens the export menu. */
.doc-split-btn {
  display: inline-flex;
  align-items: stretch;
  border: 1px solid var(--border);
  border-radius: 7px;
  overflow: hidden;
  height: 22px;
  flex-shrink: 0;
}
.doc-split-btn .doc-split-main,
.doc-split-btn .doc-split-caret {
  border: none !important;
  border-radius: 0 !important;
  height: 100% !important;
  min-height: 0 !important;
  /* Keep the element fully opaque so the divider line stays crisp; dim the
     glyph via colour instead (the base .doc-action-icon-btn fades the whole
     element to 0.3, which also hides the divider). */
  opacity: 1 !important;
  color: color-mix(in srgb, var(--fg) 55%, transparent) !important;
  background: none !important;
  transition: color 0.12s, background 0.12s;
}
.doc-split-btn:hover .doc-split-main,
.doc-split-btn:hover .doc-split-caret { color: var(--fg) !important; }
.doc-split-btn .doc-split-caret {
  border-left: 1px solid var(--border) !important;
  padding: 0 5px 0 7px !important;
}
.doc-split-btn .doc-split-main:hover,
.doc-split-btn .doc-split-caret:hover {
  background: color-mix(in srgb, var(--fg) 8%, transparent) !important;
  color: var(--fg) !important;
}

/* Editor pane — body-level flex sibling */
.doc-editor-pane {
  flex: 1;
  min-width: 0;
  max-width: 70vw;
  container-type: inline-size;
  container-name: docpane;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  border-left: 1px solid var(--border);
  box-shadow: -10px 0 22px rgba(0, 0, 0, 0.16);
  overflow: hidden;
  height: 100%;
  position: relative;
  z-index: 1;
  color-scheme: dark;
  /* Smooth open: slide in from the right + fade. Same easing/duration as
     the notes pane so both drawers feel like one mechanism. */
  animation: doc-pane-enter 200ms cubic-bezier(0.22, 0.61, 0.36, 1) both;
  transform-origin: right center;
  will-change: transform, opacity;
}
@keyframes doc-pane-enter {
  from { transform: translateX(24px); opacity: 0; }
  to   { transform: translateX(0);    opacity: 1; }
}
.doc-editor-pane.doc-pane-leaving {
  animation: doc-pane-leave 160ms cubic-bezier(0.4, 0, 1, 1) both;
  pointer-events: none;
}
@keyframes doc-pane-leave {
  from { transform: translateX(0);    opacity: 1; }
  to   { transform: translateX(24px); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .doc-editor-pane,
  .doc-editor-pane.doc-pane-leaving { animation: none; }
}
.doc-loading-overlay {
  position: absolute;
  inset: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
}

/* ---- Tab bar ---- */
.doc-tab-bar {
  display: flex;
  align-items: stretch;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
  height: 36px;
}
.doc-tab-scroll {
  display: flex;
  align-items: stretch;
  flex: 1;
  min-width: 0;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  justify-content: flex-start;
  /* Fade tabs into the bar's background at the edges (next to the scroll
     arrows) so an overflowing tab dissolves instead of being hard-cut. The
     fade is conditional — when we're at an edge there's nothing to fade to,
     so the mask gradient becomes flat on that side (no shadow). */
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 18px, #000 calc(100% - 18px), transparent 100%);
          mask-image: linear-gradient(to right, transparent 0, #000 18px, #000 calc(100% - 18px), transparent 100%);
}
.doc-tab-scroll.is-at-left {
  -webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 18px), transparent 100%);
          mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 18px), transparent 100%);
}
.doc-tab-scroll.is-at-right {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 18px, #000 100%);
          mask-image: linear-gradient(to right, transparent 0, #000 18px, #000 100%);
}
.doc-tab-scroll.is-at-left.is-at-right {
  -webkit-mask-image: none;
          mask-image: none;
}
.doc-tab-scroll::-webkit-scrollbar { display: none; }
.doc-tab-arrow {
  background: none;
  border: none;
  color: var(--fg);
  opacity: 0.3;
  cursor: pointer;
  font-size: 18px;
  padding: 0 6px;
  flex-shrink: 0;
  transition: opacity 0.15s;
  line-height: 1;
  display: flex;
  align-items: center;
  position: relative;
  top: -2px;
}
.doc-tab-arrow:hover {
  opacity: 1;
}
#doc-tab-right,
#doc-tab-left {
  position: relative;
  top: 3px;
}
/* Mobile swipe-down grab handle at the top of the doc sheet. */
.doc-mobile-grabber { display: none; }
@media (max-width: 768px) {
  body.doc-view .doc-mobile-grabber {
    display: block;
    flex-shrink: 0;
    height: 18px;
    position: relative;
    background: transparent;
    background-color: transparent;
    background-image: none;
    touch-action: none;
    cursor: grab;
  }
  body.doc-view .doc-mobile-grabber::before {
    content: '';
    position: absolute;
    top: 7px;
    left: 50%;
    transform: translateX(-50%);
    width: 36px;
    height: 4px;
    background: var(--fg);
    opacity: 0.25;
    border-radius: 2px;
  }
}

/* Ghost tab shown in the empty state (new, not-yet-saved document). Muted +
   italic so it reads as a placeholder, and non-interactive so clicking it
   can't hit the tab handlers (it has no data-doc-id). */
.doc-tab.doc-tab-ghost {
  /* New, not-yet-saved doc tab. It already carries .active, so it shows the
     accent underline like any active tab — that's all we want. The old dashed
     border + italic/dim "pending" styling looked weird, so they're gone. */
  pointer-events: none;
}
.doc-tab {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 10px;
  font-family: inherit;
  font-size: 11px;
  color: var(--fg);
  opacity: 0.4;
  cursor: pointer;
  white-space: nowrap;
  transition: opacity 0.1s, background 0.1s;
  flex-shrink: 0;
  border-right: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  position: relative;
}
.doc-tab:hover {
  opacity: 0.7;
  background: color-mix(in srgb, var(--fg) 3%, transparent);
}
.doc-tab.active {
  opacity: 1;
  background: color-mix(in srgb, var(--fg) 5%, transparent);
  border-radius: 7px 7px 0 0;
}
.doc-tab.active::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--red);
}
.doc-tab-title {
  max-width: 140px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.doc-tab-title-input {
  background: transparent;
  border: 1px solid var(--fg);
  border-radius: 2px;
  color: var(--fg);
  font-family: inherit;
  font-size: 11px;
  padding: 0 4px;
  height: 18px;
  width: 120px;
  max-width: 180px;
  outline: none;
}
.doc-tab-lang {
  font-size: 9px;
  opacity: 0.5;
}
.doc-tab-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  color: var(--fg);
  opacity: 0.4;
  cursor: pointer;
  font-size: 18px;
  line-height: 1;
  padding: 0 5px;
  margin-left: 3px;
  flex-shrink: 0;
  align-self: center;
  transition: opacity 0.12s;
}
.doc-tab-close:hover { opacity: 1; }
/* Mobile-only footer (Close + Copy); hidden on desktop and in email mode. */
.doc-mobile-footer { display: none; }
.doc-tab-new {
  background: none;
  border: none;
  color: var(--fg);
  opacity: 0.25;
  cursor: pointer;
  font-size: 11px;
  font-weight: 600;
  padding: 0 10px;
  transition: opacity 0.1s;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  height: 100%;
}
.doc-tab-new:hover {
  opacity: 0.7;
}
.doc-tab-play {
  background: none;
  border: none;
  color: var(--fg);
  opacity: 0.3;
  cursor: pointer;
  padding: 0 2px;
  font-size: 10px;
  line-height: 1;
  transition: opacity 0.1s, color 0.1s;
  flex-shrink: 0;
}
.doc-tab-play:hover {
  opacity: 1;
  color: var(--green, #4ec970);
}
.doc-tab-play.active {
  opacity: 1;
  color: var(--green, #4ec970);
}
.doc-tab.dragging {
  opacity: 0.3;
}
.doc-tab.drag-over {
  border-left: 2px solid var(--fg);
}

/* ---- HTML preview iframe ---- */
.doc-html-preview {
  flex: 1;
  width: 100%;
  border: none;
  background: #fff;
}

/* ---- Editor header ---- */
.doc-editor-header {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  /* Moved to the bottom as a footer: flex order pushes it last in the pane
     column, and the divider flips to the top edge. */
  order: 99;
  border-top: 1px solid var(--border);
  flex-shrink: 0;
  flex-wrap: nowrap;
  min-height: 36px;
  background: var(--bg);
}
/* Version now lives in the document tab, not the footer. */
#doc-version-badge { display: none !important; }
/* Language icon chip on doc tabs — sits between the version pill and title.
   Hidden when empty so docs without a language don't get awkward spacing. */
.doc-tab-lang {
  display: inline-flex; align-items: center;
  flex-shrink: 0;
  align-self: center;
}
.doc-tab-lang:empty { display: none; }
.doc-tab-lang svg { display: block; }

.doc-tab-version {
  font-size: 9px;
  font-weight: 600;
  padding: 1px 6px;
  cursor: pointer;
  flex-shrink: 0;
  align-self: center;
  line-height: 1.4;
  /* Sits to the LEFT of the title now — space it off the title text. */
  margin-right: 6px;
  /* Accent pill so it's obvious the version is a clickable control. */
  color: var(--accent-primary, var(--red));
  border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 45%, transparent);
  background: color-mix(in srgb, var(--accent-primary, var(--red)) 12%, transparent);
  border-radius: 9px;
}
.doc-tab-version:hover {
  border-color: var(--accent-primary, var(--red));
  background: color-mix(in srgb, var(--accent-primary, var(--red)) 22%, transparent);
}
.doc-close-btn {
  order: -1;
  opacity: 0.5;
  flex-shrink: 0;
}
.doc-close-btn:hover {
  opacity: 1;
}

.doc-editor-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 1;
  justify-content: flex-end;
}
.doc-left .doc-editor-actions {
  margin-left: 0;
}

.doc-version-badge {
  background: color-mix(in srgb, var(--red) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--red) 55%, transparent);
  color: var(--red);
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 600;
  line-height: 1.4;
  cursor: pointer;
  user-select: none;
  transition: background 0.1s, border-color 0.1s;
  opacity: 0.9;
}
.doc-version-badge:hover {
  opacity: 1;
  background: color-mix(in srgb, var(--red) 20%, transparent);
  border-color: var(--red);
}

.doc-stream-indicator {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  color: var(--accent);
  opacity: 0.9;
  white-space: nowrap;
  animation: doc-stream-pulse 1.5s ease-in-out infinite;
}
.doc-stream-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
}
@keyframes doc-stream-pulse {
  0%, 100% { opacity: 0.5; }
  50% { opacity: 1; }
}
.doc-updated-flash {
  animation: doc-update-flash 0.6s ease-out;
}
@keyframes doc-update-flash {
  0%   { box-shadow: inset 0 0 0 2px var(--accent); }
  100% { box-shadow: inset 0 0 0 2px transparent; }
}

/* In the doc footer the type picker sits next to the accent Copy/Export split —
   match its 28px height and 6px radius so the right-hand controls line up. */
.doc-actions-footer #doc-language-select {
  height: 28px;
  border-radius: 6px;
  font-size: 11px;
  top: 0;
}
/* Lang-type icon shown to the LEFT of the language select. Browsers won't
   render SVG inside 

. */ .a11y-visually-hidden { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0, 0, 0, 0) !important; white-space: nowrap !important; border: 0 !important; } /* ── Custom language type picker (replaces visible chrome of native */ .adm-provider-picker { position: relative; margin-bottom: 6px; } .adm-provider-combo { display: flex; align-items: stretch; } .adm-provider-combo input { flex: 1; min-width: 0; border-top-right-radius: 0; border-bottom-right-radius: 0; } .adm-provider-btn { width: 100%; display: flex; align-items: center; gap: 8px; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 6px; padding: 5px 8px; font-family: inherit; font-size: 12px; cursor: pointer; text-align: left; } .adm-provider-combo .adm-provider-btn { width: 128px; flex-shrink: 0; border-left: 0; border-top-left-radius: 0; border-bottom-left-radius: 0; justify-content: space-between; } .adm-provider-btn:hover { border-color: color-mix(in srgb, var(--fg) 30%, var(--border)); } .adm-provider-current { flex: 1; display: flex; align-items: center; gap: 8px; min-width: 0; } .adm-provider-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .adm-provider-logo { width: 14px; height: 14px; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; color: var(--fg); opacity: 0.85; } .adm-provider-logo svg { width: 14px; height: 14px; } .adm-provider-logo:empty { background: color-mix(in srgb, var(--fg) 12%, transparent); border-radius: 50%; } .adm-provider-caret { flex-shrink: 0; opacity: 0.5; transition: transform 0.15s; } .adm-provider-picker:has(.adm-provider-menu:not(.hidden)) .adm-provider-caret { transform: rotate(180deg); } .adm-provider-menu { position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 100; max-height: 280px; overflow-y: auto; background: var(--panel); border: 1px solid var(--border); border-radius: 6px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25); padding: 4px; } .adm-provider-menu.hidden { display: none; } .adm-provider-item { display: flex; align-items: center; gap: 8px; padding: 5px 8px; border-radius: 4px; font-size: 12px; cursor: pointer; } .adm-provider-item:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); } .adm-provider-item.active { background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); } /* When the Appearance tab is open, the #settings-modal-opacity slider (in the header) fades the modal so the user can see the rest of the UI react as toggles flip. The fade is applied as a background color-mix in JS (settings.js) rather than element opacity, so the controls/text stay crisp — mirroring the Theme customizer's opacity slider. */ /* Search provider fallback chain — chips + drag-reorder */ .search-fallback-chain { flex: 1; display: flex; align-items: center; flex-wrap: wrap; gap: 6px; min-height: 28px; } .search-fb-chip { display: inline-flex; align-items: center; gap: 4px; padding: 3px 4px 3px 6px; background: color-mix(in srgb, var(--fg) 6%, transparent); border: 1px solid var(--border); border-radius: 10px; font-size: 11px; cursor: grab; user-select: none; } .search-fb-chip.dragging { opacity: 0.4; } .search-fb-grip { opacity: 0.35; font-size: 10px; line-height: 1; cursor: grab; } .search-fb-logo { width: 12px; height: 12px; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; opacity: 0.85; } .search-fb-logo svg { width: 12px; height: 12px; } .search-fb-remove { background: none; border: none; color: var(--fg); opacity: 0.45; cursor: pointer; font-size: 14px; line-height: 1; padding: 0 2px; transition: opacity 0.15s, color 0.15s; } .search-fb-remove:hover { opacity: 1; color: var(--red); } .search-fb-add { background: var(--bg); color: var(--fg); border: 1px dashed var(--border); border-radius: 10px; padding: 2px 6px; font-family: inherit; font-size: 11px; outline: none; cursor: pointer; } .search-fb-add:focus, .search-fb-add:hover { border-color: var(--accent, var(--red)); border-style: solid; } .mcp-tools-search { width: 100%; box-sizing: border-box; margin-bottom: 6px; padding: 5px 8px; font-size: 11px; font-family: inherit; background: color-mix(in srgb, var(--fg) 4%, transparent); border: 1px solid var(--border); border-radius: 4px; color: var(--fg); outline: none; } .mcp-tools-search:focus { border-color: color-mix(in srgb, var(--accent) 60%, var(--border)); } .mcp-tools-count { font-size: 11px; font-weight: 600; opacity: 0.7; } /* Badges */ .admin-badge { font-size: 10px; padding: 2px 6px; border-radius: 4px; background: color-mix(in srgb, var(--red) 20%, transparent); color: var(--red); font-weight: 600; } .admin-badge-off { background: color-mix(in srgb, var(--color-error) 20%, transparent); color: var(--color-error); } /* Buttons */ .admin-btn-delete { background: none; border: 1px solid color-mix(in srgb, var(--color-error) 27%, transparent); color: var(--color-error); padding: 4px 10px; border-radius: 6px; cursor: pointer; font-size: 11px; font-family: inherit; transition: all 0.15s; } .admin-btn-delete:hover { background: var(--color-error); border-color: var(--color-error); color: #fff; } .admin-btn-add { padding: 4px 10px; border: none; border-radius: 6px; background: var(--red); color: #fff; cursor: pointer; font-weight: 600; font-size: 11px; font-family: inherit; transition: all 0.15s; } .admin-btn-add:hover { background: color-mix(in srgb, var(--red) 80%, white); } .admin-btn-add:disabled { opacity: 0.5; cursor: not-allowed; } .admin-btn-sm { padding: 3px 8px; border: 1px solid var(--border); border-radius: 6px; background: var(--panel); color: var(--fg); cursor: pointer; font-size: 11px; } .admin-btn-sm:hover { background: var(--border); border-color: var(--red); } .admin-spinner { display: inline-block; width: 12px; height: 12px; border: 2px solid var(--border); border-top-color: var(--red); border-radius: 50%; animation: admin-spin 0.6s linear infinite; vertical-align: -2px; margin-right: 2px; } @keyframes admin-spin { to { transform: rotate(360deg); } } /* Forms */ .admin-add-form { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; } .admin-add-form input { flex: 1; min-width: 120px; padding: 5px 8px; height: 32px; box-sizing: border-box; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-family: inherit; font-size: 12px; } .admin-add-form input:focus { outline: none; border-color: var(--red); } .admin-switch-inline { display: flex; align-items: center; gap: 6px; font-size: 11px; color: color-mix(in srgb, var(--fg) 60%, transparent); cursor: default; } .admin-model-form { display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; } .admin-model-form input, .admin-model-form select { padding: 5px 8px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 12px; } .admin-model-form input:focus { outline: none; border-color: var(--red); } .admin-model-form-row { display: flex; gap: 6px; } .admin-model-form-row input { flex: 1; } .adm-ep-inline-msg { min-height: 16px; margin-top: 5px; font-size: 11px; } /* Endpoint items */ .admin-ep-item { display: flex; align-items: center; gap: 6px; padding: 6px 8px; background: var(--bg); border-radius: 6px; margin-bottom: 4px; font-size: 12px; } .admin-ep-info { flex: 1; overflow: hidden; } .admin-ep-name { color: var(--fg); font-weight: 500; } .admin-ep-detail { color: color-mix(in srgb, var(--fg) 40%, transparent); font-size: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .admin-ep-actions { display: flex; gap: 4px; flex-shrink: 0; } /* Status messages */ .admin-error { color: var(--color-error); font-size: 11px; margin-top: 4px; } .admin-success { color: var(--color-success); font-size: 11px; margin-top: 4px; } .admin-empty { color: color-mix(in srgb, var(--fg) 40%, transparent); font-size: 12px; padding: 10px 0; text-align: center; } /* Endpoint-list empty states ("No endpoints configured" / "None") get a left-aligned, accent-colored treatment so they read as "this row is your placeholder, click Add" rather than a centered "nothing here" grey blob. */ #adm-epList-local .admin-empty, #adm-epList-api .admin-empty { text-align: left; padding: 8px 4px; color: var(--accent-primary, var(--accent, var(--red))); } /* RAG upload zone */ .admin-rag-upload-zone { border: 2px dashed var(--border); border-radius: 8px; padding: 14px; text-align: center; cursor: pointer; transition: border-color 0.2s, background 0.2s; margin-bottom: 8px; } .admin-rag-upload-zone:hover, .admin-rag-upload-zone.dragover { border-color: var(--red); background: color-mix(in srgb, var(--red) 7%, transparent); } .admin-rag-upload-zone p { color: color-mix(in srgb, var(--fg) 50%, transparent); font-size: 11px; margin-top: 4px; } .admin-rag-icon { font-size: 18px; } .admin-rag-dir-row { display: flex; gap: 6px; margin-bottom: 8px; } .admin-rag-dir-row input { flex: 1; padding: 5px 8px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 12px; } .admin-rag-dir-row input:focus { outline: none; border-color: var(--red); } .admin-rag-dir-row button { padding: 5px 8px; border: none; border-radius: 6px; background: var(--red); color: #fff; cursor: pointer; font-size: 12px; white-space: nowrap; } .admin-rag-toolbar { display: flex; justify-content: flex-end; margin-bottom: 6px; } .admin-rag-section-label { font-size: 10px; color: color-mix(in srgb, var(--fg) 50%, transparent); text-transform: uppercase; letter-spacing: 0.05em; margin: 8px 0 4px; } .admin-rag-item { display: flex; justify-content: space-between; align-items: center; padding: 6px 8px; background: var(--bg); border-radius: 6px; margin-bottom: 4px; font-size: 12px; } .admin-rag-item-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--fg); } .admin-rag-item-meta { color: color-mix(in srgb, var(--fg) 40%, transparent); font-size: 10px; margin: 0 6px; flex-shrink: 0; } .admin-rag-item .admin-btn-delete { font-size: 10px; padding: 2px 6px; } .admin-rag-status { font-size: 11px; color: color-mix(in srgb, var(--fg) 50%, transparent); margin-top: 6px; } /* Token reveal */ .admin-token-reveal { margin-top: 8px; padding: 8px; background: color-mix(in srgb, var(--red) 8%, var(--bg)); border: 1px solid color-mix(in srgb, var(--red) 25%, var(--border)); border-radius: 8px; } /* Selects in admin cards */ .admin-card select { background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 12px; } .admin-card input { font-family: inherit; font-size: 12px; } .admin-card label { font-size: 12px; } /* ---- Document Library ---- */ .doclib-modal-content { width: min(600px, 92vw); max-height: 85vh; font-size: 12px; background: var(--bg); } /* Anchor doclib modal to top so only the bottom edge moves on resize */ #doclib-modal { align-items: flex-start; padding-top: 8vh; } .doclib-modal-content .modal-header h4 { font-size: 1rem; } /* The "open in new tab" email modal inherited the 1rem doclib header, which read way bigger than the email subject in the regular (inline) reader. Pin it smaller so the two views are consistent. */ .email-reader-tab-modal .modal-header h4 { font-size: 13px; font-weight: 600; } /* Match the sticky modal-header to the doclib modal-content body color (var(--bg)) instead of the global var(--panel) default — otherwise the header reads as a darker stripe above the email list. */ .doclib-modal-content > .modal-header { background: var(--bg); } .doclib-stats { font-size: 11px; color: var(--fg-muted); margin-bottom: 8px; } .doclib-toolbar { display: flex; gap: 8px; margin-bottom: 8px; align-items: center; } .doclib-search { flex: 1; padding: 6px 8px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 12px; font-family: inherit; outline: none; transition: border-color 0.15s; } #archive-select-btn { height: auto; padding: 5px 8px; } .doclib-search:focus { border-color: var(--red); } .doclib-sort { padding: 6px 8px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 12px; font-family: inherit; outline: none; cursor: pointer; } .doclib-chips { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 8px; } .doclib-chip { padding: 2px 10px; border-radius: 12px; font-size: 10px; border: 1px solid var(--border); background: transparent; color: var(--fg-muted); cursor: pointer; user-select: none; transition: background 0.15s, border-color 0.15s; } .doclib-chip:hover { border-color: var(--red); } .doclib-chip.active { background: color-mix(in srgb, var(--red) 15%, transparent); border-color: color-mix(in srgb, var(--red) 40%, transparent); color: var(--red); } /* Document library — language chip row */ .doclib-lang-chips { display: flex; flex-wrap: wrap; gap: 4px; padding: 2px 0; max-height: 52px; overflow-y: auto; } .doclib-lang-chips:empty { display: none; } /* Mobile: keep tag chips on ONE row and scroll horizontally instead of wrapping into a tall multi-line block (Serve, library — anywhere .doclib-lang-chips is used). */ @media (max-width: 768px) { .doclib-lang-chips { flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; max-height: none; -webkit-overflow-scrolling: touch; scrollbar-width: none; } .doclib-lang-chips::-webkit-scrollbar { display: none; } .doclib-lang-chips > * { flex-shrink: 0; } } #doclib-tidy-btn, #doclib-select-btn, #doclib-chats-tidy-btn { position: relative; top: -3px; } /* Document library — list layout */ .doclib-grid { display: flex; flex-direction: column; gap: 4px; max-height: 400px; overflow-y: auto; padding: 0; position: relative; } /* Gallery grid domino-in cascade on open — same recipe as the document/email libraries. Applied to #gallery-grid in gallery.js on the first render after each open, removed after the longest delay so re-renders feel instant. */ .gallery-just-opened > .gallery-card { animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; } .gallery-just-opened > :nth-child(1) { animation-delay: 0.02s; } .gallery-just-opened > :nth-child(2) { animation-delay: 0.04s; } .gallery-just-opened > :nth-child(3) { animation-delay: 0.06s; } .gallery-just-opened > :nth-child(4) { animation-delay: 0.08s; } .gallery-just-opened > :nth-child(5) { animation-delay: 0.10s; } .gallery-just-opened > :nth-child(6) { animation-delay: 0.12s; } .gallery-just-opened > :nth-child(7) { animation-delay: 0.14s; } .gallery-just-opened > :nth-child(8) { animation-delay: 0.16s; } .gallery-just-opened > :nth-child(9) { animation-delay: 0.18s; } .gallery-just-opened > :nth-child(10) { animation-delay: 0.20s; } .gallery-just-opened > :nth-child(11) { animation-delay: 0.22s; } .gallery-just-opened > :nth-child(12) { animation-delay: 0.24s; } .gallery-just-opened > :nth-child(13) { animation-delay: 0.26s; } .gallery-just-opened > :nth-child(14) { animation-delay: 0.28s; } .gallery-just-opened > :nth-child(15) { animation-delay: 0.30s; } .gallery-just-opened > :nth-child(16) { animation-delay: 0.32s; } .gallery-just-opened > :nth-child(17) { animation-delay: 0.34s; } .gallery-just-opened > :nth-child(18) { animation-delay: 0.36s; } .gallery-just-opened > :nth-child(19) { animation-delay: 0.38s; } .gallery-just-opened > :nth-child(20) { animation-delay: 0.40s; } .gallery-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } /* Tasks list domino-in cascade on open — mirrors gallery / doclib. Applied to #tasks-list in tasks.js on the first render after each open, removed after the longest delay so re-renders feel instant. */ .tasks-just-opened > .task-card { animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; } .tasks-just-opened > :nth-child(1) { animation-delay: 0.02s; } .tasks-just-opened > :nth-child(2) { animation-delay: 0.04s; } .tasks-just-opened > :nth-child(3) { animation-delay: 0.06s; } .tasks-just-opened > :nth-child(4) { animation-delay: 0.08s; } .tasks-just-opened > :nth-child(5) { animation-delay: 0.10s; } .tasks-just-opened > :nth-child(6) { animation-delay: 0.12s; } .tasks-just-opened > :nth-child(7) { animation-delay: 0.14s; } .tasks-just-opened > :nth-child(8) { animation-delay: 0.16s; } .tasks-just-opened > :nth-child(9) { animation-delay: 0.18s; } .tasks-just-opened > :nth-child(10) { animation-delay: 0.20s; } .tasks-just-opened > :nth-child(11) { animation-delay: 0.22s; } .tasks-just-opened > :nth-child(12) { animation-delay: 0.24s; } .tasks-just-opened > :nth-child(13) { animation-delay: 0.26s; } .tasks-just-opened > :nth-child(14) { animation-delay: 0.28s; } .tasks-just-opened > :nth-child(15) { animation-delay: 0.30s; } .tasks-just-opened > :nth-child(16) { animation-delay: 0.32s; } .tasks-just-opened > :nth-child(17) { animation-delay: 0.34s; } .tasks-just-opened > :nth-child(18) { animation-delay: 0.36s; } .tasks-just-opened > :nth-child(19) { animation-delay: 0.38s; } .tasks-just-opened > :nth-child(20) { animation-delay: 0.40s; } .tasks-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } /* Document library cascade — same recipe as email library, applied per-tab the first time content loads (chats / archive / research / documents). Module-level Set in documentLibrary.js prevents re-firing on tab swaps or filter/sort re-renders within the same page session. */ .doclib-just-opened > .memory-item, .doclib-just-opened > .doclib-card { animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; } .doclib-just-opened > :nth-child(1) { animation-delay: 0.02s; } .doclib-just-opened > :nth-child(2) { animation-delay: 0.04s; } .doclib-just-opened > :nth-child(3) { animation-delay: 0.06s; } .doclib-just-opened > :nth-child(4) { animation-delay: 0.08s; } .doclib-just-opened > :nth-child(5) { animation-delay: 0.10s; } .doclib-just-opened > :nth-child(6) { animation-delay: 0.12s; } .doclib-just-opened > :nth-child(7) { animation-delay: 0.14s; } .doclib-just-opened > :nth-child(8) { animation-delay: 0.16s; } .doclib-just-opened > :nth-child(9) { animation-delay: 0.18s; } .doclib-just-opened > :nth-child(10) { animation-delay: 0.20s; } .doclib-just-opened > :nth-child(11) { animation-delay: 0.22s; } .doclib-just-opened > :nth-child(12) { animation-delay: 0.24s; } .doclib-just-opened > :nth-child(13) { animation-delay: 0.26s; } .doclib-just-opened > :nth-child(14) { animation-delay: 0.28s; } .doclib-just-opened > :nth-child(15) { animation-delay: 0.30s; } .doclib-just-opened > :nth-child(16) { animation-delay: 0.32s; } .doclib-just-opened > :nth-child(17) { animation-delay: 0.34s; } .doclib-just-opened > :nth-child(18) { animation-delay: 0.36s; } .doclib-just-opened > :nth-child(19) { animation-delay: 0.38s; } .doclib-just-opened > :nth-child(20) { animation-delay: 0.40s; } .doclib-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } /* Domino cascade on first open of the email library — mirrors the sidebar .section-just-expanded recipe so list items spring in staggered. Class is applied to #email-lib-grid in emailLibrary.js on the first render only, then removed after the longest delay so re-renders feel instant. */ .email-lib-just-opened .doclib-card { animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; } .email-lib-just-opened .doclib-card:nth-child(1) { animation-delay: 0.02s; } .email-lib-just-opened .doclib-card:nth-child(2) { animation-delay: 0.04s; } .email-lib-just-opened .doclib-card:nth-child(3) { animation-delay: 0.06s; } .email-lib-just-opened .doclib-card:nth-child(4) { animation-delay: 0.08s; } .email-lib-just-opened .doclib-card:nth-child(5) { animation-delay: 0.10s; } .email-lib-just-opened .doclib-card:nth-child(6) { animation-delay: 0.12s; } .email-lib-just-opened .doclib-card:nth-child(7) { animation-delay: 0.14s; } .email-lib-just-opened .doclib-card:nth-child(8) { animation-delay: 0.16s; } .email-lib-just-opened .doclib-card:nth-child(9) { animation-delay: 0.18s; } .email-lib-just-opened .doclib-card:nth-child(10) { animation-delay: 0.20s; } .email-lib-just-opened .doclib-card:nth-child(11) { animation-delay: 0.22s; } .email-lib-just-opened .doclib-card:nth-child(12) { animation-delay: 0.24s; } .email-lib-just-opened .doclib-card:nth-child(13) { animation-delay: 0.26s; } .email-lib-just-opened .doclib-card:nth-child(14) { animation-delay: 0.28s; } .email-lib-just-opened .doclib-card:nth-child(15) { animation-delay: 0.30s; } .email-lib-just-opened .doclib-card:nth-child(16) { animation-delay: 0.32s; } .email-lib-just-opened .doclib-card:nth-child(17) { animation-delay: 0.34s; } .email-lib-just-opened .doclib-card:nth-child(18) { animation-delay: 0.36s; } .email-lib-just-opened .doclib-card:nth-child(19) { animation-delay: 0.38s; } .email-lib-just-opened .doclib-card:nth-child(20) { animation-delay: 0.40s; } /* Cap the cascade at 20 — beyond that they animate together so opening a long inbox doesn't take forever to settle. */ .email-lib-just-opened .doclib-card:nth-child(n+21) { animation-delay: 0.42s; } /* Inside the email library modal, the grid needs to grow with the modal — but `flex: 1 1 0` collapses to 0 when the parent isn't height-constrained (which is the non-fullscreen default). Use `auto` basis + a sensible `min-height` floor so it shows ~400px naturally and absorbs more when the modal is resized / fullscreened. */ #email-lib-modal .doclib-grid { max-height: none; height: auto; flex: 1 1 auto; min-height: 400px; } /* ── Mobile compose FAB (email) ── Replaces the top-right "New" button on phones with a round mail-icon button bottom-right that collapses to a circle while the list scrolls and springs back out to "New" when scrolling stops. Desktop is unchanged. */ .email-lib-fab { display: none; } @media (max-width: 768px) { /* The absolute FAB anchors to the email panel card. */ #email-lib-modal .admin-card { position: relative; } /* Hide the toolbar "New" button — the FAB is the mobile entry point. */ #email-lib-compose-btn { display: none; } #email-lib-modal .email-lib-fab { --fab-size: 56px; display: flex; align-items: center; gap: 9px; /* Hidden until the email list has rendered; JS adds .fab-revealed to pop it in (scale-from-center). Avoids the button flashing at the top and sliding down before _positionFab() places it. */ transform: scale(0); opacity: 0; transform-origin: center center; position: absolute; right: calc(16px + env(safe-area-inset-right, 0px)); bottom: calc(18px + env(safe-area-inset-bottom, 0px)); height: var(--fab-size); min-width: var(--fab-size); padding: 0 20px 0 18px; border: none; border-radius: 999px; background: var(--accent-primary, var(--red)); color: #fff; font-family: inherit; font-size: 15px; font-weight: 600; line-height: 1; cursor: pointer; box-shadow: 0 6px 18px rgba(0,0,0,.38), 0 2px 6px rgba(0,0,0,.28); z-index: 30; pointer-events: auto; /* This (base) transition governs the EXPAND — slower + graceful. */ transition: padding 420ms cubic-bezier(0.22, 1, 0.36, 1), box-shadow 240ms ease, transform 140ms ease, background 140ms ease; will-change: padding, transform; } #email-lib-modal .email-lib-fab svg { flex: 0 0 auto; display: block; } #email-lib-modal .email-lib-fab .email-lib-fab-label { overflow: hidden; white-space: nowrap; max-width: 120px; opacity: 1; /* EXPAND timing — label eases out a touch after the width opens up. */ transition: max-width 420ms cubic-bezier(0.22, 1, 0.36, 1), opacity 300ms 60ms cubic-bezier(0.22, 1, 0.36, 1), margin 420ms cubic-bezier(0.22, 1, 0.36, 1); } /* Collapsed (while scrolling) → pure circle, icon only. We DON'T set an explicit width: the container is auto-width and shrinks smoothly as the padding + label max-width animate (min-width keeps it circular). Setting a fixed width here made the collapse snap, since auto↔px width can't tween. This (.collapsed) transition governs the COLLAPSE — kept snappy. */ #email-lib-modal .email-lib-fab.collapsed { padding: 0; justify-content: center; transition: padding 230ms cubic-bezier(0.4, 0, 0.2, 1), box-shadow 200ms ease, transform 140ms ease; } #email-lib-modal .email-lib-fab.collapsed .email-lib-fab-label { max-width: 0; opacity: 0; margin-left: -9px; transition: max-width 230ms cubic-bezier(0.4, 0, 0.2, 1), opacity 150ms cubic-bezier(0.4, 0, 0.2, 1), margin 230ms cubic-bezier(0.4, 0, 0.2, 1); } /* Entrance: circle-expand-from-middle pop, played once when revealed. No `forwards` fill so the resting transform (scale 1) comes from this rule — that keeps the :active press transform working afterwards. */ #email-lib-modal .email-lib-fab.fab-revealed { transform: scale(1); opacity: 1; animation: emailFabPop 380ms cubic-bezier(0.34, 1.56, 0.64, 1); } #email-lib-modal .email-lib-fab:active { transform: scale(0.93); filter: brightness(0.92); box-shadow: 0 3px 10px rgba(0,0,0,.4); } } @keyframes emailFabPop { from { transform: scale(0); opacity: 0; } 60% { transform: scale(1.06); opacity: 1; } to { transform: scale(1); opacity: 1; } } @media (prefers-reduced-motion: reduce) { #email-lib-modal .email-lib-fab, #email-lib-modal .email-lib-fab .email-lib-fab-label { transition-duration: 1ms; } #email-lib-modal .email-lib-fab.fab-revealed { animation: none; } } /* When the modal-content has explicit height (fullscreen or user-resized), drop the floor so the grid sizes purely from flex. */ #email-lib-modal.email-lib-fullscreen .doclib-grid, #email-lib-modal .modal-content[style*="height"] .doclib-grid { min-height: 0; } /* Document library: same fix as the email library (#74). When fullscreened the modal-content gets an inline `height: 100vh`, but the inner .doclib-grid stays capped at the 400px default so the list looks like a tiny strip floating in a giant empty modal. Mirror the email recipe: make the modal a flex column, give the body/admin-card claim of the remaining height, and let the grid take the rest. Scoped to the fullscreen class so windowed-mode layout is unchanged. */ /* Inner flex chain that lets the chats/documents grid claim the full remaining height of the modal. Applies in BOTH fullscreen AND right-docked states — without the docked state included, dragging a fullscreen doclib to the right snap zone breaks the flex layout because exitFullscreen removes .doclib-fullscreen, and the grid falls back to its base max-height: 400px showing only ~8 items. The CSS variable :is() selector lets both states share one rule. */ #doclib-modal.doclib-fullscreen .doclib-modal-content, #doclib-modal.modal-right-docked .doclib-modal-content, #doclib-modal.modal-left-docked .doclib-modal-content { display: flex; flex-direction: column; overflow: hidden; } /* Fullscreen positioning — scoped to NOT apply when also right-docked. Without this exclusion, dragging a fullscreen doclib to the right snap zone keeps .doclib-fullscreen on the modal and these !important rules override the dock's inline styles, leaving the modal stuck fullscreen instead of becoming a side panel. */ #doclib-modal.doclib-fullscreen:not(.modal-right-docked) .doclib-modal-content { position: fixed !important; top: 0 !important; left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)) !important; right: 0 !important; bottom: 0 !important; width: auto !important; max-width: none !important; height: 100vh !important; max-height: 100vh !important; border-radius: 0 !important; transform: none !important; } #doclib-modal.doclib-fullscreen .modal-header, #doclib-modal.doclib-fullscreen .lib-tabs, #doclib-modal.modal-right-docked .modal-header, #doclib-modal.modal-right-docked .lib-tabs, #doclib-modal.modal-left-docked .modal-header, #doclib-modal.modal-left-docked .lib-tabs { flex: 0 0 auto; } #doclib-modal.doclib-fullscreen .modal-body, #doclib-modal.modal-right-docked .modal-body, #doclib-modal.modal-left-docked .modal-body { flex: 1 1 0; min-height: 0; overflow: hidden; display: flex; flex-direction: column; } #doclib-modal.doclib-fullscreen .admin-card, #doclib-modal.modal-right-docked .admin-card, #doclib-modal.modal-left-docked .admin-card { flex: 1 1 0; min-height: 0; display: flex; flex-direction: column; } #doclib-modal.doclib-fullscreen .doclib-grid:not(:has(.doclib-card-expanded)), #doclib-modal.modal-right-docked .doclib-grid:not(:has(.doclib-card-expanded)), #doclib-modal.modal-left-docked .doclib-grid:not(:has(.doclib-card-expanded)) { max-height: none; height: auto; flex: 1 1 auto; min-height: 0; overflow-y: auto; } /* "Reply" docks the open email modal to the left as a sidebar so the doc panel (which slides in from the right of the chat area) is visible side-by-side. The .modal backdrop is already pointer-events:none for email modals — only the content takes pointer events — so the chat / doc panel underneath stays interactive. */ .modal.email-snap-left { align-items: stretch; justify-content: flex-start; left: 0 !important; width: 100% !important; } .modal.email-snap-left .modal-content { left: var(--email-doc-split-left-x, 0px) !important; width: var(--email-doc-split-email-w, 420px) !important; max-width: var(--email-doc-split-email-w, 420px) !important; border-right: 1px solid var(--border); box-shadow: 4px 0 14px rgba(0, 0, 0, 0.18); border-radius: 0; } @media (min-width: 769px) { body.email-doc-split-active.doc-view #email-lib-modal, body.email-doc-split-active.doc-view.email-front #email-lib-modal, body.email-doc-split-active.doc-view .modal[id^="email-reader-"], body.email-doc-split-active.doc-view.email-front .modal[id^="email-reader-"] { z-index: 150 !important; } body.email-doc-split-active #email-lib-modal.email-snap-left, body.email-doc-split-active #email-lib-modal.modal-left-docked { left: var(--email-doc-split-left-x, 0px) !important; width: var(--email-doc-split-email-w, 420px) !important; overflow: hidden !important; z-index: 155 !important; } body.email-doc-split-active #email-lib-modal.email-snap-left:not(.modal-dragging) .modal-content, body.email-doc-split-active #email-lib-modal.modal-left-docked:not(.modal-dragging) .modal-content, body.email-doc-split-active .modal[id^="email-reader-"].email-snap-left:not(.modal-dragging) .modal-content, body.email-doc-split-active .modal[id^="email-reader-"].modal-left-docked:not(.modal-dragging) .modal-content { position: absolute !important; left: 0 !important; top: 0 !important; bottom: 0 !important; right: auto !important; width: var(--email-doc-split-email-w, 420px) !important; max-width: var(--email-doc-split-email-w, 420px) !important; height: 100vh !important; max-height: 100vh !important; transform: none !important; margin: 0 !important; } body.email-doc-split-active.doc-view .doc-divider { display: none !important; } body.email-doc-split-active.doc-view .doc-editor-pane { position: fixed !important; left: var(--email-doc-split-right-x, 420px) !important; right: 0 !important; top: 0 !important; bottom: 0 !important; width: auto !important; max-width: none !important; height: 100vh !important; z-index: 260 !important; margin-top: 0 !important; transform: none !important; border-left: none !important; } } /* Email reader "Search text in this thread" / from-sender toggle — DISABLED on all viewports while the search/threaded-sidebar UX is too buggy to ship. Re-enable by removing this rule + the JS .remove() guards in emailLibrary.js. */ body [data-act="from-sender"] { display: none !important; } /* Snap-to-right docking. A modal dragged to the right edge becomes a docked side panel (mirrors Notes/Doc panels). Body reserves space via padding-right so the chat / notes / doc panel underneath shrinks to fit instead of being hidden behind the panel. */ body.right-dock-active { padding-right: var(--right-dock-w, 0px); } body.left-dock-active { padding-left: var(--left-dock-w, 0px); } .modal.modal-right-docked { align-items: stretch; justify-content: flex-end; } .modal.modal-right-docked .modal-content { border-left: 1px solid var(--border); box-shadow: -4px 0 14px rgba(0, 0, 0, 0.18); border-radius: 0; } .modal.modal-left-docked { align-items: stretch; justify-content: flex-start; } .modal.modal-left-docked .modal-content { border-right: 1px solid var(--border); box-shadow: 4px 0 14px rgba(0, 0, 0, 0.18); border-radius: 0; /* Pin transitions OFF on the dock's positioning properties. The wide sidebar collapse/expand changes --sidebar-w instantly, which means the docked modal's `left: calc(...)` jumps by ~240px. Any CSS transition on `left` (inherited or otherwise) would animate that jump as a fly-across. Same defense for right-docked / fullscreen panels in case future themes add a generic transition. */ transition: none !important; } .modal.modal-right-docked .modal-content, #email-lib-modal.email-lib-fullscreen .modal-content, #doclib-modal.doclib-fullscreen .doclib-modal-content { transition: none !important; } .modal.modal-right-docked .email-reader-header, .modal.modal-left-docked .email-reader-header { flex-direction: column; gap: 6px; } .modal.modal-right-docked .email-reader-actions, .modal.modal-left-docked .email-reader-actions { align-self: flex-end; } .modal.modal-right-docked .email-reader-meta-row, .modal.modal-left-docked .email-reader-meta-row { display: grid; grid-template-columns: 1fr; gap: 2px; align-items: start; } .modal.modal-right-docked .email-reader-meta-row strong, .modal.modal-left-docked .email-reader-meta-row strong { min-width: 0; } .modal.modal-right-docked .recipient-chip, .modal.modal-left-docked .recipient-chip { max-width: 100%; } .archive-list { margin-top: 8px; border-top: 1px solid var(--border); padding-top: 8px; } #archive-modal .memory-sort-select, #archive-modal .memory-toolbar-btn { height: 32px; } .archive-menu-btn { position: relative; top: 2px; } #archive-chips .doclib-chip { height: 22px; display: inline-flex; align-items: center; font-size: 10px; padding: 0 8px; } /* Library tab panels — Chats / Archive / Research / Documents share the same toolbar dimensions so the sort dropdown + Select button line up identically across tabs. */ #doclib-panel-chats .memory-sort-select, #doclib-panel-archive .memory-sort-select, #doclib-panel-research .memory-sort-select, [data-doclib-panel="documents"] .memory-sort-select { height: 24px; font-size: 11px; width: 86px; } #doclib-panel-chats .memory-toolbar-btn, #doclib-panel-archive .memory-toolbar-btn, #doclib-panel-research .memory-toolbar-btn, [data-doclib-panel="documents"] .memory-toolbar-btn { height: 24px; font-size: 11px; position: relative; top: -3px; } /* Research's Recent (sort) + Select sat 1px lower than the other tabs — nudge up. */ #doclib-panel-research .memory-sort-select { position: relative; top: 1.5px; } #doclib-panel-research .memory-toolbar-btn { top: -4.5px; } #doclib-research-search { position: relative; top: -1.5px; } [data-doclib-panel] { padding-top: 6px !important; } #doclib-panel-chats, #doclib-panel-archive { padding-top: 14px !important; } #doclib-panel-chats .memory-desc, #doclib-panel-archive .memory-desc, #doclib-panel-research .memory-desc { margin-top: 7px; } #doclib-panel-chats .memory-search-input, #doclib-panel-archive .memory-search-input { height: 30px !important; min-height: 30px !important; flex-shrink: 0; font-size: 11px; } /* Unified search bar across Library tabs + Memory modal — same height, same magnifying-glass icon at the start, consistent padding. */ #doclib-panel-chats .memory-search-input, #doclib-panel-archive .memory-search-input, #doclib-panel-research .memory-search-input, [data-doclib-panel="documents"] .memory-search-input, #memory-modal .memory-search-input, #tasks-modal .memory-search-input { height: 30px; min-height: 30px; font-size: 11px; background-image: url("data:image/svg+xml;utf8,"); background-repeat: no-repeat; background-position: 9px center; padding-left: 28px; } #doclib-panel-chats .doclib-chip { height: 28px; } #doclib-panel-archive .doclib-chip { height: 28px; } #doclib-panel-chats .doclib-chip, #doclib-panel-archive .doclib-chip { display: inline-flex; align-items: center; font-size: 9px; padding: 0 8px; border-radius: 10px; } .doclib-grid:has(.doclib-card-expanded) > .doclib-card:not(.doclib-card-expanded) { display: none; } /* Hide everything except the grid when a card is expanded */ .admin-card:has(.doclib-card-expanded) > *:not(.doclib-grid):not(.hwfit-cached-list) { opacity: 0; max-height: 0; overflow: hidden; pointer-events: none; margin: 0 !important; padding: 0 !important; transition: opacity 0.12s ease, max-height 0.2s ease; } .admin-card:has(.doclib-card-expanded) > .memory-bulk-bar { display: none; } .admin-card:has(.doclib-card-expanded) > .doclib-grid, .admin-card:has(.doclib-card-expanded) > .hwfit-cached-list { max-height: none !important; min-height: 0 !important; overflow: visible !important; } /* Firefox-mobile (no :has()) fallback: when reading an email, fully remove the list-mode chrome (accounts row, toolbar, bulk bar, desc) — display:none so no residual flex-gap leaves an empty strip above the reader — and zero the admin-card gap so the grid/reader starts flush at the top. */ #email-lib-modal.email-reading .admin-card > *:not(.doclib-grid):not(.hwfit-cached-list) { display: none !important; } #email-lib-modal.email-reading .admin-card { gap: 0 !important; } /* Override for chat-rows + research-rows: those expand inline with a bounded preview (max-height: 60vh), so the grid must stay clipped and scrollable. Without this scoping the overflow:visible above lets the expanded chat preview escape the grid and overlap whatever's underneath/beside the modal (e.g., a docked sibling panel) — what the user calls "merging with other windows". */ .admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { overflow: hidden auto !important; } /* Email modal needs the grid to STAY a constrained flex container when expanded — overflow:visible (set above for cookbook) lets content escape instead of letting the expanded reader fill via flex:1. We keep the same max-height:none + min-height:0 but flip overflow back so the reader actually fills the modal. */ #email-lib-modal .admin-card:has(.doclib-card-expanded) > .doclib-grid, #email-lib-modal.email-reading .admin-card > .doclib-grid { overflow: hidden !important; display: flex !important; flex: 1 1 auto !important; } #email-lib-modal.email-reading .doclib-modal-content { min-height: var(--email-reading-modal-min-h, auto); } #email-lib-modal .doclib-card.doclib-card-expanded { flex: 1 1 auto !important; height: 100% !important; /* Neutral frame on the active email — the accent border + glow felt overbearing on desktop; the size jump alone is enough signal. */ border: 1px solid var(--border) !important; box-shadow: 0 6px 18px rgba(0,0,0,0.12) !important; animation: none !important; } /* Desktop-only, ONLY on the currently-expanded email card. Nudges the title row down 6px / right 2px, bolds the subject, hides the timestamp from the meta line (the reader header carries its own date), and pulls the prev/next nav arrows in 4px. */ @media (min-width: 769px) { #email-lib-modal .doclib-card-expanded .email-card-titlerow, #email-lib-modal .doclib-card-expanded .email-card-titlerow > * { position: relative; top: 6px; left: 2px; } /* Smaller subject — the reader header below already carries weight. */ #email-lib-modal .doclib-card-expanded .email-card-titlerow .memory-item-title { font-weight: 700; font-size: 12px; } /* A bit more breathing room around the title row so the smaller bold text doesn't crowd the meta line below. (Timestamp kept — user changed mind.) */ #email-lib-modal .doclib-card-expanded .email-card-titlerow { padding: 4px 0 6px; line-height: 1.35; } /* Nudge the date down/left in the meta row, and give the meta line +4px of extra bottom space so the shifted date isn't clipped. */ #email-lib-modal .doclib-card-expanded .email-meta-date { position: relative; top: 4px; left: 0; } #email-lib-modal .doclib-card-expanded .memory-item-meta { padding-bottom: 4px; } #email-lib-modal .doclib-card-expanded .email-card-nav-arrows { position: relative; left: -4px; } } /* Skills modal: same mechanism as email above. The default expanded-grid rule (overflow:visible, no flex) lets the card expand inline to ~content height instead of filling — which is why the skill preview stopped at partial height. Flip the grid back to a constrained flex container and let the expanded card fill it, exactly like the email reader. */ #memory-modal .admin-card:has(.doclib-card-expanded) > .doclib-grid { overflow: hidden !important; display: flex !important; flex-direction: column !important; flex: 1 1 auto !important; } #memory-modal .doclib-card.doclib-card-expanded { flex: 1 1 auto !important; height: 100% !important; } /* When the from-sender sidebar is open inside the email card, the email body would otherwise lose 280px to padding-right and read as a narrow strip. Widen the whole modal so the body keeps its normal text width and the sidebar appears alongside, not on top of it. Also force the modal to its full max-height — short emails would otherwise leave the sidebar squeezed into a tiny vertical strip. */ #email-lib-modal .modal-content:has(.from-sender-panel) { width: min(1020px, 95vw) !important; height: 85vh !important; transition: width 0.22s ease-out, height 0.22s ease-out; } #email-lib-modal .modal-content { transition: width 0.22s ease-out, height 0.22s ease-out; } @media (min-width: 769px) { body:not(.email-doc-split-active) #email-lib-modal:not(.email-lib-fullscreen):not(.modal-left-docked):not(.modal-right-docked) .modal-content { min-height: min(560px, 85vh); } } /* Cookbook's cached-model list should scale with viewport height, not be capped at 400px */ .hwfit-cached-list { max-height: min(75vh, 900px) !important; overflow-y: auto; } /* Drag-and-drop visual hint for the email compose pane. Subtle accent outline + tinted overlay so it's obvious files will attach if dropped. */ .doc-editor-pane.email-dragover { outline: 2px dashed var(--accent, #2563eb); outline-offset: -8px; background: color-mix(in srgb, var(--accent, #2563eb) 6%, transparent); } .doc-editor-pane.email-dragover::after { content: 'Drop to attach'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: var(--accent, #2563eb); color: #fff; padding: 8px 16px; border-radius: 6px; font-size: 12px; pointer-events: none; z-index: 1000; } /* Email reader window — make the body always fill the available vertical space regardless of how the window is positioned/resized. The user can drag the bottom-right corner to resize; the body re-flexes automatically. */ .email-window-modal .modal-content { display: flex !important; flex-direction: column !important; resize: both; overflow: hidden; min-width: 320px; min-height: 240px; /* When user resizes/drags so the window covers a side, force the inner body to fill all the way to the bottom. The default max-height: 85vh (set inline by _makeDraggable's exitFullscreen) and 80vh from the opener both cap at the *viewport* — if the browser window then changes size (e.g. OS aero-snap), the modal stays at the old height and truncates rows. Drop the cap whenever an explicit width OR height has been written inline by the resize handle / drag handler. */ } .email-window-modal .modal-content[style*="width"], .email-window-modal .modal-content[style*="height"] { max-height: calc(100vh - 16px) !important; } .email-window-modal .email-window-body { flex: 1 1 auto !important; min-height: 0 !important; /* Explicit basis of 0 makes the body grow to fill remaining space rather than its content's intrinsic height (which was capping the thread). */ flex-basis: 0 !important; max-height: none !important; overflow: auto !important; overscroll-behavior: contain; } /* Individual email reader window — fullscreen mode (drag-to-top snap or double-click header). Same recipe as the library modal: pin to viewport edges and let the inner body absorb the remaining height. */ body:not(.email-doc-split-active) .email-window-modal.email-window-fullscreen .modal-content { position: fixed !important; inset: 0 !important; left: 0 !important; top: 0 !important; width: 100vw !important; max-width: 100vw !important; height: 100vh !important; max-height: 100vh !important; border-radius: 0 !important; transform: none !important; display: flex !important; flex-direction: column !important; } body:not(.email-doc-split-active) .email-window-modal.email-window-fullscreen .email-window-body { flex: 1 1 auto !important; min-height: 0 !important; max-height: none !important; } /* Email library modal: fullscreen mode + larger mobile sheet (mirrors the cookbook treatment so the toolbar and bulk actions stay reachable). Leaves whichever navigation strip is currently visible on the left (the wide sidebar OR the icon rail — they're mutually exclusive, so one of the two CSS vars is 0 at any given time, set by init.js). */ body:not(.email-doc-split-active) #email-lib-modal.email-lib-fullscreen:not(.modal-right-docked) .modal-content { position: fixed !important; top: 0 !important; left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)) !important; right: 0 !important; bottom: 0 !important; width: auto !important; max-width: none !important; height: 100vh !important; max-height: 100vh !important; border-radius: 0 !important; transform: none !important; } /* Mobile: the /email route adds .email-lib-fullscreen, but the desktop full- bleed rule above squares off the corners and offsets the email by the icon rail. On phones the email should be the normal bottom-sheet — full width, rounded top corners. Same specificity as the rule above + later in source, so it wins on mobile. */ @media (max-width: 768px) { body:not(.email-doc-split-active) #email-lib-modal.email-lib-fullscreen:not(.modal-right-docked) .modal-content { left: 0 !important; right: 0 !important; width: 100vw !important; max-width: 100vw !important; height: 100dvh !important; max-height: 100dvh !important; border-radius: 14px 14px 0 0 !important; border-top: 1px solid var(--border) !important; } } /* Make the inner panes actually grow to fill the fullscreen container instead of staying at their natural size. The body owns the remaining height below the header; the admin-card + grid then expand into it. */ #email-lib-modal.email-lib-fullscreen .modal-body { flex: 1 1 auto !important; min-height: 0 !important; overflow: hidden !important; } #email-lib-modal.email-lib-fullscreen .modal-body > .admin-card { flex: 1 1 auto !important; min-height: 0 !important; } #email-lib-modal.email-lib-fullscreen .doclib-grid { flex: 1 1 auto !important; min-height: 0 !important; max-height: none !important; overflow: auto !important; } @media (max-width: 768px) { /* Mobile email modal sizing — keep in sync with the rule earlier in the file. 75dvh tall always so flex children (the expanded email reader) have a real height to grow into. */ #email-lib-modal .modal-content { max-height: 90dvh !important; max-height: 90vh !important; height: 90dvh !important; height: 90vh !important; } /* Inner panes grow to fill the modal-content — without flex:1 on the body, the expanded email reader sits in a tiny box because there's nothing pushing it to take the remaining height. overflow:hidden + min-height:0 lets each layer pass its constraints down. */ #email-lib-modal .modal-body, #email-lib-modal .admin-card { flex: 1 1 auto !important; min-height: 0 !important; overflow: hidden !important; max-height: none !important; } #email-lib-modal .doclib-grid { flex: 1 1 auto !important; min-height: 0 !important; max-height: none !important; overflow-y: auto !important; } /* modal-content keeps its scroll OFF here — the inner flex children (doclib-grid for the list view, email-reader-body for the expanded reader) own the scroll surfaces. Without this, double-scroll layouts trap touches and the reader can't claim full height. */ #email-lib-modal .modal-content { overflow: hidden !important; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; touch-action: pan-y; } /* Attachment row on phones: cap to ~2 rows so it never dominates the reader. If more chips exist, the row scrolls vertically. Smaller chip padding + max-width keeps each chip compact so two rows usually fit everything anyway. */ #email-lib-modal .email-reader-atts { padding: 4px 10px !important; gap: 3px !important; max-height: calc(2 * 22px + 12px); /* 2 rows × chip height + padding */ overflow-y: auto; align-content: flex-start; } #email-lib-modal .email-attachment-chip { padding: 1px 6px !important; font-size: 10px !important; max-width: 130px !important; line-height: 18px !important; } } /* Mobile: only ONE scroll surface inside the cookbook modal. The modal-content is the scroller. Everything inside (cookbook-body, cookbook-group, all the inner lists/panels) gets overflow: visible so touch-pan never gets trapped in a nested scroller. Without this, three levels of overflow:auto + max-height combinations produce dead-zone areas where swipes do nothing. */ @media (max-width: 768px) { #cookbook-modal .modal-content { overflow-y: auto !important; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; touch-action: pan-y; } #cookbook-modal .cookbook-body, #cookbook-modal .cookbook-group, #cookbook-modal .cookbook-section-body, #cookbook-modal .hwfit-cached-list, #cookbook-modal .doclib-grid, #cookbook-modal .hwfit-list, #cookbook-modal .hwfit-panel-cmd { overflow: visible !important; max-height: none !important; min-height: 0 !important; } /* Running tmux output: cap how tall an expanded card gets (a long-lived job can leave thousands of lines) so it doesn't balloon, but keep its own scroll so you can read it. Outputs default collapsed on mobile now, so you're usually looking at one expanded card at a time — no wall of nested scrollers, and you collapse it to move past. */ #cookbook-modal .cookbook-output-pre, #cookbook-modal .cookbook-task .cookbook-output-pre { max-height: 45vh !important; min-height: 0 !important; overflow-y: auto !important; overflow-x: hidden !important; overscroll-behavior: auto; /* chain to the modal at the scroll boundary */ } /* cookbook-body's flex:1 was needed when it owned scrolling — drop it so the inner content drives modal-content's scroll height. */ #cookbook-modal .cookbook-body { flex: 0 0 auto !important; height: auto !important; } } .memory-toolbar { transition: opacity 0.12s ease, max-height 0.2s ease; max-height: 120px; } /* The Servers list reuses .memory-toolbar for layout but must grow with every added server — the 120px cap above was clipping manually-added servers. */ .memory-toolbar.cookbook-servers-toolbar { max-height: none; overflow: visible; } .doclib-card { background: color-mix(in srgb, var(--fg) 3%, transparent); border: 1px solid var(--border); border-radius: 8px; cursor: pointer; transition: all 0.15s, opacity 0.3s, max-height 0.3s ease; position: relative; } .doclib-card.doclib-card-deleting { opacity: 0; max-height: 0 !important; overflow: hidden; margin: 0; padding: 0; border-color: transparent; } .doclib-card:hover { background: color-mix(in srgb, var(--fg) 5%, transparent); border-color: color-mix(in srgb, var(--fg) 16%, transparent); } .doclib-card.selected, /* Universal selection highlight: any card-shaped row (documents, chats, archive, research, skills, memories, tasks) gets the same accent outline when its checkbox is checked. Done via :has() so the highlight follows selection without per-renderer JS changes. The canonical checkbox class is .memory-select-cb across every list. */ .memory-item:has(.memory-select-cb:checked), .doclib-card:has(.memory-select-cb:checked), .doclib-chat-row:has(.memory-select-cb:checked) { border-color: color-mix(in srgb, var(--red) 40%, transparent); background: color-mix(in srgb, var(--red) 4%, transparent); /* 2px accent outline overlaid on the 1px border — reads as a thicker selected border without shifting layout (every card would otherwise need a 2px transparent border to keep the same width). */ outline: 2px solid var(--accent-primary, var(--red)); outline-offset: -1px; } .doclib-card-header { display: flex; align-items: center; padding: 7px 8px; gap: 6px; min-width: 0; } /* Mobile only: push the unexpanded email card's title-row text down 4px so it sits visually centered with the surrounding card padding. Targets the actual title-row class (.email-card-titlerow) — the earlier attempt used .doclib-card-header which the email card builds differently. */ @media (max-width: 768px) { #email-lib-modal .doclib-card:not(.doclib-card-expanded) .email-card-titlerow { margin-top: 4px; } } .doclib-card-title { font-size: 11px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; flex: 1; } .doclib-card-ver { padding: 1px 6px; border-radius: 8px; background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); color: var(--accent, var(--red)); font-size: 9px; flex-shrink: 0; font-weight: 600; letter-spacing: 0.3px; min-width: 24px; text-align: left; box-sizing: border-box; } .doclib-card-ver-muted { background: color-mix(in srgb, var(--fg) 6%, transparent); color: color-mix(in srgb, var(--fg) 35%, transparent); } .doclib-card-lang { padding: 1px 6px; border-radius: 8px; background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--accent); font-size: 9px; text-align: left; flex-shrink: 0; min-width: 52px; box-sizing: border-box; } .doclib-card-session { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 60px; width: 100px; max-width: 100px; font-size: 9px; color: var(--fg-muted); flex-shrink: 0; text-align: left; } .doclib-card-time { flex-shrink: 0; opacity: 0.5; font-size: 9px; color: var(--fg-muted); min-width: 50px; text-align: left; } /* Footer — used by archive cards */ .doclib-card-footer { display: contents; } /* Preview — hidden by default, shown on expand */ .doclib-card-preview { display: none; position: relative; padding: 8px 12px; font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace; font-size: 9.5px; line-height: 1.5; color: var(--code-fg, var(--hl-fg, var(--fg))); border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); margin: 0; } .doclib-card-preview pre { margin: 0; white-space: pre-wrap; word-break: break-all; } .doclib-card-expanded .doclib-card-preview pre { flex: 1; overflow-y: auto; min-height: 0; } .doclib-card-preview code.hljs { background: none; padding: 0; } /* Expanded-only action bar — inside preview. Buttons shifted up 4px by trimming the row's top margin so they sit closer to the preview text. */ .doclib-card-expanded-actions { display: none; align-items: flex-start; gap: 6px; padding: 8px 0 2px; border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); margin-top: 4px; } .doclib-card-expanded-actions > .doclib-card-action-btn, .doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn { position: relative; top: -4px; } .doclib-action-group { display: flex; flex-direction: column; gap: 3px; } .doclib-action-btn-row { display: flex; gap: 6px; } .doclib-action-hint-row { display: flex; gap: 6px; } /* Match the chat/research footer buttons exactly: flat, bordered, app font, accent on hover. */ .doclib-card-action-btn { display: inline-flex; align-items: center; justify-content: center; gap: 4px; box-sizing: border-box; font-size: 10px; padding: 3px 8px; border-radius: 4px; background: none; border: 1px solid var(--border); color: var(--fg-muted); cursor: pointer; /* These buttons live inside .doclib-card-preview, which forces a monospace font. The chat/research footer buttons instead inherit the modal font, which is the app font (Fira Code) on mobile and Inter on desktop (.modal-content's Inter rule is inside @media min-width:769px). Mirror that here so all three footers match in both contexts. */ font-family: var(--font-family, 'Fira Code', monospace); transition: border-color 0.15s, color 0.15s; } @media (min-width: 769px) { .doclib-card-action-btn { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; letter-spacing: -0.015em; } } .doclib-card-action-btn:hover { border-color: var(--accent, var(--red)); color: var(--accent, var(--red)); } .doclib-card-text-btn-danger { color: var(--color-danger, #e06c75); border-color: var(--color-danger, #e06c75); } .doclib-card-text-btn-danger:hover { color: #ff4d4d; border-color: #ff4d4d; } .doclib-card-expanded-actions > .doclib-card-action-btn { font-size: 10px; padding: 3px 8px; } /* Push the Open/Clone group to the right edge of the action bar — Delete anchors the left, primary actions sit opposite. */ .doclib-card-expanded-actions > .doclib-action-group { margin-left: auto; } .doclib-btn-hint { font-weight: normal; opacity: 0.35; font-size: 8px; min-width: 58px; padding: 0 8px; box-sizing: border-box; } @media (max-width: 768px) { .doclib-btn-hint { display: none !important; } } /* Chat row expand preview — matches the documents-tab expand style. */ .doclib-chat-row { flex-direction: column; align-items: stretch; } .doclib-chat-row .doclib-card-chevron { opacity: 0.3; transition: transform 0.2s ease, opacity 0.15s; flex-shrink: 0; } .doclib-chat-row.doclib-card-expanded .doclib-card-chevron { opacity: 0.6; } /* Release .memory-item's base max-height: 200px when a chat row is expanded. Without this, the row clips at 200px on desktop and the preview's messages/actions visually spill past the row's box — "colliding" with the next chat in the column. The mobile takeover below already handles this via the same override; this rule is the desktop-or-any-viewport equivalent. */ .doclib-chat-row.doclib-card-expanded { max-height: none; overflow: visible; } /* When a chat row is expanded, take over the grid the same way documents do: hide siblings, hide the admin-card toolbar, and let this row plus its preview claim the full available height. .memory-item's default max-height: 200px must be overridden or the preview will be tiny. */ /* Mobile-only "card takeover" for an expanded chat — hides siblings, hides the admin-card toolbar, and lets this row + its preview claim the full available height. On desktop the expanded preview just renders inline below the row with its messages constrained by max-height + overflow:auto so the user gets a normal scroll. */ @media (max-width: 820px) { .doclib-grid:has(.doclib-chat-row.doclib-card-expanded) > .doclib-chat-row:not(.doclib-card-expanded) { display: none; } .admin-card:has(.doclib-chat-row.doclib-card-expanded) > *:not(.doclib-grid) { display: none; } .admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { flex: 1 1 auto !important; max-height: none !important; overflow-y: auto !important; } .doclib-chat-row.doclib-card-expanded { flex: 1 1 auto !important; max-height: none !important; min-height: 0 !important; overflow: hidden !important; display: flex !important; flex-direction: column !important; align-items: stretch !important; } .doclib-chat-row.doclib-card-expanded .doclib-chat-header { flex: 0 0 auto !important; } .doclib-chat-row.doclib-card-expanded .doclib-chat-preview { flex: 1 1 auto !important; min-height: 0 !important; overflow: hidden !important; display: flex !important; flex-direction: column !important; padding-bottom: 4px !important; } .doclib-chat-row.doclib-card-expanded .doclib-chat-preview-messages { -webkit-overflow-scrolling: touch; max-height: none !important; flex: 1 1 auto !important; min-height: 0 !important; } } .doclib-chat-preview { font-size: 11px; padding: 0 4px 6px; } .doclib-chat-preview .doclib-chat-open-btn { display: inline-flex; align-items: center; gap: 4px; font-size: 10px; padding: 3px 8px; border-radius: 4px; color: var(--fg-muted); border: 1px solid var(--border); background: none; cursor: pointer; font-family: inherit; transition: border-color 0.15s, color 0.15s; } .doclib-chat-preview .doclib-chat-open-btn:hover { border-color: var(--accent, var(--red)); color: var(--accent, var(--red)); } .doclib-chat-preview-messages { margin-top: 6px; max-height: 320px; overflow-y: auto; -webkit-overflow-scrolling: touch; padding-right: 4px; } /* Desktop expanded state — match the documents-tab behavior: hide sibling chats, hide the admin-card toolbar/header, and let the expanded row + its messages preview claim the full modal height. Chat rows use `memory-item doclib-chat-row` (no `doclib-card` class) so the document-tab's global rules at line 12284-12304 skip them — these mirror those rules scoped to chat rows. The mobile media query above (820px) keeps the same takeover behavior for phones. */ @media (min-width: 821px) { .doclib-grid:has(.doclib-chat-row.doclib-card-expanded) > .doclib-chat-row:not(.doclib-card-expanded) { display: none; } .admin-card:has(.doclib-chat-row.doclib-card-expanded) > *:not(.doclib-grid) { display: none; } .admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { flex: 1 1 auto !important; max-height: none !important; overflow: hidden auto !important; } .doclib-chat-row.doclib-card-expanded { flex: 1 1 auto !important; max-height: none !important; min-height: 0 !important; display: flex !important; flex-direction: column !important; } .doclib-chat-row.doclib-card-expanded .doclib-chat-preview { flex: 1 1 auto !important; min-height: 0 !important; display: flex !important; flex-direction: column !important; } .doclib-chat-row.doclib-card-expanded .doclib-chat-preview-messages { flex: 1 1 auto !important; min-height: 0 !important; max-height: none !important; overflow-y: auto !important; } } .doclib-chat-preview-actions { display: flex; justify-content: flex-end; align-items: center; gap: 8px; padding: 2px 0 2px; border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); margin-top: 2px; flex-shrink: 0; /* Whole action footer nudged down 5px. */ position: relative; top: 5px; } .doclib-chat-delete-btn, .doclib-chat-archive-btn, .doclib-chat-restore-btn, .doclib-chat-discuss-btn, .doclib-chat-copy-btn { display: inline-flex; align-items: center; gap: 4px; font-size: 10px; padding: 3px 8px; border-radius: 4px; background: none; cursor: pointer; font-family: inherit; transition: border-color 0.15s, color 0.15s; } .doclib-chat-archive-btn, .doclib-chat-restore-btn, .doclib-chat-discuss-btn, .doclib-chat-copy-btn { color: var(--fg-muted); border: 1px solid var(--border); } /* Restore sits on the right of the Delete in archive previews. */ .doclib-chat-restore-btn { margin-left: auto; } .doclib-chat-restore-btn:hover { color: var(--accent, var(--red)); border-color: var(--accent, var(--red)); } /* Delete + Archive pin to the left of the actions row; the `margin-right: auto` on the last left-side button (Archive) pushes Copy + Open to the right. Delete sits furthest left. */ .doclib-chat-archive-btn { margin-right: auto; } .doclib-chat-archive-btn:hover, .doclib-chat-discuss-btn:hover, .doclib-chat-copy-btn:hover { color: var(--accent, var(--red)); border-color: var(--accent, var(--red)); } .doclib-chat-delete-btn { color: var(--color-danger, #e06c75); border: 1px solid var(--color-danger, #e06c75); } .doclib-chat-delete-btn:hover { color: #ff4d4d; border-color: #ff4d4d; } /* When a chat is archived there's no Archive button, so Delete becomes the only left-side action and needs the auto margin to push Open right. */ .doclib-chat-preview-actions:not(:has(.doclib-chat-archive-btn)) > .doclib-chat-delete-btn { margin-right: auto; } /* Hide the "..." menu while the chat card is expanded — archive + delete live in the preview footer instead. */ .doclib-chat-row.doclib-card-expanded ._chat-menu { display: none; } .doclib-chat-preview-actions.doclib-chat-preview-actions-top { border-top: none; border-bottom: 1px solid color-mix(in srgb, var(--border) 30%, transparent); margin-top: 4px; margin-bottom: 6px; padding: 0 0 6px; justify-content: flex-start; } .doclib-chat-preview-messages .doclib-chat-msg { margin: 4px 0 10px; padding-left: 8px; border-left: 2px solid color-mix(in srgb, var(--border) 70%, transparent); } .doclib-chat-preview-messages .doclib-chat-msg.user { border-left-color: color-mix(in srgb, var(--accent, var(--red)) 60%, transparent); } .doclib-chat-preview-messages .doclib-chat-msg-role { font-size: 9px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; opacity: 0.55; margin-bottom: 2px; } .doclib-chat-preview-messages .doclib-chat-msg.user .doclib-chat-msg-role { color: var(--accent, var(--red)); opacity: 0.85; } .doclib-chat-preview-messages .doclib-chat-msg-body { white-space: pre-wrap; word-break: break-word; opacity: 0.85; line-height: 1.45; } /* Chat-bubble preview — mirrors the real chat layout. User bubbles hug right with an accent tint; assistant bubbles hug left with a neutral panel tint and a small model tag at the top. */ .doclib-chat-bubble-row { display: flex; margin: 6px 0; } .doclib-chat-bubble-row.user { justify-content: flex-end; } .doclib-chat-bubble-row.assistant { justify-content: flex-start; } .doclib-chat-bubble { max-width: 85%; padding: 6px 10px 8px; border-radius: 14px; font-size: 11px; line-height: 1.45; border: 1px solid var(--border); background: color-mix(in srgb, var(--fg) 4%, transparent); } .doclib-chat-bubble-row.user .doclib-chat-bubble { background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); border-color: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); border-bottom-right-radius: 4px; } .doclib-chat-bubble-row.assistant .doclib-chat-bubble { border-bottom-left-radius: 4px; } .doclib-chat-msg-model { display: inline-block; font-size: 8px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; opacity: 0.55; margin-bottom: 3px; padding: 1px 6px; border-radius: 6px; background: color-mix(in srgb, var(--fg) 8%, transparent); color: var(--fg-muted, var(--fg)); } .doclib-chat-bubble-body { word-break: break-word; } .doclib-chat-bubble-body p { margin: 4px 0; } .doclib-chat-bubble-body p:first-child { margin-top: 0; } .doclib-chat-bubble-body p:last-child { margin-bottom: 0; } .doclib-chat-bubble-body pre { font-size: 10px; margin: 4px 0; padding: 6px 8px; background: color-mix(in srgb, var(--bg) 60%, transparent); border-radius: 6px; overflow-x: auto; } .doclib-chat-bubble-body code { font-size: 10px; padding: 1px 4px; border-radius: 3px; background: color-mix(in srgb, var(--fg) 8%, transparent); } .doclib-chat-bubble-body pre code { background: none; padding: 0; } .doclib-chat-bubble-body ul, .doclib-chat-bubble-body ol { margin: 4px 0; padding-left: 18px; } .doclib-card-collapse-chevron { display: none; align-items: center; opacity: 0.3; cursor: pointer; flex-shrink: 0; margin-left: 4px; transition: opacity 0.15s; } .doclib-card-collapse-chevron:hover { opacity: 0.7; } .doclib-card-expanded .doclib-card-collapse-chevron { display: inline-flex; } /* Expanded card — fills the whole grid */ .doclib-card.doclib-card-expanded { flex: 1; min-height: 0; background: color-mix(in srgb, var(--fg) 3%, transparent); border: 1px solid var(--border); border-radius: 8px; display: flex; flex-direction: column; overflow: hidden; animation: doclib-expand 0.2s ease-out; } @keyframes doclib-expand { from { opacity: 0.5; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } } .doclib-card.doclib-card-expanded { flex-direction: column !important; align-items: stretch !important; max-height: none !important; gap: 4px !important; } .doclib-card.doclib-card-expanded > .memory-item-actions { display: none; } /* Non-preview children of an expanded doc card carry an inline flex:1 from the row layout. With the card now flex-column those children would steal half the height and shrink the preview — pin them at auto height. */ .doclib-card.doclib-card-expanded > div:not(.doclib-card-preview):not(.doclib-card-header):not(.memory-item-actions):not(.email-card-reader) { flex: 0 0 auto !important; } /* The email reader IS the scroll container — keep its flex:1 so its internal body can claim the rest of the card's height and scroll. */ .doclib-card.doclib-card-expanded > .email-card-reader { flex: 1 1 auto !important; min-height: 0 !important; } .doclib-card.doclib-card-expanded .doclib-card-preview { display: flex; flex-direction: column; flex: 1 1 auto !important; overflow-y: auto; min-height: 0; } .doclib-card.doclib-card-expanded .doclib-card-expanded-actions { display: flex; flex-shrink: 0; } /* Collapsible skills section headers (Your skills / Built-in). */ .skills-section-header { display: flex; align-items: center; gap: 6px; cursor: pointer; user-select: none; padding: 6px 4px 4px; font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; opacity: 0.6; font-weight: 600; } .skills-section-header:hover { opacity: 0.9; } .skills-section-chevron { transition: transform 0.15s ease; flex-shrink: 0; } .skills-section-header.collapsed .skills-section-chevron { transform: rotate(-90deg); } .skills-section-count { margin-left: auto; opacity: 0.6; font-variant-numeric: tabular-nums; font-weight: normal; } .skill-card-section-hidden { display: none !important; } /* Warning banner shown when previewing/editing a built-in capability. */ .skill-builtin-warn { font-size: 10.5px; line-height: 1.45; color: var(--color-warning, #f0ad4e); background: color-mix(in srgb, var(--color-warning, #f0ad4e) 12%, transparent); border: 1px solid color-mix(in srgb, var(--color-warning, #f0ad4e) 35%, transparent); border-radius: 6px; padding: 6px 9px; margin: 0 0 8px; flex-shrink: 0; } /* Hide the section headers while a card is expanded (full-screen reader). */ .doclib-grid:has(.doclib-card-expanded) > .skills-section-header { display: none; } /* #skills-list wears both .memory-list and .doclib-grid. doclib-grid's max-height:400px would cap the list AND clamp an expanded card — keep the memory-list fill-the-modal behaviour instead. */ #skills-list.doclib-grid { max-height: none; flex: 1; min-height: 0; } #skills-list.doclib-grid:has(.doclib-card-expanded) { overflow: hidden; /* expanded card owns its own scroll */ } /* When a skill is expanded, hide the sibling Add-Skill form card so the expanded SKILL.md uses the full panel. Otherwise the two admin-cards split the height ~50/50 and the expanded skill is stuck in its half while the Add form idles in the other. */ .memory-tab-panel[data-memory-panel="skills"]:has(.doclib-card-expanded) > .admin-card:nth-of-type(2) { display: none !important; } /* Skills cards reuse the doclib expand/footer machinery. The SKILL.md preview + the edit textarea need to fill the expanded card and own their own scroll, same as a document preview. */ /* Collapsed skill rows used a smaller (0.9em code) title, so the click row read slimmer than document/chat/library cards. Give the header a min-height so the tap target matches the other library items. */ .skill-card > .skill-card-header { min-height: 46px; box-sizing: border-box; display: flex; align-items: center; gap: 6px; } .skill-conf-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; } /* Name + description column. Name wraps to 2 lines (skill slugs are long — use the space rather than truncating to "check-model-downl…"). */ .skill-card-textcol { flex: 1 1 auto; min-width: 0; overflow: hidden; } .skill-card-name { font-weight: 600; font-size: 0.9em; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; word-break: break-word; line-height: 1.3; } .skill-card-desc { font-size: 10px; opacity: 0.55; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* Right cluster — pills (right-aligned), stats, then the menu/chevron. */ .skill-card-right { display: flex; align-items: center; gap: 6px; flex-shrink: 0; margin-left: auto; } .skill-card-right .memory-cat-badge { flex-shrink: 0; } .skill-model-pill { max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .skill-model-student { background: color-mix(in srgb, var(--fg) 10%, transparent); } .skill-model-teacher { background: color-mix(in srgb, var(--color-warning, #f0ad4e) 24%, transparent); color: var(--color-warning, #f0ad4e); } .skill-necessity-pill { max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .skill-necessity-duplicate { background: color-mix(in srgb, var(--color-warning, #f0ad4e) 22%, transparent); color: var(--color-warning, #f0ad4e); } .skill-necessity-trivial { background: color-mix(in srgb, var(--color-danger, #e06c75) 26%, transparent); color: var(--color-danger, #e06c75); border-color: color-mix(in srgb, var(--color-danger, #e06c75) 38%, transparent); } .skill-necessity-irrelevant { background: color-mix(in srgb, var(--color-danger, #e06c75) 18%, transparent); color: var(--color-danger, #e06c75); } .skill-duplicate-keep { background: color-mix(in srgb, var(--color-success, #4ade80) 22%, transparent); color: var(--color-success, #4ade80); } .skill-duplicate-lower { background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent); color: var(--color-danger, #e06c75); } .skill-stats { font-size: 9px; font-family: monospace; white-space: nowrap; color: color-mix(in srgb, var(--fg) 45%, transparent); } .skill-conf { font-weight: 700; } .skill-verified, .skill-teachermark, .skill-needsmark { display: inline-flex; align-items: center; vertical-align: middle; margin-right: 3px; } .skill-verified { color: var(--accent, #4ade80); } .skill-teachermark { color: var(--color-warning, #f0ad4e); } .skill-needsmark { color: var(--color-danger, #e06c75); cursor: help; } /* The card currently being processed by an "Audit now" run glows + pulses so it's obvious which one the audit is on. */ @keyframes skill-audit-pulse { 0%, 100% { box-shadow: 0 0 6px 0 color-mix(in srgb, var(--accent, #4ade80) 55%, transparent); } 50% { box-shadow: 0 0 16px 3px color-mix(in srgb, var(--accent, #4ade80) 85%, transparent); } } .skill-card.skill-audit-active { border-color: var(--accent, #4ade80) !important; animation: skill-audit-pulse 1.3s ease-in-out infinite; } /* A test is running for this skill — the app's whirlpool spinner is injected next to the name from JS (see _setCardRunning) so the collapsed/folded card still makes it obvious work is in progress. */ /* Collapsed bar shows the kebab menu; expanded shows an up-chevron to collapse. (Toggled by the .doclib-card-expanded class.) */ .skill-kebab-btn { display: inline-flex; align-items: center; justify-content: center; background: none; border: none; padding: 4px; margin: 0; cursor: pointer; color: var(--fg); opacity: 0.5; border-radius: 5px; flex-shrink: 0; position: relative; top: -2px; } .skill-kebab-btn:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 10%, transparent); } .skill-chevron-up { display: none; align-items: center; opacity: 0.5; flex-shrink: 0; } .skill-card.doclib-card-expanded .skill-kebab-btn { display: none; } .skill-card.doclib-card-expanded .skill-chevron-up { display: inline-flex; } /* Kebab dropdown */ .skill-kebab-menu { position: fixed; z-index: 100002; min-width: 150px; padding: 4px; background: var(--panel, var(--bg)); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 6px 20px rgba(0,0,0,0.22); display: flex; flex-direction: column; gap: 1px; } .skill-kebab-item { display: flex; align-items: center; gap: 8px; width: 100%; padding: 7px 9px; background: none; border: none; border-radius: 5px; cursor: pointer; color: var(--fg); font-size: 12px; text-align: left; } .skill-kebab-item:hover { background: color-mix(in srgb, var(--fg) 10%, transparent); } .skill-kebab-item.danger { color: var(--color-error, #e55); } .skill-kebab-item.danger:hover { background: color-mix(in srgb, var(--color-error, #e55) 15%, transparent); } /* Section labels separating "Your skills" from the read-only "Built-in capabilities" list. */ .skills-section-label { font-size: 10px; opacity: 0.5; text-transform: uppercase; letter-spacing: 0.04em; margin: 10px 2px 4px; flex-shrink: 0; } .skills-section-label:first-child { margin-top: 0; } /* When a learned skill is expanded the grid hides sibling cards; hide the section labels too so they don't orphan above the collapsed list. */ #skills-list:has(.doclib-card-expanded) .skills-section-label { display: none; } /* Built-in cards are read-only — no expand/hover-pointer affordance. */ .skill-builtin-card { cursor: default; } .skill-builtin-card:hover { border-color: var(--border); } .skill-card-preview .skill-md-pre { font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; font-size: 11.5px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; } /* Size the expanded skill card to its content (footer sits right under the preview) but cap at the modal height so a long SKILL.md scrolls instead of overflowing. The inherited `.doclib-card-expanded { flex: 1 }` forced the card to fill the whole modal, which left a big empty void between a short skill's text and the footer. */ .skill-card.doclib-card-expanded { flex: 0 1 auto !important; max-height: 100% !important; } /* :has()-free expand layout. skills.js adds .skills-has-expanded to the skills admin-card when a skill opens, so this works on engines that don't support :has() (e.g. older Firefox mobile, where the expand stuck at ~50%). Hide everything but the list so the grid fills the card; the absolutely-positioned expanded card (mobile) then fills the grid. */ .admin-card.skills-has-expanded > *:not(#skills-list) { display: none !important; } .admin-card.skills-has-expanded > #skills-list.doclib-grid { flex: 1 1 auto !important; height: 100% !important; min-height: 0 !important; overflow: hidden !important; } /* Expand animation — a clean opacity fade. The mobile card snaps to a full-screen overlay (position:absolute), so any translate/scale on it reads as a jarring "explosion"; a pure fade just appears smoothly. (No transform also keeps skills.js's height measurement accurate.) */ @keyframes skill-card-expand { from { opacity: 0; } to { opacity: 1; } } .skill-card.doclib-card-expanded { animation: skill-card-expand 0.18s ease-out; } /* Switching directly from one expanded card to another: no fade (it would show the previous card collapsing through the semi-transparent new one). */ .skill-card.doclib-card-expanded.skill-expand-instant { animation: none !important; } @media (prefers-reduced-motion: reduce) { .skill-card.doclib-card-expanded, .skill-card.doclib-card-expanded .doclib-card-preview { animation: none !important; } } /* The preview WRAPPER inherits flex:1 (grow) from the generic doclib expand rule — that's what stretched the card and left a void / pushed the footer out. Size it to content (shrink + scroll only when long) so the footer sits right under the preview and stays fully visible. */ .skill-card.doclib-card-expanded .doclib-card-preview { flex: 0 1 auto !important; min-height: 0; /* The footer lives INSIDE this wrapper. Don't let the wrapper scroll, or the footer scrolls off with the content. Keep it clipped; the
     inside is the scroller, so the footer stays pinned at the bottom. */
  overflow: hidden !important;
}
.skill-card.doclib-card-expanded .skill-md-pre {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}
/* Skill footer buttons sit 4px high (shared rule sets top:-4px) — drop them
   back down 4px so they're vertically centered in the skill card footer. */
.skill-card .doclib-card-expanded-actions > .doclib-card-action-btn,
.skill-card .doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn {
  top: 0;
}
/* Skills select-mode bulk bar — nudge Cancel / Approve / Delete up 2px. */
#skills-bulk-bar .memory-toolbar-btn {
  position: relative;
  top: -2px;
}
/* ── Audit-all progress panel ── */
.skills-audit-panel {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 10px;
  margin: 6px 0;
  background: color-mix(in srgb, var(--fg) 3%, transparent);
}
.skills-audit-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.skills-audit-title { font-size: 12px; font-weight: 600; }
.skills-audit-bar { height: 4px; border-radius: 2px; background: color-mix(in srgb, var(--fg) 12%, transparent); margin: 6px 0; overflow: hidden; }
.skills-audit-fill { height: 100%; background: var(--accent, var(--red)); transition: width 0.3s ease; }
.skills-audit-summary { font-size: 11px; opacity: 0.7; margin-bottom: 4px; }
.skills-audit-log {
  max-height: 22vh; overflow-y: auto;
  font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace;
  font-size: 10.5px; line-height: 1.5; opacity: 0.8;
}
/* ── Skill test monitor + AI eval verdict ── */
.skill-test { display: flex; flex-direction: column; gap: 8px; min-height: 0; }
.skill-test-log {
  flex: 1 1 auto;
  min-height: 120px;
  max-height: 48vh;
  overflow-y: auto;
  font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace;
  font-size: 9.5px;
  line-height: 1.45;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: color-mix(in srgb, var(--fg) 3%, transparent);
  white-space: pre-wrap;
  word-break: break-word;
}
.skill-test-log > div { margin: 1px 0; }
.skill-test-meta  { opacity: 0.5; }
.skill-test-task  { color: var(--accent, var(--red)); font-weight: 500; font-size: 9px; opacity: 0.85; }
.skill-test-round { opacity: 0.6; margin-top: 6px !important; }
.skill-test-tool  { color: var(--accent, var(--red)); opacity: 0.85; }
.skill-test-out   { opacity: 0.7; padding-left: 10px; }
.skill-test-say   { white-space: pre-wrap; }
.skill-test-err   { color: var(--color-danger, #e06c75); }
.skill-eval-head  { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.skill-eval-badge {
  font-size: 10px; font-weight: 700; letter-spacing: 0.03em;
  padding: 2px 8px; border-radius: 10px; flex-shrink: 0;
}
.skill-eval-ok   { background: color-mix(in srgb, var(--color-success, #4ade80) 26%, transparent); color: var(--color-success, #4ade80); }
.skill-eval-warn { background: color-mix(in srgb, var(--color-warning, #f0ad4e) 26%, transparent); color: var(--color-warning, #f0ad4e); }
.skill-eval-bad  { background: color-mix(in srgb, var(--color-danger, #e06c75) 26%, transparent); color: var(--color-danger, #e06c75); }
.skill-eval-unknown { background: color-mix(in srgb, var(--fg) 14%, transparent); }
.skill-eval-summary { font-size: 11px; opacity: 0.85; }
.skill-eval-issues { margin: 6px 0 0; padding-left: 18px; font-size: 11px; opacity: 0.8; }
.skill-eval-issues li { margin: 2px 0; }
.skill-eval-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 8px; }
.skill-eval-approve.suggested {
  border-color: var(--color-success, #4ade80) !important;
  color: var(--color-success, #4ade80) !important;
}
/* Already published — the button confirms the state (click to unpublish). */
.skill-eval-approve.is-approved {
  border-color: var(--color-success, #4ade80) !important;
  color: var(--color-success, #4ade80) !important;
  background: color-mix(in srgb, var(--color-success, #4ade80) 14%, transparent) !important;
}
/* Add-Skill form: a "rich placeholder" overlay so only the FIRST word (Title,
   When to use, How, Tags) is accent-colored while the rest stays muted — real
   placeholders can't be partially colored. The native placeholder is a single
   space so :placeholder-shown still toggles the overlay. */
.skill-ph-wrap { position: relative; }
.skill-ph-wrap .skill-hint-input { width: 100%; box-sizing: border-box; }
.skill-rich-ph {
  position: absolute;
  left: 11px; right: 11px; top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
  font-size: 12px;
  line-height: 1.2;
  color: color-mix(in srgb, var(--fg) 40%, transparent);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Textarea: align the hint to the first line instead of vertical-centering. */
.skill-rich-ph-top { top: 7px; transform: none; }
.skill-rich-ph .k { color: var(--accent, var(--red)); }
/* Hide the overlay once the field has real content. */
.skill-hint-input:not(:placeholder-shown) ~ .skill-rich-ph { display: none; }
.skill-md-editor {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  box-sizing: border-box;
  resize: none;
  font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace;
  font-size: 11.5px;
  line-height: 1.5;
  padding: 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--fg);
}
.doclib-empty {
  text-align: center;
  color: color-mix(in srgb, var(--fg) 35%, transparent);
  padding: 32px 16px;
  font-size: 12px;
  font-style: italic;
}
/* Unified loading row across every Library tab (Chats / Documents / Research /
   Archive): one opacity, one size, with the whirlpool spinner next to it. */
.lib-loading-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 28px 16px;
  font-size: 12px;
  color: color-mix(in srgb, var(--fg) 45%, transparent);
}
.doclib-load-more {
  display: block;
  margin: 10px auto 0;
  padding: 6px 16px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--fg-muted);
  font-size: 11px;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
  flex-shrink: 0;
  position: relative;
  top: 8px;
}
/* Soft dark gradient above the button — blends the list content into the
   load-more strip instead of a hard cutoff. Spans the modal width and
   sits behind/above the button. */
.doclib-load-more::before {
  content: "";
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 100%;
  margin-bottom: 4px;
  width: min(600px, 92vw);
  height: 34px;
  pointer-events: none;
  background: linear-gradient(to top, color-mix(in srgb, #000 24%, transparent), transparent);
}
.doclib-load-more:hover {
  border-color: var(--red);
  color: var(--red);
}

/* Document library toolbar buttons */
.doclib-toolbar-btn {
  background: none;
  border: 1px solid var(--border);
  color: var(--fg-muted);
  font-size: 11px;
  padding: 5px 10px;
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.15s;
  white-space: nowrap;
  font-family: inherit;
  display: inline-flex;
  align-items: center;
  gap: 3px;
}
.doclib-toolbar-btn:hover {
  color: var(--fg);
  border-color: var(--fg);
}
.doclib-toolbar-btn.active {
  background: color-mix(in srgb, var(--red) 15%, transparent);
  border-color: color-mix(in srgb, var(--red) 40%, transparent);
  color: var(--red);
}
.doclib-toolbar-btn.danger {
  color: var(--color-error, #e55);
}
.doclib-toolbar-btn.danger:hover:not(:disabled) {
  border-color: var(--color-error, #e55);
}
.doclib-toolbar-btn:disabled {
  opacity: 0.4;
  cursor: default;
}

/* Bulk action bar */
.doclib-bulk-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  border: 1px solid color-mix(in srgb, var(--red) 30%, transparent);
  border-radius: 6px;
  background: color-mix(in srgb, var(--red) 5%, transparent);
  font-size: 10px;
  margin-bottom: 8px;
}
.doclib-bulk-bar.hidden {
  display: none;
}
.doclib-bulk-check-all {
  display: flex;
  align-items: center;
  gap: 3px;
  cursor: pointer;
  color: color-mix(in srgb, var(--fg) 60%, transparent);
  font-size: 10px;
}
#doclib-selected-count {
  color: color-mix(in srgb, var(--fg) 50%, transparent);
  font-size: 10px;
}
#doclib-bulk-bar .memory-toolbar-btn {
  position: relative;
  top: -3px;
}

/* Custom checkboxes */
.doclib-select-cb,
.doclib-bulk-check-all input {
  -webkit-appearance: none;
  appearance: none;
  width: 13px;
  height: 13px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg);
  cursor: pointer;
  flex-shrink: 0;
  margin: 0;
  position: relative;
  transition: all 0.15s;
}
.doclib-select-cb:hover,
.doclib-bulk-check-all input:hover {
  border-color: var(--red);
}
.doclib-select-cb:checked,
.doclib-bulk-check-all input:checked {
  background: var(--red);
  border-color: var(--red);
}
.doclib-select-cb:checked::after,
.doclib-bulk-check-all input:checked::after {
  content: '';
  position: absolute;
  left: 3px;
  top: 0px;
  width: 4px;
  height: 8px;
  border: solid var(--bg);
  border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg);
}


/* + button outside scroll area (when doc is on right side) */

@media (max-width: 600px) {
  .doclib-toolbar {
    flex-direction: column;
  }
  .doclib-card-session,
  .doclib-card-time {
    display: none;
  }
  .doclib-card-preview {
    max-height: 40vh;
  }
}

/* ── Archive browser ── */
.archive-list {
  grid-template-columns: 1fr !important;
  gap: 4px !important;
}
.archive-row {
  flex-direction: row !important;
}
.archive-row .doclib-card-header {
  flex: 1;
  padding: 8px 10px;
}
.archive-row .doclib-card-title {
  flex: 1;
  min-width: 0;
}
/* Archive column layout */
.archive-header {
  display: flex;
  align-items: center;
  padding: 4px 10px;
  gap: 6px;
  font-size: 9px;
  font-weight: 600;
  opacity: 0.35;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.archive-col-title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-left: -4px;
}
.archive-col-msgs {
  width: 40px;
  flex-shrink: 0;
  text-align: left;
  margin-left: -8px;
}
.archive-col-model {
  width: 90px;
  flex-shrink: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.archive-col-time {
  width: 55px;
  flex-shrink: 0;
  text-align: right;
}
.archive-col-menu {
  width: 24px;
  flex-shrink: 0;
}
.archive-menu-btn {
  background: transparent;
  border: none;
  color: var(--fg);
  opacity: 0.3;
  cursor: pointer;
  padding: 4px;
  line-height: 0;
  border-radius: 4px;
  transition: background .15s, opacity .15s;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  margin-left: 4px;
  vertical-align: middle;
  height: 100%;
}
.archive-menu-btn svg {
  display: block;
  margin-top: -6px;
}
.archive-menu-btn:hover {
  background: color-mix(in srgb, var(--fg) 10%, transparent);
  opacity: 1;
}

/* ── Gallery (image library) ── */

.gallery-modal-content {
  width: min(820px, 94vw);
  max-height: 92vh;
  font-size: 12px;
}
/* While the Edit tab is active the editor needs a *definite* height so
   the right-side tool panel (`.ge-right-panel { overflow-y:auto }`)
   scrolls INTERNALLY. Without this the modal only has `max-height`, so
   `.gallery-editor { height:100% }` can't resolve — the editor grows to
   its full content height, overflows the modal body, and the modal-body
   scrollbar that appears can't be wheel-scrolled because the editor's
   `overscroll-behavior:contain` blocks the chain. Pinning a real height
   here makes the panel bounded and scrollable as designed. Scoped to
   the editor view (matched via the container's inline `display:flex`)
   so the Photos/Albums views keep sizing to their content. */
.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) {
  height: 92vh;
}
/* Photo-detail view sizing (issue #314).
   The detail view is rendered as a `position:absolute; inset:0` overlay
   *inside* `.gallery-images-container`, painted over the photo grid. Because
   it's absolutely positioned it can't contribute to the container's height —
   the container (and therefore the overlay's `inset:0` box) collapses to the
   height of the grid sitting behind it. When the library only has a few
   photos that grid is short, so the detail view is crushed: the image is
   clipped and the metadata sidebar (`overflow-y:auto`) is squeezed into a
   tiny, internally-scrolling strip. (With a large library the grid is tall,
   which is why it looked fine in the demo video but cramped for users with
   few photos.)
   Fix: when the detail view is open, hide the grid-view siblings and drop the
   overlay into normal flow. The container — and the window, up to its 92vh
   max-height — then sizes to the detail's own content (image + metadata), so
   nothing is clipped or squeezed regardless of how many photos exist. Scoped
   via the detail element's inline `display:flex` so the grid / albums views
   keep sizing to their own content. Works on both desktop and the mobile
   full-screen sheet. */
#gallery-images-container:has(> #gallery-detail[style*="flex"]) > *:not(#gallery-detail) {
  display: none !important;
}
#gallery-images-container:has(> #gallery-detail[style*="flex"]) > #gallery-detail {
  position: static;
}
/* Containing block for the photo-detail overlay — keeps it inside the body
   so it sits below the modal header and the tab strip instead of covering them. */
.gallery-images-container { position: relative; }
.gallery-modal-content .modal-header h4 {
  font-size: 1rem;
}
.gallery-stats {
  font-size: 10px;
  color: color-mix(in srgb, var(--fg) 50%, transparent);
  margin-bottom: 4px;
}
.gallery-toolbar {
  display: flex;
  gap: 6px;
  margin-bottom: 4px;
  align-items: center;
}
.gallery-toolbar-break { display: none; }
/* Search input + its "↵ enter to tag" hint live in a relative wrapper that
   carries the 2px down-shift, so input and hint move together. */
.gallery-search-wrap { position: relative; flex: 1; min-width: 0; display: flex; top: 2px; }
.gallery-search {
  flex: 1;
  width: 100%;
  padding: 4px 8px;
  padding-right: 78px;   /* room for the enter hint */
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 6px;
  font: inherit;
  font-size: 12px;
  outline: none;
}
.gallery-search-enter-hint {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 10px;
  font-weight: 600;
  color: var(--accent, var(--red));
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.12s;
  white-space: nowrap;
}
/* Show the hint only once the user has typed something (placeholder gone). */
.gallery-search:not(:placeholder-shown) ~ .gallery-search-enter-hint { opacity: 0.95; }
#gallery-select-btn { position: relative; top: 2px; font-size: 12px; padding: 10px 11px 12px; }
.gallery-search:focus {
  border-color: var(--red);
}
.gallery-model-filter,
.gallery-sort {
  padding: 4px 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--fg);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
}
.gallery-tag-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-bottom: 4px;
}
.gallery-chip {
  height: 28px;
  box-sizing: border-box;
  padding: 0 13px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  border-radius: 14px;
  font-size: 12px;
  line-height: 1;
  border: 1px solid var(--border);
  background: none;
  color: var(--fg);
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}
.gallery-chip:hover {
  border-color: var(--red);
}
.gallery-chip.active {
  background: color-mix(in srgb, var(--red) 15%, transparent);
  border-color: color-mix(in srgb, var(--red) 40%, transparent);
  color: var(--red);
}
/* Album chips row */
.gallery-album-chips {
  display: flex; gap: 4px; flex-wrap: wrap; padding: 0 0 3px;
}
.gallery-chip-fav { color: var(--red); }
.gallery-chip-fav.active { background: color-mix(in srgb, var(--red) 15%, transparent); border-color: var(--red); }
.gallery-chip-add { opacity: 0.5; font-size: 14px; padding: 2px 10px; }
/* Active-album indicator — appears when the Photos grid is filtered to one album. */
.gallery-chip-active-album {
  display: inline-flex; align-items: center; gap: 4px;
  background: color-mix(in srgb, var(--red) 14%, transparent);
  border-color: color-mix(in srgb, var(--red) 45%, transparent);
  position: relative;
  top: 6px;
}
.gallery-chip-clear {
  background: none;
  border: none;
  color: inherit;
  opacity: 0.6;
  cursor: pointer;
  padding: 0;
  margin-left: 1px;
  width: 12px;
  height: 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  line-height: 1;
  position: relative;
  top: -4px;
}
.gallery-chip-clear:hover { opacity: 1; }



/* Favorite button on card */
/* Video cards: