diff --git a/web/bun.lock b/web/bun.lock index aadebcc1..c16db1d9 100644 --- a/web/bun.lock +++ b/web/bun.lock @@ -11,7 +11,9 @@ "@types/mdx": "^2.0.13", "ethers": "^6.16.0", "lenis": "^1.3.0", + "lucide-react": "^1.23.0", "next": "16.1.1", + "next-themes": "^0.4.6", "posthog-js": "^1.382.0", "react": "19.2.3", "react-dom": "19.2.3", @@ -814,6 +816,8 @@ "lru-cache": ["lru-cache@11.5.1", "", {}, "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A=="], + "lucide-react": ["lucide-react@1.23.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-38BpJcD0JhFosxHApP/BYsBetLpQFRoTRzEzstM/XCc3jsAG7wqaY1lgVwxiUe3xqYE+lNxo2PkCmYwXWrwwIw=="], + "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], @@ -916,6 +920,8 @@ "next": ["next@16.1.1", "", { "dependencies": { "@next/env": "16.1.1", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.1", "@next/swc-darwin-x64": "16.1.1", "@next/swc-linux-arm64-gnu": "16.1.1", "@next/swc-linux-arm64-musl": "16.1.1", "@next/swc-linux-x64-gnu": "16.1.1", "@next/swc-linux-x64-musl": "16.1.1", "@next/swc-win32-arm64-msvc": "16.1.1", "@next/swc-win32-x64-msvc": "16.1.1", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-QI+T7xrxt1pF6SQ/JYFz95ro/mg/1Znk5vBebsWwbpejj1T0A23hO7GYEaVac9QUOT2BIMiuzm0L99ooq7k0/w=="], + "next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="], + "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], diff --git a/web/package.json b/web/package.json index cb142942..9ffc5231 100644 --- a/web/package.json +++ b/web/package.json @@ -21,7 +21,9 @@ "@types/mdx": "^2.0.13", "ethers": "^6.16.0", "lenis": "^1.3.0", + "lucide-react": "^1.23.0", "next": "16.1.1", + "next-themes": "^0.4.6", "posthog-js": "^1.382.0", "react": "19.2.3", "react-dom": "19.2.3" diff --git a/web/src/app/globals.css b/web/src/app/globals.css index 464b4d1f..7e5ecb43 100644 --- a/web/src/app/globals.css +++ b/web/src/app/globals.css @@ -16,6 +16,27 @@ --alert-soft: #fbe4e4; } +.dark { + --paper: #0f1115; + --paper-deep: #1a1d23; + + --ink: #f5f5f5; + --ink-soft: #d0d0d0; + --ink-faint: #8d8d8d; + + --rule: #f5f5f5; + + --accent: #5b7fff; + --accent-press: #3d63ff; + --accent-soft: #1c2648; + + --ok: #37c871; + --ok-soft: #163222; + + --alert: #ff6b6b; + --alert-soft: #3a1717; +} + @theme inline { --color-paper: var(--paper); --color-paper-deep: var(--paper-deep); diff --git a/web/src/app/layout.tsx b/web/src/app/layout.tsx index cc466260..2e5d9ac2 100644 --- a/web/src/app/layout.tsx +++ b/web/src/app/layout.tsx @@ -3,6 +3,7 @@ import localFont from "next/font/local"; import "./globals.css"; import { SmoothScroll } from "@/components/smooth-scroll"; import { ColdStartWarmup } from "@/components/cold-start-warmup"; +import { ThemeProvider } from "@/components/theme-provider"; // Satoshi Variable (Fontshare, ITF) — humanist grotesk for body / UI. const satoshi = localFont({ @@ -40,13 +41,15 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + - - - {children} + + + + {children} + ); diff --git a/web/src/components/nav.tsx b/web/src/components/nav.tsx index 9a57f2e8..02045210 100644 --- a/web/src/components/nav.tsx +++ b/web/src/components/nav.tsx @@ -1,4 +1,5 @@ import Link from "next/link"; +import { ThemeToggle } from "./theme-toggle"; import { WalletWidget } from "./wallet-widget"; const CHAIN_NAME_LOWER = ( @@ -8,14 +9,14 @@ const CHAIN_NAME_LOWER = ( export function Nav() { return (
-
+
MicroAI Paygate x402 · {CHAIN_NAME_LOWER} -
diff --git a/web/src/components/theme-provider.tsx b/web/src/components/theme-provider.tsx new file mode 100644 index 00000000..79142e0d --- /dev/null +++ b/web/src/components/theme-provider.tsx @@ -0,0 +1,17 @@ +"use client"; + +import { ThemeProvider as NextThemesProvider } from "next-themes"; +import type { ReactNode } from "react"; + +export function ThemeProvider({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} diff --git a/web/src/components/theme-toggle.tsx b/web/src/components/theme-toggle.tsx new file mode 100644 index 00000000..94305e4d --- /dev/null +++ b/web/src/components/theme-toggle.tsx @@ -0,0 +1,39 @@ +"use client"; + +import { Moon, Sun } from "lucide-react"; +import { useTheme } from "next-themes"; +import { useEffect, useState } from "react"; + +export function ThemeToggle() { + const { theme, setTheme } = useTheme(); + const [mounted, setMounted] = useState(false); + const isLightTheme = theme === "light"; + + useEffect(() => { + const frame = requestAnimationFrame(() => setMounted(true)); + + return () => cancelAnimationFrame(frame); + }, []); + + if (!mounted) return null; + + return ( + + ); +}