From 84a6fe43761f9a39a338ceaaa09d86f17bc5fb75 Mon Sep 17 00:00:00 2001
From: "Hunter W."
Date: Fri, 27 Mar 2026 13:01:56 -0400
Subject: [PATCH] added dark/light mode and control
---
app/globals.css | 122 +++++++++++++++++++++++++------------
app/layout.tsx | 12 +++-
components/Footer.tsx | 2 +-
components/ThemeSwitch.tsx | 95 +++++++++++++++++++++++++++++
package-lock.json | 11 ++++
package.json | 3 +-
pages/_app.tsx | 10 +++
7 files changed, 212 insertions(+), 43 deletions(-)
create mode 100644 components/ThemeSwitch.tsx
create mode 100644 pages/_app.tsx
diff --git a/app/globals.css b/app/globals.css
index 778d944..94f27b1 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -1,52 +1,52 @@
:root {
/* Background */
- --bg: #101114;
- --bg-raised: #141518;
- --surface: #1a1b1f;
- --surface-hover: #212229;
- --surface-active: #282a33;
-
+ --bg: #ffffff;
+ --bg-raised: #f8f8fa;
+ --surface: #f1f2f4;
+ --surface-hover: #e8e9ed;
+ --surface-active: #dfe0e5;
+
/* Border */
- --border: #2c2d34;
- --border-strong: #3d3f48;
- --border-subtle: #232429;
-
+ --border: #d4d5db;
+ --border-strong: #b8bac2;
+ --border-subtle: #e4e5ea;
+
/* Foreground / text */
- --fg: #e4e5e9;
- --fg-secondary: #a0a3ad;
- --muted: #5f6270;
- --faint: #43454f;
-
+ --fg: #131419;
+ --fg-secondary: #4e5264;
+ --muted: #7e8290;
+ --faint: #b0b3bd;
+
/* Accent */
- --accent: #8a9dff;
- --accent-hover: #9dacff;
- --accent-strong: #7088ff;
- --accent-bg: rgba(138, 157, 255, 0.08);
- --accent-bg-hover: rgba(138, 157, 255, 0.14);
-
+ --accent: #5468e0;
+ --accent-hover: #4758cc;
+ --accent-strong: #3d4cb8;
+ --accent-bg: rgba(84, 104, 224, 0.07);
+ --accent-bg-hover: rgba(84, 104, 224, 0.13);
+
/* Semantic */
- --success: #5ec98f;
- --success-bg: rgba(94, 201, 143, 0.08);
- --warning: #e0a855;
- --warning-bg: rgba(224, 168, 85, 0.08);
- --error: #e06565;
- --error-bg: rgba(224, 101, 101, 0.08);
- --info: #60a5d6;
- --info-bg: rgba(96, 165, 214, 0.08);
-
+ --success: #1a8f54;
+ --success-bg: rgba(26, 143, 84, 0.07);
+ --warning: #b07818;
+ --warning-bg: rgba(176, 120, 24, 0.07);
+ --error: #c93c3c;
+ --error-bg: rgba(201, 60, 60, 0.07);
+ --info: #2e7db5;
+ --info-bg: rgba(46, 125, 181, 0.07);
+
/* Overlay / depth */
- --overlay: rgba(0, 0, 0, 0.55);
- --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.25);
- --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.30);
- --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.35);
-
+ --overlay: rgba(0, 0, 0, 0.2);
+ --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06);
+ --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.08);
+ --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.10);
+
/* Radii */
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 10px;
--radius-xl: 16px;
-
+
/* Spacing */
--space-1: 4px;
--space-2: 8px;
@@ -58,7 +58,7 @@
--space-10: 40px;
--space-12: 48px;
--space-16: 64px;
-
+
/* Typography */
--text-xs: 11px;
--text-sm: 13px;
@@ -71,7 +71,7 @@
--leading-tight: 1.2;
--leading-normal: 1.5;
--leading-relaxed: 1.7;
-
+
/* Transitions */
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
@@ -80,6 +80,50 @@
--duration-slow: 350ms;
}
+[data-theme="dark"] {
+
+ /* Background */
+ --bg: #101114;
+ --bg-raised: #141518;
+ --surface: #1a1b1f;
+ --surface-hover: #212229;
+ --surface-active: #282a33;
+
+ /* Border */
+ --border: #2c2d34;
+ --border-strong: #3d3f48;
+ --border-subtle: #232429;
+
+ /* Foreground / text */
+ --fg: #e4e5e9;
+ --fg-secondary: #a0a3ad;
+ --muted: #5f6270;
+ --faint: #43454f;
+
+ /* Accent */
+ --accent: #8a9dff;
+ --accent-hover: #9dacff;
+ --accent-strong: #7088ff;
+ --accent-bg: rgba(138, 157, 255, 0.08);
+ --accent-bg-hover: rgba(138, 157, 255, 0.14);
+
+ /* Semantic */
+ --success: #5ec98f;
+ --success-bg: rgba(94, 201, 143, 0.08);
+ --warning: #e0a855;
+ --warning-bg: rgba(224, 168, 85, 0.08);
+ --error: #e06565;
+ --error-bg: rgba(224, 101, 101, 0.08);
+ --info: #60a5d6;
+ --info-bg: rgba(96, 165, 214, 0.08);
+
+ /* Overlay / depth */
+ --overlay: rgba(0, 0, 0, 0.55);
+ --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.25);
+ --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.30);
+ --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.35);
+}
+
*,
*::before,
*::after {
@@ -104,4 +148,4 @@ body {
h1 {
font-size: 32px !important;
}
-}
+}
\ No newline at end of file
diff --git a/app/layout.tsx b/app/layout.tsx
index 8f2e084..6bc3fbd 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -2,6 +2,8 @@ import type { Metadata } from "next";
import { JetBrains_Mono, DM_Sans } from "next/font/google";
import { SITE } from "@/data/content";
import "./globals.css";
+import { ThemeProvider } from 'next-themes'
+import ThemeSwitch from "@/components/ThemeSwitch";
const mono = JetBrains_Mono({
subsets: ["latin"],
@@ -26,8 +28,14 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
-
- {children}
+
+
+
+
+
+ {children}
+
+
);
}
diff --git a/components/Footer.tsx b/components/Footer.tsx
index a8097a5..8cfcd67 100644
--- a/components/Footer.tsx
+++ b/components/Footer.tsx
@@ -24,7 +24,7 @@ export default function Footer() {
>
- (2026) - Hunter W.
+ (2026) - Hunter W. - Powered by Next.JS
diff --git a/components/ThemeSwitch.tsx b/components/ThemeSwitch.tsx
new file mode 100644
index 0000000..ca0e00b
--- /dev/null
+++ b/components/ThemeSwitch.tsx
@@ -0,0 +1,95 @@
+'use client'
+
+import { useState, useEffect } from 'react'
+import { useTheme } from 'next-themes'
+
+const themes = ['system', 'light', 'dark'] as const
+
+const ThemeSwitch = () => {
+ const [mounted, setMounted] = useState(false)
+ const { theme, setTheme } = useTheme()
+
+ useEffect(() => {
+ setMounted(true)
+ }, [])
+
+ if (!mounted) return null
+
+ const next = () => {
+ const i = themes.indexOf(theme as (typeof themes)[number])
+ setTheme(themes[(i + 1) % themes.length])
+ }
+
+ return (
+
+ )
+}
+
+export default ThemeSwitch
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index ea7b9be..d9d2801 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "0.1.0",
"dependencies": {
"next": "16.2.1",
+ "next-themes": "^0.4.6",
"react": "19.2.4",
"react-dom": "19.2.4"
},
@@ -5048,6 +5049,16 @@
}
}
},
+ "node_modules/next-themes": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
+ "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
+ }
+ },
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
diff --git a/package.json b/package.json
index 42a7060..cce12a5 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,8 @@
"dependencies": {
"next": "16.2.1",
"react": "19.2.4",
- "react-dom": "19.2.4"
+ "react-dom": "19.2.4",
+ "next-themes": "^0.4.6"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
diff --git a/pages/_app.tsx b/pages/_app.tsx
new file mode 100644
index 0000000..b591d28
--- /dev/null
+++ b/pages/_app.tsx
@@ -0,0 +1,10 @@
+import type { AppProps } from 'next/app'
+ import { ThemeProvider } from 'next-themes'
+
+export default function MyApp({ Component, pageProps }: AppProps) {
+ return (
+
+
+
+ )
+}
\ No newline at end of file