diff --git a/src/app/_fork/page.tsx b/src/app/_fork/page.tsx index a9d6e64..55454f6 100644 --- a/src/app/_fork/page.tsx +++ b/src/app/_fork/page.tsx @@ -591,7 +591,7 @@ vercel padding: '0.875rem 1.75rem', background: 'var(--accent-moss)', border: '1px solid var(--accent-forest)', - color: 'var(--text-primary)', + color: 'var(--text-on-accent)', textDecoration: 'none', fontSize: '1rem', transition: 'all 0.3s ease', diff --git a/src/app/_integrate/page.tsx b/src/app/_integrate/page.tsx index cb3ad3f..dfa7849 100644 --- a/src/app/_integrate/page.tsx +++ b/src/app/_integrate/page.tsx @@ -213,7 +213,7 @@ aturi.to is a community tool for the ATProto ecosystem. The service is free and background: copied ? 'var(--accent-moss)' : 'var(--bg-primary)', border: '1px solid', borderColor: copied ? 'var(--accent-forest)' : 'var(--border-medium)', - color: 'var(--text-primary)', + color: copied ? 'var(--text-on-accent)' : 'var(--text-primary)', fontSize: '0.9rem', cursor: 'pointer', transition: 'all 0.3s ease', diff --git a/src/app/create/page.tsx b/src/app/create/page.tsx index dfa1329..9922288 100644 --- a/src/app/create/page.tsx +++ b/src/app/create/page.tsx @@ -182,7 +182,7 @@ export default function CreatePage() { style={{ padding: '1rem 2rem', background: input.trim() ? 'var(--accent-moss)' : 'var(--bg-tertiary)', - color: input.trim() ? 'var(--text-primary)' : 'var(--text-tertiary)', + color: input.trim() ? 'var(--text-on-accent)' : 'var(--text-tertiary)', border: '1px solid', borderColor: input.trim() ? 'var(--accent-forest)' : 'var(--border-medium)', fontSize: '1rem', @@ -295,7 +295,7 @@ export default function CreatePage() { minWidth: '200px', padding: '0.875rem 1.5rem', background: copied ? 'var(--accent-moss)' : 'var(--bg-tertiary)', - color: 'var(--text-primary)', + color: copied ? 'var(--text-on-accent)' : 'var(--text-primary)', border: '1px solid', borderColor: copied ? 'var(--accent-forest)' : 'var(--border-strong)', fontSize: '0.95rem', diff --git a/src/app/fork/page.tsx b/src/app/fork/page.tsx index a9d6e64..55454f6 100644 --- a/src/app/fork/page.tsx +++ b/src/app/fork/page.tsx @@ -591,7 +591,7 @@ vercel padding: '0.875rem 1.75rem', background: 'var(--accent-moss)', border: '1px solid var(--accent-forest)', - color: 'var(--text-primary)', + color: 'var(--text-on-accent)', textDecoration: 'none', fontSize: '1rem', transition: 'all 0.3s ease', diff --git a/src/app/globals.css b/src/app/globals.css index 874bf67..3bf6b32 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -6,32 +6,107 @@ --bg-secondary: #151515; --bg-tertiary: #1a1a1a; --bg-elevated: #202020; - + /* Natural, organic accent colors */ --accent-moss: #4a5a3f; --accent-forest: #2d3a28; --accent-bark: #3d3329; --accent-stone: #4a4a46; - + /* Text colors - muted and calm */ --text-primary: #e8e8e6; --text-secondary: #a8a8a6; --text-tertiary: #686866; --text-accent: #8a9a7f; - + /* Ambient glow effects */ --glow-subtle: rgba(138, 154, 127, 0.05); --glow-medium: rgba(138, 154, 127, 0.1); --glow-strong: rgba(138, 154, 127, 0.2); - + /* Borders - subtle and organic */ --border-subtle: rgba(232, 232, 230, 0.08); --border-medium: rgba(232, 232, 230, 0.12); --border-strong: rgba(232, 232, 230, 0.16); - + + /* Page-level ambient glow used by body::before. Tuned moodier than the + standard glow tokens so the dark backdrop reads atmospheric; overridden + in [data-theme='light'] below. */ + --page-glow-a: rgba(138, 154, 127, 0.25); + --page-glow-b: rgba(74, 90, 63, 0.22); + --page-glow-c: rgba(138, 154, 127, 0.18); + + /* Translucent floating-element backdrop used behind backdrop-filter blurs + (e.g. the scroll-to-choose indicator). Tracks --bg-primary's hue so the + overlay blends with the page in both themes. */ + --bg-overlay: rgba(15, 15, 15, 0.85); + --bg-overlay-hover: rgba(18, 18, 18, 0.9); + --shadow-overlay: 0 8px 32px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.03); + --shadow-overlay-hover: 0 12px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.05); + + /* Raw-JSON / preview modals across PostPreview, RecordPreview, and the + margin/* previews share the same look. Themed once here so the modals + also flip when the user toggles light mode. */ + --modal-backdrop: rgba(10, 10, 10, 0.75); + --modal-bg: linear-gradient(145deg, rgba(21, 21, 21, 0.95) 0%, rgba(26, 26, 26, 0.98) 100%); + --modal-shadow: 0 20px 60px rgba(0, 0, 0, 0.5), 0 0 80px var(--glow-subtle); + + /* Opacity for the global noise overlay. Lower in light mode so paper + backgrounds don't look gritty. */ + --noise-opacity: 0.6; + + /* Text drawn on the dark-green primary buttons / active toggles. The + accent backgrounds stay dark in both themes, so this token does too. */ + --text-on-accent: #f0f0ee; + /* Typography scale */ --font-serif: 'Crimson Pro', serif; --font-mono: 'IBM Plex Mono', monospace; + + color-scheme: dark; +} + +[data-theme='light'] { + --bg-primary: #faf8f3; + --bg-secondary: #f1ede4; + --bg-tertiary: #e8e2d4; + --bg-elevated: #ffffff; + + --accent-moss: #6c7c5f; + --accent-forest: #4d5a48; + --accent-bark: #6e5f4c; + --accent-stone: #76766f; + + --text-primary: #1a1c18; + --text-secondary: #3e3f3a; + --text-tertiary: #5c5d56; + --text-accent: #4d5e3f; + + --glow-subtle: rgba(77, 94, 63, 0.05); + --glow-medium: rgba(77, 94, 63, 0.1); + --glow-strong: rgba(77, 94, 63, 0.18); + + --border-subtle: rgba(26, 28, 24, 0.14); + --border-medium: rgba(26, 28, 24, 0.22); + --border-strong: rgba(26, 28, 24, 0.32); + + /* Softer page glow so the paper backdrop reads warm but not muddy. */ + --page-glow-a: rgba(77, 94, 63, 0.14); + --page-glow-b: rgba(110, 95, 76, 0.12); + --page-glow-c: rgba(77, 94, 63, 0.1); + + --bg-overlay: rgba(250, 248, 243, 0.85); + --bg-overlay-hover: rgba(241, 237, 228, 0.92); + --shadow-overlay: 0 8px 32px rgba(26, 28, 24, 0.12), 0 0 0 1px rgba(26, 28, 24, 0.04); + --shadow-overlay-hover: 0 12px 40px rgba(26, 28, 24, 0.16), 0 0 0 1px rgba(26, 28, 24, 0.06); + + --modal-backdrop: rgba(26, 28, 24, 0.45); + --modal-bg: linear-gradient(145deg, rgba(255, 255, 255, 0.98) 0%, rgba(241, 237, 228, 0.99) 100%); + --modal-shadow: 0 20px 60px rgba(26, 28, 24, 0.18), 0 0 80px var(--glow-subtle); + + --noise-opacity: 0.25; + + color-scheme: light; } * { @@ -76,10 +151,10 @@ body::before { width: 140%; z-index: 0; pointer-events: none; - background: - radial-gradient(circle at 20% 30%, rgba(138, 154, 127, 0.25) 0%, transparent 50%), - radial-gradient(circle at 80% 70%, rgba(74, 90, 63, 0.22) 0%, transparent 50%), - radial-gradient(circle at 50% 50%, rgba(138, 154, 127, 0.18) 0%, transparent 60%); + background: + radial-gradient(circle at 20% 30%, var(--page-glow-a) 0%, transparent 50%), + radial-gradient(circle at 80% 70%, var(--page-glow-b) 0%, transparent 50%), + radial-gradient(circle at 50% 50%, var(--page-glow-c) 0%, transparent 60%); filter: blur(90px); animation: organicBreathe 8s ease-in-out infinite, organicShift 12s ease-in-out infinite; will-change: transform, opacity, filter; @@ -451,7 +526,7 @@ body::after { position: fixed; inset: 0; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 800 800' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.8' numOctaves='5' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.03'/%3E%3C/svg%3E"); - opacity: 0.6; + opacity: var(--noise-opacity); pointer-events: none; z-index: 9999; } @@ -537,7 +612,7 @@ html { /* Selection */ ::selection { background: var(--accent-moss); - color: var(--text-primary); + color: var(--text-on-accent); } /* Focus styles */ diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c62c86f..97d77fd 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,6 +2,8 @@ import type { Metadata } from "next"; import { Analytics } from "@vercel/analytics/next"; import Footer from "@/components/Footer"; import PageTransition from "@/components/PageTransition"; +import ThemeSync from "@/components/ThemeSync"; +import { DEFAULT_THEME, THEME_INIT_SCRIPT } from "@/lib/theme"; import "./globals.css"; export const metadata: Metadata = { @@ -29,15 +31,20 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + {/* Apple TN3156 requires an `application/activity+json` link element on social-network post pages for rich previews in Messages. Bluesky's bskyweb base template emits this with an empty href on EVERY page; we mirror that exactly. */} + {/* Apply the saved theme before first paint to avoid a dark/light + flash on cold loads. The script reads from localStorage and sets + data-theme on synchronously. */} +