/// Catppuccin Mocha palette + CSS custom property generation. /// /// All UI colors flow through --ctp-* CSS custom properties. /// Components NEVER hardcode colors — they reference these variables. /// Catppuccin Mocha color palette — hex values. pub struct CatppuccinMocha; impl CatppuccinMocha { // Base colors pub const BASE: &str = "#1e1e2e"; pub const MANTLE: &str = "#181825"; pub const CRUST: &str = "#11111b"; // Surface colors pub const SURFACE0: &str = "#313244"; pub const SURFACE1: &str = "#45475a"; pub const SURFACE2: &str = "#585b70"; // Overlay colors pub const OVERLAY0: &str = "#6c7086"; pub const OVERLAY1: &str = "#7f849c"; pub const OVERLAY2: &str = "#9399b2"; // Text colors pub const SUBTEXT0: &str = "#a6adc8"; pub const SUBTEXT1: &str = "#bac2de"; pub const TEXT: &str = "#cdd6f4"; // Accent colors pub const ROSEWATER: &str = "#f5e0dc"; pub const FLAMINGO: &str = "#f2cdcd"; pub const PINK: &str = "#f5c2e7"; pub const MAUVE: &str = "#cba6f7"; pub const RED: &str = "#f38ba8"; pub const MAROON: &str = "#eba0ac"; pub const PEACH: &str = "#fab387"; pub const YELLOW: &str = "#f9e2af"; pub const GREEN: &str = "#a6e3a1"; pub const TEAL: &str = "#94e2d5"; pub const SKY: &str = "#89dceb"; pub const SAPPHIRE: &str = "#74c7ec"; pub const BLUE: &str = "#89b4fa"; pub const LAVENDER: &str = "#b4befe"; } /// Generate complete CSS stylesheet with Catppuccin Mocha custom properties /// and base layout styles. pub fn generate_css() -> String { format!( r#" /* === Catppuccin Mocha CSS Custom Properties === */ :root {{ /* Base */ --ctp-base: {base}; --ctp-mantle: {mantle}; --ctp-crust: {crust}; /* Surface */ --ctp-surface0: {surface0}; --ctp-surface1: {surface1}; --ctp-surface2: {surface2}; /* Overlay */ --ctp-overlay0: {overlay0}; --ctp-overlay1: {overlay1}; --ctp-overlay2: {overlay2}; /* Text */ --ctp-subtext0: {subtext0}; --ctp-subtext1: {subtext1}; --ctp-text: {text}; /* Accents */ --ctp-rosewater: {rosewater}; --ctp-flamingo: {flamingo}; --ctp-pink: {pink}; --ctp-mauve: {mauve}; --ctp-red: {red}; --ctp-maroon: {maroon}; --ctp-peach: {peach}; --ctp-yellow: {yellow}; --ctp-green: {green}; --ctp-teal: {teal}; --ctp-sky: {sky}; --ctp-sapphire: {sapphire}; --ctp-blue: {blue}; --ctp-lavender: {lavender}; /* Typography */ --ui-font-family: 'Inter', system-ui, -apple-system, sans-serif; --ui-font-size: 0.875rem; --term-font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace; --term-font-size: 0.8125rem; }} /* === Global Reset === */ *, *::before, *::after {{ box-sizing: border-box; margin: 0; padding: 0; }} body {{ font-family: var(--ui-font-family); font-size: var(--ui-font-size); color: var(--ctp-text); background: var(--ctp-base); overflow: hidden; height: 100vh; width: 100vw; }} #main {{ height: 100vh; width: 100vw; display: flex; flex-direction: column; }} /* === Scrollbar Styling === */ ::-webkit-scrollbar {{ width: 0.5rem; height: 0.5rem; }} ::-webkit-scrollbar-track {{ background: var(--ctp-mantle); }} ::-webkit-scrollbar-thumb {{ background: var(--ctp-surface1); border-radius: 0.25rem; }} ::-webkit-scrollbar-thumb:hover {{ background: var(--ctp-surface2); }} /* === Layout Classes === */ .app-shell {{ display: flex; flex-direction: column; height: 100vh; width: 100vw; overflow: hidden; }} .app-body {{ display: flex; flex: 1; overflow: hidden; min-height: 0; }} /* === Sidebar === */ .sidebar-rail {{ width: 2.75rem; background: var(--ctp-crust); border-right: 1px solid var(--ctp-surface0); display: flex; flex-direction: column; align-items: center; padding-top: 0.75rem; gap: 0.5rem; flex-shrink: 0; }} .sidebar-icon {{ width: 2rem; height: 2rem; display: flex; align-items: center; justify-content: center; border-radius: 0.375rem; cursor: pointer; color: var(--ctp-overlay1); transition: all 0.15s ease; font-size: 1rem; }} .sidebar-icon:hover {{ background: var(--ctp-surface0); color: var(--ctp-text); }} .sidebar-icon.active {{ background: var(--ctp-surface0); color: var(--ctp-blue); }} .sidebar-spacer {{ flex: 1; }} /* === Drawer Panel === */ .drawer-panel {{ width: 18rem; background: var(--ctp-mantle); border-right: 1px solid var(--ctp-surface0); overflow-y: auto; padding: 1rem; flex-shrink: 0; }} .drawer-title {{ font-size: 0.8125rem; font-weight: 600; color: var(--ctp-subtext1); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 1rem; }} /* === Project Grid === */ .project-grid {{ flex: 1; display: grid; grid-template-columns: repeat(auto-fit, minmax(28rem, 1fr)); gap: 0.75rem; padding: 0.75rem; overflow-y: auto; align-content: start; }} /* === Project Box === */ .project-box {{ background: var(--ctp-mantle); border: 1px solid var(--ctp-surface0); border-radius: 0.5rem; display: flex; flex-direction: column; min-height: 20rem; max-height: 40rem; overflow: hidden; }} .project-header {{ display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0.75rem; background: var(--ctp-crust); border-bottom: 1px solid var(--ctp-surface0); border-radius: 0.5rem 0.5rem 0 0; flex-shrink: 0; }} .project-header .status-dot {{ width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }} .status-dot.running {{ background: var(--ctp-green); box-shadow: 0 0 6px var(--ctp-green); }} .status-dot.idle {{ background: var(--ctp-overlay0); }} .status-dot.stalled {{ background: var(--ctp-peach); }} /* PulsingDot component classes */ .pulsing-dot {{ display: inline-block; border-radius: 50%; flex-shrink: 0; }} .dot-running {{ background: var(--ctp-green); box-shadow: 0 0 6px var(--ctp-green); }} .dot-idle {{ background: var(--ctp-overlay0); }} .dot-stalled {{ background: var(--ctp-peach); box-shadow: 0 0 4px var(--ctp-peach); }} .dot-done {{ background: var(--ctp-blue); }} .dot-error {{ background: var(--ctp-red); box-shadow: 0 0 4px var(--ctp-red); }} /* PulsingDot: 6-step color fade, no CSS transition (zero animation engine overhead) */ .pulsing-dot {{ display: inline-block; border-radius: 50%; flex-shrink: 0; }} /* Running: green (#a6e3a1) → surface2 (#585b70) in 6 steps */ .dot-running.dot-s0 {{ background: #a6e3a1; box-shadow: 0 0 6px #a6e3a1; }} .dot-running.dot-s1 {{ background: #8ec094; box-shadow: 0 0 3px #8ec094; }} .dot-running.dot-s2 {{ background: #769d87; box-shadow: none; }} .dot-running.dot-s3 {{ background: #6a8a7c; box-shadow: none; }} .dot-running.dot-s4 {{ background: #769d87; box-shadow: none; }} .dot-running.dot-s5 {{ background: #8ec094; box-shadow: 0 0 3px #8ec094; }} /* Stalled: peach (#fab387) → surface2 in 6 steps */ .dot-stalled.dot-s0 {{ background: #fab387; box-shadow: 0 0 4px #fab387; }} .dot-stalled.dot-s1 {{ background: #d9a07c; box-shadow: none; }} .dot-stalled.dot-s2 {{ background: #b88d71; box-shadow: none; }} .dot-stalled.dot-s3 {{ background: #a88068; box-shadow: none; }} .dot-stalled.dot-s4 {{ background: #b88d71; box-shadow: none; }} .dot-stalled.dot-s5 {{ background: #d9a07c; box-shadow: none; }} .status-dot.error {{ background: var(--ctp-red); }} .project-name {{ font-weight: 600; font-size: 0.875rem; color: var(--ctp-text); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }} .project-cwd {{ font-size: 0.6875rem; color: var(--ctp-overlay0); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; direction: rtl; max-width: 12rem; }} /* === Tab Bar === */ .tab-bar {{ display: flex; gap: 0; background: var(--ctp-crust); border-bottom: 1px solid var(--ctp-surface0); flex-shrink: 0; }} .tab {{ padding: 0.375rem 0.75rem; font-size: 0.75rem; color: var(--ctp-overlay1); cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.15s ease; user-select: none; }} .tab:hover {{ color: var(--ctp-text); background: var(--ctp-surface0); }} .tab.active {{ color: var(--ctp-blue); border-bottom-color: var(--ctp-blue); }} /* === Tab Content === */ .tab-content {{ flex: 1; overflow: hidden; display: flex; flex-direction: column; min-height: 0; }} /* === Agent Pane === */ .agent-pane {{ display: flex; flex-direction: column; flex: 1; min-height: 0; }} .message-list {{ flex: 1; overflow-y: auto; padding: 0.5rem 0.75rem; display: flex; flex-direction: column; gap: 0.5rem; }} .message {{ padding: 0.5rem 0.75rem; border-radius: 0.375rem; font-size: 0.8125rem; line-height: 1.5; word-wrap: break-word; }} .message.user {{ background: color-mix(in srgb, var(--ctp-blue) 12%, transparent); border-left: 3px solid var(--ctp-blue); }} .message.assistant {{ background: color-mix(in srgb, var(--ctp-mauve) 8%, transparent); border-left: 3px solid var(--ctp-mauve); }} .message.tool {{ background: color-mix(in srgb, var(--ctp-teal) 8%, transparent); border-left: 3px solid var(--ctp-teal); font-family: var(--term-font-family); font-size: 0.75rem; }} .message-role {{ font-size: 0.6875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.25rem; }} .message.user .message-role {{ color: var(--ctp-blue); }} .message.assistant .message-role {{ color: var(--ctp-mauve); }} .message.tool .message-role {{ color: var(--ctp-teal); }} .message-text {{ color: var(--ctp-text); }} .tool-details {{ margin-top: 0.25rem; }} .tool-details summary {{ cursor: pointer; color: var(--ctp-overlay1); font-size: 0.6875rem; user-select: none; }} .tool-details summary:hover {{ color: var(--ctp-text); }} .tool-output {{ margin-top: 0.375rem; padding: 0.375rem 0.5rem; background: var(--ctp-crust); border-radius: 0.25rem; font-family: var(--term-font-family); font-size: 0.6875rem; color: var(--ctp-subtext0); white-space: pre-wrap; max-height: 10rem; overflow-y: auto; }} /* === Prompt Input === */ .prompt-bar {{ display: flex; gap: 0.5rem; padding: 0.5rem 0.75rem; background: var(--ctp-crust); border-top: 1px solid var(--ctp-surface0); align-items: center; flex-shrink: 0; }} .prompt-input {{ flex: 1; background: var(--ctp-surface0); border: 1px solid var(--ctp-surface1); border-radius: 0.375rem; padding: 0.4375rem 0.75rem; color: var(--ctp-text); font-family: var(--ui-font-family); font-size: 0.8125rem; outline: none; transition: border-color 0.15s ease; }} .prompt-input:focus {{ border-color: var(--ctp-blue); }} .prompt-input::placeholder {{ color: var(--ctp-overlay0); }} .prompt-send {{ background: var(--ctp-blue); color: var(--ctp-crust); border: none; border-radius: 0.375rem; padding: 0.4375rem 0.75rem; font-size: 0.75rem; font-weight: 600; cursor: pointer; transition: opacity 0.15s ease; }} .prompt-send:hover {{ opacity: 0.85; }} .prompt-send:disabled {{ opacity: 0.4; cursor: not-allowed; }} /* === Agent Status Strip === */ .agent-status {{ display: flex; align-items: center; gap: 0.5rem; padding: 0.25rem 0.75rem; background: var(--ctp-crust); border-top: 1px solid var(--ctp-surface0); font-size: 0.6875rem; color: var(--ctp-overlay1); flex-shrink: 0; }} .status-badge {{ padding: 0.125rem 0.375rem; border-radius: 0.25rem; font-weight: 600; text-transform: uppercase; font-size: 0.625rem; letter-spacing: 0.05em; }} .status-badge.idle {{ background: color-mix(in srgb, var(--ctp-overlay0) 20%, transparent); color: var(--ctp-overlay1); }} .status-badge.running {{ background: color-mix(in srgb, var(--ctp-green) 20%, transparent); color: var(--ctp-green); }} .status-badge.done {{ background: color-mix(in srgb, var(--ctp-blue) 20%, transparent); color: var(--ctp-blue); }} .status-badge.error {{ background: color-mix(in srgb, var(--ctp-red) 20%, transparent); color: var(--ctp-red); }} /* === Terminal Area === */ .terminal-area {{ background: var(--ctp-crust); border-top: 1px solid var(--ctp-surface0); height: 8rem; overflow-y: auto; padding: 0.5rem; font-family: var(--term-font-family); font-size: var(--term-font-size); color: var(--ctp-green); flex-shrink: 0; }} .terminal-line {{ line-height: 1.4; white-space: pre-wrap; }} .terminal-prompt {{ color: var(--ctp-blue); }} .terminal-output {{ color: var(--ctp-subtext0); }} /* === Status Bar === */ .status-bar {{ display: flex; align-items: center; justify-content: space-between; padding: 0.25rem 0.75rem; background: var(--ctp-crust); border-top: 1px solid var(--ctp-surface0); font-size: 0.6875rem; color: var(--ctp-overlay1); flex-shrink: 0; height: 1.5rem; }} .status-bar-left, .status-bar-right {{ display: flex; align-items: center; gap: 1rem; }} .status-item {{ display: flex; align-items: center; gap: 0.25rem; }} .status-count {{ font-weight: 600; }} .status-count.running {{ color: var(--ctp-green); }} .status-count.idle {{ color: var(--ctp-overlay1); }} .status-count.stalled {{ color: var(--ctp-peach); }} .status-cost {{ color: var(--ctp-yellow); font-weight: 600; }} /* === Settings Panel === */ .settings-section {{ margin-bottom: 1.5rem; }} .settings-section-title {{ font-size: 0.75rem; font-weight: 600; color: var(--ctp-subtext0); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.75rem; padding-bottom: 0.375rem; border-bottom: 1px solid var(--ctp-surface0); }} .settings-row {{ display: flex; align-items: center; justify-content: space-between; padding: 0.375rem 0; font-size: 0.8125rem; }} .settings-label {{ color: var(--ctp-subtext1); }} .settings-value {{ color: var(--ctp-text); font-weight: 500; }} .settings-select {{ background: var(--ctp-surface0); border: 1px solid var(--ctp-surface1); border-radius: 0.25rem; padding: 0.25rem 0.5rem; color: var(--ctp-text); font-size: 0.75rem; outline: none; }} .settings-select:focus {{ border-color: var(--ctp-blue); }} /* === Command Palette === */ .palette-overlay {{ position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5); display: flex; align-items: flex-start; justify-content: center; padding-top: 20vh; z-index: 1000; }} .palette-box {{ width: 32rem; max-width: 90vw; background: var(--ctp-mantle); border: 1px solid var(--ctp-surface0); border-radius: 0.5rem; box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.4); overflow: hidden; }} .palette-input {{ width: 100%; padding: 0.75rem 1rem; background: transparent; border: none; border-bottom: 1px solid var(--ctp-surface0); color: var(--ctp-text); font-family: var(--ui-font-family); font-size: 0.9375rem; outline: none; }} .palette-input::placeholder {{ color: var(--ctp-overlay0); }} .palette-results {{ max-height: 20rem; overflow-y: auto; }} .palette-item {{ padding: 0.5rem 1rem; display: flex; align-items: center; gap: 0.75rem; cursor: pointer; transition: background 0.1s ease; }} .palette-item:hover, .palette-item.selected {{ background: var(--ctp-surface0); }} .palette-item-icon {{ color: var(--ctp-overlay1); font-size: 0.875rem; width: 1.25rem; text-align: center; }} .palette-item-label {{ color: var(--ctp-text); font-size: 0.8125rem; }} .palette-item-shortcut {{ margin-left: auto; font-size: 0.6875rem; color: var(--ctp-overlay0); font-family: var(--term-font-family); }} /* === Docs Tab === */ .docs-pane {{ padding: 0.75rem; overflow-y: auto; flex: 1; }} .docs-entry {{ padding: 0.375rem 0.5rem; border-radius: 0.25rem; cursor: pointer; color: var(--ctp-subtext1); font-size: 0.8125rem; transition: background 0.1s ease; }} .docs-entry:hover {{ background: var(--ctp-surface0); color: var(--ctp-text); }} /* === Files Tab === */ .files-pane {{ padding: 0.75rem; overflow-y: auto; flex: 1; }} .file-tree-item {{ display: flex; align-items: center; gap: 0.375rem; padding: 0.25rem 0.5rem; border-radius: 0.25rem; cursor: pointer; color: var(--ctp-subtext1); font-size: 0.75rem; font-family: var(--term-font-family); }} .file-tree-item:hover {{ background: var(--ctp-surface0); color: var(--ctp-text); }} .file-tree-icon {{ color: var(--ctp-overlay0); width: 1rem; text-align: center; }} /* === Provider Badge === */ .provider-badge {{ font-size: 0.5625rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; padding: 0.0625rem 0.3125rem; border-radius: 0.1875rem; background: color-mix(in srgb, var(--ctp-mauve) 15%, transparent); color: var(--ctp-mauve); }} "#, base = CatppuccinMocha::BASE, mantle = CatppuccinMocha::MANTLE, crust = CatppuccinMocha::CRUST, surface0 = CatppuccinMocha::SURFACE0, surface1 = CatppuccinMocha::SURFACE1, surface2 = CatppuccinMocha::SURFACE2, overlay0 = CatppuccinMocha::OVERLAY0, overlay1 = CatppuccinMocha::OVERLAY1, overlay2 = CatppuccinMocha::OVERLAY2, subtext0 = CatppuccinMocha::SUBTEXT0, subtext1 = CatppuccinMocha::SUBTEXT1, text = CatppuccinMocha::TEXT, rosewater = CatppuccinMocha::ROSEWATER, flamingo = CatppuccinMocha::FLAMINGO, pink = CatppuccinMocha::PINK, mauve = CatppuccinMocha::MAUVE, red = CatppuccinMocha::RED, maroon = CatppuccinMocha::MAROON, peach = CatppuccinMocha::PEACH, yellow = CatppuccinMocha::YELLOW, green = CatppuccinMocha::GREEN, teal = CatppuccinMocha::TEAL, sky = CatppuccinMocha::SKY, sapphire = CatppuccinMocha::SAPPHIRE, blue = CatppuccinMocha::BLUE, lavender = CatppuccinMocha::LAVENDER, ) }