Skip to content

Commit 87e3242

Browse files
feat: add dark mode with system preference detection
Adds a theme toggle button (fixed top-right) with sun/moon icons. The default theme follows the browser's prefers-color-scheme setting; the user's manual choice is persisted in localStorage.
1 parent bef9465 commit 87e3242

2 files changed

Lines changed: 180 additions & 17 deletions

File tree

index.html

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,35 @@
66
<title>Skipper — Test control from a Google Spreadsheet</title>
77
<meta name="description" content="Enable and disable tests directly from a Google Spreadsheet — no code changes required." />
88
<link rel="stylesheet" href="style.css" />
9+
<script>
10+
(function () {
11+
var stored = localStorage.getItem('skipper-theme');
12+
var theme = stored || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
13+
document.documentElement.setAttribute('data-theme', theme);
14+
})();
15+
</script>
916
</head>
1017
<body>
1118

19+
<button id="theme-toggle" class="theme-toggle" aria-label="Toggle theme">
20+
<!-- Sun icon (shown in dark mode) -->
21+
<svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
22+
<circle cx="12" cy="12" r="5"/>
23+
<line x1="12" y1="1" x2="12" y2="3"/>
24+
<line x1="12" y1="21" x2="12" y2="23"/>
25+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
26+
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
27+
<line x1="1" y1="12" x2="3" y2="12"/>
28+
<line x1="21" y1="12" x2="23" y2="12"/>
29+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
30+
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
31+
</svg>
32+
<!-- Moon icon (shown in light mode) -->
33+
<svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
34+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
35+
</svg>
36+
</button>
37+
1238
<!-- ─── Hero ──────────────────────────────────────── -->
1339
<section class="hero">
1440
<img class="hero-logo hero-logo--mobile" src="assets/logo.png" alt="Skipper logo" />
@@ -300,5 +326,18 @@ <h3>Does Skipper use Skipper for its own tests?</h3>
300326
</p>
301327
</footer>
302328

329+
330+
<script>
331+
(function () {
332+
var btn = document.getElementById('theme-toggle');
333+
btn.addEventListener('click', function () {
334+
var current = document.documentElement.getAttribute('data-theme');
335+
var next = current === 'dark' ? 'light' : 'dark';
336+
document.documentElement.setAttribute('data-theme', next);
337+
localStorage.setItem('skipper-theme', next);
338+
});
339+
})();
340+
</script>
341+
303342
</body>
304343
</html>

style.css

