mirror of
https://github.com/timmypidashev/web.git
synced 2026-04-14 02:53:51 +00:00
Add theme families
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { X, ExternalLink } from "lucide-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 { applyTheme, getStoredThemeId } from "@/lib/themes/engine";
|
||||||
import { ANIMATION_IDS, ANIMATION_LABELS, type AnimationId } from "@/lib/animations";
|
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" },
|
{ 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 = [
|
const animOptions = [
|
||||||
{ id: "shuffle", color: "text-red-bright", activeBg: "bg-red-bright/15", activeBorder: "border-red-bright/40" },
|
{ 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" },
|
{ 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" },
|
{ 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 }) {
|
export function SettingsSheet({ open, onClose }: { open: boolean; onClose: () => void }) {
|
||||||
const [currentTheme, setCurrentTheme] = useState(getStoredThemeId());
|
const [currentTheme, setCurrentTheme] = useState(getStoredThemeId());
|
||||||
const [currentAnim, setCurrentAnim] = useState<string>("shuffle");
|
const [currentAnim, setCurrentAnim] = useState<string>("shuffle");
|
||||||
@@ -37,7 +42,6 @@ export function SettingsSheet({ open, onClose }: { open: boolean; onClose: () =>
|
|||||||
const handleTheme = (id: string) => {
|
const handleTheme = (id: string) => {
|
||||||
applyTheme(id);
|
applyTheme(id);
|
||||||
setCurrentTheme(id);
|
setCurrentTheme(id);
|
||||||
onClose();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAnim = (id: string) => {
|
const handleAnim = (id: string) => {
|
||||||
@@ -48,6 +52,8 @@ export function SettingsSheet({ open, onClose }: { open: boolean; onClose: () =>
|
|||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const currentFamily = THEMES[currentTheme]?.family ?? FAMILIES[0].id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Backdrop */}
|
{/* 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 ${
|
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"
|
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" }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between px-5 pt-4 pb-2">
|
<div className="flex items-center justify-between px-5 pt-4 pb-2">
|
||||||
<span className="text-foreground/80 font-bold text-lg">Settings</span>
|
<span className="text-foreground/80 font-bold text-lg">Settings</span>
|
||||||
@@ -76,20 +82,42 @@ export function SettingsSheet({ open, onClose }: { open: boolean; onClose: () =>
|
|||||||
{/* Theme */}
|
{/* Theme */}
|
||||||
<div>
|
<div>
|
||||||
<div className="text-foreground/50 text-xs uppercase tracking-wider mb-2">Theme</div>
|
<div className="text-foreground/50 text-xs uppercase tracking-wider mb-2">Theme</div>
|
||||||
<div className="flex gap-2">
|
|
||||||
{themeOptions.map((opt) => (
|
{/* Family selector */}
|
||||||
|
<div className="flex gap-2 mb-3 overflow-x-auto pb-1">
|
||||||
|
{FAMILIES.map((family) => (
|
||||||
<button
|
<button
|
||||||
key={opt.id}
|
key={family.id}
|
||||||
onClick={() => handleTheme(opt.id)}
|
onClick={() => handleTheme(family.default)}
|
||||||
|
className={`flex-shrink-0 px-3 py-1.5 rounded-lg text-xs font-medium border transition-colors duration-200 ${
|
||||||
|
currentFamily === family.id
|
||||||
|
? "bg-foreground/10 text-foreground/80 border-foreground/20"
|
||||||
|
: "bg-foreground/5 text-foreground/30 border-transparent hover:text-foreground/50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{family.name}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Variant selector for current family */}
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{FAMILIES.find((f) => f.id === currentFamily)?.themes.map((theme, i) => {
|
||||||
|
const style = variantColors[i % variantColors.length];
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={theme.id}
|
||||||
|
onClick={() => handleTheme(theme.id)}
|
||||||
className={`flex-1 py-2.5 rounded-lg text-sm font-medium border transition-colors duration-200 ${
|
className={`flex-1 py-2.5 rounded-lg text-sm font-medium border transition-colors duration-200 ${
|
||||||
currentTheme === opt.id
|
currentTheme === theme.id
|
||||||
? `${opt.activeBg} ${opt.color} ${opt.activeBorder}`
|
? `${style.activeBg} ${style.color} ${style.activeBorder}`
|
||||||
: "bg-foreground/5 text-foreground/40 border-transparent hover:text-foreground/60"
|
: "bg-foreground/5 text-foreground/40 border-transparent hover:text-foreground/60"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{opt.label}
|
{theme.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,35 @@
|
|||||||
import { useRef, useState, useEffect } from "react";
|
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 FADE_DURATION = 300;
|
||||||
|
|
||||||
const LABELS: Record<string, string> = {
|
|
||||||
darkbox: "classic",
|
|
||||||
"darkbox-retro": "retro",
|
|
||||||
"darkbox-dim": "dim",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ThemeSwitcher() {
|
export default function ThemeSwitcher() {
|
||||||
const [hovering, setHovering] = useState(false);
|
const [hovering, setHovering] = useState(false);
|
||||||
const [currentLabel, setCurrentLabel] = useState("");
|
const [familyName, setFamilyName] = useState("");
|
||||||
|
const [variantLabel, setVariantLabel] = useState("");
|
||||||
|
|
||||||
const maskRef = useRef<HTMLDivElement>(null);
|
const maskRef = useRef<HTMLDivElement>(null);
|
||||||
const animatingRef = useRef(false);
|
const animatingRef = useRef(false);
|
||||||
const committedRef = useRef("");
|
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(() => {
|
useEffect(() => {
|
||||||
committedRef.current = getStoredThemeId();
|
committedRef.current = getStoredThemeId();
|
||||||
setCurrentLabel(LABELS[committedRef.current] ?? "");
|
syncLabels(committedRef.current);
|
||||||
|
|
||||||
const handleSwap = () => {
|
const handleSwap = () => {
|
||||||
const id = getStoredThemeId();
|
const id = getStoredThemeId();
|
||||||
applyTheme(id);
|
applyTheme(id);
|
||||||
committedRef.current = id;
|
committedRef.current = id;
|
||||||
setCurrentLabel(LABELS[id] ?? "");
|
syncLabels(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("astro:after-swap", handleSwap);
|
document.addEventListener("astro:after-swap", handleSwap);
|
||||||
@@ -34,7 +38,7 @@ export default function ThemeSwitcher() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleClick = () => {
|
function animateTransition(nextId: string) {
|
||||||
if (animatingRef.current) return;
|
if (animatingRef.current) return;
|
||||||
animatingRef.current = true;
|
animatingRef.current = true;
|
||||||
|
|
||||||
@@ -51,10 +55,9 @@ export default function ThemeSwitcher() {
|
|||||||
mask.style.visibility = "visible";
|
mask.style.visibility = "visible";
|
||||||
mask.style.transition = "none";
|
mask.style.transition = "none";
|
||||||
|
|
||||||
const next = getNextTheme(committedRef.current);
|
applyTheme(nextId);
|
||||||
applyTheme(next.id);
|
committedRef.current = nextId;
|
||||||
committedRef.current = next.id;
|
syncLabels(nextId);
|
||||||
setCurrentLabel(LABELS[next.id] ?? "");
|
|
||||||
|
|
||||||
mask.offsetHeight;
|
mask.offsetHeight;
|
||||||
|
|
||||||
@@ -69,6 +72,18 @@ export default function ThemeSwitcher() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mask.addEventListener("transitionend", onEnd);
|
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 (
|
return (
|
||||||
@@ -77,14 +92,24 @@ export default function ThemeSwitcher() {
|
|||||||
className="fixed bottom-4 right-4 z-[101] pointer-events-auto hidden lg:block"
|
className="fixed bottom-4 right-4 z-[101] pointer-events-auto hidden lg:block"
|
||||||
onMouseEnter={() => setHovering(true)}
|
onMouseEnter={() => setHovering(true)}
|
||||||
onMouseLeave={() => setHovering(false)}
|
onMouseLeave={() => setHovering(false)}
|
||||||
onClick={handleClick}
|
|
||||||
style={{ cursor: "pointer" }}
|
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="text-foreground font-bold text-sm select-none transition-opacity duration-200"
|
className="text-foreground font-bold text-sm select-none transition-opacity duration-200 inline-flex items-center gap-0"
|
||||||
style={{ opacity: hovering ? 0.8 : 0.15 }}
|
style={{ opacity: hovering ? 0.8 : 0.15 }}
|
||||||
>
|
>
|
||||||
{currentLabel}
|
<button
|
||||||
|
onClick={handleFamilyClick}
|
||||||
|
className="hover:text-yellow-bright transition-colors duration-150 cursor-pointer bg-transparent border-none p-0 font-bold text-sm text-inherit"
|
||||||
|
>
|
||||||
|
{familyName}
|
||||||
|
</button>
|
||||||
|
<span className="mx-1 opacity-40">·</span>
|
||||||
|
<button
|
||||||
|
onClick={handleVariantClick}
|
||||||
|
className="hover:text-blue-bright transition-colors duration-150 cursor-pointer bg-transparent border-none p-0 font-bold text-sm text-inherit"
|
||||||
|
>
|
||||||
|
{variantLabel}
|
||||||
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -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 { CSS_PROPS } from "@/lib/themes/props";
|
||||||
import type { Theme } from "@/lib/themes/types";
|
import type { Theme } from "@/lib/themes/types";
|
||||||
|
|
||||||
@@ -11,6 +11,26 @@ export function saveTheme(id: string): void {
|
|||||||
localStorage.setItem("theme", id);
|
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 {
|
export function getNextTheme(currentId: string): Theme {
|
||||||
const list = Object.values(THEMES);
|
const list = Object.values(THEMES);
|
||||||
const idx = list.findIndex((t) => t.id === currentId);
|
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]);
|
root.style.setProperty(prop, theme.colors[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
document.dispatchEvent(new CustomEvent("theme-changed", { detail: { id } }));
|
document.dispatchEvent(new CustomEvent("theme-changed", { detail: { id } }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
94
src/lib/themes/families/catppuccin.ts
Normal file
94
src/lib/themes/families/catppuccin.ts
Normal file
@@ -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",
|
||||||
|
};
|
||||||
71
src/lib/themes/families/darkbox.ts
Normal file
71
src/lib/themes/families/darkbox.ts
Normal file
@@ -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",
|
||||||
|
};
|
||||||
70
src/lib/themes/families/everforest.ts
Normal file
70
src/lib/themes/families/everforest.ts
Normal file
@@ -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",
|
||||||
|
};
|
||||||
70
src/lib/themes/families/gruvbox.ts
Normal file
70
src/lib/themes/families/gruvbox.ts
Normal file
@@ -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",
|
||||||
|
};
|
||||||
74
src/lib/themes/families/kanagawa.ts
Normal file
74
src/lib/themes/families/kanagawa.ts
Normal file
@@ -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",
|
||||||
|
};
|
||||||
74
src/lib/themes/families/rosepine.ts
Normal file
74
src/lib/themes/families/rosepine.ts
Normal file
@@ -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",
|
||||||
|
};
|
||||||
@@ -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";
|
export const DEFAULT_THEME_ID = "darkbox-retro";
|
||||||
|
|
||||||
function theme(
|
export const FAMILIES: ThemeFamily[] = [
|
||||||
id: string,
|
darkbox,
|
||||||
name: string,
|
gruvbox,
|
||||||
type: "dark" | "light",
|
everforest,
|
||||||
colors: Theme["colors"],
|
catppuccin,
|
||||||
palette: [number, number, number][]
|
rosepine,
|
||||||
): Theme {
|
kanagawa,
|
||||||
return { id, name, type, colors, canvasPalette: palette };
|
];
|
||||||
|
|
||||||
|
// Flat lookup — backward compatible with all existing consumers
|
||||||
|
export const THEMES: Record<string, Theme> = {};
|
||||||
|
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<string, Theme> = {
|
|
||||||
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]]),
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -20,8 +20,17 @@ export interface ThemeColors {
|
|||||||
|
|
||||||
export interface Theme {
|
export interface Theme {
|
||||||
id: string;
|
id: string;
|
||||||
|
family: string;
|
||||||
|
label: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: "dark" | "light";
|
type: "dark" | "light";
|
||||||
colors: ThemeColors;
|
colors: ThemeColors;
|
||||||
canvasPalette: [number, number, number][];
|
canvasPalette: [number, number, number][];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ThemeFamily {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
themes: Theme[];
|
||||||
|
default: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,19 +47,19 @@
|
|||||||
to bottom,
|
to bottom,
|
||||||
transparent 0px,
|
transparent 0px,
|
||||||
transparent 2px,
|
transparent 2px,
|
||||||
rgba(0, 0, 0, 0.12) 2px,
|
rgb(var(--color-foreground) / 0.06) 2px,
|
||||||
rgba(0, 0, 0, 0.12) 4px
|
rgb(var(--color-foreground) / 0.06) 4px
|
||||||
);
|
);
|
||||||
animation: crt-scroll 12s linear infinite;
|
animation: crt-scroll 12s linear infinite;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.crt-bloom {
|
.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(
|
background: radial-gradient(
|
||||||
ellipse at center,
|
ellipse at center,
|
||||||
transparent 50%,
|
transparent 50%,
|
||||||
rgba(0, 0, 0, 0.25) 100%
|
rgb(var(--color-background) / 0.25) 100%
|
||||||
);
|
);
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user