/* Codeman - Terminal-First UI */ /* Self-hosted UI + mono fonts (Carbon Aurora · Daylight) — served from /fonts, no external CDN, CSP-safe (font-src 'self'). Variable woff2 cover all weights. */ @font-face { font-family: 'Manrope'; font-style: normal; font-display: swap; font-weight: 400 800; src: url('fonts/manrope-variable.woff2') format('woff2'); } @font-face { font-family: 'JetBrains Mono'; font-style: normal; font-display: swap; font-weight: 400 700; src: url('fonts/jetbrains-mono-variable.woff2') format('woff2'); } /* Icons-only per-glyph fallback for the terminal (Symbols Nerd Font Mono, MIT, fonts/LICENSE-nerd-fonts.txt). Sits BEHIND the text fonts in the xterm stack (constants.js: TERMINAL_FONT_DEFAULT_STACK), so it only ever supplies the private-use-area glyphs shell prompts draw (powerline, p10k/starship folder and git icons) — text rendering is untouched. `block` display, not `swap`: there is no fallback that CAN render these glyphs, so swapping in tofu first would poison xterm's glyph atlas until the next re-render. */ @font-face { font-family: 'Symbols Nerd Font Mono'; font-style: normal; font-display: block; src: url('fonts/symbols-nerd-font-mono.woff2') format('woff2'); } :root { /* Dark is the safe fallback. Light skins override this so native selects, date/time pickers, form controls, and scrollbars match the chosen skin. */ color-scheme: dark; /* Carbon Aurora · Daylight — deep-but-not-black slate, surfaces step UP into the light */ --bg-dark: #11151c; --bg-card: #1b222c; --bg-input: #202833; --bg-hover: #2a323d; --border: #2b333f; --border-light: #3a4350; --text: #f3f6fa; --text-dim: #98a2b1; --text-muted: #717b8c; /* SINGLE cohesive accent — optimistic emerald → teal (replaces the old blue) */ --accent: #38b6f0; --accent-hover: #3ec8ee; /* calmer secondary green for non-primary pills/dots so Run + ring stay focal */ --green: #44b993; /* warm, quiet "working" amber (no second saturated hue) */ --yellow: #f0c25a; --red: #e06363; /* Daylight accent extras — referenced by the v1.0 override block at end of file */ --accent-d: #2487c9; --emerald: #3ec8ee; --teal: #2b8fd9; --accent-soft: #44b993; --accent-ink: #04223a; --warm: #e8c79a; --ring-glow: 0 0 12px -2px rgba(56, 182, 240, 0.55); --header-height: 36px; --toolbar-height: 42px; --tab-rail-width: 256px; --sidebar-width: 260px; --sidebar-width-rich: 300px; /* detailed rows carry a stamps line as well */ --sidebar-width-collapsed: 44px; /* == --touch-target-min */ --sidebar-transition: 0.18s ease; --glass-bg: rgba(31, 38, 48, 0.85); --glass-border: rgba(255, 255, 255, 0.08); --control-bg: rgba(255, 255, 255, 0.045); --control-bg-hover: rgba(255, 255, 255, 0.08); --control-border: rgba(255, 255, 255, 0.09); --control-border-hover: rgba(255, 255, 255, 0.14); --floating-bg: rgba(31, 38, 48, 0.96); --banner-bg-a: rgba(31, 38, 48, 0.92); --banner-bg-b: rgba(22, 27, 35, 0.92); --modal-backdrop: rgba(0, 0, 0, 0.6); --elevated-shadow: 0 16px 64px rgba(0, 0, 0, 0.5), 0 4px 16px rgba(0, 0, 0, 0.3); --subtle-shadow: 0 -1px 3px rgba(0, 0, 0, 0.3), 0 -4px 16px rgba(0, 0, 0, 0.15); --btn-radius: 6px; --transition-smooth: 0.2s cubic-bezier(0.4, 0, 0.2, 1); /* Session color palette — retoned toward the Daylight system */ --session-red: #e06363; --session-orange: #e8a45c; --session-yellow: #f0c25a; --session-green: #44b993; --session-blue: #2b8fd9; --session-purple: #a98fe0; --session-pink: #e07db0; /* Safe area insets for notched devices (iPhone X+, Dynamic Island) */ --safe-area-top: env(safe-area-inset-top, 0px); --safe-area-bottom: env(safe-area-inset-bottom, 0px); --safe-area-left: env(safe-area-inset-left, 0px); --safe-area-right: env(safe-area-inset-right, 0px); /* Touch target minimum (iOS Human Interface Guidelines) */ --touch-target-min: 44px; /* UI font + accent/gradient/hover tokens referenced by the v1.0 override block. These :root values equal the daylight-blue palette so the no-attribute fallback matches current behavior. */ --ui-font: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; --accent-rgb: 56, 182, 240; --accent-grad-a: #3ec8ee; --accent-grad-b: #2b8fd9; --run-hover-a: #5cc4f5; --run-hover-b: #3aa3e2; --gear-hover: #2a93d0; /* Compatibility aliases used by newer panels and overlays. Keep these references dynamic so every skin, including light skins, inherits the same semantic surface and text colors. */ --bg-primary: var(--bg-dark); --bg-secondary: var(--bg-card); --bg-tertiary: var(--bg-input); --text-primary: var(--text); --text-secondary: var(--text-dim); --border-color: var(--border); --accent-color: var(--accent); --success: var(--green); --error: var(--red); --danger: var(--red); --font-mono: 'Fira Code', 'JetBrains Mono', 'SF Mono', Menlo, Monaco, monospace; --shadow-lg: var(--elevated-shadow); } /* ===== Skin: Daylight Blue (current default) ===== */ html[data-skin="daylight-blue"] { --bg-dark: #11151c; --bg-card: #1b222c; --bg-input: #202833; --bg-hover: #2a323d; --border: #2b333f; --border-light: #3a4350; --text: #f3f6fa; --text-dim: #98a2b1; --text-muted: #717b8c; --accent: #38b6f0; --accent-hover: #3ec8ee; --term-bg: #161b23; --green: #44b993; --yellow: #f0c25a; --red: #e06363; --glass-bg: rgba(31, 38, 48, 0.85); --glass-border: rgba(255, 255, 255, 0.08); --accent-d: #2487c9; --emerald: #3ec8ee; --teal: #2b8fd9; --accent-soft: #44b993; --accent-ink: #04223a; --accent-rgb: 56, 182, 240; --accent-grad-a: #3ec8ee; --accent-grad-b: #2b8fd9; --run-hover-a: #5cc4f5; --run-hover-b: #3aa3e2; --gear-hover: #2a93d0; --ring-glow: 0 0 12px -2px rgba(56, 182, 240, 0.55); --session-red: #e06363; --session-orange: #e8a45c; --session-yellow: #f0c25a; --session-green: #44b993; --session-blue: #2b8fd9; --session-purple: #a98fe0; --session-pink: #e07db0; --ui-font: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } /* ===== Skin: Daylight Green ===== */ html[data-skin="daylight-green"] { --bg-dark: #11151c; --bg-card: #1b222c; --bg-input: #202833; --bg-hover: #2a323d; --border: #2b333f; --border-light: #3a4350; --text: #f3f6fa; --text-dim: #98a2b1; --text-muted: #717b8c; --accent: #2fd3aa; --accent-hover: #34d8a0; --term-bg: #161b23; --green: #44b993; --yellow: #f0c25a; --red: #e06363; --glass-bg: rgba(31, 38, 48, 0.85); --glass-border: rgba(255, 255, 255, 0.08); --accent-d: #19a987; --emerald: #34d8a0; --teal: #2bcbbb; --accent-soft: #44b993; --accent-ink: #062019; --accent-rgb: 47, 211, 170; --accent-grad-a: #34d8a0; --accent-grad-b: #2bcbbb; --run-hover-a: #3ee0aa; --run-hover-b: #34d4c4; --gear-hover: #1cbd97; --ring-glow: 0 0 12px -2px rgba(47, 211, 170, 0.55); --session-red: #e06363; --session-orange: #e8a45c; --session-yellow: #f0c25a; --session-green: #44b993; --session-blue: #2bcbbb; --session-purple: #a98fe0; --session-pink: #e07db0; --ui-font: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } /* ===== Skin: OG Codeman (original look — no Daylight polish, system font) ===== */ html[data-skin="og"] { --bg-dark: #09090b; --bg-card: #131316; --bg-input: #1a1a1f; --bg-hover: #1f1f26; --border: #232329; --border-light: #2e2e38; --text: #ececf0; --text-dim: #8b8b97; --text-muted: #52525e; --accent: #3b82f6; --accent-hover: #60a5fa; --term-bg: #0d0d0d; --green: #22c55e; --yellow: #eab308; --red: #ef4444; --glass-bg: rgba(19, 19, 22, 0.85); --glass-border: rgba(255, 255, 255, 0.06); /* OG is a neutral near-black palette with no blue in it. Without this the :root slate default would repaint every modal/palette/floating window rgba(31,38,48,.96) and quietly break the skin's identity. */ --floating-bg: rgba(19, 19, 22, 0.96); /* daylight-only extras left at sane neutral defaults (unused — override block does not apply to og) */ --accent-d: #3b82f6; --emerald: #3b82f6; --teal: #2563eb; --accent-soft: #22c55e; --accent-ink: #0b1220; --accent-rgb: 59, 130, 246; --accent-grad-a: #3b82f6; --accent-grad-b: #2563eb; --run-hover-a: #60a5fa; --run-hover-b: #3b82f6; --gear-hover: #2563eb; --ring-glow: 0 0 12px -2px rgba(59, 130, 246, 0.55); --session-red: #ef4444; --session-orange: #f97316; --session-yellow: #eab308; --session-green: #22c55e; --session-blue: #3b82f6; --session-purple: #a855f7; --session-pink: #ec4899; --ui-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } /* ===== Light skins ===== Each palette covers both the application chrome and the xterm palette in terminal-ui.js. The names reference their source palettes; Paper Gray is a Codeman-specific neutral theme informed by GitHub Primer Light. */ html[data-skin="paper-gray"] { color-scheme: light; --bg-dark: #f3f4f6; --bg-card: #ffffff; --bg-input: #eef1f4; --bg-hover: #e4e9ef; --border: #d0d7de; --border-light: #afb8c1; --text: #1f2328; --text-dim: #59636e; --text-muted: #6e7781; --accent: #0969da; --accent-hover: #0550ae; --term-bg: #f6f8fa; --green: #1a7f37; --yellow: #9a6700; --red: #cf222e; --glass-bg: rgba(255, 255, 255, 0.9); --glass-border: rgba(31, 35, 40, 0.15); --control-bg: rgba(31, 35, 40, 0.045); --control-bg-hover: rgba(31, 35, 40, 0.08); --control-border: rgba(31, 35, 40, 0.12); --control-border-hover: rgba(31, 35, 40, 0.2); --floating-bg: rgba(255, 255, 255, 0.97); --banner-bg-a: rgba(255, 255, 255, 0.96); --banner-bg-b: rgba(238, 241, 244, 0.96); --modal-backdrop: rgba(31, 35, 40, 0.32); --elevated-shadow: 0 16px 48px rgba(31, 35, 40, 0.18), 0 3px 12px rgba(31, 35, 40, 0.1); --accent-d: #0550ae; --emerald: #0969da; --teal: #218bff; --accent-soft: #1a7f37; --accent-ink: #ffffff; --accent-rgb: 9, 105, 218; --accent-grad-a: #218bff; --accent-grad-b: #0969da; --run-hover-a: #0969da; --run-hover-b: #0550ae; --gear-hover: #033d8b; --ring-glow: 0 0 10px -2px rgba(9, 105, 218, 0.35); --session-red: #cf222e; --session-orange: #bc4c00; --session-yellow: #9a6700; --session-green: #1a7f37; --session-blue: #0969da; --session-purple: #8250df; --session-pink: #bf3989; --ui-font: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } html[data-skin="solarized-light"] { color-scheme: light; --bg-dark: #fdf6e3; --bg-card: #fffaf0; --bg-input: #eee8d5; --bg-hover: #e5deca; --border: #d7cfb9; --border-light: #b8ad91; --text: #073642; --text-dim: #586e75; --text-muted: #6f8185; --accent: #147ba3; --accent-hover: #07658b; --term-bg: #fdf6e3; --green: #6f8100; --yellow: #8f7000; --red: #c43c39; --glass-bg: rgba(255, 250, 240, 0.9); --glass-border: rgba(88, 110, 117, 0.2); --control-bg: rgba(88, 110, 117, 0.07); --control-bg-hover: rgba(88, 110, 117, 0.12); --control-border: rgba(88, 110, 117, 0.18); --control-border-hover: rgba(88, 110, 117, 0.3); --floating-bg: rgba(255, 250, 240, 0.97); --banner-bg-a: rgba(255, 250, 240, 0.96); --banner-bg-b: rgba(238, 232, 213, 0.96); --modal-backdrop: rgba(7, 54, 66, 0.28); --elevated-shadow: 0 16px 48px rgba(7, 54, 66, 0.16), 0 3px 12px rgba(7, 54, 66, 0.09); --accent-d: #07658b; --emerald: #2aa198; --teal: #147ba3; --accent-soft: #6f8100; --accent-ink: #ffffff; --accent-rgb: 20, 123, 163; --accent-grad-a: #2aa198; --accent-grad-b: #147ba3; --run-hover-a: #168f86; --run-hover-b: #07658b; --gear-hover: #075676; --ring-glow: 0 0 10px -2px rgba(20, 123, 163, 0.32); --session-red: #dc322f; --session-orange: #cb4b16; --session-yellow: #9b7800; --session-green: #758600; --session-blue: #147ba3; --session-purple: #6c71c4; --session-pink: #d33682; --ui-font: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } html[data-skin="catppuccin-latte"] { color-scheme: light; --bg-dark: #eff1f5; --bg-card: #f8f9fb; --bg-input: #e6e9ef; --bg-hover: #dce0e8; --border: #ccd0da; --border-light: #acb0be; --text: #4c4f69; --text-dim: #5c5f77; --text-muted: #6c6f85; --accent: #1e66f5; --accent-hover: #174fbf; --term-bg: #eff1f5; --green: #3b8f2b; --yellow: #a86605; --red: #d20f39; --glass-bg: rgba(248, 249, 251, 0.9); --glass-border: rgba(76, 79, 105, 0.16); --control-bg: rgba(76, 79, 105, 0.055); --control-bg-hover: rgba(76, 79, 105, 0.1); --control-border: rgba(76, 79, 105, 0.14); --control-border-hover: rgba(76, 79, 105, 0.24); --floating-bg: rgba(248, 249, 251, 0.97); --banner-bg-a: rgba(248, 249, 251, 0.96); --banner-bg-b: rgba(230, 233, 239, 0.96); --modal-backdrop: rgba(76, 79, 105, 0.28); --elevated-shadow: 0 16px 48px rgba(76, 79, 105, 0.17), 0 3px 12px rgba(76, 79, 105, 0.09); --accent-d: #174fbf; --emerald: #179299; --teal: #1e66f5; --accent-soft: #3b8f2b; --accent-ink: #ffffff; --accent-rgb: 30, 102, 245; --accent-grad-a: #209fb5; --accent-grad-b: #1e66f5; --run-hover-a: #17889c; --run-hover-b: #174fbf; --gear-hover: #153f99; --ring-glow: 0 0 10px -2px rgba(30, 102, 245, 0.32); --session-red: #d20f39; --session-orange: #d65d0e; --session-yellow: #a86605; --session-green: #3b8f2b; --session-blue: #1e66f5; --session-purple: #8839ef; --session-pink: #c63c91; --ui-font: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } html[data-skin="rose-pine-dawn"] { color-scheme: light; --bg-dark: #faf4ed; --bg-card: #fffaf3; --bg-input: #f2e9e1; --bg-hover: #ebe1da; --border: #dfdad9; --border-light: #c8c1c0; --text: #575279; --text-dim: #6e6a86; --text-muted: #797593; --accent: #286983; --accent-hover: #1f5266; --term-bg: #faf4ed; --green: #286983; --yellow: #96681f; --red: #b4637a; --glass-bg: rgba(255, 250, 243, 0.9); --glass-border: rgba(87, 82, 121, 0.16); --control-bg: rgba(87, 82, 121, 0.055); --control-bg-hover: rgba(87, 82, 121, 0.1); --control-border: rgba(87, 82, 121, 0.14); --control-border-hover: rgba(87, 82, 121, 0.24); --floating-bg: rgba(255, 250, 243, 0.97); --banner-bg-a: rgba(255, 250, 243, 0.96); --banner-bg-b: rgba(242, 233, 225, 0.96); --modal-backdrop: rgba(87, 82, 121, 0.28); --elevated-shadow: 0 16px 48px rgba(87, 82, 121, 0.17), 0 3px 12px rgba(87, 82, 121, 0.09); --accent-d: #1f5266; --emerald: #56949f; --teal: #286983; --accent-soft: #907aa9; --accent-ink: #ffffff; --accent-rgb: 40, 105, 131; --accent-grad-a: #56949f; --accent-grad-b: #286983; --run-hover-a: #3f7f8b; --run-hover-b: #1f5266; --gear-hover: #193f4f; --ring-glow: 0 0 10px -2px rgba(40, 105, 131, 0.32); --session-red: #b4637a; --session-orange: #c06f45; --session-yellow: #96681f; --session-green: #286983; --session-blue: #477f91; --session-purple: #907aa9; --session-pink: #b4637a; --ui-font: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } /* Components that predate skin tokens used literal dark glass fills. Keep their structure, but make those elevated surfaces coherent in light mode. */ html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) { scrollbar-color: var(--border-light) var(--bg-input); } html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) :is( .modal-content, .ralph-dropdown, .ralph-panel.detached, .mobile-case-picker-sheet, .command-palette, .case-combobox-list ) { background: var(--floating-bg); border-color: var(--control-border); color: var(--text); box-shadow: var(--elevated-shadow); } html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) :is( .settings-item, .cron-job-row, #cronModal .form-row-switch.cron-switch-row, .cron-weekdays label ) { background: var(--control-bg); border-color: var(--control-border); } html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) :is( .settings-item:hover, .cron-job-row:hover, .cron-weekdays label:hover ) { background: var(--control-bg-hover); border-color: var(--control-border-hover); } /* Status badges and accent-tinted pills bake in pale light-on-dark ink (#cdddff, #9dc0ff, #ffc107, #fff, …) over a low-alpha tint. That reads at 1.0–1.9:1 once the tint sits on a light surface — the search filter chips literally vanish. Re-point the ink at each skin's own dark tokens and keep the tint as the category signal. */ html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) :is( .search-filter-chip.active, .search-badge-session, .history-view-all-btn, .session-tab .tab-mode.gemini, .session-tab .tab-mode.antigravity, .session-tab .tab-mode.pi, .session-tab .tab-mode.grok, .session-tab .tab-mode.omp ) { color: var(--accent-d); } html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) :is( .search-badge-file, .tunnel-panel-btn.btn-start, .voice-provider-status.active ) { color: var(--green); } html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) :is( .ralph-task-priority.priority-p1, .ralph-circuit-breaker.half-open ) { color: var(--yellow); } html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="catppuccin-latte"], [data-skin="rose-pine-dawn"]) :is( .search-badge-event, .respawn-state, .history-detail-path, .mobile-case-item, .mobile-case-picker-header h3, .btn-toolbar.btn-case-mobile:hover ) { color: var(--text); } * { box-sizing: border-box; margin: 0; padding: 0; } /* ========== Accessibility: Focus Styles ========== */ /* Skip link for keyboard users */ .skip-link { position: absolute; top: -50px; left: 8px; padding: 8px 16px; background: var(--accent); color: white; text-decoration: none; border-radius: 4px; z-index: 10000; font-size: 0.875rem; transition: top 0.2s; } .skip-link:focus { top: 8px; } /* Global focus-visible styles for keyboard navigation */ :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } /* Remove default outline for mouse users */ :focus:not(:focus-visible) { outline: none; } /* Button focus styles */ button:focus-visible, .btn-toolbar:focus-visible, .btn-icon:focus-visible, .btn-icon-header:focus-visible, .btn-icon-sm:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } /* Input focus styles */ input:focus-visible, select:focus-visible, textarea:focus-visible { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); outline: none; } /* xterm.js hidden textarea — must never show focus ring or caret. * On mobile (touch devices), override xterm.css defaults that position the * textarea at left:-9999em with width/height:0. iOS Safari ignores keyboard * input to off-screen zero-size textareas, so we move it on-screen with a * minimal size while keeping it visually invisible. */ .xterm-helper-textarea { border: none !important; box-shadow: none !important; outline: none !important; opacity: 0 !important; caret-color: transparent !important; } .touch-device .xterm .xterm-helper-textarea { left: var(--xterm-helper-left, 0px) !important; top: var(--xterm-helper-top, 0px) !important; width: 1px !important; height: 1px !important; z-index: 0 !important; font-size: 16px !important; /* prevent iOS auto-zoom on focus */ } /* Session tab focus */ .session-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; z-index: 1; } /* Modal close button focus */ .modal-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } body { font-family: var(--ui-font); background: var(--bg-dark); color: var(--text); height: 100vh; height: 100dvh; height: var(--app-height, 100dvh); overflow: hidden; } /* App Layout - Terminal First */ .app { display: flex; flex-direction: column; height: 100vh; height: 100dvh; height: var(--app-height, 100dvh); } /* Compact Header */ .header { display: flex; align-items: flex-start; min-height: var(--header-height); padding: 0.35rem 0.75rem; background: var(--glass-bg); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border-bottom: 1px solid var(--glass-border); flex-shrink: 0; position: relative; z-index: 100; /* Above main content so dropdowns work */ /* Performance: isolate header from terminal reflow — tab badge updates and token counter changes shouldn't trigger terminal layout recalculation */ contain: layout style paint; } .header-brand { display: flex; align-items: center; padding-right: 0.75rem; border-right: 1px solid var(--glass-border); margin-right: 0.5rem; height: var(--header-height); flex-shrink: 0; } .logo { font-size: 0.85rem; font-weight: 700; color: var(--accent-hover); letter-spacing: -0.02em; cursor: pointer; transition: color var(--transition-smooth), text-shadow var(--transition-smooth), filter var(--transition-smooth); } /* Single-letter brand shown only on phones (see mobile.css phone breakpoint) */ .logo .logo-compact { display: none; } .logo:hover { color: #93c5fd; text-shadow: 0 0 12px rgba(96, 165, 250, 0.5), 0 0 24px rgba(96, 165, 250, 0.2); filter: brightness(1.1); } /* Session Tabs */ .session-tabs { display: flex; align-items: flex-start; flex-wrap: nowrap; gap: 4px 2px; flex: 1; overflow-x: auto; overflow-y: hidden; padding: 0.15rem 0.25rem; /* Performance: isolate layout recalculations */ contain: layout; } .session-tabs.tabs-two-rows { flex-wrap: wrap; overflow-x: hidden; overflow-y: auto; max-height: 120px; } .session-tabs.tabs-auto-wrap { flex-wrap: wrap; overflow-x: hidden; overflow-y: auto; max-height: 96px; } .session-tabs::-webkit-scrollbar { width: 4px; height: 0; } .session-tabs::-webkit-scrollbar-track { background: transparent; } .session-tabs::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; } .session-tabs::-webkit-scrollbar-thumb:hover { background: var(--border-light); } .tab-rail { display: none; flex: 0 0 var(--tab-rail-width); width: var(--tab-rail-width); min-width: 0; overflow: hidden; background: var(--glass-bg); border-right: 1px solid var(--glass-border); position: relative; z-index: 11; } html[data-tab-orientation='vertical'] .tab-rail { display: flex; flex-direction: column; } html[data-tab-orientation='vertical'] .tab-rail .session-tabs { --lineage-vertical-gutter: 24px; flex-direction: column; flex-wrap: nowrap; align-items: stretch; gap: 2px; padding: 0.35rem; padding-left: calc(0.35rem + var(--lineage-vertical-gutter)); overflow-x: hidden; overflow-y: auto; max-height: none; } html[data-tab-orientation='vertical'] .tab-rail .session-tab { width: 100%; max-width: none; justify-content: flex-start; } html[data-tab-orientation='vertical'] .tab-rail .session-tab > * { flex-shrink: 0; } html[data-tab-orientation='vertical'] .tab-rail .session-tab .tab-info { flex: 1 1 auto; min-width: 0; } html[data-tab-orientation='vertical'] .header-right { margin-left: auto; } .tab-rail-resize-handle { position: absolute; z-index: 2; top: 0; right: 0; bottom: 0; width: 16px; cursor: ew-resize; touch-action: none; transition: background-color 0.15s ease; } .tab-rail-resize-handle:hover, .tab-rail-resize-handle:focus-visible { background: color-mix(in srgb, var(--accent) 24%, transparent); outline: 2px solid var(--accent); outline-offset: -2px; } html:not([data-tab-orientation='vertical']) .tab-rail-resize-handle { display: none; } .tab-rail-resize-shield:not([hidden]) { display: block; position: fixed; inset: 0; z-index: 10000; cursor: ew-resize; } body.tab-rail-resizing, body.tab-rail-resizing * { cursor: ew-resize !important; user-select: none !important; } @media (prefers-reduced-motion: reduce) { .tab-rail, .tab-rail-resize-handle { transition: none !important; } } .session-tab { display: flex; align-items: center; gap: 0.35rem; position: relative; padding: 0.35rem 0.6rem; background: transparent; border: 1px solid transparent; border-radius: var(--btn-radius); color: var(--text-dim); font-size: 0.75rem; cursor: pointer; white-space: nowrap; transition: background var(--transition-smooth), border-color var(--transition-smooth), color var(--transition-smooth), box-shadow var(--transition-smooth); } .session-tab:hover { background: rgba(34, 197, 94, 0.06); color: var(--text); box-shadow: 0 0 8px 2px rgba(34, 197, 94, 0.18), inset 0 0 6px 1px rgba(34, 197, 94, 0.06); border-color: rgba(34, 197, 94, 0.18); } .session-tab:active { background: rgba(34, 197, 94, 0.12); box-shadow: 0 0 12px 4px rgba(34, 197, 94, 0.35), inset 0 0 8px 2px rgba(34, 197, 94, 0.15); border-color: rgba(34, 197, 94, 0.3); } .session-tab.active { background: rgba(34, 197, 94, 0.15) !important; border: 2px solid #00FF66 !important; color: #fff !important; box-shadow: none !important; outline: none !important; } .session-tab.tab-loading::after { content: ""; position: absolute; left: 8px; right: 8px; bottom: -2px; height: 2px; border-radius: 999px; background: linear-gradient(90deg, transparent, rgba(96, 165, 250, 0.95), transparent); animation: tab-load-sweep 0.9s linear infinite; pointer-events: none; } .tab-load-spinner { width: 10px; height: 10px; border: 2px solid rgba(96, 165, 250, 0.3); border-top-color: rgba(96, 165, 250, 0.95); border-radius: 50%; flex: 0 0 10px; animation: tab-load-spin 0.7s linear infinite; } @keyframes tab-load-spin { to { transform: rotate(360deg); } } @keyframes tab-load-sweep { 0% { transform: translateX(-45%); opacity: 0.35; } 50% { opacity: 1; } 100% { transform: translateX(45%); opacity: 0.35; } } /* Tab switch feedback: bright green glow on the newly-active tab */ .session-tab.tab-glow { animation: tab-glow 0.35s ease-out; } @keyframes tab-glow { 0% { box-shadow: 0 0 0px 0px rgba(34, 197, 94, 0); background: transparent; } 10% { box-shadow: 0 0 18px 6px rgba(34, 197, 94, 0.7), 0 0 40px 12px rgba(34, 197, 94, 0.3), inset 0 0 12px 3px rgba(34, 197, 94, 0.25); background: rgba(34, 197, 94, 0.12); } 30% { box-shadow: 0 0 14px 5px rgba(34, 197, 94, 0.5), 0 0 30px 10px rgba(34, 197, 94, 0.2), inset 0 0 10px 2px rgba(34, 197, 94, 0.15); background: rgba(34, 197, 94, 0.08); } 100% { box-shadow: 0 0 0px 0px rgba(34, 197, 94, 0); background: transparent; } } /* ── Tab entrance animations (tab-entrance.js) ───────────────────────────── Style is picked by html[data-tab-anim]; the stagger arrives as an inline --tab-enter-delay per tab (negative when an entrance is resuming after a re-render). Duration is scaled by --anim-enter-scale so one slider retimes every style. Colour/glow effects live on ::before, never on the tab itself: .session-tab.active sets background/border/box-shadow with !important (and a newly created session is normally the active one), and !important beats an animation. Transform and opacity are unaffected, so the tab keeps those. */ .session-tab.tab-enter { animation-delay: var(--tab-enter-delay, 0ms); animation-fill-mode: both; will-change: transform, opacity; } .session-tab.tab-enter::before { content: ""; position: absolute; inset: -1px; border-radius: inherit; pointer-events: none; opacity: 0; animation-delay: var(--tab-enter-delay, 0ms); animation-fill-mode: both; } /* Slide, drifts in from the right and settles. */ html[data-tab-anim="slide"] .session-tab.tab-enter { animation-name: tab-enter-slide; animation-duration: calc(380ms * var(--anim-enter-scale, 1)); animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1); } @keyframes tab-enter-slide { from { opacity: 0; transform: translate3d(24px, 3px, 0) scale(0.96); } to { opacity: 1; transform: none; } } /* Pop, springs past full size, then settles back. */ html[data-tab-anim="pop"] .session-tab.tab-enter { animation-name: tab-enter-pop; animation-duration: calc(460ms * var(--anim-enter-scale, 1)); animation-timing-function: ease-out; } @keyframes tab-enter-pop { 0% { opacity: 0; transform: scale(0.4); } 55% { opacity: 1; transform: scale(1.13); } 76% { opacity: 1; transform: scale(0.965); } 100% { opacity: 1; transform: scale(1); } } /* CRT, snaps open as a hot line, then unfolds vertically. */ html[data-tab-anim="crt"] .session-tab.tab-enter { animation-name: tab-enter-crt; animation-duration: calc(520ms * var(--anim-enter-scale, 1)); animation-timing-function: cubic-bezier(0.3, 0.9, 0.3, 1); } html[data-tab-anim="crt"] .session-tab.tab-enter::before { animation-name: tab-enter-crt-flash; animation-duration: calc(520ms * var(--anim-enter-scale, 1)); animation-timing-function: ease-out; } @keyframes tab-enter-crt { 0% { opacity: 0; transform: scale3d(0.04, 0.09, 1); } 20% { opacity: 1; transform: scale3d(1, 0.09, 1); } 56% { opacity: 1; transform: scale3d(1, 1.14, 1); } 78% { opacity: 1; transform: scale3d(1, 0.95, 1); } 100% { opacity: 1; transform: none; } } @keyframes tab-enter-crt-flash { 0% { opacity: 1; background: rgba(205, 255, 225, 0.95); box-shadow: 0 0 26px 8px rgba(0, 255, 102, 0.6); } 20% { opacity: 0.9; background: rgba(150, 255, 195, 0.7); box-shadow: 0 0 20px 6px rgba(0, 255, 102, 0.45); } 60% { opacity: 0.32; background: rgba(0, 255, 102, 0.14); box-shadow: 0 0 12px 2px rgba(0, 255, 102, 0.22); } 100% { opacity: 0; background: transparent; box-shadow: none; } } /* Unroll, the strip makes room and the tab widens into it. */ html[data-tab-anim="unroll"] .session-tab.tab-enter { animation-name: tab-enter-unroll; animation-duration: calc(480ms * var(--anim-enter-scale, 1)); animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1); overflow: hidden; } @keyframes tab-enter-unroll { 0% { max-width: 0; opacity: 0; padding-left: 0; padding-right: 0; transform: translateY(3px); } 30% { opacity: 1; } 100% { max-width: 900px; opacity: 1; padding-left: 0.6rem; padding-right: 0.6rem; transform: none; } } /* Boot, flickers on under a green scan sweep, like a display warming up. */ html[data-tab-anim="boot"] .session-tab.tab-enter { animation-name: tab-enter-boot; animation-duration: calc(720ms * var(--anim-enter-scale, 1)); animation-timing-function: linear; } html[data-tab-anim="boot"] .session-tab.tab-enter::before { background: linear-gradient( 100deg, transparent 0%, rgba(0, 255, 102, 0.06) 34%, rgba(200, 255, 220, 0.5) 50%, rgba(0, 255, 102, 0.06) 66%, transparent 100% ); background-size: 280% 100%; animation-name: tab-enter-boot-scan; animation-duration: calc(720ms * var(--anim-enter-scale, 1)); animation-timing-function: ease-in-out; } @keyframes tab-enter-boot { 0% { opacity: 0; transform: translate3d(0, 5px, 0); } 8% { opacity: 0.85; } 15% { opacity: 0.12; } 23% { opacity: 1; } 31% { opacity: 0.35; } 42% { opacity: 1; transform: none; } 55% { opacity: 0.72; } 66% { opacity: 1; } 100% { opacity: 1; transform: none; } } @keyframes tab-enter-boot-scan { 0% { opacity: 0; background-position: 150% 0; } 12% { opacity: 1; } 86% { opacity: 1; } 100% { opacity: 0; background-position: -50% 0; } } /* Flip, drops in as a card hinged on its top edge. */ html[data-tab-anim="flip"] .session-tab.tab-enter { animation-name: tab-enter-flip; animation-duration: calc(520ms * var(--anim-enter-scale, 1)); animation-timing-function: cubic-bezier(0.3, 0.9, 0.3, 1); transform-origin: 50% 0%; } @keyframes tab-enter-flip { 0% { opacity: 0; transform: perspective(700px) rotateX(-92deg); } 55% { opacity: 1; transform: perspective(700px) rotateX(13deg); } 78% { opacity: 1; transform: perspective(700px) rotateX(-5deg); } 100% { opacity: 1; transform: perspective(700px) rotateX(0deg); } } /* ── Entrance lab (?animlab=1) ───────────────────────────────────────────── */ .anim-lab { position: fixed; right: 14px; bottom: 14px; z-index: 3100; width: 300px; max-height: 88vh; display: flex; flex-direction: column; padding: 10px 12px 12px; border: 1px solid var(--border-light); border-radius: 10px; background: rgba(12, 16, 14, 0.96); box-shadow: 0 12px 40px rgba(0, 0, 0, 0.55); color: var(--text); font-size: 0.72rem; backdrop-filter: blur(6px); } .anim-lab-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; font-weight: 600; letter-spacing: 0.02em; } .anim-lab-close { background: none; border: none; color: var(--text-dim); font-size: 1.1rem; line-height: 1; cursor: pointer; } .anim-lab-close:hover { color: var(--text); } .anim-lab-themes { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 10px; } .anim-lab-themes button { padding: 3px 8px; border: 1px solid var(--border-light); border-radius: 999px; background: rgba(255, 255, 255, 0.04); color: var(--text-dim); font-size: 0.66rem; cursor: pointer; } .anim-lab-themes button:hover { background: rgba(34, 197, 94, 0.16); color: var(--text); } .anim-lab-themes button.selected { border-color: rgba(0, 255, 102, 0.7); background: rgba(34, 197, 94, 0.2); color: #fff; } .anim-lab-scroll { flex: 1; min-height: 0; overflow-y: auto; margin-bottom: 8px; } .anim-lab-scroll::-webkit-scrollbar { width: 5px; } .anim-lab-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; } .anim-lab-group { margin-bottom: 10px; } .anim-lab-group-title { margin-bottom: 4px; font-size: 0.62rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted); } .anim-lab-style { display: flex; flex-direction: column; gap: 1px; width: 100%; margin-bottom: 3px; padding: 5px 8px; border: 1px solid transparent; border-radius: 6px; background: rgba(255, 255, 255, 0.03); color: var(--text-dim); text-align: left; cursor: pointer; } .anim-lab-style:hover { background: rgba(34, 197, 94, 0.09); color: var(--text); } .anim-lab-style.selected { border-color: rgba(0, 255, 102, 0.6); background: rgba(34, 197, 94, 0.14); color: #fff; } .anim-lab-style strong { font-size: 0.73rem; font-weight: 600; } .anim-lab-style em { font-style: normal; font-size: 0.64rem; opacity: 0.7; } .anim-lab-range { display: grid; grid-template-columns: auto 1fr; align-items: center; gap: 4px 8px; margin-bottom: 6px; color: var(--text-dim); } .anim-lab-range output { justify-self: end; color: var(--text); font-variant-numeric: tabular-nums; } .anim-lab-range input { grid-column: 1 / -1; width: 100%; accent-color: #00ff66; } .anim-lab-demo { display: flex; align-items: center; gap: 6px; margin-top: 8px; color: var(--text-dim); } .anim-lab-demo button { flex: 1; padding: 4px 0; border: 1px solid var(--border-light); border-radius: 6px; background: rgba(255, 255, 255, 0.04); color: var(--text); cursor: pointer; } .anim-lab-demo button:hover { background: rgba(34, 197, 94, 0.16); border-color: rgba(0, 255, 102, 0.5); } .anim-lab-hint { margin: 8px 0 0; font-size: 0.64rem; opacity: 0.55; } /* ── Window entrance animations ──────────────────────────────────────────── Applied to a window already sitting at its resting position. The `fly` style is NOT here: it is the pre-existing JS transition in subagent-windows.js that flies the window out of its parent tab, and it skips this class entirely. */ .subagent-window.win-enter, .ultracode-window.win-enter { animation-delay: var(--win-enter-delay, 0ms); animation-fill-mode: both; will-change: transform, opacity, filter; } .subagent-window.win-enter::before, .ultracode-window.win-enter::before { content: ""; position: absolute; inset: 0; z-index: 5; border-radius: inherit; pointer-events: none; opacity: 0; animation-delay: var(--win-enter-delay, 0ms); animation-fill-mode: both; } /* CRT, bursts open as a hot line, then unfolds vertically. */ html[data-win-anim="crt"] .subagent-window.win-enter, html[data-win-anim="crt"] .ultracode-window.win-enter { animation-name: win-enter-crt; animation-duration: calc(560ms * var(--anim-enter-scale, 1)); animation-timing-function: cubic-bezier(0.3, 0.9, 0.3, 1); } html[data-win-anim="crt"] .subagent-window.win-enter::before, html[data-win-anim="crt"] .ultracode-window.win-enter::before { animation-name: win-enter-crt-flash; animation-duration: calc(560ms * var(--anim-enter-scale, 1)); animation-timing-function: ease-out; } @keyframes win-enter-crt { 0% { opacity: 0; transform: scale3d(0.55, 0.008, 1); } 22% { opacity: 1; transform: scale3d(1, 0.01, 1); } 58% { opacity: 1; transform: scale3d(1, 1.05, 1); } 80% { opacity: 1; transform: scale3d(1, 0.98, 1); } 100% { opacity: 1; transform: none; } } @keyframes win-enter-crt-flash { 0% { opacity: 1; background: rgba(205, 255, 225, 0.9); box-shadow: 0 0 40px 14px rgba(0, 255, 102, 0.55); } 22% { opacity: 0.8; background: rgba(120, 255, 180, 0.5); box-shadow: 0 0 30px 10px rgba(0, 255, 102, 0.4); } 60% { opacity: 0.25; background: rgba(0, 255, 102, 0.1); box-shadow: 0 0 14px 3px rgba(0, 255, 102, 0.18); } 100% { opacity: 0; background: transparent; box-shadow: none; } } /* Materialize, resolves out of a blur with a short glitch. */ html[data-win-anim="materialize"] .subagent-window.win-enter, html[data-win-anim="materialize"] .ultracode-window.win-enter { animation-name: win-enter-materialize; animation-duration: calc(620ms * var(--anim-enter-scale, 1)); animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1); } @keyframes win-enter-materialize { 0% { opacity: 0; transform: scale(0.94); filter: blur(16px) brightness(1.7); } 35% { opacity: 0.85; filter: blur(6px) brightness(1.3); } 45% { opacity: 0.4; } 55% { opacity: 0.95; filter: blur(3px) brightness(1.15); } 70% { opacity: 0.75; } 100% { opacity: 1; transform: none; filter: none; } } /* Unfold, hinges down from the top edge. */ html[data-win-anim="unfold"] .subagent-window.win-enter, html[data-win-anim="unfold"] .ultracode-window.win-enter { animation-name: win-enter-unfold; animation-duration: calc(560ms * var(--anim-enter-scale, 1)); animation-timing-function: cubic-bezier(0.3, 0.9, 0.3, 1); transform-origin: 50% 0%; } @keyframes win-enter-unfold { 0% { opacity: 0; transform: perspective(1600px) rotateX(-88deg); } 55% { opacity: 1; transform: perspective(1600px) rotateX(9deg); } 80% { opacity: 1; transform: perspective(1600px) rotateX(-3deg); } 100% { opacity: 1; transform: perspective(1600px) rotateX(0deg); } } /* Beam down, holds still (so its connection line can draw toward a stable rect), then materializes out of a green wash. NO transform: a transformed window reports a transformed rect and the line would aim at the wrong place. */ html[data-win-anim="beam"] .subagent-window.win-enter, html[data-win-anim="beam"] .ultracode-window.win-enter { animation-name: win-enter-beam; animation-duration: calc(620ms * var(--anim-enter-scale, 1)); animation-timing-function: ease-out; } html[data-win-anim="beam"] .subagent-window.win-enter::before, html[data-win-anim="beam"] .ultracode-window.win-enter::before { animation-name: win-enter-beam-wash; animation-duration: calc(620ms * var(--anim-enter-scale, 1)); animation-timing-function: ease-out; } @keyframes win-enter-beam { 0% { opacity: 0; filter: brightness(2.6) saturate(0.4); } 20% { opacity: 0.55; } 32% { opacity: 0.2; } 48% { opacity: 0.9; filter: brightness(1.5) saturate(0.8); } 100% { opacity: 1; filter: none; } } @keyframes win-enter-beam-wash { 0% { opacity: 0.9; background: linear-gradient(180deg, rgba(190, 255, 215, 0.85), rgba(0, 255, 102, 0)); } 45% { opacity: 0.5; background: linear-gradient(180deg, rgba(0, 255, 102, 0.35), rgba(0, 255, 102, 0)); } 100% { opacity: 0; background: transparent; } } /* Pop, springs open from the centre. */ html[data-win-anim="pop"] .subagent-window.win-enter, html[data-win-anim="pop"] .ultracode-window.win-enter { animation-name: win-enter-pop; animation-duration: calc(460ms * var(--anim-enter-scale, 1)); animation-timing-function: ease-out; } @keyframes win-enter-pop { 0% { opacity: 0; transform: scale(0.55); } 58% { opacity: 1; transform: scale(1.045); } 80% { opacity: 1; transform: scale(0.99); } 100% { opacity: 1; transform: scale(1); } } /* ── Main terminal pane entrance animations ──────────────────────────────── ⚠ transform / opacity / clip-path ONLY. xterm's FitAddon derives rows+cols from getComputedStyle(parent).width/height, the untransformed layout box - so these are invisible to it, but animating width/height/padding here would resize the PTY mid-animation. Colour washes go on ::before, never as a `filter` on the container: that would blur a full-screen WebGL canvas every frame. `will-change` is deliberately not set, the base rule needs its `will-change: contents` for terminal compositing. */ .terminal-container.term-enter { animation-fill-mode: both; } .terminal-container.term-enter::before { content: ""; position: absolute; inset: 0; z-index: 4; pointer-events: none; opacity: 0; animation-fill-mode: both; } /* CRT, power-on: a hot line that expands to full height. */ html[data-term-anim="crt"] .terminal-container.term-enter { animation-name: term-enter-crt; animation-duration: calc(560ms * var(--anim-enter-scale, 1)); animation-timing-function: cubic-bezier(0.3, 0.9, 0.3, 1); } html[data-term-anim="crt"] .terminal-container.term-enter::before { animation-name: term-enter-crt-flash; animation-duration: calc(560ms * var(--anim-enter-scale, 1)); animation-timing-function: ease-out; } @keyframes term-enter-crt { 0% { opacity: 0; transform: scale3d(0.6, 0.004, 1); } 20% { opacity: 1; transform: scale3d(1, 0.006, 1); } 58% { opacity: 1; transform: scale3d(1, 1.03, 1); } 80% { opacity: 1; transform: scale3d(1, 0.99, 1); } 100% { opacity: 1; transform: none; } } @keyframes term-enter-crt-flash { 0% { opacity: 1; background: rgba(210, 255, 230, 0.85); } 20% { opacity: 0.7; background: rgba(120, 255, 180, 0.4); } 60% { opacity: 0.2; background: rgba(0, 255, 102, 0.08); } 100% { opacity: 0; background: transparent; } } /* Boot, flickers on under a green scan sweep. */ html[data-term-anim="boot"] .terminal-container.term-enter { animation-name: term-enter-boot; animation-duration: calc(760ms * var(--anim-enter-scale, 1)); animation-timing-function: linear; } html[data-term-anim="boot"] .terminal-container.term-enter::before { background: linear-gradient( 180deg, transparent 0%, rgba(0, 255, 102, 0.05) 40%, rgba(200, 255, 220, 0.28) 50%, rgba(0, 255, 102, 0.05) 60%, transparent 100% ); background-size: 100% 300%; animation-name: term-enter-boot-scan; animation-duration: calc(760ms * var(--anim-enter-scale, 1)); animation-timing-function: ease-in-out; } @keyframes term-enter-boot { 0% { opacity: 0; transform: scale(0.995); } 8% { opacity: 0.85; } 15% { opacity: 0.1; } 24% { opacity: 1; } 33% { opacity: 0.35; } 45% { opacity: 1; transform: none; } 58% { opacity: 0.7; } 70% { opacity: 1; } 100% { opacity: 1; transform: none; } } @keyframes term-enter-boot-scan { 0% { opacity: 0; background-position: 0 -150%; } 12% { opacity: 1; } 86% { opacity: 1; } 100% { opacity: 0; background-position: 0 150%; } } /* Wipe, reveals top-to-bottom behind a bright edge. */ html[data-term-anim="wipe"] .terminal-container.term-enter { animation-name: term-enter-wipe; animation-duration: calc(520ms * var(--anim-enter-scale, 1)); animation-timing-function: cubic-bezier(0.35, 0.85, 0.3, 1); } html[data-term-anim="wipe"] .terminal-container.term-enter::before { background: linear-gradient(180deg, rgba(0, 255, 102, 0.16) 0%, rgba(190, 255, 215, 0.5) 82%, transparent 100%); animation-name: term-enter-wipe-edge; animation-duration: calc(520ms * var(--anim-enter-scale, 1)); animation-timing-function: cubic-bezier(0.35, 0.85, 0.3, 1); } @keyframes term-enter-wipe { 0% { opacity: 1; clip-path: inset(0 0 100% 0); } 100% { opacity: 1; clip-path: inset(0 0 0 0); } } @keyframes term-enter-wipe-edge { 0% { opacity: 0.9; clip-path: inset(0 0 100% 0); } 80% { opacity: 0.45; } 100% { opacity: 0; clip-path: inset(0 0 0 0); } } /* Slide up, rises into place from below. */ html[data-term-anim="slide"] .terminal-container.term-enter { animation-name: term-enter-slide; animation-duration: calc(420ms * var(--anim-enter-scale, 1)); animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1); } @keyframes term-enter-slide { 0% { opacity: 0; transform: translate3d(0, 34px, 0); } 100% { opacity: 1; transform: none; } } /* Fade, quiet, with a touch of scale. */ html[data-term-anim="fade"] .terminal-container.term-enter { animation-name: term-enter-fade; animation-duration: calc(340ms * var(--anim-enter-scale, 1)); animation-timing-function: ease-out; } @keyframes term-enter-fade { 0% { opacity: 0; transform: scale(0.985); } 100% { opacity: 1; transform: none; } } /* ── Connection-line entrance animations ─────────────────────────────────── `--line-len` is the measured path length, stamped inline by _applyLineEntrances(); `--line-enter-delay` is negative when an entrance is resuming after the SVG was rebuilt underneath it. */ .connection-line.line-enter { animation-delay: var(--line-enter-delay, 0ms); animation-fill-mode: both; } /* Draw, the line paints itself from the tab down to the window. Overrides the resting 5 3 dash to a single full-length dash for the duration. */ html[data-line-anim="draw"] .connection-line.line-enter { stroke-dasharray: var(--line-len); animation-name: line-enter-draw; animation-duration: calc(420ms * var(--anim-enter-scale, 1)); animation-timing-function: cubic-bezier(0.32, 0.8, 0.3, 1); } @keyframes line-enter-draw { from { stroke-dashoffset: var(--line-len); opacity: 1; } to { stroke-dashoffset: 0; opacity: 0.9; } } /* Fade, plain opacity ramp, dashes intact. */ html[data-line-anim="fade"] .connection-line.line-enter { animation-name: line-enter-fade; animation-duration: calc(300ms * var(--anim-enter-scale, 1)); animation-timing-function: ease-out; } @keyframes line-enter-fade { from { opacity: 0; } to { opacity: 0.9; } } /* Packet, the base line fades in and a separate bright dash rides down it. The packet is its own cloned path so the base line keeps its dashed look. */ html[data-line-anim="packet"] .connection-line.line-enter { animation-name: line-enter-fade; animation-duration: calc(300ms * var(--anim-enter-scale, 1)); animation-timing-function: ease-out; } .connection-line-packet { fill: none; stroke: #cdfbe3; stroke-width: 4; stroke-linecap: round; pointer-events: none; stroke-dasharray: 20px 100000px; filter: drop-shadow(0 0 4px rgba(0, 255, 102, 0.95)) drop-shadow(0 0 10px rgba(59, 130, 246, 0.7)); animation-name: line-enter-packet; animation-duration: calc(700ms * var(--anim-enter-scale, 1)); animation-delay: var(--line-enter-delay, 0ms); animation-timing-function: cubic-bezier(0.4, 0, 0.3, 1); animation-fill-mode: both; } @keyframes line-enter-packet { 0% { stroke-dashoffset: 20px; opacity: 0; } 12% { opacity: 1; } 85% { opacity: 1; } 100% { stroke-dashoffset: calc(-1 * var(--line-len)); opacity: 0; } } .anim-lab-check { display: flex; align-items: center; gap: 6px; margin: -4px 0 12px; padding: 0 2px; color: var(--text-dim); font-size: 0.66rem; cursor: pointer; } .anim-lab-check input { accent-color: #00ff66; } @media (prefers-reduced-motion: reduce) { .session-tab.tab-enter, .session-tab.tab-enter::before, .subagent-window.win-enter, .subagent-window.win-enter::before, .ultracode-window.win-enter, .ultracode-window.win-enter::before, .terminal-container.term-enter, .terminal-container.term-enter::before, .connection-line.line-enter, .connection-line-packet { animation: none !important; } } .session-tab .tab-status { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; } .session-tab .tab-status.idle { background: var(--green); } .session-tab .tab-status.busy { position: relative; background: var(--green); animation: pulse 1.5s infinite; /* Performance: GPU acceleration for animation */ will-change: opacity; } /* Orbiting ring on a WORKING tab: the same `tab-load-spin` circle the desktop home rail (.home-sessions-dot--working) and the phone overview (.mobile-overview-dot--working) draw, so "working" reads identically on every surface. Pseudo-element on purpose — the skin block below sets `box-shadow: none` on .tab-status.busy to keep tab dots halo-free, and it outranks any plain class rule here; a ring drawn with a border is untouched by that and keeps the tabs quiet while still showing motion. Absolutely positioned, so it never widens the tab or shifts the label. */ .session-tab .tab-status.busy::after { content: ''; position: absolute; inset: -3px; border: 1.5px solid color-mix(in srgb, var(--green) 25%, transparent); border-top-color: var(--green); border-radius: 50%; animation: tab-load-spin 0.7s linear infinite; pointer-events: none; } .session-tab .tab-status.error { background: var(--red); } .session-tab .tab-status.ended { background: var(--text-muted); opacity: 0.5; } /* Session color coding - left border indicator */ .session-tab[data-color="red"] { border-left: 3px solid var(--session-red); } .session-tab[data-color="orange"] { border-left: 3px solid var(--session-orange); } .session-tab[data-color="yellow"] { border-left: 3px solid var(--session-yellow); } .session-tab[data-color="green"] { border-left: 3px solid var(--session-green); } .session-tab[data-color="blue"] { border-left: 3px solid var(--session-blue); } .session-tab[data-color="purple"] { border-left: 3px solid var(--session-purple); } .session-tab[data-color="pink"] { border-left: 3px solid var(--session-pink); } /* Active tabs with color get subtle background tint */ .session-tab.active[data-color="red"] { background: rgba(239, 68, 68, 0.15); border-color: rgba(239, 68, 68, 0.5); box-shadow: 0 0 10px rgba(239, 68, 68, 0.3); } .session-tab.active[data-color="orange"] { background: rgba(249, 115, 22, 0.15); border-color: rgba(249, 115, 22, 0.5); box-shadow: 0 0 10px rgba(249, 115, 22, 0.3); } .session-tab.active[data-color="yellow"] { background: rgba(234, 179, 8, 0.15); border-color: rgba(234, 179, 8, 0.5); box-shadow: 0 0 10px rgba(234, 179, 8, 0.3); } .session-tab.active[data-color="green"] { background: rgba(34, 197, 94, 0.15); border-color: rgba(34, 197, 94, 0.5); box-shadow: 0 0 10px rgba(34, 197, 94, 0.3); } .session-tab.active[data-color="blue"] { background: rgba(59, 130, 246, 0.15); border-color: rgba(59, 130, 246, 0.5); box-shadow: 0 0 10px rgba(59, 130, 246, 0.3); } .session-tab.active[data-color="purple"] { background: rgba(168, 85, 247, 0.15); border-color: rgba(168, 85, 247, 0.5); box-shadow: 0 0 10px rgba(168, 85, 247, 0.3); } .session-tab.active[data-color="pink"] { background: rgba(236, 72, 153, 0.15); border-color: rgba(236, 72, 153, 0.5); box-shadow: 0 0 10px rgba(236, 72, 153, 0.3); } /* Tab number indicator (Alt+N shortcut hint) */ .session-tab .tab-number { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 3px; background: rgba(255, 255, 255, 0.15); border: 1px solid rgba(255, 255, 255, 0.35); color: rgba(255, 255, 255, 0.75); font-size: 0.6rem; font-weight: 700; flex-shrink: 0; font-family: monospace; } .session-tab.active .tab-number { background: rgba(0, 255, 102, 0.2) !important; border-color: rgba(0, 255, 102, 0.5) !important; color: #00FF66 !important; } .session-tab .tab-info { display: flex; flex-direction: column; overflow: hidden; min-width: 0; } .session-tab .tab-name-row { display: flex; align-items: center; gap: 0.25rem; } .session-tab .tab-name { overflow: hidden; text-overflow: ellipsis; } .session-tab .tab-name-prefix { display: none; } html[data-tab-orientation='vertical'] .tab-rail .session-tab .tab-name { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; overflow-wrap: anywhere; white-space: normal; line-height: 1.25; } html[data-tab-orientation='vertical'] .tab-rail .session-tab .tab-name-prefix { display: inline; } html[data-tab-orientation='vertical'] .tab-rail .session-tab .tab-name.tab-name-renaming { display: flex; align-items: center; -webkit-box-orient: initial; -webkit-line-clamp: unset; line-clamp: unset; overflow: visible; } html[data-tab-orientation='vertical'] .tab-name-renaming .tab-rename-prefix { flex: 0 0 auto; } html[data-tab-orientation='vertical'] .tab-name-renaming .tab-rename-input { flex: 1 1 0; width: auto; min-width: 0; } /* Tab folder path — hidden by default, shown via .tabs-show-folder on container */ .session-tab .tab-folder { font-size: 0.6rem; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.2; display: none; } .tabs-show-folder .session-tab .tab-folder { display: block; } .session-tab .tab-close { opacity: 0; width: 0; padding: 0; font-size: 0.9rem; line-height: 1; color: var(--text-muted); overflow: hidden; cursor: default; border-radius: 3px; /* Performance: use transform for GPU acceleration */ transition: opacity 0.05s ease-out, width 0.05s ease-out, padding 0.05s ease-out; } /* Icons expand on the ACTIVE tab only (in flow): selection is a deliberate click, so the width change never happens while aiming at a tab, background tabs keep their full title on hover, and a stray click can only switch. Middle-click closes any tab (_setupTabMiddleClickClose in app.js). */ .session-tab.active .tab-close { opacity: 1; width: auto; padding: 0.15rem 0.35rem; } .session-tab .tab-close:hover { color: var(--red); background: rgba(255, 255, 255, 0.1); } .session-tab.new-tab { padding: 0.35rem 0.5rem; color: var(--text-muted); font-size: 1rem; font-weight: 300; } .session-tab.new-tab:hover { color: var(--green); } /* Tab alerts: a STEADY red/yellow base with a pulse breathing on top. ⚠ The original animation swung background AND border to transparent at its 0%/100% keyframes, so for roughly half of every cycle an alerted tab was indistinguishable from a normal one: a glance (or a screenshot, owner report 2026-08-15) read "no alert" while the home rail showed a steady NEEDS YOU. A pending permission is BLOCKING the agent, so the tab must look blocked at every instant; only the intensity is allowed to move. The status dot joins in (red/yellow, (0,4,0) so it outranks the skin block's (0,3,1) dot rules), mirroring the phone overview's red-row language. */ /* The alert paints on ::before, NEVER on the tab element: .session-tab.active forces background/border/box-shadow with !important, and !important beats even a running animation, so an element-level alert vanished the moment the tab was selected. The permission is still blocking while you look at it, so the red ring must survive selection and clear only on resolution (owner call 2026-08-15). Same convention as the entrance styles (see the tab-enter block). (0,3,x) via the strip parent on purpose: the non-OG skin block quiets decorative glows (`.tab-glow { box-shadow: none }` lands at (0,2,1)), and an alert halo is signal, not decor, so it must outrank that on every skin. The overlay paints above the tab's inline content (positioned vs flow), which is fine at these alphas and is exactly what keeps it visible over the active tab's opaque-ish background. */ .session-tabs .session-tab.tab-alert-action::before { content: ''; position: absolute; inset: -2px; border-radius: inherit; pointer-events: none; /* Explicit: .tab-enter::before (entrance animations) parks ::before at opacity 0 with fill-mode both, and an alerted tab that is also entering would otherwise inherit that and render an invisible alert. Our animation shorthand already displaces theirs at this specificity; the opacity must be pinned the same way. */ opacity: 1; border: 2px solid var(--red); background: rgba(239, 68, 68, 0.12); box-shadow: 0 0 8px rgba(239, 68, 68, 0.4); animation: tab-blink-red 2.5s ease-in-out infinite; } .session-tab.tab-alert-action .tab-status.idle, .session-tab.tab-alert-action .tab-status.busy, .session-tab.tab-alert-action .tab-status { background: var(--red); box-shadow: 0 0 6px rgba(239, 68, 68, 0.7); } .session-tabs .session-tab.tab-alert-idle::before { content: ''; position: absolute; inset: -2px; border-radius: inherit; pointer-events: none; opacity: 1; /* see the action variant above */ border: 2px solid var(--yellow); background: rgba(234, 179, 8, 0.1); box-shadow: 0 0 8px rgba(234, 179, 8, 0.35); animation: tab-blink-yellow 3.5s ease-in-out infinite; } .session-tab.tab-alert-idle .tab-status.idle, .session-tab.tab-alert-idle .tab-status.busy, .session-tab.tab-alert-idle .tab-status { background: var(--yellow); box-shadow: 0 0 6px rgba(234, 179, 8, 0.6); } @keyframes tab-blink-red { 0%, 100% { background: rgba(239, 68, 68, 0.12); box-shadow: 0 0 8px rgba(239, 68, 68, 0.4); } 50% { background: rgba(239, 68, 68, 0.3); box-shadow: 0 0 16px rgba(239, 68, 68, 0.75); } } @keyframes tab-blink-yellow { 0%, 100% { background: rgba(234, 179, 8, 0.1); box-shadow: 0 0 8px rgba(234, 179, 8, 0.35); } 50% { background: rgba(234, 179, 8, 0.24); box-shadow: 0 0 14px rgba(234, 179, 8, 0.65); } } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } } /* Tab drag-and-drop reordering */ .session-tab[draggable="true"] { cursor: grab; } .session-tab.dragging { opacity: 0.5; cursor: grabbing; } .session-tab.drag-over-left { box-shadow: -2px 0 0 0 var(--accent); } .session-tab.drag-over-right { box-shadow: 2px 0 0 0 var(--accent); } /* Header Right */ .header-right { display: flex; align-items: center; gap: 0.5rem; margin-left: auto; padding-left: 0.75rem; border-left: 1px solid var(--glass-border); } /* Connection Indicator */ .connection-indicator { display: flex; align-items: center; gap: 0.35rem; font-size: 0.7rem; color: var(--text-dim); font-family: 'SF Mono', Monaco, monospace; white-space: nowrap; } .connection-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; } .connection-dot.connected { background: var(--green); } .connection-dot.fallback { background: var(--yellow); box-shadow: 0 0 6px var(--yellow); } .connection-dot.offline { background: var(--red); box-shadow: 0 0 6px var(--red); } .connection-dot.reconnecting { background: var(--yellow); animation: connection-pulse 1.5s ease-in-out infinite; } .connection-dot.draining { background: var(--green); animation: connection-pulse 0.8s ease-in-out infinite; } @keyframes connection-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } } /* Tunnel Indicator */ .tunnel-indicator { display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; background: transparent; border: none; cursor: pointer; border-radius: var(--btn-radius); padding: 0; position: relative; transition: background var(--transition-smooth); } .tunnel-indicator:hover { background: rgba(255, 255, 255, 0.06); } .tunnel-dot { width: 9px; height: 9px; border-radius: 50%; background: #22c55e; box-shadow: 0 0 6px #22c55e, 0 0 12px rgba(34, 197, 94, 0.4); animation: tunnel-glow 2s ease-in-out infinite; } .tunnel-indicator.connecting .tunnel-dot { background: #f59e0b; box-shadow: 0 0 6px #f59e0b, 0 0 12px rgba(245, 158, 11, 0.4); animation: tunnel-glow-amber 1s ease-in-out infinite; } @keyframes tunnel-glow { 0%, 100% { box-shadow: 0 0 6px #22c55e, 0 0 12px rgba(34, 197, 94, 0.4); } 50% { box-shadow: 0 0 8px #22c55e, 0 0 18px rgba(34, 197, 94, 0.6); } } @keyframes tunnel-glow-amber { 0%, 100% { box-shadow: 0 0 6px #f59e0b, 0 0 12px rgba(245, 158, 11, 0.4); opacity: 1; } 50% { box-shadow: 0 0 4px #f59e0b; opacity: 0.4; } } /* Tunnel Panel Dropdown */ .tunnel-panel { position: fixed; top: 36px; right: 12px; width: 320px; background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); z-index: 1000; overflow: hidden; } .tunnel-panel-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 14px; border-bottom: 1px solid var(--border); } .tunnel-panel-header h3 { margin: 0; font-size: 13px; font-weight: 600; color: var(--text); display: flex; align-items: center; gap: 8px; } .tunnel-panel-status { font-size: 11px; font-weight: 400; color: #22c55e; } .tunnel-panel-status.offline { color: var(--text-muted); } .tunnel-panel-body { padding: 10px 14px; } .tunnel-panel-section { margin-bottom: 12px; } .tunnel-panel-section:last-child { margin-bottom: 0; } .tunnel-panel-label { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: 6px; } .tunnel-panel-url { font-family: 'SF Mono', Monaco, monospace; font-size: 11px; color: var(--accent-hover); word-break: break-all; cursor: pointer; padding: 6px 8px; background: var(--bg-input); border-radius: 4px; transition: background 0.15s; } .tunnel-panel-url:hover { background: var(--bg-hover); } .tunnel-panel-stat { display: flex; align-items: center; justify-content: space-between; padding: 4px 0; font-size: 12px; color: var(--text-dim); } .tunnel-panel-stat-value { color: var(--text); font-family: 'SF Mono', Monaco, monospace; font-size: 11px; } .tunnel-panel-session { display: flex; align-items: center; gap: 8px; padding: 6px 8px; background: var(--bg-input); border-radius: 4px; margin-bottom: 4px; font-size: 11px; color: var(--text-dim); } .tunnel-panel-session-dot { width: 6px; height: 6px; border-radius: 50%; background: #22c55e; flex-shrink: 0; } .tunnel-panel-session-info { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .tunnel-panel-session-method { font-size: 10px; padding: 1px 5px; border-radius: 3px; background: var(--bg-hover); color: var(--text-muted); text-transform: uppercase; flex-shrink: 0; } .tunnel-panel-actions { display: flex; gap: 6px; padding-top: 8px; border-top: 1px solid var(--border); } .tunnel-panel-btn { flex: 1; padding: 6px 0; font-size: 12px; font-weight: 500; border: 1px solid var(--border); border-radius: 5px; cursor: pointer; text-align: center; transition: all 0.15s; } .tunnel-panel-btn.btn-start { background: rgba(34, 197, 94, 0.1); border-color: rgba(34, 197, 94, 0.3); color: #22c55e; } .tunnel-panel-btn.btn-start:hover { background: rgba(34, 197, 94, 0.2); } .tunnel-panel-btn.btn-stop { background: rgba(239, 68, 68, 0.1); border-color: rgba(239, 68, 68, 0.3); color: #ef4444; } .tunnel-panel-btn.btn-stop:hover { background: rgba(239, 68, 68, 0.2); } .tunnel-panel-btn.btn-qr { background: var(--bg-input); color: var(--text-dim); } .tunnel-panel-btn.btn-qr:hover { background: var(--bg-hover); color: var(--text); } .tunnel-panel-btn.btn-revoke { background: rgba(239, 68, 68, 0.1); border-color: rgba(239, 68, 68, 0.3); color: #ef4444; font-size: 11px; } .tunnel-panel-btn.btn-revoke:hover { background: rgba(239, 68, 68, 0.2); } .tunnel-panel-empty { font-size: 11px; color: var(--text-muted); font-style: italic; padding: 4px 0; } .header-tokens { font-size: 0.75rem; font-weight: 500; color: var(--accent-hover); font-family: 'SF Mono', Monaco, monospace; } .header-system-stats { display: flex; gap: 0.75rem; font-size: 0.65rem; font-family: 'SF Mono', Monaco, monospace; color: var(--text-dim); padding: 0 0.5rem; } .stat-item { display: flex; align-items: center; gap: 0.35rem; } .stat-label { color: var(--text-muted); font-size: 0.6rem; text-transform: uppercase; min-width: 24px; } .stat-bar { width: 40px; height: 5px; background: rgba(255, 255, 255, 0.06); border-radius: 3px; overflow: hidden; } .stat-bar-fill { height: 100%; border-radius: 3px; transition: width 0.3s ease, background 0.3s ease; } .stat-bar-cpu { background: linear-gradient(90deg, var(--green) 0%, var(--green) 100%); } .stat-bar-cpu.medium { background: linear-gradient(90deg, var(--green) 0%, var(--yellow) 100%); } .stat-bar-cpu.high { background: linear-gradient(90deg, var(--yellow) 0%, var(--red) 100%); } .stat-bar-mem { background: linear-gradient(90deg, var(--accent) 0%, var(--accent-hover) 100%); } .stat-bar-mem.medium { background: linear-gradient(90deg, var(--accent) 0%, var(--yellow) 100%); } .stat-bar-mem.high { background: linear-gradient(90deg, var(--yellow) 0%, var(--red) 100%); } .stat-value { min-width: 32px; text-align: right; color: var(--text-dim); } .stat-value.high { color: var(--red); font-weight: 500; } .btn-icon-header { display: flex; align-items: center; justify-content: center; width: 26px; height: 26px; background: transparent; border: none; color: var(--text-muted); font-size: 1rem; cursor: pointer; border-radius: var(--btn-radius); transition: all var(--transition-smooth); } .btn-icon-header:hover { background: rgba(255, 255, 255, 0.07); color: var(--text); transform: rotate(45deg); } /* Admin Panel header button: only shown to admins in multi-user mode (marker class removed by admin-ui.js after identity boot). Deliberately BIG and prominent: it is the entry point to user/permission management. */ .btn-admin-panel { display: inline-flex; align-items: center; gap: 6px; padding: 5px 14px; background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%); color: var(--accent-ink); border: none; border-radius: var(--btn-radius); font-size: 0.85rem; font-weight: 700; letter-spacing: 0.02em; cursor: pointer; box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 45%, transparent); transition: all var(--transition-smooth); } .btn-admin-panel:hover { filter: brightness(1.1); box-shadow: 0 0 16px color-mix(in srgb, var(--accent) 65%, transparent); } .btn-admin-panel--hidden { display: none !important; } /* Admin user tables (Admin Panel modal + the App Settings Users tab). */ .admin-users th, .admin-users td { padding: 5px 12px 5px 0; vertical-align: top; border-bottom: 1px solid color-mix(in srgb, var(--text-muted) 20%, transparent); } /* Multi-monitor header button: hidden by default (opt-in via App Settings → Display → "Header Displays"). The server strips this class at render when the setting is on; the client toggles it live on save. */ .btn-multimonitor--hidden { display: none !important; } /* Redraw terminal header button: hidden by default (opt-in via App Settings → Display → "Header Displays"). Pure client-side toggle. */ .btn-redraw-terminal--hidden { display: none !important; } .btn-icon-header.btn-settings { width: 30px; height: 30px; margin-left: 0; } .btn-icon-header.btn-sm { width: 20px; height: 20px; font-size: 0.7rem; font-weight: 600; } .btn-icon-header.btn-sm:hover { transform: none; } .header-font-controls { display: flex; align-items: center; gap: 0.25rem; padding: 0.15rem 0.4rem; background: rgba(255, 255, 255, 0.04); border: 1px solid rgba(255, 255, 255, 0.04); border-radius: var(--btn-radius); } .header-font-controls .font-size-display { font-size: 0.7rem; color: var(--text-muted); min-width: 18px; text-align: center; font-family: 'SF Mono', Monaco, monospace; } /* Session tab gear icon */ .session-tab .tab-gear { opacity: 0; width: 0; padding: 0; font-size: 1rem; line-height: 1; color: var(--text-muted); cursor: pointer; overflow: hidden; transition: opacity 0.15s, width 0.15s, padding 0.15s, transform 0.2s; } .session-tab.active .tab-gear { opacity: 1; width: auto; padding: 0 0.3rem; } .session-tab .tab-gear:hover { color: var(--accent-hover); transform: rotate(45deg); } /* ===== Session detach / undock (beta/session-detach) ===================== */ /* Pop-out (detach) icon on each tab — mirrors .tab-gear reveal-on-hover. */ .session-tab .tab-detach { opacity: 0; width: 0; padding: 0; font-size: 0.95rem; line-height: 1; color: var(--text-muted); cursor: pointer; overflow: hidden; transition: opacity 0.15s, width 0.15s, padding 0.15s; } .session-tab.active .tab-detach { opacity: 1; width: auto; padding: 0 0.3rem; } .session-tab .tab-detach:hover { color: var(--accent-hover); background: rgba(255, 255, 255, 0.1); border-radius: 3px; } /* A tab whose session is popped out into its own window. */ .session-tab.detached { opacity: 0.72; } .session-tab.detached .tab-detach { /* Keep the pop-out icon visible while detached as a re-focus affordance. */ opacity: 1; width: auto; padding: 0 0.3rem; color: var(--accent-hover); } .session-tab .tab-detached-badge { display: none; align-items: center; font-size: 0.55rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; padding: 1px 4px; margin-left: 4px; border-radius: 3px; background: rgba(96, 165, 250, 0.18); color: var(--accent-hover); } .session-tab.detached .tab-detached-badge { display: inline-flex; } /* ===== Tab action icons: active tab only ================================= All three per-tab icons live in a .tab-actions wrapper (in flow; it adds no width of its own while the children keep the width:0 collapse above). They expand only on the ACTIVE tab: selection is a deliberate click, so the tab-strip geometry never shifts while the pointer is aiming, hovering a background tab changes nothing (full title stays readable), and a stray click can only switch sessions. Middle-click closes any tab. The phone layout in mobile.css follows the same active-only pattern with its own sizing; the tablet touch fallback there keeps icons always visible. */ .session-tab .tab-actions { display: flex; align-items: center; } .session-tab .tab-more { display: none; align-items: center; justify-content: center; width: 1.75rem; height: 1.5rem; padding: 0; visibility: hidden; pointer-events: none; border: 0; border-radius: 4px; background: transparent; color: var(--text-muted); font: 700 1rem/1 monospace; cursor: pointer; } html[data-session-list='sidebar'][data-sidebar='expanded'] .session-sidebar .tab-name-row > .tab-actions > .tab-more, html[data-tab-orientation='vertical']:not(.tab-rail-compact) .tab-rail .tab-name-row > .tab-actions > .tab-more, html[data-tab-orientation='vertical'].tab-rail-compact .session-tab.active > .tab-actions > .tab-more { display: inline-flex; } .session-tab:hover > .tab-actions > .tab-more, .session-tab:focus-within > .tab-actions > .tab-more, .session-tab.active > .tab-actions > .tab-more, html[data-session-list='sidebar'][data-sidebar='expanded'] .session-sidebar :is(.session-tab:hover, .session-tab:focus-within, .session-tab.active) .tab-name-row > .tab-actions > .tab-more, html[data-tab-orientation='vertical']:not(.tab-rail-compact) .tab-rail :is(.session-tab:hover, .session-tab:focus-within, .session-tab.active) .tab-name-row > .tab-actions > .tab-more { visibility: visible; pointer-events: auto; } html[data-tab-orientation='vertical'].tab-rail-compact .session-tab .tab-actions > :is(.tab-gear, .tab-detach, .tab-close) { display: none; } @media (pointer: coarse) { .session-tab > .tab-actions > .tab-more, html[data-session-list='sidebar'][data-sidebar='expanded'] .session-sidebar .session-tab .tab-name-row > .tab-actions > .tab-more, html[data-tab-orientation='vertical']:not(.tab-rail-compact) .tab-rail .session-tab .tab-name-row > .tab-actions > .tab-more { visibility: visible; pointer-events: auto; } } .tab-rail-action-menu { position: fixed; z-index: 2000; display: grid; min-width: 180px; padding: 0.3rem; border: 1px solid var(--glass-border); border-radius: var(--btn-radius); background: var(--floating-bg); box-shadow: var(--elevated-shadow); } .tab-rail-action-menu button { padding: 0.45rem 0.6rem; border: 0; border-radius: 4px; background: transparent; color: var(--text); text-align: left; cursor: pointer; } .tab-rail-action-menu button:hover, .tab-rail-action-menu button:focus-visible { background: var(--control-bg-hover); outline: 2px solid var(--accent); outline-offset: -2px; } .tab-rail-action-menu .danger { color: var(--red); } :is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-range-field { display: grid; grid-template-columns: minmax(112px, 1fr) 44px; align-items: center; gap: 8px; width: min(220px, 44vw); } :is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-range-field input { width: 100%; accent-color: var(--accent); } :is(#appSettingsModal, #sessionOptionsModal, #createCaseModal) .set-range-field output { color: var(--text); font-family: var(--font-mono); font-size: 0.7rem; font-variant-numeric: tabular-nums; text-align: right; } /* Pop-out button is opt-in (App Settings → Tab Bar, default off; per-device). settings-ui.js mirrors the setting as the tabs-show-detach class on . A tab that is ALREADY detached keeps its icon regardless: it is the re-focus affordance for the popped-out window. A SINGLE tab can also opt in via Session Options → Session (`tab-show-detach` on the tab, per-device map in session-ui.js) while the general toggle stays off; the active-tab reveal rules above are shared, so the overridden tab behaves identically. Phones are unaffected either way: mobile.css hides .tab-detach with !important. */ html:not(.tabs-show-detach) .session-tab:not(.detached):not(.tab-show-detach) .tab-detach { display: none; } /* ===== Solo (detached single-session) window chrome ===================== */ body.solo-mode .session-tabs, body.solo-mode .session-tabs-host, body.solo-mode .session-sidebar, body.solo-mode .btn-sidebar-toggle, body.solo-mode .header-system-stats, body.solo-mode .header-tokens, body.solo-mode .btn-notifications, body.solo-mode .btn-multimonitor, body.solo-mode .header-plan-usage, body.solo-mode .btn-lifecycle-log { display: none !important; } .solo-session-title { flex: 1; min-width: 0; font-weight: 600; font-size: 13px; color: var(--text); padding: 0 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* "Session unavailable" overlay for a solo window whose session has ended. */ .solo-gone-overlay { position: fixed; inset: 0; z-index: 5000; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px; background: rgba(9, 9, 11, 0.92); color: var(--text); text-align: center; padding: 24px; } .solo-gone-overlay h2 { margin: 0; font-size: 18px; } .solo-gone-overlay p { margin: 0; color: var(--text-muted); font-size: 13px; } /* Mode indicator on session tab */ .session-tab .tab-mode { font-size: 0.6rem; padding: 1px 4px; border-radius: 3px; background: var(--bg-hover); color: var(--text-muted); text-transform: uppercase; } .session-tab .tab-mode.shell { background: rgba(234, 179, 8, 0.2); color: var(--yellow); } .session-tab .tab-mode.opencode { background: rgba(16, 185, 129, 0.2); color: #10b981; } .session-tab .tab-mode.codex { background: rgba(168, 85, 247, 0.2); color: #a855f7; } .session-tab .tab-mode.gemini { background: rgba(138, 180, 248, 0.2); color: #8ab4f8; } .session-tab .tab-mode.antigravity { background: rgba(34, 211, 238, 0.2); color: #22d3ee; } .session-tab .tab-mode.omp { background: rgba(129, 140, 248, 0.2); color: #818cf8; } .session-tab .tab-mode.pi { background: rgba(244, 114, 182, 0.2); color: #f472b6; } .session-tab .tab-mode.grok { background: rgba(212, 212, 216, 0.18); color: #d4d4d8; } /* DeepSeek: the vendor's own brand blue. Deliberately NOT added to the light-skin `--accent-d` override list above (which rescues gemini/antigravity/ pi/grok, whose pastels wash out on paper backgrounds) — this indigo already carries enough contrast on the light skins, and overriding it would throw away the one cue that separates a dsh tab from its neighbours. */ .session-tab .tab-mode.deepseek { background: rgba(77, 107, 254, 0.18); color: #7c93ff; } /* Timer Banner - Compact */ .timer-banner { display: flex; align-items: center; gap: 1rem; padding: 0.4rem 1rem; background: linear-gradient(90deg, rgba(30, 58, 95, 0.8), rgba(21, 34, 56, 0.8)); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); border-bottom: 1px solid rgba(59, 130, 246, 0.2); flex-shrink: 0; } .timer-content { display: flex; align-items: center; gap: 1rem; flex: 1; } .timer-value { font-size: 1rem; font-weight: 600; font-family: 'SF Mono', Monaco, monospace; color: var(--accent-hover); } .timer-progress { flex: 1; height: 4px; background: rgba(0, 0, 0, 0.3); border-radius: 2px; max-width: 200px; } .timer-progress-fill { height: 100%; background: linear-gradient(90deg, #3b82f6, #60a5fa); border-radius: 2px; transition: width 1s linear; } .timer-meta { font-size: 0.7rem; color: var(--text-dim); } /* Respawn Banner - Compact 2-column layout */ .respawn-banner { display: flex; padding: 0.25rem 0.75rem; background: linear-gradient(90deg, rgba(30, 58, 30, 0.8), rgba(19, 37, 19, 0.8)); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); border-bottom: 1px solid rgba(34, 197, 94, 0.2); font-size: 0.7rem; flex-shrink: 0; } .respawn-compact-layout { display: flex; width: 100%; gap: 1rem; } .respawn-status-col { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; flex-shrink: 0; } .respawn-status-row1 { display: flex; align-items: center; gap: 0.5rem; } .respawn-status-row2 { display: flex; align-items: center; gap: 0.5rem; min-height: 1.2em; font-size: 0.65rem; color: var(--text-dim); } .respawn-status-row2:empty { display: none; } .respawn-countdown-timers { display: flex; gap: 0.3rem; flex-wrap: wrap; } .respawn-countdown-timer { display: flex; align-items: center; gap: 0.25rem; background: rgba(0, 0, 0, 0.35); padding: 0.1rem 0.35rem; border-radius: 3px; font-family: 'SF Mono', Monaco, monospace; font-size: 0.6rem; } .respawn-countdown-timer .timer-name { color: var(--text-dim); } .respawn-countdown-timer .timer-value { color: var(--green); font-weight: 500; min-width: 2em; text-align: right; } .respawn-timer-bar { width: 30px; height: 2px; background: rgba(255, 255, 255, 0.15); border-radius: 1px; overflow: hidden; } .respawn-timer-progress { height: 100%; background: var(--green); transition: width 0.1s linear; } .respawn-action-log { flex: 1; max-height: 3.2em; overflow-y: auto; font-size: 0.65rem; font-family: 'SF Mono', Monaco, monospace; color: var(--text-dim); border-left: 1px solid rgba(34, 197, 94, 0.2); padding-left: 0.5rem; } .respawn-action-log::-webkit-scrollbar { width: 3px; } .respawn-action-log::-webkit-scrollbar-thumb { background: rgba(34, 197, 94, 0.3); border-radius: 2px; } .respawn-action-entry { padding: 0.05rem 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .respawn-action-entry .action-time { color: var(--text-muted); margin-right: 0.3rem; } .respawn-action-entry .action-type { color: var(--green); margin-right: 0.3rem; } .respawn-action-entry .action-detail { color: var(--text-dim); } /* Command entries are highlighted */ .respawn-action-entry.action-command { color: var(--accent); } .respawn-action-entry.action-command .action-type { color: var(--accent); font-weight: 600; } .respawn-action-entry.action-command .action-detail { color: var(--text); font-weight: 500; } .respawn-indicator { color: var(--green); animation: spin 2s linear infinite; font-size: 0.8rem; } .respawn-state { color: #fff; font-weight: 600; background: rgba(34, 197, 94, 0.3); padding: 0.1rem 0.4rem; border-radius: 3px; font-size: 0.7rem; } .respawn-state.respawn-blocked { background: rgba(244, 67, 54, 0.3); color: #f44336; animation: respawn-blocked-pulse 1.5s ease-in-out infinite; } @keyframes respawn-blocked-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } } /* Respawn spacer removed - using flex layout */ .respawn-cycles { color: var(--text-muted); font-family: 'SF Mono', Monaco, monospace; font-size: 0.65rem; } .respawn-timer { color: var(--accent-hover); font-family: 'SF Mono', Monaco, monospace; font-weight: 500; font-size: 0.65rem; padding: 0 0.4rem; border-left: 1px solid rgba(34, 197, 94, 0.3); } .respawn-tokens { color: var(--text-dim); font-family: 'SF Mono', Monaco, monospace; font-size: 0.65rem; } .detection-status { color: var(--text-dim); font-size: 0.65rem; } .detection-ai-check { font-size: 0.65rem; padding: 0.05rem 0.3rem; border-radius: 2px; background: rgba(255, 255, 255, 0.1); } .detection-ai-check.ai-checking { color: var(--accent); background: rgba(59, 130, 246, 0.15); } .detection-ai-check.ai-idle { color: var(--green); background: rgba(34, 197, 94, 0.15); } .detection-ai-check.ai-working { color: var(--yellow); background: rgba(234, 179, 8, 0.15); } .detection-ai-check.ai-disabled { color: var(--text-muted); background: rgba(255, 255, 255, 0.05); } .detection-confidence { color: var(--text-dim); font-family: 'SF Mono', Monaco, monospace; font-size: 0.6rem; padding: 0.05rem 0.25rem; background: rgba(34, 197, 94, 0.15); border-radius: 2px; } .detection-confidence.high { background: rgba(34, 197, 94, 0.3); color: var(--green); } .detection-confidence.medium { background: rgba(234, 179, 8, 0.2); color: var(--yellow); } .detection-confidence.hook-confirmed { background: rgba(139, 92, 246, 0.3); color: var(--purple, #a78bfa); } .detection-hook { font-size: 0.65rem; padding: 0.05rem 0.3rem; border-radius: 2px; background: rgba(139, 92, 246, 0.15); color: var(--purple, #a78bfa); } .detection-hook.hook-active { background: rgba(139, 92, 246, 0.25); color: var(--purple, #a78bfa); font-weight: 500; } @keyframes pulse-hook { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } } @keyframes spin { to { transform: rotate(360deg); } } /* ===== Enhanced Ralph Wiggum Loop Panel ===== */ .ralph-panel { background: var(--glass-bg); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); border-bottom: 1px solid var(--glass-border); font-size: 0.75rem; flex-shrink: 0; overflow: hidden; position: relative; overflow-anchor: none; /* Prevent scroll anchoring when expanding/collapsing */ /* Performance: isolate paint and layout operations */ contain: layout style paint; } /* Mini Progress Ring (in summary) */ .ralph-ring-mini { position: relative; width: 32px; height: 32px; flex-shrink: 0; } .ralph-ring-svg { width: 100%; height: 100%; transform: rotate(-90deg); } .ralph-ring-bg { fill: none; stroke: var(--bg-input); stroke-width: 3; } .ralph-ring-progress { fill: none; stroke: url(#ralphGradient); stroke-width: 3; stroke-linecap: round; stroke-dasharray: 100; stroke-dashoffset: 100; transition: stroke-dashoffset 0.5s ease-out; } .ralph-ring-progress-mini { fill: none; stroke: url(#ralphGradientMini); stroke-width: 3; stroke-linecap: round; stroke-dasharray: 100; stroke-dashoffset: 100; transition: stroke-dashoffset 0.5s ease-out; } .ralph-ring-text { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 0.4rem; font-weight: 700; background: linear-gradient(135deg, #3b82f6, #22c55e); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; line-height: 1; text-align: center; } /* Summary Info */ .ralph-summary-info { flex: 1; display: flex; align-items: center; gap: 0.75rem; min-width: 0; } .ralph-title { font-weight: 600; color: var(--text); font-size: 0.8rem; white-space: nowrap; } .ralph-status-badge { display: flex; align-items: center; gap: 0.35rem; padding: 0.2rem 0.5rem; border-radius: 4px; background: var(--bg-input); font-size: 0.7rem; font-weight: 500; } .ralph-status-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--text-muted); transition: background 0.2s; } .ralph-status-badge.active .ralph-status-dot { background: var(--accent); animation: ralph-pulse 1.5s ease-in-out infinite; } .ralph-status-badge.completed .ralph-status-dot { background: var(--green); } .ralph-status-badge.tracking .ralph-status-dot { background: var(--yellow); } @keyframes ralph-pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.6; transform: scale(1.2); } } /* Circuit Breaker Badge */ .ralph-circuit-breaker { display: flex; align-items: center; gap: 0.25rem; padding: 0.2rem 0.5rem; border-radius: 4px; font-size: 0.7rem; font-weight: 600; cursor: pointer; transition: all 0.2s; margin-left: 0.5rem; } .ralph-circuit-breaker.half-open { background: rgba(255, 193, 7, 0.2); color: #ffc107; border: 1px solid rgba(255, 193, 7, 0.4); } .ralph-circuit-breaker.half-open:hover { background: rgba(255, 193, 7, 0.3); } .ralph-circuit-breaker.open { background: rgba(244, 67, 54, 0.2); color: #f44336; border: 1px solid rgba(244, 67, 54, 0.4); animation: circuit-breaker-pulse 1s ease-in-out infinite; } .ralph-circuit-breaker.open:hover { background: rgba(244, 67, 54, 0.3); } .ralph-circuit-breaker .cb-icon { font-size: 0.8rem; } .ralph-circuit-breaker .cb-text { font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.5px; } @keyframes circuit-breaker-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } } /* RALPH_STATUS Block Display */ .ralph-status-block { background: var(--bg-input); border-radius: 4px; padding: 0.5rem 0.75rem; margin-top: 0.5rem; font-size: 0.75rem; } .ralph-status-block-header { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.4rem; font-weight: 600; color: var(--text-bright); } .ralph-status-block-status { padding: 0.1rem 0.4rem; border-radius: 3px; font-size: 0.65rem; font-weight: 600; text-transform: uppercase; } .ralph-status-block-status.in-progress { background: rgba(33, 150, 243, 0.2); color: #2196f3; } .ralph-status-block-status.complete { background: rgba(76, 175, 80, 0.2); color: #4caf50; } .ralph-status-block-status.blocked { background: rgba(244, 67, 54, 0.2); color: #f44336; } .ralph-status-block-stats { display: flex; gap: 1rem; color: var(--text-dim); } .ralph-status-block-stats span { display: flex; align-items: center; gap: 0.25rem; } .ralph-status-block-recommendation { margin-top: 0.4rem; padding-top: 0.4rem; border-top: 1px solid var(--border); color: var(--text-muted); font-style: italic; } /* Summary Stats */ .ralph-summary-stats { display: flex; align-items: center; gap: 0.75rem; } .ralph-stat { display: flex; align-items: center; gap: 0.25rem; color: var(--text-dim); font-size: 0.7rem; } .ralph-stat-icon { font-size: 0.75rem; opacity: 0.7; } .ralph-toggle { color: var(--text-dim); font-size: 0.6rem; flex-shrink: 0; } /* Hide detail when collapsed */ .ralph-panel.collapsed .ralph-detail { display: none; } /* Expanded Detail View - Compact horizontal layout */ .ralph-detail { padding: 0.5rem 0.75rem; border-top: 1px solid var(--border); background: var(--bg-dark); } .ralph-detail-row { display: flex; align-items: center; gap: 1rem; } /* Compact Progress Ring */ .ralph-ring-compact { position: relative; width: 75px; height: 75px; flex-shrink: 0; } .ralph-ring-compact .ralph-ring-percent { font-size: 0.95rem; font-weight: 600; } .ralph-ring-compact .ralph-ring-label { font-size: 0.55rem; } /* Unified Info + Tasks Section */ .ralph-info-unified { flex: 1; display: flex; align-items: flex-start; gap: 0.75rem; overflow: hidden; min-width: 0; background: var(--bg-input); padding: 0.4rem 0.6rem; border-radius: 4px; } .ralph-info-labels { display: grid; grid-template-columns: auto auto; gap: 0.15rem 0.5rem; flex-shrink: 0; align-items: center; } .ralph-info-row { display: contents; } .ralph-info-label { font-size: 0.5rem; white-space: nowrap; text-transform: uppercase; letter-spacing: 0.3px; color: var(--text-muted); text-align: right; } .ralph-info-unified code { font-size: 0.7rem; font-weight: 500; padding: 0; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; background: transparent; color: var(--text); } .ralph-info-unified span:not(.ralph-info-label) { font-size: 0.7rem; font-weight: 500; color: var(--text); } .ralph-info-unified .ralph-tasks-count { font-size: 0.7rem; font-weight: 500; color: var(--text); } .ralph-info-unified .ralph-tasks-grid { flex: 1; display: flex; flex-wrap: wrap; gap: 0.2rem; padding: 0; max-height: 60px; overflow-y: auto; align-content: flex-start; } .ralph-tasks-compact .ralph-task-card { padding: 0.2rem 0.4rem; font-size: 0.65rem; border-left-width: 2px; } .ralph-tasks-compact .ralph-task-icon { font-size: 0.7rem; } .ralph-tasks-compact .ralph-task-content { max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* Large Progress Ring */ .ralph-ring-svg-large { width: 100%; height: 100%; transform: rotate(-90deg); } .ralph-ring-track { fill: none; stroke: var(--bg-input); stroke-width: 6; } .ralph-ring-fill { fill: none; stroke: url(#ralphGradient); stroke-width: 6; stroke-linecap: round; stroke-dasharray: 264; stroke-dashoffset: 264; transition: stroke-dashoffset 0.5s ease-out; } .ralph-ring-center { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; } .ralph-ring-percent { font-size: 1.25rem; font-weight: 700; color: var(--text); line-height: 1; } .ralph-ring-label { font-size: 0.6rem; color: var(--text-dim); margin-top: 0.15rem; } .ralph-tasks-count { color: var(--text-dim); font-weight: 400; } .ralph-tasks-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 0.5rem; padding: 0.5rem; max-height: 200px; overflow-y: auto; } /* Task Cards */ .ralph-task-card { display: flex; align-items: flex-start; gap: 0.5rem; padding: 0.5rem; background: var(--bg-dark); border-radius: 4px; border-left: 2px solid var(--text-muted); transition: border-color 0.2s, background 0.2s; position: relative; } .ralph-task-card.task-pending { border-left-color: var(--text-muted); } .ralph-task-card.task-in-progress { border-left-color: var(--accent); background: rgba(59, 130, 246, 0.05); } .ralph-task-card.task-completed { border-left-color: var(--green); opacity: 0.75; } .ralph-task-icon { flex-shrink: 0; width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; font-size: 0.7rem; border-radius: 50%; } .ralph-task-card.task-pending .ralph-task-icon { color: var(--text-muted); } .ralph-task-card.task-in-progress .ralph-task-icon { color: var(--accent); animation: ralph-spin 2s linear infinite; } .ralph-task-card.task-completed .ralph-task-icon { color: var(--green); } @keyframes ralph-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .ralph-task-content { flex: 1; font-size: 0.7rem; color: var(--text-dim); line-height: 1.3; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } .ralph-task-card.task-completed .ralph-task-content { color: var(--text-dim); } /* Priority badges */ .ralph-task-priority { font-size: 0.55rem; font-weight: 700; padding: 0.1rem 0.25rem; border-radius: 2px; flex-shrink: 0; text-transform: uppercase; } .ralph-task-priority.priority-p0 { background: rgba(244, 67, 54, 0.3); color: #f44336; } .ralph-task-priority.priority-p1 { background: rgba(255, 193, 7, 0.3); color: #ffc107; } .ralph-task-priority.priority-p2 { background: rgba(33, 150, 243, 0.2); color: #2196f3; } /* Priority-based card highlighting */ .ralph-task-card.task-priority-p0 { border-left-color: #f44336; } .ralph-task-card.task-priority-p1:not(.task-completed) { border-left-color: #ffc107; } /* Empty state */ .ralph-state-empty { color: var(--text-muted); font-style: italic; padding: 0.5rem; } /* Ralph panel summary layout adjustments */ .ralph-summary { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0.75rem; user-select: none; transition: background 0.15s; } .ralph-summary-content { display: flex; align-items: center; gap: 0.75rem; flex: 1; cursor: pointer; } .ralph-summary-content:hover { background: var(--bg-hover); } .ralph-controls { display: flex; gap: 0.25rem; flex-shrink: 0; } /* Ralph Menu Dropdown */ .ralph-menu-container { position: relative; } .ralph-dropdown { display: none; position: absolute; top: 100%; right: 0; background: var(--floating-bg); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border: 1px solid var(--control-border); border-radius: 10px; min-width: 170px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4), 0 2px 8px rgba(0, 0, 0, 0.2); z-index: 1000; padding: 4px; } .ralph-dropdown.show { display: block; } .ralph-dropdown button { display: block; width: 100%; padding: 0.5rem 0.75rem; border: none; background: transparent; color: var(--text); text-align: left; font-size: 0.75rem; cursor: pointer; border-radius: var(--btn-radius); transition: background var(--transition-smooth); } .ralph-dropdown button:hover { background: rgba(255, 255, 255, 0.07); } .ralph-dropdown hr { margin: 0.25rem 4px; border: none; border-top: 1px solid rgba(255, 255, 255, 0.06); } /* Detached Ralph Panel */ .ralph-panel.detached { position: fixed; top: 80px; left: 50px; width: 600px; height: 400px; min-width: 350px; min-height: 200px; max-width: 90vw; max-height: 80vh; z-index: 1000; border: 1px solid var(--control-border); border-radius: 12px; box-shadow: var(--elevated-shadow); background: var(--floating-bg); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); overflow: hidden; resize: both; display: flex; flex-direction: column; } .ralph-panel.detached .ralph-summary { cursor: move; background: linear-gradient(180deg, var(--bg-hover) 0%, var(--bg-card) 100%); border-bottom: 1px solid var(--border); border-radius: 8px 8px 0 0; flex-shrink: 0; } .ralph-panel.detached .ralph-summary-content { cursor: move; } .ralph-panel.detached .ralph-summary-content:hover { background: transparent; } .ralph-panel.detached .ralph-detail { flex: 1; max-height: none; overflow: auto; display: flex; flex-direction: column; } .ralph-panel.detached .ralph-detail-row { flex: 1; overflow: auto; } .ralph-panel.detached .ralph-info-unified { flex-direction: column; flex: 1; overflow: auto; } .ralph-panel.detached .ralph-info-unified .ralph-tasks-grid { max-height: none; flex: 1; } .ralph-panel.detached.collapsed { min-height: auto; height: auto; } /* Main - Terminal Area */ .main { flex: 1; display: flex; overflow: hidden; background: var(--bg-dark); position: relative; } .terminal-wrap { flex: 1; display: flex; flex-direction: column; overflow: hidden; /* Anchor for the partial-history banner, which overlays rather than stacks. */ position: relative; } /* Partial-history banner (#258). OVERLAY, not a flex child, on purpose: FitAddon derives rows/cols from the terminal parent's computed height, so a banner that occupied real layout space would SIGWINCH the CLI every time truncation state changed and make Ink repaint the world. Floating it costs a few covered rows at the top, which the dismiss button releases. */ .history-trunc-bar { position: absolute; top: 0; left: 0; right: 0; z-index: 6; /* under the local-echo overlay (7), over terminal content */ display: flex; align-items: center; gap: 10px; padding: 7px 10px; font-size: 12px; line-height: 1.35; color: var(--text-dim); background: var(--bg-card); border-bottom: 1px solid var(--border); box-shadow: 0 2px 8px rgb(0 0 0 / 22%); } /* `.history-trunc-bar` sets display:flex, which outranks the hidden attribute's UA display:none — without this the banner can never be hidden. */ .history-trunc-bar[hidden] { display: none; } .history-trunc-text { flex: 1; min-width: 0; } .history-trunc-load { flex: none; padding: 4px 10px; font-size: 12px; font-family: inherit; color: var(--text); background: var(--bg-hover); border: 1px solid var(--border); border-radius: 5px; cursor: pointer; } .history-trunc-load:hover:not(:disabled) { background: var(--border-light); } .history-trunc-load:disabled { opacity: 0.6; cursor: default; } .history-trunc-dismiss { flex: none; width: 22px; height: 22px; padding: 0; font-size: 15px; line-height: 1; color: var(--text-muted); background: none; border: none; border-radius: 4px; cursor: pointer; } .history-trunc-dismiss:hover { color: var(--text); background: var(--bg-hover); } .terminal-container { flex: 1; background: var(--term-bg, #161b23); overflow: hidden; position: relative; /* Performance: isolate layout and paint operations. Do NOT use contain:strict — size containment breaks xterm.js FitAddon dimension calculations. */ contain: layout paint; /* Performance: promote to own compositor layer for isolation from header/toolbar/panel repaints. Eliminates compositing stalls when surrounding UI elements update during typing. */ will-change: contents; } .terminal-container .xterm { width: 100%; min-width: 0; height: 100%; padding: 0; } .terminal-container .xterm-viewport { background: transparent !important; } /* Touch devices: prevent browser from claiming the touch gesture before our JS touchmove handler fires. Without this, the browser starts native scrolling during the first few px of finger travel and ignores our subsequent preventDefault() — breaking custom terminal scroll. */ .touch-device .terminal-container, .touch-device .terminal-container .xterm, .touch-device .terminal-container .xterm-viewport, .touch-device .terminal-container .xterm-screen { touch-action: none; } /* Mobile terminal taps are cursor-positioning gestures, not text-selection gestures. xterm re-enables selectable DOM text in its accessibility tree, so force the whole terminal subtree back to non-selectable on touch devices. Synthetic mouse events for tap-to-position still dispatch normally through JavaScript. */ body.touch-device .terminal-container .xterm, body.touch-device .terminal-container .xterm .xterm-viewport, body.touch-device .terminal-container .xterm .xterm-screen, body.touch-device .terminal-container .xterm .xterm-rows, body.touch-device .terminal-container .xterm .xterm-accessibility-tree, body.touch-device .terminal-container .xterm .xterm-accessibility-tree *, body.touch-device .terminal-container .xterm .xterm-selection, body.touch-device .terminal-container .xterm .xterm-helper-textarea { user-select: none !important; -webkit-user-select: none !important; -webkit-touch-callout: none !important; } /* Touch text-selection bar (long-press → select → Copy). Lives in styles.css, NOT mobile.css: the gesture is touch-driven, not width-driven, and mobile.css is media-gated to ≤1023px — a touch tablet in landscape would get the gesture with no bar to copy from. Built in JS (index.html is read once at server start, so markup added there would need a restart to appear). z-index 900 sits above terminal content and the local-echo overlay (7) and deliberately BELOW floating agent windows (1000), so it can never cover their controls. */ .term-select-bar { position: absolute; z-index: 900; display: none; gap: 2px; padding: 3px; background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45); } .term-select-bar.visible { display: flex; } .term-select-btn { min-height: 38px; min-width: 46px; padding: 0 0.7rem; border: none; border-radius: 6px; background: transparent; color: var(--text); font-family: inherit; font-size: 0.82rem; font-weight: 600; cursor: pointer; /* The bar is the one place in the terminal subtree a tap must land on a control rather than a cell, so it opts out of the gesture styles above. */ touch-action: manipulation; } .term-select-btn:active { background: var(--bg-hover); } .term-select-btn--close { min-width: 38px; padding: 0; color: var(--text-muted); } /* Welcome Overlay */ .welcome-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; align-items: center; justify-content: center; background: var(--bg-dark); z-index: 10; opacity: 0; pointer-events: none; transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .welcome-overlay.visible { opacity: 1; pointer-events: auto; } /* The overlay fills
and never scrolls, so the content has to fit INSIDE it rather than run off both ends: on a 1470x840 window the title sat above the top edge and "Or click Run to start" below the bottom one, with no way to reach either. Bounding the column at the overlay height and letting the one genuinely elastic block absorb the difference (the Resume list, which already scrolls internally) keeps the whole screen visible at any window height. `overflow-y: auto` is the backstop for a window too short even for that. */ .welcome-content { display: flex; flex-direction: column; box-sizing: border-box; max-height: 100%; overflow-y: auto; text-align: center; max-width: 520px; padding: 2rem; } /* Only the history block gives ground; the title, buttons and hint keep their natural height, so nothing above or below the list ever gets squeezed. */ .welcome-content > * { flex-shrink: 0; } .welcome-title { font-size: 3.5rem; font-weight: 800; background: linear-gradient(135deg, #60a5fa 0%, #93c5fd 50%, #60a5fa 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin-bottom: 1rem; letter-spacing: -0.04em; } .welcome-desc { color: var(--text-dim); font-size: 0.9rem; line-height: 1.6; margin-bottom: 0.5rem; } .welcome-actions { display: flex; gap: 0.75rem; justify-content: center; flex-wrap: wrap; margin-top: 2rem; margin-bottom: 1.5rem; } .welcome-btn { display: flex; align-items: center; gap: 0.5rem; padding: 0.7rem 1.3rem; font-size: 0.85rem; font-weight: 600; border-radius: 10px; border: 1px solid transparent; cursor: pointer; transition: all var(--transition-smooth); letter-spacing: 0.01em; } .welcome-btn-claude { background: linear-gradient(135deg, #1a3358 0%, #1e4fb8 50%, #2563eb 100%); border-color: rgba(59, 130, 246, 0.4); color: #e0e7ff; box-shadow: 0 2px 8px rgba(37, 99, 235, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.08); } .welcome-btn-claude:hover { background: linear-gradient(135deg, #2454a8 0%, #2d6cf0 50%, #4b8af7 100%); box-shadow: 0 4px 20px rgba(59, 130, 246, 0.35), 0 0 40px rgba(59, 130, 246, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.1); border-color: rgba(96, 165, 250, 0.5); color: #fff; transform: translateY(-1px); } .welcome-btn-opencode { background: linear-gradient(135deg, #0a2e2a 0%, #0b3d35 50%, #0d4a40 100%); border-color: rgba(16, 185, 129, 0.4); color: #6ee7b7; box-shadow: 0 2px 8px rgba(16, 185, 129, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.06); } .welcome-btn-opencode:hover { background: linear-gradient(135deg, #0d4a40 0%, #116a58 50%, #15856e 100%); box-shadow: 0 4px 20px rgba(16, 185, 129, 0.3), 0 0 40px rgba(16, 185, 129, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.08); border-color: rgba(52, 211, 153, 0.5); color: #a7f3d0; transform: translateY(-1px); } /* Antigravity: cyan identity, matching .btn-toolbar.btn-run.mode-antigravity and .run-mode-dot.antigravity so the welcome action reads as the same backend. */ .welcome-btn-antigravity { background: linear-gradient(135deg, #0b2b33 0%, #0e7490 55%, #0891b2 100%); border-color: rgba(34, 211, 238, 0.4); color: #cffafe; box-shadow: 0 2px 8px rgba(34, 211, 238, 0.16), inset 0 1px 0 rgba(255, 255, 255, 0.06); } .welcome-btn-antigravity:hover { background: linear-gradient(135deg, #124450 0%, #0891b2 55%, #06b6d4 100%); box-shadow: 0 4px 20px rgba(34, 211, 238, 0.3), 0 0 40px rgba(8, 145, 178, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.08); border-color: rgba(103, 232, 249, 0.5); color: #ecfeff; transform: translateY(-1px); } /* Pi (pi.dev): rose identity, matching .btn-toolbar.btn-run.mode-pi and .run-mode-dot.pi so the welcome action reads as the same backend. */ .welcome-btn-pi { background: linear-gradient(135deg, #33121f 0%, #9d174d 55%, #be185d 100%); border-color: rgba(244, 114, 182, 0.4); color: #fce7f3; box-shadow: 0 2px 8px rgba(244, 114, 182, 0.16), inset 0 1px 0 rgba(255, 255, 255, 0.06); } .welcome-btn-pi:hover { background: linear-gradient(135deg, #4a1a2c 0%, #be185d 55%, #db2777 100%); box-shadow: 0 4px 20px rgba(244, 114, 182, 0.3), 0 0 40px rgba(190, 24, 93, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.08); border-color: rgba(249, 168, 212, 0.5); color: #fff1f7; transform: translateY(-1px); } /* OMP: indigo identity, matching .btn-toolbar.btn-run.mode-omp and .run-mode-dot.omp so the welcome action reads as the same backend. */ .welcome-btn-omp { background: linear-gradient(135deg, #1e1b4b 0%, #4f46e5 55%, #6366f1 100%); border-color: rgba(129, 140, 248, 0.4); color: #e0e7ff; box-shadow: 0 2px 8px rgba(129, 140, 248, 0.16), inset 0 1px 0 rgba(255, 255, 255, 0.06); } .welcome-btn-omp:hover { background: linear-gradient(135deg, #312e81 0%, #6366f1 55%, #818cf8 100%); box-shadow: 0 4px 20px rgba(129, 140, 248, 0.3), 0 0 40px rgba(79, 70, 229, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.08); border-color: rgba(165, 180, 252, 0.5); color: #eef2ff; transform: translateY(-1px); } /* Grok (xAI): monochrome charcoal identity, matching .btn-toolbar.btn-run.mode-grok and .run-mode-dot.grok so the welcome action reads as the same backend. */ .welcome-btn-grok { background: linear-gradient(135deg, #131316 0%, #27272a 55%, #3f3f46 100%); border-color: rgba(212, 212, 216, 0.4); color: #f4f4f5; box-shadow: 0 2px 8px rgba(212, 212, 216, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.06); } .welcome-btn-grok:hover { background: linear-gradient(135deg, #1f1f23 0%, #3f3f46 55%, #52525b 100%); box-shadow: 0 4px 20px rgba(212, 212, 216, 0.22), 0 0 40px rgba(161, 161, 170, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.08); border-color: rgba(228, 228, 231, 0.5); color: #fafafa; transform: translateY(-1px); } /* DeepSeek Harness: the #4d6bfe blue identity, matching .btn-toolbar.btn-run.mode-deepseek and .run-mode-dot.deepseek so the welcome action reads as the same backend. */ .welcome-btn-deepseek { background: linear-gradient(135deg, #101a4d 0%, #2740c4 55%, #4d6bfe 100%); border-color: rgba(124, 147, 255, 0.4); color: #eef2ff; box-shadow: 0 2px 8px rgba(77, 107, 254, 0.16), inset 0 1px 0 rgba(255, 255, 255, 0.06); } .welcome-btn-deepseek:hover { background: linear-gradient(135deg, #16225f 0%, #3350e6 55%, #6b83ff 100%); box-shadow: 0 4px 20px rgba(77, 107, 254, 0.3), 0 0 40px rgba(39, 64, 196, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.08); border-color: rgba(150, 170, 255, 0.5); color: #f8faff; transform: translateY(-1px); } .welcome-btn-gemini { background: linear-gradient(135deg, #10243f 0%, #174ea6 55%, #4f46e5 100%); border-color: rgba(96, 165, 250, 0.4); color: #dbeafe; box-shadow: 0 2px 8px rgba(96, 165, 250, 0.16), inset 0 1px 0 rgba(255, 255, 255, 0.06); } .welcome-btn-gemini:hover { background: linear-gradient(135deg, #17345f 0%, #2563eb 55%, #6366f1 100%); box-shadow: 0 4px 20px rgba(96, 165, 250, 0.3), 0 0 40px rgba(99, 102, 241, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.08); border-color: rgba(147, 197, 253, 0.5); color: #eff6ff; transform: translateY(-1px); } .welcome-btn-tunnel { background: linear-gradient(135deg, #221538 0%, #3b1a7a 50%, #6d28d9 100%); border-color: rgba(124, 58, 237, 0.4); color: #ddd6fe; box-shadow: 0 2px 8px rgba(109, 40, 217, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.06); } .welcome-btn-tunnel:hover { background: linear-gradient(135deg, #4c1d95 0%, #6d28d9 50%, #7c3aed 100%); box-shadow: 0 4px 20px rgba(124, 58, 237, 0.35), 0 0 40px rgba(124, 58, 237, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.1); border-color: rgba(139, 92, 246, 0.5); color: #fff; transform: translateY(-1px); } .welcome-btn-tunnel.active { background: linear-gradient(135deg, #047857 0%, #059669 50%, #10b981 100%); border-color: rgba(52, 211, 153, 0.5); color: #ecfdf5; box-shadow: 0 2px 8px rgba(16, 185, 129, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.08); } .welcome-btn-tunnel.active:hover { background: linear-gradient(135deg, #059669 0%, #10b981 50%, #34d399 100%); box-shadow: 0 4px 20px rgba(52, 211, 153, 0.35), 0 0 40px rgba(52, 211, 153, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.1); color: #fff; transform: translateY(-1px); } .welcome-btn-tunnel.connecting { background: linear-gradient(135deg, #1e1e3a 0%, #312e81 50%, #4338ca 100%); border-color: rgba(99, 102, 241, 0.4); color: #c7d2fe; cursor: wait; } .tunnel-spinner { display: inline-block; width: 16px; height: 16px; border: 2px solid rgba(255,255,255,0.25); border-top-color: currentColor; border-radius: 50%; animation: tunnel-spin 0.8s linear infinite; flex-shrink: 0; } @keyframes tunnel-spin { to { transform: rotate(360deg); } } .welcome-qr { display: none; flex-direction: column; align-items: center; margin-top: 1.5rem; margin-bottom: 0.5rem; cursor: pointer; transition: transform 0.2s ease; } .welcome-qr.visible { display: flex; } .welcome-qr-inner { background: #fff; border-radius: 10px; padding: 12px; width: 140px; height: 140px; transition: width 0.25s ease, height 0.25s ease, padding 0.25s ease; } .welcome-qr-inner svg { width: 100%; height: 100%; } .welcome-qr.expanded .welcome-qr-inner { width: 280px; height: 280px; padding: 16px; } .welcome-qr-url { margin-top: 8px; font-family: monospace; font-size: 11px; color: var(--text-muted); word-break: break-all; max-width: 320px; text-align: center; } /* History sessions panel (welcome overlay). The one shrinkable child of .welcome-content: a flex column so the list below can give up height on a short window instead of pushing the hint off-screen. It ships `style="display:none"` and loadHistorySessions() clears that to '', which is what lets this `display: flex` take effect. */ .history-sessions { display: flex; flex-direction: column; /* Beats the blanket `.welcome-content > * { flex-shrink: 0 }` above (same specificity, later in the file): this block is the one that gives ground. */ flex-shrink: 1; min-height: 0; margin-top: 1.5rem; width: 100%; max-width: 560px; } .history-sessions > .search-panel, .history-sessions > .history-header { flex-shrink: 0; } /* ── COD-9 cross-session search (folded into the welcome history panel) ── */ .search-panel { width: 100%; margin-bottom: 0.9rem; text-align: left; } .search-input-row { position: relative; display: flex; align-items: center; } .search-input { flex: 1; width: 100%; box-sizing: border-box; padding: 0.55rem 2rem 0.55rem 0.8rem; font-size: 0.85rem; color: var(--text); background: rgba(255, 255, 255, 0.04); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 8px; outline: none; transition: border-color var(--transition-smooth), background var(--transition-smooth); } .search-input:focus { border-color: rgba(59, 130, 246, 0.5); background: rgba(255, 255, 255, 0.06); } .search-input::placeholder { color: var(--text-dim); } .search-clear-btn { position: absolute; right: 0.4rem; top: 50%; transform: translateY(-50%); appearance: none; border: none; background: transparent; color: var(--text-dim); font-size: 1.2rem; line-height: 1; padding: 0.1rem 0.35rem; cursor: pointer; border-radius: 6px; } .search-clear-btn:hover { color: var(--text); background: rgba(255, 255, 255, 0.08); } .search-filters { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.5rem; align-items: center; } .search-filter-group { display: flex; gap: 0.3rem; flex-wrap: wrap; } .search-filter-secondary { margin-left: auto; } .search-filter-chip { appearance: none; border: 1px solid rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.03); color: var(--text-muted); font-size: 0.68rem; padding: 0.28rem 0.6rem; border-radius: 999px; cursor: pointer; transition: all var(--transition-smooth); } .search-filter-chip:hover { border-color: rgba(59, 130, 246, 0.3); color: var(--text); } .search-filter-chip.active { background: rgba(59, 130, 246, 0.18); border-color: rgba(59, 130, 246, 0.5); color: #cdddff; } .search-select { appearance: none; background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.1); color: var(--text-muted); font-size: 0.68rem; padding: 0.28rem 0.5rem; border-radius: 6px; cursor: pointer; max-width: 9rem; } .search-select:focus { outline: none; border-color: rgba(59, 130, 246, 0.5); } .search-results { display: flex; flex-direction: column; gap: 0.3rem; margin-top: 0.7rem; max-height: 320px; overflow-y: auto; } .search-results[hidden] { display: none; } .search-group-header { display: flex; align-items: center; gap: 0.4rem; margin-top: 0.4rem; padding: 0 0.2rem; } .search-group-header:first-child { margin-top: 0; } .search-group-label { font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-dim); font-weight: 600; } .search-group-count { font-size: 0.6rem; color: var(--text-muted); background: rgba(255, 255, 255, 0.06); border-radius: 999px; padding: 0.05rem 0.4rem; } .search-result-card { display: flex; flex-direction: column; gap: 0.25rem; background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 8px; padding: 0.5rem 0.7rem; cursor: pointer; transition: all var(--transition-smooth); } .search-result-card:hover, .search-result-card:focus-visible { border-color: rgba(59, 130, 246, 0.35); background: rgba(255, 255, 255, 0.06); outline: none; } .search-result-top { display: flex; align-items: center; gap: 0.5rem; min-width: 0; } .search-result-badge { font-size: 0.58rem; text-transform: uppercase; letter-spacing: 0.04em; padding: 0.1rem 0.4rem; border-radius: 4px; flex-shrink: 0; font-weight: 600; } .search-badge-session { background: rgba(59, 130, 246, 0.18); color: #9dc0ff; } .search-badge-event { background: rgba(168, 85, 247, 0.18); color: #d4b3ff; } .search-badge-file { background: rgba(34, 197, 94, 0.16); color: #95e6b3; } /* Past session (issue #261): activating the card resumes the conversation rather than switching to a tab, so the badge says so. */ .search-badge-past { background: rgba(245, 158, 11, 0.18); color: #f0c073; } .search-result-name { flex: 1; min-width: 0; font-size: 0.74rem; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .search-result-time { font-size: 0.65rem; color: var(--text-dim); white-space: nowrap; flex-shrink: 0; } .search-result-snippet { font-size: 0.7rem; color: var(--text-muted); line-height: 1.35; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; word-break: break-word; } .search-empty, .search-truncated { font-size: 0.72rem; color: var(--text-dim); padding: 0.6rem 0.2rem; text-align: left; } .search-truncated { border-top: 1px dashed rgba(255, 255, 255, 0.08); margin-top: 0.3rem; color: var(--text-muted); } @media (max-width: 640px) { .search-filter-secondary { margin-left: 0; } .search-select { max-width: 7rem; } /* Tablet/narrow: let the controls drop under the title instead of squeezing it. */ .history-header { flex-wrap: wrap; } .history-controls { width: 100%; margin-left: 0; } .history-filter { flex: 1; width: auto; } } /* Title row for the past-session list: label + count on the left, filter and sort on the right (issue #260, 35 conversations in a 4-row box). */ .history-header { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; } .history-title { font-size: 0.85rem; color: var(--text-dim); font-weight: 500; text-align: left; } .history-count { font-size: 0.68rem; color: var(--text-dim); background: rgba(255, 255, 255, 0.05); border-radius: 999px; padding: 0.1rem 0.45rem; white-space: nowrap; } .history-controls { display: flex; align-items: center; gap: 0.35rem; margin-left: auto; } .history-filter { width: 8.5rem; box-sizing: border-box; padding: 0.28rem 0.5rem; font-size: 0.68rem; color: var(--text); background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 6px; outline: none; transition: border-color var(--transition-smooth), background var(--transition-smooth); } .history-filter:focus { border-color: rgba(59, 130, 246, 0.5); background: rgba(255, 255, 255, 0.06); } .history-filter::placeholder { color: var(--text-dim); } .history-sort { max-width: 7.5rem; } .history-empty { padding: 0.75rem 0.5rem; font-size: 0.75rem; color: var(--text-dim); text-align: center; } /* Collapsed height fits the initial page of rows; expanding the LIST has to expand the BOX too, or "Show more" just deepens a scroll well. */ .history-list { display: flex; flex-direction: column; gap: 0.35rem; /* Shrinks (never grows) inside the bounded welcome column: on a short window it gives up height so the hint below stays on screen, and scrolls the rows it can no longer show. `min-height: 0` is what lets it go below content. */ flex: 0 1 auto; min-height: 0; max-height: min(42vh, 360px); overflow-y: auto; } .history-list.expanded { max-height: min(64vh, 660px); } .history-item { display: flex; flex-direction: column; background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 8px; transition: all var(--transition-smooth); text-align: left; } .history-item:hover { border-color: rgba(59, 130, 246, 0.3); background: rgba(255, 255, 255, 0.06); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); } .history-item.expanded { border-color: rgba(59, 130, 246, 0.35); background: rgba(255, 255, 255, 0.05); } /* COD-139: pinned sessions float to the top with a subtle accent highlight. */ .history-item.is-pinned { border-color: rgba(245, 158, 11, 0.35); background: rgba(245, 158, 11, 0.08); box-shadow: inset 3px 0 0 rgba(245, 158, 11, 0.7); } .history-item.is-pinned:hover { border-color: rgba(245, 158, 11, 0.5); background: rgba(245, 158, 11, 0.12); } .history-item-pin { margin-right: 0.35rem; font-size: 0.75rem; line-height: 1; vertical-align: baseline; } .history-item-main { display: flex; align-items: center; gap: 0.6rem; padding: 0.55rem 0.8rem; cursor: pointer; border-radius: 8px; } .history-item-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.2rem; } .history-item-title { font-size: 0.8rem; color: var(--text); line-height: 1.35; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; word-break: break-word; } .history-item-subtitle { font-size: 0.65rem; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .history-item-subtitle.is-case { color: #7aa7ff; font-weight: 500; } /* Badge row (mode + LIVE pills) sits between the title and subtitle (COD-121). */ .history-item-badges { display: inline-flex; flex-wrap: wrap; gap: 0.3rem; align-items: center; } .history-item-badge { font-size: 0.55rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; line-height: 1; padding: 0.12rem 0.35rem; border-radius: 0.35rem; background: var(--bg-hover); color: var(--text-muted); white-space: nowrap; } .history-item-badge-live { background: color-mix(in srgb, var(--green) 22%, transparent); color: var(--green); } /* Worktree pill (#266). Tinted off the accent so it reads as metadata rather than status — LIVE is the only badge that should look like state. */ .history-item-badge-worktree { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); max-width: 22ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .history-item-meta { font-size: 0.7rem; color: var(--text-muted); white-space: nowrap; align-self: center; } .history-item-expand { appearance: none; border: none; background: transparent; color: var(--text-dim); font-size: 1.1rem; line-height: 1; padding: 0.25rem 0.4rem; cursor: pointer; border-radius: 6px; transition: background var(--transition-smooth), color var(--transition-smooth); flex-shrink: 0; } .history-item-expand:hover { background: rgba(255, 255, 255, 0.08); color: var(--text); } .history-item.expanded .history-item-expand { color: #7aa7ff; transform: rotate(90deg); } .history-item-detail { padding: 0.5rem 0.8rem 0.7rem; border-top: 1px dashed rgba(255, 255, 255, 0.08); display: flex; flex-direction: column; gap: 0.4rem; font-size: 0.72rem; color: var(--text-muted); } .history-item-detail[hidden] { display: none; } .history-detail-row { display: flex; gap: 0.5rem; align-items: flex-start; word-break: break-word; } .history-detail-label { color: var(--text-dim); min-width: 46px; flex-shrink: 0; font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.04em; padding-top: 0.1rem; } .history-detail-value { color: var(--text); flex: 1; min-width: 0; white-space: pre-wrap; } .history-detail-path { font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 0.68rem; color: #a8b5c9; } .history-detail-meta { color: var(--text-dim); font-size: 0.68rem; } @media (max-width: 640px) { .history-item-meta { font-size: 0.65rem; } .history-item-main { gap: 0.45rem; padding: 0.6rem 0.7rem; } } .history-show-more { width: 100%; padding: 0.5rem; margin-top: 0.25rem; background: rgba(255, 255, 255, 0.04); border: 1px dashed rgba(255, 255, 255, 0.1); border-radius: 8px; color: var(--text-muted); font-size: 0.75rem; cursor: pointer; transition: background var(--transition-smooth), color var(--transition-smooth); } .history-show-more:hover { background: rgba(255, 255, 255, 0.08); color: var(--text); } .history-detail-actions { margin-top: 0.5rem; } .history-view-all-btn { width: 100%; padding: 0.45rem 0.75rem; background: rgba(99, 179, 237, 0.08); border: 1px solid rgba(99, 179, 237, 0.25); border-radius: 6px; color: rgba(99, 179, 237, 0.95); font-size: 0.78rem; font-weight: 500; cursor: pointer; transition: background var(--transition-smooth), border-color var(--transition-smooth); } .history-view-all-btn:hover { background: rgba(99, 179, 237, 0.15); border-color: rgba(99, 179, 237, 0.5); } /* Folder history modal */ .folder-history-modal .modal-body { padding: 0.75rem 1rem 1rem; } .folder-history-subtitle { color: var(--text-muted); font-size: 0.78rem; font-family: 'SF Mono', Menlo, Consolas, monospace; word-break: break-all; margin-bottom: 0.75rem; padding-bottom: 0.5rem; border-bottom: 1px solid rgba(255, 255, 255, 0.06); } .folder-history-list { display: flex; flex-direction: column; gap: 0.5rem; } .folder-history-empty { padding: 2rem 0.5rem; text-align: center; color: var(--text-muted); font-size: 0.85rem; } .welcome-hint { color: var(--text-muted); font-size: 0.8rem; } .welcome-hint kbd { display: inline-block; padding: 0.15rem 0.45rem; background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 5px; font-family: inherit; font-size: 0.75rem; color: var(--text-dim); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); } .welcome-ralph-link { background: none; border: none; color: var(--text-muted); font-size: 0.75rem; cursor: pointer; padding: 0.25rem 0; margin-top: 0.25rem; transition: color 0.15s ease; } .welcome-ralph-link:hover { color: var(--accent); } /* Short window (a 1470x840 laptop is the common case): the column above now fits by construction, but only by taking the space out of the Resume list, which collapses to two rows. Buying ~80px back from the vertical rhythm keeps the list usable without touching how the screen looks on a tall display. Phones are covered by mobile.css, which loads later and compacts harder. */ @media (max-height: 900px) { .welcome-content { padding: 1.25rem 2rem; } .welcome-title { font-size: 2.75rem; margin-bottom: 0.5rem; } .welcome-actions { margin-top: 1.25rem; margin-bottom: 1rem; } .history-sessions { margin-top: 1rem; } } /* Bottom Toolbar */ .toolbar { display: flex; align-items: center; justify-content: space-between; position: relative; height: var(--toolbar-height); padding: 0 0.75rem; background: var(--glass-bg); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border-top: 1px solid var(--glass-border); box-shadow: 0 -1px 3px rgba(0, 0, 0, 0.2), 0 -4px 12px rgba(0, 0, 0, 0.1); flex-shrink: 0; /* contain: style only — layout/paint containment clips the case-settings popover that extends above the toolbar (popover uses position:absolute + bottom:100%) */ contain: style; /* backdrop-filter above makes this a stacking context, which TRAPS the z-index:1000 on .run-mode-menu inside it. Without an explicit z-index here the toolbar resolves to auto (0) and .welcome-overlay (z-index:10, inside
) paints over the popped-up Run menu: with no session open, every item in that menu is unclickable. Must stay below .modal (1000). */ z-index: 20; } /* backdrop-filter creates a stacking context, trapping the popover's z-index inside the toolbar. When the popover is open, raise the toolbar above the CJK input (z-index 52) so the popover is interactable. */ .toolbar:has(.case-settings-popover:not(.hidden)), .toolbar:has(.case-combobox-list:not(.hidden)) { z-index: 100; } .toolbar-left, .toolbar-right { display: flex; align-items: center; gap: 0.5rem; } .toolbar-center { position: absolute; left: 50%; transform: translateX(-50%); } /* Below ~1500px the absolutely-centered group can land on top of the case picker (the left group widens with the case name), so fall back to normal flex flow where overlap is impossible */ @media (max-width: 1500px) { .toolbar-center { position: static; transform: none; margin-left: 0.4rem; } } .version-display { font-family: 'SF Mono', Monaco, monospace; font-size: 0.6rem; color: var(--text-muted); padding: 0.2rem 0.5rem; background: rgba(255, 255, 255, 0.04); border: 1px solid rgba(255, 255, 255, 0.04); border-radius: var(--btn-radius); cursor: default; user-select: none; opacity: 0.8; letter-spacing: 0.02em; } .toolbar-group { display: flex; align-items: center; gap: 0; } .btn-toolbar { display: flex; align-items: center; gap: 0.35rem; padding: 0.4rem 0.8rem; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.07); border-radius: var(--btn-radius); color: var(--text-dim); font-size: 0.75rem; cursor: pointer; transition: all var(--transition-smooth); white-space: nowrap; } .btn-toolbar:hover { background: rgba(255, 255, 255, 0.09); color: var(--text); border-color: rgba(255, 255, 255, 0.12); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2); } .btn-toolbar:active { background: rgba(255, 255, 255, 0.06); transform: translateY(0.5px); } .btn-toolbar.btn-sm { padding: 0.3rem 0.55rem; font-size: 0.7rem; } .btn-toolbar.btn-primary { background: var(--accent); border-color: var(--accent); color: white; } .btn-toolbar.btn-primary:hover { background: var(--accent-hover); } /* Run Button Group — split button with dropdown */ .run-btn-group { display: flex; position: relative; } .btn-toolbar.btn-run { border-radius: var(--btn-radius) 0 0 var(--btn-radius); border-right: 1px solid rgba(255,255,255,0.12); font-weight: 600; padding: 0.45rem 0.9rem; gap: 0.45rem; letter-spacing: 0.01em; } .btn-toolbar.btn-run svg { flex-shrink: 0; width: 12px; height: 12px; } .btn-toolbar.btn-run-gear { border-radius: 0 var(--btn-radius) var(--btn-radius) 0; padding: 0.45rem 0.45rem; min-width: unset; } /* Claude mode colors (default) */ .btn-toolbar.btn-run.mode-claude, .btn-toolbar.btn-run-gear.mode-claude { background: linear-gradient(135deg, #1a3358 0%, #1e4fb8 50%, #2563eb 100%); border-color: rgba(59, 130, 246, 0.5); color: #e0e7ff; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.08); } .btn-toolbar.btn-run.mode-claude:hover, .btn-toolbar.btn-run-gear.mode-claude:hover { background: linear-gradient(135deg, #2454a8 0%, #2d6cf0 50%, #4b8af7 100%); box-shadow: 0 0 16px rgba(59, 130, 246, 0.4), 0 2px 8px rgba(59, 130, 246, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.1); border-color: rgba(96, 165, 250, 0.6); color: #fff; } /* OpenCode mode colors */ .btn-toolbar.btn-run.mode-opencode, .btn-toolbar.btn-run-gear.mode-opencode { background: linear-gradient(135deg, #0a2e2a 0%, #0b3d35 50%, #0d4a40 100%); border-color: rgba(16, 185, 129, 0.5); color: #6ee7b7; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.06); } .btn-toolbar.btn-run.mode-opencode:hover, .btn-toolbar.btn-run-gear.mode-opencode:hover { background: linear-gradient(135deg, #0d4a40 0%, #116a58 50%, #15856e 100%); box-shadow: 0 0 12px rgba(16, 185, 129, 0.35), 0 2px 8px rgba(16, 185, 129, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.08); border-color: rgba(52, 211, 153, 0.6); color: #a7f3d0; } /* Codex mode colors */ .btn-toolbar.btn-run.mode-codex, .btn-toolbar.btn-run-gear.mode-codex { background: linear-gradient(135deg, #2a0a3e 0%, #350b4d 50%, #400d5e 100%); border-color: rgba(168, 85, 247, 0.5); color: #d8b4fe; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.06); } .btn-toolbar.btn-run.mode-codex:hover, .btn-toolbar.btn-run-gear.mode-codex:hover { background: linear-gradient(135deg, #400d5e 0%, #581c87 50%, #6b21a8 100%); box-shadow: 0 0 12px rgba(168, 85, 247, 0.35), 0 2px 8px rgba(168, 85, 247, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.08); border-color: rgba(192, 132, 252, 0.6); color: #e9d5ff; } /* Gemini mode colors */ .btn-toolbar.btn-run.mode-gemini, .btn-toolbar.btn-run-gear.mode-gemini { background: linear-gradient(135deg, #10243f 0%, #174ea6 55%, #4f46e5 100%); border-color: rgba(96, 165, 250, 0.5); color: #dbeafe; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.06); } .btn-toolbar.btn-run.mode-gemini:hover, .btn-toolbar.btn-run-gear.mode-gemini:hover { background: linear-gradient(135deg, #17345f 0%, #2563eb 55%, #6366f1 100%); box-shadow: 0 0 12px rgba(96, 165, 250, 0.35), 0 2px 8px rgba(99, 102, 241, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.08); border-color: rgba(147, 197, 253, 0.6); color: #eff6ff; } /* Antigravity mode colors */ .btn-toolbar.btn-run.mode-antigravity, .btn-toolbar.btn-run-gear.mode-antigravity { background: linear-gradient(135deg, #0b2b33 0%, #0e7490 55%, #0891b2 100%); border-color: rgba(34, 211, 238, 0.5); color: #cffafe; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.06); } .btn-toolbar.btn-run.mode-antigravity:hover, .btn-toolbar.btn-run-gear.mode-antigravity:hover { background: linear-gradient(135deg, #124450 0%, #0891b2 55%, #06b6d4 100%); box-shadow: 0 0 12px rgba(34, 211, 238, 0.35), 0 2px 8px rgba(8, 145, 178, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.08); border-color: rgba(103, 232, 249, 0.6); color: #ecfeff; } /* Pi mode colors. NOTE: this base-sheet pair only renders on the `og` skin — the nested `html:not([data-skin="og"])` block further down re-declares `.btn-toolbar.btn-run` at a HIGHER specificity, which is why gemini's and antigravity's gradients are dead on the default skin. Pi therefore also carries a rule inside that block (search `.btn-toolbar.btn-run.mode-pi`). */ .btn-toolbar.btn-run.mode-pi, .btn-toolbar.btn-run-gear.mode-pi { background: linear-gradient(135deg, #33121f 0%, #9d174d 55%, #be185d 100%); border-color: rgba(244, 114, 182, 0.5); color: #fce7f3; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.06); } .btn-toolbar.btn-run.mode-pi:hover, .btn-toolbar.btn-run-gear.mode-pi:hover { background: linear-gradient(135deg, #4a1a2c 0%, #be185d 55%, #db2777 100%); box-shadow: 0 0 12px rgba(244, 114, 182, 0.35), 0 2px 8px rgba(190, 24, 93, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.08); border-color: rgba(249, 168, 212, 0.6); color: #fff1f7; } /* OMP mode colors */ .btn-toolbar.btn-run.mode-omp, .btn-toolbar.btn-run-gear.mode-omp { background: linear-gradient(135deg, #312e81 0%, #4f46e5 55%, #6366f1 100%); border-color: rgba(129, 140, 248, 0.5); color: #e0e7ff; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.06); } .btn-toolbar.btn-run.mode-omp:hover, .btn-toolbar.btn-run-gear.mode-omp:hover { background: linear-gradient(135deg, #3730a3 0%, #6366f1 55%, #818cf8 100%); box-shadow: 0 0 12px rgba(129, 140, 248, 0.35), 0 2px 8px rgba(79, 70, 229, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.08); border-color: rgba(165, 180, 252, 0.6); color: #eef2ff;} /* Grok mode colors. Same cascade note as pi above: this base-sheet pair only renders on the `og` skin — the nested `html:not([data-skin="og"])` block re-declares `.btn-toolbar.btn-run` at a HIGHER specificity, so grok also carries a rule inside that block (search `.btn-toolbar.btn-run.mode-grok`). */ .btn-toolbar.btn-run.mode-grok, .btn-toolbar.btn-run-gear.mode-grok { background: linear-gradient(135deg, #131316 0%, #27272a 55%, #3f3f46 100%); border-color: rgba(212, 212, 216, 0.5); color: #f4f4f5; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.06); } .btn-toolbar.btn-run.mode-grok:hover, .btn-toolbar.btn-run-gear.mode-grok:hover { background: linear-gradient(135deg, #1f1f23 0%, #3f3f46 55%, #52525b 100%); box-shadow: 0 0 12px rgba(212, 212, 216, 0.28), 0 2px 8px rgba(63, 63, 70, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.08); border-color: rgba(228, 228, 231, 0.6); color: #fafafa; } /* DeepSeek mode colors. Same cascade note as pi/grok above: this base-sheet pair only renders on the `og` skin — the nested `html:not([data-skin="og"])` block re-declares `.btn-toolbar.btn-run` at a HIGHER specificity, so deepseek also carries a rule inside that block (search `.btn-toolbar.btn-run.mode-deepseek`). */ .btn-toolbar.btn-run.mode-deepseek, .btn-toolbar.btn-run-gear.mode-deepseek { background: linear-gradient(135deg, #101a4d 0%, #2740c4 55%, #4d6bfe 100%); border-color: rgba(124, 147, 255, 0.55); color: #eef2ff; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.06); } .btn-toolbar.btn-run.mode-deepseek:hover, .btn-toolbar.btn-run-gear.mode-deepseek:hover { background: linear-gradient(135deg, #16225f 0%, #3350e6 55%, #6b83ff 100%); box-shadow: 0 0 12px rgba(77, 107, 254, 0.35), 0 2px 8px rgba(39, 64, 196, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.08); border-color: rgba(150, 170, 255, 0.65); color: #f8faff; } /* Dropdown menu */ .run-mode-menu { display: none; position: absolute; bottom: 100%; left: 0; margin-bottom: 6px; background: var(--floating-bg); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border: 1px solid var(--control-border); border-radius: 10px; padding: 4px; z-index: 1000; min-width: 190px; /* Every long row inside (recent-session paths, saved URL names) ellipsizes, so the cap is a deliberate choice rather than a fit-the-content result: the menu overlays the terminal and does not need to reach across it. 250 rather than a rounder 240 because it is the width at which the common `~//` + timestamp recent-session row still fits whole, which is what that list is for. */ max-width: 250px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3); } /* Above phone width the menu becomes a full-width drawer across the bottom of the window. The 250px cap above was sized for a `~//` row, which assumed the home prefix had been abbreviated — it never was on macOS, so real rows blew straight through it and ellipsized into `/Users//co…`, identical on every line. Width is what buys room for the worktree + branch, so the cap is lifted rather than nudged. Phones keep the compact popover: mobile.css positions this menu itself and a viewport-wide drawer there would cover the composer. */ @media (min-width: 769px) { .run-mode-menu { /* Bounded, not viewport-wide. `100vw - 24px` spanned the whole window while every row stayed shrink-to-fit at ~250px (see below), so the menu grew by ~1100px of dead space. 760px is what one full row costs: name + worktree pill + parent path + timestamp. */ max-width: none; width: min(760px, calc(100vw - 24px)); } .run-mode-history { /* More rows are worth showing once each one is legible. */ max-height: 320px; } } .run-mode-menu.active { display: flex; flex-direction: column; gap: 2px; } .run-mode-option { display: flex; align-items: center; gap: 8px; padding: 7px 12px; background: none; border: none; border-radius: var(--btn-radius); color: var(--text); cursor: pointer; font-size: 0.8rem; white-space: nowrap; transition: background var(--transition-smooth); } .run-mode-option:hover { background: rgba(255, 255, 255, 0.07); } .run-mode-option.selected { background: rgba(255, 255, 255, 0.08); font-weight: 600; } .run-mode-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; } .run-mode-dot.claude { background: #3b82f6; } .run-mode-dot.opencode { background: #10b981; } .run-mode-dot.codex { background: #a855f7; } .run-mode-dot.gemini { background: #8ab4f8; } .run-mode-dot.antigravity { background: #22d3ee; } .run-mode-dot.pi { background: #f472b6; } .run-mode-dot.grok { background: #a1a1aa; } .run-mode-dot.deepseek { background: #4d6bfe; } .run-mode-dot.omp { background: #818cf8; } .run-mode-dot.shell { background: #94a3b8; } /* Phone-only Enter button (see index.html). Hidden by default at every width; mobile.css turns it on inside @media (max-width: 430px), where it takes over the slot the Shell button occupies on wider screens. */ .btn-toolbar.btn-enter { display: none; } .run-mode-sep { height: 1px; background: var(--border); margin: 4px 0; } .run-mode-header { padding: 4px 10px 2px; font-size: 0.72em; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.04em; font-weight: 600; } .run-mode-history { max-height: 200px; overflow-y: auto; } /* `overflow-y: auto` computes overflow-x to auto as well, which makes this a scroll container and lets a row size itself to its own content. A long path therefore scrolled sideways instead of ellipsizing, and before the menu was narrowed it simply pinned the menu at its max-width. */ .run-mode-history .run-mode-option { max-width: 100%; /* `.run-mode-history` is a block scroller, not a flex container, so these