diff --git a/src/app/globals.css b/src/app/globals.css index 7386e3f..6f0eec2 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,5 +1,9 @@ @import "tailwindcss"; +/* ── Theme system: light is default, .dark activates dark mode ── */ +/* Surface colors are remapped so bg-surface-950 = page background, */ +/* text-surface-50 = main text — in BOTH themes. The scale flips. */ + @theme inline { --color-brand-50: #eef6ff; --color-brand-100: #d9eaff; @@ -13,22 +17,55 @@ --color-brand-900: #18378f; --color-brand-950: #142357; - --color-surface-50: #f8fafc; - --color-surface-100: #f1f5f9; - --color-surface-200: #e2e8f0; - --color-surface-300: #cbd5e1; - --color-surface-400: #94a3b8; - --color-surface-500: #64748b; - --color-surface-600: #475569; - --color-surface-700: #334155; - --color-surface-800: #1e293b; - --color-surface-900: #0f172a; - --color-surface-950: #020617; + /* Surface scale — mapped to semantic vars that flip with theme */ + --color-surface-50: var(--s-50); + --color-surface-100: var(--s-100); + --color-surface-200: var(--s-200); + --color-surface-300: var(--s-300); + --color-surface-400: var(--s-400); + --color-surface-500: var(--s-500); + --color-surface-600: var(--s-600); + --color-surface-700: var(--s-700); + --color-surface-800: var(--s-800); + --color-surface-900: var(--s-900); + --color-surface-950: var(--s-950); --font-sans: "Inter", system-ui, -apple-system, sans-serif; --font-mono: "JetBrains Mono", "Fira Code", monospace; } +/* ── Light theme (default) ── */ +:root { + --s-50: #020617; /* main text — near black */ + --s-100: #0f172a; /* heading text */ + --s-200: #1e293b; /* strong text */ + --s-300: #475569; /* secondary text */ + --s-400: #64748b; /* muted text */ + --s-500: #94a3b8; /* very muted */ + --s-600: #cbd5e1; /* faint text */ + --s-700: #e2e8f0; /* borders */ + --s-800: #f1f5f9; /* card backgrounds */ + --s-900: #f8fafc; /* secondary page bg */ + --s-950: #ffffff; /* page background — white */ + color-scheme: light; +} + +/* ── Dark theme ── */ +.dark { + --s-50: #f8fafc; /* main text — near white */ + --s-100: #f1f5f9; /* heading text */ + --s-200: #e2e8f0; /* strong text */ + --s-300: #cbd5e1; /* secondary text */ + --s-400: #94a3b8; /* muted text */ + --s-500: #64748b; /* very muted */ + --s-600: #475569; /* faint text */ + --s-700: #334155; /* borders */ + --s-800: #1e293b; /* card backgrounds */ + --s-900: #0f172a; /* secondary page bg */ + --s-950: #020617; /* page background — near black */ + color-scheme: dark; +} + html { scroll-behavior: smooth; -webkit-font-smoothing: antialiased; @@ -39,6 +76,7 @@ body { background-color: var(--color-surface-950); color: var(--color-surface-100); font-family: var(--font-sans); + transition: background-color 0.3s ease, color 0.3s ease; } ::selection { @@ -54,9 +92,18 @@ body { } .glass-card { - background: rgba(30, 41, 59, 0.5); + background: var(--glass-bg); backdrop-filter: blur(16px); - border: 1px solid rgba(148, 163, 184, 0.1); + border: 1px solid var(--glass-border); +} + +:root { + --glass-bg: rgba(241, 245, 249, 0.5); + --glass-border: rgba(0, 0, 0, 0.08); +} +.dark { + --glass-bg: rgba(30, 41, 59, 0.5); + --glass-border: rgba(148, 163, 184, 0.1); } .glow-border { @@ -71,3 +118,18 @@ body { transparent ); } + +/* Theme toggle button */ +.theme-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + border-radius: 8px; + transition: background-color 0.2s, color 0.2s; + cursor: pointer; +} +.theme-toggle:hover { + background-color: var(--color-surface-800); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 681526b..0b7803c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,7 +4,7 @@ import "./globals.css"; export const metadata: Metadata = { title: "DashCaddy - Self-Hosting Made Beautiful", description: - "Deploy 50+ Docker apps with one click. Automatic SSL, DNS, and reverse proxy configuration. The all-in-one self-hosting dashboard.", + "Deploy 76+ Docker apps with one click. AI-powered self-hosting with automatic SSL, DNS, reverse proxy, security center, and fleet management.", keywords: [ "self-hosting", "docker", @@ -14,11 +14,12 @@ export const metadata: Metadata = { "ssl", "dns", "homelab", + "AI self-hosting", ], openGraph: { title: "DashCaddy - Self-Hosting Made Beautiful", description: - "Deploy 50+ Docker apps with one click. Automatic SSL, DNS, and reverse proxy configuration.", + "Deploy 76+ Docker apps with one click. AI-powered self-hosting with automatic SSL, DNS, and reverse proxy.", url: "https://dashcaddy.net", siteName: "DashCaddy", type: "website", @@ -27,17 +28,32 @@ export const metadata: Metadata = { card: "summary_large_image", title: "DashCaddy - Self-Hosting Made Beautiful", description: - "Deploy 50+ Docker apps with one click. Automatic SSL, DNS, and reverse proxy.", + "Deploy 76+ Docker apps with one click. AI-powered self-hosting.", }, }; +// Inline script to set theme class BEFORE first paint — prevents FOUC +const themeScript = ` +(function() { + try { + var stored = localStorage.getItem('theme'); + var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + var theme = stored || (prefersDark ? 'dark' : 'light'); + if (theme === 'dark') document.documentElement.classList.add('dark'); + } catch(e) {} +})(); +`; + export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( - + +
+ + {children} diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 65c8cb0..69b8368 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -1,8 +1,19 @@ +'use client'; + import Link from 'next/link'; import Image from 'next/image'; +import { useState, useEffect } from 'react'; export default function Footer() { const currentYear = new Date().getFullYear(); + const [theme, setTheme] = useState<'light' | 'dark'>('dark'); + + useEffect(() => { + const stored = localStorage.getItem('theme') as 'light' | 'dark' | null; + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + setTheme(stored || (prefersDark ? 'dark' : 'light')); + document.documentElement.classList.toggle('dark', (stored || (prefersDark ? 'dark' : 'light')) === 'dark'); + }, []); const footerSections = [ { @@ -52,7 +63,13 @@ export default function Footer() { {/* Brand Section */}Self-hosted Docker dashboard with automatic SSL, DNS, and reverse proxy. Making self-hosting beautiful and effortless. diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 7e729c6..69e0ead 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -2,10 +2,27 @@ import Link from 'next/link'; import Image from 'next/image'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; export default function Navbar() { const [isOpen, setIsOpen] = useState(false); + const [theme, setTheme] = useState<'light' | 'dark'>('dark'); + + // Initialize theme from localStorage or system preference + useEffect(() => { + const stored = localStorage.getItem('theme') as 'light' | 'dark' | null; + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + const initial = stored || (prefersDark ? 'dark' : 'light'); + setTheme(initial); + document.documentElement.classList.toggle('dark', initial === 'dark'); + }, []); + + const toggleTheme = () => { + const next = theme === 'dark' ? 'light' : 'dark'; + setTheme(next); + document.documentElement.classList.toggle('dark', next === 'dark'); + localStorage.setItem('theme', next); + }; const navLinks = [ { href: '/features', label: 'Features' }, @@ -20,7 +37,14 @@ export default function Navbar() {