Skip to content

Commit 8b5d84b

Browse files
committed
feat: implement light/dark theme toggle with persistent storage and system preference support
1 parent 574dc59 commit 8b5d84b

2 files changed

Lines changed: 164 additions & 9 deletions

File tree

index.html

Lines changed: 85 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,46 @@
1212
rel="stylesheet">
1313
<link rel="stylesheet" href="style.css">
1414
<link rel="icon" href="/favicon.ico">
15+
<script>
16+
(function () {
17+
const savedTheme = localStorage.getItem('theme');
18+
if (savedTheme) {
19+
document.documentElement.setAttribute('data-theme', savedTheme);
20+
}
21+
})();
22+
</script>
1523
</head>
1624

1725
<body>
1826

1927
<nav>
20-
<a href="/" class="nav-logo">droidspaces</a>
21-
<ul class="nav-links">
22-
<li><a href="#features">Features</a></li>
23-
<li><a hidden href="docs/">Docs</a></li>
24-
<li><a href="https://github.com/ravindu644/Droidspaces-OSS">GitHub</a></li>
25-
<li><a href="https://t.me/Droidspaces">Telegram</a></li>
26-
</ul>
28+
<div class="container" style="display: flex; align-items: center; justify-content: space-between; height: 100%;">
29+
<a href="/" class="nav-logo">droidspaces</a>
30+
<ul class="nav-links">
31+
<li><a href="#features">Features</a></li>
32+
<li><a href="https://github.com/ravindu644/Droidspaces-OSS">GitHub</a></li>
33+
<li><a href="https://t.me/Droidspaces">Telegram</a></li>
34+
<li>
35+
<button class="theme-toggle" id="theme-toggle" title="Toggle theme">
36+
<svg class="sun-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
37+
style="display:none">
38+
<circle cx="12" cy="12" r="5"></circle>
39+
<line x1="12" y1="1" x2="12" y2="3"></line>
40+
<line x1="12" y1="21" x2="12" y2="23"></line>
41+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
42+
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
43+
<line x1="1" y1="12" x2="3" y2="12"></line>
44+
<line x1="21" y1="12" x2="23" y2="12"></line>
45+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
46+
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
47+
</svg>
48+
<svg class="moon-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
49+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
50+
</svg>
51+
</button>
52+
</li>
53+
</ul>
54+
</div>
2755
</nav>
2856

2957
<!-- HERO -->
@@ -420,6 +448,56 @@ <h2>Ready to run real containers?</h2>
420448
}
421449

422450
revealLines();
451+
452+
// Theme Toggle Logic
453+
const themeToggle = document.getElementById('theme-toggle');
454+
const sunIcon = themeToggle.querySelector('.sun-icon');
455+
const moonIcon = themeToggle.querySelector('.moon-icon');
456+
const mediaQuery = window.matchMedia('(prefers-color-scheme: light)');
457+
458+
function updateIcons(theme) {
459+
if (theme === 'light') {
460+
sunIcon.style.display = 'none';
461+
moonIcon.style.display = 'block';
462+
} else {
463+
sunIcon.style.display = 'block';
464+
moonIcon.style.display = 'none';
465+
}
466+
}
467+
468+
function getEffectiveTheme() {
469+
const saved = localStorage.getItem('theme');
470+
if (saved) return saved;
471+
return mediaQuery.matches ? 'light' : 'dark';
472+
}
473+
474+
function applyTheme(theme) {
475+
if (theme === 'system') {
476+
document.documentElement.removeAttribute('data-theme');
477+
updateIcons(mediaQuery.matches ? 'light' : 'dark');
478+
} else {
479+
document.documentElement.setAttribute('data-theme', theme);
480+
updateIcons(theme);
481+
}
482+
}
483+
484+
// Initial apply
485+
applyTheme(getEffectiveTheme());
486+
487+
// Listen for system changes
488+
mediaQuery.addEventListener('change', (e) => {
489+
if (!localStorage.getItem('theme')) {
490+
applyTheme(e.matches ? 'light' : 'dark');
491+
}
492+
});
493+
494+
themeToggle.addEventListener('click', () => {
495+
const currentTheme = getEffectiveTheme();
496+
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
497+
498+
localStorage.setItem('theme', newTheme);
499+
applyTheme(newTheme);
500+
});
423501
})();
424502
</script>
425503
</body>

