New themes

This commit is contained in:
Brian Fertig 2026-08-28 12:24:52 -06:00
parent be6926c027
commit 80176636b7
4 changed files with 513 additions and 20 deletions

View File

@ -6,8 +6,8 @@
/* "dark:" must be true for every dark-surfaced theme, not just .dark -- /* "dark:" must be true for every dark-surfaced theme, not just .dark --
the dark looks (Dark, Ocean, Pine, Plum, Midnight, Ember, Rosewood, the dark looks (Dark, Ocean, Pine, Plum, Midnight, Ember, Rosewood,
Cyberpunk) all turn on dark utilities. */ Cyberpunk, Starfield) all turn on dark utilities. */
@custom-variant dark (&:is(.dark *, .ocean *, .theme-pine *, .theme-plum *, .theme-midnight *, .theme-ember *, .theme-rosewood *, .theme-cyberpunk *)); @custom-variant dark (&:is(.dark *, .ocean *, .theme-pine *, .theme-plum *, .theme-midnight *, .theme-ember *, .theme-rosewood *, .theme-cyberpunk *, .theme-starfield *));
@theme inline { @theme inline {
--color-background: var(--background); --color-background: var(--background);
@ -750,12 +750,152 @@
--sidebar-ring: oklch(0.85 0.14 195); --sidebar-ring: oklch(0.85 0.14 195);
} }
/* Vaporwave -- pastel synthwave: a soft pinklavendermint sky, a hot-magenta
action color, an electric-cyan focus, and a rounded retro face (Righteous).
Light-surfaced; see the extras section for the pastel sky, the synthwave
sun, the soft glow, and the to-do treatment. */
.theme-vaporwave {
color-scheme: light;
--background: oklch(0.92 0.045 320);
--foreground: oklch(0.32 0.09 335);
--card: oklch(0.972 0.012 330);
--card-foreground: oklch(0.32 0.09 335);
--popover: oklch(0.972 0.012 330);
--popover-foreground: oklch(0.32 0.09 335);
/* Hot magenta -- deep enough for a clean, WCAG-AA white label. */
--primary: oklch(0.56 0.26 350);
--primary-foreground: oklch(0.99 0 0);
--secondary: oklch(0.9 0.045 325);
--secondary-foreground: oklch(0.34 0.09 335);
--muted: oklch(0.925 0.025 325);
--muted-foreground: oklch(0.47 0.08 335);
/* A cool mint accent so the hover state isn't just more pink. */
--accent: oklch(0.89 0.055 200);
--accent-foreground: oklch(0.3 0.09 200);
--destructive: oklch(0.58 0.21 25);
--destructive-foreground: oklch(0.99 0 0);
--border: oklch(0.8 0.055 325);
--input: oklch(0.8 0.055 325);
/* Electric-cyan focus ring. */
--ring: oklch(0.56 0.19 195);
--lane: oklch(1 0 0 / 6%);
--chart-1: oklch(0.6 0.24 350);
--chart-2: oklch(0.62 0.2 300);
--chart-3: oklch(0.62 0.18 200);
--chart-4: oklch(0.66 0.16 160);
--chart-5: oklch(0.72 0.15 90);
/* Soft, chrome-rounded corners. */
--radius: 0.85rem;
--sidebar: oklch(0.89 0.05 325);
--sidebar-foreground: oklch(0.32 0.09 335);
--sidebar-primary: oklch(0.56 0.26 350);
--sidebar-primary-foreground: oklch(0.99 0 0);
--sidebar-accent: oklch(0.85 0.065 325);
--sidebar-accent-foreground: oklch(0.34 0.09 335);
--sidebar-border: oklch(0.8 0.055 325);
--sidebar-ring: oklch(0.56 0.19 195);
}
/* Starfield -- deep space: near-black indigo, a warm star-gold action color,
a bright-cyan focus, and a sci-fi face (Orbitron). Dark-surfaced; see the
extras section for the starfield, the nebula glow, and the to-do treatment.
*/
.theme-starfield {
color-scheme: dark;
--background: oklch(0.14 0.035 270);
--foreground: oklch(0.9 0.025 250);
--card: oklch(0.185 0.04 270);
--card-foreground: oklch(0.9 0.025 250);
--popover: oklch(0.2 0.045 270);
--popover-foreground: oklch(0.9 0.025 250);
/* Star-gold -- a warm light against the cold void; carries dark ink. */
--primary: oklch(0.78 0.15 82);
--primary-foreground: oklch(0.19 0.04 270);
--secondary: oklch(0.24 0.05 270);
--secondary-foreground: oklch(0.88 0.04 250);
--muted: oklch(0.21 0.045 270);
--muted-foreground: oklch(0.7 0.035 255);
--accent: oklch(0.27 0.06 270);
--accent-foreground: oklch(0.9 0.04 250);
--destructive: oklch(0.56 0.21 25);
--destructive-foreground: oklch(0.99 0 0);
--border: oklch(0.9 0.04 250 / 16%);
--input: oklch(0.9 0.04 250 / 13%);
/* Bright-cyan focus. */
--ring: oklch(0.78 0.15 210);
--lane: oklch(1 0 0 / 5%);
--chart-1: oklch(0.78 0.15 210);
--chart-2: oklch(0.72 0.16 82);
--chart-3: oklch(0.7 0.16 290);
--chart-4: oklch(0.74 0.14 160);
--chart-5: oklch(0.8 0.12 30);
/* Medium, calm corners. */
--radius: 0.7rem;
--sidebar: oklch(0.165 0.042 270);
--sidebar-foreground: oklch(0.9 0.025 250);
--sidebar-primary: oklch(0.78 0.15 82);
--sidebar-primary-foreground: oklch(0.19 0.04 270);
--sidebar-accent: oklch(0.27 0.06 270);
--sidebar-accent-foreground: oklch(0.9 0.04 250);
--sidebar-border: oklch(0.9 0.04 250 / 16%);
--sidebar-ring: oklch(0.78 0.15 210);
}
/* Notebook -- stationery: a warm desk tone, white ruled-paper cards, a blue
ballpoint action color, a red margin rule, and a casual handwriting face
(Caveat). Light-surfaced; see the extras section for the ruled paper, the
margin line, and the highlighter to-do treatment. */
.theme-notebook {
color-scheme: light;
--background: oklch(0.86 0.025 80);
--foreground: oklch(0.25 0.025 70);
--card: oklch(0.978 0.008 90);
--card-foreground: oklch(0.25 0.025 70);
--popover: oklch(0.978 0.008 90);
--popover-foreground: oklch(0.25 0.025 70);
/* Blue ballpoint ink -- deep enough for a clean white label. */
--primary: oklch(0.46 0.13 255);
--primary-foreground: oklch(0.99 0 0);
--secondary: oklch(0.9 0.025 250);
--secondary-foreground: oklch(0.28 0.03 250);
--muted: oklch(0.93 0.012 90);
--muted-foreground: oklch(0.44 0.035 75);
/* A light blue, like a page highlight. */
--accent: oklch(0.89 0.045 250);
--accent-foreground: oklch(0.3 0.05 250);
--destructive: oklch(0.57 0.21 25);
--destructive-foreground: oklch(0.99 0 0);
/* A pale blue rule line. */
--border: oklch(0.79 0.035 250);
--input: oklch(0.79 0.035 250);
/* Blue-ink focus. */
--ring: oklch(0.5 0.15 255);
--lane: oklch(1 0 0 / 5%);
--chart-1: oklch(0.5 0.13 255);
--chart-2: oklch(0.55 0.14 190);
--chart-3: oklch(0.6 0.13 80);
--chart-4: oklch(0.55 0.18 25);
--chart-5: oklch(0.58 0.14 130);
/* Slightly rounded paper corners. */
--radius: 0.6rem;
--sidebar: oklch(0.83 0.028 80);
--sidebar-foreground: oklch(0.25 0.025 70);
--sidebar-primary: oklch(0.46 0.13 255);
--sidebar-primary-foreground: oklch(0.99 0 0);
--sidebar-accent: oklch(0.85 0.05 250);
--sidebar-accent-foreground: oklch(0.28 0.04 250);
--sidebar-border: oklch(0.78 0.035 250);
--sidebar-ring: oklch(0.5 0.15 255);
}
/* --------------------------------------------------------------------------- /* ---------------------------------------------------------------------------
EXTRA THEMES -- beyond tokens, Blueprint and Cyberpunk carry their own EXTRA THEMES -- beyond tokens, Blueprint, Cyberpunk, Vaporwave, Starfield,
fonts, backgrounds, and animations. Everything here is scoped under the and Notebook each carry their own fonts, backgrounds, and animations.
theme's own class on <html>, so no other theme is touched. The reduced- Everything here is scoped under the theme's own class on <html>, so no
motion guard in @layer base still kills the animations for users who other theme is touched. The reduced-motion guard in @layer base still
ask for it (the static styling remains). kills the animations for users who ask for it (the static styling remains).
All the corner character comes from each theme's `--radius` token (the
`rounded-*` utilities scale from it), so no per-theme corner overrides.
--------------------------------------------------------------------------- */ --------------------------------------------------------------------------- */
/* ------------------------------ Blueprint -------------------------------- */ /* ------------------------------ Blueprint -------------------------------- */
@ -1152,8 +1292,10 @@ html.theme-cyberpunk [data-todo-checkbox] svg {
filter: drop-shadow(0 0 3px rgb(34 255 238 / 0.85)); filter: drop-shadow(0 0 3px rgb(34 255 238 / 0.85));
} }
/* The whole card glitches every so often -- a brief RGB-split, a skew, and a /* The whole card glitches every so often -- a brief RGB-split, a move, and a
sliced clip, then it settles back. Most of the cycle is calm; the burst is a skew, then it settles back. No clip-path: the card never gets sliced away,
it only jitters, skews, and throws a chromatic fringe, so the whole thing
stays visible the whole time. Most of the cycle is calm; the burst is a
handful of held frames (steps, so it jitters rather than eases). Cards are handful of held frames (steps, so it jitters rather than eases). Cards are
phase-offset so a boardful of them don't all tear on the same beat. */ phase-offset so a boardful of them don't all tear on the same beat. */
html.theme-cyberpunk [data-group-card] { html.theme-cyberpunk [data-group-card] {
@ -1183,16 +1325,18 @@ html.theme-cyberpunk [data-group-card]:nth-child(3n) {
} }
88% { 88% {
transform: translate(3px, -1px); transform: translate(3px, -1px);
clip-path: inset(0 0 14% 0);
filter: saturate(1.8); filter: saturate(1.8);
box-shadow:
-3px 0 rgb(255 43 214 / 0.55),
3px 0 rgb(34 255 238 / 0.55);
} }
89% { 89% {
transform: translate(-1px, 1px) skewX(2deg); transform: translate(-1px, 1px) skewX(2deg);
clip-path: inset(12% 0 0 0); filter: hue-rotate(14deg) saturate(1.5);
} }
90% { 90% {
transform: translate(2px, -1px); transform: translate(2px, -1px);
clip-path: inset(6% 0 18% 0); filter: saturate(1.4);
box-shadow: box-shadow:
-4px 0 rgb(255 43 214 / 0.5), -4px 0 rgb(255 43 214 / 0.5),
4px 0 rgb(34 255 238 / 0.5); 4px 0 rgb(34 255 238 / 0.5);
@ -1207,6 +1351,311 @@ html.theme-cyberpunk [data-group-card]:nth-child(3n) {
} }
} }
/* ------------------------------ Vaporwave ------------------------------- */
/* The retro face carries the whole deck: Righteous (a rounded, retro-futur-
istic face) for headings AND for the body/to-do text, so everything reads
like a glossy 90s desktop, not just the title. Retarget the theme's font
variables so every `font-sans` surface (to-dos, buttons, nav, note editor)
and every `font-heading` title picks it up. */
html.theme-vaporwave {
--font-sans: var(--font-vaporwave), "Trebuchet MS", "Segoe UI", sans-serif;
--font-heading: var(--font-vaporwave), "Trebuchet MS", "Segoe UI", sans-serif;
}
/* Righteous already runs a touch wide; a hair of tracking reads cleaner. */
html.theme-vaporwave .font-heading {
letter-spacing: 0.015em;
}
/* Pastel sky: a soft pinklavendermint wash, with a warm sunrise glow
bleeding down from the top. Sits on the token's background color. */
html.theme-vaporwave body {
background-image:
radial-gradient(
120% 55% at 50% -12%,
oklch(0.9 0.26 25 / 0.4) 0%,
oklch(0.9 0.24 345 / 0.22) 34%,
transparent 62%
),
linear-gradient(
180deg,
oklch(0.9 0.075 322) 0%,
oklch(0.945 0.05 292) 46%,
oklch(0.955 0.055 190) 100%
);
}
/* Faint scanlines rolling slowly, like a monitor left on in the morning. */
html.theme-vaporwave body::before {
content: "";
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background-image: repeating-linear-gradient(
0deg,
oklch(0.42 0.16 320 / 0.035) 0px,
oklch(0.42 0.16 320 / 0.035) 1px,
transparent 1px,
transparent 4px
);
animation: vw-roll 16s linear infinite;
}
@keyframes vw-roll {
from {
background-position-y: 0px;
}
to {
background-position-y: 4px;
}
}
/* Primary blooms with a soft magenta glow; focus glows electric cyan. */
html.theme-vaporwave .bg-primary {
box-shadow: 0 4px 18px oklch(0.62 0.25 350 / 0.35);
}
html.theme-vaporwave :focus-visible {
box-shadow:
0 0 0 3px color-mix(in oklch, var(--ring) 40%, transparent),
0 0 14px oklch(0.56 0.19 195 / 0.5);
}
/* To-do checkbox: rounded (the chrome look), a cyan glow that builds on
hover, and a magenta bloom the moment it's checked. (The check's own
stroke/fill already come from the group's accent color.) */
html.theme-vaporwave [data-todo-checkbox] {
border-radius: 6px;
}
html.theme-vaporwave [data-todo-checkbox]:hover {
box-shadow: 0 0 10px oklch(0.56 0.19 195 / 0.5);
}
html.theme-vaporwave [data-todo-checkbox][aria-checked="true"] {
box-shadow: 0 0 12px oklch(0.62 0.25 350 / 0.55);
}
html.theme-vaporwave [data-todo-checkbox] svg {
filter: drop-shadow(0 0 3px oklch(0.56 0.19 195 / 0.8));
}
/* Completed to-do: the strike flickers with a soft neon glow before it
settles, like a sign powering down. */
html.theme-vaporwave .line-through {
text-decoration-thickness: 1.5px;
animation: vw-flicker 420ms steps(6, jump-none) 1;
}
@keyframes vw-flicker {
0%,
100% {
text-shadow: none;
}
20% {
text-shadow: 0 0 8px oklch(0.62 0.25 350 / 0.7);
}
45% {
text-shadow: 0 0 3px oklch(0.56 0.19 195 / 0.6);
}
70% {
text-shadow: 0 0 6px oklch(0.62 0.25 350 / 0.5);
}
}
/* ------------------------------- Starfield ------------------------------ */
/* The sci-fi face carries the whole deck: Orbitron (a geometric, techy face)
for headings AND for the body/to-do text, so everything reads like a ship
console. Retarget the theme's font variables. */
html.theme-starfield {
--font-sans: var(--font-starfield), "Segoe UI", system-ui, sans-serif;
--font-heading: var(--font-starfield), "Segoe UI", system-ui, sans-serif;
}
/* Orbitron is wide and angular; a touch of tracking sells the console feel. */
html.theme-starfield .font-heading {
letter-spacing: 0.05em;
}
/* The void: a deep indigo nebula fading to near-black at the edges. */
html.theme-starfield body {
background-image: radial-gradient(
120% 100% at 50% 0%,
oklch(0.23 0.08 272) 0%,
oklch(0.16 0.05 268) 55%,
oklch(0.12 0.04 262) 100%
);
}
/* A starfield: a tiled field of small stars at a couple of sizes/temperatures,
sitting behind the content (stars show in the void between surfaces). A
slow twinkle keeps it alive. */
html.theme-starfield body::before {
content: "";
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background-image:
radial-gradient(1px 1px at 20px 30px, oklch(0.98 0 0 / 0.95), transparent),
radial-gradient(1px 1px at 140px 110px, oklch(0.94 0.03 210 / 0.75), transparent),
radial-gradient(1.5px 1.5px at 230px 180px, oklch(0.9 0.06 85 / 0.7), transparent),
radial-gradient(1px 1px at 90px 210px, oklch(0.88 0.05 300 / 0.55), transparent),
radial-gradient(2px 2px at 200px 60px, oklch(0.96 0.02 210 / 0.5), transparent);
background-size: 280px 280px;
animation: sf-twinkle 7s ease-in-out infinite;
}
@keyframes sf-twinkle {
0%,
100% {
opacity: 0.5;
}
50% {
opacity: 1;
}
}
/* The odd shooting star streaking across the void. */
html.theme-starfield body::after {
content: "";
position: fixed;
top: 14%;
left: -8%;
width: 110px;
height: 1.5px;
z-index: -1;
pointer-events: none;
border-radius: 999px;
background: linear-gradient(90deg, transparent, oklch(0.98 0 0 / 0.9));
opacity: 0;
animation: sf-shoot 13s ease-in 3s infinite;
}
@keyframes sf-shoot {
0% {
transform: translate(0, 0) rotate(-9deg);
opacity: 0;
}
3% {
opacity: 0.9;
}
11% {
transform: translate(64vw, 9vh) rotate(-9deg);
opacity: 0;
}
100% {
opacity: 0;
}
}
/* Star-gold primary blooms; focus glows bright cyan. */
html.theme-starfield .bg-primary {
box-shadow: 0 0 16px oklch(0.78 0.15 82 / 0.32);
}
html.theme-starfield :focus-visible {
box-shadow:
0 0 0 3px color-mix(in oklch, var(--ring) 40%, transparent),
0 0 14px oklch(0.78 0.15 210 / 0.5);
}
/* To-do checkbox: a crisp console box; a cyan glow builds on hover and blooms
when checked. (The check's own stroke/fill already come from the group's
accent color.) */
html.theme-starfield [data-todo-checkbox] {
border-radius: 4px;
}
html.theme-starfield [data-todo-checkbox]:hover {
box-shadow: 0 0 10px oklch(0.78 0.15 210 / 0.5);
}
html.theme-starfield [data-todo-checkbox][aria-checked="true"] {
box-shadow: 0 0 12px oklch(0.78 0.15 210 / 0.6);
}
html.theme-starfield [data-todo-checkbox] svg {
filter: drop-shadow(0 0 3px oklch(0.78 0.15 210 / 0.85));
}
/* A completed to-do keeps a faint star-glow on its strike, like a name lit up
against the dark. */
html.theme-starfield .line-through {
text-decoration-thickness: 1.5px;
text-shadow: 0 0 8px oklch(0.9 0.1 85 / 0.35);
}
/* ------------------------------- Notebook ------------------------------- */
/* The pen script carries the whole deck: Caveat (a casual handwriting face)
for headings AND for the body/to-do text, so everything reads as written by
hand. Retarget the theme's font variables. Caveat runs light at small
sizes, so give the body a touch of weight. */
html.theme-notebook {
--font-sans: var(--font-notebook), "Segoe Print", "Bradley Hand", cursive;
--font-heading: var(--font-notebook), "Segoe Print", "Bradley Hand", cursive;
}
html.theme-notebook body {
font-weight: 500;
}
html.theme-notebook .font-heading {
font-weight: 600;
}
/* A handwriting face crowds under the base's tight tracking -- relax it. */
html.theme-notebook body,
html.theme-notebook h1,
html.theme-notebook h2,
html.theme-notebook h3,
html.theme-notebook h4 {
letter-spacing: 0;
}
/* Warm desk: the paper cards read as sheets laid on a worktop. */
html.theme-notebook body {
background-image: linear-gradient(180deg, oklch(0.88 0.024 82), oklch(0.84 0.028 80));
}
/* Ruled paper on the white card surfaces: blue rules + a red margin line on
the left, the classic notebook page. */
html.theme-notebook [data-slot="card"],
html.theme-notebook [data-slot="dialog-content"] {
background-image:
linear-gradient(90deg, transparent 24px, oklch(0.6 0.19 25 / 0.4) 24px 26px, transparent 26px),
repeating-linear-gradient(180deg, transparent 0 27px, oklch(0.68 0.09 250 / 0.4) 27px 28px);
}
/* Group cards keep their own pastel tint (they read as index cards) but carry
a faint red margin line so they stay in the stationery family. */
html.theme-notebook [data-group-card] {
background-image: linear-gradient(90deg, transparent 20px, oklch(0.6 0.19 25 / 0.3) 20px 22px, transparent 22px);
}
/* Blue-ink focus. */
html.theme-notebook .bg-primary {
box-shadow: 0 3px 14px oklch(0.46 0.13 255 / 0.28);
}
html.theme-notebook :focus-visible {
box-shadow:
0 0 0 3px color-mix(in oklch, var(--ring) 35%, transparent),
0 0 12px oklch(0.5 0.15 255 / 0.4);
}
/* To-do checkbox: a printed box with a blue-ink stroke; a highlighter glow
builds on hover and marks the checked box. (The check's own stroke/fill
already come from the group's accent color.) */
html.theme-notebook [data-todo-checkbox] {
border-radius: 4px;
}
html.theme-notebook [data-todo-checkbox]:hover {
box-shadow: 0 0 0 3px oklch(0.9 0.17 105 / 0.5);
}
html.theme-notebook [data-todo-checkbox][aria-checked="true"] {
box-shadow: 0 0 0 3px oklch(0.9 0.17 105 / 0.3);
}
/* Completed to-do: a highlighter sweeps under the words, then the pen strikes
through -- the classic "done it, now cross it out" note. */
html.theme-notebook .line-through {
text-decoration-thickness: 1.5px;
background-image: linear-gradient(0deg, oklch(0.93 0.19 105 / 0.4) 0 100%);
background-repeat: no-repeat;
background-size: 0% 60%;
background-position: 0 62%;
animation: nb-highlight 420ms ease-out forwards;
}
@keyframes nb-highlight {
to {
background-size: 100% 60%;
}
}
@layer base { @layer base {
* { * {
@apply border-border outline-ring/50; @apply border-border outline-ring/50;

View File

@ -1,5 +1,14 @@
import type { Metadata, Viewport } from "next"; import type { Metadata, Viewport } from "next";
import { Architects_Daughter, Figtree, Inter, JetBrains_Mono, VT323 } from "next/font/google"; import {
Architects_Daughter,
Caveat,
Figtree,
Inter,
JetBrains_Mono,
Orbitron,
Righteous,
VT323,
} from "next/font/google";
import "./globals.css"; import "./globals.css";
import { ThemeProvider } from "@/components/theme/theme-provider"; import { ThemeProvider } from "@/components/theme/theme-provider";
@ -42,6 +51,27 @@ const vt323 = VT323({
weight: "400", weight: "400",
}); });
/** Vaporwave theme's retro face (see app/globals.css) -- a rounded,
* retro-futuristic face for the pastel synthwave look. */
const righteous = Righteous({
variable: "--font-vaporwave",
subsets: ["latin"],
weight: "400",
});
/** Starfield theme's sci-fi face (see app/globals.css). */
const orbitron = Orbitron({
variable: "--font-starfield",
subsets: ["latin"],
});
/** Notebook theme's handwriting face (see app/globals.css) -- a casual pen
* script for the stationery look. */
const caveat = Caveat({
variable: "--font-notebook",
subsets: ["latin"],
});
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Organize", title: "Organize",
description: "An extended to-do list and notes organizer.", description: "An extended to-do list and notes organizer.",
@ -68,21 +98,22 @@ export default function RootLayout({ children }: LayoutProps<"/">) {
return ( return (
<html <html
lang="en" lang="en"
className={`${inter.variable} ${figtree.variable} ${jetbrainsMono.variable} ${architectsDaughter.variable} ${vt323.variable} h-full antialiased`} className={`${inter.variable} ${figtree.variable} ${jetbrainsMono.variable} ${architectsDaughter.variable} ${vt323.variable} ${righteous.variable} ${orbitron.variable} ${caveat.variable} h-full antialiased`}
suppressHydrationWarning suppressHydrationWarning
> >
<body className="min-h-full flex flex-col"> <body className="min-h-full flex flex-col">
{/* Sixteen looks: eight light (Default, Sunset, Meadow, Honey, Rosé, {/* Nineteen looks: ten light (Default, Sunset, Meadow, Honey, Rosé,
Lavender, Slate, Blueprint) and eight dark (Dark, Ocean, Pine, Lavender, Slate, Blueprint, Vaporwave, Notebook) and nine dark
Plum, Midnight, Ember, Rosewood, Cyberpunk) -- complete token (Dark, Ocean, Pine, Plum, Midnight, Ember, Rosewood, Cyberpunk,
sets in app/globals.css; Blueprint and Cyberpunk additionally Starfield) -- complete token sets in app/globals.css; Blueprint,
carry their own fonts, backgrounds, and animations. Cyberpunk, Vaporwave, Starfield, and Notebook additionally carry
their own fonts, backgrounds, and animations.
"system" resolves to Default/Dark by OS preference. "system" resolves to Default/Dark by OS preference.
No-FOUC theme restore: runs before first paint, applies the No-FOUC theme restore: runs before first paint, applies the
stored preference to <html> (class + color-scheme). */} stored preference to <html> (class + color-scheme). */}
<script <script
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: `!(function(){try{var r=document.documentElement,c=['theme-default','theme-sunset','theme-meadow','theme-honey','theme-rose','theme-lavender','theme-slate','theme-blueprint','dark','ocean','theme-pine','theme-plum','theme-midnight','theme-ember','theme-rosewood','theme-cyberpunk','light'];for(var i=0;i<c.length;i++){r.classList.remove(c[i]);}var v=null;try{v=localStorage.getItem('theme');}catch(e){}var m={default:'theme-default',sunset:'theme-sunset',meadow:'theme-meadow',honey:'theme-honey',rose:'theme-rose',lavender:'theme-lavender',slate:'theme-slate',blueprint:'theme-blueprint',dark:'dark',ocean:'ocean',pine:'theme-pine',plum:'theme-plum',midnight:'theme-midnight',ember:'theme-ember',rosewood:'theme-rosewood',cyberpunk:'theme-cyberpunk'};var d=['dark','ocean','pine','plum','midnight','ember','rosewood','cyberpunk'];if(v==='system'||!v){v=window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'default';}if(m[v]){r.classList.add(m[v]);r.style.colorScheme=d.indexOf(v)>=0?'dark':'light';}}catch(e){}})();`, __html: `!(function(){try{var r=document.documentElement,c=['theme-default','theme-sunset','theme-meadow','theme-honey','theme-rose','theme-lavender','theme-slate','theme-blueprint','theme-vaporwave','theme-notebook','dark','ocean','theme-pine','theme-plum','theme-midnight','theme-ember','theme-rosewood','theme-cyberpunk','theme-starfield','light'];for(var i=0;i<c.length;i++){r.classList.remove(c[i]);}var v=null;try{v=localStorage.getItem('theme');}catch(e){}var m={default:'theme-default',sunset:'theme-sunset',meadow:'theme-meadow',honey:'theme-honey',rose:'theme-rose',lavender:'theme-lavender',slate:'theme-slate',blueprint:'theme-blueprint',vaporwave:'theme-vaporwave',notebook:'theme-notebook',dark:'dark',ocean:'ocean',pine:'theme-pine',plum:'theme-plum',midnight:'theme-midnight',ember:'theme-ember',rosewood:'theme-rosewood',cyberpunk:'theme-cyberpunk',starfield:'theme-starfield'};var d=['dark','ocean','pine','plum','midnight','ember','rosewood','cyberpunk','starfield'];if(v==='system'||!v){v=window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'default';}if(m[v]){r.classList.add(m[v]);r.style.colorScheme=d.indexOf(v)>=0?'dark':'light';}}catch(e){}})();`,
}} }}
/> />
<ThemeProvider defaultTheme="system"> <ThemeProvider defaultTheme="system">

