Skip to content

Commit 88f67c4

Browse files
author
Forge
committed
feat: dual theme — light default + dark toggle with localStorage
1 parent b0168b0 commit 88f67c4

1 file changed

Lines changed: 154 additions & 34 deletions

File tree

benchmarks/landing.html

Lines changed: 154 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!DOCTYPE html>
2-
<html lang="en">
2+
<html lang="en" data-theme="light">
33
<head>
44
<meta charset="UTF-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -8,7 +8,38 @@
88
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
99
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
1010
<style>
11+
/* ── Light theme (default) ────────────── */
1112
:root {
13+
--bg: #F8FAFC;
14+
--bg-card: #FFFFFF;
15+
--bg-elevated: #F1F5F9;
16+
--text: #0F172A;
17+
--text-dim: #64748B;
18+
--accent: #16A34A;
19+
--accent-glow: rgba(22,163,74,0.2);
20+
--red: #DC2626;
21+
--yellow: #D97706;
22+
--blue: #2563EB;
23+
--purple: #9333EA;
24+
--cyan: #0891B2;
25+
--border: #E2E8F0;
26+
--nav-bg: rgba(248,250,252,0.85);
27+
--nav-border: rgba(226,232,240,0.7);
28+
--code-bg: #F1F5F9;
29+
--code-border: #E2E8F0;
30+
--glow-hero: rgba(22,163,74,0.08);
31+
--glow-cta: rgba(22,163,74,0.06);
32+
--radius: 12px;
33+
--spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);
34+
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
35+
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
36+
--shadow-sm: 0 1px 3px rgba(0,0,0,0.06);
37+
--shadow-md: 0 4px 20px rgba(0,0,0,0.08);
38+
--shadow-hover: 0 8px 30px rgba(22,163,74,0.12);
39+
}
40+
41+
/* ── Dark theme ───────────────────────── */
42+
[data-theme="dark"] {
1243
--bg: #0F172A;
1344
--bg-card: #1E293B;
1445
--bg-elevated: #334155;
@@ -22,11 +53,17 @@
2253
--purple: #A855F7;
2354
--cyan: #06B6D4;
2455
--border: #334155;
25-
--radius: 12px;
26-
--spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);
27-
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
28-
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
56+
--nav-bg: rgba(15,23,42,0.85);
57+
--nav-border: rgba(51,65,85,0.5);
58+
--code-bg: #0F172A;
59+
--code-border: #334155;
60+
--glow-hero: rgba(34,197,94,0.06);
61+
--glow-cta: rgba(34,197,94,0.06);
62+
--shadow-sm: 0 1px 3px rgba(0,0,0,0.3);
63+
--shadow-md: 0 4px 20px rgba(0,0,0,0.3);
64+
--shadow-hover: 0 8px 30px rgba(34,197,94,0.12);
2965
}
66+
3067
* { margin:0; padding:0; box-sizing:border-box; }
3168
html { scroll-behavior: smooth; }
3269
body {
@@ -35,9 +72,18 @@
3572
color: var(--text);
3673
line-height: 1.6;
3774
overflow-x: hidden;
75+
transition: background 0.4s var(--ease-out), color 0.4s var(--ease-out);
3876
}
3977
::selection { background: var(--accent-glow); }
4078