style.css

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,71 @@
1616
--sans: 'DM Sans', sans-serif;
1717
}
1818

19+
@media (prefers-color-scheme: light) {
20+
:root:not([data-theme="dark"]) {
21+
--bg: #ffffff;
22+
--bg2: #f9fafb;
23+
--bg3: #f3f4f6;
24+
--border: #e5e7eb;
25+
--border2: #d1d5db;
26+
--text: #111827;
27+
--muted: #4b5563;
28+
--accent: #2563eb;
29+
--accent2: #1d4ed8;
30+
}
31+
}
32+
33+
:root[data-theme="light"] {
34+
--bg: #ffffff;
35+
--bg2: #f9fafb;
36+
--bg3: #f3f4f6;
37+
--border: #e5e7eb;
38+
--border2: #d1d5db;
39+
--text: #111827;
40+
--muted: #4b5563;
41+
--accent: #2563eb;
42+
--accent2: #1d4ed8;
43+
}
44+
45+
:root[data-theme="light"] .hl { background: rgba(37, 99, 235, 0.07) !important; }
46+
:root[data-theme="light"] .hero::before {
47+
background:
48+
radial-gradient(ellipse 60% 50% at 20% 40%, rgba(37, 99, 235, 0.04) 0%, transparent 60%),
49+
radial-gradient(ellipse 40% 60% at 80% 20%, rgba(52, 168, 83, 0.03) 0%, transparent 50%);
50+
}
51+
:root[data-theme="light"] .hero-badge {
52+
background: rgba(37, 99, 235, 0.05);
53+
border-color: rgba(37, 99, 235, 0.1);
54+
}
55+
56+
@media (prefers-color-scheme: light) {
57+
:root:not([data-theme="dark"]) .hl { background: rgba(37, 99, 235, 0.07) !important; }
58+
:root:not([data-theme="dark"]) .hero::before {
59+
background:
60+
radial-gradient(ellipse 60% 50% at 20% 40%, rgba(37, 99, 235, 0.04) 0%, transparent 60%),
61+
radial-gradient(ellipse 40% 60% at 80% 20%, rgba(52, 168, 83, 0.03) 0%, transparent 50%);
62+
}
63+
:root:not([data-theme="dark"]) .hero-badge {
64+
background: rgba(37, 99, 235, 0.05);
65+
border-color: rgba(37, 99, 235, 0.1);
66+
}
67+
}
68+
69+
.theme-toggle {
70+
background: none;
71+
border: none;
72+
color: var(--muted);
73+
cursor: pointer;
74+
padding: 0.25rem;
75+
margin-left: 0.5rem;
76+
display: flex;
77+
align-items: center;
78+
justify-content: center;
79+
transition: color 0.15s;
80+
}
81+
.theme-toggle:hover { color: var(--text); }
82+
.theme-toggle svg { width: 18px; height: 18px; }
83+
1984
html { scroll-behavior: smooth; }
2085

2186
body {
@@ -30,12 +95,24 @@
3095
/* NAV */
3196
nav {
3297
position: sticky; top: 0; z-index: 100;
33-
display: flex; align-items: center; justify-content: space-between;
3498
padding: 0 2rem;
3599
height: 56px;
36-
background: rgba(10,12,15,0.85);
100+
background: var(--bg);
101+
opacity: 0.98;
37102
backdrop-filter: blur(12px);
38103
border-bottom: 1px solid var(--border);
104+
transition: background 0.3s, border-color 0.3s;
105+
}
106+
:root:not([data-theme="light"]) nav {
107+
background: rgba(10,12,15,0.85);
108+
}
109+
:root[data-theme="light"] nav {
110+
background: rgba(255,255,255,0.85);
111+
}
112+
@media (prefers-color-scheme: light) {
113+
:root:not([data-theme="dark"]) nav {
114+
background: rgba(255,255,255,0.85);
115+
}
39116
}
40117
.nav-logo {
41118
font-family: var(--mono);

0 commit comments

Comments
 (0)