From 8fa6fdec1ff7590b48040bb6ecbe325930cf8d9c Mon Sep 17 00:00:00 2001 From: vikingowl Date: Thu, 1 Jan 2026 04:46:31 +0100 Subject: [PATCH] feat: implement light/dark theme toggle with CSS custom properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add CSS custom properties for theme colors (:root and .dark) - Create utility classes: bg-theme-*, text-theme-*, border-theme-* - Update +layout.svelte main containers - Update Sidenav with theme-aware navigation links - Update TopNav header and action buttons - Update ChatWindow main area and input section - Update ChatInput with themed input container - Update MessageItem with theme-aware message bubbles - Update EmptyState with themed welcome cards Theme colors automatically switch between light and dark mode when clicking the theme toggle button in the top navigation. 馃 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- frontend/src/app.css | 59 +++++++++++++++++++ .../src/lib/components/chat/ChatInput.svelte | 32 +++++----- .../src/lib/components/chat/ChatWindow.svelte | 12 ++-- .../src/lib/components/chat/EmptyState.svelte | 14 ++--- .../lib/components/chat/MessageItem.svelte | 12 ++-- .../src/lib/components/layout/Sidenav.svelte | 14 ++--- .../src/lib/components/layout/TopNav.svelte | 16 ++--- frontend/src/routes/+layout.svelte | 4 +- 8 files changed, 111 insertions(+), 52 deletions(-) diff --git a/frontend/src/app.css b/frontend/src/app.css index 942dd40..c28ffc6 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -2,10 +2,69 @@ @tailwind components; @tailwind utilities; +/* Theme colors using CSS custom properties */ +:root { + /* Light mode (default) */ + --color-bg-primary: theme('colors.slate.50'); + --color-bg-secondary: theme('colors.slate.100'); + --color-bg-tertiary: theme('colors.slate.200'); + --color-bg-sidenav: theme('colors.slate.100'); + --color-bg-topnav: theme('colors.white'); + --color-bg-input: theme('colors.white'); + --color-bg-hover: theme('colors.slate.200'); + --color-bg-message-user: theme('colors.slate.200'); + --color-bg-message-assistant: transparent; + --color-text-primary: theme('colors.slate.900'); + --color-text-secondary: theme('colors.slate.600'); + --color-text-muted: theme('colors.slate.500'); + --color-text-placeholder: theme('colors.slate.400'); + --color-border: theme('colors.slate.300'); + --color-border-subtle: theme('colors.slate.200'); +} + +.dark { + /* Dark mode */ + --color-bg-primary: theme('colors.slate.900'); + --color-bg-secondary: theme('colors.slate.800'); + --color-bg-tertiary: theme('colors.slate.700'); + --color-bg-sidenav: theme('colors.slate.950'); + --color-bg-topnav: theme('colors.slate.900'); + --color-bg-input: theme('colors.slate.800'); + --color-bg-hover: theme('colors.slate.700'); + --color-bg-message-user: theme('colors.slate.700'); + --color-bg-message-assistant: transparent; + --color-text-primary: theme('colors.slate.100'); + --color-text-secondary: theme('colors.slate.300'); + --color-text-muted: theme('colors.slate.400'); + --color-text-placeholder: theme('colors.slate.500'); + --color-border: theme('colors.slate.700'); + --color-border-subtle: theme('colors.slate.800'); +} + +/* Utility classes for theme colors */ +.bg-theme-primary { background-color: var(--color-bg-primary); } +.bg-theme-secondary { background-color: var(--color-bg-secondary); } +.bg-theme-tertiary { background-color: var(--color-bg-tertiary); } +.bg-theme-sidenav { background-color: var(--color-bg-sidenav); } +.bg-theme-topnav { background-color: var(--color-bg-topnav); } +.bg-theme-input { background-color: var(--color-bg-input); } +.bg-theme-hover { background-color: var(--color-bg-hover); } +.bg-theme-message-user { background-color: var(--color-bg-message-user); } +.text-theme-primary { color: var(--color-text-primary); } +.text-theme-secondary { color: var(--color-text-secondary); } +.text-theme-muted { color: var(--color-text-muted); } +.text-theme-placeholder { color: var(--color-text-placeholder); } +.border-theme { border-color: var(--color-border); } +.border-theme-subtle { border-color: var(--color-border-subtle); } +.hover\:bg-theme-hover:hover { background-color: var(--color-bg-hover); } +.placeholder-theme-placeholder::placeholder { color: var(--color-text-placeholder); } + /* Base styles */ html, body { @apply h-full; + background-color: var(--color-bg-primary); + color: var(--color-text-primary); } /* Text selection styling for dark theme */ diff --git a/frontend/src/lib/components/chat/ChatInput.svelte b/frontend/src/lib/components/chat/ChatInput.svelte index ad33380..fb772a8 100644 --- a/frontend/src/lib/components/chat/ChatInput.svelte +++ b/frontend/src/lib/components/chat/ChatInput.svelte @@ -258,8 +258,8 @@ {#if isDragOver} -
-
+
+
@@ -270,7 +270,7 @@ Drop files here {/if} - + {#if isVisionModel} Images, text files, and PDFs supported {:else} @@ -295,7 +295,7 @@ />
{#if pendingImages.length > 0 || pendingAttachments.length > 0} @@ -346,7 +346,7 @@ {placeholder} {disabled} rows="1" - class="max-h-[200px] min-h-[40px] flex-1 resize-none bg-transparent px-1 py-1.5 text-slate-100 placeholder-slate-500 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50" + class="max-h-[200px] min-h-[40px] flex-1 resize-none bg-transparent px-1 py-1.5 text-theme-primary placeholder-theme-placeholder focus:outline-none disabled:cursor-not-allowed disabled:opacity-50" aria-label="Message input" data-chat-input > @@ -379,7 +379,7 @@ disabled={!canSend} class="flex h-9 w-9 items-center justify-center rounded-xl transition-colors focus:outline-none focus:ring-2 focus:ring-violet-500/50 {canSend ? 'bg-violet-500/20 text-violet-400 hover:bg-violet-500/30 hover:text-violet-300' - : 'text-slate-600 cursor-not-allowed'}" + : 'text-theme-muted cursor-not-allowed'}" aria-label="Send message" title="Send message" > @@ -397,19 +397,19 @@
-

- Enter send - - Shift+Enter new line - +

+ Enter send + + Shift+Enter new line + {#if isVisionModel} - images - + + images + + {/if} - files supported + files supported {#if showTokenCount} - - + + ~{formatTokenCount(tokenEstimate.totalTokens)} tokens {/if} diff --git a/frontend/src/lib/components/chat/ChatWindow.svelte b/frontend/src/lib/components/chat/ChatWindow.svelte index 91a17f3..56b2d3a 100644 --- a/frontend/src/lib/components/chat/ChatWindow.svelte +++ b/frontend/src/lib/components/chat/ChatWindow.svelte @@ -807,7 +807,7 @@ } -

+
{#if hasMessages}
-
+
-
+
@@ -850,8 +850,8 @@
-

+

{#if hasModel} Start a conversation {:else} @@ -97,9 +97,9 @@ {/if}

-

+

{#if hasModel && selectedModel} - Chatting with {selectedModel.name} + Chatting with {selectedModel.name} {:else} Select a model from the sidebar to start chatting {/if} @@ -155,9 +155,9 @@ onclick={() => selectPrompt(props.type)} class="flex items-start gap-3 rounded-xl border p-3 text-left transition-all {active ? 'border-violet-500/50 bg-violet-500/10' - : 'border-slate-800/50 bg-slate-800/30 hover:border-slate-700 hover:bg-slate-800/60'}" + : 'border-theme hover:border-theme-subtle bg-theme-secondary/30 hover:bg-theme-secondary/60'}" > -

+
{#if props.icon === 'lightbulb'} @@ -177,8 +177,8 @@ {/if}
-

{props.title}

-

{props.description}

+

{props.title}

+

{props.description}

{/snippet} diff --git a/frontend/src/lib/components/chat/MessageItem.svelte b/frontend/src/lib/components/chat/MessageItem.svelte index 3b7495b..9d95d2c 100644 --- a/frontend/src/lib/components/chat/MessageItem.svelte +++ b/frontend/src/lib/components/chat/MessageItem.svelte @@ -123,10 +123,10 @@
- Conversation Summary - Earlier messages compressed + Conversation Summary + Earlier messages compressed
-