diff --git a/src/components/mobile-nav/settings-sheet.tsx b/src/components/mobile-nav/settings-sheet.tsx index 48536e8..6b5c455 100644 --- a/src/components/mobile-nav/settings-sheet.tsx +++ b/src/components/mobile-nav/settings-sheet.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { X, ExternalLink } from "lucide-react"; -import { THEMES } from "@/lib/themes"; +import { FAMILIES, THEMES } from "@/lib/themes"; import { applyTheme, getStoredThemeId } from "@/lib/themes/engine"; import { ANIMATION_IDS, ANIMATION_LABELS, type AnimationId } from "@/lib/animations"; @@ -11,12 +11,6 @@ const footerLinks = [ { href: "https://github.com/timmypidashev/web", label: "Source", color: "text-purple" }, ]; -const themeOptions = [ - { id: "darkbox", label: "classic", color: "text-yellow-bright", activeBg: "bg-yellow-bright/15", activeBorder: "border-yellow-bright/40" }, - { id: "darkbox-retro", label: "retro", color: "text-orange-bright", activeBg: "bg-orange-bright/15", activeBorder: "border-orange-bright/40" }, - { id: "darkbox-dim", label: "dim", color: "text-purple-bright", activeBg: "bg-purple-bright/15", activeBorder: "border-purple-bright/40" }, -]; - const animOptions = [ { id: "shuffle", color: "text-red-bright", activeBg: "bg-red-bright/15", activeBorder: "border-red-bright/40" }, { id: "game-of-life", color: "text-green-bright", activeBg: "bg-green-bright/15", activeBorder: "border-green-bright/40" }, @@ -26,6 +20,17 @@ const animOptions = [ { id: "pipes", color: "text-blue-bright", activeBg: "bg-blue-bright/15", activeBorder: "border-blue-bright/40" }, ]; +// Cycle through accent colors for variant buttons +const variantColors = [ + { color: "text-yellow-bright", activeBg: "bg-yellow-bright/15", activeBorder: "border-yellow-bright/40" }, + { color: "text-orange-bright", activeBg: "bg-orange-bright/15", activeBorder: "border-orange-bright/40" }, + { color: "text-purple-bright", activeBg: "bg-purple-bright/15", activeBorder: "border-purple-bright/40" }, + { color: "text-blue-bright", activeBg: "bg-blue-bright/15", activeBorder: "border-blue-bright/40" }, + { color: "text-green-bright", activeBg: "bg-green-bright/15", activeBorder: "border-green-bright/40" }, + { color: "text-red-bright", activeBg: "bg-red-bright/15", activeBorder: "border-red-bright/40" }, + { color: "text-aqua-bright", activeBg: "bg-aqua-bright/15", activeBorder: "border-aqua-bright/40" }, +]; + export function SettingsSheet({ open, onClose }: { open: boolean; onClose: () => void }) { const [currentTheme, setCurrentTheme] = useState(getStoredThemeId()); const [currentAnim, setCurrentAnim] = useState("shuffle"); @@ -37,7 +42,6 @@ export function SettingsSheet({ open, onClose }: { open: boolean; onClose: () => const handleTheme = (id: string) => { applyTheme(id); setCurrentTheme(id); - onClose(); }; const handleAnim = (id: string) => { @@ -48,6 +52,8 @@ export function SettingsSheet({ open, onClose }: { open: boolean; onClose: () => onClose(); }; + const currentFamily = THEMES[currentTheme]?.family ?? FAMILIES[0].id; + return ( <> {/* Backdrop */} @@ -63,7 +69,7 @@ export function SettingsSheet({ open, onClose }: { open: boolean; onClose: () => className={`fixed left-0 right-0 bottom-0 z-[70] bg-background border-t border-foreground/10 rounded-t-2xl transition-transform duration-300 ease-out ${ open ? "translate-y-0" : "translate-y-full" }`} - style={{ paddingBottom: "env(safe-area-inset-bottom, 0px)" }} + style={{ paddingBottom: "env(safe-area-inset-bottom, 0px)", maxHeight: "80vh", overflowY: "auto" }} >
Settings @@ -76,21 +82,43 @@ export function SettingsSheet({ open, onClose }: { open: boolean; onClose: () => {/* Theme */}
Theme
-
- {themeOptions.map((opt) => ( + + {/* Family selector */} +
+ {FAMILIES.map((family) => ( ))}
+ + {/* Variant selector for current family */} +
+ {FAMILIES.find((f) => f.id === currentFamily)?.themes.map((theme, i) => { + const style = variantColors[i % variantColors.length]; + return ( + + ); + })} +
{/* Animation */} diff --git a/src/components/theme-switcher/index.tsx b/src/components/theme-switcher/index.tsx index 6d7c55c..13006ca 100644 --- a/src/components/theme-switcher/index.tsx +++ b/src/components/theme-switcher/index.tsx @@ -1,31 +1,35 @@ import { useRef, useState, useEffect } from "react"; -import { getStoredThemeId, getNextTheme, applyTheme } from "@/lib/themes/engine"; +import { THEMES, FAMILIES } from "@/lib/themes"; +import { getStoredThemeId, getNextFamily, getNextVariant, applyTheme } from "@/lib/themes/engine"; const FADE_DURATION = 300; -const LABELS: Record = { - darkbox: "classic", - "darkbox-retro": "retro", - "darkbox-dim": "dim", -}; - export default function ThemeSwitcher() { const [hovering, setHovering] = useState(false); - const [currentLabel, setCurrentLabel] = useState(""); + const [familyName, setFamilyName] = useState(""); + const [variantLabel, setVariantLabel] = useState(""); const maskRef = useRef(null); const animatingRef = useRef(false); const committedRef = useRef(""); + function syncLabels(id: string) { + const theme = THEMES[id]; + if (!theme) return; + const family = FAMILIES.find((f) => f.id === theme.family); + setFamilyName(family?.name.toLowerCase() ?? theme.family); + setVariantLabel(theme.label); + } + useEffect(() => { committedRef.current = getStoredThemeId(); - setCurrentLabel(LABELS[committedRef.current] ?? ""); + syncLabels(committedRef.current); const handleSwap = () => { const id = getStoredThemeId(); applyTheme(id); committedRef.current = id; - setCurrentLabel(LABELS[id] ?? ""); + syncLabels(id); }; document.addEventListener("astro:after-swap", handleSwap); @@ -34,7 +38,7 @@ export default function ThemeSwitcher() { }; }, []); - const handleClick = () => { + function animateTransition(nextId: string) { if (animatingRef.current) return; animatingRef.current = true; @@ -51,10 +55,9 @@ export default function ThemeSwitcher() { mask.style.visibility = "visible"; mask.style.transition = "none"; - const next = getNextTheme(committedRef.current); - applyTheme(next.id); - committedRef.current = next.id; - setCurrentLabel(LABELS[next.id] ?? ""); + applyTheme(nextId); + committedRef.current = nextId; + syncLabels(nextId); mask.offsetHeight; @@ -69,6 +72,18 @@ export default function ThemeSwitcher() { }; mask.addEventListener("transitionend", onEnd); + } + + const handleFamilyClick = (e: React.MouseEvent) => { + e.stopPropagation(); + const next = getNextFamily(committedRef.current); + animateTransition(next.id); + }; + + const handleVariantClick = (e: React.MouseEvent) => { + e.stopPropagation(); + const next = getNextVariant(committedRef.current); + animateTransition(next.id); }; return ( @@ -77,14 +92,24 @@ export default function ThemeSwitcher() { className="fixed bottom-4 right-4 z-[101] pointer-events-auto hidden lg:block" onMouseEnter={() => setHovering(true)} onMouseLeave={() => setHovering(false)} - onClick={handleClick} - style={{ cursor: "pointer" }} > - {currentLabel} + + · +
diff --git a/src/lib/themes/engine.ts b/src/lib/themes/engine.ts index deaadb3..96408f5 100644 --- a/src/lib/themes/engine.ts +++ b/src/lib/themes/engine.ts @@ -1,4 +1,4 @@ -import { THEMES, DEFAULT_THEME_ID } from "@/lib/themes"; +import { THEMES, FAMILIES, DEFAULT_THEME_ID } from "@/lib/themes"; import { CSS_PROPS } from "@/lib/themes/props"; import type { Theme } from "@/lib/themes/types"; @@ -11,6 +11,26 @@ export function saveTheme(id: string): void { localStorage.setItem("theme", id); } +/** Cycle to the next theme family, jumping to its default variant. */ +export function getNextFamily(currentId: string): Theme { + const current = THEMES[currentId]; + const familyId = current?.family ?? FAMILIES[0].id; + const idx = FAMILIES.findIndex((f) => f.id === familyId); + const next = FAMILIES[(idx + 1) % FAMILIES.length]; + return THEMES[next.default]; +} + +/** Cycle to the next variant within the current family. */ +export function getNextVariant(currentId: string): Theme { + const current = THEMES[currentId]; + if (!current) return Object.values(THEMES)[0]; + const family = FAMILIES.find((f) => f.id === current.family); + if (!family) return current; + const idx = family.themes.findIndex((t) => t.id === currentId); + return family.themes[(idx + 1) % family.themes.length]; +} + +// Keep for backward compat (cycles all themes linearly) export function getNextTheme(currentId: string): Theme { const list = Object.values(THEMES); const idx = list.findIndex((t) => t.id === currentId); @@ -27,7 +47,6 @@ export function previewTheme(id: string): void { root.style.setProperty(prop, theme.colors[key]); } - document.dispatchEvent(new CustomEvent("theme-changed", { detail: { id } })); } diff --git a/src/lib/themes/families/catppuccin.ts b/src/lib/themes/families/catppuccin.ts new file mode 100644 index 0000000..9e7a44d --- /dev/null +++ b/src/lib/themes/families/catppuccin.ts @@ -0,0 +1,94 @@ +import type { Theme, ThemeFamily } from "../types"; + +// Catppuccin — warm pastel palette. Each flavor has its own accent colors. + +const mocha: Theme = { + id: "catppuccin-mocha", + family: "catppuccin", + label: "mocha", + name: "Catppuccin Mocha", + type: "dark", + colors: { + background: "30 30 46", + foreground: "205 214 244", + red: "243 139 168", redBright: "246 166 190", + orange: "250 179 135", orangeBright: "252 200 170", + green: "166 227 161", greenBright: "190 236 186", + yellow: "249 226 175", yellowBright: "251 235 200", + blue: "137 180 250", blueBright: "172 202 251", + purple: "203 166 247", purpleBright: "220 192 249", + aqua: "148 226 213", aquaBright: "180 236 228", + surface: "49 50 68", + }, + canvasPalette: [[243,139,168],[166,227,161],[249,226,175],[137,180,250],[203,166,247],[148,226,213]], +}; + +const macchiato: Theme = { + id: "catppuccin-macchiato", + family: "catppuccin", + label: "macchiato", + name: "Catppuccin Macchiato", + type: "dark", + colors: { + background: "36 39 58", + foreground: "202 211 245", + red: "237 135 150", redBright: "242 167 180", + orange: "245 169 127", orangeBright: "248 192 165", + green: "166 218 149", greenBright: "190 232 180", + yellow: "238 212 159", yellowBright: "243 226 190", + blue: "138 173 244", blueBright: "170 198 247", + purple: "198 160 246", purpleBright: "218 190 249", + aqua: "139 213 202", aquaBright: "175 228 220", + surface: "54 58 79", + }, + canvasPalette: [[237,135,150],[166,218,149],[238,212,159],[138,173,244],[198,160,246],[139,213,202]], +}; + +const frappe: Theme = { + id: "catppuccin-frappe", + family: "catppuccin", + label: "frappé", + name: "Catppuccin Frappé", + type: "dark", + colors: { + background: "48 52 70", + foreground: "198 208 245", + red: "231 130 132", redBright: "238 160 162", + orange: "239 159 118", orangeBright: "244 185 158", + green: "166 209 137", greenBright: "190 222 172", + yellow: "229 200 144", yellowBright: "237 216 178", + blue: "140 170 238", blueBright: "172 196 242", + purple: "202 158 230", purpleBright: "218 186 238", + aqua: "129 200 190", aquaBright: "168 216 208", + surface: "65 69 89", + }, + canvasPalette: [[231,130,132],[166,209,137],[229,200,144],[140,170,238],[202,158,230],[129,200,190]], +}; + +const latte: Theme = { + id: "catppuccin-latte", + family: "catppuccin", + label: "latte", + name: "Catppuccin Latte", + type: "light", + colors: { + background: "239 241 245", + foreground: "76 79 105", + red: "210 15 57", redBright: "228 50 82", + orange: "254 100 11", orangeBright: "254 135 60", + green: "64 160 43", greenBright: "85 180 65", + yellow: "223 142 29", yellowBright: "236 170 60", + blue: "30 102 245", blueBright: "70 130 248", + purple: "136 57 239", purpleBright: "162 95 244", + aqua: "23 146 153", aquaBright: "55 168 175", + surface: "204 208 218", + }, + canvasPalette: [[210,15,57],[64,160,43],[223,142,29],[30,102,245],[136,57,239],[23,146,153]], +}; + +export const catppuccin: ThemeFamily = { + id: "catppuccin", + name: "Catppuccin", + themes: [mocha, macchiato, frappe, latte], + default: "catppuccin-mocha", +}; diff --git a/src/lib/themes/families/darkbox.ts b/src/lib/themes/families/darkbox.ts new file mode 100644 index 0000000..cc46549 --- /dev/null +++ b/src/lib/themes/families/darkbox.ts @@ -0,0 +1,71 @@ +import type { Theme, ThemeFamily } from "../types"; + +const classic: Theme = { + id: "darkbox", + family: "darkbox", + label: "classic", + name: "Darkbox Classic", + type: "dark", + colors: { + background: "0 0 0", + foreground: "235 219 178", + red: "251 73 52", redBright: "255 110 85", + orange: "254 128 25", orangeBright: "255 165 65", + green: "184 187 38", greenBright: "210 215 70", + yellow: "250 189 47", yellowBright: "255 215 85", + blue: "131 165 152", blueBright: "165 195 180", + purple: "211 134 155", purpleBright: "235 165 180", + aqua: "142 192 124", aquaBright: "175 220 160", + surface: "60 56 54", + }, + canvasPalette: [[251,73,52],[184,187,38],[250,189,47],[131,165,152],[211,134,155],[142,192,124]], +}; + +const retro: Theme = { + id: "darkbox-retro", + family: "darkbox", + label: "retro", + name: "Darkbox Retro", + type: "dark", + colors: { + background: "0 0 0", + foreground: "189 174 147", + red: "204 36 29", redBright: "251 73 52", + orange: "214 93 14", orangeBright: "254 128 25", + green: "152 151 26", greenBright: "184 187 38", + yellow: "215 153 33", yellowBright: "250 189 47", + blue: "69 133 136", blueBright: "131 165 152", + purple: "177 98 134", purpleBright: "211 134 155", + aqua: "104 157 106", aquaBright: "142 192 124", + surface: "60 56 54", + }, + canvasPalette: [[204,36,29],[152,151,26],[215,153,33],[69,133,136],[177,98,134],[104,157,106]], +}; + +const dim: Theme = { + id: "darkbox-dim", + family: "darkbox", + label: "dim", + name: "Darkbox Dim", + type: "dark", + colors: { + background: "0 0 0", + foreground: "168 153 132", + red: "157 0 6", redBright: "204 36 29", + orange: "175 58 3", orangeBright: "214 93 14", + green: "121 116 14", greenBright: "152 151 26", + yellow: "181 118 20", yellowBright: "215 153 33", + blue: "7 102 120", blueBright: "69 133 136", + purple: "143 63 113", purpleBright: "177 98 134", + aqua: "66 123 88", aquaBright: "104 157 106", + surface: "60 56 54", + }, + canvasPalette: [[157,0,6],[121,116,14],[181,118,20],[7,102,120],[143,63,113],[66,123,88]], +}; + +export const darkbox: ThemeFamily = { + id: "darkbox", + name: "Darkbox", + themes: [classic, retro, dim], + default: "darkbox-retro", +}; diff --git a/src/lib/themes/families/everforest.ts b/src/lib/themes/families/everforest.ts new file mode 100644 index 0000000..3b4d622 --- /dev/null +++ b/src/lib/themes/families/everforest.ts @@ -0,0 +1,70 @@ +import type { Theme, ThemeFamily } from "../types"; + +// Everforest — warm green-toned palette. Same accents across +// hard/medium/soft, only backgrounds change. + +const accents = { + red: "230 126 128", redBright: "240 155 157", + orange: "230 152 117", orangeBright: "240 177 150", + green: "167 192 128", greenBright: "190 210 160", + yellow: "219 188 127", yellowBright: "233 208 163", + blue: "127 187 179", blueBright: "160 208 200", + purple: "214 153 182", purpleBright: "228 180 202", + aqua: "131 192 146", aquaBright: "162 212 176", +} as const; + +const palette: [number, number, number][] = [ + [230,126,128],[167,192,128],[219,188,127],[127,187,179],[214,153,182],[131,192,146], +]; + +const hard: Theme = { + id: "everforest-hard", + family: "everforest", + label: "hard", + name: "Everforest Hard", + type: "dark", + colors: { + background: "39 46 51", + foreground: "211 198 170", + ...accents, + surface: "55 65 69", + }, + canvasPalette: palette, +}; + +const medium: Theme = { + id: "everforest-medium", + family: "everforest", + label: "medium", + name: "Everforest Medium", + type: "dark", + colors: { + background: "45 53 59", + foreground: "211 198 170", + ...accents, + surface: "61 72 77", + }, + canvasPalette: palette, +}; + +const soft: Theme = { + id: "everforest-soft", + family: "everforest", + label: "soft", + name: "Everforest Soft", + type: "dark", + colors: { + background: "52 63 68", + foreground: "211 198 170", + ...accents, + surface: "68 80 85", + }, + canvasPalette: palette, +}; + +export const everforest: ThemeFamily = { + id: "everforest", + name: "Everforest", + themes: [hard, medium, soft], + default: "everforest-medium", +}; diff --git a/src/lib/themes/families/gruvbox.ts b/src/lib/themes/families/gruvbox.ts new file mode 100644 index 0000000..7bafc86 --- /dev/null +++ b/src/lib/themes/families/gruvbox.ts @@ -0,0 +1,70 @@ +import type { Theme, ThemeFamily } from "../types"; + +// Original gruvbox palette — same accents across hard/medium/soft, +// only background and surface change. + +const accents = { + red: "204 36 29", redBright: "251 73 52", + orange: "214 93 14", orangeBright: "254 128 25", + green: "152 151 26", greenBright: "184 187 38", + yellow: "215 153 33", yellowBright: "250 189 47", + blue: "69 133 136", blueBright: "131 165 152", + purple: "177 98 134", purpleBright: "211 134 155", + aqua: "104 157 106", aquaBright: "142 192 124", +} as const; + +const palette: [number, number, number][] = [ + [204,36,29],[152,151,26],[215,153,33],[69,133,136],[177,98,134],[104,157,106], +]; + +const hard: Theme = { + id: "gruvbox-hard", + family: "gruvbox", + label: "hard", + name: "Gruvbox Hard", + type: "dark", + colors: { + background: "29 32 33", + foreground: "235 219 178", + ...accents, + surface: "60 56 54", + }, + canvasPalette: palette, +}; + +const medium: Theme = { + id: "gruvbox-medium", + family: "gruvbox", + label: "medium", + name: "Gruvbox Medium", + type: "dark", + colors: { + background: "40 40 40", + foreground: "235 219 178", + ...accents, + surface: "60 56 54", + }, + canvasPalette: palette, +}; + +const soft: Theme = { + id: "gruvbox-soft", + family: "gruvbox", + label: "soft", + name: "Gruvbox Soft", + type: "dark", + colors: { + background: "50 48 47", + foreground: "235 219 178", + ...accents, + surface: "80 73 69", + }, + canvasPalette: palette, +}; + +export const gruvbox: ThemeFamily = { + id: "gruvbox", + name: "Gruvbox", + themes: [hard, medium, soft], + default: "gruvbox-medium", +}; diff --git a/src/lib/themes/families/kanagawa.ts b/src/lib/themes/families/kanagawa.ts new file mode 100644 index 0000000..561d2fb --- /dev/null +++ b/src/lib/themes/families/kanagawa.ts @@ -0,0 +1,74 @@ +import type { Theme, ThemeFamily } from "../types"; + +// Kanagawa — inspired by Katsushika Hokusai's paintings. +// Each variant has its own distinct palette. + +const wave: Theme = { + id: "kanagawa-wave", + family: "kanagawa", + label: "wave", + name: "Kanagawa Wave", + type: "dark", + colors: { + background: "31 31 40", + foreground: "220 215 186", + red: "195 64 67", redBright: "255 93 98", + orange: "255 160 102", orangeBright: "255 184 140", + green: "118 148 106", greenBright: "152 187 108", + yellow: "192 163 110", yellowBright: "230 195 132", + blue: "126 156 216", blueBright: "127 180 202", + purple: "149 127 184", purpleBright: "175 158 206", + aqua: "106 149 137", aquaBright: "122 168 159", + surface: "42 42 55", + }, + canvasPalette: [[195,64,67],[118,148,106],[192,163,110],[126,156,216],[149,127,184],[106,149,137]], +}; + +const dragon: Theme = { + id: "kanagawa-dragon", + family: "kanagawa", + label: "dragon", + name: "Kanagawa Dragon", + type: "dark", + colors: { + background: "24 22 22", + foreground: "197 201 197", + red: "196 116 110", redBright: "195 64 67", + orange: "182 146 123", orangeBright: "255 160 102", + green: "135 169 135", greenBright: "152 187 108", + yellow: "196 178 138", yellowBright: "230 195 132", + blue: "139 164 176", blueBright: "126 156 216", + purple: "162 146 163", purpleBright: "149 127 184", + aqua: "142 164 162", aquaBright: "122 168 159", + surface: "40 39 39", + }, + canvasPalette: [[196,116,110],[135,169,135],[196,178,138],[139,164,176],[162,146,163],[142,164,162]], +}; + +const lotus: Theme = { + id: "kanagawa-lotus", + family: "kanagawa", + label: "lotus", + name: "Kanagawa Lotus", + type: "light", + colors: { + background: "242 236 188", + foreground: "84 84 100", + red: "200 64 83", redBright: "215 71 75", + orange: "233 138 0", orangeBright: "245 160 30", + green: "111 137 78", greenBright: "130 158 98", + yellow: "222 152 0", yellowBright: "240 178 40", + blue: "77 105 155", blueBright: "93 87 163", + purple: "98 76 131", purpleBright: "118 100 155", + aqua: "89 123 117", aquaBright: "110 145 138", + surface: "228 215 148", + }, + canvasPalette: [[200,64,83],[111,137,78],[222,152,0],[77,105,155],[98,76,131],[89,123,117]], +}; + +export const kanagawa: ThemeFamily = { + id: "kanagawa", + name: "Kanagawa", + themes: [wave, dragon, lotus], + default: "kanagawa-wave", +}; diff --git a/src/lib/themes/families/rosepine.ts b/src/lib/themes/families/rosepine.ts new file mode 100644 index 0000000..084dc30 --- /dev/null +++ b/src/lib/themes/families/rosepine.ts @@ -0,0 +1,74 @@ +import type { Theme, ThemeFamily } from "../types"; + +// Rosé Pine — soft muted palette inspired by the natural world. +// Only 6 accent hues; aqua is derived between pine and foam. + +const main: Theme = { + id: "rosepine-main", + family: "rosepine", + label: "main", + name: "Rosé Pine", + type: "dark", + colors: { + background: "25 23 36", + foreground: "224 222 244", + red: "235 111 146", redBright: "241 145 174", + orange: "235 188 186", orangeBright: "240 208 206", + green: "49 116 143", greenBright: "78 140 165", + yellow: "246 193 119", yellowBright: "249 212 160", + blue: "156 207 216", blueBright: "180 222 229", + purple: "196 167 231", purpleBright: "214 190 239", + aqua: "100 170 185", aquaBright: "135 192 205", + surface: "38 35 58", + }, + canvasPalette: [[235,111,146],[49,116,143],[246,193,119],[156,207,216],[196,167,231],[235,188,186]], +}; + +const moon: Theme = { + id: "rosepine-moon", + family: "rosepine", + label: "moon", + name: "Rosé Pine Moon", + type: "dark", + colors: { + background: "35 33 54", + foreground: "224 222 244", + red: "235 111 146", redBright: "241 145 174", + orange: "234 154 151", orangeBright: "241 186 184", + green: "62 143 176", greenBright: "90 165 195", + yellow: "246 193 119", yellowBright: "249 212 160", + blue: "156 207 216", blueBright: "180 222 229", + purple: "196 167 231", purpleBright: "214 190 239", + aqua: "110 178 196", aquaBright: "140 196 210", + surface: "57 53 82", + }, + canvasPalette: [[235,111,146],[62,143,176],[246,193,119],[156,207,216],[196,167,231],[234,154,151]], +}; + +const dawn: Theme = { + id: "rosepine-dawn", + family: "rosepine", + label: "dawn", + name: "Rosé Pine Dawn", + type: "light", + colors: { + background: "250 244 237", + foreground: "87 82 121", + red: "180 99 122", redBright: "200 120 142", + orange: "215 130 126", orangeBright: "230 155 152", + green: "40 105 131", greenBright: "60 125 150", + yellow: "234 157 52", yellowBright: "242 180 85", + blue: "86 148 159", blueBright: "110 168 178", + purple: "144 122 169", purpleBright: "168 148 188", + aqua: "62 128 146", aquaBright: "85 150 165", + surface: "242 233 225", + }, + canvasPalette: [[180,99,122],[40,105,131],[234,157,52],[86,148,159],[144,122,169],[215,130,126]], +}; + +export const rosepine: ThemeFamily = { + id: "rosepine", + name: "Rosé Pine", + themes: [main, moon, dawn], + default: "rosepine-main", +}; diff --git a/src/lib/themes/index.ts b/src/lib/themes/index.ts index d089ea4..321bab9 100644 --- a/src/lib/themes/index.ts +++ b/src/lib/themes/index.ts @@ -1,58 +1,26 @@ -import type { Theme } from "./types"; +import type { Theme, ThemeFamily } from "./types"; +import { darkbox } from "./families/darkbox"; +import { gruvbox } from "./families/gruvbox"; +import { everforest } from "./families/everforest"; +import { catppuccin } from "./families/catppuccin"; +import { rosepine } from "./families/rosepine"; +import { kanagawa } from "./families/kanagawa"; export const DEFAULT_THEME_ID = "darkbox-retro"; -function theme( - id: string, - name: string, - type: "dark" | "light", - colors: Theme["colors"], - palette: [number, number, number][] -): Theme { - return { id, name, type, colors, canvasPalette: palette }; +export const FAMILIES: ThemeFamily[] = [ + darkbox, + gruvbox, + everforest, + catppuccin, + rosepine, + kanagawa, +]; + +// Flat lookup — backward compatible with all existing consumers +export const THEMES: Record = {}; +for (const family of FAMILIES) { + for (const theme of family.themes) { + THEMES[theme.id] = theme; + } } - -// Three darkbox variants from darkbox.nvim -// Classic (vivid) → Retro (muted) → Dim (deep) -// Each variant's "bright" is the next level up's base. - -export const THEMES: Record = { - darkbox: theme("darkbox", "Darkbox Classic", "dark", { - background: "0 0 0", - foreground: "235 219 178", - red: "251 73 52", redBright: "255 110 85", - orange: "254 128 25", orangeBright: "255 165 65", - green: "184 187 38", greenBright: "210 215 70", - yellow: "250 189 47", yellowBright: "255 215 85", - blue: "131 165 152", blueBright: "165 195 180", - purple: "211 134 155", purpleBright: "235 165 180", - aqua: "142 192 124", aquaBright: "175 220 160", - surface: "60 56 54", - }, [[251,73,52],[184,187,38],[250,189,47],[131,165,152],[211,134,155],[142,192,124]]), - - "darkbox-retro": theme("darkbox-retro", "Darkbox Retro", "dark", { - background: "0 0 0", - foreground: "189 174 147", - red: "204 36 29", redBright: "251 73 52", - orange: "214 93 14", orangeBright: "254 128 25", - green: "152 151 26", greenBright: "184 187 38", - yellow: "215 153 33", yellowBright: "250 189 47", - blue: "69 133 136", blueBright: "131 165 152", - purple: "177 98 134", purpleBright: "211 134 155", - aqua: "104 157 106", aquaBright: "142 192 124", - surface: "60 56 54", - }, [[204,36,29],[152,151,26],[215,153,33],[69,133,136],[177,98,134],[104,157,106]]), - - "darkbox-dim": theme("darkbox-dim", "Darkbox Dim", "dark", { - background: "0 0 0", - foreground: "168 153 132", - red: "157 0 6", redBright: "204 36 29", - orange: "175 58 3", orangeBright: "214 93 14", - green: "121 116 14", greenBright: "152 151 26", - yellow: "181 118 20", yellowBright: "215 153 33", - blue: "7 102 120", blueBright: "69 133 136", - purple: "143 63 113", purpleBright: "177 98 134", - aqua: "66 123 88", aquaBright: "104 157 106", - surface: "60 56 54", - }, [[157,0,6],[121,116,14],[181,118,20],[7,102,120],[143,63,113],[66,123,88]]), -}; diff --git a/src/lib/themes/types.ts b/src/lib/themes/types.ts index 9a9423d..dcadcae 100644 --- a/src/lib/themes/types.ts +++ b/src/lib/themes/types.ts @@ -20,8 +20,17 @@ export interface ThemeColors { export interface Theme { id: string; + family: string; + label: string; name: string; type: "dark" | "light"; colors: ThemeColors; canvasPalette: [number, number, number][]; } + +export interface ThemeFamily { + id: string; + name: string; + themes: Theme[]; + default: string; +} diff --git a/src/style/globals.css b/src/style/globals.css index 99bdd36..15f1cf2 100644 --- a/src/style/globals.css +++ b/src/style/globals.css @@ -47,19 +47,19 @@ to bottom, transparent 0px, transparent 2px, - rgba(0, 0, 0, 0.12) 2px, - rgba(0, 0, 0, 0.12) 4px + rgb(var(--color-foreground) / 0.06) 2px, + rgb(var(--color-foreground) / 0.06) 4px ); animation: crt-scroll 12s linear infinite; z-index: 1; } .crt-bloom { - box-shadow: inset 0 0 100px 30px rgba(0, 0, 0, 0.3); + box-shadow: inset 0 0 100px 30px rgb(var(--color-background) / 0.3); background: radial-gradient( ellipse at center, transparent 50%, - rgba(0, 0, 0, 0.25) 100% + rgb(var(--color-background) / 0.25) 100% ); z-index: 2; }