Skip to content

Commit d9a73e0

Browse files
zhihaopanclaude
andcommitted
feat: add 7-theme switcher with color dot UI
Add multi-theme support with 7 palettes (Catppuccin Mocha, GitHub Dark, Nord, Dracula, Solarized Dark, One Dark, Catppuccin Latte). Themes are selectable via colored dots in the tab bar, persisted to localStorage, and default to Catppuccin Latte (light theme). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6de837d commit d9a73e0

3 files changed

Lines changed: 316 additions & 11 deletions

File tree

css/style.css

Lines changed: 234 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
/* ===== Catppuccin Dark Theme Variables ===== */
2-
:root {
1+
/* ===== Theme Variables ===== */
2+
3+
/* --- Default: Catppuccin Mocha (dark purple) --- */
4+
:root,
5+
[data-theme="catppuccin-mocha"] {
36
--bg: #1e1e2e;
47
--bg-secondary: #252536;
58
--bg-input: #1a1a2a;
@@ -22,7 +25,6 @@
2225
--scrollbar-thumb: #3a3a5a;
2326
--scrollbar-track: transparent;
2427
--fold-hover: #33334d;
25-
/* Diff highlight colors */
2628
--diff-added: #a6e3a1;
2729
--diff-removed: #f38ba8;
2830
--diff-modified: #f9e2af;
@@ -31,6 +33,198 @@
3133
--diff-modified-bg: rgba(249,226,175,0.1);
3234
}
3335

36+
/* --- GitHub Dark --- */
37+
[data-theme="github-dark"] {
38+
--bg: #0d1117;
39+
--bg-secondary: #161b22;
40+
--bg-input: #0d1117;
41+
--border: #30363d;
42+
--text: #e6edf3;
43+
--text-muted: #7d8590;
44+
--accent: #58a6ff;
45+
--error: #f85149;
46+
--key-color: #79c0ff;
47+
--string-color: #a5d6ff;
48+
--number-color: #79c0ff;
49+
--boolean-color: #ff7b72;
50+
--null-color: #f85149;
51+
--brace-color: #8b949e;
52+
--toolbar-bg: #161b22;
53+
--btn-bg: #21262d;
54+
--btn-hover: #30363d;
55+
--toggle-bg: #30363d;
56+
--toggle-active: #58a6ff;
57+
--scrollbar-thumb: #30363d;
58+
--scrollbar-track: transparent;
59+
--fold-hover: #1c2128;
60+
--diff-added: #3fb950;
61+
--diff-removed: #f85149;
62+
--diff-modified: #d29922;
63+
--diff-added-bg: rgba(63,185,80,0.12);
64+
--diff-removed-bg: rgba(248,81,73,0.12);
65+
--diff-modified-bg: rgba(210,153,34,0.12);
66+
}
67+
68+
/* --- Nord --- */
69+
[data-theme="nord"] {
70+
--bg: #2e3440;
71+
--bg-secondary: #3b4252;
72+
--bg-input: #2e3440;
73+
--border: #4c566a;
74+
--text: #eceff4;
75+
--text-muted: #81a1c1;
76+
--accent: #88c0d0;
77+
--error: #bf616a;
78+
--key-color: #81a1c1;
79+
--string-color: #a3be8c;
80+
--number-color: #b48ead;
81+
--boolean-color: #d08770;
82+
--null-color: #bf616a;
83+
--brace-color: #7b88a1;
84+
--toolbar-bg: #3b4252;
85+
--btn-bg: #434c5e;
86+
--btn-hover: #4c566a;
87+
--toggle-bg: #4c566a;
88+
--toggle-active: #88c0d0;
89+
--scrollbar-thumb: #4c566a;
90+
--scrollbar-track: transparent;
91+
--fold-hover: #434c5e;
92+
--diff-added: #a3be8c;
93+
--diff-removed: #bf616a;
94+
--diff-modified: #ebcb8b;
95+
--diff-added-bg: rgba(163,190,140,0.12);
96+
--diff-removed-bg: rgba(191,97,106,0.12);
97+
--diff-modified-bg: rgba(235,203,139,0.12);
98+
}
99+
100+
/* --- Dracula --- */
101+
[data-theme="dracula"] {
102+
--bg: #282a36;
103+
--bg-secondary: #2d2f3d;
104+
--bg-input: #21222c;
105+
--border: #44475a;
106+
--text: #f8f8f2;
107+
--text-muted: #6272a4;
108+
--accent: #bd93f9;
109+
--error: #ff5555;
110+
--key-color: #bd93f9;
111+
--string-color: #50fa7b;
112+
--number-color: #ffb86c;
113+
--boolean-color: #ff79c6;
114+
--null-color: #ff5555;
115+
--brace-color: #6272a4;
116+
--toolbar-bg: #2d2f3d;
117+
--btn-bg: #44475a;
118+
--btn-hover: #525575;
119+
--toggle-bg: #44475a;
120+
--toggle-active: #bd93f9;
121+
--scrollbar-thumb: #44475a;
122+
--scrollbar-track: transparent;
123+
--fold-hover: #383a4a;
124+
--diff-added: #50fa7b;
125+
--diff-removed: #ff5555;
126+
--diff-modified: #f1fa8c;
127+
--diff-added-bg: rgba(80,250,123,0.1);
128+
--diff-removed-bg: rgba(255,85,85,0.1);
129+
--diff-modified-bg: rgba(241,250,140,0.1);
130+
}
131+
132+
/* --- Solarized Dark --- */
133+
[data-theme="solarized-dark"] {
134+
--bg: #002b36;
135+
--bg-secondary: #073642;
136+
--bg-input: #002b36;
137+
--border: #2a4a52;
138+
--text: #93a1a1;
139+
--text-muted: #657b83;
140+
--accent: #268bd2;
141+
--error: #dc322f;
142+
--key-color: #268bd2;
143+
--string-color: #2aa198;
144+
--number-color: #d33682;
145+
--boolean-color: #cb4b16;
146+
--null-color: #dc322f;
147+
--brace-color: #586e75;
148+
--toolbar-bg: #073642;
149+
--btn-bg: #0a4050;
150+
--btn-hover: #14546a;
151+
--toggle-bg: #2a4a52;
152+
--toggle-active: #268bd2;
153+
--scrollbar-thumb: #2a4a52;
154+
--scrollbar-track: transparent;
155+
--fold-hover: #0d3d4d;
156+
--diff-added: #2aa198;
157+
--diff-removed: #dc322f;
158+
--diff-modified: #b58900;
159+
--diff-added-bg: rgba(42,161,152,0.12);
160+
--diff-removed-bg: rgba(220,50,47,0.12);
161+
--diff-modified-bg: rgba(181,137,0,0.12);
162+
}
163+
164+
/* --- One Dark (Atom) --- */
165+
[data-theme="one-dark"] {
166+
--bg: #282c34;
167+
--bg-secondary: #2c313a;
168+
--bg-input: #23272e;
169+
--border: #3e4451;
170+
--text: #abb2bf;
171+
--text-muted: #636d83;
172+
--accent: #61afef;
173+
--error: #e06c75;
174+
--key-color: #61afef;
175+
--string-color: #98c379;
176+
--number-color: #d19a66;
177+
--boolean-color: #c678dd;
178+
--null-color: #e06c75;
179+
--brace-color: #636d83;
180+
--toolbar-bg: #2c313a;
181+
--btn-bg: #3e4451;
182+
--btn-hover: #4b5263;
183+
--toggle-bg: #3e4451;
184+
--toggle-active: #61afef;
185+
--scrollbar-thumb: #3e4451;
186+
--scrollbar-track: transparent;
187+
--fold-hover: #333842;
188+
--diff-added: #98c379;
189+
--diff-removed: #e06c75;
190+
--diff-modified: #e5c07b;
191+
--diff-added-bg: rgba(152,195,121,0.1);
192+
--diff-removed-bg: rgba(224,108,117,0.1);
193+
--diff-modified-bg: rgba(229,192,123,0.1);
194+
}
195+
196+
/* --- Light: Catppuccin Latte --- */
197+
[data-theme="catppuccin-latte"] {
198+
--bg: #eff1f5;
199+
--bg-secondary: #e6e9ef;
200+
--bg-input: #ffffff;
201+
--border: #ccd0da;
202+
--text: #4c4f69;
203+
--text-muted: #8c8fa1;
204+
--accent: #1e66f5;
205+
--error: #d20f39;
206+
--key-color: #1e66f5;
207+
--string-color: #40a02b;
208+
--number-color: #fe640b;
209+
--boolean-color: #8839ef;
210+
--null-color: #d20f39;
211+
--brace-color: #9ca0b0;
212+
--toolbar-bg: #e6e9ef;
213+
--btn-bg: #dce0e8;
214+
--btn-hover: #ccd0da;
215+
--toggle-bg: #ccd0da;
216+
--toggle-active: #1e66f5;
217+
--scrollbar-thumb: #bcc0cc;
218+
--scrollbar-track: transparent;
219+
--fold-hover: #dce0e8;
220+
--diff-added: #40a02b;
221+
--diff-removed: #d20f39;
222+
--diff-modified: #df8e1d;
223+
--diff-added-bg: rgba(64,160,43,0.1);
224+
--diff-removed-bg: rgba(210,15,57,0.1);
225+
--diff-modified-bg: rgba(223,142,29,0.1);
226+
}
227+
34228
* { margin: 0; padding: 0; box-sizing: border-box; }
35229

36230
body {
@@ -51,7 +245,14 @@ body {
51245
background: var(--toolbar-bg);
52246
border-bottom: 2px solid var(--border);
53247
flex-shrink: 0;
248+
}
249+
250+
.tab-bar-tabs {
251+
display: flex;
252+
align-items: center;
54253
overflow-x: auto;
254+
flex: 1;
255+
min-width: 0;
55256
}
56257

57258
.tab-bar .app-title {
@@ -538,3 +739,33 @@ body {
538739
margin: 0 6px;
539740
color: var(--text-muted);
540741
}
742+
743+
/* ===== Theme Switcher ===== */
744+
.theme-switcher {
745+
display: flex;
746+
align-items: center;
747+
gap: 6px;
748+
padding: 0 16px;
749+
flex-shrink: 0;
750+
height: 100%;
751+
}
752+
753+
.theme-switcher .theme-dot {
754+
width: 14px;
755+
height: 14px;
756+
border-radius: 50%;
757+
cursor: pointer;
758+
border: 2px solid transparent;
759+
transition: border-color 0.15s, transform 0.15s;
760+
flex-shrink: 0;
761+
}
762+
763+
.theme-switcher .theme-dot:hover {
764+
transform: scale(1.3);
765+
border-color: var(--text);
766+
}
767+
768+
.theme-switcher .theme-dot.active {
769+
border-color: var(--text);
770+
transform: scale(1.2);
771+
}

index.html

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,26 @@
44
<meta charset="UTF-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1.0">
66
<title>开发者工具箱</title>
7-
<link rel="stylesheet" href="css/style.css?v=20260320-center-middle">
7+
<link rel="stylesheet" href="css/style.css?v=20260320-themes">
8+
<script>
9+
// Apply saved theme immediately to prevent flash
10+
try { var t = localStorage.getItem('devtools-theme') || 'catppuccin-latte'; document.documentElement.setAttribute('data-theme', t); } catch(e) {}
11+
</script>
812
</head>
913
<body>
1014

1115
<!-- Tab Navigation Bar -->
1216
<div class="tab-bar">
13-
<span class="app-title">🛠 开发者工具箱</span>
14-
<button class="tab-btn active" data-tab="tab-json-viewer">JSON 查看器</button>
15-
<button class="tab-btn" data-tab="tab-json-diff">JSON 对比</button>
16-
<button class="tab-btn" data-tab="tab-base64">Base64</button>
17-
<button class="tab-btn" data-tab="tab-url-codec">URL 编解码</button>
18-
<button class="tab-btn" data-tab="tab-timestamp">时间戳</button>
17+
<div class="tab-bar-tabs">
18+
<span class="app-title">🛠 开发者工具箱</span>
19+
<button class="tab-btn active" data-tab="tab-json-viewer">JSON 查看器</button>
20+
<button class="tab-btn" data-tab="tab-json-diff">JSON 对比</button>
21+
<button class="tab-btn" data-tab="tab-base64">Base64</button>
22+
<button class="tab-btn" data-tab="tab-url-codec">URL 编解码</button>
23+
<button class="tab-btn" data-tab="tab-timestamp">时间戳</button>
24+
</div>
25+
<!-- Theme Switcher -->
26+
<div class="theme-switcher" id="themeSwitcher"></div>
1927
</div>
2028

2129
<!-- ===== Tab 1: JSON Viewer ===== -->
@@ -170,7 +178,8 @@ <h2>时间戳转换</h2>
170178
</div>
171179

172180
<!-- Scripts -->
173-
<script src="js/app.js"></script>
181+
<script src="js/theme.js?v=20260320-dots"></script>
182+
<script src="js/app.js?v=20260320"></script>
174183
<script src="js/json-viewer.js"></script>
175184
<script src="js/json-diff.js"></script>
176185
<script src="js/base64.js"></script>

js/theme.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// ===== Theme Switcher =====
2+
(function() {
3+
'use strict';
4+
5+
var THEMES = [
6+
{ id: 'catppuccin-mocha', name: 'Catppuccin Mocha', color: '#89b4fa' },
7+
{ id: 'github-dark', name: 'GitHub Dark', color: '#58a6ff' },
8+
{ id: 'nord', name: 'Nord', color: '#88c0d0' },
9+
{ id: 'dracula', name: 'Dracula', color: '#bd93f9' },
10+
{ id: 'solarized-dark', name: 'Solarized Dark', color: '#268bd2' },
11+
{ id: 'one-dark', name: 'One Dark', color: '#61afef' },
12+
{ id: 'catppuccin-latte', name: 'Catppuccin Latte', color: '#dce0e8' }
13+
];
14+
15+
var STORAGE_KEY = 'devtools-theme';
16+
17+
function getStoredTheme() {
18+
try { return localStorage.getItem(STORAGE_KEY); } catch(e) { return null; }
19+
}
20+
21+
function storeTheme(id) {
22+
try { localStorage.setItem(STORAGE_KEY, id); } catch(e) {}
23+
}
24+
25+
function applyTheme(id) {
26+
document.documentElement.setAttribute('data-theme', id);
27+
storeTheme(id);
28+
29+
// Update active dot
30+
var container = document.getElementById('themeSwitcher');
31+
if (!container) return;
32+
var dots = container.querySelectorAll('.theme-dot');
33+
dots.forEach(function(dot) {
34+
dot.classList.toggle('active', dot.getAttribute('data-theme-id') === id);
35+
});
36+
}
37+
38+
function init() {
39+
var container = document.getElementById('themeSwitcher');
40+
if (!container) return;
41+
42+
// Build dots
43+
THEMES.forEach(function(t) {
44+
var dot = document.createElement('div');
45+
dot.className = 'theme-dot';
46+
dot.setAttribute('data-theme-id', t.id);
47+
dot.setAttribute('title', t.name);
48+
dot.style.background = t.color;
49+
dot.addEventListener('click', function() {
50+
applyTheme(t.id);
51+
});
52+
container.appendChild(dot);
53+
});
54+
55+
// Restore saved theme
56+
var saved = getStoredTheme();
57+
applyTheme(saved || 'catppuccin-latte');
58+
}
59+
60+
if (document.readyState === 'loading') {
61+
document.addEventListener('DOMContentLoaded', init);
62+
} else {
63+
init();
64+
}
65+
})();

0 commit comments

Comments
 (0)