79+
/* ── Theme transition ─────────────────── */
80+
*, *::before, *::after {
81+
transition: background-color 0.4s var(--ease-out),
82+
border-color 0.4s var(--ease-out),
83+
color 0.4s var(--ease-out),
84+
box-shadow 0.4s var(--ease-out);
85+
}
86+
4187
/* ── Scroll reveal ─────────────────────── */
4288
.reveal {
4389
opacity: 0;
@@ -49,8 +95,8 @@
4995
/* ── Nav ──────────────────────────────── */
5096
nav {
5197
position: fixed; top:0; left:0; right:0; z-index:100;
52-
background: rgba(15,23,42,0.85); backdrop-filter: blur(12px);
53-
border-bottom: 1px solid rgba(51,65,85,0.5);
98+
background: var(--nav-bg); backdrop-filter: blur(12px);
99+
border-bottom: 1px solid var(--nav-border);
54100
padding: 0 2rem; height: 56px;
55101
display: flex; align-items: center; justify-content: space-between;
56102
}
@@ -59,12 +105,41 @@
59105
background: linear-gradient(135deg, var(--accent), var(--cyan));
60106
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
61107
}
108+
nav .nav-links { display:flex; align-items:center; gap:0.5rem; }
62109
nav a {
63110
color: var(--text-dim); text-decoration: none; font-size: 0.875rem;
64111
transition: color 0.2s; margin-left: 1.5rem;
65112
}
66113
nav a:hover { color: var(--text); }
67114