Lines changed: 141 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,85 @@
1313
--accent: #3b82f6;
1414
--accent-dark: #2563eb;
1515
--radius: 12px;
16+
17+
/* Badge colors */
18+
--badge-js-bg: #fef9c3; --badge-js-text: #854d0e;
19+
--badge-ts-bg: #dbeafe; --badge-ts-text: #1d4ed8;
20+
--badge-php-bg: #ede9fe; --badge-php-text: #6d28d9;
21+
--badge-go-bg: #d1fae5; --badge-go-text: #065f46;
22+
--badge-py-bg: #fef9c3; --badge-py-text: #92400e;
23+
--badge-java-bg: #fee2e2; --badge-java-text: #991b1b;
24+
--badge-fw-bg: #f1f5f9; --badge-fw-text: #334155; --badge-fw-border: #cbd5e1;
25+
26+
/* Featured card */
27+
--featured-bg: #eff6ff;
28+
--featured-border: #bfdbfe;
29+
--featured-label-color: #2563eb;
30+
--featured-label-border: #bfdbfe;
31+
32+
/* Inline code */
33+
--code-bg: #dbeafe; --code-text: #1d4ed8;
34+
--faq-code-bg: #f1f5f9; --faq-code-text: #334155;
35+
}
36+
37+
/* ─── Dark mode: media query (no-JS fallback) ────────── */
38+
@media (prefers-color-scheme: dark) {
39+
html:not([data-theme="light"]) {
40+
--bg: #0f172a;
41+
--surface: #1e293b;
42+
--border: #334155;
43+
--text: #f1f5f9;
44+
--text-muted: #94a3b8;
45+
--accent: #60a5fa;
46+
--accent-dark: #93c5fd;
47+
48+
--badge-js-bg: #422006; --badge-js-text: #fde68a;
49+
--badge-ts-bg: #1e3a5f; --badge-ts-text: #93c5fd;
50+
--badge-php-bg: #2e1065; --badge-php-text: #c4b5fd;
51+
--badge-go-bg: #064e3b; --badge-go-text: #6ee7b7;
52+
--badge-py-bg: #422006; --badge-py-text: #fde68a;
53+
--badge-java-bg: #450a0a; --badge-java-text: #fca5a5;
54+
--badge-fw-bg: #0f172a; --badge-fw-text: #94a3b8; --badge-fw-border: #475569;
55+
56+
--featured-bg: #172554;
57+
--featured-border: #1d4ed8;
58+
--featured-label-color: #93c5fd;
59+
--featured-label-border: #1d4ed8;
60+
61+
--code-bg: #1e3a5f; --code-text: #93c5fd;
62+
--faq-code-bg: #0f172a; --faq-code-text: #94a3b8;
63+
64+
color-scheme: dark;
65+
}
66+
}
67+
68+
/* ─── Dark mode: manual toggle ───────────────────────── */
69+
html[data-theme="dark"] {
70+
--bg: #0f172a;
71+
--surface: #1e293b;
72+
--border: #334155;
73+
--text: #f1f5f9;
74+
--text-muted: #94a3b8;
75+
--accent: #60a5fa;
76+
--accent-dark: #93c5fd;
77+
78+
--badge-js-bg: #422006; --badge-js-text: #fde68a;
79+
--badge-ts-bg: #1e3a5f; --badge-ts-text: #93c5fd;
80+
--badge-php-bg: #2e1065; --badge-php-text: #c4b5fd;
81+
--badge-go-bg: #064e3b; --badge-go-text: #6ee7b7;
82+
--badge-py-bg: #422006; --badge-py-text: #fde68a;
83+
--badge-java-bg: #450a0a; --badge-java-text: #fca5a5;
84+
--badge-fw-bg: #0f172a; --badge-fw-text: #94a3b8; --badge-fw-border: #475569;
85+
86+
--featured-bg: #172554;
87+
--featured-border: #1d4ed8;
88+
--featured-label-color: #93c5fd;
89+
--featured-label-border: #1d4ed8;
90+
91+
--code-bg: #1e3a5f; --code-text: #93c5fd;
92+
--faq-code-bg: #0f172a; --faq-code-text: #94a3b8;
93+
94+
color-scheme: dark;
1695
}
1796

