/* 01-agent-chat - part of the former static/style.css. * * The stylesheet is split by area, but the split is mechanical: rules were * moved verbatim and NOT regrouped. The numeric prefix is the load order, and * that order is what preserves the cascade. A rule sits in the file its * original position fell into, so an area's rules are not all in its file and * a file is not only that area's rules. * * Do not reorder the tags in static/index.html, and do not move a rule * between files: with 1,151 redeclarations and 1,886 !important in this * cascade, either can change which declaration wins. * * static/index.html and the PRECACHE list in static/sw.js must agree on this * order. tests/test_static_stylesheet_manifest.py and * tests/test_css_computed_style_snapshot.py fail if that drifts. */ #cookbook-modal .modal-content { height: 94vh; max-height: 94vh; } .pdf-export-overlay .modal-content { max-height: 86vh; } /* ── 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, #starfield-depth-canvas, #ascii-fireflies-background { 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.memory-disabled.bg-pattern-synapse { background-image: none; } body.memory-disabled #synapse-canvas { display: none; } body.bg-pattern-perlin-flow, body.bg-pattern-petals, body.bg-pattern-sparkles { /* canvas-only backgrounds */ } body.bg-pattern-starfield-depth, body.bg-pattern-ascii-fireflies { /* rendered by their fixed background layer */ } #ascii-fireflies-background { position: fixed; inset: 0; z-index: 0; box-sizing: border-box; margin: 0; padding: 14px 16px; overflow: hidden; pointer-events: none; white-space: pre; color: var(--bg-effect-color, var(--fg)); font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 12px; line-height: 1; letter-spacing: 0; } /* ── 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; width: 26px; height: 26px; box-sizing: border-box; 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: 44px; right: 44px; 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; min-width: 0; max-width: calc(100% - 88px); overflow: hidden; } .chat-meta-overlay > * { pointer-events: auto; flex-shrink: 0; } .chat-meta-overlay #current-meta { display: inline-block; overflow: hidden; text-overflow: ellipsis; cursor: pointer; min-width: 0; flex: 0 1 auto; max-width: min(56vw, 520px); line-height: 1.25; } .chat-top-bar:has(.incognito-indicator:not([style*="display:none"]):not([style*="display: none"])) .chat-meta-overlay { left: 54px; right: 44px; max-width: calc(100% - 98px); } body:has(.sidebar.right-side) .chat-top-bar:has(.incognito-indicator:not([style*="display:none"]):not([style*="display: none"])) .chat-meta-overlay { left: 44px; right: 54px; } .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 > svg { color: var(--accent, var(--red)); } .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-settings-text-only .dropdown-icon { display: none; } #export-compact-btn { display: none !important; } #export-compact-btn .dropdown-icon { display: none; } .export-compact-pill { display: inline; padding: 0; border: 0; border-radius: 0; background: transparent; color: inherit; line-height: inherit; } .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-compact-btn { margin: 5px 6px 7px; min-height: 38px; padding: 9px 14px; } .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-avatar { position: relative; left: -4px; } .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; min-height: 0; flex: 0 0 auto; } /* Keep a large chat history from pushing the rest of the sidebar below the fold. The chat rows scroll independently; the sections beneath them stay reachable in the main sidebar scroll area. */ #sessions-section #session-list { max-height: min(42vh, 420px); flex: 0 0 auto; overflow-y: auto; overflow-x: hidden; scrollbar-width: thin; min-height: 0; } #tools-section { margin-top: 1px; } @media (max-width: 768px) { #sessions-section { flex: 0 0 auto !important; overflow: visible !important; } #sessions-section #session-list { max-height: min(34vh, 300px) !important; flex: 0 0 auto !important; overflow-y: auto !important; overflow-x: hidden !important; } } #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: none; 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; } body.sidebar-collapsed:not(.hamburger-only) .icon-rail, html.ody-sidebar-mini .icon-rail { display: flex; } html.ody-sidebar-mini .sidebar, html.ody-sidebar-off .sidebar { width: 0 !important; min-width: 0 !important; opacity: 0; overflow: hidden; pointer-events: none; padding: 0 !important; border: 0 !important; } html.ody-sidebar-off .icon-rail { display: none !important; } .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; } @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 round control so the two read as a paired set. 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: transparent; color: color-mix(in srgb, var(--fg) 58%, transparent); border: 1px solid transparent; 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: 999px; flex-shrink: 0; transition: background 0.15s, color 0.15s; } .modal-minimize-btn:hover { background: color-mix(in srgb, var(--fg) 10%, transparent); border-color: color-mix(in srgb, var(--fg) 12%, transparent); color: var(--fg); } .modal-minimize-btn svg { transform: translateY(2px); } .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: 100; 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%; --trash-zone-color: var(--accent, var(--red, #e53935)); background: var(--trash-zone-color); 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(--trash-zone-color) 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(var(--trash-zone-scale, 1.22)); box-shadow: 0 0 0 14px color-mix(in srgb, var(--trash-zone-color) 22%, transparent), 0 12px 36px color-mix(in srgb, var(--trash-zone-color) 60%, transparent); animation: trash-zone-breathe 1.35s ease-in-out infinite; } /* 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: color-mix(in srgb, var(--trash-zone-color) 95%, transparent); border-right-color: color-mix(in srgb, var(--trash-zone-color) 65%, transparent); border-bottom-color: color-mix(in srgb, var(--trash-zone-color) 30%, transparent); 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; } /* When a chip is swirling into the trash X, its inline `rotate(720deg)` drags every child + ::after badge along with it — the count/dot pill spinning looks chaotic. Fade those out fast at the start of the close so visually only the icon glyph rotates. */ .minimized-dock-chip.chip-trashing > :not(svg), .minimized-dock-chip.chip-trashing::after, .minimized-dock-chip.chip-trashing::before { opacity: 0 !important; transition: opacity 0.16s ease-out !important; } @keyframes whirlpool-spin { to { transform: rotate(360deg); } } @keyframes trash-zone-breathe { 0%, 100% { box-shadow: 0 0 0 10px color-mix(in srgb, var(--trash-zone-color) 16%, transparent), 0 10px 30px color-mix(in srgb, var(--trash-zone-color) 48%, transparent); } 50% { box-shadow: 0 0 0 16px color-mix(in srgb, var(--trash-zone-color) 25%, transparent), 0 14px 38px color-mix(in srgb, var(--trash-zone-color) 68%, transparent); } } @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); } /* Sleek hover labels for the mini sidebar. Use a real child span instead of ::before/::after so status dots and minimized indicators keep working. */ .rail-hover-label { position: absolute; left: 100%; top: 50%; margin-left: 6px; transform: translateY(-50%) translateX(-6px); opacity: 0; pointer-events: none; white-space: nowrap; font-size: 10px; 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); } .icon-rail-btn:hover .rail-hover-label { 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; /* 1.3 (not 1) so Fira Code's tall glyph box isn't vertically clipped in Chromium/Edge — mirrors the .list-item fix. The title is flex-centred in a fixed-height (29px) header, so this adds headroom without reflow. */ line-height: 1.3; 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; } @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; } #session-sort-btn { transform: translateX(2px); } /* Chats library — grid icon, hover-reveal so the header only toggles collapse. Uses !important to win over .list-item-plus-btn's opacity:1!important (the email-style plus button forces always-on, which we don't want here — the manage button should fade until the user actually hovers the section). */ #sessions-section .chats-manage-btn { opacity: 0.4 !important; 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.85 !important; } #sessions-section .chats-manage-btn:hover, #sessions-section .chats-manage-btn:focus-visible { opacity: 1 !important; } /* No hover background on the Chats manage button — the inherited section-header-btn rule was painting a tinted box behind the 'manage' label as it slid in. */ #sessions-section .chats-manage-btn, #sessions-section .chats-manage-btn:hover, #sessions-section .chats-manage-btn:focus-visible { background: transparent !important; } @media (hover: none) { #sessions-section .chats-manage-btn { opacity: 0.35 !important; } #sessions-section .chats-manage-btn:active { opacity: 1 !important; } } /* Pull the 'manage' label out of email's absolute-positioning trick so it sits in the flex flow and expands the button's clickable area as it slides in. Otherwise hovering the visible 'manage' text would actually be outside the button's bounding box. */ #sessions-section .chats-manage-btn .list-item-plus-label { position: static; transform: none; max-width: 0; overflow: hidden; padding-right: 0; transition: max-width 0.2s ease, opacity 0.18s ease, padding-right 0.18s ease; } #sessions-section .chats-manage-btn:hover .list-item-plus-label, #sessions-section .chats-manage-btn:focus-visible .list-item-plus-label, #sessions-section .section-header-flex:hover .chats-manage-btn .list-item-plus-label { max-width: 80px; padding-right: 4px; } /* 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.4; 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.4; } .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, .date-section-header) { 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, .date-section-header):nth-child(1) { animation-delay: 0.04s; } .section.section-just-expanded :is(.list-item, .models-row, .date-section-header):nth-child(2) { animation-delay: 0.08s; } .section.section-just-expanded :is(.list-item, .models-row, .date-section-header):nth-child(3) { animation-delay: 0.12s; } .section.section-just-expanded :is(.list-item, .models-row, .date-section-header):nth-child(4) { animation-delay: 0.16s; } .section.section-just-expanded :is(.list-item, .models-row, .date-section-header):nth-child(5) { animation-delay: 0.20s; } .section.section-just-expanded :is(.list-item, .models-row, .date-section-header):nth-child(6) { animation-delay: 0.24s; } .section.section-just-expanded :is(.list-item, .models-row, .date-section-header):nth-child(7) { animation-delay: 0.28s; } .section.section-just-expanded :is(.list-item, .models-row, .date-section-header):nth-child(8) { animation-delay: 0.32s; } .section.section-just-expanded :is(.list-item, .models-row, .date-section-header):nth-child(9) { animation-delay: 0.36s; } .section.section-just-expanded :is(.list-item, .models-row, .date-section-header):nth-child(10) { animation-delay: 0.40s; } .section.section-just-expanded :is(.list-item, .models-row, .date-section-header):nth-child(11) { animation-delay: 0.44s; } .section.section-just-expanded :is(.list-item, .models-row, .date-section-header):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, .date-section-header) { animation: section-domino-out 0.22s ease-in forwards; } .section.section-just-collapsing :is(.list-item, .models-row, .date-section-header):nth-last-child(1) { animation-delay: 0.00s; } .section.section-just-collapsing :is(.list-item, .models-row, .date-section-header):nth-last-child(2) { animation-delay: 0.025s; } .section.section-just-collapsing :is(.list-item, .models-row, .date-section-header):nth-last-child(3) { animation-delay: 0.05s; } .section.section-just-collapsing :is(.list-item, .models-row, .date-section-header):nth-last-child(4) { animation-delay: 0.075s; } .section.section-just-collapsing :is(.list-item, .models-row, .date-section-header):nth-last-child(5) { animation-delay: 0.10s; } .section.section-just-collapsing :is(.list-item, .models-row, .date-section-header):nth-last-child(6) { animation-delay: 0.125s; } .section.section-just-collapsing :is(.list-item, .models-row, .date-section-header):nth-last-child(7) { animation-delay: 0.15s; } .section.section-just-collapsing :is(.list-item, .models-row, .date-section-header):nth-last-child(8) { animation-delay: 0.175s; } .section.section-just-collapsing :is(.list-item, .models-row, .date-section-header):nth-last-child(9) { animation-delay: 0.20s; } .section.section-just-collapsing :is(.list-item, .models-row, .date-section-header):nth-last-child(10) { animation-delay: 0.225s; } .section.section-just-collapsing :is(.list-item, .models-row, .date-section-header):nth-last-child(11) { animation-delay: 0.25s; } .section.section-just-collapsing :is(.list-item, .models-row, .date-section-header):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); } } .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); } #models.models-refreshing { opacity: 0.72; transition: opacity 0.16s ease; } /* 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: none; } .session-star.provider-logo.processing svg { animation: none; } .session-star.notify { animation: none; opacity: 1; /* Completed runs keep the provider slot but turn it into a compact success check, so the logo does not simply appear to vanish. */ width: 14px; height: 14px; background: transparent; border-color: transparent; color: var(--accent, var(--red)); display: inline-flex; align-items: center; justify-content: center; } /* 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; } .session-star.notify::after { content: ''; width: 14px; height: 14px; background: currentColor; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpolyline points='20 6 9 17 4 12' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat; } .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; } .session-item.dragging-to-folder { opacity: 0.55; border-color: var(--red); box-shadow: 0 4px 14px color-mix(in srgb, var(--red) 22%, transparent); } .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; transition: margin-left 160ms cubic-bezier(0.22, 0.61, 0.36, 1), margin-right 160ms cubic-bezier(0.22, 0.61, 0.36, 1); } .chat-meta { font-size:12px; color:color-mix(in srgb, var(--fg) 60%, transparent); margin-bottom:6px; } .chat-history { display:flex; flex-direction:column; flex:1; overflow-y:auto; overflow-x:hidden; overscroll-behavior-y: none; margin-bottom:8px; white-space:normal; min-height:0; /* Streaming completion replaces the live turn with its persisted DOM. We preserve the scroll position explicitly; native anchoring would otherwise fight that replacement and create a visible bounce. */ overflow-anchor: none; --chat-max: 800px; padding-left: max(0px, calc((100% - var(--chat-max)) / 2)); padding-right: max(12px, calc((100% - var(--chat-max)) / 2 + 12px)); } .chat-history > * { flex: 0 0 auto; } .chat-pull-refresh { --pull-refresh-y: 0px; --pull-refresh-progress: 0; position: absolute; top: 8px; left: 50%; z-index: 48; width: 34px; height: 34px; display: none; align-items: center; justify-content: center; border: 1px solid color-mix(in srgb, var(--border) 75%, transparent); border-radius: 999px; background: color-mix(in srgb, var(--panel) 94%, transparent); box-shadow: 0 8px 20px color-mix(in srgb, #000 16%, transparent); opacity: calc(0.25 + (var(--pull-refresh-progress) * 0.75)); pointer-events: none; transform: translate(-50%, calc(-42px + var(--pull-refresh-y))) scale(calc(0.82 + (var(--pull-refresh-progress) * 0.18))); transition: opacity 0.16s ease, transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1), border-color 0.16s ease; } .chat-pull-refresh.is-visible, .chat-pull-refresh.is-refreshing { display: inline-flex; } .chat-pull-refresh.is-ready, .chat-pull-refresh.is-refreshing { border-color: color-mix(in srgb, var(--accent, var(--red)) 58%, transparent); color: var(--accent, var(--red)); } .chat-pull-refresh .spinner-whirlpool { margin: 0 !important; width: 18px !important; height: 18px !important; } .chat-pull-refresh canvas { display: block; } #email-lib-modal .email-pull-refresh { top: 112px; z-index: 4; } /* 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; } /* The tip is a full sentence that wraps to 4-5 lines on narrow phones, where the welcome block shrink-wraps small; the shared 60px ceiling (sized for the one-line sub/version) clipped its last line. Give the tip a taller ceiling so it isn't truncated. Kept above the max-height:650px block below so that rule's max-height:0 still collapses it on short viewports. */ #welcome-screen .welcome-tip { max-height: 120px; } @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%); transition: opacity 0.16s ease; } @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; } #welcome-screen .welcome-tip.nobody-session-note { display:flex; flex-direction:column; gap:2px; } /* 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; max-width: min(160px, 70vw); } .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; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* 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) { .chat-meta-overlay { left: 36px; right: 36px; } #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; position: relative; z-index: 20; } .chat-container.welcome-active:has(.chat-input-bar:focus-within) #welcome-screen, .chat-container.welcome-active:has(textarea#message:not(:placeholder-shown)) #welcome-screen { opacity: 0; pointer-events: none; transform: translate(-50%, -50%); transition: opacity 0.16s ease; } .chat-container.welcome-active:has(.input-icon-btn.expanded) #welcome-screen, .chat-container.welcome-active:has(.send-btn.newchat-expanded) #welcome-screen, .chat-container.welcome-active:has(#model-picker-wrap:not(.picker-auto-hidden) .model-picker-menu:not(.hidden)) #welcome-screen { opacity: 0.45; transform: translate(-50%, -76%) scale(0.94); } .chat-container.welcome-active:has(.input-icon-btn.expanded) #welcome-screen .welcome-tip, .chat-container.welcome-active:has(.send-btn.newchat-expanded) #welcome-screen .welcome-tip, .chat-container.welcome-active:has(#model-picker-wrap:not(.picker-auto-hidden) .model-picker-menu:not(.hidden)) #welcome-screen .welcome-tip { opacity: 0.14; } .chat-container.welcome-active textarea#message { max-height: min(34vh, 150px); } } @media (max-width: 420px) { .chat-meta-overlay { left: 34px; right: 34px; } .chat-meta-overlay #session-cost-display, .chat-meta-overlay .chat-meta-count { display: none !important; } #welcome-screen { width: calc(100vw - 36px); } #welcome-screen .welcome-name, #welcome-screen .welcome-sub, #welcome-screen .welcome-tip { max-width: 100%; } .incognito-btn { padding: 6px 9px; max-width: 120px; } } @media (max-width: 340px) { .incognito-btn .incognito-label { display: none; } .incognito-btn { width: 30px; height: 30px; padding: 0; border-radius: 999px; } } .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-user:has(textarea) { width: 85%; } .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); } .user-mode-pill { display: inline-flex; align-items: center; justify-content: center; height: 15px; min-width: 15px; padding: 0 3px; border-radius: 4px; border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); background: color-mix(in srgb, var(--fg) 6%, transparent); color: color-mix(in srgb, var(--fg) 58%, transparent); font-size: 10px; font-weight: 600; line-height: 15px; text-transform: lowercase; } .user-mode-pill svg { display: block; width: 11px; height: 11px; } .user-mode-pill[data-auto="true"]::after { content: 'auto'; margin-left: 4px; opacity: 0.65; font-weight: 500; } .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); } .chat-queued-bubble-host { order: 2147483647; display: flex; flex-direction: column; align-items: stretch; flex: 0 0 auto; width: 100%; min-width: 0; } .msg-user.msg-user-queued { opacity: 0.62; cursor: pointer; animation: none; border: 1px dashed color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); background: color-mix(in srgb, var(--accent, var(--red)) 8%, var(--input-bg, var(--panel))); transition: opacity 0.12s ease, border-color 0.12s ease, transform 0.12s ease; } .msg-user.msg-user-queued:hover { opacity: 0.9; border-color: color-mix(in srgb, var(--accent, var(--red)) 85%, transparent); transform: translateY(-1px); } .msg-user.msg-user-queued .queued-pill { display: inline-flex; align-items: center; gap: 3px; height: 14px; padding: 0 5px; margin-left: 5px; border-radius: 999px; border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); color: var(--accent, var(--red)); background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); font-size: 8px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; vertical-align: 1px; } .msg-user.msg-user-queued .queued-pill svg { flex: 0 0 auto; opacity: 0.9; } .msg-ai .body { color: var(--fg); } .model-endpoint-add-btn { display: inline-flex; align-items: center; gap: 4px; margin-left: 7px; padding: 2px 7px; border: 1px solid color-mix(in srgb, var(--red) 34%, var(--border)); border-radius: 999px; background: color-mix(in srgb, var(--red) 8%, transparent); color: var(--red); font: inherit; font-size: 0.78em; line-height: 1.45; cursor: pointer; vertical-align: 1px; } .model-endpoint-add-btn:hover { background: color-mix(in srgb, var(--red) 14%, transparent); border-color: color-mix(in srgb, var(--red) 55%, var(--border)); } .model-endpoint-add-btn:disabled { cursor: default; opacity: 0.72; } .model-endpoint-add-btn.added { color: var(--color-save-green, #4caf50); border-color: color-mix(in srgb, var(--color-save-green, #4caf50) 45%, var(--border)); background: color-mix(in srgb, var(--color-save-green, #4caf50) 9%, transparent); } .task-completed-marker { display: inline-flex; align-items: center; gap: 7px; margin: 7px 0 2px; padding: 5px 9px; border: 1px solid color-mix(in srgb, var(--color-save-green, #4caf50) 42%, var(--border)); border-radius: 999px; background: color-mix(in srgb, var(--color-save-green, #4caf50) 9%, transparent); color: var(--color-save-green, #4caf50); font-size: 0.86em; font-weight: 600; } .task-completed-icon { display: inline-flex; align-items: center; justify-content: center; width: 17px; height: 17px; border-radius: 50%; background: color-mix(in srgb, var(--color-save-green, #4caf50) 18%, transparent); flex: 0 0 auto; } .rag-sources { margin-top: 12px; border: 1px solid var(--border); border-radius: 6px; padding: 8px; font-size: 11px; } .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%; transition: border-color 0.16s ease, background 0.16s ease; } body.plan-mode-active .chat-input-bar { border-style: dashed; border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); background: color-mix(in srgb, var(--accent, var(--red)) 8%, var(--input-bg, var(--panel))); } .chat-input-top { width: 100%; position: relative; display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; } .plan-mode-status { position: absolute; right: 0; bottom: 2px; z-index: 245; display: inline-flex; align-items: center; gap: 7px; height: 22px; padding: 0 6px 0 8px; border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); border-radius: 999px; background: color-mix(in srgb, var(--accent, var(--red)) 12%, var(--input-bg, var(--panel))); color: var(--accent, var(--red)); font-size: 10px; font-weight: 700; line-height: 1; transition: opacity 0.22s ease, transform 0.22s ease; } .plan-mode-status[hidden] { display: none !important; } .plan-mode-status.plan-mode-status-autohide { opacity: 0; pointer-events: none; transform: translateY(-4px) scale(0.96); } .plan-mode-status-toggle { width: 26px; height: 14px; padding: 0; border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); border-radius: 999px; background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); cursor: pointer; position: relative; flex-shrink: 0; } .plan-mode-status-toggle span { position: absolute; top: 2px; right: 2px; width: 8px; height: 8px; border-radius: 50%; background: var(--accent, var(--red)); } .plan-mode-status-toggle:hover { background: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); } .chat-input-top > .model-picker-wrap { position: relative; grid-column: 2; grid-row: 1; top: 0; right: auto; z-index: 250; transform-origin: top right; transition: opacity 0.22s ease, transform 0.22s ease; will-change: opacity, transform; } body.plan-mode-active .chat-input-top > .model-picker-wrap { opacity: 0; pointer-events: none; transform: translateY(-4px) scale(0.96); } .chat-input-top > .model-picker-wrap.model-picker-autohide, .chat-input-top > .model-picker-wrap.picker-auto-hidden { display: none !important; 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 { grid-column: 1; grid-row: 1; min-width: 0; width: 100%; background: transparent; border: none; outline: none; resize: none; font-size: 14px; line-height: 1.5; color: var(--fg); min-height: 24px; max-height: 200px; 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; } } .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; } /* Reasoning effort dropdown in Chat Context */ .reasoning-effort-wrap { position: relative; display: inline-flex; align-items: center; } .reasoning-effort-btn { display: inline-flex; align-items: center; gap: 4px; height: 28px; padding: 0 8px; border-radius: 6px; border: 1px solid var(--border); background: color-mix(in srgb, var(--fg) 4%, transparent); color: var(--fg); font-size: 11.5px; font-weight: 500; cursor: pointer; user-select: none; transition: background 0.15s, border-color 0.15s; } .reasoning-effort-btn:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); border-color: color-mix(in srgb, var(--fg) 20%, transparent); } .reasoning-effort-chevron { opacity: 0.5; transition: transform 0.15s; flex-shrink: 0; } .reasoning-effort-btn[aria-expanded="true"] .reasoning-effort-chevron { transform: rotate(180deg); } .reasoning-effort-menu { position: absolute; bottom: calc(100% + 6px); right: 0; min-width: 140px; background: var(--bg-surface, var(--bg)); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35); padding: 4px; z-index: 1000; display: flex; flex-direction: column; gap: 2px; } .reasoning-effort-menu.hidden { display: none !important; } .reasoning-effort-option { display: flex; align-items: center; justify-content: space-between; padding: 6px 10px; border-radius: 5px; font-size: 12px; color: var(--fg); cursor: pointer; background: transparent; border: none; text-align: left; width: 100%; transition: background 0.12s; } .reasoning-effort-option:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); } .reasoning-effort-option.active { color: var(--accent); font-weight: 600; } .chat-context-popup .reasoning-effort-wrap { width: 100%; margin-top: 8px; } .chat-context-popup .reasoning-effort-btn { width: 100%; font: inherit; } .chat-context-popup .reasoning-effort-prefix { flex: 1; text-align: left; } .chat-context-popup .reasoning-effort-menu { top: calc(100% + 4px); bottom: auto; max-height: 180px; overflow-y: auto; } .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); } .plan-mode-btn { width: 28px; height: 28px; padding: 0; flex-shrink: 0; top: -2px; } .plan-mode-btn.active { color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); } /* GitHub-style task lists (- [ ] / - [x]) */ li.task-item { list-style: none; margin-left: -1.2em; display: flex; align-items: flex-start; gap: 8px; } li.task-item .task-check { flex: 0 0 auto; width: 15px; height: 15px; margin-top: 3px; border-radius: 4px; border: 1.5px solid color-mix(in srgb, var(--fg) 45%, transparent); box-sizing: border-box; position: relative; } li.task-item.task-done .task-check { background: var(--accent); border-color: var(--accent); } li.task-item.task-done .task-check::after { content: ''; position: absolute; left: 4px; top: 1px; width: 4px; height: 8px; border: solid var(--bg); border-width: 0 2px 2px 0; transform: rotate(45deg); } li.task-item.task-done .task-text { opacity: 0.6; text-decoration: line-through; } .plan-inline-actions { display: flex; align-items: center; gap: 7px; margin-top: 10px; } .plan-inline-execute, .plan-inline-clear { height: 26px; border-radius: 7px; border: 1px solid color-mix(in srgb, var(--fg) 13%, transparent); background: transparent; color: var(--fg); font: inherit; font-size: 11px; padding: 0 9px; display: inline-flex; align-items: center; gap: 5px; cursor: pointer; } .plan-inline-execute { background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); border-color: color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); color: var(--accent, var(--red)); font-weight: 650; } .plan-review-panel { position: fixed; right: 24px; bottom: 92px; z-index: 500; width: min(520px, calc(100vw - 32px)); max-height: min(560px, calc(100vh - 120px)); display: flex; flex-direction: column; overflow: hidden; border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, var(--border)); border-radius: 8px; background: var(--panel); color: var(--fg); box-shadow: 0 14px 40px color-mix(in srgb, #000 32%, transparent); } .plan-review-panel[hidden] { display: none !important; } .plan-review-panel-header, .plan-review-actions { display: flex; align-items: center; gap: 8px; padding: 10px 12px; } .plan-review-panel-header { justify-content: space-between; border-bottom: 1px solid var(--border); } .plan-review-panel-header h3 { margin: 0; font-size: 13px; line-height: 1.2; } .plan-review-close { width: 24px; height: 24px; padding: 0; border: 0; background: transparent; color: var(--fg); font-size: 19px; line-height: 1; cursor: pointer; opacity: 0.65; } .plan-review-close:hover { opacity: 1; } .plan-review-content { margin: 0; padding: 12px; overflow: auto; white-space: pre-wrap; word-break: break-word; font-size: 12px; line-height: 1.55; font-family: inherit; } .plan-review-actions { border-top: 1px solid var(--border); } .plan-review-execute, .plan-review-clear { height: 28px; padding: 0 9px; border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); border-radius: 6px; background: transparent; color: var(--fg); font: inherit; font-size: 11px; cursor: pointer; display: inline-flex; align-items: center; gap: 5px; } .plan-review-execute { border-color: color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); color: var(--accent, var(--red)); font-weight: 650; } /* 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); } #workspace-indicator-btn.active { opacity: 1; color: var(--fg); background: color-mix(in srgb, var(--fg) 9%, transparent); } #workspace-indicator-btn.active:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 9%, transparent); } #workspace-indicator-btn.active > svg:not(.tool-indicator-x), #workspace-indicator-btn.active > span { opacity: 1; } #workspace-indicator-btn .workspace-indicator-icon { opacity: 1 !important; color: currentColor !important; stroke: currentColor !important; flex-shrink: 0; } #workspace-indicator-btn > svg:first-of-type { opacity: 1 !important; color: currentColor !important; stroke: currentColor !important; } /* 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; } .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; } } /* 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, .mode-toggle.mode-right::before { transform: translateX(100%); } .mode-toggle.mode-toggle-three::before { width: 33.3333%; } .mode-toggle.mode-toggle-three.mode-mid::before { transform: translateX(100%); } .mode-toggle.mode-toggle-three.mode-third::before { transform: translateX(200%); } #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; } @media (max-width: 768px) { .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span { font-size: 0; } .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span::after { content: 'Unif'; font-size: 11px; } } /* 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-left: 7px; margin-right: 5px; } .chat-context-pill { display: inline-flex; align-items: center; gap: 3px; height: auto; padding: 0; margin-left: 7px; border: 0; border-radius: 0; background: transparent; color: color-mix(in srgb, var(--fg) 35%, transparent); font-family: inherit; font-size: 0.7rem; font-weight: 400; line-height: 1; cursor: pointer; white-space: nowrap; position: relative; z-index: 2; --ctx-stroke: var(--color-muted, #888); transition: color 0.15s, opacity 0.15s; } .chat-context-pill[hidden] { display: none !important; } .chat-context-pill:hover, .chat-context-pill.open { color: color-mix(in srgb, var(--fg) 70%, transparent); --ctx-stroke: var(--ctx-color); border: 0; background: transparent; } .chat-context-pill.loading { opacity: 0.55; } .chat-context-pill.warn { color: var(--orange, #f59e0b); background: transparent; } .chat-context-pill.danger { color: var(--red, #ef4444); background: transparent; } .chat-context-pill svg { flex-shrink: 0; } .chat-context-pill .ctx-ring-pct { color: currentColor; font-size: inherit; font-weight: inherit; line-height: 1; } .chat-context-dot { display: none; } .chat-context-popup { position: fixed; z-index: 10060; width: 238px; padding: 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--panel); color: var(--fg); box-shadow: 0 12px 34px color-mix(in srgb, #000 26%, transparent); font-size: 11px; line-height: 1.35; } .chat-context-popup.warn { color: var(--orange, #f59e0b); } .chat-context-popup.danger { color: var(--red, #ef4444); } .chat-context-popup-title { font-weight: 650; margin-bottom: 8px; color: var(--accent, var(--red)); } .chat-context-popup-bar { height: 6px; overflow: hidden; border-radius: 999px; background: color-mix(in srgb, var(--fg) 10%, transparent); margin-bottom: 6px; } .chat-context-popup-fill { height: 100%; width: 0; border-radius: inherit; background: var(--red, var(--fg)); } .chat-context-popup-row { display: flex; justify-content: space-between; gap: 10px; margin-top: 4px; color: color-mix(in srgb, var(--fg) 70%, transparent); } .chat-context-popup-row span:last-child { color: var(--fg); text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 148px; } .chat-context-toggle-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 8px; padding-top: 8px; border-top: 1px solid color-mix(in srgb, var(--border) 45%, transparent); } .chat-context-toggle-copy { display: flex; flex-direction: column; gap: 1px; min-width: 0; color: color-mix(in srgb, var(--fg) 76%, transparent); } .chat-context-toggle-copy > span:first-child { white-space: nowrap; color: var(--accent, var(--red)); } .chat-context-toggle-state { font-size: 10px; color: color-mix(in srgb, var(--fg) 45%, transparent); } .chat-context-toggle { position: relative; flex: 0 0 auto; width: 32px; height: 18px; border: 1px solid color-mix(in srgb, var(--border) 75%, transparent); border-radius: 999px; background: color-mix(in srgb, var(--fg) 8%, transparent); cursor: pointer; transition: background 0.15s, border-color 0.15s, opacity 0.15s; } .chat-context-toggle::after { content: ""; position: absolute; top: 3px; left: 3px; width: 10px; height: 10px; border-radius: 50%; background: color-mix(in srgb, var(--fg) 62%, transparent); transition: transform 0.15s, background 0.15s; } .chat-context-toggle.active { border-color: color-mix(in srgb, var(--accent) 55%, transparent); background: color-mix(in srgb, var(--accent) 18%, transparent); } .chat-context-toggle.active::after { transform: translateX(14px); background: var(--fg); box-shadow: 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); } .chat-context-toggle:disabled { opacity: 0.6; cursor: default; } .chat-context-threshold-row { margin-top: 8px; } .chat-context-threshold-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 5px; color: color-mix(in srgb, var(--fg) 70%, transparent); } .chat-context-threshold-top span:last-child { color: var(--fg); font-variant-numeric: tabular-nums; } .chat-context-threshold-slider { width: 100%; margin: 0; } .chat-context-compact-btn { display: inline-flex; align-items: center; justify-content: center; gap: 1px; width: 100%; height: 26px; margin-top: 9px; border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent); border-radius: 6px; background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); cursor: pointer; font: inherit; font-size: 11px; font-weight: 600; } .chat-context-compact-btn svg { display: block; flex: 0 0 auto; margin-left: -2px; } .chat-context-compact-btn svg circle:last-child { stroke: var(--accent, var(--red)); transition: stroke-dasharray 0.22s ease; } .chat-context-compact-btn:hover svg circle:last-child { stroke-dasharray: 35 0; } .chat-context-compact-btn:hover { background: color-mix(in srgb, var(--accent) 18%, transparent); } .chat-context-popup, .chat-context-popup * { font-size: 10px !important; } /* 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: 250; 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-search-wrap { position: relative; min-width: 0; } .model-picker-menu input[type="text"] { width: 100%; box-sizing: border-box; padding: 6px 30px 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-refresh-btn { appearance: none; position: absolute; top: 50%; right: 4px; transform: translateY(-50%); width: 22px; height: 22px; border: 0; border-radius: 4px; background: transparent; color: color-mix(in srgb, var(--fg) 54%, transparent); display: inline-flex; align-items: center; justify-content: center; padding: 0; cursor: pointer; } .model-picker-refresh-btn:hover, .model-picker-refresh-btn:focus-visible { color: var(--fg); background: color-mix(in srgb, var(--fg) 8%, transparent); outline: none; } .model-picker-refresh-btn:disabled { opacity: 0.7; cursor: default; } .model-picker-refresh-btn.spinning svg { animation: model-picker-refresh-spin 0.75s linear infinite; } .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-loading { max-height: min(120px, 28dvh); overflow: hidden; } .model-picker-list.is-empty { max-height: 0; overflow: hidden; } .model-picker-list .model-picker-loading-row { justify-content: center; gap: 7px; min-height: 38px; padding: 8px 10px; color: color-mix(in srgb, var(--fg) 58%, transparent); font-size: 0.82em; } .model-picker-list .model-picker-loading-row .ai-spinner-whirlpool { position: relative; top: 1px; } .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 bookmark — 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 svg { display: block; } .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: 4px; 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.08s ease-out; } @keyframes overflow-menu-pop { 0% { transform: scale(0.96) translateY(2px); 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.08s ease-in forwards; animation-delay: 0s; } @keyframes overflow-menu-pop-out { 0% { transform: scale(1) translateY(0); opacity: 1; } 100% { transform: scale(0.96) translateY(2px); 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.08s ease-out backwards; animation-delay: 0s !important; } .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.06s ease-in forwards; animation-delay: 0s !important; } .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-enter { animation: msg-footer-enter 180ms ease-out both; } @keyframes msg-footer-enter { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: translateY(0); } } @media (prefers-reduced-motion: reduce) { .msg-footer-enter { animation: none; } } .msg-footer .response-metrics { font-size:0.75rem; color:var(--color-muted-alt); transition: color .15s; } .msg-footer .response-metrics:hover { color:var(--fg); } .agent-thread > .agent-thread-footer { margin-top: 4px; margin-left: 2px; } /* Keep the context ring and percentage together at the footer's left edge. */ .ctx-ring { display: inline-flex; align-items: center; gap: 3px; margin-left: 0; 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); } .footer-open-gallery-btn { gap: 4px; font-size: 11px; } /* 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 .resend-message-icon { display:block; width:13px; height:13px; transform:translateY(1px); } .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; } .overflow-icon .resend-message-icon { display:block; width:13px; height:13px; margin:0 auto; } .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; } .continue-btn.resume-btn { display:inline-flex; align-items:center; justify-content:space-between; gap:8px; padding:3px 8px 3px 5px; border:none; border-radius:5px; font-size:0.85em; font-weight:400; line-height:1.2; position:relative; top:2px; } .continue-btn.resume-btn:hover { border:none; } .agent-finish-editor { margin: 4px 0 6px 16px; font-size: 0.8em; } .agent-finish-editor:disabled { cursor: default; opacity: 0.5; } .continue-btn.resume-btn .resume-btn-icon { display:block; width:16px; height:16px; flex:0 0 16px; margin-left:auto; } /* Round-cap "Continue" affordance — a cohesive centered pill at the chat bottom (not the bare red in-message stopped style). */ .rounds-exhausted { justify-content:center; gap:12px; width:fit-content; max-width:90%; margin:14px auto 4px; padding:7px 8px 7px 16px; border:1px solid var(--border); border-radius:999px; background:color-mix(in srgb, var(--fg) 4%, transparent); opacity:1; } .rounds-exhausted .rounds-exhausted-label { color:color-mix(in srgb, var(--fg) 60%, transparent); font-size:0.95em; } .rounds-exhausted .continue-btn { font-size:0.9em; font-weight:600; opacity:1; color:var(--bg); background:var(--accent, var(--red)); border-radius:999px; padding:4px 14px; line-height:1.3; } .rounds-exhausted .continue-btn:hover { opacity:0.88; } .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:9px 12px 10px; font-size:0.78rem; color:var(--fg); box-shadow:0 8px 24px rgba(0,0,0,0.4); min-width:280px; max-width:min(360px, calc(100vw - 16px)); line-height:1.45; } .ctx-label { display:block; width:auto; color:var(--color-muted-alt); font-size:0.75rem; } .ctx-popup-title { display:flex; align-items:center; gap:6px; margin-bottom:8px; padding-bottom:7px; border-bottom:1px solid color-mix(in srgb, var(--border) 65%, transparent); font-weight:650; letter-spacing:.01em; } .ctx-popup-title::before { content:''; width:6px; height:6px; flex:0 0 6px; border-radius:50%; background:var(--accent, var(--red)); box-shadow:0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); } .ctx-stat-section { display:flex; flex-direction:column; gap:2px; } .ctx-stat-section + .ctx-stat-section { margin-top:6px; padding-top:6px; border-top:1px solid color-mix(in srgb, var(--border) 45%, transparent); } .ctx-stat-row { display:grid; grid-template-columns:minmax(82px, 1fr) minmax(0, 2fr); align-items:baseline; gap:10px; min-height:16px; } .ctx-stat-row .ctx-label { white-space:nowrap; } .ctx-stat-value { color:var(--fg); text-align:right; font-variant-numeric:tabular-nums; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .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; display:inline-flex; align-items:center; justify-content:center; gap:5px; } .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; } /* Chat-native visual explanations render in an isolated preview. */ .chat-svg-visual { width: 100%; max-width: 760px; margin: 12px 0 16px; } .chat-svg-preview { display: block; width: 100%; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); } .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:9px 9px 9px 10px; border-radius:8px; 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); /* Toasts must stay above the chat-context settings popup so a setting change is visible while that popup remains open. */ z-index: 10070; pointer-events: none; box-shadow: 0 4px 12px rgba(0,0,0,0.2); backdrop-filter: blur(12px); max-width: min(420px, calc(100vw - 32px)); min-width: min(240px, calc(100vw - 32px)); min-height: 40px; display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: start; column-gap: 8px; box-sizing: border-box; } .toast.show { opacity:1; transform: translateX(0); } .toast:not(:has(.toast-checkmark, .toast-whirlpool, .toast-error-icon)) { grid-template-columns: minmax(0, 1fr) auto; } .toast-message { min-width: 0; padding: 3px 0 2px; position: relative; top: 0; line-height: 1.35; overflow-wrap: anywhere; } .toast.toast-ai-reply-progress .toast-message { top: 2px; } .toast.toast-ai-reply-result .toast-message { top: 0; } .toast.toast-sending .toast-message { top: 2px; } .toast.toast-message-sent .toast-message { top: 4px; } .toast-actions { display: inline-flex; align-items: center; align-self: start; gap: 3px; margin-left: 2px; } .toast-action-btn { min-height: 26px; padding: 3px 7px 3px 9px; border: 1px solid color-mix(in srgb, var(--fg) 34%, transparent); border-radius: 6px; background: color-mix(in srgb, var(--fg) 4%, transparent); color: var(--fg); cursor: pointer; font: inherit; font-weight: 600; line-height: 1; pointer-events: auto; display: inline-flex; align-items: center; gap: 5px; transition: border-color 0.15s ease, background 0.15s ease; } .toast-action-btn:hover { border-color: color-mix(in srgb, var(--accent) 65%, var(--border)); background: color-mix(in srgb, var(--accent) 10%, transparent); } .toast-action-icon { display: inline-flex; align-items: center; } .toast-action-hint { padding: 2px 4px; border: 0; border-radius: 3px; background: color-mix(in srgb, var(--fg) 9%, transparent); color: inherit; font: 500 8px/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; letter-spacing: 0.2px; opacity: 0.62; text-transform: uppercase; pointer-events: none; } .toast .toast-checkmark { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; margin-top: 5px; margin-right: 0; position: relative; top: 4px; color: var(--accent, var(--red)); 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; } .toast .toast-whirlpool { position: relative; top: 4px; } @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-close-btn { margin-left: 0; padding: 0; width: 22px; height: 22px; line-height: 1; border: none; background: none; color: var(--fg); opacity: 0.5; cursor: pointer; font-size: 16px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; pointer-events: auto; flex-shrink: 0; transition: transform 0.22s ease, opacity 0.15s ease, background 0.15s ease; } .toast-error-icon { display: inline-flex; align-items: center; justify-content: center; width: 17px; height: 17px; margin-top: 4px; border: 1px solid currentColor; border-radius: 50%; font-size: 10px; font-weight: 800; line-height: 1; } .toast-close-btn:hover { opacity: 1; transform: rotate(90deg); background: color-mix(in srgb, var(--fg) 8%, transparent); } .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 toast enters from the panel side. ui.js supplies the exact right edge and available width. */ body:has(.notes-pane.modal-right-docked) .toast { 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%); } /* Notes-only panel-aware placement. Documents and Email retain the normal top-right toast position. */ .toast.toast-panel-offset { transform: translateX(120%); } .toast.toast-panel-offset.show { transform: translateX(0); } .toast.toast-panel-offset.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; } .toast-action-hint { display: none; } } .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: 400; 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; /* Force opaque background — Firefox mobile renders the backdrop-filter+background combo as semi-transparent on the slide-out drawer, letting the chat input model picker label show through the sidebar. */ /* Keep the drawer tied to the Sidebar color picker. This used to force --panel on mobile, leaving only the header responsive to --sidebar-bg. */ background: var(--sidebar-bg, var(--panel)) !important; backdrop-filter: none !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); } html.ody-sidebar-off .sidebar, html.ody-mobile-startup-sidebar-hidden .sidebar { transform: translateX(-100%) !important; pointer-events: none !important; opacity: 0 !important; } html.ody-sidebar-off .sidebar.right-side, html.ody-mobile-startup-sidebar-hidden .sidebar.right-side { transform: translateX(100%) !important; } html.ody-sidebar-off .icon-rail, html.ody-mobile-startup-sidebar-hidden .icon-rail { display: none !important; } /* Backdrop behind sidebar */ #sidebar-backdrop { position: fixed; inset: 0; z-index: 390; 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; left: 50% !important; right: auto !important; top: 50%; transform: translate(-50%, -50%); position: absolute; width: min(calc(100vw - 104px), 320px); max-width: calc(100vw - 104px); overflow: hidden; justify-content: center; } .chat-meta-overlay #current-meta { max-width: 100%; } .chat-meta-overlay .export-dl-btn { display: inline-flex; } #export-compact-btn { position: relative; left: -2px; } /* Incognito — smaller on mobile */ .incognito-btn { padding: 4px 10px; font-size: 10px; } /* Keep the active Nobody indicator inside the chat header on mobile. Pinning it next to the hamburger made it collide with the title/splash on tight screens and detached it from the chat state it represents. */ .incognito-indicator { display: none !important; } /* 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;
/* The preview lives inside an already bounded expanded card. Giving
it a viewport-sized minimum pushes its footer below that card,
where overflow:hidden clips it. The text child owns scrolling. */
min-height: 0 !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 0 !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;
}
#email-lib-modal .doclib-card.doclib-card-expanded .doclib-card-preview .email-reader-body {
margin-left: -6px !important;
margin-right: -6px !important;
padding-left: 12px !important;
padding-right: 12px !important;
background: var(--panel) !important;
box-shadow: inset 0 1px 0 color-mix(in srgb, var(--border) 65%, transparent) !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;
/* Top-align meta + actions on mobile. With the two-row action
cluster (Summary/More above Reply/Forward/AI), align-items:
center pulled the meta's From:/To: rows down into the visual
middle of the header, leaving them visibly off-axis. */
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;
flex-shrink: 0 !important;
margin-left: auto !important;
order: 0 !important;
}
#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;
}
/* Meta on mobile: From / To / Cc each on their own row, label
tight to chips, chips scroll horizontally per row. */
#email-lib-modal .email-reader-meta,
.email-reader-tab-modal .email-reader-meta,
.email-window-modal .email-reader-meta {
display: flex !important;
flex-direction: column !important;
gap: 2px !important;
min-width: 0 !important;
width: 100% !important;
}
#email-lib-modal .email-reader-meta-row,
.email-reader-tab-modal .email-reader-meta-row,
.email-window-modal .email-reader-meta-row {
display: flex !important;
grid-template-columns: none !important;
align-items: center !important;
gap: 6px !important;
padding-right: 0 !important;
min-width: 0 !important;
}
#email-lib-modal .email-reader-meta-row .recipient-chips,
.email-reader-tab-modal .email-reader-meta-row .recipient-chips,
.email-window-modal .email-reader-meta-row .recipient-chips {
display: inline-flex !important;
flex-wrap: nowrap !important;
overflow-x: auto !important;
overflow-y: hidden !important;
max-width: 100% !important;
scrollbar-width: none !important;
gap: 4px !important;
}
#email-lib-modal .email-reader-meta-row .recipient-chips::-webkit-scrollbar,
.email-reader-tab-modal .email-reader-meta-row .recipient-chips::-webkit-scrollbar,
.email-window-modal .email-reader-meta-row .recipient-chips::-webkit-scrollbar {
display: none !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: auto !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;
}
/* Documents need the same bounded flex chain as skills when a card is
opened. Without this, the grid falls back to its list layout and the
expanded preview only grows to its content height on mobile. */
#doclib-modal .admin-card:has(.doclib-card-expanded) > .doclib-grid {
display: flex !important;
flex: 1 1 0 !important;
flex-direction: column !important;
min-height: 0 !important;
overflow: hidden !important;
}
#doclib-modal .doclib-card.doclib-card-expanded {
flex: 1 1 auto !important;
height: 100% !important;
min-height: 0 !important;
}
#doclib-modal .doclib-card.doclib-card-expanded .doclib-card-preview {
flex: 1 1 0 !important;
min-height: 0 !important;
}
#doclib-modal .doclib-card.doclib-card-expanded .doclib-card-pdf-frame {
height: auto !important;
flex: 1 1 0 !important;
min-height: 0 !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: nowrap;
}
/* 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;
}
/* On phones, keep Delete explicit while the other secondary actions
stay available from the kebab. */
#doclib-modal .doclib-card-expanded-actions > .doclib-expanded-archive-btn,
#doclib-modal .doclib-card-expanded-actions .doclib-expanded-clone-btn,
#doclib-modal .doclib-card-expanded-actions .doclib-expanded-export-btn {
display: none !important;
}
#doclib-modal .doclib-card-expanded-actions > .doclib-expanded-delete-btn {
display: inline-flex !important;
order: 0;
left: 0 !important;
margin-left: 0;
}
#doclib-modal .doclib-card-expanded-actions > .doclib-action-group {
order: 2;
margin-left: 6px !important;
min-width: 0;
}
#doclib-modal .doclib-card.doclib-card-expanded .doclib-card-expanded-actions {
box-sizing: border-box;
width: 100%;
max-width: 100%;
min-width: 0;
overflow: visible;
}
#doclib-modal .doclib-card-expanded-actions .doclib-expanded-open-btn {
min-height: 30px;
padding: 5px 10px;
max-width: 100%;
}
#doclib-modal .doclib-card-expanded-actions > .doclib-expanded-mobile-more {
display: inline-flex;
order: 1;
width: 32px;
min-width: 32px;
min-height: 30px;
margin-left: auto;
padding: 0;
}
/* 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 ----- */
.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;
}
.session-loading-state {
min-height: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
gap: 12px;
color: var(--fg);
font-size: 12px;
pointer-events: none;
position: relative;
padding: 16px 14px 24px;
box-sizing: border-box;
overflow: hidden;
}
.session-loading-state::before { content: none; }
.session-skeleton-bubble {
width: min(78%, 680px);
border: 1px solid color-mix(in srgb, var(--border) 74%, transparent);
border-radius: 10px;
background: color-mix(in srgb, var(--panel, var(--bg)) 72%, transparent);
padding: 10px 12px 12px;
box-shadow: 0 8px 22px rgba(0, 0, 0, 0.14);
}
.session-skeleton-bubble.is-user {
align-self: flex-end;
width: min(64%, 520px);
}
.session-skeleton-bubble.is-ai {
align-self: flex-start;
}
.session-skeleton-line {
position: relative;
overflow: hidden;
background: color-mix(in srgb, var(--fg) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--border) 82%, transparent);
border-radius: 999px;
}
.session-skeleton-line {
height: 10px;
margin-top: 8px;
opacity: 0.62;
}
.session-skeleton-line::after {
content: '';
position: absolute;
inset: -1px;
transform: translateX(-120%);
background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--fg) 14%, transparent), transparent);
animation: session-skeleton-shimmer 1.25s ease-in-out infinite;
}
.session-skeleton-bubble:nth-child(2) .session-skeleton-line::after { animation-delay: 0.12s; }
.session-skeleton-bubble:nth-child(3) .session-skeleton-line::after { animation-delay: 0.24s; }
.session-skeleton-bubble:nth-child(4) .session-skeleton-line::after { animation-delay: 0.36s; }
@keyframes session-skeleton-shimmer {
100% { transform: translateX(120%); }
}
@media (max-width: 768px) {
.session-loading-state { padding: 12px 10px 18px; gap: 10px; }
.session-skeleton-bubble { width: 86%; padding: 9px 10px 11px; border-radius: 9px; }
.session-skeleton-bubble.is-user { width: 72%; }
.session-skeleton-line { height: 9px; margin-top: 7px; }
}
/* 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;
}
.memory-modal-content,
.tasks-modal-content,
.preset-modal-content,
#cookbook-modal .modal-content,
#theme-popup,
.doclib-modal-content,
.gallery-modal-content {
container-type: inline-size;
}
.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 opts out of the sticky header on mobile; the title bar scrolls
away with the content instead of following. Background is inherited from
the shared .modal-header rule so frosted/glass themes stay consistent. */
#cookbook-modal .modal-header {
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);
}
/* Headers with an optional Peek control already have the title's
auto-margin grouping all right-side controls. A second auto-margin on
the injected minimize button splits the group and makes it look
different from the other modal headers. */
.modal-header .theme-opacity-wrap + .modal-minimize-btn {
margin-left: 0 !important;
}
.close-btn,
.modal-close {
background:transparent;
color:color-mix(in srgb, var(--fg) 58%, transparent);
border:1px solid transparent;
font-size:0;
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:999px;
line-height:1;
flex-shrink:0;
position:relative;
}
.close-btn > svg,
.modal-close > svg {
display:none;
}
.close-btn::before,
.modal-close::before {
content:"";
width:10px;
height:10px;
display:block;
background:currentColor;
-webkit-mask:
url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 2L8 8M8 2L2 8' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E")
center / contain no-repeat;
mask:
url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 2L8 8M8 2L2 8' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E")
center / contain no-repeat;
}
.close-btn:hover,
.modal-close:hover {
background:color-mix(in srgb, var(--fg) 10%, transparent);
border-color:color-mix(in srgb, var(--fg) 12%, transparent);
color:var(--fg);
}
/* Minimize button — sits beside the close button on every modal */
.minimize-btn {
background:transparent;
color:color-mix(in srgb, var(--fg) 58%, transparent);
border:1px solid transparent;
font-size:0;
font-weight:700;
width:24px;
height:24px;
padding:0;
display:inline-flex;
align-items:center;
justify-content:center;
line-height:1;
cursor:pointer;
border-radius:999px;
flex-shrink:0;
margin-left:auto; /* push to the right so it docks next to .close-btn */
margin-right:4px;
}
.minimize-btn::before {
content:"";
width:9px;
height:2px;
border-radius:999px;
background:currentColor;
display:block;
transform:translateY(2px);
}
.modal-minimize-btn.minimize-btn::before {
content:none;
display:none;
}
.minimize-btn:hover {
background:color-mix(in srgb, var(--fg) 10%, transparent);
border-color:color-mix(in srgb, var(--fg) 12%, transparent);
color:var(--fg);
}
@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;
}
.email-attachment-warning-modal {
z-index: 99999 !important;
}
.email-attachment-warning-modal .modal-content {
position: relative;
z-index: 100001;
}
.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); }
#styled-confirm-ok {
position: relative;
top: 2px;
}
#styled-confirm-cancel .styled-confirm-cancel-label {
position:relative;
top:2px;
}
.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); }
.cookbook-gguf-delete-box {
width:560px;
max-width:92vw;
}
.cookbook-gguf-delete-list {
display:flex;
flex-direction:column;
gap:6px;
max-height:42vh;
overflow:auto;
padding:2px 2px 4px;
}
.cookbook-gguf-delete-row {
display:grid;
grid-template-columns:18px minmax(0,1fr);
gap:7px 8px;
align-items:start;
padding:7px 8px;
border:1px solid var(--border);
border-radius:7px;
background:color-mix(in srgb, var(--panel, var(--bg)) 92%, var(--fg) 8%);
cursor:pointer;
}
.cookbook-gguf-delete-row:hover {
border-color:color-mix(in srgb, var(--accent-primary, var(--fg)) 45%, var(--border));
}
.cookbook-gguf-delete-cb {
-webkit-appearance:none;
appearance:none;
width:8px !important;
height:8px !important;
min-width:8px;
min-height:8px;
padding:0;
margin:4px 0 0;
border:1px solid var(--border);
border-radius:50%;
background:transparent;
box-sizing:content-box;
cursor:pointer;
transition:background 0.15s, border-color 0.15s, transform 0.12s;
}
.cookbook-gguf-delete-cb:hover {
border-color:var(--accent, var(--red));
transform:scale(1.12);
}
.cookbook-gguf-delete-cb:checked {
background:var(--accent, var(--red));
border-color:var(--accent, var(--red));
}
.cookbook-gguf-delete-main,
.cookbook-gguf-delete-path {
min-width:0;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.cookbook-gguf-delete-main {
font-size:0.86rem;
color:var(--fg);
}
.cookbook-gguf-delete-path {
grid-column:2;
margin-top:-2px;
font-size:0.74rem;
opacity:0.58;
}
/* 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-nav-btn.agent-working::after,
body.chat-agent-working #scroll-bottom-btn.show::after {
content: '';
position: absolute;
inset: 0;
border-radius: 10px;
padding: 1px;
background: conic-gradient(
from 0deg,
transparent 0deg,
transparent 245deg,
color-mix(in srgb, var(--accent, var(--red)) 78%, transparent) 315deg,
transparent 360deg
);
-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;
opacity: 0.65;
pointer-events: none;
animation: scrollAgentWorkingRing 1.05s linear infinite;
}
@keyframes scrollAgentWorkingRing {
to { transform: rotate(360deg); }
}
#scroll-bottom-btn.show { opacity:1; pointer-events:auto; }
.scroll-nav-caret {
display: inline-block;
font-family: Arial, Helvetica, sans-serif;
font-size: 16px;
line-height: 1;
transform: translateY(-1px);
pointer-events: none;
}
@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);
}
.persona-display-name-row .vis-label {
position: relative;
top: -4px;
}
.persona-display-name-row .vis-switch {
position: relative;
left: 4px;
}
.md-attach-paperclip-icon {
position: relative;
top: 1px;
}
/* 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;
}
#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: var(--search-overlay-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;
}
.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; position: relative; }
.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%; }
#theme-bg-effect-color { position: relative; top: 2px; }
.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;
position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
}
.color-row input[type="color"],
.color-row input.cp-swatch-input { margin-left: auto; }
/* Keep the harmony hex readout on the left, with the swatch dot anchored
to the right like the other color controls. */
#theme-harmony-card #harmony-accent-hex {
order: -1;
margin-right: auto;
}
.color-row:has(.color-reset-btn.changed) input[type="color"],
.color-row:has(.color-reset-btn.changed) input.cp-swatch-input { margin-right: 28px; }
.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); }
#memory-import-btn svg,
#memory-export-btn svg {
color: var(--accent, var(--red));
}
.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); }
@media (max-width: 768px) {
/* The switch is too cramped beside the Font and Density selects on
narrow screens; give Frosted a dedicated row instead. */
.theme-fd-row:first-of-type {
flex-wrap: wrap;
}
.theme-fd-row:first-of-type #theme-frosted-group {
flex: 1 1 100%;
flex-direction: row;
align-items: center;
justify-content: space-between;
min-width: 0;
}
}
.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%;
}