View File

@ -38,6 +38,8 @@ export const THEMES = [
"lavender", "lavender",
"slate", "slate",
"blueprint", "blueprint",
"vaporwave",
"notebook",
"dark", "dark",
"ocean", "ocean",
"pine", "pine",
@ -46,6 +48,7 @@ export const THEMES = [
"ember", "ember",
"rosewood", "rosewood",
"cyberpunk", "cyberpunk",
"starfield",
] as const; ] as const;
export type ThemeName = (typeof THEMES)[number]; export type ThemeName = (typeof THEMES)[number];
export type RawTheme = ThemeName | "system"; export type RawTheme = ThemeName | "system";
@ -60,6 +63,8 @@ const THEME_CLASSES: Record<ThemeName, string> = {
lavender: "theme-lavender", lavender: "theme-lavender",
slate: "theme-slate", slate: "theme-slate",
blueprint: "theme-blueprint", blueprint: "theme-blueprint",
vaporwave: "theme-vaporwave",
notebook: "theme-notebook",
dark: "dark", dark: "dark",
ocean: "ocean", ocean: "ocean",
pine: "theme-pine", pine: "theme-pine",
@ -68,6 +73,7 @@ const THEME_CLASSES: Record<ThemeName, string> = {
ember: "theme-ember", ember: "theme-ember",
rosewood: "theme-rosewood", rosewood: "theme-rosewood",
cyberpunk: "theme-cyberpunk", cyberpunk: "theme-cyberpunk",
starfield: "theme-starfield",
}; };
const ALL_CLASSES = Object.values(THEME_CLASSES); const ALL_CLASSES = Object.values(THEME_CLASSES);
/* Themes whose *surfaces* are dark (see app/globals.css). The single source /* Themes whose *surfaces* are dark (see app/globals.css). The single source
@ -82,6 +88,7 @@ export const DARK_SURFACES: ThemeName[] = [
"ember", "ember",
"rosewood", "rosewood",
"cyberpunk", "cyberpunk",
"starfield",
]; ];
interface ThemeContextValue { interface ThemeContextValue {

View File

@ -20,6 +20,9 @@ import {
Wine, Wine,
DraftingCompass, DraftingCompass,
CircuitBoard, CircuitBoard,
Disc3,
Orbit,
NotebookText,
} from "lucide-react"; } from "lucide-react";
import { useTheme } from "@/components/theme/theme-provider"; import { useTheme } from "@/components/theme/theme-provider";
@ -52,6 +55,8 @@ const LIGHT_OPTIONS: ThemeOption[] = [
{ value: "lavender", label: "Lavender", icon: Flower2, swatch: "#7c3aed" }, { value: "lavender", label: "Lavender", icon: Flower2, swatch: "#7c3aed" },
{ value: "slate", label: "Slate", icon: Mountain, swatch: "#4a6d9c" }, { value: "slate", label: "Slate", icon: Mountain, swatch: "#4a6d9c" },
{ value: "blueprint", label: "Blueprint", icon: DraftingCompass, swatch: "#2b4bc4" }, { value: "blueprint", label: "Blueprint", icon: DraftingCompass, swatch: "#2b4bc4" },
{ value: "vaporwave", label: "Vaporwave", icon: Disc3, swatch: "#e879f9" },
{ value: "notebook", label: "Notebook", icon: NotebookText, swatch: "#3b5fc4" },
]; ];
const DARK_OPTIONS: ThemeOption[] = [ const DARK_OPTIONS: ThemeOption[] = [
{ value: "dark", label: "Dark", icon: Moon, swatch: "#7c83f2" }, { value: "dark", label: "Dark", icon: Moon, swatch: "#7c83f2" },
@ -62,6 +67,7 @@ const DARK_OPTIONS: ThemeOption[] = [
{ value: "ember", label: "Ember", icon: Flame, swatch: "#fbbf24" }, { value: "ember", label: "Ember", icon: Flame, swatch: "#fbbf24" },
{ value: "rosewood", label: "Rosewood", icon: Wine, swatch: "#fb7185" }, { value: "rosewood", label: "Rosewood", icon: Wine, swatch: "#fb7185" },
{ value: "cyberpunk", label: "Cyberpunk", icon: CircuitBoard, swatch: "#ff2bd6" }, { value: "cyberpunk", label: "Cyberpunk", icon: CircuitBoard, swatch: "#ff2bd6" },
{ value: "starfield", label: "Starfield", icon: Orbit, swatch: "#38bdf8" },
]; ];
const ALL_OPTIONS = [...LIGHT_OPTIONS, ...DARK_OPTIONS]; const ALL_OPTIONS = [...LIGHT_OPTIONS, ...DARK_OPTIONS];