Skip to content

Commit 8aae18c

Browse files
dotsystemsdevsitxashancodeclaude
committed
feat: dark mode support (candlelit morgue theme) — merge #26
Integrates dark mode from @itxashancode's PR #26 (closes #13). Resolved conflicts with recent main: - next.config.mjs: kept removal of /faq redirect (real /faq page exists), excluded plausible additions (already wired separately) - src/app/layout.tsx: kept tracker script + added theme-init script - src/app/page.tsx: kept master's SuspenseFallback + dashboard - src/components/SiteFooter.tsx: kept 'Open source' GitHub link, excluded buymeacoffee CTA, added ThemeToggle Co-authored-by: itxashancode <itxashancode@users.noreply.github.com> Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2 parents 02908f2 + 0f37488 commit 8aae18c

15 files changed

Lines changed: 241 additions & 156 deletions

next.config.mjs

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,38 @@
1-
/** @type {import('next').NextConfig} */
2-
const nextConfig = {
3-
async redirects() {
4-
return [
5-
{ source: '/pricing', destination: '/', permanent: true },
6-
{ source: '/privacy', destination: '/about', permanent: true },
7-
{ source: '/terms', destination: '/about', permanent: true },
8-
]
9-
},
10-
async headers() {
11-
return [
12-
{
13-
source: '/(.*)',
14-
headers: [
15-
{ key: 'X-Content-Type-Options', value: 'nosniff' },
16-
{ key: 'X-Frame-Options', value: 'DENY' },
17-
{ key: 'X-XSS-Protection', value: '1; mode=block' },
18-
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
19-
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
20-
{
21-
key: 'Content-Security-Policy',
22-
value: [
23-
"default-src 'self'",
24-
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://va.vercel-scripts.com",
25-
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
26-
"font-src 'self' https://fonts.gstatic.com",
27-
"img-src 'self' data: blob: https://img.shields.io https://avatars.githubusercontent.com",
28-
"connect-src 'self' https://vitals.vercel-insights.com https://va.vercel-scripts.com",
29-
"frame-ancestors 'none'",
30-
].join('; '),
31-
},
32-
],
33-
},
34-
]
35-
},
36-
}
37-
38-
export default nextConfig
1+
/** @type {import('next').NextConfig} */
2+
const nextConfig = {
3+
async redirects() {
4+
return [
5+
{ source: '/pricing', destination: '/', permanent: true },
6+
{ source: '/privacy', destination: '/about', permanent: true },
7+
{ source: '/terms', destination: '/about', permanent: true },
8+
]
9+
},
10+
async headers() {
11+
return [
12+
{
13+
source: '/(.*)',
14+
headers: [
15+
{ key: 'X-Content-Type-Options', value: 'nosniff' },
16+
{ key: 'X-Frame-Options', value: 'DENY' },
17+
{ key: 'X-XSS-Protection', value: '1; mode=block' },
18+
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
19+
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
20+
{
21+
key: 'Content-Security-Policy',
22+
value: [
23+
"default-src 'self'",
24+
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://va.vercel-scripts.com",
25+
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
26+
"font-src 'self' https://fonts.gstatic.com",
27+
"img-src 'self' data: blob: https://img.shields.io https://avatars.githubusercontent.com",
28+
"connect-src 'self' https://vitals.vercel-insights.com https://va.vercel-scripts.com",
29+
"frame-ancestors 'none'",
30+
].join('; '),
31+
},
32+
],
33+
},
34+
]
35+
},
36+
}
37+
38+
export default nextConfig

src/app/globals.css

Lines changed: 52 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,46 @@
99
/* Backgrounds */
1010
--c-bg: #FAF6EF; /* page background */
1111
--c-surface: #EDE8E1; /* cards */
12+
--c-surface-raised: #F5F0E8; /* cards lifted above surface */
1213

1314
/* Text */
1415
--c-ink: #160A06; /* primary text */
1516
--c-ink-2: #4a4440; /* body text on cards */
16-
--c-muted: #9a9288; /* metadata, labels */
17+
--c-muted: #8a8a8a; /* metadata, labels */
1718
--c-faint: #b0aca8; /* very quiet text */
1819

1920
/* Borders */
2021
--c-border: #1a1a1a; /* all solid borders — cards, inputs, buttons */
2122
--c-border-light: #cec6bb; /* dividers, ghost buttons */
2223

