Add light theme
14 files changed,  +367, -192
M src/app.html
+6, -0
 1@@ -3,6 +3,12 @@
 2 	<head>
 3 		<meta charset="utf-8" />
 4 		<meta name="viewport" content="width=device-width, initial-scale=1" />
 5+		<script>
 6+			(function() {
 7+				var t = localStorage.getItem('lcr-theme') || 'dark';
 8+				document.documentElement.setAttribute('data-theme', t);
 9+			})();
10+		</script>
11 		%sveltekit.head%
12 	</head>
13 	<body data-sveltekit-preload-data="hover">
M src/lib/components/CommentEditor.svelte
+5, -5
 1@@ -51,12 +51,12 @@
 2 	}
 3 </script>
 4 
 5-<div class="bg-zinc-800 border border-zinc-600 rounded-lg p-3 my-1 mx-2">
 6+<div class="bg-panel border border-border-strong rounded-lg p-3 my-1 mx-2">
 7 	<div class="flex gap-1 mb-2">
 8 		{#each ['comment', 'suggestion', 'question'] as type}
 9 			{@const active = commentType === type}
10 			<button
11-				class="text-xs px-2 py-1 rounded transition-colors {active ? 'bg-zinc-600 text-white' : 'text-zinc-400 hover:text-zinc-300 hover:bg-zinc-700'}"
12+				class="text-xs px-2 py-1 rounded transition-colors {active ? 'bg-active text-primary' : 'text-tertiary hover:text-secondary hover:bg-hover'}"
13 				onclick={() => (commentType = type as typeof commentType)}
14 			>
15 				{type === 'comment' ? '💬' : type === 'suggestion' ? '💡' : '❓'}
16@@ -69,16 +69,16 @@
17 		bind:value={text}
18 		onkeydown={handleKeydown}
19 		placeholder="Leave a {commentType}..."
20-		class="w-full bg-zinc-900 text-zinc-200 border border-zinc-600 rounded px-3 py-2 text-sm resize-y min-h-[80px] focus:outline-none focus:border-blue-500 placeholder:text-zinc-500"
21+		class="w-full bg-surface text-primary border border-border-strong rounded px-3 py-2 text-sm resize-y min-h-[80px] focus:outline-none focus:border-blue-500 placeholder:text-muted"
22 		rows="3"
23 	></textarea>
24 	<div class="flex items-center justify-between mt-2">
25-		<span class="text-xs text-zinc-500">
26+		<span class="text-xs text-muted">
27 			{navigator?.platform?.includes('Mac') ? '⌘' : 'Ctrl'}+Enter to submit
28 		</span>
29 		<div class="flex gap-2">
30 			<button
31-				class="text-xs px-3 py-1.5 rounded text-zinc-400 hover:text-zinc-200 hover:bg-zinc-700 transition-colors"
32+				class="text-xs px-3 py-1.5 rounded text-tertiary hover:text-primary hover:bg-hover transition-colors"
33 				onclick={cancel}
34 			>
35 				Cancel
M src/lib/components/CommentThread.svelte
+13, -13
 1@@ -22,40 +22,40 @@
 2 	}
 3 </script>
 4 
 5-<div class="border-l-2 {thread.outdated ? 'border-yellow-500/60 opacity-75' : thread.resolved ? 'border-zinc-600 opacity-60' : 'border-blue-500'} bg-zinc-800/50 rounded-r-lg my-1 mx-2">
 6+<div class="border-l-2 {thread.outdated ? 'border-accent-yellow/60 opacity-75' : thread.resolved ? 'border-border-strong opacity-60' : 'border-accent-blue'} bg-panel/50 rounded-r-lg my-1 mx-2">
 7 	{#if thread.outdated}
 8-		<div class="px-3 py-1.5 bg-yellow-900/20 border-b border-zinc-700 flex items-center gap-2">
 9-			<span class="text-xs font-medium text-yellow-400">Outdated</span>
10-			<span class="text-xs text-yellow-500/70">The code this comment was on has changed</span>
11+		<div class="px-3 py-1.5 bg-accent-yellow/20 border-b border-border flex items-center gap-2">
12+			<span class="text-xs font-medium text-accent-yellow">Outdated</span>
13+			<span class="text-xs text-accent-yellow/70">The code this comment was on has changed</span>
14 		</div>
15 	{/if}
16 	{#each thread.comments as comment, i}
17-		<div class="px-3 py-2 {i > 0 ? 'border-t border-zinc-700' : ''}">
18+		<div class="px-3 py-2 {i > 0 ? 'border-t border-border' : ''}">
19 			<div class="flex items-center gap-2 mb-1">
20 				<span class="text-sm">{typeIcons[comment.type]}</span>
21-				<span class="text-xs font-medium text-zinc-300 capitalize">{comment.type}</span>
22-				<span class="text-xs text-zinc-500">{formatTime(comment.createdAt)}</span>
23-				<span class="text-xs text-zinc-600">Line {comment.lineNumber}</span>
24+				<span class="text-xs font-medium text-secondary capitalize">{comment.type}</span>
25+				<span class="text-xs text-muted">{formatTime(comment.createdAt)}</span>
26+				<span class="text-xs text-faint">Line {comment.lineNumber}</span>
27 			</div>
28-			<p class="text-sm text-zinc-200 whitespace-pre-wrap">{comment.body}</p>
29+			<p class="text-sm text-primary whitespace-pre-wrap">{comment.body}</p>
30 		</div>
31 	{/each}
32 
33-	<div class="px-3 py-2 border-t border-zinc-700 flex gap-2">
34+	<div class="px-3 py-2 border-t border-border flex gap-2">
35 		<button
36-			class="text-xs text-zinc-400 hover:text-blue-400 transition-colors"
37+			class="text-xs text-tertiary hover:text-accent-blue transition-colors"
38 			onclick={() => (replying = !replying)}
39 		>
40 			Reply
41 		</button>
42 		<button
43-			class="text-xs text-zinc-400 hover:text-green-400 transition-colors"
44+			class="text-xs text-tertiary hover:text-accent-green transition-colors"
45 			onclick={() => resolveThread(thread.id)}
46 		>
47 			{thread.resolved ? 'Unresolve' : 'Resolve'}
48 		</button>
49 		<button
50-			class="text-xs text-zinc-400 hover:text-red-400 transition-colors"
51+			class="text-xs text-tertiary hover:text-accent-red transition-colors"
52 			onclick={() => deleteThread(thread.id)}
53 		>
54 			Delete
M src/lib/components/CommitDialog.svelte
+18, -18
 1@@ -47,22 +47,22 @@
 2 	<div class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center" onclick={close}>
 3 		<!-- svelte-ignore a11y_click_events_have_key_events -->
 4 		<!-- svelte-ignore a11y_no_static_element_interactions -->
 5-		<div class="bg-zinc-800 border border-zinc-600 rounded-xl shadow-2xl w-full max-w-lg mx-4" onclick={(e) => e.stopPropagation()}>
 6-			<div class="px-5 py-4 border-b border-zinc-700">
 7-				<h2 class="text-lg font-semibold text-zinc-200">Commit Changes</h2>
 8+		<div class="bg-panel border border-border-strong rounded-xl shadow-2xl w-full max-w-lg mx-4" onclick={(e) => e.stopPropagation()}>
 9+			<div class="px-5 py-4 border-b border-border">
10+				<h2 class="text-lg font-semibold text-primary">Commit Changes</h2>
11 			</div>
12 
13 			{#if result?.success}
14 				<div class="p-5">
15 					<div class="text-center">
16-						<div class="text-green-400 text-4xl mb-3">✓</div>
17-						<p class="text-zinc-200 font-medium">Committed successfully</p>
18-						<p class="text-zinc-400 text-sm mt-1">Hash: {result.hash}</p>
19+						<div class="text-accent-green text-4xl mb-3">✓</div>
20+						<p class="text-primary font-medium">Committed successfully</p>
21+						<p class="text-tertiary text-sm mt-1">Hash: {result.hash}</p>
22 					</div>
23 				</div>
24-				<div class="px-5 py-3 border-t border-zinc-700 flex justify-end">
25+				<div class="px-5 py-3 border-t border-border flex justify-end">
26 					<button
27-						class="text-sm px-4 py-2 rounded bg-zinc-600 text-white hover:bg-zinc-500 transition-colors"
28+						class="text-sm px-4 py-2 rounded bg-active text-primary hover:opacity-80 transition-colors"
29 						onclick={close}
30 					>
31 						Done
32@@ -72,17 +72,17 @@
33 				<div class="p-5 space-y-4">
34 					<!-- Commit scope -->
35 					<div>
36-						<label class="block text-sm text-zinc-400 mb-2">What to commit</label>
37+						<label class="block text-sm text-tertiary mb-2">What to commit</label>
38 						<div class="flex gap-2">
39 							<button
40-								class="flex-1 text-left px-3 py-2 rounded-lg border text-sm transition-colors {commitMode === 'approved' ? 'border-green-600 bg-green-600/10 text-green-400' : 'border-zinc-600 text-zinc-400 hover:border-zinc-500'}"
41+								class="flex-1 text-left px-3 py-2 rounded-lg border text-sm transition-colors {commitMode === 'approved' ? 'border-green-600 bg-accent-green/10 text-accent-green' : 'border-border-strong text-tertiary hover:border-border-strong'}"
42 								onclick={() => (commitMode = 'approved')}
43 							>
44 								<div class="font-medium">Approved files only</div>
45 								<div class="text-xs mt-0.5 opacity-70">{$approvedCount.approved} of {$approvedCount.total} files</div>
46 							</button>
47 							<button
48-								class="flex-1 text-left px-3 py-2 rounded-lg border text-sm transition-colors {commitMode === 'all' ? 'border-blue-600 bg-blue-600/10 text-blue-400' : 'border-zinc-600 text-zinc-400 hover:border-zinc-500'}"
49+								class="flex-1 text-left px-3 py-2 rounded-lg border text-sm transition-colors {commitMode === 'all' ? 'border-blue-600 bg-accent-blue/10 text-accent-blue' : 'border-border-strong text-tertiary hover:border-border-strong'}"
50 								onclick={() => (commitMode = 'all')}
51 							>
52 								<div class="font-medium">All changes</div>
53@@ -92,36 +92,36 @@
54 					</div>
55 
56 					{#if commitMode === 'approved' && $approvedCount.approved === 0}
57-						<div class="text-sm rounded-lg px-3 py-2 bg-yellow-500/10 text-yellow-400">
58+						<div class="text-sm rounded-lg px-3 py-2 bg-accent-yellow/10 text-accent-yellow">
59 							No files approved yet. Approve files in the sidebar first, or commit all changes.
60 						</div>
61 					{/if}
62 
63 					<!-- Commit message -->
64 					<div>
65-						<label class="block text-sm text-zinc-400 mb-2" for="commit-msg">Commit message</label>
66+						<label class="block text-sm text-tertiary mb-2" for="commit-msg">Commit message</label>
67 						<textarea
68 							id="commit-msg"
69 							bind:value={message}
70 							onkeydown={handleKeydown}
71-							class="w-full bg-zinc-900 text-zinc-200 border border-zinc-600 rounded-lg px-3 py-2 text-sm resize-y min-h-[100px] focus:outline-none focus:border-blue-500 placeholder:text-zinc-500"
72+							class="w-full bg-surface text-primary border border-border-strong rounded-lg px-3 py-2 text-sm resize-y min-h-[100px] focus:outline-none focus:border-blue-500 placeholder:text-muted"
73 							placeholder="Describe your changes..."
74 							rows="4"
75 						></textarea>
76 					</div>
77 
78 					{#if result?.error}
79-						<p class="text-red-400 text-sm">{result.error}</p>
80+						<p class="text-accent-red text-sm">{result.error}</p>
81 					{/if}
82 				</div>
83 
84-				<div class="px-5 py-3 border-t border-zinc-700 flex items-center justify-between">
85-					<span class="text-xs text-zinc-500">
86+				<div class="px-5 py-3 border-t border-border flex items-center justify-between">
87+					<span class="text-xs text-muted">
88 						{navigator?.platform?.includes('Mac') ? '⌘' : 'Ctrl'}+Enter to commit
89 					</span>
90 					<div class="flex gap-2">
91 						<button
92-							class="text-sm px-4 py-2 rounded text-zinc-400 hover:text-zinc-200 hover:bg-zinc-700 transition-colors"
93+							class="text-sm px-4 py-2 rounded text-tertiary hover:text-primary hover:bg-hover transition-colors"
94 							onclick={close}
95 						>
96 							Cancel
M src/lib/components/DiffView.svelte
+39, -39
  1@@ -40,8 +40,8 @@
  2 	}
  3 
  4 	function lineBg(type: string): string {
  5-		if (type === 'add') return 'bg-green-950/40';
  6-		if (type === 'del') return 'bg-red-950/40';
  7+		if (type === 'add') return 'bg-diff-add';
  8+		if (type === 'del') return 'bg-diff-del';
  9 		return '';
 10 	}
 11 
 12@@ -156,10 +156,10 @@
 13 
 14 		<!-- Hunk header — hidden when merged with previous hunk -->
 15 		{#if shouldShowHeader(hunkIndex)}
 16-			<div class="bg-zinc-800 text-zinc-500 text-xs border-y border-zinc-700 select-none flex items-center">
 17+			<div class="bg-panel text-muted text-xs border-y border-border select-none flex items-center">
 18 				{#if canExpandBefore(hunkIndex)}
 19 					<button
 20-						class="text-blue-400 hover:text-blue-300 hover:bg-zinc-700 transition-colors px-3 py-1"
 21+						class="text-accent-blue hover:opacity-75 hover:bg-hover transition-colors px-3 py-1"
 22 						onclick={() => expandBefore(hunkIndex)}
 23 						title="Show {EXPAND_COUNT} more lines above"
 24 					>
 25@@ -176,10 +176,10 @@
 26 			<!-- Expanded lines before hunk -->
 27 			{#each beforeLines as line}
 28 				{@const lineNum = line.newNumber || line.oldNumber || 0}
 29-				<div class="diff-line group flex hover:bg-zinc-700/20 relative">
 30-					<button class="absolute left-0 top-0 w-5 h-full flex items-center justify-center opacity-0 group-hover:opacity-100 text-blue-400 hover:text-blue-300 z-10 transition-opacity" onclick={() => startComment(lineNum, 'new', line.content)} title="Add comment">+</button>
 31-					<span class="w-12 shrink-0 text-right pr-2 text-zinc-600 select-none text-xs leading-6 pl-5">{line.oldNumber ?? ''}</span>
 32-					<span class="w-12 shrink-0 text-right pr-2 text-zinc-600 select-none text-xs leading-6 border-r border-zinc-700">{line.newNumber ?? ''}</span>
 33+				<div class="diff-line group flex hover:bg-hover/20 relative">
 34+					<button class="absolute left-0 top-0 w-5 h-full flex items-center justify-center opacity-0 group-hover:opacity-100 text-accent-blue z-10 transition-opacity" onclick={() => startComment(lineNum, 'new', line.content)} title="Add comment">+</button>
 35+					<span class="w-12 shrink-0 text-right pr-2 text-faint select-none text-xs leading-6 pl-5">{line.oldNumber ?? ''}</span>
 36+					<span class="w-12 shrink-0 text-right pr-2 text-faint select-none text-xs leading-6 border-r border-border">{line.newNumber ?? ''}</span>
 37 					<span class="w-6 shrink-0 text-center select-none leading-6"> </span>
 38 					<span class="flex-1 whitespace-pre-wrap break-all leading-6 pr-4">{#if line.html}{@html line.html}{:else}{line.content || ' '}{/if}</span>
 39 				</div>
 40@@ -190,11 +190,11 @@
 41 				{@const lineNum = getLineKey(line)}
 42 				{@const side = getLineSide(line)}
 43 				{@const lineThreads = getThreadsForLine(lineNum, side)}
 44-				<div class="diff-line group flex hover:bg-zinc-700/20 relative {lineBg(line.type)}">
 45-					<button class="absolute left-0 top-0 w-5 h-full flex items-center justify-center opacity-0 group-hover:opacity-100 text-blue-400 hover:text-blue-300 z-10 transition-opacity" onclick={() => startComment(lineNum, side, line.content)} title="Add comment">+</button>
 46-					<span class="w-12 shrink-0 text-right pr-2 text-zinc-600 select-none text-xs leading-6 pl-5">{line.oldNumber ?? ''}</span>
 47-					<span class="w-12 shrink-0 text-right pr-2 text-zinc-600 select-none text-xs leading-6 border-r border-zinc-700">{line.newNumber ?? ''}</span>
 48-					<span class="w-6 shrink-0 text-center select-none leading-6 {line.type === 'add' ? 'text-green-400' : line.type === 'del' ? 'text-red-400' : ''}">{line.type === 'add' ? '+' : line.type === 'del' ? '-' : ' '}</span>
 49+				<div class="diff-line group flex hover:bg-hover/20 relative {lineBg(line.type)}">
 50+					<button class="absolute left-0 top-0 w-5 h-full flex items-center justify-center opacity-0 group-hover:opacity-100 text-accent-blue z-10 transition-opacity" onclick={() => startComment(lineNum, side, line.content)} title="Add comment">+</button>
 51+					<span class="w-12 shrink-0 text-right pr-2 text-faint select-none text-xs leading-6 pl-5">{line.oldNumber ?? ''}</span>
 52+					<span class="w-12 shrink-0 text-right pr-2 text-faint select-none text-xs leading-6 border-r border-border">{line.newNumber ?? ''}</span>
 53+					<span class="w-6 shrink-0 text-center select-none leading-6 {line.type === 'add' ? 'text-accent-green' : line.type === 'del' ? 'text-accent-red' : ''}">{line.type === 'add' ? '+' : line.type === 'del' ? '-' : ' '}</span>
 54 					<span class="flex-1 whitespace-pre-wrap break-all leading-6 pr-4">{#if line.html}{@html line.html}{:else}{line.content || ' '}{/if}</span>
 55 				</div>
 56 				{#each lineThreads as thread}<CommentThread {thread} />{/each}
 57@@ -204,10 +204,10 @@
 58 			<!-- Expanded lines after hunk -->
 59 			{#each afterLines as line}
 60 				{@const lineNum = line.newNumber || line.oldNumber || 0}
 61-				<div class="diff-line group flex hover:bg-zinc-700/20 relative">
 62-					<button class="absolute left-0 top-0 w-5 h-full flex items-center justify-center opacity-0 group-hover:opacity-100 text-blue-400 hover:text-blue-300 z-10 transition-opacity" onclick={() => startComment(lineNum, 'new', line.content)} title="Add comment">+</button>
 63-					<span class="w-12 shrink-0 text-right pr-2 text-zinc-600 select-none text-xs leading-6 pl-5">{line.oldNumber ?? ''}</span>
 64-					<span class="w-12 shrink-0 text-right pr-2 text-zinc-600 select-none text-xs leading-6 border-r border-zinc-700">{line.newNumber ?? ''}</span>
 65+				<div class="diff-line group flex hover:bg-hover/20 relative">
 66+					<button class="absolute left-0 top-0 w-5 h-full flex items-center justify-center opacity-0 group-hover:opacity-100 text-accent-blue z-10 transition-opacity" onclick={() => startComment(lineNum, 'new', line.content)} title="Add comment">+</button>
 67+					<span class="w-12 shrink-0 text-right pr-2 text-faint select-none text-xs leading-6 pl-5">{line.oldNumber ?? ''}</span>
 68+					<span class="w-12 shrink-0 text-right pr-2 text-faint select-none text-xs leading-6 border-r border-border">{line.newNumber ?? ''}</span>
 69 					<span class="w-6 shrink-0 text-center select-none leading-6"> </span>
 70 					<span class="flex-1 whitespace-pre-wrap break-all leading-6 pr-4">{#if line.html}{@html line.html}{:else}{line.content || ' '}{/if}</span>
 71 				</div>
 72@@ -215,9 +215,9 @@
 73 
 74 			<!-- Expand down button below hunk content -->
 75 			{#if canExpandAfter(hunkIndex)}
 76-				<div class="flex items-center border-b border-zinc-700/50 bg-zinc-800/30 hover:bg-zinc-800/60 transition-colors">
 77+				<div class="flex items-center border-b border-border/50 bg-panel/30 hover:bg-panel/60 transition-colors">
 78 					<button
 79-						class="text-blue-400 hover:text-blue-300 transition-colors px-3 py-0.5 flex items-center gap-2 text-xs"
 80+						class="text-accent-blue hover:opacity-75 transition-colors px-3 py-0.5 flex items-center gap-2 text-xs"
 81 						onclick={() => expandAfter(hunkIndex)}
 82 						title="Show {EXPAND_COUNT} more lines below"
 83 					>
 84@@ -233,16 +233,16 @@
 85 			<!-- Expanded before -->
 86 			{#each beforeLines as line}
 87 				<div class="flex">
 88-					<div class="w-1/2 border-r border-zinc-700">
 89+					<div class="w-1/2 border-r border-border">
 90 						<div class="diff-line flex leading-6">
 91-							<span class="w-12 shrink-0 text-right pr-2 text-zinc-600 select-none text-xs leading-6 pl-5">{line.oldNumber ?? ''}</span>
 92+							<span class="w-12 shrink-0 text-right pr-2 text-faint select-none text-xs leading-6 pl-5">{line.oldNumber ?? ''}</span>
 93 							<span class="w-6 shrink-0 text-center select-none leading-6"> </span>
 94 							<span class="flex-1 whitespace-pre-wrap break-all leading-6 pr-2">{#if line.html}{@html line.html}{:else}{line.content || ' '}{/if}</span>
 95 						</div>
 96 					</div>
 97 					<div class="w-1/2">
 98 						<div class="diff-line flex leading-6">
 99-							<span class="w-12 shrink-0 text-right pr-2 text-zinc-600 select-none text-xs leading-6 pl-5">{line.newNumber ?? ''}</span>
100+							<span class="w-12 shrink-0 text-right pr-2 text-faint select-none text-xs leading-6 pl-5">{line.newNumber ?? ''}</span>
101 							<span class="w-6 shrink-0 text-center select-none leading-6"> </span>
102 							<span class="flex-1 whitespace-pre-wrap break-all leading-6 pr-2">{#if line.html}{@html line.html}{:else}{line.content || ' '}{/if}</span>
103 						</div>
104@@ -256,30 +256,30 @@
105 				{@const oldThreads = pair.old ? getThreadsForLine(pair.old.oldNumber!, 'old') : []}
106 				{@const newThreads = pair.new ? getThreadsForLine(pair.new.newNumber!, 'new') : []}
107 				<div class="flex">
108-					<div class="w-1/2 border-r border-zinc-700">
109+					<div class="w-1/2 border-r border-border">
110 						{#if pair.old}
111-							<div class="diff-line group flex hover:bg-zinc-700/20 relative {pair.old.type === 'del' ? 'bg-red-950/40' : ''}">
112-								<button class="absolute left-0 top-0 w-5 h-full flex items-center justify-center opacity-0 group-hover:opacity-100 text-blue-400 hover:text-blue-300 z-10 transition-opacity" onclick={() => startComment(pair.old!.oldNumber!, 'old', pair.old!.content)} title="Add comment">+</button>
113-								<span class="w-12 shrink-0 text-right pr-2 text-zinc-600 select-none text-xs leading-6 pl-5">{pair.old.oldNumber ?? ''}</span>
114-								<span class="w-6 shrink-0 text-center select-none leading-6 {pair.old.type === 'del' ? 'text-red-400' : ''}">{pair.old.type === 'del' ? '-' : ' '}</span>
115+							<div class="diff-line group flex hover:bg-hover/20 relative {pair.old.type === 'del' ? 'bg-diff-del' : ''}">
116+								<button class="absolute left-0 top-0 w-5 h-full flex items-center justify-center opacity-0 group-hover:opacity-100 text-accent-blue z-10 transition-opacity" onclick={() => startComment(pair.old!.oldNumber!, 'old', pair.old!.content)} title="Add comment">+</button>
117+								<span class="w-12 shrink-0 text-right pr-2 text-faint select-none text-xs leading-6 pl-5">{pair.old.oldNumber ?? ''}</span>
118+								<span class="w-6 shrink-0 text-center select-none leading-6 {pair.old.type === 'del' ? 'text-accent-red' : ''}">{pair.old.type === 'del' ? '-' : ' '}</span>
119 								<span class="flex-1 whitespace-pre-wrap break-all leading-6 pr-2">{#if pair.old.html}{@html pair.old.html}{:else}{pair.old.content || ' '}{/if}</span>
120 							</div>
121-						{:else}<div class="diff-line flex bg-zinc-800/20 leading-6">&nbsp;</div>{/if}
122+						{:else}<div class="diff-line flex bg-panel/20 leading-6">&nbsp;</div>{/if}
123 					</div>
124 					<div class="w-1/2">
125 						{#if pair.new}
126-							<div class="diff-line group flex hover:bg-zinc-700/20 relative {pair.new.type === 'add' ? 'bg-green-950/40' : ''}">
127-								<button class="absolute left-0 top-0 w-5 h-full flex items-center justify-center opacity-0 group-hover:opacity-100 text-blue-400 hover:text-blue-300 z-10 transition-opacity" onclick={() => startComment(pair.new!.newNumber!, 'new', pair.new!.content)} title="Add comment">+</button>
128-								<span class="w-12 shrink-0 text-right pr-2 text-zinc-600 select-none text-xs leading-6 pl-5">{pair.new.newNumber ?? ''}</span>
129-								<span class="w-6 shrink-0 text-center select-none leading-6 {pair.new.type === 'add' ? 'text-green-400' : ''}">{pair.new.type === 'add' ? '+' : ' '}</span>
130+							<div class="diff-line group flex hover:bg-hover/20 relative {pair.new.type === 'add' ? 'bg-diff-add' : ''}">
131+								<button class="absolute left-0 top-0 w-5 h-full flex items-center justify-center opacity-0 group-hover:opacity-100 text-accent-blue z-10 transition-opacity" onclick={() => startComment(pair.new!.newNumber!, 'new', pair.new!.content)} title="Add comment">+</button>
132+								<span class="w-12 shrink-0 text-right pr-2 text-faint select-none text-xs leading-6 pl-5">{pair.new.newNumber ?? ''}</span>
133+								<span class="w-6 shrink-0 text-center select-none leading-6 {pair.new.type === 'add' ? 'text-accent-green' : ''}">{pair.new.type === 'add' ? '+' : ' '}</span>
134 								<span class="flex-1 whitespace-pre-wrap break-all leading-6 pr-2">{#if pair.new.html}{@html pair.new.html}{:else}{pair.new.content || ' '}{/if}</span>
135 							</div>
136-						{:else}<div class="diff-line flex bg-zinc-800/20 leading-6">&nbsp;</div>{/if}
137+						{:else}<div class="diff-line flex bg-panel/20 leading-6">&nbsp;</div>{/if}
138 					</div>
139 				</div>
140 				{#if oldThreads.length > 0 || newThreads.length > 0}
141 					<div class="flex">
142-						<div class="w-1/2 border-r border-zinc-700">
143+						<div class="w-1/2 border-r border-border">
144 							{#each oldThreads as thread}<CommentThread {thread} />{/each}
145 							{#if pair.old && isCommenting(pair.old.oldNumber!, 'old')}<CommentEditor filePath={file.path} lineNumber={pair.old.oldNumber!} side="old" originalLineContent={$commentingLine?.originalLineContent} />{/if}
146 						</div>
147@@ -294,16 +294,16 @@
148 			<!-- Expanded after -->
149 			{#each afterLines as line}
150 				<div class="flex">
151-					<div class="w-1/2 border-r border-zinc-700">
152+					<div class="w-1/2 border-r border-border">
153 						<div class="diff-line flex leading-6">
154-							<span class="w-12 shrink-0 text-right pr-2 text-zinc-600 select-none text-xs leading-6 pl-5">{line.oldNumber ?? ''}</span>
155+							<span class="w-12 shrink-0 text-right pr-2 text-faint select-none text-xs leading-6 pl-5">{line.oldNumber ?? ''}</span>
156 							<span class="w-6 shrink-0 text-center select-none leading-6"> </span>
157 							<span class="flex-1 whitespace-pre-wrap break-all leading-6 pr-2">{#if line.html}{@html line.html}{:else}{line.content || ' '}{/if}</span>
158 						</div>
159 					</div>
160 					<div class="w-1/2">
161 						<div class="diff-line flex leading-6">
162-							<span class="w-12 shrink-0 text-right pr-2 text-zinc-600 select-none text-xs leading-6 pl-5">{line.newNumber ?? ''}</span>
163+							<span class="w-12 shrink-0 text-right pr-2 text-faint select-none text-xs leading-6 pl-5">{line.newNumber ?? ''}</span>
164 							<span class="w-6 shrink-0 text-center select-none leading-6"> </span>
165 							<span class="flex-1 whitespace-pre-wrap break-all leading-6 pr-2">{#if line.html}{@html line.html}{:else}{line.content || ' '}{/if}</span>
166 						</div>
167@@ -312,9 +312,9 @@
168 			{/each}
169 
170 			{#if canExpandAfter(hunkIndex)}
171-				<div class="flex items-center border-b border-zinc-700/50 bg-zinc-800/30 hover:bg-zinc-800/60 transition-colors">
172+				<div class="flex items-center border-b border-border/50 bg-panel/30 hover:bg-panel/60 transition-colors">
173 					<button
174-						class="text-blue-400 hover:text-blue-300 transition-colors px-3 py-0.5 flex items-center gap-2 text-xs"
175+						class="text-accent-blue hover:opacity-75 transition-colors px-3 py-0.5 flex items-center gap-2 text-xs"
176 						onclick={() => expandAfter(hunkIndex)}
177 						title="Show {EXPAND_COUNT} more lines below"
178 					>
M src/lib/components/ExportDialog.svelte
+19, -19
 1@@ -50,61 +50,61 @@
 2 	<div class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center" onclick={close}>
 3 		<!-- svelte-ignore a11y_click_events_have_key_events -->
 4 		<!-- svelte-ignore a11y_no_static_element_interactions -->
 5-		<div class="bg-zinc-800 border border-zinc-600 rounded-xl shadow-2xl w-full max-w-md mx-4" onclick={(e) => e.stopPropagation()}>
 6-			<div class="px-5 py-4 border-b border-zinc-700">
 7-				<h2 class="text-lg font-semibold text-zinc-200">Export Review</h2>
 8-				<p class="text-sm text-zinc-400 mt-1">Send your review comments to AI or save for reference</p>
 9+		<div class="bg-panel border border-border-strong rounded-xl shadow-2xl w-full max-w-md mx-4" onclick={(e) => e.stopPropagation()}>
10+			<div class="px-5 py-4 border-b border-border">
11+				<h2 class="text-lg font-semibold text-primary">Export Review</h2>
12+				<p class="text-sm text-tertiary mt-1">Send your review comments to AI or save for reference</p>
13 			</div>
14 
15 			<div class="p-5 space-y-2">
16 				<button
17-					class="w-full text-left px-4 py-3 rounded-lg border border-zinc-600 hover:bg-zinc-700/50 transition-colors group"
18+					class="w-full text-left px-4 py-3 rounded-lg border border-border-strong hover:bg-hover/50 transition-colors group"
19 					onclick={() => exportAs('claude')}
20 					disabled={exporting}
21 				>
22-					<div class="text-sm font-medium text-zinc-200 group-hover:text-white">Send to Claude Code</div>
23-					<div class="text-xs text-zinc-400 mt-0.5">Stream review to Claude with live output in terminal</div>
24+					<div class="text-sm font-medium text-primary group-hover:opacity-80">Send to Claude Code</div>
25+					<div class="text-xs text-tertiary mt-0.5">Stream review to Claude with live output in terminal</div>
26 				</button>
27 
28 				<button
29-					class="w-full text-left px-4 py-3 rounded-lg border border-zinc-600 hover:bg-zinc-700/50 transition-colors group"
30+					class="w-full text-left px-4 py-3 rounded-lg border border-border-strong hover:bg-hover/50 transition-colors group"
31 					onclick={() => exportAs('clipboard')}
32 					disabled={exporting}
33 				>
34-					<div class="text-sm font-medium text-zinc-200 group-hover:text-white">Copy to Clipboard</div>
35-					<div class="text-xs text-zinc-400 mt-0.5">Copy structured review text for any AI tool</div>
36+					<div class="text-sm font-medium text-primary group-hover:opacity-80">Copy to Clipboard</div>
37+					<div class="text-xs text-tertiary mt-0.5">Copy structured review text for any AI tool</div>
38 				</button>
39 
40 				<button
41-					class="w-full text-left px-4 py-3 rounded-lg border border-zinc-600 hover:bg-zinc-700/50 transition-colors group"
42+					class="w-full text-left px-4 py-3 rounded-lg border border-border-strong hover:bg-hover/50 transition-colors group"
43 					onclick={() => exportAs('json')}
44 					disabled={exporting}
45 				>
46-					<div class="text-sm font-medium text-zinc-200 group-hover:text-white">Export as JSON</div>
47-					<div class="text-xs text-zinc-400 mt-0.5">Save .lcr-review.json in project root</div>
48+					<div class="text-sm font-medium text-primary group-hover:opacity-80">Export as JSON</div>
49+					<div class="text-xs text-tertiary mt-0.5">Save .lcr-review.json in project root</div>
50 				</button>
51 
52 				<button
53-					class="w-full text-left px-4 py-3 rounded-lg border border-zinc-600 hover:bg-zinc-700/50 transition-colors group"
54+					class="w-full text-left px-4 py-3 rounded-lg border border-border-strong hover:bg-hover/50 transition-colors group"
55 					onclick={() => exportAs('markdown')}
56 					disabled={exporting}
57 				>
58-					<div class="text-sm font-medium text-zinc-200 group-hover:text-white">Export as Markdown</div>
59-					<div class="text-xs text-zinc-400 mt-0.5">Save .lcr-review.md in project root</div>
60+					<div class="text-sm font-medium text-primary group-hover:opacity-80">Export as Markdown</div>
61+					<div class="text-xs text-tertiary mt-0.5">Save .lcr-review.md in project root</div>
62 				</button>
63 			</div>
64 
65 			{#if result}
66 				<div class="px-5 pb-3">
67-					<div class="text-sm rounded-lg px-3 py-2 {result.success ? 'bg-green-500/10 text-green-400' : 'bg-red-500/10 text-red-400'}">
68+					<div class="text-sm rounded-lg px-3 py-2 {result.success ? 'bg-accent-green/10 text-accent-green' : 'bg-accent-red/10 text-accent-red'}">
69 						{result.message || result.path || 'Done!'}
70 					</div>
71 				</div>
72 			{/if}
73 
74-			<div class="px-5 py-3 border-t border-zinc-700 flex justify-end">
75+			<div class="px-5 py-3 border-t border-border flex justify-end">
76 				<button
77-					class="text-sm px-4 py-2 rounded text-zinc-400 hover:text-zinc-200 hover:bg-zinc-700 transition-colors"
78+					class="text-sm px-4 py-2 rounded text-tertiary hover:text-primary hover:bg-hover transition-colors"
79 					onclick={close}
80 				>
81 					Close
M src/lib/components/FileTree.svelte
+13, -13
 1@@ -2,10 +2,10 @@
 2 	import { files, selectedFile, fileCommentCounts, toggleApproval } from '$lib/stores/files.ts';
 3 
 4 	const statusColors: Record<string, string> = {
 5-		added: 'text-green-400',
 6-		modified: 'text-yellow-400',
 7-		deleted: 'text-red-400',
 8-		renamed: 'text-blue-400'
 9+		added: 'text-accent-green',
10+		modified: 'text-accent-yellow',
11+		deleted: 'text-accent-red',
12+		renamed: 'text-accent-blue'
13 	};
14 
15 	const statusLabels: Record<string, string> = {
16@@ -36,7 +36,7 @@
17 </script>
18 
19 <div class="h-full overflow-y-auto">
20-	<div class="px-3 py-2 text-xs font-semibold text-zinc-400 uppercase tracking-wider">
21+	<div class="px-3 py-2 text-xs font-semibold text-tertiary uppercase tracking-wider">
22 		Changed Files ({$files.length})
23 	</div>
24 	<div class="space-y-px">
25@@ -46,12 +46,12 @@
26 			<!-- svelte-ignore a11y_click_events_have_key_events -->
27 			<!-- svelte-ignore a11y_no_static_element_interactions -->
28 			<div
29-				class="w-full text-left px-3 py-1.5 flex items-center gap-2 text-sm hover:bg-zinc-700/50 transition-colors cursor-pointer {isSelected ? 'bg-zinc-700/80 text-white' : 'text-zinc-300'} {file.approved ? 'opacity-60' : ''}"
30+				class="w-full text-left px-3 py-1.5 flex items-center gap-2 text-sm hover:bg-hover/50 transition-colors cursor-pointer {isSelected ? 'bg-hover/80 text-primary' : 'text-secondary'} {file.approved ? 'opacity-60' : ''}"
31 				onclick={() => selectFile(file.path)}
32 			>
33 				<!-- Approve checkbox -->
34 				<button
35-					class="shrink-0 w-4 h-4 rounded border flex items-center justify-center transition-colors {file.approved ? 'bg-green-600 border-green-600 text-white' : 'border-zinc-500 hover:border-green-400 text-transparent hover:text-green-400'}"
36+					class="shrink-0 w-4 h-4 rounded border flex items-center justify-center transition-colors {file.approved ? 'bg-green-600 border-green-600 text-white' : 'border-border-strong hover:border-accent-green text-transparent hover:text-accent-green'}"
37 					onclick={(e) => handleApprove(e, file.path)}
38 					title={file.approved ? 'Unapprove file' : 'Approve file'}
39 				>
40@@ -63,22 +63,22 @@
41 				<span class="font-mono text-xs font-bold w-4 text-center {statusColors[file.status]}">
42 					{statusLabels[file.status]}
43 				</span>
44-				<span class="truncate flex-1 {file.approved ? 'line-through decoration-zinc-500' : ''}">
45+				<span class="truncate flex-1 {file.approved ? 'line-through decoration-muted' : ''}">
46 					{#if getDirectory(file.path)}
47-						<span class="text-zinc-500">{getDirectory(file.path)}/</span>
48+						<span class="text-muted">{getDirectory(file.path)}/</span>
49 					{/if}
50 					<span class="font-medium">{getFilename(file.path)}</span>
51 				</span>
52-				<span class="text-xs text-zinc-500 tabular-nums shrink-0">
53+				<span class="text-xs text-muted tabular-nums shrink-0">
54 					{#if file.additions > 0}
55-						<span class="text-green-400">+{file.additions}</span>
56+						<span class="text-accent-green">+{file.additions}</span>
57 					{/if}
58 					{#if file.deletions > 0}
59-						<span class="text-red-400">-{file.deletions}</span>
60+						<span class="text-accent-red">-{file.deletions}</span>
61 					{/if}
62 				</span>
63 				{#if commentCount > 0}
64-					<span class="bg-blue-500/20 text-blue-400 text-xs px-1.5 py-0.5 rounded-full font-medium">
65+					<span class="bg-accent-blue/20 text-accent-blue text-xs px-1.5 py-0.5 rounded-full font-medium">
66 						{commentCount}
67 					</span>
68 				{/if}
M src/lib/components/TerminalPanel.svelte
+68, -34
  1@@ -7,6 +7,7 @@
  2 		restartSession,
  3 		hasPendingReview
  4 	} from '$lib/stores/terminal.ts';
  5+	import { theme } from '$lib/stores/ui.ts';
  6 	import { onDestroy } from 'svelte';
  7 
  8 	let isResizing = $state(false);
  9@@ -19,6 +20,60 @@
 10 	let ws: WebSocket | null = null;
 11 	let resizeObserver: ResizeObserver | null = null;
 12 
 13+	const terminalThemes: Record<string, Record<string, string>> = {
 14+		dark: {
 15+			background: '#1e1e1e',
 16+			foreground: '#d4d4d4',
 17+			cursor: '#d4d4d4',
 18+			selectionBackground: '#264f78',
 19+			black: '#1e1e1e',
 20+			red: '#f44747',
 21+			green: '#6a9955',
 22+			yellow: '#d7ba7d',
 23+			blue: '#569cd6',
 24+			magenta: '#c586c0',
 25+			cyan: '#4ec9b0',
 26+			white: '#d4d4d4',
 27+			brightBlack: '#808080',
 28+			brightRed: '#f44747',
 29+			brightGreen: '#6a9955',
 30+			brightYellow: '#d7ba7d',
 31+			brightBlue: '#569cd6',
 32+			brightMagenta: '#c586c0',
 33+			brightCyan: '#4ec9b0',
 34+			brightWhite: '#ffffff'
 35+		},
 36+		light: {
 37+			background: '#f8f8f8',
 38+			foreground: '#383a42',
 39+			cursor: '#383a42',
 40+			selectionBackground: '#add6ff',
 41+			black: '#383a42',
 42+			red: '#e45649',
 43+			green: '#50a14f',
 44+			yellow: '#c18401',
 45+			blue: '#4078f2',
 46+			magenta: '#a626a4',
 47+			cyan: '#0184bc',
 48+			white: '#fafafa',
 49+			brightBlack: '#a0a1a7',
 50+			brightRed: '#e45649',
 51+			brightGreen: '#50a14f',
 52+			brightYellow: '#c18401',
 53+			brightBlue: '#4078f2',
 54+			brightMagenta: '#a626a4',
 55+			brightCyan: '#0184bc',
 56+			brightWhite: '#ffffff'
 57+		}
 58+	};
 59+
 60+	// React to theme changes
 61+	$effect(() => {
 62+		if (term) {
 63+			term.options.theme = terminalThemes[$theme];
 64+		}
 65+	});
 66+
 67 	onDestroy(() => {
 68 		resizeObserver?.disconnect();
 69 		ws?.close();
 70@@ -50,28 +105,7 @@
 71 				cursorBlink: true,
 72 				fontSize: 13,
 73 				fontFamily: 'Menlo, Monaco, "Courier New", monospace',
 74-				theme: {
 75-					background: '#1e1e1e',
 76-					foreground: '#d4d4d4',
 77-					cursor: '#d4d4d4',
 78-					selectionBackground: '#264f78',
 79-					black: '#1e1e1e',
 80-					red: '#f44747',
 81-					green: '#6a9955',
 82-					yellow: '#d7ba7d',
 83-					blue: '#569cd6',
 84-					magenta: '#c586c0',
 85-					cyan: '#4ec9b0',
 86-					white: '#d4d4d4',
 87-					brightBlack: '#808080',
 88-					brightRed: '#f44747',
 89-					brightGreen: '#6a9955',
 90-					brightYellow: '#d7ba7d',
 91-					brightBlue: '#569cd6',
 92-					brightMagenta: '#c586c0',
 93-					brightCyan: '#4ec9b0',
 94-					brightWhite: '#ffffff'
 95-				},
 96+				theme: terminalThemes[$theme],
 97 				allowProposedApi: true
 98 			});
 99 
100@@ -192,7 +226,7 @@
101 
102 {#if $terminalOpen}
103 	<div
104-		class="flex flex-col border-t border-zinc-700 bg-[#1e1e1e]"
105+		class="flex flex-col border-t border-border bg-terminal"
106 		style="height: {minimized ? 'auto' : `${$terminalHeight}px`}"
107 	>
108 		{#if !minimized}
109@@ -203,12 +237,12 @@
110 			></div>
111 		{/if}
112 
113-		<div class="flex items-center gap-2 px-3 py-1.5 bg-zinc-900 border-b border-zinc-700/50 select-none shrink-0">
114+		<div class="flex items-center gap-2 px-3 py-1.5 bg-surface border-b border-border/50 select-none shrink-0">
115 			<div class="flex items-center gap-2">
116-				<span class="text-xs font-medium text-zinc-300">Claude Terminal</span>
117+				<span class="text-xs font-medium text-secondary">Claude Terminal</span>
118 				<span class="flex items-center gap-1">
119-					<span class="w-2 h-2 rounded-full {connected ? 'bg-green-400' : 'bg-zinc-500'}"></span>
120-					<span class="text-[10px] text-zinc-500">{connected ? 'Connected' : 'Disconnected'}</span>
121+					<span class="w-2 h-2 rounded-full {connected ? 'bg-green-400' : 'bg-muted'}"></span>
122+					<span class="text-[10px] text-muted">{connected ? 'Connected' : 'Disconnected'}</span>
123 				</span>
124 			</div>
125 
126@@ -217,7 +251,7 @@
127 			<div class="flex items-center gap-1">
128 				<button
129 					onclick={sendReviewToClaude}
130-					class="text-[10px] px-2 py-0.5 rounded text-blue-400 hover:bg-blue-400/10 transition-colors"
131+					class="text-[10px] px-2 py-0.5 rounded text-accent-blue hover:bg-accent-blue/10 transition-colors"
132 					title="Send current review comments to Claude"
133 				>
134 					Send Review
135@@ -225,35 +259,35 @@
136 
137 				<button
138 					onclick={stopClaudeSession}
139-					class="text-[10px] px-2 py-0.5 rounded text-red-400 hover:bg-red-400/10 transition-colors"
140+					class="text-[10px] px-2 py-0.5 rounded text-accent-red hover:bg-accent-red/10 transition-colors"
141 				>
142 					Stop
143 				</button>
144 
145 				<button
146 					onclick={restartSession}
147-					class="text-[10px] px-2 py-0.5 rounded text-zinc-400 hover:bg-zinc-700 transition-colors"
148+					class="text-[10px] px-2 py-0.5 rounded text-tertiary hover:bg-hover transition-colors"
149 				>
150 					Restart
151 				</button>
152 
153 				<button
154 					onclick={() => term?.clear()}
155-					class="text-[10px] px-2 py-0.5 rounded text-zinc-400 hover:bg-zinc-700 transition-colors"
156+					class="text-[10px] px-2 py-0.5 rounded text-tertiary hover:bg-hover transition-colors"
157 				>
158 					Clear
159 				</button>
160 
161 				<button
162 					onclick={() => minimized = !minimized}
163-					class="text-[10px] px-2 py-0.5 rounded text-zinc-400 hover:bg-zinc-700 transition-colors"
164+					class="text-[10px] px-2 py-0.5 rounded text-tertiary hover:bg-hover transition-colors"
165 				>
166 					{minimized ? 'Expand' : 'Minimize'}
167 				</button>
168 
169 				<button
170 					onclick={() => $terminalOpen = false}
171-					class="text-[10px] px-2 py-0.5 rounded text-zinc-400 hover:bg-zinc-700 transition-colors"
172+					class="text-[10px] px-2 py-0.5 rounded text-tertiary hover:bg-hover transition-colors"
173 				>
174 					Close
175 				</button>
176@@ -266,7 +300,7 @@
177 				class="flex-1 overflow-hidden"
178 			></div>
179 			{#if initError}
180-				<div class="p-3 text-red-400 text-xs">{initError}</div>
181+				<div class="p-3 text-accent-red text-xs">{initError}</div>
182 			{/if}
183 		{/if}
184 	</div>
M src/lib/components/Toolbar.svelte
+34, -25
  1@@ -1,5 +1,5 @@
  2 <script lang="ts">
  3-	import { viewMode, showCommitDialog, showExportDialog } from '$lib/stores/ui.ts';
  4+	import { viewMode, showCommitDialog, showExportDialog, theme } from '$lib/stores/ui.ts';
  5 	import { stats, reviewStatus, submitReview } from '$lib/stores/review.ts';
  6 	import { diffMode, approvedCount, diffStats, loadAllDiffs } from '$lib/stores/files.ts';
  7 
  8@@ -21,19 +21,19 @@
  9 	}
 10 </script>
 11 
 12-<header class="bg-zinc-800 border-b border-zinc-700 px-4 py-2 flex items-center gap-3">
 13-	<h1 class="text-sm font-semibold text-zinc-200 mr-4">Local Code Review</h1>
 14+<header class="bg-panel border-b border-border px-4 py-2 flex items-center gap-3">
 15+	<h1 class="text-sm font-semibold text-primary mr-4">Local Code Review</h1>
 16 
 17 	<!-- View mode toggle -->
 18-	<div class="flex bg-zinc-900 rounded overflow-hidden border border-zinc-700">
 19+	<div class="flex bg-surface rounded overflow-hidden border border-border">
 20 		<button
 21-			class="text-xs px-3 py-1 transition-colors {$viewMode === 'unified' ? 'bg-zinc-600 text-white' : 'text-zinc-400 hover:text-zinc-200'}"
 22+			class="text-xs px-3 py-1 transition-colors {$viewMode === 'unified' ? 'bg-active text-white' : 'text-tertiary hover:text-primary'}"
 23 			onclick={() => ($viewMode = 'unified')}
 24 		>
 25 			Unified
 26 		</button>
 27 		<button
 28-			class="text-xs px-3 py-1 transition-colors {$viewMode === 'split' ? 'bg-zinc-600 text-white' : 'text-zinc-400 hover:text-zinc-200'}"
 29+			class="text-xs px-3 py-1 transition-colors {$viewMode === 'split' ? 'bg-active text-white' : 'text-tertiary hover:text-primary'}"
 30 			onclick={() => ($viewMode = 'split')}
 31 		>
 32 			Split
 33@@ -41,15 +41,15 @@
 34 	</div>
 35 
 36 	<!-- Diff mode toggle -->
 37-	<div class="flex bg-zinc-900 rounded overflow-hidden border border-zinc-700">
 38+	<div class="flex bg-surface rounded overflow-hidden border border-border">
 39 		<button
 40-			class="text-xs px-3 py-1 transition-colors {$diffMode === 'full' ? 'bg-zinc-600 text-white' : 'text-zinc-400 hover:text-zinc-200'}"
 41+			class="text-xs px-3 py-1 transition-colors {$diffMode === 'full' ? 'bg-active text-white' : 'text-tertiary hover:text-primary'}"
 42 			onclick={() => setDiffMode('full')}
 43 		>
 44 			Full Diff
 45 		</button>
 46 		<button
 47-			class="text-xs px-3 py-1 transition-colors {$diffMode === 'unstaged' ? 'bg-zinc-600 text-white' : 'text-zinc-400 hover:text-zinc-200'}"
 48+			class="text-xs px-3 py-1 transition-colors {$diffMode === 'unstaged' ? 'bg-active text-white' : 'text-tertiary hover:text-primary'}"
 49 			onclick={() => setDiffMode('unstaged')}
 50 		>
 51 			Needs Review
 52@@ -60,14 +60,14 @@
 53 
 54 	<!-- Diff stats -->
 55 	{#if $approvedCount.total > 0}
 56-		<span class="text-xs text-zinc-400 flex items-center gap-2">
 57-			<span><span class="text-green-400">{$approvedCount.approved}</span>/{$approvedCount.total} approved</span>
 58-			<span class="text-zinc-600">|</span>
 59-			<span class="text-green-400">+{$diffStats.additions}</span>
 60-			<span class="text-red-400">-{$diffStats.deletions}</span>
 61+		<span class="text-xs text-tertiary flex items-center gap-2">
 62+			<span><span class="text-accent-green">{$approvedCount.approved}</span>/{$approvedCount.total} approved</span>
 63+			<span class="text-faint">|</span>
 64+			<span class="text-accent-green">+{$diffStats.additions}</span>
 65+			<span class="text-accent-red">-{$diffStats.deletions}</span>
 66 			{#if $diffStats.unapprovedAdditions > 0 || $diffStats.unapprovedDeletions > 0}
 67-				<span class="text-zinc-600">|</span>
 68-				<span class="text-yellow-400">
 69+				<span class="text-faint">|</span>
 70+				<span class="text-accent-yellow">
 71 					{$diffStats.unapprovedAdditions + $diffStats.unapprovedDeletions} lines to review
 72 				</span>
 73 			{/if}
 74@@ -76,10 +76,10 @@
 75 
 76 	<!-- Stats -->
 77 	{#if $stats.totalComments > 0}
 78-		<span class="text-xs text-zinc-400">
 79+		<span class="text-xs text-tertiary">
 80 			{$stats.totalComments} comment{$stats.totalComments !== 1 ? 's' : ''}
 81 			{#if $stats.unresolvedThreads > 0}
 82-				<span class="text-yellow-400">({$stats.unresolvedThreads} unresolved)</span>
 83+				<span class="text-accent-yellow">({$stats.unresolvedThreads} unresolved)</span>
 84 			{/if}
 85 		</span>
 86 	{/if}
 87@@ -87,15 +87,24 @@
 88 	<!-- Status badge -->
 89 	{#if $reviewStatus !== 'pending'}
 90 		<span
 91-			class="text-xs px-2 py-0.5 rounded-full {$reviewStatus === 'approved' ? 'bg-green-500/20 text-green-400' : 'bg-yellow-500/20 text-yellow-400'}"
 92+			class="text-xs px-2 py-0.5 rounded-full {$reviewStatus === 'approved' ? 'bg-accent-green/20 text-accent-green' : 'bg-accent-yellow/20 text-accent-yellow'}"
 93 		>
 94 			{$reviewStatus === 'approved' ? 'Approved' : 'Changes Requested'}
 95 		</span>
 96 	{/if}
 97 
 98+	<!-- Theme toggle -->
 99+	<button
100+		class="text-xs px-2 py-1.5 rounded border border-border-strong text-tertiary hover:text-primary hover:bg-hover transition-colors"
101+		onclick={() => theme.toggle()}
102+		title="Toggle theme"
103+	>
104+		{$theme === 'dark' ? '☀️' : '🌙'}
105+	</button>
106+
107 	<!-- Export button -->
108 	<button
109-		class="text-xs px-3 py-1.5 rounded border border-zinc-600 text-zinc-300 hover:bg-zinc-700 transition-colors"
110+		class="text-xs px-3 py-1.5 rounded border border-border-strong text-secondary hover:bg-hover transition-colors"
111 		onclick={() => ($showExportDialog = true)}
112 	>
113 		Export
114@@ -117,18 +126,18 @@
115 				class="fixed inset-0 z-10"
116 				onclick={() => (showReviewMenu = false)}
117 			></div>
118-			<div class="absolute right-0 top-full mt-1 bg-zinc-800 border border-zinc-600 rounded-lg shadow-xl z-20 py-1 min-w-[220px]">
119+			<div class="absolute right-0 top-full mt-1 bg-panel border border-border-strong rounded-lg shadow-xl z-20 py-1 min-w-[220px]">
120 				<button
121-					class="w-full text-left px-3 py-2 text-sm text-zinc-300 hover:bg-zinc-700 transition-colors"
122+					class="w-full text-left px-3 py-2 text-sm text-secondary hover:bg-hover transition-colors"
123 					onclick={() => { handleRequestChanges(); showReviewMenu = false; }}
124 				>
125-					<span class="text-yellow-400 mr-2">↻</span> Request Changes (send to AI)
126+					<span class="text-accent-yellow mr-2">↻</span> Request Changes (send to AI)
127 				</button>
128 				<button
129-					class="w-full text-left px-3 py-2 text-sm text-zinc-300 hover:bg-zinc-700 transition-colors"
130+					class="w-full text-left px-3 py-2 text-sm text-secondary hover:bg-hover transition-colors"
131 					onclick={() => { handleApprove(); showReviewMenu = false; }}
132 				>
133-					<span class="text-green-400 mr-2">✓</span> Approve & Commit
134+					<span class="text-accent-green mr-2">✓</span> Approve & Commit
135 				</button>
136 			</div>
137 		{/if}
M src/lib/server/highlighter.ts
+34, -10
 1@@ -5,7 +5,7 @@ let highlighter: Highlighter | null = null;
 2 async function getHighlighter(): Promise<Highlighter> {
 3 	if (!highlighter) {
 4 		highlighter = await createHighlighter({
 5-			themes: ['github-dark'],
 6+			themes: ['github-dark', 'github-light'],
 7 			langs: [] // Start empty, load on demand
 8 		});
 9 	}
10@@ -15,6 +15,8 @@ async function getHighlighter(): Promise<Highlighter> {
11 /**
12  * Highlight a full file and return a map of line number → HTML string.
13  * Line numbers are 1-indexed.
14+ * Output uses CSS custom properties for dual-theme support:
15+ *   <span class="st" style="--sd:#dark;--sl:#light">text</span>
16  */
17 export async function highlightLines(
18 	code: string,
19@@ -40,20 +42,42 @@ export async function highlightLines(
20 			}
21 		}
22 
23-		const result = hl.codeToTokens(code, {
24+		const darkResult = hl.codeToTokens(code, {
25 			lang: language as BundledLanguage,
26 			theme: 'github-dark'
27 		});
28 
29-		for (let i = 0; i < result.tokens.length; i++) {
30-			const lineTokens = result.tokens[i];
31+		const lightResult = hl.codeToTokens(code, {
32+			lang: language as BundledLanguage,
33+			theme: 'github-light'
34+		});
35+
36+		for (let i = 0; i < darkResult.tokens.length; i++) {
37+			const darkTokens = darkResult.tokens[i];
38+			const lightTokens = lightResult.tokens[i];
39 			let html = '';
40-			for (const token of lineTokens) {
41-				const escaped = escapeHtml(token.content);
42-				if (token.color) {
43-					html += `<span style="color:${token.color}">${escaped}</span>`;
44-				} else {
45-					html += escaped;
46+
47+			if (lightTokens && darkTokens.length === lightTokens.length) {
48+				// Both themes have matching tokens — use dual-theme spans
49+				for (let j = 0; j < darkTokens.length; j++) {
50+					const escaped = escapeHtml(darkTokens[j].content);
51+					const dc = darkTokens[j].color;
52+					const lc = lightTokens[j].color;
53+					if (dc || lc) {
54+						html += `<span class="st" style="--sd:${dc || 'inherit'};--sl:${lc || 'inherit'}">${escaped}</span>`;
55+					} else {
56+						html += escaped;
57+					}
58+				}
59+			} else {
60+				// Fallback: dark-only tokens (shouldn't normally happen)
61+				for (const token of darkTokens) {
62+					const escaped = escapeHtml(token.content);
63+					if (token.color) {
64+						html += `<span class="st" style="--sd:${token.color};--sl:${token.color}">${escaped}</span>`;
65+					} else {
66+						html += escaped;
67+					}
68 				}
69 			}
70 			lineMap.set(i + 1, html);
M src/lib/stores/ui.ts
+29, -0
 1@@ -1,8 +1,37 @@
 2 import { writable } from 'svelte/store';
 3+import { browser } from '$app/environment';
 4 
 5 export type ViewMode = 'unified' | 'split';
 6+export type Theme = 'light' | 'dark';
 7 
 8 export const viewMode = writable<ViewMode>('unified');
 9+
10+function createThemeStore() {
11+	const initial: Theme = browser
12+		? (localStorage.getItem('lcr-theme') as Theme) || 'dark'
13+		: 'dark';
14+
15+	const { subscribe, set } = writable<Theme>(initial);
16+
17+	return {
18+		subscribe,
19+		set(value: Theme) {
20+			if (browser) {
21+				localStorage.setItem('lcr-theme', value);
22+				document.documentElement.setAttribute('data-theme', value);
23+			}
24+			set(value);
25+		},
26+		toggle() {
27+			let current: Theme = 'dark';
28+			subscribe((v) => (current = v))();
29+			const next = current === 'dark' ? 'light' : 'dark';
30+			this.set(next);
31+		}
32+	};
33+}
34+
35+export const theme = createThemeStore();
36 export const sidebarOpen = writable(true);
37 export const showCommitDialog = writable(false);
38 export const showExportDialog = writable(false);
M src/routes/+layout.svelte
+1, -1
1@@ -8,6 +8,6 @@
2 	<title>Local Code Review</title>
3 </svelte:head>
4 
5-<div class="h-screen w-screen overflow-hidden bg-zinc-900 text-zinc-200">
6+<div class="h-screen w-screen overflow-hidden bg-surface text-primary">
7 	{@render children()}
8 </div>
M src/routes/+page.svelte
+13, -13
 1@@ -59,7 +59,7 @@
 2 	<div class="flex-1 flex overflow-hidden">
 3 		<!-- Sidebar -->
 4 		{#if $sidebarOpen}
 5-			<aside class="w-72 shrink-0 bg-zinc-800/50 border-r border-zinc-700 overflow-hidden flex flex-col">
 6+			<aside class="w-72 shrink-0 bg-panel/50 border-r border-border overflow-hidden flex flex-col">
 7 				<FileTree />
 8 			</aside>
 9 		{/if}
10@@ -67,32 +67,32 @@
11 		<!-- Main diff area -->
12 		<main class="flex-1 overflow-y-auto">
13 			{#if loading}
14-				<div class="flex items-center justify-center h-full text-zinc-500">
15+				<div class="flex items-center justify-center h-full text-muted">
16 					<div class="text-center">
17 						<div class="text-2xl mb-2 animate-pulse">⏳</div>
18 						<p class="text-sm">Loading changes...</p>
19 					</div>
20 				</div>
21 			{:else if error}
22-				<div class="flex items-center justify-center h-full text-red-400">
23+				<div class="flex items-center justify-center h-full text-accent-red">
24 					<div class="text-center max-w-md">
25 						<div class="text-2xl mb-2">⚠️</div>
26 						<p class="text-sm">{error}</p>
27 					</div>
28 				</div>
29 			{:else if $files.length === 0}
30-				<div class="flex items-center justify-center h-full text-zinc-500">
31+				<div class="flex items-center justify-center h-full text-muted">
32 					<div class="text-center">
33 						<div class="text-4xl mb-3">✨</div>
34-						<p class="font-medium text-zinc-300">No uncommitted changes</p>
35+						<p class="font-medium text-secondary">No uncommitted changes</p>
36 						<p class="text-sm mt-1">Make some changes and come back to review them</p>
37 					</div>
38 				</div>
39 			{:else if $selectedDiff}
40 				<!-- File header -->
41-				<div class="sticky top-0 z-10 bg-zinc-900/95 backdrop-blur border-b border-zinc-700 px-4 py-2 flex items-center gap-2">
42+				<div class="sticky top-0 z-10 bg-surface/95 backdrop-blur border-b border-border px-4 py-2 flex items-center gap-2">
43 					<button
44-						class="shrink-0 w-5 h-5 rounded border flex items-center justify-center transition-colors {$selectedFileData?.approved ? 'bg-green-600 border-green-600 text-white' : 'border-zinc-500 hover:border-green-400 text-transparent hover:text-green-400'}"
45+						class="shrink-0 w-5 h-5 rounded border flex items-center justify-center transition-colors {$selectedFileData?.approved ? 'bg-green-600 border-green-600 text-white' : 'border-border-strong hover:border-accent-green text-transparent hover:text-accent-green'}"
46 						onclick={() => toggleApproval($selectedDiff!.path)}
47 						title={$selectedFileData?.approved ? 'Unapprove file' : 'Approve file'}
48 					>
49@@ -100,25 +100,25 @@
50 							<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
51 						</svg>
52 					</button>
53-					<span class="font-mono text-sm text-zinc-200">{$selectedDiff.path}</span>
54-					<span class="text-xs text-zinc-500">({$selectedDiff.language})</span>
55+					<span class="font-mono text-sm text-primary">{$selectedDiff.path}</span>
56+					<span class="text-xs text-muted">({$selectedDiff.language})</span>
57 					{#if $selectedFileData}
58 						<span class="text-xs tabular-nums">
59 							{#if $selectedFileData.additions > 0}
60-								<span class="text-green-400">+{$selectedFileData.additions}</span>
61+								<span class="text-accent-green">+{$selectedFileData.additions}</span>
62 							{/if}
63 							{#if $selectedFileData.deletions > 0}
64-								<span class="text-red-400 ml-1">-{$selectedFileData.deletions}</span>
65+								<span class="text-accent-red ml-1">-{$selectedFileData.deletions}</span>
66 							{/if}
67 						</span>
68 					{/if}
69 					{#if $selectedFileData?.approved}
70-						<span class="text-xs px-2 py-0.5 rounded-full bg-green-500/20 text-green-400">Approved</span>
71+						<span class="text-xs px-2 py-0.5 rounded-full bg-accent-green/20 text-accent-green">Approved</span>
72 					{/if}
73 				</div>
74 				<DiffView file={$selectedDiff} />
75 			{:else}
76-				<div class="flex items-center justify-center h-full text-zinc-500">
77+				<div class="flex items-center justify-center h-full text-muted">
78 					<p class="text-sm">Select a file from the sidebar</p>
79 				</div>
80 			{/if}
M src/routes/layout.css
+75, -2
 1@@ -1,5 +1,74 @@
 2 @import 'tailwindcss';
 3 
 4+/* Theme design tokens */
 5+:root,
 6+[data-theme="dark"] {
 7+	--t-surface: #18181b;
 8+	--t-panel: #27272a;
 9+	--t-hover: #3f3f46;
10+	--t-active: #52525b;
11+	--t-text: #e4e4e7;
12+	--t-text-2: #d4d4d8;
13+	--t-text-3: #a1a1aa;
14+	--t-text-muted: #71717a;
15+	--t-text-faint: #52525b;
16+	--t-border: #3f3f46;
17+	--t-border-strong: #52525b;
18+	--t-accent-green: #4ade80;
19+	--t-accent-red: #f87171;
20+	--t-accent-blue: #60a5fa;
21+	--t-accent-yellow: #facc15;
22+	--t-diff-add: rgba(5, 46, 22, 0.4);
23+	--t-diff-del: rgba(69, 10, 10, 0.4);
24+	--t-scrollbar: #52525b;
25+	--t-scrollbar-hover: #71717a;
26+	--t-terminal-bg: #1e1e1e;
27+}
28+
29+[data-theme="light"] {
30+	--t-surface: #ffffff;
31+	--t-panel: #f4f4f5;
32+	--t-hover: #e4e4e7;
33+	--t-active: #d4d4d8;
34+	--t-text: #18181b;
35+	--t-text-2: #27272a;
36+	--t-text-3: #52525b;
37+	--t-text-muted: #71717a;
38+	--t-text-faint: #a1a1aa;
39+	--t-border: #e4e4e7;
40+	--t-border-strong: #d4d4d8;
41+	--t-accent-green: #16a34a;
42+	--t-accent-red: #dc2626;
43+	--t-accent-blue: #2563eb;
44+	--t-accent-yellow: #ca8a04;
45+	--t-diff-add: #dcfce7;
46+	--t-diff-del: #fee2e2;
47+	--t-scrollbar: #d4d4d8;
48+	--t-scrollbar-hover: #a1a1aa;
49+	--t-terminal-bg: #f8f8f8;
50+}
51+
52+@theme {
53+	--color-surface: var(--t-surface);
54+	--color-panel: var(--t-panel);
55+	--color-hover: var(--t-hover);
56+	--color-active: var(--t-active);
57+	--color-primary: var(--t-text);
58+	--color-secondary: var(--t-text-2);
59+	--color-tertiary: var(--t-text-3);
60+	--color-muted: var(--t-text-muted);
61+	--color-faint: var(--t-text-faint);
62+	--color-border: var(--t-border);
63+	--color-border-strong: var(--t-border-strong);
64+	--color-accent-green: var(--t-accent-green);
65+	--color-accent-red: var(--t-accent-red);
66+	--color-accent-blue: var(--t-accent-blue);
67+	--color-accent-yellow: var(--t-accent-yellow);
68+	--color-diff-add: var(--t-diff-add);
69+	--color-diff-del: var(--t-diff-del);
70+	--color-terminal: var(--t-terminal-bg);
71+}
72+
73 html, body {
74 	margin: 0;
75 	padding: 0;
76@@ -18,10 +87,14 @@ html, body {
77 }
78 
79 ::-webkit-scrollbar-thumb {
80-	background: #52525b;
81+	background: var(--t-scrollbar);
82 	border-radius: 4px;
83 }
84 
85 ::-webkit-scrollbar-thumb:hover {
86-	background: #71717a;
87+	background: var(--t-scrollbar-hover);
88 }
89+
90+/* Syntax highlighting dual theme */
91+.st { color: var(--sd); }
92+[data-theme="light"] .st { color: var(--sl); }