115+
/* ── Theme toggle ─────────────────────── */
116+
.theme-toggle {
117+
background: var(--bg-elevated);
118+
border: 1px solid var(--border);
119+
border-radius: 100px;
120+
padding: 0.4rem;
121+
display: flex; align-items: center; gap: 0.25rem;
122+
cursor: pointer;
123+
margin-left: 1rem;
124+
transition: border-color 0.3s;
125+
}
126+
.theme-toggle:hover { border-color: var(--accent); }
127+
.theme-toggle .toggle-option {
128+
width: 32px; height: 28px;
129+
display: flex; align-items: center; justify-content: center;
130+
border-radius: 100px;
131+
font-size: 0.85rem;
132+
transition: background 0.3s var(--spring);
133+
}
134+
.theme-toggle .toggle-option.active {
135+
background: var(--accent);
136+
color: #fff;
137+
}
138+
[data-theme="dark"] .theme-toggle .toggle-option.active {
139+
background: var(--accent);
140+
color: #0F172A;
141+
}
142+
68143
/* ── Container ────────────────────────── */
69144
.container { max-width: 1100px; margin:0 auto; padding:0 2rem; }
70145
section { padding: 6rem 0; }
@@ -74,19 +149,19 @@
74149
.hero::before {
75150
content:''; position:absolute; top:-100px; left:50%; transform:translateX(-50%);
76151
width: 600px; height: 600px;
77-
background: radial-gradient(circle, rgba(34,197,94,0.06) 0%, transparent 70%);
152+
background: radial-gradient(circle, var(--glow-hero) 0%, transparent 70%);
78153
pointer-events: none;
79154
}
80155
.hero .badge {
81156
display: inline-flex; align-items: center; gap: 0.5rem;
82-
background: rgba(34,197,94,0.1); border: 1px solid rgba(34,197,94,0.2);
157+
background: rgba(34,197,94,0.08); border: 1px solid rgba(34,197,94,0.2);
83158
padding: 0.4rem 1rem; border-radius: 100px; font-size: 0.85rem;
84159
color: var(--accent); margin-bottom: 1.5rem;
85160
animation: badge-pulse 3s ease-in-out infinite;
86161
}
87162
.hero .badge .dot { width:8px; height:8px; background:var(--accent); border-radius:50%; }
88163
@keyframes badge-pulse {
89-
0%,100% { box-shadow: 0 0 0 0 rgba(34,197,94,0.3); }
164+
0%,100% { box-shadow: 0 0 0 0 var(--accent-glow); }
90165
50% { box-shadow: 0 0 0 10px rgba(34,197,94,0); }
91166
}
92167
.hero h1 {
@@ -106,17 +181,20 @@
106181
display: inline-flex; align-items: center; gap:0.5rem;
107182
padding: 0.75rem 1.75rem; border-radius: 100px; font-weight: 600;
108183
font-size: 0.95rem; text-decoration: none;
109-
transition: transform 0.2s var(--spring), box-shadow 0.2s;
184+
transition: transform 0.2s var(--spring), box-shadow 0.2s var(--spring);
110185
}
111186
.btn-primary {
112-
background: var(--accent); color: #0F172A;
113-
box-shadow: 0 0 20px rgba(34,197,94,0.25);
187+
background: var(--accent); color: #fff;
188+
box-shadow: 0 0 20px var(--accent-glow);
114189
}
115-
.btn-primary:hover { transform: scale(1.05); box-shadow: 0 0 35px rgba(34,197,94,0.4); }
190+
[data-theme="light"] .btn-primary { color: #fff; }
191+
[data-theme="dark"] .btn-primary { color: #0F172A; }
192+
.btn-primary:hover { transform: scale(1.05); box-shadow: 0 0 35px var(--accent-glow); }
116193
.btn-ghost {
117194
border: 1px solid var(--border); color: var(--text);
195+
background: transparent;
118196
}
119-
.btn-ghost:hover { transform: scale(1.05); border-color: var(--text-dim); }
197+
.btn-ghost:hover { transform: scale(1.05); border-color: var(--accent); }
120198

121199
/* ── Stats grid ──────────────────────── */
122200
.stats-grid {
@@ -127,12 +205,13 @@
127205
background: var(--bg-card); border: 1px solid var(--border);
128206
border-radius: var(--radius); padding: 1.5rem; text-align: center;
129207
cursor: default;
130-
transition: transform 0.3s var(--spring), border-color 0.3s, box-shadow 0.3s;
208+
box-shadow: var(--shadow-sm);
209+
transition: transform 0.3s var(--spring), border-color 0.3s, box-shadow 0.3s var(--spring);
131210
}
132211
.stat-card:hover {
133212
transform: translateY(-4px) scale(1.03);
134213
border-color: var(--accent);
135-
box-shadow: 0 8px 30px rgba(34,197,94,0.1);
214+
box-shadow: var(--shadow-hover);
136215
}
137216
.stat-card .number {
138217
font-size: 2rem; font-weight: 700; font-variant-numeric: tabular-nums;
@@ -149,21 +228,23 @@
149228
.step {
150229
background: var(--bg-card); border: 1px solid var(--border);
151230
border-radius: var(--radius); padding: 2rem; position: relative; overflow: hidden;
152-
transition: transform 0.3s var(--spring), border-color 0.3s;
231+
box-shadow: var(--shadow-sm);
232+
transition: transform 0.3s var(--spring), border-color 0.3s, box-shadow 0.3s var(--spring);
153233
}
154-
.step:hover { transform: translateY(-3px); border-color: var(--accent); }
234+
.step:hover { transform: translateY(-3px); border-color: var(--accent); box-shadow: var(--shadow-hover); }
155235
.step .step-num {
156-
font-size: 3.5rem; font-weight: 800; color: rgba(34,197,94,0.08);
236+
font-size: 3.5rem; font-weight: 800; color: var(--accent-glow);
157237
position: absolute; top: -8px; right: 16px; line-height: 1;
238+
opacity: 0.35;
158239
}
159240
.step code {
160-
display: block; background: #0F172A; padding: 0.75rem 1rem;
241+
display: block; background: var(--code-bg); padding: 0.75rem 1rem;
161242
border-radius: 8px; margin-top: 1rem; font-size: 0.85rem;
162-
color: var(--accent); border: 1px solid var(--border);
163-
font-family: 'SF Mono', 'Fira Code', monospace;
243+
color: var(--accent); border: 1px solid var(--code-border);
244+
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
164245
transition: box-shadow 0.3s;
165246
}
166-
.step:hover code { box-shadow: 0 0 15px rgba(34,197,94,0.1); }
247+
.step:hover code { box-shadow: 0 0 15px var(--accent-glow); }
167248
.step h3 { font-size: 1.15rem; margin-bottom: 0.5rem; }
168249

169250
/* ── Comparison ──────────────────────── */
@@ -172,7 +253,10 @@
172253
.comp-col {
173254
background: var(--bg-card); border: 1px solid var(--border);
174255
border-radius: var(--radius); padding: 2rem;
256+
box-shadow: var(--shadow-sm);
257+
transition: box-shadow 0.3s var(--spring);
175258
}
259+
.comp-col:hover { box-shadow: var(--shadow-md); }
176260
.comp-col.basic { border-top: 3px solid var(--red); }
177261
.comp-col.pro { border-top: 3px solid var(--accent); }
178262
.comp-col h3 { font-size:1.1rem; margin-bottom:1rem; }
@@ -189,16 +273,22 @@
189273
.arch-block {
190274
background: var(--bg-card); border: 1px solid var(--border);
191275
border-radius: var(--radius); padding: 1.25rem 1.5rem; text-align:center;
192-
transition: transform 0.3s var(--spring), box-shadow 0.3s;
276+
box-shadow: var(--shadow-sm);
277+
transition: transform 0.3s var(--spring), box-shadow 0.3s var(--spring);
193278
min-width: 110px;
194279
}
195-
.arch-block:hover { transform: scale(1.08); box-shadow: 0 4px 25px rgba(34,197,94,0.15); }
280+
.arch-block:hover { transform: scale(1.08); box-shadow: var(--shadow-hover); }
196281
.arch-block .icon { font-size:1.8rem; margin-bottom:0.25rem; }
197282
.arch-block .name { font-weight:600; font-size:0.9rem; }
198283
.arch-arrow { color:var(--accent); font-size:1.5rem; font-weight:300; }
199284

200285
/* ── CTA ─────────────────────────────── */
201-
.cta-section { text-align:center; background: radial-gradient(ellipse at center, rgba(34,197,94,0.06) 0%, transparent 70%); border-radius: var(--radius); padding:4rem 2rem; }
286+
.cta-section {
287+
text-align:center;
288+
background: radial-gradient(ellipse at center, var(--glow-cta) 0%, transparent 70%);
289+
border-radius: var(--radius); padding:4rem 2rem;
290+
border: 1px solid var(--border);
291+
}
202292
.cta-section h2 { font-size:2rem; margin-bottom:0.75rem; }
203293
.cta-section p { color:var(--text-dim); margin-bottom:2rem; font-size:1.1rem; }
204294

@@ -230,17 +320,24 @@
230320
.hero { padding-top:7rem; }
231321
.arch { flex-direction:column; }
232322
.arch-arrow { transform:rotate(90deg); }
323+
nav { padding: 0 1rem; }
324+
nav a { margin-left: 0.75rem; font-size: 0.8rem; }
233325
}
234326
</style>
235327
</head>
236328
<body>
237329

238330
<nav>
239331
<div class="logo">⚡ BenchmarkFlow</div>
240-
<div>
332+
<div class="nav-links">
241333
<a href="#how">How</a>
242334
<a href="#compare">Compare</a>
243335
<a href="https://github.com/SecureBananaLabs/bug-bounty/pull/68" target="_blank">PR #68 ↗</a>
336+
<!-- Theme toggle -->
337+
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
338+
<span class="toggle-option" data-theme-val="light" id="lightOpt">☀️</span>
339+
<span class="toggle-option" data-theme-val="dark" id="darkOpt">🌙</span>
340+
</button>
244341
</div>
245342
</nav>
246343

@@ -284,7 +381,7 @@ <h3>Run the benchmark</h3>
284381
<div class="step">
285382
<div class="step-num">03</div>
286383
<h3>View the dashboard</h3>
287-
<p style="color:var(--text-dim);font-size:0.9rem;">Dark-themed HTML report with color-coded metrics.</p>
384+
<p style="color:var(--text-dim);font-size:0.9rem;">Interactive HTML report with color-coded metrics. Light & dark mode.</p>
288385
<code>make bench-report</code>
289386
</div>
290387
</div>
@@ -344,7 +441,7 @@ <h2>Architecture</h2>
344441
<section id="compare">
345442
<div class="container">
346443
<div class="section-header reveal">
347-
<h2>Why this over <code style="color:var(--red);background:rgba(239,68,68,0.1);padding:0.15rem 0.5rem;border-radius:4px;">autocannon</code></h2>
444+
<h2>Why this over <code style="color:var(--red);background:rgba(239,68,68,0.08);padding:0.15rem 0.5rem;border-radius:4px;">autocannon</code></h2>
348445
<p>The bounty asked for a CSV. Here's why this delivers more.</p>
349446
</div>
350447
<div class="comparison stagger" id="comparison">
@@ -368,7 +465,7 @@ <h3 style="color:var(--accent);">This PR</h3>
368465
<li>Auto on every push & PR</li>
369466
<li>GitHub Actions CI</li>
370467
<li>PR comment with diff</li>
371-
<li>Dark-themed dashboard</li>
468+
<li>Light & dark dashboard</li>
372469
<li>Docker Compose reproducible</li>
373470
</ul>
374471
</div>
@@ -381,7 +478,7 @@ <h3 style="color:var(--accent);">This PR</h3>
381478
<div class="container">
382479
<div class="cta-section reveal">
383480
<h2>Ready to benchmark your API?</h2>
384-
<p>Fork, run <code style="background:rgba(34,197,94,0.1);padding:0.2rem 0.6rem;border-radius:4px;">make bench</code>, and see the dashboard in 30 seconds.</p>
481+
<p>Fork, run <code style="background:rgba(34,197,94,0.08);padding:0.2rem 0.6rem;border-radius:4px;color:var(--accent);">make bench</code>, and see the dashboard in 30 seconds.</p>
385482
<div style="display:flex;gap:1rem;justify-content:center;flex-wrap:wrap;">
386483
<a href="https://github.com/SecureBananaLabs/bug-bounty/pull/68" target="_blank" class="btn btn-primary">View PR #68 on GitHub</a>
387484
<a href="https://github.com/AtlasNexusOps/bug-bounty" target="_blank" class="btn btn-ghost">Fork the repo</a>
@@ -396,6 +493,31 @@ <h2>Ready to benchmark your API?</h2>
396493
</footer>
397494

398495
<script>
496+
// ── Theme toggle ─────────────────────────
497+
(function() {
498+
const html = document.documentElement;
499+
const lightOpt = document.getElementById('lightOpt');
500+
const darkOpt = document.getElementById('darkOpt');
501+
502+
// Load saved preference (default: light)
503+
const saved = localStorage.getItem('benchmarkflow-theme') || 'light';
504+
html.setAttribute('data-theme', saved);
505+
updateToggle(saved);
506+
507+
document.getElementById('themeToggle').addEventListener('click', () => {
508+
const current = html.getAttribute('data-theme');
509+
const next = current === 'dark' ? 'light' : 'dark';
510+
html.setAttribute('data-theme', next);
511+
localStorage.setItem('benchmarkflow-theme', next);
512+
updateToggle(next);
513+
});
514+
515+
function updateToggle(theme) {
516+
lightOpt.classList.toggle('active', theme === 'light');
517+
darkOpt.classList.toggle('active', theme === 'dark');
518+
}
519+
})();
520+
399521
// ── Scroll-triggered reveals ─────────────
400522
const observer = new IntersectionObserver((entries) => {
401523
entries.forEach(entry => {
@@ -426,7 +548,6 @@ <h2>Ready to benchmark your API?</h2>
426548

427549
function tick(now) {
428550
const progress = Math.min((now - start) / duration, 1);
429-
// spring-like easing
430551
const eased = 1 - Math.pow(1 - progress, 4);
431552
current = Math.round(target * eased);
432553
el.textContent = isCurrency ? current + '$' : current + suffix;
@@ -436,7 +557,6 @@ <h2>Ready to benchmark your API?</h2>
436557
});
437558
}
438559

439-
// Trigger counters when hero stats become visible
440560
const heroStats = document.getElementById('hero-stats');
441561
const heroObs = new IntersectionObserver((entries) => {
442562
if (entries[0].isIntersecting) {

0 commit comments

Comments
 (0)