14 files changed,
+367,
-192
+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">
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
+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
+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
+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"> </div>{/if}
122+ {:else}<div class="diff-line flex bg-panel/20 leading-6"> </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"> </div>{/if}
137+ {:else}<div class="diff-line flex bg-panel/20 leading-6"> </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 >
+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
+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}
+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>
+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}
+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);
+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);
+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>
+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}
+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); }