2324
/* Accent */
24-
--c-red: #8B0000; /* dead / error */
25+
--c-red: #8B1A1A; /* dead / error */
2526
--c-green: #2d7a3c; /* alive */
2627
--c-amber: #b45309; /* struggling */
28+
--c-stamp-red: #8B1A1A; /* semantic alias for certificate stamp */
29+
}
2730

28-
/* Extended */
29-
--c-surface-raised: #F5F0E8; /* cards lifted above surface */
30-
--c-stamp-red: #8B0000; /* semantic alias for certificate stamp */
31+
[data-theme="dark"] {
32+
/* Backgrounds */
33+
--c-bg: #0F0C09; /* page background */
34+
--c-surface: #1A1510; /* cards */
35+
--c-surface-raised: #241D16; /* cards lifted above surface */
36+
37+
/* Text */
38+
--c-ink: #EDE8E1; /* primary text */
39+
--c-ink-2: #9a9288; /* body text on cards */
40+
--c-muted: #6a6058; /* metadata, labels */
41+
--c-faint: #4a4038; /* very quiet text */
42+
43+
/* Borders */
44+
--c-border: #3a3028; /* all solid borders — cards, inputs, buttons */
45+
--c-border-light: #2a241e; /* dividers, ghost buttons */
46+
47+
/* Accent */
48+
--c-red: #C0392B; /* dead / error */
49+
--c-green: #27ae60; /* alive */
50+
--c-amber: #d35400; /* struggling */
51+
--c-stamp-red: #C0392B;
3152
}
3253

3354
/* ── Unified record / institutional card system ───────────────────── */
@@ -106,7 +127,7 @@
106127
.page-shell-main {
107128
min-height: 100vh;
108129
min-height: 100dvh;
109-
background: #FAF6EF;
130+
background: var(--c-bg);
110131
display: flex;
111132
flex-direction: column;
112133
align-items: center;
@@ -164,7 +185,7 @@
164185
display: inline-block;
165186
width: 8px;
166187
height: 8px;
167-
background: #8b0000;
188+
background: var(--c-red);
168189
border-radius: 50%;
169190
animation: loading-bounce 1s ease-in-out infinite;
170191
}
@@ -180,14 +201,14 @@
180201
font-size: clamp(3.2rem, 9vw, 5rem);
181202
line-height: 0.95;
182203
margin: 6px 0 12px 0;
183-
color: #160a06;
204+
color: var(--c-ink);
184205
}
185206