1897
body {
@@ -49,6 +128,7 @@ body {
49128
object-fit: contain;
50129
filter: drop-shadow(0 8px 24px rgba(59, 130, 246, 0.15));
51130
flex-shrink: 0;
131+
border-radius: 5px;
52132
}
53133

54134
.hero-logo--mobile { display: none; }
@@ -177,18 +257,18 @@ body {
177257
}
178258

179259
/* Language badges */
180-
.badge-js { background: #fef9c3; color: #854d0e; }
181-
.badge-ts { background: #dbeafe; color: #1d4ed8; }
182-
.badge-php { background: #ede9fe; color: #6d28d9; }
183-
.badge-go { background: #d1fae5; color: #065f46; }
184-
.badge-py { background: #fef9c3; color: #92400e; }
185-
.badge-java { background: #fee2e2; color: #991b1b; }
260+
.badge-js { background: var(--badge-js-bg); color: var(--badge-js-text); }
261+
.badge-ts { background: var(--badge-ts-bg); color: var(--badge-ts-text); }
262+
.badge-php { background: var(--badge-php-bg); color: var(--badge-php-text); }
263+
.badge-go { background: var(--badge-go-bg); color: var(--badge-go-text); }
264+
.badge-py { background: var(--badge-py-bg); color: var(--badge-py-text); }
265+
.badge-java { background: var(--badge-java-bg); color: var(--badge-java-text); }
186266

187267
/* Framework badges */
188268
.badge-fw {
189-
background: #f1f5f9;
190-
color: #334155;
191-
border: 1px solid #cbd5e1;
269+
background: var(--badge-fw-bg);
270+
color: var(--badge-fw-text);
271+
border: 1px solid var(--badge-fw-border);
192272
}
193273

194274
.card-desc {
@@ -308,8 +388,8 @@ body {
308388
.uc-card--featured {
309389
flex-direction: column;
310390
gap: 24px;
311-
background: #eff6ff;
312-
border-color: #bfdbfe;
391+
background: var(--featured-bg);
392+
border-color: var(--featured-border);
313393
}
314394

315395
.uc-featured-header {
@@ -336,9 +416,9 @@ body {
336416
font-weight: 700;
337417
text-transform: uppercase;
338418
letter-spacing: 0.06em;
339-
color: #2563eb;
419+
color: var(--featured-label-color);
340420
padding-top: 4px;
341-
border-top: 1px solid #bfdbfe;
421+
border-top: 1px solid var(--featured-label-border);
342422
}
343423

344424
.uc-reasons {
@@ -367,8 +447,8 @@ body {
367447
.uc-reason code {
368448
font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
369449
font-size: 0.8em;
370-
background: #dbeafe;
371-
color: #1d4ed8;
450+
background: var(--code-bg);
451+
color: var(--code-text);
372452
padding: 1px 5px;
373453
border-radius: 4px;
374454
}
@@ -428,8 +508,8 @@ body {
428508
.faq-item code {
429509
font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
430510
font-size: 0.8em;
431-
background: #f1f5f9;
432-
color: #334155;
511+
background: var(--faq-code-bg);
512+
color: var(--faq-code-text);
433513
padding: 1px 5px;
434514
border-radius: 4px;
435515
border: 1px solid var(--border);
@@ -466,6 +546,50 @@ footer a:hover {
466546
text-decoration: underline;
467547
}
468548

549+
/* ─── Theme toggle button ────────────────────────────── */
550+
.theme-toggle {
551+
position: fixed;
552+
top: 16px;
553+
right: 16px;
554+
z-index: 100;
555+
background: var(--surface);
556+
border: 1px solid var(--border);
557+
border-radius: 8px;
558+
width: 40px;
559+
height: 40px;
560+
display: flex;
561+
align-items: center;
562+
justify-content: center;
563+
cursor: pointer;
564+
color: var(--text-muted);
565+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
566+
padding: 0;
567+
transition: background-color 0.15s, color 0.15s;
568+
}
569+
570+
.theme-toggle:hover {
571+
color: var(--text);
572+
background: var(--bg);
573+
}
574+
575+
.theme-toggle svg {
576+
width: 18px;
577+
height: 18px;
578+
flex-shrink: 0;
579+
}
580+
581+
/* Show/hide icons based on current theme */
582+
.icon-sun { display: none; }
583+
.icon-moon { display: block; }
584+
585+
html[data-theme="dark"] .icon-sun { display: block; }
586+
html[data-theme="dark"] .icon-moon { display: none; }
587+
588+
@media (prefers-color-scheme: dark) {
589+
html:not([data-theme="light"]) .icon-sun { display: block; }
590+
html:not([data-theme="light"]) .icon-moon { display: none; }
591+
}
592+
469593
/* ─── Responsive ─────────────────────────────────────── */
470594
@media (max-width: 640px) {
471595
.hero { text-align: center; padding: 48px 24px 40px; }

0 commit comments

Comments
 (0)