diff --git a/ui/src/styles/base.css b/ui/src/styles/base.css index 54a2c6288..0ef14dd7e 100644 --- a/ui/src/styles/base.css +++ b/ui/src/styles/base.css @@ -1,40 +1,40 @@ @import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"); :root { - /* Background - Warmer dark with depth */ - --bg: #12141a; - --bg-accent: #14161d; - --bg-elevated: #1a1d25; - --bg-hover: #262a35; - --bg-muted: #262a35; + /* Background - Warm dark with sepia undertone */ + --bg: #0D0C07; + --bg-accent: #11100A; + --bg-elevated: #1B1A16; + --bg-hover: #252419; + --bg-muted: #252419; - /* Card / Surface - More contrast between levels */ - --card: #181b22; - --card-foreground: #f4f4f5; - --card-highlight: rgba(255, 255, 255, 0.05); - --popover: #181b22; - --popover-foreground: #f4f4f5; + /* Card / Surface - Clear hierarchy with warm tones */ + --card: #151410; + --card-foreground: #f4f4f0; + --card-highlight: rgba(255, 252, 245, 0.05); + --popover: #151410; + --popover-foreground: #f4f4f0; /* Panel */ - --panel: #12141a; - --panel-strong: #1a1d25; - --panel-hover: #262a35; - --chrome: rgba(18, 20, 26, 0.95); - --chrome-strong: rgba(18, 20, 26, 0.98); + --panel: #0D0C07; + --panel-strong: #1B1A16; + --panel-hover: #252419; + --chrome: rgba(13, 12, 7, 0.95); + --chrome-strong: rgba(13, 12, 7, 0.98); - /* Text - Slightly warmer */ - --text: #e4e4e7; - --text-strong: #fafafa; - --chat-text: #e4e4e7; - --muted: #71717a; - --muted-strong: #52525b; - --muted-foreground: #71717a; + /* Text - Warm white tones */ + --text: #e8e6e0; + --text-strong: #faf9f6; + --chat-text: #e8e6e0; + --muted: #7a776d; + --muted-strong: #5c5952; + --muted-foreground: #7a776d; - /* Border - Subtle but defined */ - --border: #27272a; - --border-strong: #3f3f46; - --border-hover: #52525b; - --input: #27272a; + /* Border - Warm and subtle */ + --border: #2a2820; + --border-strong: #3d3a30; + --border-hover: #524f42; + --input: #2a2820; --ring: #ff5c5c; /* Accent - Punchy signature red */ @@ -47,9 +47,9 @@ --primary: #ff5c5c; --primary-foreground: #ffffff; - /* Secondary - Teal accent for variety */ - --secondary: #1e2028; - --secondary-foreground: #f4f4f5; + /* Secondary - Warm elevated surface */ + --secondary: #1B1A16; + --secondary-foreground: #f4f4f0; --accent-2: #14b8a6; --accent-2-muted: rgba(20, 184, 166, 0.7); --accent-2-subtle: rgba(20, 184, 166, 0.15); @@ -74,7 +74,7 @@ --focus-glow: 0 0 0 2px var(--bg), 0 0 0 4px var(--ring), 0 0 20px var(--accent-glow); /* Grid */ - --grid-line: rgba(255, 255, 255, 0.04); + --grid-line: rgba(255, 252, 245, 0.04); /* Theme transition */ --theme-switch-x: 50%; @@ -111,77 +111,85 @@ color-scheme: dark; } -/* Light theme - Clean with subtle warmth */ +/* Light theme - Premium with warm gray tones and clear hierarchy */ :root[data-theme="light"] { - --bg: #fafafa; - --bg-accent: #f5f5f5; + /* Warm gray backgrounds with clear hierarchy */ + --bg: #f8f8f7; + --bg-accent: #f3f3f1; --bg-elevated: #ffffff; - --bg-hover: #f0f0f0; - --bg-muted: #f0f0f0; - --bg-content: #f5f5f5; + --bg-hover: #ebebea; + --bg-muted: #eeeeed; + --bg-content: #f3f3f1; + /* Cards stand out clearly against background */ --card: #ffffff; - --card-foreground: #18181b; - --card-highlight: rgba(0, 0, 0, 0.03); + --card-foreground: #1a1a1a; + --card-highlight: rgba(255, 255, 255, 0.8); --popover: #ffffff; - --popover-foreground: #18181b; + --popover-foreground: #1a1a1a; - --panel: #fafafa; - --panel-strong: #f5f5f5; - --panel-hover: #ebebeb; - --chrome: rgba(250, 250, 250, 0.95); - --chrome-strong: rgba(250, 250, 250, 0.98); + /* Panel - subtle warm tint */ + --panel: #f8f8f7; + --panel-strong: #f3f3f1; + --panel-hover: #e8e8e6; + --chrome: rgba(248, 248, 247, 0.95); + --chrome-strong: rgba(248, 248, 247, 0.98); - --text: #3f3f46; - --text-strong: #18181b; - --chat-text: #3f3f46; - --muted: #71717a; - --muted-strong: #52525b; - --muted-foreground: #71717a; + /* Text - rich contrast */ + --text: #3d3d3d; + --text-strong: #1a1a1a; + --chat-text: #3d3d3d; + --muted: #6b6b6b; + --muted-strong: #525252; + --muted-foreground: #6b6b6b; - --border: #e4e4e7; - --border-strong: #d4d4d8; - --border-hover: #a1a1aa; - --input: #e4e4e7; + /* Border - subtle definition with warm undertone */ + --border: #e0e0de; + --border-strong: #c8c8c5; + --border-hover: #a0a09d; + --input: #e0e0de; + /* Accent - confident red */ --accent: #dc2626; --accent-hover: #ef4444; --accent-muted: #dc2626; - --accent-subtle: rgba(220, 38, 38, 0.12); + --accent-subtle: rgba(220, 38, 38, 0.1); --accent-foreground: #ffffff; - --accent-glow: rgba(220, 38, 38, 0.15); + --accent-glow: rgba(220, 38, 38, 0.12); --primary: #dc2626; --primary-foreground: #ffffff; - --secondary: #f4f4f5; - --secondary-foreground: #3f3f46; + /* Secondary - warm off-white */ + --secondary: #f0f0ee; + --secondary-foreground: #3d3d3d; --accent-2: #0d9488; --accent-2-muted: rgba(13, 148, 136, 0.75); - --accent-2-subtle: rgba(13, 148, 136, 0.12); + --accent-2-subtle: rgba(13, 148, 136, 0.1); + /* Semantic colors - slightly muted for light theme */ --ok: #16a34a; --ok-muted: rgba(22, 163, 74, 0.75); - --ok-subtle: rgba(22, 163, 74, 0.1); + --ok-subtle: rgba(22, 163, 74, 0.08); --destructive: #dc2626; --destructive-foreground: #fafafa; --warn: #d97706; --warn-muted: rgba(217, 119, 6, 0.75); - --warn-subtle: rgba(217, 119, 6, 0.1); + --warn-subtle: rgba(217, 119, 6, 0.08); --danger: #dc2626; --danger-muted: rgba(220, 38, 38, 0.75); - --danger-subtle: rgba(220, 38, 38, 0.1); + --danger-subtle: rgba(220, 38, 38, 0.08); --info: #2563eb; --focus: rgba(220, 38, 38, 0.2); --focus-glow: 0 0 0 2px var(--bg), 0 0 0 4px var(--ring), 0 0 16px var(--accent-glow); - --grid-line: rgba(0, 0, 0, 0.05); + --grid-line: rgba(0, 0, 0, 0.04); - /* Light shadows */ - --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06); - --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(0, 0, 0, 0.04); - --shadow-lg: 0 12px 28px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(0, 0, 0, 0.04); - --shadow-xl: 0 24px 48px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.04); + /* Light shadows - crisper with subtle warmth */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05), 0 1px 1px rgba(0, 0, 0, 0.03); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.04); + --shadow-lg: 0 12px 28px rgba(0, 0, 0, 0.08), 0 4px 8px rgba(0, 0, 0, 0.04); + --shadow-xl: 0 24px 48px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.05); --shadow-glow: 0 0 24px var(--accent-glow); color-scheme: light; @@ -206,36 +214,17 @@ body { -moz-osx-font-smoothing: grayscale; } -/* Theme transition */ -@keyframes theme-circle-transition { - 0% { - clip-path: circle(0% at var(--theme-switch-x, 50%) var(--theme-switch-y, 50%)); - } - 100% { - clip-path: circle(150% at var(--theme-switch-x, 50%) var(--theme-switch-y, 50%)); - } -} - -html.theme-transition { - view-transition-name: theme; -} - -html.theme-transition::view-transition-old(theme) { - mix-blend-mode: normal; - animation: none; - z-index: 1; -} - -html.theme-transition::view-transition-new(theme) { - mix-blend-mode: normal; - z-index: 2; - animation: theme-circle-transition 0.4s var(--ease-out) forwards; +/* Theme transition - clean crossfade */ +::view-transition-old(root), +::view-transition-new(root) { + animation-duration: 0.15s; + animation-timing-function: ease-out; } @media (prefers-reduced-motion: reduce) { - html.theme-transition::view-transition-old(theme), - html.theme-transition::view-transition-new(theme) { - animation: none !important; + ::view-transition-old(root), + ::view-transition-new(root) { + animation: none; } } diff --git a/ui/src/styles/chat/layout.css b/ui/src/styles/chat/layout.css index 589b0b62d..04bef1647 100644 --- a/ui/src/styles/chat/layout.css +++ b/ui/src/styles/chat/layout.css @@ -344,6 +344,12 @@ color: var(--text); } +:root[data-theme="light"] .btn--icon.active { + border-color: var(--accent); + background: var(--accent-subtle); + color: var(--accent); +} + .btn--icon svg { display: block; width: 18px; diff --git a/ui/src/styles/components.css b/ui/src/styles/components.css index 27dfe62d1..937edea2d 100644 --- a/ui/src/styles/components.css +++ b/ui/src/styles/components.css @@ -49,6 +49,8 @@ border-color var(--duration-normal) var(--ease-out), box-shadow var(--duration-normal) var(--ease-out); box-shadow: inset 0 1px 0 var(--card-highlight); + min-width: 0; + overflow: hidden; } .stat:hover { @@ -62,6 +64,9 @@ font-weight: 500; text-transform: uppercase; letter-spacing: 0.04em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .stat-value { @@ -70,6 +75,9 @@ margin-top: 6px; letter-spacing: -0.03em; line-height: 1.1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .stat-value.ok { @@ -201,6 +209,30 @@ color: var(--danger); } +/* Health pill - prominent status indicator */ +.pill--health { + font-weight: 600; + letter-spacing: -0.01em; +} + +.pill--health.ok { + border-color: rgba(34, 197, 94, 0.2); + background: var(--ok-subtle); +} + +.pill--health.ok .mono { + color: var(--ok); +} + +.pill--health.offline { + border-color: rgba(245, 158, 11, 0.2); + background: var(--warn-subtle); +} + +.pill--health.offline .mono { + color: var(--warn); +} + /* =========================================== Theme Toggle =========================================== */ @@ -326,7 +358,7 @@ .btn:active { background: var(--secondary); - transform: translateY(0); + transform: translateY(0) scale(0.98); box-shadow: none; } @@ -447,6 +479,14 @@ .field select:focus { border-color: var(--ring); box-shadow: var(--focus-ring); + outline: none; +} + +.field input:focus-visible, +.field textarea:focus-visible, +.field select:focus-visible { + border-color: var(--ring); + box-shadow: var(--focus-glow); } .field select { @@ -467,16 +507,49 @@ } .field.checkbox { - grid-template-columns: auto 1fr; + display: flex; + flex-direction: row; align-items: center; + gap: 8px; +} + +.field.checkbox span { + order: 2; +} + +.field.checkbox input[type="checkbox"] { + order: 1; + width: 16px; + height: 16px; + margin: 0; + accent-color: var(--accent); + cursor: pointer; +} + +/* Checkboxes in filter rows - align to bottom like other fields */ +.filters .field.checkbox { + align-self: flex-end; + padding-bottom: 10px; +} + +/* Checkboxes in form grids - align to bottom of grid cell */ +.form-grid .field.checkbox { + align-self: end; + padding-bottom: 8px; } .config-form .field.checkbox { + display: grid; grid-template-columns: 18px minmax(0, 1fr); column-gap: 10px; } +.config-form .field.checkbox span { + order: unset; +} + .config-form .field.checkbox input[type="checkbox"] { + order: unset; margin: 0; width: 16px; height: 16px; @@ -522,6 +595,39 @@ font-family: var(--mono); } +/* =========================================== + Keyboard Shortcuts (kbd) + =========================================== */ + +kbd { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 20px; + height: 20px; + padding: 0 6px; + font-family: var(--font-body); + font-size: 11px; + font-weight: 500; + line-height: 1; + color: var(--muted); + background: var(--secondary); + border: 1px solid var(--border); + border-bottom-width: 2px; + border-radius: 4px; + box-shadow: 0 1px 0 var(--border); +} + +kbd + kbd { + margin-left: 4px; +} + +:root[data-theme="light"] kbd { + background: var(--bg); + border-color: var(--border-strong); + box-shadow: 0 1px 0 var(--border-strong); +} + /* =========================================== Callouts - Informative with subtle depth =========================================== */ @@ -554,6 +660,156 @@ color: var(--ok); } +.callout.warn { + border-color: rgba(245, 158, 11, 0.25); + background: linear-gradient(135deg, rgba(245, 158, 11, 0.08) 0%, rgba(245, 158, 11, 0.04) 100%); + color: var(--warn); +} + +/* =========================================== + Empty States - Clear and helpful + =========================================== */ + +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + padding: 48px 24px; + text-align: center; + color: var(--muted); +} + +.empty-state__icon { + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-lg); + background: var(--secondary); + border: 1px solid var(--border); + color: var(--muted); + margin-bottom: 4px; +} + +.empty-state__icon svg { + width: 24px; + height: 24px; + stroke: currentColor; + fill: none; + stroke-width: 1.5px; + stroke-linecap: round; + stroke-linejoin: round; +} + +.empty-state__title { + font-size: 15px; + font-weight: 600; + color: var(--text); + letter-spacing: -0.01em; +} + +.empty-state__description { + font-size: 13px; + max-width: 320px; + line-height: 1.5; +} + +.empty-state__action { + margin-top: 8px; +} + +/* Disconnected state - More prominent */ +.disconnected-state { + display: flex; + align-items: center; + gap: 14px; + padding: 16px 20px; + border-radius: var(--radius-lg); + border: 1px dashed var(--border-strong); + background: var(--secondary); + animation: rise 0.35s var(--ease-out) backwards; +} + +.disconnected-state__icon { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-md); + background: var(--warn-subtle); + color: var(--warn); + flex-shrink: 0; +} + +.disconnected-state__icon svg { + width: 20px; + height: 20px; + stroke: currentColor; + fill: none; + stroke-width: 2px; + stroke-linecap: round; + stroke-linejoin: round; +} + +.disconnected-state__content { + flex: 1; + min-width: 0; +} + +.disconnected-state__title { + font-size: 14px; + font-weight: 600; + color: var(--text); + letter-spacing: -0.01em; +} + +.disconnected-state__description { + font-size: 13px; + color: var(--muted); + margin-top: 2px; +} + +.disconnected-state__action { + flex-shrink: 0; +} + +/* Loading state placeholder */ +.loading-placeholder { + display: flex; + flex-direction: column; + gap: 12px; + padding: 24px; +} + +.loading-placeholder__bar { + height: 12px; + border-radius: var(--radius-sm); + background: linear-gradient( + 90deg, + var(--secondary) 25%, + var(--bg-hover) 50%, + var(--secondary) 75% + ); + background-size: 200% 100%; + animation: shimmer 1.5s infinite; +} + +.loading-placeholder__bar--short { + width: 60%; +} + +.loading-placeholder__bar--medium { + width: 80%; +} + +.loading-placeholder__bar--full { + width: 100%; +} + /* Compaction indicator */ .compaction-indicator { font-size: 13px; @@ -603,6 +859,23 @@ background: var(--bg); } +/* Light theme cards - subtle shadow for elevation */ +:root[data-theme="light"] .card { + box-shadow: var(--shadow-sm); +} + +:root[data-theme="light"] .card:hover { + box-shadow: var(--shadow-md); +} + +:root[data-theme="light"] .stat { + box-shadow: var(--shadow-sm); +} + +:root[data-theme="light"] .stat:hover { + box-shadow: var(--shadow-md); +} + /* =========================================== Lists =========================================== */ @@ -1484,3 +1757,137 @@ flex-wrap: wrap; gap: 8px; } + +/* =========================================== + Crab Loader - Processing animation + =========================================== */ + +.crab-loader { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20px; + padding: 32px 24px; +} + +.crab-loader__crab { + position: relative; + animation: crab-walk 0.6s ease-in-out infinite; +} + +.crab-loader__svg { + display: block; +} + +.crab-loader--sm .crab-loader__svg { + width: 32px; + height: auto; +} + +.crab-loader--md .crab-loader__svg { + width: 56px; + height: auto; +} + +.crab-loader--lg .crab-loader__svg { + width: 96px; + height: auto; +} + +.crab-loader__claw-left { + transform-origin: 52px 78px; + animation: claw-pinch-left 0.4s ease-in-out infinite; +} + +.crab-loader__claw-right { + transform-origin: 468px 78px; + animation: claw-pinch-right 0.4s ease-in-out infinite; +} + +.crab-loader__text { + display: flex; + align-items: center; + gap: 4px; + color: var(--muted); + font-size: 14px; + font-weight: 500; +} + +.crab-loader__dots { + display: flex; + gap: 3px; + margin-left: 2px; +} + +.crab-loader__dot { + width: 4px; + height: 4px; + border-radius: var(--radius-full); + background: var(--muted); + animation: dot-bounce 1.2s ease-in-out infinite; +} + +.crab-loader__dot:nth-child(2) { + animation-delay: 0.2s; +} + +.crab-loader__dot:nth-child(3) { + animation-delay: 0.4s; +} + +/* Crab walking animation - side to side sway */ +@keyframes crab-walk { + 0%, 100% { + transform: translateX(-2px) rotate(-2deg); + } + 50% { + transform: translateX(2px) rotate(2deg); + } +} + +/* Left claw pinching */ +@keyframes claw-pinch-left { + 0%, 100% { + transform: rotate(0deg); + } + 50% { + transform: rotate(15deg); + } +} + +/* Right claw pinching */ +@keyframes claw-pinch-right { + 0%, 100% { + transform: rotate(0deg); + } + 50% { + transform: rotate(-15deg); + } +} + +/* Bouncing dots */ +@keyframes dot-bounce { + 0%, 80%, 100% { + opacity: 0.4; + transform: translateY(0); + } + 40% { + opacity: 1; + transform: translateY(-3px); + } +} + +/* Reduced motion */ +@media (prefers-reduced-motion: reduce) { + .crab-loader__crab, + .crab-loader__claw-left, + .crab-loader__claw-right, + .crab-loader__dot { + animation: none; + } + + .crab-loader__dot { + opacity: 0.6; + } +} diff --git a/ui/src/styles/layout.css b/ui/src/styles/layout.css index c2a5c6fe3..cad7a08a5 100644 --- a/ui/src/styles/layout.css +++ b/ui/src/styles/layout.css @@ -430,6 +430,17 @@ background: var(--bg-content); } +/* Light theme nav - subtle background difference */ +:root[data-theme="light"] .nav { + background: var(--bg); + border-right: 1px solid var(--border); +} + +:root[data-theme="light"] .topbar { + background: var(--bg); + box-shadow: 0 1px 0 var(--border); +} + .content--chat { overflow: hidden; padding-bottom: 0; @@ -437,19 +448,20 @@ /* Content header */ .content-header { + position: relative; display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; - padding: 4px 8px; - overflow: hidden; + padding: 4px 8px 12px; + overflow: visible; transform-origin: top center; transition: opacity var(--shell-focus-duration) var(--shell-focus-ease), transform var(--shell-focus-duration) var(--shell-focus-ease), max-height var(--shell-focus-duration) var(--shell-focus-ease), padding var(--shell-focus-duration) var(--shell-focus-ease); - max-height: 80px; + max-height: 90px; } .shell--chat-focus .content-header { @@ -481,6 +493,28 @@ gap: 8px; } +/* Page header separator - subtle gradient fade */ +.content-header::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 1px; + background: linear-gradient( + 90deg, + transparent 0%, + var(--border) 20%, + var(--border) 80%, + transparent 100% + ); + opacity: 0.6; +} + +.shell--chat-focus .content-header::after { + display: none; +} + /* Chat view header adjustments */ .content--chat .content-header { flex-direction: row; diff --git a/ui/src/ui/app-render.helpers.ts b/ui/src/ui/app-render.helpers.ts index c2190e1c9..e32a11154 100644 --- a/ui/src/ui/app-render.helpers.ts +++ b/ui/src/ui/app-render.helpers.ts @@ -2,7 +2,7 @@ import { html } from "lit"; import { repeat } from "lit/directives/repeat.js"; import type { AppViewState } from "./app-view-state"; -import { iconForTab, pathForTab, titleForTab, type Tab } from "./navigation"; +import { iconForTab, pathForTab, titleForTab, TAB_GROUPS, type Tab } from "./navigation"; import { icons } from "./icons"; import { loadChatHistory } from "./controllers/chat"; import { refreshChat } from "./app-chat"; @@ -29,6 +29,13 @@ export function renderTab(state: AppViewState, tab: Tab) { return; } event.preventDefault(); + // Auto-expand the group containing this tab + const group = TAB_GROUPS.find((g) => g.tabs.includes(tab)); + if (group && state.settings.navGroupsCollapsed[group.label]) { + const next = { ...state.settings.navGroupsCollapsed }; + next[group.label] = false; + state.applySettings({ ...state.settings, navGroupsCollapsed: next }); + } state.setTab(tab); }} title=${titleForTab(tab)} diff --git a/ui/src/ui/app-render.ts b/ui/src/ui/app-render.ts index d692693cb..aa0cd6213 100644 --- a/ui/src/ui/app-render.ts +++ b/ui/src/ui/app-render.ts @@ -139,7 +139,7 @@ export function renderApp(state: AppViewState) {