186207
.page-hero-sub {
187208
font-family: var(--font-courier), system-ui, sans-serif;
188209
font-size: clamp(13px, 3vw, 15px);
189210
font-weight: 400;
190-
color: #5f5f5f;
211+
color: var(--c-ink-2);
191212
line-height: 1.35;
192213
margin: 0 auto;
193214
max-width: 520px;
@@ -222,7 +243,7 @@
222243
.page-hero-micro {
223244
font-family: var(--font-courier), system-ui, sans-serif;
224245
font-size: 11px;
225-
color: #8a8a8a;
246+
color: var(--c-muted);
226247
line-height: 1.5;
227248
margin: 4px auto 0;
228249
letter-spacing: 0.03em;
@@ -347,7 +368,7 @@ button {
347368
}
348369
@media (hover: hover) and (pointer: fine) {
349370
.cert-btn-primary:hover {
350-
background: #2a2a2a !important;
371+
opacity: 0.9 !important;
351372
}
352373
}
353374
.cert-btn-primary:active {
@@ -360,7 +381,7 @@ button {
360381
}
361382
@media (hover: hover) and (pointer: fine) {
362383
.cert-btn-secondary:hover {
363-
background: #EDE8E1 !important;
384+
background: var(--c-surface-raised) !important;
364385
}
365386
}
366387
.cert-btn-secondary:active {
@@ -387,7 +408,7 @@ button {
387408
}
388409
.readme-badge-caption {
389410
font-size: 10px;
390-
color: #b0a89e;
411+
color: var(--c-muted);
391412
letter-spacing: 0.04em;
392413
margin: 10px 0 0;
393414
}
@@ -462,7 +483,7 @@ button {
462483

463484

464485
a.subpage-inline-mail {
465-
color: #1a1a1a;
486+
color: var(--c-ink);
466487
text-decoration: underline;
467488
text-underline-offset: 3px;
468489
word-break: break-all;
@@ -483,10 +504,11 @@ a.subpage-inline-mail {
483504
right: 0;
484505
bottom: 0;
485506
z-index: 40;
486-
background: rgba(250, 246, 239, 0.92);
507+
background: var(--c-bg);
508+
opacity: 0.92;
487509
backdrop-filter: blur(6px);
488510
-webkit-backdrop-filter: blur(6px);
489-
border-top: 1px solid #e2dcd1;
511+
border-top: 1px solid var(--c-border-light);
490512
}
491513

492514
.site-footer--compact {
@@ -504,25 +526,26 @@ a.subpage-inline-mail {
504526
}
505527

506528
.input-button-wrapper:focus-within {
507-
box-shadow: 0 0 0 3px rgba(26,26,26,0.12);
529+
box-shadow: 0 0 0 3px var(--c-border-light);
530+
opacity: 0.8;
508531
}
509532

510533
.input-submit-button:hover {
511-
box-shadow: 0 0 0 1px rgba(0,0,0,0.22);
534+
opacity: 0.9;
512535
}
513536

514537
.input-submit-button:active {
515538
opacity: 0.9;
516539
}
517540

518541
.input-submit-button--dark {
519-
background: #1a1a1a !important;
520-
color: #fff !important;
542+
background: var(--c-ink) !important;
543+
color: var(--c-bg) !important;
521544
}
522545

523546
@media (hover: hover) and (pointer: fine) {
524547
.input-submit-button--dark:hover {
525-
background: #2a2a2a !important;
548+
opacity: 0.9 !important;
526549
}
527550
}
528551

@@ -531,7 +554,7 @@ a.subpage-inline-mail {
531554
}
532555

533556
.recent-card:focus-visible {
534-
outline: 2px solid #1a1a1a;
557+
outline: 2px solid var(--c-border);
535558
outline-offset: 4px;
536559
}
537560

@@ -589,8 +612,8 @@ a.subpage-inline-mail {
589612
@apply antialiased;
590613
font-family: var(--font-courier), system-ui, sans-serif;
591614
font-size: 14px;
592-
color: #160A06;
593-
background-color: #FAF6EF;
615+
color: var(--c-ink);
616+
background-color: var(--c-bg);
594617
overflow-x: hidden;
595618
}
596619
html { overflow-x: hidden; }
@@ -600,11 +623,11 @@ a.subpage-inline-mail {
600623
cursor: default !important;
601624
}
602625
* { box-sizing: border-box; }
603-
input::placeholder { color: #aaa; opacity: 1; }
626+
input::placeholder { color: var(--c-faint); opacity: 0.7; }
604627
input:-webkit-autofill,
605628
input:-webkit-autofill:focus {
606-
-webkit-text-fill-color: #160A06;
607-
-webkit-box-shadow: 0 0 0 1000px #FAF6EF inset;
629+
-webkit-text-fill-color: var(--c-ink);
630+
-webkit-box-shadow: 0 0 0 1000px var(--c-bg) inset;
608631
transition: background-color 5000s ease-in-out 0s;
609632
}
610633
}
@@ -640,7 +663,7 @@ a.subpage-inline-mail {
640663
text-align: center;
641664
font-family: var(--font-courier), system-ui, sans-serif;
642665
font-size: 11px;
643-
color: #7a7a7a;
666+
color: var(--c-muted);
644667
letter-spacing: 0.02em;
645668
}
646669

@@ -679,7 +702,7 @@ a.subpage-inline-mail {
679702
.input-submit-button {
680703
width: 100%;
681704
border-left: none !important;
682-
border-top: 2px solid #1a1a1a !important;
705+
border-top: 2px solid var(--c-border) !important;
683706
margin-top: 0 !important;
684707
justify-content: center !important;
685708
text-align: center !important;

src/app/layout.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,15 @@ const jsonLd = {
9494

9595
export default function RootLayout({ children }: { children: React.ReactNode }) {
9696
return (
97-
<html lang="en">
97+
<html lang="en" suppressHydrationWarning>
9898
<head>
9999
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
100100
<script defer src="https://tracker-mauve-sigma.vercel.app/t.js" data-project="commitmentissues" />
101+
<script
102+
dangerouslySetInnerHTML={{
103+
__html: `(function(){try{var t=localStorage.getItem('theme');if(!t&&window.matchMedia('(prefers-color-scheme:dark)').matches)t='dark';if(!t)t='light';document.documentElement.setAttribute('data-theme',t)}catch(e){}})();`,
104+
}}
105+
/>
101106
</head>
102107
<body className={`${spaceGrotesk.variable} ${unifraktur.variable} ${lora.variable} antialiased`}>
103108
<ScannerBanner />

0 commit comments

Comments
 (0)