Skip to content

Commit 5f1aebc

Browse files
committed
feat: Implement multi-theme switcher with 8 color palettes
This commit introduces a fully functional theme switcher supporting 4 dark modes and 4 light modes. It uses CSS custom properties on the :root pseudo-class for atomic theme overrides. - Created `theme-init.js` in <head> to read localStorage synchronously, preventing Flash of Wrong Theme (FOWT). - Created an interactive popover for desktop and integrated options into the mobile menu. - Added smooth background color transitions on the body. - Replaced physical transparent borders with inset box-shadows on swatches to eliminate WebKit rendering artifacts. - Dynamically updates the <meta name="theme-color"> attribute across sessions.
1 parent ec5ba31 commit 5f1aebc

5 files changed

Lines changed: 1350 additions & 1 deletion

File tree

assets/css/index.css

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,318 @@
3030
--transition-smooth: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
3131
}
3232

33+
/* =========================================================================
34+
Theme Overrides — Dark Themes
35+
========================================================================= */
36+
37+
/* Sunset — warm amber/orange on dark charcoal */
38+
[data-theme="sunset"] {
39+
--bg-base: #0f0b07;
40+
--bg-surface: #1a1410;
41+
--text-main: #f5f0e8;
42+
--text-muted: #a89882;
43+
--border-color: rgba(255, 159, 67, 0.08);
44+
--accent-primary: #ff9f43;
45+
--accent-secondary: #ee5a24;
46+
--accent-tertiary: #f368e0;
47+
--accent-quaternary: #ff6b6b;
48+
--glass-bg: rgba(255, 255, 255, 0.03);
49+
--glass-border: rgba(255, 159, 67, 0.05);
50+
}
51+
52+
/* Forest — emerald/mint on deep dark-green */
53+
[data-theme="forest"] {
54+
--bg-base: #060d0a;
55+
--bg-surface: #0f1a14;
56+
--text-main: #e8f5e9;
57+
--text-muted: #81a88a;
58+
--border-color: rgba(0, 230, 118, 0.08);
59+
--accent-primary: #00e676;
60+
--accent-secondary: #69f0ae;
61+
--accent-tertiary: #00bcd4;
62+
--accent-quaternary: #26a69a;
63+
--glass-bg: rgba(255, 255, 255, 0.03);
64+
--glass-border: rgba(0, 230, 118, 0.05);
65+
}
66+
67+
/* Dusk — lavender/violet on dark plum */
68+
[data-theme="dusk"] {
69+
--bg-base: #0c0a12;
70+
--bg-surface: #15121f;
71+
--text-main: #ede9f6;
72+
--text-muted: #9b8ec2;
73+
--border-color: rgba(179, 136, 255, 0.08);
74+
--accent-primary: #b388ff;
75+
--accent-secondary: #7c4dff;
76+
--accent-tertiary: #ff80ab;
77+
--accent-quaternary: #ea80fc;
78+
--glass-bg: rgba(255, 255, 255, 0.03);
79+
--glass-border: rgba(179, 136, 255, 0.05);
80+
}
81+
82+
/* =========================================================================
83+
Theme Overrides — Light Themes
84+
========================================================================= */
85+
86+
/* Cyber Light — cool blue-gray */
87+
[data-theme="cyber-light"] {
88+
--bg-base: #f0f4f8;
89+
--bg-surface: #ffffff;
90+
--text-main: #1a202c;
91+
--text-muted: #64748b;
92+
--border-color: rgba(0, 0, 0, 0.08);
93+
--accent-primary: #0891b2;
94+
--accent-secondary: #3b82f6;
95+
--accent-tertiary: #8b5cf6;
96+
--accent-quaternary: #ec4899;
97+
--glass-bg: rgba(255, 255, 255, 0.7);
98+
--glass-border: rgba(0, 0, 0, 0.06);
99+
}
100+
101+
/* Sunset Light — warm cream */
102+
[data-theme="sunset-light"] {
103+
--bg-base: #fdf6ee;
104+
--bg-surface: #ffffff;
105+
--text-main: #2d1f0e;
106+
--text-muted: #92744e;
107+
--border-color: rgba(0, 0, 0, 0.08);
108+
--accent-primary: #d97706;
109+
--accent-secondary: #dc4a14;
110+
--accent-tertiary: #db2777;
111+
--accent-quaternary: #e11d48;
112+
--glass-bg: rgba(255, 255, 255, 0.7);
113+
--glass-border: rgba(0, 0, 0, 0.06);
114+
}
115+
116+
/* Forest Light — sage green */
117+
[data-theme="forest-light"] {
118+
--bg-base: #f0f7f2;
119+
--bg-surface: #ffffff;
120+
--text-main: #1a2e22;
121+
--text-muted: #5f8a6a;
122+
--border-color: rgba(0, 0, 0, 0.08);
123+
--accent-primary: #059650;
124+
--accent-secondary: #10b981;
125+
--accent-tertiary: #0891b2;
126+
--accent-quaternary: #0d9488;
127+
--glass-bg: rgba(255, 255, 255, 0.7);
128+
--glass-border: rgba(0, 0, 0, 0.06);
129+
}
130+
131+
/* Dusk Light — lavender tint */
132+
[data-theme="dusk-light"] {
133+
--bg-base: #f3f0f9;
134+
--bg-surface: #ffffff;
135+
--text-main: #1e1533;
136+
--text-muted: #7e6daa;
137+
--border-color: rgba(0, 0, 0, 0.08);
138+
--accent-primary: #7c4dff;
139+
--accent-secondary: #9c27b0;
140+
--accent-tertiary: #e91e90;
141+
--accent-quaternary: #f06292;
142+
--glass-bg: rgba(255, 255, 255, 0.7);
143+
--glass-border: rgba(0, 0, 0, 0.06);
144+
}
145+
146+
/* =========================================================================
147+
Light Theme — Element-specific overrides
148+
Hardcoded dark colors that need adjustment for light backgrounds.
149+
========================================================================= */
150+
[data-theme="cyber-light"] .navbar.scrolled,
151+
[data-theme="sunset-light"] .navbar.scrolled,
152+
[data-theme="forest-light"] .navbar.scrolled,
153+
[data-theme="dusk-light"] .navbar.scrolled {
154+
background: rgba(255, 255, 255, 0.85);
155+
}
156+
157+
[data-theme="cyber-light"] .mobile-menu,
158+
[data-theme="sunset-light"] .mobile-menu,
159+
[data-theme="forest-light"] .mobile-menu,
160+
[data-theme="dusk-light"] .mobile-menu {
161+
background: rgba(255, 255, 255, 0.98);
162+
}
163+
164+
[data-theme="cyber-light"] .footer,
165+
[data-theme="sunset-light"] .footer,
166+
[data-theme="forest-light"] .footer,
167+
[data-theme="dusk-light"] .footer {
168+
background: rgba(245, 245, 250, 0.5);
169+
}
170+
171+
[data-theme="cyber-light"] .btn-primary,
172+
[data-theme="sunset-light"] .btn-primary,
173+
[data-theme="forest-light"] .btn-primary,
174+
[data-theme="dusk-light"] .btn-primary {
175+
color: #fff;
176+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
177+
}
178+
179+
[data-theme="cyber-light"] .btn-primary:hover,
180+
[data-theme="sunset-light"] .btn-primary:hover,
181+
[data-theme="forest-light"] .btn-primary:hover,
182+
[data-theme="dusk-light"] .btn-primary:hover {
183+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
184+
}
185+
186+
[data-theme="cyber-light"] .glass-card:hover,
187+
[data-theme="sunset-light"] .glass-card:hover,
188+
[data-theme="forest-light"] .glass-card:hover,
189+
[data-theme="dusk-light"] .glass-card:hover {
190+
box-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.15);
191+
}
192+
193+
[data-theme="cyber-light"] .bg-glow,
194+
[data-theme="sunset-light"] .bg-glow,
195+
[data-theme="forest-light"] .bg-glow,
196+
[data-theme="dusk-light"] .bg-glow {
197+
opacity: 0.15;
198+
}
199+
200+
[data-theme="cyber-light"] .timeline-marker,
201+
[data-theme="sunset-light"] .timeline-marker,
202+
[data-theme="forest-light"] .timeline-marker,
203+
[data-theme="dusk-light"] .timeline-marker {
204+
border-color: var(--bg-base);
205+
}
206+
207+
/* =========================================================================
208+
Theme Switcher UI
209+
========================================================================= */
210+
.theme-switcher-wrapper {
211+
position: relative;
212+
}
213+
214+
.theme-switcher {
215+
cursor: pointer;
216+
position: relative;
217+
}
218+
219+
.theme-popover {
220+
position: absolute;
221+
top: calc(100% + 12px);
222+
right: 0;
223+
background: var(--bg-surface);
224+
border: 1px solid var(--border-color);
225+
border-radius: 16px;
226+
padding: 1rem;
227+
min-width: 220px;
228+
opacity: 0;
229+
visibility: hidden;
230+
transform: translateY(-8px);
231+
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
232+
z-index: 200;
233+
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
234+
}
235+
236+
.theme-popover.active {
237+
opacity: 1;
238+
visibility: visible;
239+
transform: translateY(0);
240+
}
241+
242+
.theme-group-title {
243+
display: block;
244+
font-size: 0.7rem;
245+
font-family: var(--font-heading);
246+
font-weight: 600;
247+
text-transform: uppercase;
248+
letter-spacing: 1.5px;
249+
color: var(--text-muted);
250+
margin-bottom: 0.5rem;
251+
padding: 0 0.25rem;
252+
}
253+
254+
.theme-group + .theme-group {
255+
margin-top: 0.75rem;
256+
padding-top: 0.75rem;
257+
border-top: 1px solid var(--border-color);
258+
}
259+
260+
.theme-options {
261+
display: grid;
262+
grid-template-columns: repeat(4, 1fr);
263+
gap: 0.5rem;
264+
}
265+
266+
.theme-option {
267+
display: flex;
268+
flex-direction: column;
269+
align-items: center;
270+
gap: 0.3rem;
271+
background: none;
272+
border: none;
273+
cursor: pointer;
274+
padding: 0.4rem 0.25rem;
275+
border-radius: 10px;
276+
transition: var(--transition-smooth);
277+
font-family: var(--font-heading);
278+
}
279+
280+
.theme-option:hover {
281+
background: var(--glass-bg);
282+
}
283+
284+
.theme-swatch {
285+
display: block;
286+
width: 28px;
287+
height: 28px;
288+
border-radius: 50%;
289+
transition: all 0.3s ease;
290+
position: relative;
291+
}
292+
293+
/* Swatch gradient colors */
294+
.theme-swatch-cyber { background: linear-gradient(135deg, #00f2fe, #4facfe); }
295+
.theme-swatch-sunset { background: linear-gradient(135deg, #ff9f43, #ee5a24); }
296+
.theme-swatch-forest { background: linear-gradient(135deg, #00e676, #69f0ae); }
297+
.theme-swatch-dusk { background: linear-gradient(135deg, #b388ff, #7c4dff); }
298+
.theme-swatch-cyber-light { background: linear-gradient(135deg, #0891b2, #3b82f6); }
299+
.theme-swatch-sunset-light { background: linear-gradient(135deg, #d97706, #dc4a14); }
300+
.theme-swatch-forest-light { background: linear-gradient(135deg, #059650, #10b981); }
301+
.theme-swatch-dusk-light { background: linear-gradient(135deg, #7c4dff, #9c27b0); }
302+
303+
.theme-option.active .theme-swatch {
304+
box-shadow: inset 0 0 0 2px var(--text-main), 0 0 0 2px var(--bg-surface), 0 0 0 4px var(--text-muted);
305+
}
306+
307+
.theme-option-label {
308+
font-size: 0.6rem;
309+
font-weight: 500;
310+
color: var(--text-muted);
311+
white-space: nowrap;
312+
}
313+
314+
.theme-option.active .theme-option-label {
315+
color: var(--text-main);
316+
font-weight: 600;
317+
}
318+
319+
/* Mobile theme switcher */
320+
.mobile-theme-switcher {
321+
padding: 1rem 0;
322+
}
323+
324+
.mobile-theme-switcher .theme-group-title {
325+
text-align: center;
326+
margin-bottom: 0.75rem;
327+
}
328+
329+
.mobile-theme-switcher .theme-group + .theme-group {
330+
margin-top: 1rem;
331+
padding-top: 1rem;
332+
}
333+
334+
.mobile-theme-switcher .theme-options {
335+
display: flex;
336+
justify-content: center;
337+
gap: 1rem;
338+
}
339+
340+
.mobile-theme-switcher .theme-swatch {
341+
width: 36px;
342+
height: 36px;
343+
}
344+
33345
/* =========================================================================
34346
Reset & Base Styles
35347
========================================================================= */
@@ -51,6 +363,7 @@ body {
51363
overflow-x: hidden;
52364
-webkit-font-smoothing: antialiased;
53365
position: relative;
366+
transition: background-color 0.4s ease, color 0.4s ease;
54367
}
55368

56369
a {
@@ -236,6 +549,7 @@ h1, h2, h3, h4, h5, h6 {
236549
gap: 0.3rem;
237550
font-size: 0.95rem;
238551
font-weight: 500;
552+
color: var(--text-main);
239553
background: rgba(255, 255, 255, 0.05);
240554
padding: 0.5rem 1rem;
241555
border-radius: 20px;

0 commit comments

Comments
 (0)