diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index d65690e..b72810c 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -2,6 +2,22 @@ All notable changes to `create-start-kit-dev` will be documented in this file. +## [0.1.8] - 2026-02-22 + +### Added + +- Theme customization flags for `create` command: `--theme`, `--base-color`, `--radius`, `--font` +- 21 built-in color themes (neutral, stone, zinc, gray, amber, blue, cyan, emerald, fuchsia, green, indigo, lime, orange, pink, purple, red, rose, sky, teal, violet, yellow) +- 4 base colors, 5 radius presets, 3 font options (Inter, Geist Sans, System) +- Generated `app.css` includes full CSS variable blocks, `@theme inline`, `@layer base`, and marquee utilities +- Graceful fallback to defaults for invalid flag values with warning messages +- Unit tests for theme parsing and CSS generation (24 tests) + +### Changed + +- `create` command now skips `--` prefixed args when detecting project name +- Updated usage help to document theme options + ## [0.1.6] - 2026-02-19 ### Fixed diff --git a/packages/cli/package.json b/packages/cli/package.json index 47b6d40..77a3ce3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "create-start-kit-dev", - "version": "0.1.7", + "version": "0.1.8", "description": "CLI for scaffolding and configuring Start Kit projects", "type": "module", "license": "MIT", diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 269a039..4830e1d 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -14,6 +14,7 @@ import { runEnv } from "./phases/env"; import { runFeatures } from "./phases/features"; import { runInfra } from "./phases/infra"; import { runScaffold } from "./phases/scaffold"; +import { applyTheme, parseThemeArgs } from "./theme/theme-apply"; const PHASES: { key: Phase; @@ -126,22 +127,34 @@ async function runWizard(state: SetupState): Promise { function showUsage(): void { console.log("Usage:"); console.log( - " bunx create-start-kit-dev create [project-name] Create a new project" + " bunx create-start-kit-dev create [project-name] [options] Create a new project" ); console.log( - " bunx create-start-kit-dev init [--step ] Setup existing project" + " bunx create-start-kit-dev init [--step ] Setup existing project" ); console.log(""); + console.log("Theme options:"); + console.log(" --theme Theme color (blue, red, green, purple, ...)"); + console.log(" --base-color Base color (neutral, stone, zinc, gray)"); + console.log(" --radius Border radius (none, sm, md, lg, xl)"); + console.log(" --font Font family (inter, geist, system)"); + console.log(""); console.log("Phases: branding, features, database, env, infra"); } async function handleCreate(args: string[]): Promise { - const projectName = args.at(1); + const firstArg = args.at(1); + const projectName = firstArg && !firstArg.startsWith("--") ? firstArg : undefined; intro("Start Kit — Create New Project"); const targetDir = await runScaffold(projectName); + const themeConfig = parseThemeArgs(args); + if (themeConfig) { + applyTheme(targetDir, themeConfig); + } + // Change working directory to the new project process.chdir(targetDir); diff --git a/packages/cli/src/theme/__tests__/theme-apply.test.ts b/packages/cli/src/theme/__tests__/theme-apply.test.ts new file mode 100644 index 0000000..c6c9bab --- /dev/null +++ b/packages/cli/src/theme/__tests__/theme-apply.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, it } from "bun:test"; +import { parseThemeArgs } from "../theme-apply"; + +describe("parseThemeArgs", () => { + it("returns null when no theme flags are present", () => { + expect(parseThemeArgs(["create", "my-app"])).toBeNull(); + }); + + it("parses all four flags", () => { + const result = parseThemeArgs([ + "create", + "my-app", + "--theme", + "blue", + "--base-color", + "zinc", + "--radius", + "md", + "--font", + "geist", + ]); + + expect(result).toEqual({ + theme: "blue", + baseColor: "zinc", + radius: "md", + font: "geist", + }); + }); + + it("uses defaults for missing flags when at least one flag is present", () => { + const result = parseThemeArgs(["create", "my-app", "--theme", "red"]); + + expect(result).toEqual({ + theme: "red", + baseColor: "neutral", + radius: "lg", + font: "inter", + }); + }); + + it("falls back to defaults for invalid theme value", () => { + const result = parseThemeArgs(["create", "--theme", "nonexistent"]); + + expect(result).not.toBeNull(); + expect(result!.theme).toBe("neutral"); + }); + + it("falls back to defaults for invalid base-color value", () => { + const result = parseThemeArgs(["create", "--base-color", "nope"]); + + expect(result).not.toBeNull(); + expect(result!.baseColor).toBe("neutral"); + }); + + it("falls back to defaults for invalid radius value", () => { + const result = parseThemeArgs(["create", "--radius", "huge"]); + + expect(result).not.toBeNull(); + expect(result!.radius).toBe("lg"); + }); + + it("falls back to defaults for invalid font value", () => { + const result = parseThemeArgs(["create", "--font", "comic-sans"]); + + expect(result).not.toBeNull(); + expect(result!.font).toBe("inter"); + }); + + it("handles flags without project name", () => { + const result = parseThemeArgs(["create", "--theme", "blue"]); + + expect(result).toEqual({ + theme: "blue", + baseColor: "neutral", + radius: "lg", + font: "inter", + }); + }); +}); diff --git a/packages/cli/src/theme/__tests__/theme-utils.test.ts b/packages/cli/src/theme/__tests__/theme-utils.test.ts new file mode 100644 index 0000000..90c3ac9 --- /dev/null +++ b/packages/cli/src/theme/__tests__/theme-utils.test.ts @@ -0,0 +1,102 @@ +import { describe, expect, it } from "bun:test"; +import { buildTheme, generateFullAppCss, hexToOklch } from "../theme-utils"; + +describe("buildTheme", () => { + it("returns base-only vars when theme is a base color", () => { + const result = buildTheme("neutral", "neutral"); + expect(result.light.background).toBe("oklch(1 0 0)"); + expect(result.light.primary).toBe("oklch(0.205 0 0)"); + }); + + it("merges base + accent when theme is an accent color", () => { + const result = buildTheme("neutral", "blue"); + // base background from neutral + expect(result.light.background).toBe("oklch(1 0 0)"); + // primary overridden by blue accent + expect(result.light.primary).toBe("oklch(0.488 0.243 264.376)"); + }); + + it("falls back to first theme when base color is invalid", () => { + const result = buildTheme("invalid", "blue"); + expect(result.light.background).toBe("oklch(1 0 0)"); + }); + + it("returns base-only when accent is not found", () => { + const result = buildTheme("zinc", "nonexistent"); + expect(result.light.primary).toBe("oklch(0.21 0.006 285.885)"); + }); +}); + +describe("hexToOklch", () => { + it("converts black", () => { + const result = hexToOklch("#000000"); + expect(result).toBe("oklch(0.000 0.000 0.0)"); + }); + + it("converts white", () => { + const result = hexToOklch("#ffffff"); + expect(result).toMatch(/^oklch\(1\.000 0\.000/); + }); + + it("returns null for invalid hex", () => { + expect(hexToOklch("not-a-color")).toBeNull(); + }); + + it("handles hex without hash", () => { + expect(hexToOklch("ff0000")).not.toBeNull(); + }); +}); + +describe("generateFullAppCss", () => { + const defaultConfig = { + baseColor: "neutral" as const, + theme: "blue" as const, + radius: "md" as const, + font: "inter" as const, + }; + + it("contains tailwind imports", () => { + const css = generateFullAppCss(defaultConfig); + expect(css).toContain('@import "tailwindcss"'); + expect(css).toContain('@import "tw-animate-css"'); + }); + + it("contains @theme inline block", () => { + const css = generateFullAppCss(defaultConfig); + expect(css).toContain("@theme inline"); + }); + + it("contains :root and .dark blocks", () => { + const css = generateFullAppCss(defaultConfig); + expect(css).toContain(":root {"); + expect(css).toContain(".dark {"); + }); + + it("contains @layer base block", () => { + const css = generateFullAppCss(defaultConfig); + expect(css).toContain("@layer base"); + }); + + it("contains marquee utilities block", () => { + const css = generateFullAppCss(defaultConfig); + expect(css).toContain("@layer utilities"); + expect(css).toContain("tech-marquee-scroll"); + expect(css).toContain("prefers-reduced-motion"); + }); + + it("includes geist font import when font is geist", () => { + const css = generateFullAppCss({ ...defaultConfig, font: "geist" }); + expect(css).toContain('@import "@fontsource-variable/geist"'); + expect(css).toContain('"Geist Variable", sans-serif'); + }); + + it("does not include geist import for inter font", () => { + const css = generateFullAppCss({ ...defaultConfig, font: "inter" }); + expect(css).not.toContain("fontsource-variable/geist"); + }); + + it("sets correct radius value", () => { + const css = generateFullAppCss({ ...defaultConfig, radius: "md" }); + expect(css).toContain("--radius: 0.5rem;"); + }); +}); diff --git a/packages/cli/src/theme/theme-apply.ts b/packages/cli/src/theme/theme-apply.ts new file mode 100644 index 0000000..06bcf43 --- /dev/null +++ b/packages/cli/src/theme/theme-apply.ts @@ -0,0 +1,90 @@ +import { writeFileSync } from "node:fs"; +import { log, spinner } from "@clack/prompts"; +import type { + BaseColorName, + FontOption, + RadiusPreset, + ThemeConfig, + ThemeName, +} from "./theme-types"; +import { DEFAULT_CONFIG, FONT_OPTIONS, RADIUS_VALUES, THEMES } from "./theme-registry"; +import { generateFullAppCss } from "./theme-utils"; + +const THEME_NAMES = THEMES.map((t) => t.name); +const RADIUS_KEYS = Object.keys(RADIUS_VALUES) as RadiusPreset[]; +const FONT_VALUES = FONT_OPTIONS.map((f) => f.value); + +function extractFlag(args: string[], flag: string): string | undefined { + const idx = args.indexOf(flag); + if (idx < 0 || idx + 1 >= args.length) return undefined; + return args[idx + 1]; +} + +export function parseThemeArgs(args: string[]): ThemeConfig | null { + const rawTheme = extractFlag(args, "--theme"); + const rawBase = extractFlag(args, "--base-color"); + const rawRadius = extractFlag(args, "--radius"); + const rawFont = extractFlag(args, "--font"); + + if (!rawTheme && !rawBase && !rawRadius && !rawFont) { + return null; + } + + let theme: ThemeName = DEFAULT_CONFIG.theme; + if (rawTheme) { + if (THEME_NAMES.includes(rawTheme)) { + theme = rawTheme as ThemeName; + } else { + log.warn( + `Unknown theme "${rawTheme}". Available: ${THEME_NAMES.join(", ")}. Using default "${DEFAULT_CONFIG.theme}".` + ); + } + } + + let baseColor: BaseColorName = DEFAULT_CONFIG.baseColor; + if (rawBase) { + if (["neutral", "stone", "zinc", "gray"].includes(rawBase)) { + baseColor = rawBase as BaseColorName; + } else { + log.warn( + `Unknown base color "${rawBase}". Available: neutral, stone, zinc, gray. Using default "${DEFAULT_CONFIG.baseColor}".` + ); + } + } + + let radius: RadiusPreset = DEFAULT_CONFIG.radius; + if (rawRadius) { + if (RADIUS_KEYS.includes(rawRadius as RadiusPreset)) { + radius = rawRadius as RadiusPreset; + } else { + log.warn( + `Unknown radius "${rawRadius}". Available: ${RADIUS_KEYS.join(", ")}. Using default "${DEFAULT_CONFIG.radius}".` + ); + } + } + + let font: FontOption = DEFAULT_CONFIG.font; + if (rawFont) { + if (FONT_VALUES.includes(rawFont as FontOption)) { + font = rawFont as FontOption; + } else { + log.warn( + `Unknown font "${rawFont}". Available: ${FONT_VALUES.join(", ")}. Using default "${DEFAULT_CONFIG.font}".` + ); + } + } + + return { theme, baseColor, radius, font }; +} + +export function applyTheme(targetDir: string, config: ThemeConfig): void { + const s = spinner(); + s.start("Applying theme..."); + + const css = generateFullAppCss(config); + writeFileSync(`${targetDir}/src/app.css`, css, "utf-8"); + + s.stop( + `Theme applied: ${config.theme} (base: ${config.baseColor}, radius: ${config.radius}, font: ${config.font})` + ); +} diff --git a/packages/cli/src/theme/theme-registry.ts b/packages/cli/src/theme/theme-registry.ts new file mode 100644 index 0000000..249cd2a --- /dev/null +++ b/packages/cli/src/theme/theme-registry.ts @@ -0,0 +1,874 @@ +import type { + BaseColorName, + FontOption, + RadiusPreset, + ThemeConfig, + ThemeDefinition, +} from "./theme-types"; + +export const THEMES: ThemeDefinition[] = [ + { + name: "neutral", + title: "Neutral", + cssVars: { + light: { + background: "oklch(1 0 0)", + foreground: "oklch(0.145 0 0)", + card: "oklch(1 0 0)", + "card-foreground": "oklch(0.145 0 0)", + popover: "oklch(1 0 0)", + "popover-foreground": "oklch(0.145 0 0)", + primary: "oklch(0.205 0 0)", + "primary-foreground": "oklch(0.985 0 0)", + secondary: "oklch(0.97 0 0)", + "secondary-foreground": "oklch(0.205 0 0)", + muted: "oklch(0.97 0 0)", + "muted-foreground": "oklch(0.556 0 0)", + accent: "oklch(0.97 0 0)", + "accent-foreground": "oklch(0.205 0 0)", + destructive: "oklch(0.58 0.22 27)", + border: "oklch(0.922 0 0)", + input: "oklch(0.922 0 0)", + ring: "oklch(0.708 0 0)", + "chart-1": "oklch(0.809 0.105 251.813)", + "chart-2": "oklch(0.623 0.214 259.815)", + "chart-3": "oklch(0.546 0.245 262.881)", + "chart-4": "oklch(0.488 0.243 264.376)", + "chart-5": "oklch(0.424 0.199 265.638)", + sidebar: "oklch(0.985 0 0)", + "sidebar-foreground": "oklch(0.145 0 0)", + "sidebar-primary": "oklch(0.205 0 0)", + "sidebar-primary-foreground": "oklch(0.985 0 0)", + "sidebar-accent": "oklch(0.97 0 0)", + "sidebar-accent-foreground": "oklch(0.205 0 0)", + "sidebar-border": "oklch(0.922 0 0)", + "sidebar-ring": "oklch(0.708 0 0)", + }, + dark: { + background: "oklch(0.145 0 0)", + foreground: "oklch(0.985 0 0)", + card: "oklch(0.205 0 0)", + "card-foreground": "oklch(0.985 0 0)", + popover: "oklch(0.205 0 0)", + "popover-foreground": "oklch(0.985 0 0)", + primary: "oklch(0.87 0.00 0)", + "primary-foreground": "oklch(0.205 0 0)", + secondary: "oklch(0.269 0 0)", + "secondary-foreground": "oklch(0.985 0 0)", + muted: "oklch(0.269 0 0)", + "muted-foreground": "oklch(0.708 0 0)", + accent: "oklch(0.371 0 0)", + "accent-foreground": "oklch(0.985 0 0)", + destructive: "oklch(0.704 0.191 22.216)", + border: "oklch(1 0 0 / 10%)", + input: "oklch(1 0 0 / 15%)", + ring: "oklch(0.556 0 0)", + "chart-1": "oklch(0.809 0.105 251.813)", + "chart-2": "oklch(0.623 0.214 259.815)", + "chart-3": "oklch(0.546 0.245 262.881)", + "chart-4": "oklch(0.488 0.243 264.376)", + "chart-5": "oklch(0.424 0.199 265.638)", + sidebar: "oklch(0.205 0 0)", + "sidebar-foreground": "oklch(0.985 0 0)", + "sidebar-primary": "oklch(0.488 0.243 264.376)", + "sidebar-primary-foreground": "oklch(0.985 0 0)", + "sidebar-accent": "oklch(0.269 0 0)", + "sidebar-accent-foreground": "oklch(0.985 0 0)", + "sidebar-border": "oklch(1 0 0 / 10%)", + "sidebar-ring": "oklch(0.556 0 0)", + }, + }, + }, + { + name: "stone", + title: "Stone", + cssVars: { + light: { + background: "oklch(1 0 0)", + foreground: "oklch(0.147 0.004 49.25)", + card: "oklch(1 0 0)", + "card-foreground": "oklch(0.147 0.004 49.25)", + popover: "oklch(1 0 0)", + "popover-foreground": "oklch(0.147 0.004 49.25)", + primary: "oklch(0.216 0.006 56.043)", + "primary-foreground": "oklch(0.985 0.001 106.423)", + secondary: "oklch(0.97 0.001 106.424)", + "secondary-foreground": "oklch(0.216 0.006 56.043)", + muted: "oklch(0.97 0.001 106.424)", + "muted-foreground": "oklch(0.553 0.013 58.071)", + accent: "oklch(0.97 0.001 106.424)", + "accent-foreground": "oklch(0.216 0.006 56.043)", + destructive: "oklch(0.577 0.245 27.325)", + border: "oklch(0.923 0.003 48.717)", + input: "oklch(0.923 0.003 48.717)", + ring: "oklch(0.709 0.01 56.259)", + "chart-1": "oklch(0.646 0.222 41.116)", + "chart-2": "oklch(0.6 0.118 184.704)", + "chart-3": "oklch(0.398 0.07 227.392)", + "chart-4": "oklch(0.828 0.189 84.429)", + "chart-5": "oklch(0.769 0.188 70.08)", + sidebar: "oklch(0.985 0.001 106.423)", + "sidebar-foreground": "oklch(0.147 0.004 49.25)", + "sidebar-primary": "oklch(0.216 0.006 56.043)", + "sidebar-primary-foreground": "oklch(0.985 0.001 106.423)", + "sidebar-accent": "oklch(0.97 0.001 106.424)", + "sidebar-accent-foreground": "oklch(0.216 0.006 56.043)", + "sidebar-border": "oklch(0.923 0.003 48.717)", + "sidebar-ring": "oklch(0.709 0.01 56.259)", + }, + dark: { + background: "oklch(0.147 0.004 49.25)", + foreground: "oklch(0.985 0.001 106.423)", + card: "oklch(0.216 0.006 56.043)", + "card-foreground": "oklch(0.985 0.001 106.423)", + popover: "oklch(0.216 0.006 56.043)", + "popover-foreground": "oklch(0.985 0.001 106.423)", + primary: "oklch(0.923 0.003 48.717)", + "primary-foreground": "oklch(0.216 0.006 56.043)", + secondary: "oklch(0.268 0.007 34.298)", + "secondary-foreground": "oklch(0.985 0.001 106.423)", + muted: "oklch(0.268 0.007 34.298)", + "muted-foreground": "oklch(0.709 0.01 56.259)", + accent: "oklch(0.268 0.007 34.298)", + "accent-foreground": "oklch(0.985 0.001 106.423)", + destructive: "oklch(0.704 0.191 22.216)", + border: "oklch(1 0 0 / 10%)", + input: "oklch(1 0 0 / 15%)", + ring: "oklch(0.553 0.013 58.071)", + "chart-1": "oklch(0.488 0.243 264.376)", + "chart-2": "oklch(0.696 0.17 162.48)", + "chart-3": "oklch(0.769 0.188 70.08)", + "chart-4": "oklch(0.627 0.265 303.9)", + "chart-5": "oklch(0.645 0.246 16.439)", + sidebar: "oklch(0.216 0.006 56.043)", + "sidebar-foreground": "oklch(0.985 0.001 106.423)", + "sidebar-primary": "oklch(0.488 0.243 264.376)", + "sidebar-primary-foreground": "oklch(0.985 0.001 106.423)", + "sidebar-accent": "oklch(0.268 0.007 34.298)", + "sidebar-accent-foreground": "oklch(0.985 0.001 106.423)", + "sidebar-border": "oklch(1 0 0 / 10%)", + "sidebar-ring": "oklch(0.553 0.013 58.071)", + }, + }, + }, + { + name: "zinc", + title: "Zinc", + cssVars: { + light: { + background: "oklch(1 0 0)", + foreground: "oklch(0.141 0.005 285.823)", + card: "oklch(1 0 0)", + "card-foreground": "oklch(0.141 0.005 285.823)", + popover: "oklch(1 0 0)", + "popover-foreground": "oklch(0.141 0.005 285.823)", + primary: "oklch(0.21 0.006 285.885)", + "primary-foreground": "oklch(0.985 0 0)", + secondary: "oklch(0.967 0.001 286.375)", + "secondary-foreground": "oklch(0.21 0.006 285.885)", + muted: "oklch(0.967 0.001 286.375)", + "muted-foreground": "oklch(0.552 0.016 285.938)", + accent: "oklch(0.967 0.001 286.375)", + "accent-foreground": "oklch(0.21 0.006 285.885)", + destructive: "oklch(0.577 0.245 27.325)", + border: "oklch(0.92 0.004 286.32)", + input: "oklch(0.92 0.004 286.32)", + ring: "oklch(0.705 0.015 286.067)", + "chart-1": "oklch(0.646 0.222 41.116)", + "chart-2": "oklch(0.6 0.118 184.704)", + "chart-3": "oklch(0.398 0.07 227.392)", + "chart-4": "oklch(0.828 0.189 84.429)", + "chart-5": "oklch(0.769 0.188 70.08)", + sidebar: "oklch(0.985 0 0)", + "sidebar-foreground": "oklch(0.141 0.005 285.823)", + "sidebar-primary": "oklch(0.21 0.006 285.885)", + "sidebar-primary-foreground": "oklch(0.985 0 0)", + "sidebar-accent": "oklch(0.967 0.001 286.375)", + "sidebar-accent-foreground": "oklch(0.21 0.006 285.885)", + "sidebar-border": "oklch(0.92 0.004 286.32)", + "sidebar-ring": "oklch(0.705 0.015 286.067)", + }, + dark: { + background: "oklch(0.141 0.005 285.823)", + foreground: "oklch(0.985 0 0)", + card: "oklch(0.21 0.006 285.885)", + "card-foreground": "oklch(0.985 0 0)", + popover: "oklch(0.21 0.006 285.885)", + "popover-foreground": "oklch(0.985 0 0)", + primary: "oklch(0.92 0.004 286.32)", + "primary-foreground": "oklch(0.21 0.006 285.885)", + secondary: "oklch(0.274 0.006 286.033)", + "secondary-foreground": "oklch(0.985 0 0)", + muted: "oklch(0.274 0.006 286.033)", + "muted-foreground": "oklch(0.705 0.015 286.067)", + accent: "oklch(0.274 0.006 286.033)", + "accent-foreground": "oklch(0.985 0 0)", + destructive: "oklch(0.704 0.191 22.216)", + border: "oklch(1 0 0 / 10%)", + input: "oklch(1 0 0 / 15%)", + ring: "oklch(0.552 0.016 285.938)", + "chart-1": "oklch(0.488 0.243 264.376)", + "chart-2": "oklch(0.696 0.17 162.48)", + "chart-3": "oklch(0.769 0.188 70.08)", + "chart-4": "oklch(0.627 0.265 303.9)", + "chart-5": "oklch(0.645 0.246 16.439)", + sidebar: "oklch(0.21 0.006 285.885)", + "sidebar-foreground": "oklch(0.985 0 0)", + "sidebar-primary": "oklch(0.488 0.243 264.376)", + "sidebar-primary-foreground": "oklch(0.985 0 0)", + "sidebar-accent": "oklch(0.274 0.006 286.033)", + "sidebar-accent-foreground": "oklch(0.985 0 0)", + "sidebar-border": "oklch(1 0 0 / 10%)", + "sidebar-ring": "oklch(0.552 0.016 285.938)", + }, + }, + }, + { + name: "gray", + title: "Gray", + cssVars: { + light: { + background: "oklch(1 0 0)", + foreground: "oklch(0.13 0.028 261.692)", + card: "oklch(1 0 0)", + "card-foreground": "oklch(0.13 0.028 261.692)", + popover: "oklch(1 0 0)", + "popover-foreground": "oklch(0.13 0.028 261.692)", + primary: "oklch(0.21 0.034 264.665)", + "primary-foreground": "oklch(0.985 0.002 247.839)", + secondary: "oklch(0.967 0.003 264.542)", + "secondary-foreground": "oklch(0.21 0.034 264.665)", + muted: "oklch(0.967 0.003 264.542)", + "muted-foreground": "oklch(0.551 0.027 264.364)", + accent: "oklch(0.967 0.003 264.542)", + "accent-foreground": "oklch(0.21 0.034 264.665)", + destructive: "oklch(0.577 0.245 27.325)", + border: "oklch(0.928 0.006 264.531)", + input: "oklch(0.928 0.006 264.531)", + ring: "oklch(0.707 0.022 261.325)", + "chart-1": "oklch(0.646 0.222 41.116)", + "chart-2": "oklch(0.6 0.118 184.704)", + "chart-3": "oklch(0.398 0.07 227.392)", + "chart-4": "oklch(0.828 0.189 84.429)", + "chart-5": "oklch(0.769 0.188 70.08)", + sidebar: "oklch(0.985 0.002 247.839)", + "sidebar-foreground": "oklch(0.13 0.028 261.692)", + "sidebar-primary": "oklch(0.21 0.034 264.665)", + "sidebar-primary-foreground": "oklch(0.985 0.002 247.839)", + "sidebar-accent": "oklch(0.967 0.003 264.542)", + "sidebar-accent-foreground": "oklch(0.21 0.034 264.665)", + "sidebar-border": "oklch(0.928 0.006 264.531)", + "sidebar-ring": "oklch(0.707 0.022 261.325)", + }, + dark: { + background: "oklch(0.13 0.028 261.692)", + foreground: "oklch(0.985 0.002 247.839)", + card: "oklch(0.21 0.034 264.665)", + "card-foreground": "oklch(0.985 0.002 247.839)", + popover: "oklch(0.21 0.034 264.665)", + "popover-foreground": "oklch(0.985 0.002 247.839)", + primary: "oklch(0.928 0.006 264.531)", + "primary-foreground": "oklch(0.21 0.034 264.665)", + secondary: "oklch(0.278 0.033 256.848)", + "secondary-foreground": "oklch(0.985 0.002 247.839)", + muted: "oklch(0.278 0.033 256.848)", + "muted-foreground": "oklch(0.707 0.022 261.325)", + accent: "oklch(0.278 0.033 256.848)", + "accent-foreground": "oklch(0.985 0.002 247.839)", + destructive: "oklch(0.704 0.191 22.216)", + border: "oklch(1 0 0 / 10%)", + input: "oklch(1 0 0 / 15%)", + ring: "oklch(0.551 0.027 264.364)", + "chart-1": "oklch(0.488 0.243 264.376)", + "chart-2": "oklch(0.696 0.17 162.48)", + "chart-3": "oklch(0.769 0.188 70.08)", + "chart-4": "oklch(0.627 0.265 303.9)", + "chart-5": "oklch(0.645 0.246 16.439)", + sidebar: "oklch(0.21 0.034 264.665)", + "sidebar-foreground": "oklch(0.985 0.002 247.839)", + "sidebar-primary": "oklch(0.488 0.243 264.376)", + "sidebar-primary-foreground": "oklch(0.985 0.002 247.839)", + "sidebar-accent": "oklch(0.278 0.033 256.848)", + "sidebar-accent-foreground": "oklch(0.985 0.002 247.839)", + "sidebar-border": "oklch(1 0 0 / 10%)", + "sidebar-ring": "oklch(0.551 0.027 264.364)", + }, + }, + }, + { + name: "amber", + title: "Amber", + cssVars: { + light: { + primary: "oklch(0.67 0.16 58)", + "primary-foreground": "oklch(0.99 0.02 95)", + secondary: "oklch(0.967 0.001 286.375)", + "secondary-foreground": "oklch(0.21 0.006 285.885)", + "chart-1": "oklch(0.88 0.15 92)", + "chart-2": "oklch(0.77 0.16 70)", + "chart-3": "oklch(0.67 0.16 58)", + "chart-4": "oklch(0.56 0.15 49)", + "chart-5": "oklch(0.47 0.12 46)", + "sidebar-primary": "oklch(0.67 0.16 58)", + "sidebar-primary-foreground": "oklch(0.99 0.02 95)", + }, + dark: { + primary: "oklch(0.77 0.16 70)", + "primary-foreground": "oklch(0.28 0.07 46)", + secondary: "oklch(0.274 0.006 286.033)", + "secondary-foreground": "oklch(0.985 0 0)", + "chart-1": "oklch(0.88 0.15 92)", + "chart-2": "oklch(0.77 0.16 70)", + "chart-3": "oklch(0.67 0.16 58)", + "chart-4": "oklch(0.56 0.15 49)", + "chart-5": "oklch(0.47 0.12 46)", + "sidebar-primary": "oklch(0.77 0.16 70)", + "sidebar-primary-foreground": "oklch(0.28 0.07 46)", + }, + }, + }, + { + name: "blue", + title: "Blue", + cssVars: { + light: { + primary: "oklch(0.488 0.243 264.376)", + "primary-foreground": "oklch(0.97 0.014 254.604)", + secondary: "oklch(0.967 0.001 286.375)", + "secondary-foreground": "oklch(0.21 0.006 285.885)", + "chart-1": "oklch(0.809 0.105 251.813)", + "chart-2": "oklch(0.623 0.214 259.815)", + "chart-3": "oklch(0.546 0.245 262.881)", + "chart-4": "oklch(0.488 0.243 264.376)", + "chart-5": "oklch(0.424 0.199 265.638)", + "sidebar-primary": "oklch(0.546 0.245 262.881)", + "sidebar-primary-foreground": "oklch(0.97 0.014 254.604)", + }, + dark: { + primary: "oklch(0.42 0.18 266)", + "primary-foreground": "oklch(0.97 0.014 254.604)", + secondary: "oklch(0.274 0.006 286.033)", + "secondary-foreground": "oklch(0.985 0 0)", + "chart-1": "oklch(0.809 0.105 251.813)", + "chart-2": "oklch(0.623 0.214 259.815)", + "chart-3": "oklch(0.546 0.245 262.881)", + "chart-4": "oklch(0.488 0.243 264.376)", + "chart-5": "oklch(0.424 0.199 265.638)", + "sidebar-primary": "oklch(0.623 0.214 259.815)", + "sidebar-primary-foreground": "oklch(0.97 0.014 254.604)", + }, + }, + }, + { + name: "cyan", + title: "Cyan", + cssVars: { + light: { + primary: "oklch(0.61 0.11 222)", + "primary-foreground": "oklch(0.98 0.02 201)", + secondary: "oklch(0.967 0.001 286.375)", + "secondary-foreground": "oklch(0.21 0.006 285.885)", + "chart-1": "oklch(0.87 0.12 207)", + "chart-2": "oklch(0.80 0.13 212)", + "chart-3": "oklch(0.71 0.13 215)", + "chart-4": "oklch(0.61 0.11 222)", + "chart-5": "oklch(0.52 0.09 223)", + "sidebar-primary": "oklch(0.61 0.11 222)", + "sidebar-primary-foreground": "oklch(0.98 0.02 201)", + }, + dark: { + primary: "oklch(0.71 0.13 215)", + "primary-foreground": "oklch(0.30 0.05 230)", + secondary: "oklch(0.274 0.006 286.033)", + "secondary-foreground": "oklch(0.985 0 0)", + "chart-1": "oklch(0.87 0.12 207)", + "chart-2": "oklch(0.80 0.13 212)", + "chart-3": "oklch(0.71 0.13 215)", + "chart-4": "oklch(0.61 0.11 222)", + "chart-5": "oklch(0.52 0.09 223)", + "sidebar-primary": "oklch(0.80 0.13 212)", + "sidebar-primary-foreground": "oklch(0.30 0.05 230)", + }, + }, + }, + { + name: "emerald", + title: "Emerald", + cssVars: { + light: { + primary: "oklch(0.60 0.13 163)", + "primary-foreground": "oklch(0.98 0.02 166)", + secondary: "oklch(0.967 0.001 286.375)", + "secondary-foreground": "oklch(0.21 0.006 285.885)", + "chart-1": "oklch(0.85 0.13 165)", + "chart-2": "oklch(0.77 0.15 163)", + "chart-3": "oklch(0.70 0.15 162)", + "chart-4": "oklch(0.60 0.13 163)", + "chart-5": "oklch(0.51 0.10 166)", + "sidebar-primary": "oklch(0.60 0.13 163)", + "sidebar-primary-foreground": "oklch(0.98 0.02 166)", + }, + dark: { + primary: "oklch(0.70 0.15 162)", + "primary-foreground": "oklch(0.26 0.05 173)", + secondary: "oklch(0.274 0.006 286.033)", + "secondary-foreground": "oklch(0.985 0 0)", + "chart-1": "oklch(0.85 0.13 165)", + "chart-2": "oklch(0.77 0.15 163)", + "chart-3": "oklch(0.70 0.15 162)", + "chart-4": "oklch(0.60 0.13 163)", + "chart-5": "oklch(0.51 0.10 166)", + "sidebar-primary": "oklch(0.77 0.15 163)", + "sidebar-primary-foreground": "oklch(0.26 0.05 173)", + }, + }, + }, + { + name: "fuchsia", + title: "Fuchsia", + cssVars: { + light: { + primary: "oklch(0.59 0.26 323)", + "primary-foreground": "oklch(0.98 0.02 320)", + secondary: "oklch(0.967 0.001 286.375)", + "secondary-foreground": "oklch(0.21 0.006 285.885)", + "chart-1": "oklch(0.83 0.13 321)", + "chart-2": "oklch(0.75 0.21 322)", + "chart-3": "oklch(0.67 0.26 322)", + "chart-4": "oklch(0.59 0.26 323)", + "chart-5": "oklch(0.52 0.23 324)", + "sidebar-primary": "oklch(0.59 0.26 323)", + "sidebar-primary-foreground": "oklch(0.98 0.02 320)", + }, + dark: { + primary: "oklch(0.67 0.26 322)", + "primary-foreground": "oklch(0.98 0.02 320)", + secondary: "oklch(0.274 0.006 286.033)", + "secondary-foreground": "oklch(0.985 0 0)", + "chart-1": "oklch(0.83 0.13 321)", + "chart-2": "oklch(0.75 0.21 322)", + "chart-3": "oklch(0.67 0.26 322)", + "chart-4": "oklch(0.59 0.26 323)", + "chart-5": "oklch(0.52 0.23 324)", + "sidebar-primary": "oklch(0.75 0.21 322)", + "sidebar-primary-foreground": "oklch(0.98 0.02 320)", + }, + }, + }, + { + name: "green", + title: "Green", + cssVars: { + light: { + primary: "oklch(0.648 0.2 131.684)", + "primary-foreground": "oklch(0.986 0.031 120.757)", + secondary: "oklch(0.967 0.001 286.375)", + "secondary-foreground": "oklch(0.21 0.006 285.885)", + "chart-1": "oklch(0.871 0.15 154.449)", + "chart-2": "oklch(0.723 0.219 149.579)", + "chart-3": "oklch(0.627 0.194 149.214)", + "chart-4": "oklch(0.527 0.154 150.069)", + "chart-5": "oklch(0.448 0.119 151.328)", + "sidebar-primary": "oklch(0.648 0.2 131.684)", + "sidebar-primary-foreground": "oklch(0.986 0.031 120.757)", + }, + dark: { + primary: "oklch(0.648 0.2 131.684)", + "primary-foreground": "oklch(0.986 0.031 120.757)", + secondary: "oklch(0.274 0.006 286.033)", + "secondary-foreground": "oklch(0.985 0 0)", + "chart-1": "oklch(0.871 0.15 154.449)", + "chart-2": "oklch(0.723 0.219 149.579)", + "chart-3": "oklch(0.627 0.194 149.214)", + "chart-4": "oklch(0.527 0.154 150.069)", + "chart-5": "oklch(0.448 0.119 151.328)", + "sidebar-primary": "oklch(0.768 0.233 130.85)", + "sidebar-primary-foreground": "oklch(0.986 0.031 120.757)", + }, + }, + }, + { + name: "indigo", + title: "Indigo", + cssVars: { + light: { + primary: "oklch(0.51 0.23 277)", + "primary-foreground": "oklch(0.96 0.02 272)", + secondary: "oklch(0.967 0.001 286.375)", + "secondary-foreground": "oklch(0.21 0.006 285.885)", + "chart-1": "oklch(0.79 0.10 275)", + "chart-2": "oklch(0.68 0.16 277)", + "chart-3": "oklch(0.59 0.20 277)", + "chart-4": "oklch(0.51 0.23 277)", + "chart-5": "oklch(0.46 0.21 277)", + "sidebar-primary": "oklch(0.51 0.23 277)", + "sidebar-primary-foreground": "oklch(0.96 0.02 272)", + }, + dark: { + primary: "oklch(0.59 0.20 277)", + "primary-foreground": "oklch(0.96 0.02 272)", + secondary: "oklch(0.274 0.006 286.033)", + "secondary-foreground": "oklch(0.985 0 0)", + "chart-1": "oklch(0.79 0.10 275)", + "chart-2": "oklch(0.68 0.16 277)", + "chart-3": "oklch(0.59 0.20 277)", + "chart-4": "oklch(0.51 0.23 277)", + "chart-5": "oklch(0.46 0.21 277)", + "sidebar-primary": "oklch(0.68 0.16 277)", + "sidebar-primary-foreground": "oklch(0.96 0.02 272)", + }, + }, + }, + { + name: "lime", + title: "Lime", + cssVars: { + light: { + primary: "oklch(0.65 0.18 132)", + "primary-foreground": "oklch(0.99 0.03 121)", + secondary: "oklch(0.967 0.001 286.375)", + "secondary-foreground": "oklch(0.21 0.006 285.885)", + "chart-1": "oklch(0.90 0.18 127)", + "chart-2": "oklch(0.85 0.21 129)", + "chart-3": "oklch(0.77 0.20 131)", + "chart-4": "oklch(0.65 0.18 132)", + "chart-5": "oklch(0.53 0.14 132)", + "sidebar-primary": "oklch(0.65 0.18 132)", + "sidebar-primary-foreground": "oklch(0.99 0.03 121)", + }, + dark: { + primary: "oklch(0.77 0.20 131)", + "primary-foreground": "oklch(0.27 0.07 132)", + secondary: "oklch(0.274 0.006 286.033)", + "secondary-foreground": "oklch(0.985 0 0)", + "chart-1": "oklch(0.90 0.18 127)", + "chart-2": "oklch(0.85 0.21 129)", + "chart-3": "oklch(0.77 0.20 131)", + "chart-4": "oklch(0.65 0.18 132)", + "chart-5": "oklch(0.53 0.14 132)", + "sidebar-primary": "oklch(0.85 0.21 129)", + "sidebar-primary-foreground": "oklch(0.27 0.07 132)", + }, + }, + }, + { + name: "orange", + title: "Orange", + cssVars: { + light: { + primary: "oklch(0.646 0.222 41.116)", + "primary-foreground": "oklch(0.98 0.016 73.684)", + secondary: "oklch(0.967 0.001 286.375)", + "secondary-foreground": "oklch(0.21 0.006 285.885)", + "chart-1": "oklch(0.837 0.128 66.29)", + "chart-2": "oklch(0.705 0.213 47.604)", + "chart-3": "oklch(0.646 0.222 41.116)", + "chart-4": "oklch(0.553 0.195 38.402)", + "chart-5": "oklch(0.47 0.157 37.304)", + "sidebar-primary": "oklch(0.646 0.222 41.116)", + "sidebar-primary-foreground": "oklch(0.98 0.016 73.684)", + }, + dark: { + primary: "oklch(0.705 0.213 47.604)", + "primary-foreground": "oklch(0.98 0.016 73.684)", + secondary: "oklch(0.274 0.006 286.033)", + "secondary-foreground": "oklch(0.985 0 0)", + "chart-1": "oklch(0.837 0.128 66.29)", + "chart-2": "oklch(0.705 0.213 47.604)", + "chart-3": "oklch(0.646 0.222 41.116)", + "chart-4": "oklch(0.553 0.195 38.402)", + "chart-5": "oklch(0.47 0.157 37.304)", + "sidebar-primary": "oklch(0.705 0.213 47.604)", + "sidebar-primary-foreground": "oklch(0.98 0.016 73.684)", + }, + }, + }, + { + name: "pink", + title: "Pink", + cssVars: { + light: { + primary: "oklch(0.59 0.22 1)", + "primary-foreground": "oklch(0.97 0.01 343)", + secondary: "oklch(0.967 0.001 286.375)", + "secondary-foreground": "oklch(0.21 0.006 285.885)", + "chart-1": "oklch(0.82 0.11 346)", + "chart-2": "oklch(0.73 0.18 350)", + "chart-3": "oklch(0.66 0.21 354)", + "chart-4": "oklch(0.59 0.22 1)", + "chart-5": "oklch(0.52 0.20 4)", + "sidebar-primary": "oklch(0.59 0.22 1)", + "sidebar-primary-foreground": "oklch(0.97 0.01 343)", + }, + dark: { + primary: "oklch(0.66 0.21 354)", + "primary-foreground": "oklch(0.97 0.01 343)", + secondary: "oklch(0.274 0.006 286.033)", + "secondary-foreground": "oklch(0.985 0 0)", + "chart-1": "oklch(0.82 0.11 346)", + "chart-2": "oklch(0.73 0.18 350)", + "chart-3": "oklch(0.66 0.21 354)", + "chart-4": "oklch(0.59 0.22 1)", + "chart-5": "oklch(0.52 0.20 4)", + "sidebar-primary": "oklch(0.73 0.18 350)", + "sidebar-primary-foreground": "oklch(0.97 0.01 343)", + }, + }, + }, + { + name: "purple", + title: "Purple", + cssVars: { + light: { + primary: "oklch(0.56 0.25 302)", + "primary-foreground": "oklch(0.98 0.01 308)", + secondary: "oklch(0.967 0.001 286.375)", + "secondary-foreground": "oklch(0.21 0.006 285.885)", + "chart-1": "oklch(0.83 0.11 306)", + "chart-2": "oklch(0.72 0.18 306)", + "chart-3": "oklch(0.63 0.23 304)", + "chart-4": "oklch(0.56 0.25 302)", + "chart-5": "oklch(0.50 0.24 302)", + "sidebar-primary": "oklch(0.56 0.25 302)", + "sidebar-primary-foreground": "oklch(0.98 0.01 308)", + }, + dark: { + primary: "oklch(0.63 0.23 304)", + "primary-foreground": "oklch(0.98 0.01 308)", + secondary: "oklch(0.274 0.006 286.033)", + "secondary-foreground": "oklch(0.985 0 0)", + "chart-1": "oklch(0.83 0.11 306)", + "chart-2": "oklch(0.72 0.18 306)", + "chart-3": "oklch(0.63 0.23 304)", + "chart-4": "oklch(0.56 0.25 302)", + "chart-5": "oklch(0.50 0.24 302)", + "sidebar-primary": "oklch(0.72 0.18 306)", + "sidebar-primary-foreground": "oklch(0.98 0.01 308)", + }, + }, + }, + { + name: "red", + title: "Red", + cssVars: { + light: { + primary: "oklch(0.577 0.245 27.325)", + "primary-foreground": "oklch(0.971 0.013 17.38)", + secondary: "oklch(0.967 0.001 286.375)", + "secondary-foreground": "oklch(0.21 0.006 285.885)", + "chart-1": "oklch(0.808 0.114 19.571)", + "chart-2": "oklch(0.637 0.237 25.331)", + "chart-3": "oklch(0.577 0.245 27.325)", + "chart-4": "oklch(0.505 0.213 27.518)", + "chart-5": "oklch(0.444 0.177 26.899)", + "sidebar-primary": "oklch(0.577 0.245 27.325)", + "sidebar-primary-foreground": "oklch(0.971 0.013 17.38)", + }, + dark: { + primary: "oklch(0.637 0.237 25.331)", + "primary-foreground": "oklch(0.971 0.013 17.38)", + secondary: "oklch(0.274 0.006 286.033)", + "secondary-foreground": "oklch(0.985 0 0)", + "chart-1": "oklch(0.808 0.114 19.571)", + "chart-2": "oklch(0.637 0.237 25.331)", + "chart-3": "oklch(0.577 0.245 27.325)", + "chart-4": "oklch(0.505 0.213 27.518)", + "chart-5": "oklch(0.444 0.177 26.899)", + "sidebar-primary": "oklch(0.637 0.237 25.331)", + "sidebar-primary-foreground": "oklch(0.971 0.013 17.38)", + }, + }, + }, + { + name: "rose", + title: "Rose", + cssVars: { + light: { + primary: "oklch(0.586 0.253 17.585)", + "primary-foreground": "oklch(0.969 0.015 12.422)", + secondary: "oklch(0.967 0.001 286.375)", + "secondary-foreground": "oklch(0.21 0.006 285.885)", + "chart-1": "oklch(0.81 0.117 11.638)", + "chart-2": "oklch(0.645 0.246 16.439)", + "chart-3": "oklch(0.586 0.253 17.585)", + "chart-4": "oklch(0.514 0.222 16.935)", + "chart-5": "oklch(0.455 0.188 13.697)", + "sidebar-primary": "oklch(0.586 0.253 17.585)", + "sidebar-primary-foreground": "oklch(0.969 0.015 12.422)", + }, + dark: { + primary: "oklch(0.645 0.246 16.439)", + "primary-foreground": "oklch(0.969 0.015 12.422)", + secondary: "oklch(0.274 0.006 286.033)", + "secondary-foreground": "oklch(0.985 0 0)", + "chart-1": "oklch(0.81 0.117 11.638)", + "chart-2": "oklch(0.645 0.246 16.439)", + "chart-3": "oklch(0.586 0.253 17.585)", + "chart-4": "oklch(0.514 0.222 16.935)", + "chart-5": "oklch(0.455 0.188 13.697)", + "sidebar-primary": "oklch(0.645 0.246 16.439)", + "sidebar-primary-foreground": "oklch(0.969 0.015 12.422)", + }, + }, + }, + { + name: "sky", + title: "Sky", + cssVars: { + light: { + primary: "oklch(0.59 0.14 242)", + "primary-foreground": "oklch(0.98 0.01 237)", + secondary: "oklch(0.967 0.001 286.375)", + "secondary-foreground": "oklch(0.21 0.006 285.885)", + "chart-1": "oklch(0.83 0.10 230)", + "chart-2": "oklch(0.75 0.14 233)", + "chart-3": "oklch(0.68 0.15 237)", + "chart-4": "oklch(0.59 0.14 242)", + "chart-5": "oklch(0.50 0.12 243)", + "sidebar-primary": "oklch(0.59 0.14 242)", + "sidebar-primary-foreground": "oklch(0.98 0.01 237)", + }, + dark: { + primary: "oklch(0.68 0.15 237)", + "primary-foreground": "oklch(0.29 0.06 243)", + secondary: "oklch(0.274 0.006 286.033)", + "secondary-foreground": "oklch(0.985 0 0)", + "chart-1": "oklch(0.83 0.10 230)", + "chart-2": "oklch(0.75 0.14 233)", + "chart-3": "oklch(0.68 0.15 237)", + "chart-4": "oklch(0.59 0.14 242)", + "chart-5": "oklch(0.50 0.12 243)", + "sidebar-primary": "oklch(0.75 0.14 233)", + "sidebar-primary-foreground": "oklch(0.29 0.06 243)", + }, + }, + }, + { + name: "teal", + title: "Teal", + cssVars: { + light: { + primary: "oklch(0.60 0.10 185)", + "primary-foreground": "oklch(0.98 0.01 181)", + secondary: "oklch(0.967 0.001 286.375)", + "secondary-foreground": "oklch(0.21 0.006 285.885)", + "chart-1": "oklch(0.85 0.13 181)", + "chart-2": "oklch(0.78 0.13 182)", + "chart-3": "oklch(0.70 0.12 183)", + "chart-4": "oklch(0.60 0.10 185)", + "chart-5": "oklch(0.51 0.09 186)", + "sidebar-primary": "oklch(0.60 0.10 185)", + "sidebar-primary-foreground": "oklch(0.98 0.01 181)", + }, + dark: { + primary: "oklch(0.70 0.12 183)", + "primary-foreground": "oklch(0.28 0.04 193)", + secondary: "oklch(0.274 0.006 286.033)", + "secondary-foreground": "oklch(0.985 0 0)", + "chart-1": "oklch(0.85 0.13 181)", + "chart-2": "oklch(0.78 0.13 182)", + "chart-3": "oklch(0.70 0.12 183)", + "chart-4": "oklch(0.60 0.10 185)", + "chart-5": "oklch(0.51 0.09 186)", + "sidebar-primary": "oklch(0.78 0.13 182)", + "sidebar-primary-foreground": "oklch(0.28 0.04 193)", + }, + }, + }, + { + name: "violet", + title: "Violet", + cssVars: { + light: { + primary: "oklch(0.541 0.281 293.009)", + "primary-foreground": "oklch(0.969 0.016 293.756)", + secondary: "oklch(0.967 0.001 286.375)", + "secondary-foreground": "oklch(0.21 0.006 285.885)", + "chart-1": "oklch(0.811 0.111 293.571)", + "chart-2": "oklch(0.606 0.25 292.717)", + "chart-3": "oklch(0.541 0.281 293.009)", + "chart-4": "oklch(0.491 0.27 292.581)", + "chart-5": "oklch(0.432 0.232 292.759)", + "sidebar-primary": "oklch(0.541 0.281 293.009)", + "sidebar-primary-foreground": "oklch(0.969 0.016 293.756)", + }, + dark: { + primary: "oklch(0.606 0.25 292.717)", + "primary-foreground": "oklch(0.969 0.016 293.756)", + secondary: "oklch(0.274 0.006 286.033)", + "secondary-foreground": "oklch(0.985 0 0)", + "chart-1": "oklch(0.811 0.111 293.571)", + "chart-2": "oklch(0.606 0.25 292.717)", + "chart-3": "oklch(0.541 0.281 293.009)", + "chart-4": "oklch(0.491 0.27 292.581)", + "chart-5": "oklch(0.432 0.232 292.759)", + "sidebar-primary": "oklch(0.606 0.25 292.717)", + "sidebar-primary-foreground": "oklch(0.969 0.016 293.756)", + }, + }, + }, + { + name: "yellow", + title: "Yellow", + cssVars: { + light: { + primary: "oklch(0.852 0.199 91.936)", + "primary-foreground": "oklch(0.421 0.095 57.708)", + secondary: "oklch(0.967 0.001 286.375)", + "secondary-foreground": "oklch(0.21 0.006 285.885)", + "chart-1": "oklch(0.905 0.182 98.111)", + "chart-2": "oklch(0.795 0.184 86.047)", + "chart-3": "oklch(0.681 0.162 75.834)", + "chart-4": "oklch(0.554 0.135 66.442)", + "chart-5": "oklch(0.476 0.114 61.907)", + "sidebar-primary": "oklch(0.681 0.162 75.834)", + "sidebar-primary-foreground": "oklch(0.987 0.026 102.212)", + }, + dark: { + primary: "oklch(0.795 0.184 86.047)", + "primary-foreground": "oklch(0.421 0.095 57.708)", + secondary: "oklch(0.274 0.006 286.033)", + "secondary-foreground": "oklch(0.985 0 0)", + "chart-1": "oklch(0.905 0.182 98.111)", + "chart-2": "oklch(0.795 0.184 86.047)", + "chart-3": "oklch(0.681 0.162 75.834)", + "chart-4": "oklch(0.554 0.135 66.442)", + "chart-5": "oklch(0.476 0.114 61.907)", + "sidebar-primary": "oklch(0.795 0.184 86.047)", + "sidebar-primary-foreground": "oklch(0.987 0.026 102.212)", + }, + }, + }, +]; + +export const BASE_COLOR_NAMES = [ + "neutral", + "stone", + "zinc", + "gray", +] as const; + +export const RADIUS_VALUES: Record = { + none: "0", + sm: "0.375rem", + md: "0.5rem", + lg: "0.625rem", + xl: "0.875rem", +}; + +export const FONT_OPTIONS: { + value: FontOption; + label: string; + family: string; +}[] = [ + { value: "inter", label: "Inter", family: '"Inter Variable", sans-serif' }, + { value: "geist", label: "Geist Sans", family: '"Geist Variable", sans-serif' }, + { value: "system", label: "System", family: "system-ui, sans-serif" }, +]; + +export const DEFAULT_CONFIG: ThemeConfig = { + baseColor: "neutral", + theme: "neutral", + radius: "lg", + font: "inter", +}; diff --git a/packages/cli/src/theme/theme-types.ts b/packages/cli/src/theme/theme-types.ts new file mode 100644 index 0000000..50526c1 --- /dev/null +++ b/packages/cli/src/theme/theme-types.ts @@ -0,0 +1,44 @@ +export type ThemeCssVars = Record; + +export type ThemeDefinition = { + name: string; + title: string; + cssVars: { + light: ThemeCssVars; + dark: ThemeCssVars; + }; +}; + +export type BaseColorName = "neutral" | "stone" | "zinc" | "gray"; + +export type ThemeName = + | BaseColorName + | "amber" + | "blue" + | "cyan" + | "emerald" + | "fuchsia" + | "green" + | "indigo" + | "lime" + | "orange" + | "pink" + | "purple" + | "red" + | "rose" + | "sky" + | "teal" + | "violet" + | "yellow"; + +export type RadiusPreset = "none" | "sm" | "md" | "lg" | "xl"; + +export type FontOption = "inter" | "geist" | "system"; + +export type ThemeConfig = { + baseColor: BaseColorName; + theme: ThemeName; + radius: RadiusPreset; + font: FontOption; + customColor?: string; +}; diff --git a/packages/cli/src/theme/theme-utils.ts b/packages/cli/src/theme/theme-utils.ts new file mode 100644 index 0000000..9b0cbf6 --- /dev/null +++ b/packages/cli/src/theme/theme-utils.ts @@ -0,0 +1,211 @@ +import type { ThemeConfig, ThemeCssVars } from "./theme-types"; +import { + BASE_COLOR_NAMES, + FONT_OPTIONS, + RADIUS_VALUES, + THEMES, +} from "./theme-registry"; + +export function buildTheme( + baseColor: string, + themeName: string +): { light: ThemeCssVars; dark: ThemeCssVars } { + const base = THEMES.find((t) => t.name === baseColor); + const accent = THEMES.find((t) => t.name === themeName); + + if (!base) { + const fallback = THEMES[0]; + return { light: { ...fallback.cssVars.light }, dark: { ...fallback.cssVars.dark } }; + } + + const isBaseColorTheme = BASE_COLOR_NAMES.includes( + themeName as (typeof BASE_COLOR_NAMES)[number] + ); + + if (!accent || isBaseColorTheme) { + return { light: { ...base.cssVars.light }, dark: { ...base.cssVars.dark } }; + } + + return { + light: { ...base.cssVars.light, ...accent.cssVars.light }, + dark: { ...base.cssVars.dark, ...accent.cssVars.dark }, + }; +} + +export function hexToOklch(hex: string): string | null { + const match = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + if (!match) return null; + + let r = Number.parseInt(match[1], 16) / 255; + let g = Number.parseInt(match[2], 16) / 255; + let b = Number.parseInt(match[3], 16) / 255; + + r = r > 0.04045 ? ((r + 0.055) / 1.055) ** 2.4 : r / 12.92; + g = g > 0.04045 ? ((g + 0.055) / 1.055) ** 2.4 : g / 12.92; + b = b > 0.04045 ? ((b + 0.055) / 1.055) ** 2.4 : b / 12.92; + + const l_ = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b; + const m_ = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b; + const s_ = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b; + + const l = Math.cbrt(l_); + const m = Math.cbrt(m_); + const s = Math.cbrt(s_); + + const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s; + const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s; + const bOklab = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s; + + const C = Math.sqrt(a * a + bOklab * bOklab); + let h = (Math.atan2(bOklab, a) * 180) / Math.PI; + if (h < 0) h += 360; + + return `oklch(${L.toFixed(3)} ${C.toFixed(3)} ${h.toFixed(1)})`; +} + +export function generateCssVars(config: ThemeConfig): string { + const theme = buildTheme(config.baseColor, config.theme); + const radius = RADIUS_VALUES[config.radius]; + + const customOklch = config.customColor + ? hexToOklch(config.customColor) + : null; + + const formatVars = (vars: ThemeCssVars, includeRadius: boolean) => { + const entries = { ...vars }; + if (customOklch) { + entries.primary = customOklch; + entries["sidebar-primary"] = customOklch; + } + + const lines: string[] = []; + for (const [key, value] of Object.entries(entries)) { + if (key === "radius") continue; + lines.push(` --${key}: ${value};`); + } + if (includeRadius) { + lines.push(` --radius: ${radius};`); + } + return lines.join("\n"); + }; + + return `:root {\n${formatVars(theme.light, true)}\n}\n\n.dark {\n${formatVars(theme.dark, false)}\n}`; +} + +const TEMPLATE_UTILITIES = `@layer utilities { + @keyframes tech-marquee-scroll { + from { + transform: translateX(0); + } + to { + transform: translateX(calc(-100% - var(--tech-marquee-gap, 2.5rem))); + } + } + + .tech-marquee { + --tech-marquee-gap: 2.5rem; + --tech-marquee-duration: 40000ms; + } + + .tech-marquee__group { + animation: tech-marquee-scroll var(--tech-marquee-duration) linear infinite; + } + + .tech-marquee:hover .tech-marquee__group, + .tech-marquee:focus-within .tech-marquee__group { + animation-play-state: paused; + } + + @media (prefers-reduced-motion: reduce) { + .tech-marquee__group { + animation: none; + } + + .tech-marquee__group[aria-hidden="true"] { + display: none; + } + + .tech-marquee__track { + flex-wrap: wrap; + justify-content: center; + width: 100%; + } + } +}`; + +export function generateFullAppCss(config: ThemeConfig): string { + const font = FONT_OPTIONS.find((f) => f.value === config.font); + const fontFamily = font?.family ?? '"Inter Variable", sans-serif'; + const cssVarsBlock = generateCssVars(config); + + const fontImports = ['@import "@fontsource-variable/inter";']; + if (config.font === "geist") { + fontImports.push('@import "@fontsource-variable/geist";'); + } + + return `@import "tailwindcss"; +@import "tw-animate-css"; +@import "shadcn/tailwind.css"; +${fontImports.join("\n")} + +@theme inline { + --font-sans: ${fontFamily}; + --font-mono: "Inter Variable", monospace; + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --radius-2xl: calc(var(--radius) + 8px); + --radius-3xl: calc(var(--radius) + 12px); + --radius-4xl: calc(var(--radius) + 16px); +} + +${cssVarsBlock} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply font-sans bg-background text-foreground; + } + html { + @apply font-sans; + } +} + +${TEMPLATE_UTILITIES} +`; +}