From faf78df71c7ccb84814cecb47906676272b51b04 Mon Sep 17 00:00:00 2001 From: Krishna Santosh <75202541+krishna-santosh@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:26:01 +0530 Subject: [PATCH 01/16] feat(dashboard): add themes --- .../src/components/theme-provider.tsx | 41 +- .../dashboard/src/components/theme-toggle.tsx | 49 ++- apps/dashboard/src/styles.css | 370 +++++++++++++++++- 3 files changed, 444 insertions(+), 16 deletions(-) diff --git a/apps/dashboard/src/components/theme-provider.tsx b/apps/dashboard/src/components/theme-provider.tsx index e18440d7..dccf1438 100644 --- a/apps/dashboard/src/components/theme-provider.tsx +++ b/apps/dashboard/src/components/theme-provider.tsx @@ -1,6 +1,27 @@ import { createContext, useContext, useEffect, useState } from "react"; -type Theme = "dark" | "light" | "system"; +export type Theme = + | "dark" + | "light" + | "system" + | "crimson-moon" + | "sepia" + | "midnight-blurple" + | "forest" + | "dusk" + | "citrus-sherbet"; + +// Custom palettes derive their tokens from the shared `.theme-dark` / +// `.theme-light` markers, added alongside the palette class in the effect below. +const DARK_THEMES = [ + "crimson-moon", + "sepia", + "midnight-blurple", + "forest", + "dusk", +] as const; +const LIGHT_THEMES = ["citrus-sherbet"] as const; +const CUSTOM_THEMES: readonly string[] = [...DARK_THEMES, ...LIGHT_THEMES]; type ThemeProviderProps = { children: React.ReactNode; @@ -33,7 +54,13 @@ export function ThemeProvider({ useEffect(() => { const root = window.document.documentElement; - root.classList.remove("light", "dark"); + root.classList.remove( + "light", + "dark", + "theme-dark", + "theme-light", + ...CUSTOM_THEMES, + ); if (theme === "system") { const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") @@ -45,6 +72,16 @@ export function ThemeProvider({ return; } + if ((DARK_THEMES as readonly string[]).includes(theme)) { + root.classList.add(theme, "theme-dark"); + return; + } + + if ((LIGHT_THEMES as readonly string[]).includes(theme)) { + root.classList.add(theme, "theme-light"); + return; + } + root.classList.add(theme); }, [theme]); diff --git a/apps/dashboard/src/components/theme-toggle.tsx b/apps/dashboard/src/components/theme-toggle.tsx index 316b5c21..5a7b7768 100644 --- a/apps/dashboard/src/components/theme-toggle.tsx +++ b/apps/dashboard/src/components/theme-toggle.tsx @@ -1,37 +1,60 @@ -import { Moon, Sun } from "lucide-react"; -import { useTheme } from "@/components/theme-provider"; +import { Palette } from "lucide-react"; +import { type Theme, useTheme } from "@/components/theme-provider"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { cn } from "@/lib/utils"; + +const BASE_THEMES: { key: Theme; label: string }[] = [ + { key: "light", label: "Light" }, + { key: "dark", label: "Dark" }, + { key: "system", label: "System" }, +]; + +const NITRO_THEMES: { key: Theme; label: string }[] = [ + { key: "crimson-moon", label: "Crimson Moon" }, + { key: "sepia", label: "Sepia" }, + { key: "midnight-blurple", label: "Midnight Blurple" }, + { key: "forest", label: "Forest" }, + { key: "dusk", label: "Dusk" }, + { key: "citrus-sherbet", label: "Citrus Sherbet" }, +]; export function ModeToggle() { const { setTheme } = useTheme(); + const renderItem = ({ key, label }: { key: Theme; label: string }) => ( + setTheme(key)}> + + ); + return ( - - + Toggle theme } /> - setTheme("light")}> - Light - - setTheme("dark")}> - Dark - - setTheme("system")}> - System - + {BASE_THEMES.map(renderItem)} + + {NITRO_THEMES.map(renderItem)} ); diff --git a/apps/dashboard/src/styles.css b/apps/dashboard/src/styles.css index 349e876b..6f43b95a 100644 --- a/apps/dashboard/src/styles.css +++ b/apps/dashboard/src/styles.css @@ -6,7 +6,10 @@ @import "@fontsource-variable/geist"; -@custom-variant dark (&:is(.dark *)); +/* The custom DARK themes carry a `.theme-dark` marker, so every `dark:` utility + the shadcn components already ship activates under them too. `.theme-light` + (Citrus Sherbet) is intentionally excluded. `:root` and `.dark` are unchanged. */ +@custom-variant dark (&:is(.dark *, .theme-dark *)); :root { --background: oklch(1 0 0); @@ -79,6 +82,204 @@ --sidebar-ring: oklch(0.553 0.013 58.071); } +/* ========================================================================= + Discord Nitro–inspired theme engine. + + Each palette declares only three tints (+ a gradient and a shadow hue) at the + bottom of this file; every shadcn token below is DERIVED from them via OKLCH + ramps + color-mix(in oklab). The shadcn variables stay the public API — + nothing hardcodes a per-token color, so adding a theme is a ~6-line block. + + `.theme-dark` = dark surface + light foreground (Crimson Moon, Sepia, + Midnight Blurple, Forest, Dusk). `.theme-light` = the mirror, for a light + surface + dark foreground (Citrus Sherbet). The ThemeProvider adds the + matching marker class alongside the palette name. `:root` and `.dark` (the + original stone light/dark themes) are never touched. + ========================================================================= */ + +/* ---- shared derivation for the DARK palettes ---- */ +.theme-dark { + color-scheme: dark; + + /* surfaces: surface tint on an elevation ramp (mixed toward white) */ + --background: color-mix(in oklab, var(--theme-surface-tint) 95%, white); /* +5% canvas */ + --card: color-mix(in oklab, var(--theme-surface-tint) 92%, white); /* +8% one step up */ + --popover: color-mix(in oklab, var(--theme-surface-tint) 86%, white); /* +14% elevated */ + --sidebar: color-mix(in oklab, var(--theme-surface-tint) 97%, black); /* darker than bg */ + --muted: color-mix(in oklab, var(--theme-surface-tint) 89%, white); /* +11% fills */ + --accent: color-mix(in oklab, var(--theme-surface-tint) 87%, white); /* +13% hover fills */ + --secondary: color-mix(in oklab, var(--theme-surface-tint) 89%, white); + --input: color-mix(in oklab, var(--theme-surface-tint) 86%, white); + --sidebar-accent: color-mix(in oklab, var(--theme-surface-tint) 87%, white); + + /* primary: bright surface (buttons), deep-tint text */ + --primary: color-mix(in oklab, var(--theme-foreground-tint) 88%, var(--theme-surface-tint)); + --primary-foreground: color-mix(in oklab, var(--theme-surface-tint) 90%, black); + --sidebar-primary: var(--primary); + --sidebar-primary-foreground: var(--primary-foreground); + + /* text: foreground tint toward pure white (subtle tint, never loud) */ + --foreground: color-mix(in oklab, var(--theme-foreground-tint) 55%, white); + --card-foreground: var(--foreground); + --popover-foreground: var(--foreground); + --sidebar-foreground: color-mix(in oklab, var(--theme-foreground-tint) 60%, white); + --muted-foreground: color-mix(in oklab, var(--theme-foreground-tint) 92%, transparent); + --secondary-foreground: color-mix(in oklab, var(--theme-foreground-tint) 70%, white); + --accent-foreground: color-mix(in oklab, var(--theme-foreground-tint) 70%, white); + --sidebar-accent-foreground: var(--accent-foreground); + + /* borders at low alpha; ring is a tinted glow derived from the foreground */ + --border: color-mix(in oklab, var(--theme-foreground-tint) 7%, transparent); + --sidebar-border: color-mix(in oklab, var(--theme-foreground-tint) 6%, transparent); + --ring: color-mix(in oklab, var(--theme-foreground-tint) 60%, transparent); + --sidebar-ring: var(--ring); + + /* destructive stays a true red across every theme */ + --destructive: oklch(0.6 0.22 25); + --destructive-foreground: color-mix(in oklab, var(--theme-foreground-tint) 40%, white); + + /* charts: a monochrome ramp from the foreground tint down to the surface */ + --chart-1: var(--theme-foreground-tint); + --chart-2: color-mix(in oklab, var(--theme-foreground-tint) 78%, var(--theme-surface-tint)); + --chart-3: color-mix(in oklab, var(--theme-foreground-tint) 58%, var(--theme-surface-tint)); + --chart-4: color-mix(in oklab, var(--theme-foreground-tint) 40%, var(--theme-surface-tint)); + --chart-5: color-mix(in oklab, var(--theme-foreground-tint) 25%, var(--theme-surface-tint)); +} + +/* ---- shared derivation for the LIGHT palettes (light surface, dark fg) ---- */ +.theme-light { + color-scheme: light; + + /* surfaces: cards/popovers climb toward white; fills recede toward the fg tint */ + --background: color-mix(in oklab, var(--theme-surface-tint) 90%, white); + --card: color-mix(in oklab, var(--theme-surface-tint) 55%, white); + --popover: color-mix(in oklab, var(--theme-surface-tint) 45%, white); + --sidebar: color-mix(in oklab, var(--theme-foreground-tint) 4%, var(--theme-surface-tint)); + --muted: color-mix(in oklab, var(--theme-foreground-tint) 6%, var(--theme-surface-tint)); + --accent: color-mix(in oklab, var(--theme-foreground-tint) 9%, var(--theme-surface-tint)); + --secondary: color-mix(in oklab, var(--theme-foreground-tint) 6%, var(--theme-surface-tint)); + --input: color-mix(in oklab, var(--theme-surface-tint) 62%, white); + --sidebar-accent: color-mix(in oklab, var(--theme-foreground-tint) 9%, var(--theme-surface-tint)); + + /* primary: dark warm surface (buttons), light text */ + --primary: color-mix(in oklab, var(--theme-foreground-tint) 90%, var(--theme-surface-tint)); + --primary-foreground: color-mix(in oklab, var(--theme-surface-tint) 92%, white); + --sidebar-primary: var(--primary); + --sidebar-primary-foreground: var(--primary-foreground); + + /* text: foreground tint toward black (raise contrast on light surfaces) */ + --foreground: color-mix(in oklab, var(--theme-foreground-tint) 92%, black); + --card-foreground: var(--foreground); + --popover-foreground: var(--foreground); + --sidebar-foreground: color-mix(in oklab, var(--theme-foreground-tint) 88%, black); + --muted-foreground: color-mix(in oklab, var(--theme-foreground-tint) 60%, transparent); + --secondary-foreground: color-mix(in oklab, var(--theme-foreground-tint) 85%, black); + --accent-foreground: color-mix(in oklab, var(--theme-foreground-tint) 85%, black); + --sidebar-accent-foreground: var(--accent-foreground); + + --border: color-mix(in oklab, var(--theme-foreground-tint) 14%, transparent); + --sidebar-border: color-mix(in oklab, var(--theme-foreground-tint) 12%, transparent); + --ring: color-mix(in oklab, var(--theme-foreground-tint) 45%, transparent); + --sidebar-ring: var(--ring); + + --destructive: oklch(0.58 0.22 27); + --destructive-foreground: oklch(0.985 0 0); + + --chart-1: var(--theme-foreground-tint); + --chart-2: color-mix(in oklab, var(--theme-foreground-tint) 78%, var(--theme-surface-tint)); + --chart-3: color-mix(in oklab, var(--theme-foreground-tint) 58%, var(--theme-surface-tint)); + --chart-4: color-mix(in oklab, var(--theme-foreground-tint) 40%, var(--theme-surface-tint)); + --chart-5: color-mix(in oklab, var(--theme-foreground-tint) 25%, var(--theme-surface-tint)); +} + +/* ---- layered premium shadows (shared): inset highlight + soft ambient + + a subtle colored glow tinted by each palette's --theme-shadow-color. + Applied per data-slot below (Tailwind v4 inlines shadow-* utilities at + build time, so we set box-shadow directly rather than override tokens). ---- */ +.theme-dark, +.theme-light { + --cm-shadow-sm: + inset 0 1px 0 0 hsl(0 0% 100% / 0.05), + 0 1px 2px 0 hsl(var(--theme-shadow-color) / 0.4), + 0 2px 6px -1px hsl(var(--theme-shadow-color) / 0.3); + --cm-shadow-lg: + inset 0 1px 0 0 hsl(0 0% 100% / 0.06), + 0 4px 8px -2px hsl(var(--theme-shadow-color) / 0.42), + 0 12px 28px -6px hsl(var(--theme-shadow-color) / 0.4), + 0 0 40px -8px hsl(var(--theme-shadow-color) / 0.25); + --cm-shadow-xl: + inset 0 1px 0 0 hsl(0 0% 100% / 0.07), + 0 8px 16px -4px hsl(var(--theme-shadow-color) / 0.45), + 0 20px 48px -10px hsl(var(--theme-shadow-color) / 0.45), + 0 0 64px -12px hsl(var(--theme-shadow-color) / 0.28); +} + +/* ========================================================================= + Palettes — each declares only its three tints, a full-page gradient (dim + wash + color layer, already fixed/cover), and a shadow hue (bare HSL + channels consumed by the shadow recipes above). + ========================================================================= */ + +/* Crimson Moon */ +.crimson-moon { + --theme-surface-tint: rgb(26 0 0); + --theme-foreground-tint: rgb(255 229 229); + --theme-gradient: + linear-gradient(rgb(0 0 0 / 0.82), rgb(0 0 0 / 0.82)) fixed 0 0 / cover, + linear-gradient(64.92deg, #950909 16.17%, #000000 72%) fixed 0 0 / cover; + --theme-shadow-color: 0 72% 12%; +} + +/* Sepia */ +.sepia { + --theme-surface-tint: rgb(26 15 0); + --theme-foreground-tint: rgb(255 243 229); + --theme-gradient: + linear-gradient(rgb(0 0 0 / 0.9), rgb(0 0 0 / 0.9)) fixed 0 0 / cover, + linear-gradient(69.98deg, #857664 14.14%, #5b4421 60.35%) fixed 0 0 / cover; + --theme-shadow-color: 28 70% 10%; +} + +/* Midnight Blurple */ +.midnight-blurple { + --theme-surface-tint: rgb(8 0 26); + --theme-foreground-tint: rgb(162 153 255); + --theme-gradient: + linear-gradient(rgb(0 0 0 / 0.9), rgb(0 0 0 / 0.9)) fixed 0 0 / cover, + linear-gradient(48.17deg, #5348ca 11.21%, #140730 61.92%) fixed 0 0 / cover; + --theme-shadow-color: 250 65% 14%; +} + +/* Forest */ +.forest { + --theme-surface-tint: rgb(0 26 2); + --theme-foreground-tint: rgb(255 248 229); + --theme-gradient: + linear-gradient(rgb(0 0 0 / 0.9), rgb(0 0 0 / 0.9)) fixed 0 0 / cover, + linear-gradient(162.27deg, #142215 11.2%, #2d4d39 29.93%, #454c32 48.64%, #5a7c58 67.85%, #a98e4b 83.54%) fixed 0 0 / cover; + --theme-shadow-color: 130 60% 8%; +} + +/* Dusk */ +.dusk { + --theme-surface-tint: rgb(22 0 26); + --theme-foreground-tint: rgb(153 182 255); + --theme-gradient: + linear-gradient(rgb(0 0 0 / 0.9), rgb(0 0 0 / 0.9)) fixed 0 0 / cover, + linear-gradient(180deg, #665069 12.84%, #91a3d1 85.99%) fixed 0 0 / cover; + --theme-shadow-color: 285 45% 14%; +} + +/* Citrus Sherbet (light) */ +.citrus-sherbet { + --theme-surface-tint: rgb(255 238 204); + --theme-foreground-tint: rgb(77 23 0); + --theme-gradient: + linear-gradient(rgb(255 255 255 / 0.85), rgb(255 255 255 / 0.85)) fixed 0 0 / cover, + linear-gradient(180deg, #f3b336 31.1%, #ee8558 67.09%) fixed 0 0 / cover; + --theme-shadow-color: 28 55% 45%; +} + @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); @@ -134,3 +335,170 @@ @apply font-sans; } } + +/* ========================================================================= + Custom themes — scoped atmosphere, motion, and per-component polish. + Keyed off the .theme-dark / .theme-light markers so every palette shares + them; unlayered so they win over `@layer base`. `:root`/`.dark` (the + original stone themes) are never matched. + ========================================================================= */ + +/* Atmospheric page background: the palette's full gradient value (dim wash + + color layer, already fixed/cover) reads as depth behind the panels. */ +:is(.theme-dark, .theme-light) body { + background: var(--theme-gradient); +} + +/* Text selection tinted from the foreground tint. */ +:is(.theme-dark, .theme-light) ::selection { + background: color-mix(in oklab, var(--theme-foreground-tint) 22%, transparent); + color: var(--foreground); +} + +/* Thin, rounded, low-contrast scrollbars. */ +:is(.theme-dark, .theme-light) * { + scrollbar-width: thin; + scrollbar-color: color-mix(in oklab, var(--theme-foreground-tint) 14%, transparent) transparent; +} +:is(.theme-dark, .theme-light) *::-webkit-scrollbar { + width: 10px; + height: 10px; +} +:is(.theme-dark, .theme-light) *::-webkit-scrollbar-track { + background: transparent; +} +:is(.theme-dark, .theme-light) *::-webkit-scrollbar-thumb { + background: color-mix(in oklab, var(--theme-foreground-tint) 12%, transparent); + border-radius: 9999px; + border: 2px solid transparent; + background-clip: padding-box; +} +:is(.theme-dark, .theme-light) *::-webkit-scrollbar-thumb:hover { + background: color-mix(in oklab, var(--theme-foreground-tint) 22%, transparent); + background-clip: padding-box; +} + +/* Premium hover timing (150–180ms) on the surfaces users actually touch. */ +:is(.theme-dark, .theme-light) + :is( + a, + [data-slot="button"], + [data-slot="table-row"], + [data-slot="sidebar-menu-button"], + [data-slot="input"], + [data-slot="textarea"], + [data-slot="card"] + ) { + transition-property: color, background-color, border-color, box-shadow, transform, opacity; + transition-duration: 165ms; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} +@media (prefers-reduced-motion: reduce) { + :is(.theme-dark, .theme-light) * { + transition-duration: 0ms !important; + } +} + +/* Layered shadows on the shadow-bearing slots (sm cards, lg menus, xl modals). */ +:is(.theme-dark, .theme-light) [data-slot="card"] { + box-shadow: var(--cm-shadow-sm); +} +:is(.theme-dark, .theme-light) + :is( + [data-slot="popover-content"], + [data-slot="dropdown-menu-content"], + [data-slot="select-content"], + [data-slot="tooltip-content"] + ) { + box-shadow: var(--cm-shadow-lg); +} +:is(.theme-dark, .theme-light) + :is( + [data-slot="dialog-content"], + [data-slot="alert-dialog-content"], + [data-slot="sheet-content"], + [data-slot="drawer-content"] + ) { + box-shadow: var(--cm-shadow-xl); +} + +/* Primary button: subtle vertical gradient, premium shadow, hover lift. + `.bg-primary` targets shadcn's default button variant without touching the + component. */ +:is(.theme-dark, .theme-light) [data-slot="button"].bg-primary { + background-image: linear-gradient( + 180deg, + color-mix(in oklab, var(--primary) 94%, white) 0%, + var(--primary) 55%, + color-mix(in oklab, var(--primary) 92%, black) 100% + ); + box-shadow: var(--cm-shadow-sm); +} +:is(.theme-dark, .theme-light) [data-slot="button"].bg-primary:hover { + transform: translateY(-1px); + box-shadow: var(--cm-shadow-lg); +} +:is(.theme-dark, .theme-light) [data-slot="button"].bg-primary:active { + transform: translateY(0); +} + +/* Tables: tinted hover + selected rows. */ +:is(.theme-dark, .theme-light) [data-slot="table-row"]:hover { + background: color-mix(in oklab, var(--theme-foreground-tint) 4%, transparent); +} +:is(.theme-dark, .theme-light) [data-slot="table-row"][data-state="selected"] { + background: color-mix(in oklab, var(--theme-foreground-tint) 7%, transparent); +} + +/* Inputs: tinted fill, restrained blur, themed focus glow. */ +:is(.theme-dark, .theme-light) + :is([data-slot="input"], [data-slot="textarea"]) { + background: color-mix(in oklab, var(--input) 88%, transparent); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); +} +:is(.theme-dark, .theme-light) + :is([data-slot="input"], [data-slot="textarea"]):focus-visible { + box-shadow: + 0 0 0 3px color-mix(in oklab, var(--ring) 28%, transparent), + 0 0 18px -4px color-mix(in oklab, var(--ring) 40%, transparent); +} + +/* Sidebar menu: gentle tinted hover, no loud background. */ +:is(.theme-dark, .theme-light) [data-slot="sidebar-menu-button"]:hover { + background: color-mix(in oklab, var(--theme-foreground-tint) 5%, transparent); +} + +/* ========================================================================= + Theme swatches — the little gradient discs shown next to each theme name in + the picker. These are the color layer only (no dim wash) so they stay vivid, + and they are unscoped/global so every swatch renders regardless of the + active theme. Geometry (size / shape / ring) comes from Tailwind on the span. + ========================================================================= */ +.theme-swatch-light { + background: #ffffff; +} +.theme-swatch-dark { + background: #1c1917; +} +.theme-swatch-system { + background: linear-gradient(135deg, #fafafa 0 50%, #1c1917 50% 100%); +} +.theme-swatch-crimson-moon { + background: linear-gradient(64.92deg, #950909, #000000); +} +.theme-swatch-sepia { + background: linear-gradient(69.98deg, #857664, #5b4421); +} +.theme-swatch-midnight-blurple { + background: linear-gradient(48.17deg, #5348ca, #140730); +} +.theme-swatch-forest { + background: linear-gradient(162.27deg, #142215, #2d4d39, #454c32, #5a7c58, #a98e4b); +} +.theme-swatch-dusk { + background: linear-gradient(180deg, #665069, #91a3d1); +} +.theme-swatch-citrus-sherbet { + background: linear-gradient(180deg, #f3b336, #ee8558); +} From 05ec914ba0d0cf0129fa7bfb7d441cae5c91fedb Mon Sep 17 00:00:00 2001 From: Krishna Santosh <75202541+krishna-santosh@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:27:18 +0530 Subject: [PATCH 02/16] style: formatting --- apps/dashboard/src/styles.css | 254 ++++++++++++++++++++++++++++------ 1 file changed, 212 insertions(+), 42 deletions(-) diff --git a/apps/dashboard/src/styles.css b/apps/dashboard/src/styles.css index 6f43b95a..1c4986cb 100644 --- a/apps/dashboard/src/styles.css +++ b/apps/dashboard/src/styles.css @@ -102,19 +102,51 @@ color-scheme: dark; /* surfaces: surface tint on an elevation ramp (mixed toward white) */ - --background: color-mix(in oklab, var(--theme-surface-tint) 95%, white); /* +5% canvas */ - --card: color-mix(in oklab, var(--theme-surface-tint) 92%, white); /* +8% one step up */ - --popover: color-mix(in oklab, var(--theme-surface-tint) 86%, white); /* +14% elevated */ - --sidebar: color-mix(in oklab, var(--theme-surface-tint) 97%, black); /* darker than bg */ - --muted: color-mix(in oklab, var(--theme-surface-tint) 89%, white); /* +11% fills */ - --accent: color-mix(in oklab, var(--theme-surface-tint) 87%, white); /* +13% hover fills */ + --background: color-mix( + in oklab, + var(--theme-surface-tint) 95%, + white + ); /* +5% canvas */ + --card: color-mix( + in oklab, + var(--theme-surface-tint) 92%, + white + ); /* +8% one step up */ + --popover: color-mix( + in oklab, + var(--theme-surface-tint) 86%, + white + ); /* +14% elevated */ + --sidebar: color-mix( + in oklab, + var(--theme-surface-tint) 97%, + black + ); /* darker than bg */ + --muted: color-mix( + in oklab, + var(--theme-surface-tint) 89%, + white + ); /* +11% fills */ + --accent: color-mix( + in oklab, + var(--theme-surface-tint) 87%, + white + ); /* +13% hover fills */ --secondary: color-mix(in oklab, var(--theme-surface-tint) 89%, white); --input: color-mix(in oklab, var(--theme-surface-tint) 86%, white); --sidebar-accent: color-mix(in oklab, var(--theme-surface-tint) 87%, white); /* primary: bright surface (buttons), deep-tint text */ - --primary: color-mix(in oklab, var(--theme-foreground-tint) 88%, var(--theme-surface-tint)); - --primary-foreground: color-mix(in oklab, var(--theme-surface-tint) 90%, black); + --primary: color-mix( + in oklab, + var(--theme-foreground-tint) 88%, + var(--theme-surface-tint) + ); + --primary-foreground: color-mix( + in oklab, + var(--theme-surface-tint) 90%, + black + ); --sidebar-primary: var(--primary); --sidebar-primary-foreground: var(--primary-foreground); @@ -122,28 +154,68 @@ --foreground: color-mix(in oklab, var(--theme-foreground-tint) 55%, white); --card-foreground: var(--foreground); --popover-foreground: var(--foreground); - --sidebar-foreground: color-mix(in oklab, var(--theme-foreground-tint) 60%, white); - --muted-foreground: color-mix(in oklab, var(--theme-foreground-tint) 92%, transparent); - --secondary-foreground: color-mix(in oklab, var(--theme-foreground-tint) 70%, white); - --accent-foreground: color-mix(in oklab, var(--theme-foreground-tint) 70%, white); + --sidebar-foreground: color-mix( + in oklab, + var(--theme-foreground-tint) 60%, + white + ); + --muted-foreground: color-mix( + in oklab, + var(--theme-foreground-tint) 92%, + transparent + ); + --secondary-foreground: color-mix( + in oklab, + var(--theme-foreground-tint) 70%, + white + ); + --accent-foreground: color-mix( + in oklab, + var(--theme-foreground-tint) 70%, + white + ); --sidebar-accent-foreground: var(--accent-foreground); /* borders at low alpha; ring is a tinted glow derived from the foreground */ --border: color-mix(in oklab, var(--theme-foreground-tint) 7%, transparent); - --sidebar-border: color-mix(in oklab, var(--theme-foreground-tint) 6%, transparent); + --sidebar-border: color-mix( + in oklab, + var(--theme-foreground-tint) 6%, + transparent + ); --ring: color-mix(in oklab, var(--theme-foreground-tint) 60%, transparent); --sidebar-ring: var(--ring); /* destructive stays a true red across every theme */ --destructive: oklch(0.6 0.22 25); - --destructive-foreground: color-mix(in oklab, var(--theme-foreground-tint) 40%, white); + --destructive-foreground: color-mix( + in oklab, + var(--theme-foreground-tint) 40%, + white + ); /* charts: a monochrome ramp from the foreground tint down to the surface */ --chart-1: var(--theme-foreground-tint); - --chart-2: color-mix(in oklab, var(--theme-foreground-tint) 78%, var(--theme-surface-tint)); - --chart-3: color-mix(in oklab, var(--theme-foreground-tint) 58%, var(--theme-surface-tint)); - --chart-4: color-mix(in oklab, var(--theme-foreground-tint) 40%, var(--theme-surface-tint)); - --chart-5: color-mix(in oklab, var(--theme-foreground-tint) 25%, var(--theme-surface-tint)); + --chart-2: color-mix( + in oklab, + var(--theme-foreground-tint) 78%, + var(--theme-surface-tint) + ); + --chart-3: color-mix( + in oklab, + var(--theme-foreground-tint) 58%, + var(--theme-surface-tint) + ); + --chart-4: color-mix( + in oklab, + var(--theme-foreground-tint) 40%, + var(--theme-surface-tint) + ); + --chart-5: color-mix( + in oklab, + var(--theme-foreground-tint) 25%, + var(--theme-surface-tint) + ); } /* ---- shared derivation for the LIGHT palettes (light surface, dark fg) ---- */ @@ -154,16 +226,44 @@ --background: color-mix(in oklab, var(--theme-surface-tint) 90%, white); --card: color-mix(in oklab, var(--theme-surface-tint) 55%, white); --popover: color-mix(in oklab, var(--theme-surface-tint) 45%, white); - --sidebar: color-mix(in oklab, var(--theme-foreground-tint) 4%, var(--theme-surface-tint)); - --muted: color-mix(in oklab, var(--theme-foreground-tint) 6%, var(--theme-surface-tint)); - --accent: color-mix(in oklab, var(--theme-foreground-tint) 9%, var(--theme-surface-tint)); - --secondary: color-mix(in oklab, var(--theme-foreground-tint) 6%, var(--theme-surface-tint)); + --sidebar: color-mix( + in oklab, + var(--theme-foreground-tint) 4%, + var(--theme-surface-tint) + ); + --muted: color-mix( + in oklab, + var(--theme-foreground-tint) 6%, + var(--theme-surface-tint) + ); + --accent: color-mix( + in oklab, + var(--theme-foreground-tint) 9%, + var(--theme-surface-tint) + ); + --secondary: color-mix( + in oklab, + var(--theme-foreground-tint) 6%, + var(--theme-surface-tint) + ); --input: color-mix(in oklab, var(--theme-surface-tint) 62%, white); - --sidebar-accent: color-mix(in oklab, var(--theme-foreground-tint) 9%, var(--theme-surface-tint)); + --sidebar-accent: color-mix( + in oklab, + var(--theme-foreground-tint) 9%, + var(--theme-surface-tint) + ); /* primary: dark warm surface (buttons), light text */ - --primary: color-mix(in oklab, var(--theme-foreground-tint) 90%, var(--theme-surface-tint)); - --primary-foreground: color-mix(in oklab, var(--theme-surface-tint) 92%, white); + --primary: color-mix( + in oklab, + var(--theme-foreground-tint) 90%, + var(--theme-surface-tint) + ); + --primary-foreground: color-mix( + in oklab, + var(--theme-surface-tint) 92%, + white + ); --sidebar-primary: var(--primary); --sidebar-primary-foreground: var(--primary-foreground); @@ -171,14 +271,34 @@ --foreground: color-mix(in oklab, var(--theme-foreground-tint) 92%, black); --card-foreground: var(--foreground); --popover-foreground: var(--foreground); - --sidebar-foreground: color-mix(in oklab, var(--theme-foreground-tint) 88%, black); - --muted-foreground: color-mix(in oklab, var(--theme-foreground-tint) 60%, transparent); - --secondary-foreground: color-mix(in oklab, var(--theme-foreground-tint) 85%, black); - --accent-foreground: color-mix(in oklab, var(--theme-foreground-tint) 85%, black); + --sidebar-foreground: color-mix( + in oklab, + var(--theme-foreground-tint) 88%, + black + ); + --muted-foreground: color-mix( + in oklab, + var(--theme-foreground-tint) 60%, + transparent + ); + --secondary-foreground: color-mix( + in oklab, + var(--theme-foreground-tint) 85%, + black + ); + --accent-foreground: color-mix( + in oklab, + var(--theme-foreground-tint) 85%, + black + ); --sidebar-accent-foreground: var(--accent-foreground); --border: color-mix(in oklab, var(--theme-foreground-tint) 14%, transparent); - --sidebar-border: color-mix(in oklab, var(--theme-foreground-tint) 12%, transparent); + --sidebar-border: color-mix( + in oklab, + var(--theme-foreground-tint) 12%, + transparent + ); --ring: color-mix(in oklab, var(--theme-foreground-tint) 45%, transparent); --sidebar-ring: var(--ring); @@ -186,10 +306,26 @@ --destructive-foreground: oklch(0.985 0 0); --chart-1: var(--theme-foreground-tint); - --chart-2: color-mix(in oklab, var(--theme-foreground-tint) 78%, var(--theme-surface-tint)); - --chart-3: color-mix(in oklab, var(--theme-foreground-tint) 58%, var(--theme-surface-tint)); - --chart-4: color-mix(in oklab, var(--theme-foreground-tint) 40%, var(--theme-surface-tint)); - --chart-5: color-mix(in oklab, var(--theme-foreground-tint) 25%, var(--theme-surface-tint)); + --chart-2: color-mix( + in oklab, + var(--theme-foreground-tint) 78%, + var(--theme-surface-tint) + ); + --chart-3: color-mix( + in oklab, + var(--theme-foreground-tint) 58%, + var(--theme-surface-tint) + ); + --chart-4: color-mix( + in oklab, + var(--theme-foreground-tint) 40%, + var(--theme-surface-tint) + ); + --chart-5: color-mix( + in oklab, + var(--theme-foreground-tint) 25%, + var(--theme-surface-tint) + ); } /* ---- layered premium shadows (shared): inset highlight + soft ambient + @@ -256,7 +392,15 @@ --theme-foreground-tint: rgb(255 248 229); --theme-gradient: linear-gradient(rgb(0 0 0 / 0.9), rgb(0 0 0 / 0.9)) fixed 0 0 / cover, - linear-gradient(162.27deg, #142215 11.2%, #2d4d39 29.93%, #454c32 48.64%, #5a7c58 67.85%, #a98e4b 83.54%) fixed 0 0 / cover; + linear-gradient( + 162.27deg, + #142215 11.2%, + #2d4d39 29.93%, + #454c32 48.64%, + #5a7c58 67.85%, + #a98e4b 83.54% + ) + fixed 0 0 / cover; --theme-shadow-color: 130 60% 8%; } @@ -275,7 +419,8 @@ --theme-surface-tint: rgb(255 238 204); --theme-foreground-tint: rgb(77 23 0); --theme-gradient: - linear-gradient(rgb(255 255 255 / 0.85), rgb(255 255 255 / 0.85)) fixed 0 0 / cover, + linear-gradient(rgb(255 255 255 / 0.85), rgb(255 255 255 / 0.85)) fixed 0 0 + / cover, linear-gradient(180deg, #f3b336 31.1%, #ee8558 67.09%) fixed 0 0 / cover; --theme-shadow-color: 28 55% 45%; } @@ -351,14 +496,23 @@ /* Text selection tinted from the foreground tint. */ :is(.theme-dark, .theme-light) ::selection { - background: color-mix(in oklab, var(--theme-foreground-tint) 22%, transparent); + background: color-mix( + in oklab, + var(--theme-foreground-tint) 22%, + transparent + ); color: var(--foreground); } /* Thin, rounded, low-contrast scrollbars. */ :is(.theme-dark, .theme-light) * { scrollbar-width: thin; - scrollbar-color: color-mix(in oklab, var(--theme-foreground-tint) 14%, transparent) transparent; + scrollbar-color: color-mix( + in oklab, + var(--theme-foreground-tint) 14%, + transparent + ) + transparent; } :is(.theme-dark, .theme-light) *::-webkit-scrollbar { width: 10px; @@ -368,13 +522,21 @@ background: transparent; } :is(.theme-dark, .theme-light) *::-webkit-scrollbar-thumb { - background: color-mix(in oklab, var(--theme-foreground-tint) 12%, transparent); + background: color-mix( + in oklab, + var(--theme-foreground-tint) 12%, + transparent + ); border-radius: 9999px; border: 2px solid transparent; background-clip: padding-box; } :is(.theme-dark, .theme-light) *::-webkit-scrollbar-thumb:hover { - background: color-mix(in oklab, var(--theme-foreground-tint) 22%, transparent); + background: color-mix( + in oklab, + var(--theme-foreground-tint) 22%, + transparent + ); background-clip: padding-box; } @@ -389,7 +551,8 @@ [data-slot="textarea"], [data-slot="card"] ) { - transition-property: color, background-color, border-color, box-shadow, transform, opacity; + transition-property: + color, background-color, border-color, box-shadow, transform, opacity; transition-duration: 165ms; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } @@ -494,7 +657,14 @@ background: linear-gradient(48.17deg, #5348ca, #140730); } .theme-swatch-forest { - background: linear-gradient(162.27deg, #142215, #2d4d39, #454c32, #5a7c58, #a98e4b); + background: linear-gradient( + 162.27deg, + #142215, + #2d4d39, + #454c32, + #5a7c58, + #a98e4b + ); } .theme-swatch-dusk { background: linear-gradient(180deg, #665069, #91a3d1); From 3036f1e51a9a0ab1a8865726f27cfb0b1970e916 Mon Sep 17 00:00:00 2001 From: Krishna Santosh <75202541+krishna-santosh@users.noreply.github.com> Date: Wed, 1 Jul 2026 19:29:10 +0530 Subject: [PATCH 03/16] fix(dashboard): add width to theme selection dropdown --- apps/dashboard/src/components/theme-toggle.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/src/components/theme-toggle.tsx b/apps/dashboard/src/components/theme-toggle.tsx index 5a7b7768..2facf8fe 100644 --- a/apps/dashboard/src/components/theme-toggle.tsx +++ b/apps/dashboard/src/components/theme-toggle.tsx @@ -51,7 +51,7 @@ export function ModeToggle() { } /> - + {BASE_THEMES.map(renderItem)} {NITRO_THEMES.map(renderItem)} From 1c15a7eae7b1733b26f4a046d7318d1a3cd11ba6 Mon Sep 17 00:00:00 2001 From: Krishna Santosh <75202541+krishna-santosh@users.noreply.github.com> Date: Wed, 1 Jul 2026 19:29:41 +0530 Subject: [PATCH 04/16] fix(dashboard): adjust transition duration for theme elements to reduce motion --- apps/dashboard/src/styles.css | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/dashboard/src/styles.css b/apps/dashboard/src/styles.css index 1c4986cb..a6312e30 100644 --- a/apps/dashboard/src/styles.css +++ b/apps/dashboard/src/styles.css @@ -557,8 +557,19 @@ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } @media (prefers-reduced-motion: reduce) { - :is(.theme-dark, .theme-light) * { - transition-duration: 0ms !important; + /* Mirror the selector above so specificity ties and this later rule wins — + neutralizes the added motion without needing !important. */ + :is(.theme-dark, .theme-light) + :is( + a, + [data-slot="button"], + [data-slot="table-row"], + [data-slot="sidebar-menu-button"], + [data-slot="input"], + [data-slot="textarea"], + [data-slot="card"] + ) { + transition-duration: 0.01ms; } } From f8c0e40e834f24e759f0494d6e2bcdb58c644764 Mon Sep 17 00:00:00 2001 From: Krishna Santosh <75202541+krishna-santosh@users.noreply.github.com> Date: Thu, 2 Jul 2026 00:10:02 +0530 Subject: [PATCH 05/16] fix(dashboard): enhance theme class application and update gradient styles --- .../src/components/theme-provider.tsx | 13 +- apps/dashboard/src/styles.css | 357 +++++++++--------- 2 files changed, 180 insertions(+), 190 deletions(-) diff --git a/apps/dashboard/src/components/theme-provider.tsx b/apps/dashboard/src/components/theme-provider.tsx index dccf1438..767d76e9 100644 --- a/apps/dashboard/src/components/theme-provider.tsx +++ b/apps/dashboard/src/components/theme-provider.tsx @@ -11,8 +11,11 @@ export type Theme = | "dusk" | "citrus-sherbet"; -// Custom palettes derive their tokens from the shared `.theme-dark` / -// `.theme-light` markers, added alongside the palette class in the effect below. +// Custom palettes are applied to as a `theme-` class (e.g. +// `theme-sepia`) — the `theme-` prefix keeps them from colliding with Tailwind +// utility classes of the same name (notably `sepia`, `grayscale`, `invert`). +// Each derives its tokens from the shared `.theme-dark` / `.theme-light` marker, +// added alongside the palette class in the effect below. const DARK_THEMES = [ "crimson-moon", "sepia", @@ -59,7 +62,7 @@ export function ThemeProvider({ "dark", "theme-dark", "theme-light", - ...CUSTOM_THEMES, + ...CUSTOM_THEMES.map((t) => `theme-${t}`), ); if (theme === "system") { @@ -73,12 +76,12 @@ export function ThemeProvider({ } if ((DARK_THEMES as readonly string[]).includes(theme)) { - root.classList.add(theme, "theme-dark"); + root.classList.add(`theme-${theme}`, "theme-dark"); return; } if ((LIGHT_THEMES as readonly string[]).includes(theme)) { - root.classList.add(theme, "theme-light"); + root.classList.add(`theme-${theme}`, "theme-light"); return; } diff --git a/apps/dashboard/src/styles.css b/apps/dashboard/src/styles.css index a6312e30..5f59c441 100644 --- a/apps/dashboard/src/styles.css +++ b/apps/dashboard/src/styles.css @@ -101,89 +101,71 @@ .theme-dark { color-scheme: dark; - /* surfaces: surface tint on an elevation ramp (mixed toward white) */ - --background: color-mix( - in oklab, - var(--theme-surface-tint) 95%, - white - ); /* +5% canvas */ - --card: color-mix( - in oklab, - var(--theme-surface-tint) 92%, - white - ); /* +8% one step up */ - --popover: color-mix( - in oklab, - var(--theme-surface-tint) 86%, - white - ); /* +14% elevated */ - --sidebar: color-mix( - in oklab, - var(--theme-surface-tint) 97%, - black - ); /* darker than bg */ - --muted: color-mix( - in oklab, - var(--theme-surface-tint) 89%, - white - ); /* +11% fills */ - --accent: color-mix( - in oklab, - var(--theme-surface-tint) 87%, - white - ); /* +13% hover fills */ - --secondary: color-mix(in oklab, var(--theme-surface-tint) 89%, white); - --input: color-mix(in oklab, var(--theme-surface-tint) 86%, white); - --sidebar-accent: color-mix(in oklab, var(--theme-surface-tint) 87%, white); + /* Each STRUCTURAL surface paints `scrim + color gradient`, + viewport-fixed + cover, so they all sample ONE continuous + gradient and differ only by scrim opacity (elevation). Painted on the + structural containers below; component tokens stay solid (crisp). */ + --grad-chrome: + linear-gradient(rgb(0 0 0 / 0.86), rgb(0 0 0 / 0.86)) fixed 0 0 / cover, + var(--theme-grad-color) fixed 0 0 / cover; + --grad-base: + linear-gradient(rgb(0 0 0 / 0.82), rgb(0 0 0 / 0.82)) fixed 0 0 / cover, + var(--theme-grad-color) fixed 0 0 / cover; + --grad-raised: + linear-gradient(rgb(0 0 0 / 0.58), rgb(0 0 0 / 0.58)) fixed 0 0 / cover, + var(--theme-grad-color) fixed 0 0 / cover; + + /* neutral hover / selected overlays */ + --mod-hover: color-mix(in oklab, white 6%, transparent); + --mod-active: color-mix(in oklab, white 12%, transparent); + + /* TRANSLUCENT component surfaces: veils over the painted gradient, so every + input / chip / menu / tab picks up the theme instead of reading as flat gray. + Dark veils recess (input, muted, popover); light veils lift (card, chips, + hovers). Legible because they sit over the already-dark gradient. */ + --sidebar: color-mix(in oklab, oklch(0.16 0 0) 55%, transparent); + --background: color-mix(in oklab, oklch(0.16 0 0) 14%, transparent); + --card: color-mix(in oklab, white 5%, transparent); + --popover: color-mix(in oklab, oklch(0.15 0 0) 82%, transparent); + --muted: color-mix(in oklab, oklch(0 0 0) 22%, transparent); + --secondary: color-mix(in oklab, white 7%, transparent); + --accent: color-mix(in oklab, white 11%, transparent); + --sidebar-accent: color-mix(in oklab, white 10%, transparent); + --input: color-mix(in oklab, oklch(0 0 0) 42%, transparent); + --header-bg: color-mix(in oklab, oklch(0.14 0 0) 55%, transparent); /* primary: bright surface (buttons), deep-tint text */ --primary: color-mix( in oklab, - var(--theme-foreground-tint) 88%, + var(--theme-foreground-tint) 90%, var(--theme-surface-tint) ); --primary-foreground: color-mix( in oklab, - var(--theme-surface-tint) 90%, + var(--theme-surface-tint) 88%, black ); --sidebar-primary: var(--primary); --sidebar-primary-foreground: var(--primary-foreground); - /* text: foreground tint toward pure white (subtle tint, never loud) */ - --foreground: color-mix(in oklab, var(--theme-foreground-tint) 55%, white); + /* text: crisp near-white, negligible tint */ + --foreground: color-mix(in oklab, var(--theme-foreground-tint) 16%, white); --card-foreground: var(--foreground); --popover-foreground: var(--foreground); --sidebar-foreground: color-mix( in oklab, - var(--theme-foreground-tint) 60%, - white - ); - --muted-foreground: color-mix( - in oklab, - var(--theme-foreground-tint) 92%, - transparent - ); - --secondary-foreground: color-mix( - in oklab, - var(--theme-foreground-tint) 70%, - white - ); - --accent-foreground: color-mix( - in oklab, - var(--theme-foreground-tint) 70%, + var(--theme-foreground-tint) 22%, white ); - --sidebar-accent-foreground: var(--accent-foreground); + --muted-foreground: color-mix(in oklab, var(--foreground) 62%, transparent); + --secondary-foreground: var(--foreground); + --accent-foreground: var(--foreground); + --sidebar-accent-foreground: var(--foreground); - /* borders at low alpha; ring is a tinted glow derived from the foreground */ - --border: color-mix(in oklab, var(--theme-foreground-tint) 7%, transparent); - --sidebar-border: color-mix( - in oklab, - var(--theme-foreground-tint) 6%, - transparent - ); - --ring: color-mix(in oklab, var(--theme-foreground-tint) 60%, transparent); + /* borders / ring: neutral, low alpha */ + --border: color-mix(in oklab, white 10%, transparent); + --sidebar-border: color-mix(in oklab, white 7%, transparent); + --ring: color-mix(in oklab, var(--theme-foreground-tint) 55%, transparent); --sidebar-ring: var(--ring); /* destructive stays a true red across every theme */ @@ -222,36 +204,37 @@ .theme-light { color-scheme: light; - /* surfaces: cards/popovers climb toward white; fills recede toward the fg tint */ - --background: color-mix(in oklab, var(--theme-surface-tint) 90%, white); - --card: color-mix(in oklab, var(--theme-surface-tint) 55%, white); - --popover: color-mix(in oklab, var(--theme-surface-tint) 45%, white); - --sidebar: color-mix( - in oklab, - var(--theme-foreground-tint) 4%, - var(--theme-surface-tint) - ); - --muted: color-mix( - in oklab, - var(--theme-foreground-tint) 6%, - var(--theme-surface-tint) - ); - --accent: color-mix( - in oklab, - var(--theme-foreground-tint) 9%, - var(--theme-surface-tint) - ); - --secondary: color-mix( - in oklab, - var(--theme-foreground-tint) 6%, - var(--theme-surface-tint) - ); - --input: color-mix(in oklab, var(--theme-surface-tint) 62%, white); - --sidebar-accent: color-mix( - in oklab, - var(--theme-foreground-tint) 9%, - var(--theme-surface-tint) - ); + /* light mirror: structural surfaces paint the color gradient under a WHITE + scrim (lighter scrim = more color), viewport-fixed + cover. */ + --grad-chrome: + linear-gradient(rgb(255 255 255 / 0.6), rgb(255 255 255 / 0.6)) fixed 0 0 / + cover, + var(--theme-grad-color) fixed 0 0 / cover; + --grad-base: + linear-gradient(rgb(255 255 255 / 0.5), rgb(255 255 255 / 0.5)) fixed 0 0 / + cover, + var(--theme-grad-color) fixed 0 0 / cover; + --grad-raised: + linear-gradient(rgb(255 255 255 / 0.24), rgb(255 255 255 / 0.24)) fixed 0 0 + / cover, + var(--theme-grad-color) fixed 0 0 / cover; + + --mod-hover: color-mix(in oklab, black 5%, transparent); + --mod-active: color-mix(in oklab, black 9%, transparent); + + /* TRANSLUCENT component surfaces (mirror of the dark theme): veils over the + painted gradient so components pick up the theme. White veils lift, black + veils recess. */ + --sidebar: color-mix(in oklab, white 55%, transparent); + --background: color-mix(in oklab, white 16%, transparent); + --card: color-mix(in oklab, white 32%, transparent); + --popover: color-mix(in oklab, white 82%, transparent); + --muted: color-mix(in oklab, black 6%, transparent); + --secondary: color-mix(in oklab, black 5%, transparent); + --accent: color-mix(in oklab, black 8%, transparent); + --sidebar-accent: color-mix(in oklab, black 8%, transparent); + --input: color-mix(in oklab, white 55%, transparent); + --header-bg: color-mix(in oklab, white 55%, transparent); /* primary: dark warm surface (buttons), light text */ --primary: color-mix( @@ -267,38 +250,22 @@ --sidebar-primary: var(--primary); --sidebar-primary-foreground: var(--primary-foreground); - /* text: foreground tint toward black (raise contrast on light surfaces) */ - --foreground: color-mix(in oklab, var(--theme-foreground-tint) 92%, black); + /* text: near-black with a whisper of hue */ + --foreground: color-mix(in oklab, var(--theme-foreground-tint) 88%, black); --card-foreground: var(--foreground); --popover-foreground: var(--foreground); --sidebar-foreground: color-mix( in oklab, - var(--theme-foreground-tint) 88%, - black - ); - --muted-foreground: color-mix( - in oklab, - var(--theme-foreground-tint) 60%, - transparent - ); - --secondary-foreground: color-mix( - in oklab, - var(--theme-foreground-tint) 85%, - black - ); - --accent-foreground: color-mix( - in oklab, - var(--theme-foreground-tint) 85%, + var(--theme-foreground-tint) 82%, black ); - --sidebar-accent-foreground: var(--accent-foreground); + --muted-foreground: color-mix(in oklab, var(--foreground) 58%, transparent); + --secondary-foreground: var(--foreground); + --accent-foreground: var(--foreground); + --sidebar-accent-foreground: var(--foreground); - --border: color-mix(in oklab, var(--theme-foreground-tint) 14%, transparent); - --sidebar-border: color-mix( - in oklab, - var(--theme-foreground-tint) 12%, - transparent - ); + --border: color-mix(in oklab, black 12%, transparent); + --sidebar-border: color-mix(in oklab, black 9%, transparent); --ring: color-mix(in oklab, var(--theme-foreground-tint) 45%, transparent); --sidebar-ring: var(--ring); @@ -328,101 +295,75 @@ ); } -/* ---- layered premium shadows (shared): inset highlight + soft ambient + - a subtle colored glow tinted by each palette's --theme-shadow-color. - Applied per data-slot below (Tailwind v4 inlines shadow-* utilities at - build time, so we set box-shadow directly rather than override tokens). ---- */ +/* ---- layered shadows (shared): a faint inset highlight + soft NEUTRAL ambient. + Elevation now comes mostly from the scrim lightness, so shadows stay subtle + and neutral — no colored glow (that was a source of muddiness). Applied per + data-slot below (Tailwind v4 inlines shadow-* utilities at build time). ---- */ .theme-dark, .theme-light { --cm-shadow-sm: - inset 0 1px 0 0 hsl(0 0% 100% / 0.05), - 0 1px 2px 0 hsl(var(--theme-shadow-color) / 0.4), - 0 2px 6px -1px hsl(var(--theme-shadow-color) / 0.3); + inset 0 1px 0 0 hsl(0 0% 100% / 0.04), 0 1px 2px 0 hsl(0 0% 0% / 0.35); --cm-shadow-lg: - inset 0 1px 0 0 hsl(0 0% 100% / 0.06), - 0 4px 8px -2px hsl(var(--theme-shadow-color) / 0.42), - 0 12px 28px -6px hsl(var(--theme-shadow-color) / 0.4), - 0 0 40px -8px hsl(var(--theme-shadow-color) / 0.25); + inset 0 1px 0 0 hsl(0 0% 100% / 0.05), 0 6px 16px -4px hsl(0 0% 0% / 0.4), + 0 12px 32px -8px hsl(0 0% 0% / 0.3); --cm-shadow-xl: - inset 0 1px 0 0 hsl(0 0% 100% / 0.07), - 0 8px 16px -4px hsl(var(--theme-shadow-color) / 0.45), - 0 20px 48px -10px hsl(var(--theme-shadow-color) / 0.45), - 0 0 64px -12px hsl(var(--theme-shadow-color) / 0.28); + inset 0 1px 0 0 hsl(0 0% 100% / 0.06), 0 12px 32px -6px hsl(0 0% 0% / 0.5), + 0 24px 56px -12px hsl(0 0% 0% / 0.4); } /* ========================================================================= - Palettes — each declares only its three tints, a full-page gradient (dim - wash + color layer, already fixed/cover), and a shadow hue (bare HSL - channels consumed by the shadow recipes above). + Palettes — each declares only its three tints + the raw color gradient + (`--theme-grad-color`, no scrim). The shared blocks above build the fixed + scrim-elevation levels and derive every token from these. ========================================================================= */ /* Crimson Moon */ -.crimson-moon { +.theme-crimson-moon { --theme-surface-tint: rgb(26 0 0); --theme-foreground-tint: rgb(255 229 229); - --theme-gradient: - linear-gradient(rgb(0 0 0 / 0.82), rgb(0 0 0 / 0.82)) fixed 0 0 / cover, - linear-gradient(64.92deg, #950909 16.17%, #000000 72%) fixed 0 0 / cover; - --theme-shadow-color: 0 72% 12%; + --theme-grad-color: linear-gradient(64.92deg, #950909 16.17%, #000000 72%); } /* Sepia */ -.sepia { +.theme-sepia { --theme-surface-tint: rgb(26 15 0); --theme-foreground-tint: rgb(255 243 229); - --theme-gradient: - linear-gradient(rgb(0 0 0 / 0.9), rgb(0 0 0 / 0.9)) fixed 0 0 / cover, - linear-gradient(69.98deg, #857664 14.14%, #5b4421 60.35%) fixed 0 0 / cover; - --theme-shadow-color: 28 70% 10%; + --theme-grad-color: linear-gradient(69.98deg, #857664 14.14%, #5b4421 60.35%); } /* Midnight Blurple */ -.midnight-blurple { +.theme-midnight-blurple { --theme-surface-tint: rgb(8 0 26); --theme-foreground-tint: rgb(162 153 255); - --theme-gradient: - linear-gradient(rgb(0 0 0 / 0.9), rgb(0 0 0 / 0.9)) fixed 0 0 / cover, - linear-gradient(48.17deg, #5348ca 11.21%, #140730 61.92%) fixed 0 0 / cover; - --theme-shadow-color: 250 65% 14%; + --theme-grad-color: linear-gradient(48.17deg, #5348ca 11.21%, #140730 61.92%); } /* Forest */ -.forest { +.theme-forest { --theme-surface-tint: rgb(0 26 2); --theme-foreground-tint: rgb(255 248 229); - --theme-gradient: - linear-gradient(rgb(0 0 0 / 0.9), rgb(0 0 0 / 0.9)) fixed 0 0 / cover, - linear-gradient( - 162.27deg, - #142215 11.2%, - #2d4d39 29.93%, - #454c32 48.64%, - #5a7c58 67.85%, - #a98e4b 83.54% - ) - fixed 0 0 / cover; - --theme-shadow-color: 130 60% 8%; + --theme-grad-color: linear-gradient( + 162.27deg, + #142215 11.2%, + #2d4d39 29.93%, + #454c32 48.64%, + #5a7c58 67.85%, + #a98e4b 83.54% + ); } /* Dusk */ -.dusk { +.theme-dusk { --theme-surface-tint: rgb(22 0 26); --theme-foreground-tint: rgb(153 182 255); - --theme-gradient: - linear-gradient(rgb(0 0 0 / 0.9), rgb(0 0 0 / 0.9)) fixed 0 0 / cover, - linear-gradient(180deg, #665069 12.84%, #91a3d1 85.99%) fixed 0 0 / cover; - --theme-shadow-color: 285 45% 14%; + --theme-grad-color: linear-gradient(180deg, #665069 12.84%, #91a3d1 85.99%); } /* Citrus Sherbet (light) */ -.citrus-sherbet { +.theme-citrus-sherbet { --theme-surface-tint: rgb(255 238 204); --theme-foreground-tint: rgb(77 23 0); - --theme-gradient: - linear-gradient(rgb(255 255 255 / 0.85), rgb(255 255 255 / 0.85)) fixed 0 0 - / cover, - linear-gradient(180deg, #f3b336 31.1%, #ee8558 67.09%) fixed 0 0 / cover; - --theme-shadow-color: 28 55% 45%; + --theme-grad-color: linear-gradient(180deg, #f3b336 31.1%, #ee8558 67.09%); } @theme inline { @@ -488,10 +429,56 @@ original stone themes) are never matched. ========================================================================= */ -/* Atmospheric page background: the palette's full gradient value (dim wash + - color layer, already fixed/cover) reads as depth behind the panels. */ :is(.theme-dark, .theme-light) body { - background: var(--theme-gradient); + background: var(--grad-base); +} +:is(.theme-dark, .theme-light) [data-slot="sidebar-inner"] { + background: var(--grad-chrome); /* left nav — darkest chrome */ +} +:is(.theme-dark, .theme-light) [data-slot="sidebar-inset"] { + background: var(--grad-base); /* main content — same level as the canvas */ +} +:is(.theme-dark, .theme-light) [data-slot="card"] { + background: var(--grad-raised); /* elevated — lightest scrim, glows */ + /* a hairline ring so cards always separate from the canvas, even where the + gradient is near-black and the scrim difference alone is invisible */ + box-shadow: + var(--cm-shadow-sm), + inset 0 0 0 1px var(--border); +} + +/* Header: frosted glass over the fixed gradient (sticky, so content blurs under it). */ +:is(.theme-dark, .theme-light) header { + background: var(--header-bg); + backdrop-filter: blur(12px) saturate(1.2); + -webkit-backdrop-filter: blur(12px) saturate(1.2); +} + +/* Floating overlays: dark glass (translucent `--popover`) + blur — transform-safe + (a `fixed` gradient would break under their translate()); the translucency lets + the theme tint through. */ +:is(.theme-dark, .theme-light) + :is( + [data-slot="popover-content"], + [data-slot="dropdown-menu-content"], + [data-slot="select-content"], + [data-slot="dialog-content"], + [data-slot="alert-dialog-content"], + [data-slot="sheet-content"], + [data-slot="drawer-content"], + [data-slot="tooltip-content"] + ) { + background: var(--popover); + backdrop-filter: blur(16px) saturate(1.2); + -webkit-backdrop-filter: blur(16px) saturate(1.2); +} + +/* Tabs: the default (pill) active tab uses `bg-input/30`, which is invisible over + a translucent input — give it a clear neutral lift instead (skip line variant). */ +:is(.theme-dark, .theme-light) + [data-slot="tabs-list"]:not([data-variant="line"]) + [data-slot="tabs-trigger"][data-active] { + background: var(--mod-active); } /* Text selection tinted from the foreground tint. */ @@ -616,18 +603,18 @@ transform: translateY(0); } -/* Tables: tinted hover + selected rows. */ +/* Tables: neutral hover + selected rows */ :is(.theme-dark, .theme-light) [data-slot="table-row"]:hover { - background: color-mix(in oklab, var(--theme-foreground-tint) 4%, transparent); + background: var(--mod-hover); } :is(.theme-dark, .theme-light) [data-slot="table-row"][data-state="selected"] { - background: color-mix(in oklab, var(--theme-foreground-tint) 7%, transparent); + background: var(--mod-active); } -/* Inputs: tinted fill, restrained blur, themed focus glow. */ +/* Inputs: recessed translucent fill, restrained blur, themed focus glow. */ :is(.theme-dark, .theme-light) :is([data-slot="input"], [data-slot="textarea"]) { - background: color-mix(in oklab, var(--input) 88%, transparent); + background: var(--input); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); } @@ -638,9 +625,9 @@ 0 0 18px -4px color-mix(in oklab, var(--ring) 40%, transparent); } -/* Sidebar menu: gentle tinted hover, no loud background. */ +/* Sidebar menu: gentle neutral hover, no loud background. */ :is(.theme-dark, .theme-light) [data-slot="sidebar-menu-button"]:hover { - background: color-mix(in oklab, var(--theme-foreground-tint) 5%, transparent); + background: var(--mod-hover); } /* ========================================================================= From 7e5d2b9efe66126506c6be6bf538c856b03193a4 Mon Sep 17 00:00:00 2001 From: Krishna Santosh <75202541+krishna-santosh@users.noreply.github.com> Date: Thu, 2 Jul 2026 00:25:37 +0530 Subject: [PATCH 06/16] feat(dashboard): add Blurple Twilight and Aurora themes --- .../src/components/theme-provider.tsx | 4 +++ .../dashboard/src/components/theme-toggle.tsx | 2 ++ apps/dashboard/src/styles.css | 34 +++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/apps/dashboard/src/components/theme-provider.tsx b/apps/dashboard/src/components/theme-provider.tsx index 767d76e9..7c713751 100644 --- a/apps/dashboard/src/components/theme-provider.tsx +++ b/apps/dashboard/src/components/theme-provider.tsx @@ -7,8 +7,10 @@ export type Theme = | "crimson-moon" | "sepia" | "midnight-blurple" + | "blurple-twilight" | "forest" | "dusk" + | "aurora" | "citrus-sherbet"; // Custom palettes are applied to as a `theme-` class (e.g. @@ -20,8 +22,10 @@ const DARK_THEMES = [ "crimson-moon", "sepia", "midnight-blurple", + "blurple-twilight", "forest", "dusk", + "aurora", ] as const; const LIGHT_THEMES = ["citrus-sherbet"] as const; const CUSTOM_THEMES: readonly string[] = [...DARK_THEMES, ...LIGHT_THEMES]; diff --git a/apps/dashboard/src/components/theme-toggle.tsx b/apps/dashboard/src/components/theme-toggle.tsx index 2facf8fe..68b9a8cb 100644 --- a/apps/dashboard/src/components/theme-toggle.tsx +++ b/apps/dashboard/src/components/theme-toggle.tsx @@ -20,8 +20,10 @@ const NITRO_THEMES: { key: Theme; label: string }[] = [ { key: "crimson-moon", label: "Crimson Moon" }, { key: "sepia", label: "Sepia" }, { key: "midnight-blurple", label: "Midnight Blurple" }, + { key: "blurple-twilight", label: "Blurple Twilight" }, { key: "forest", label: "Forest" }, { key: "dusk", label: "Dusk" }, + { key: "aurora", label: "Aurora" }, { key: "citrus-sherbet", label: "Citrus Sherbet" }, ]; diff --git a/apps/dashboard/src/styles.css b/apps/dashboard/src/styles.css index 5f59c441..d24555ce 100644 --- a/apps/dashboard/src/styles.css +++ b/apps/dashboard/src/styles.css @@ -338,6 +338,13 @@ --theme-grad-color: linear-gradient(48.17deg, #5348ca 11.21%, #140730 61.92%); } +/* Blurple Twilight */ +.theme-blurple-twilight { + --theme-surface-tint: rgb(2 0 26); + --theme-foreground-tint: rgb(153 163 255); + --theme-grad-color: linear-gradient(47.61deg, #2c3fe7 11.18%, #261d83 64.54%); +} + /* Forest */ .theme-forest { --theme-surface-tint: rgb(0 26 2); @@ -359,6 +366,20 @@ --theme-grad-color: linear-gradient(180deg, #665069 12.84%, #91a3d1 85.99%); } +/* Aurora */ +.theme-aurora { + --theme-surface-tint: rgb(0 9 26); + --theme-foreground-tint: rgb(153 171 255); + --theme-grad-color: linear-gradient( + 239.16deg, + #062053 10.39%, + #191fbb 26.87%, + #13929a 48.31%, + #218573 64.98%, + #051a81 92.5% + ); +} + /* Citrus Sherbet (light) */ .theme-citrus-sherbet { --theme-surface-tint: rgb(255 238 204); @@ -654,6 +675,9 @@ .theme-swatch-midnight-blurple { background: linear-gradient(48.17deg, #5348ca, #140730); } +.theme-swatch-blurple-twilight { + background: linear-gradient(47.61deg, #2c3fe7, #261d83); +} .theme-swatch-forest { background: linear-gradient( 162.27deg, @@ -667,6 +691,16 @@ .theme-swatch-dusk { background: linear-gradient(180deg, #665069, #91a3d1); } +.theme-swatch-aurora { + background: linear-gradient( + 239.16deg, + #062053, + #191fbb, + #13929a, + #218573, + #051a81 + ); +} .theme-swatch-citrus-sherbet { background: linear-gradient(180deg, #f3b336, #ee8558); } From c30e17e05a12967f1807ac934471d61cb992fb9f Mon Sep 17 00:00:00 2001 From: Krishna Santosh <75202541+krishna-santosh@users.noreply.github.com> Date: Thu, 2 Jul 2026 20:35:17 +0530 Subject: [PATCH 07/16] fix(dashboard): refine dark and light theme gradients and component surfaces --- apps/dashboard/src/styles.css | 152 ++++++++++++++++++---------------- 1 file changed, 80 insertions(+), 72 deletions(-) diff --git a/apps/dashboard/src/styles.css b/apps/dashboard/src/styles.css index d24555ce..170077b0 100644 --- a/apps/dashboard/src/styles.css +++ b/apps/dashboard/src/styles.css @@ -101,38 +101,42 @@ .theme-dark { color-scheme: dark; - /* Each STRUCTURAL surface paints `scrim + color gradient`, - viewport-fixed + cover, so they all sample ONE continuous - gradient and differ only by scrim opacity (elevation). Painted on the - structural containers below; component tokens stay solid (crisp). */ - --grad-chrome: - linear-gradient(rgb(0 0 0 / 0.86), rgb(0 0 0 / 0.86)) fixed 0 0 / cover, - var(--theme-grad-color) fixed 0 0 / cover; - --grad-base: - linear-gradient(rgb(0 0 0 / 0.82), rgb(0 0 0 / 0.82)) fixed 0 0 / cover, - var(--theme-grad-color) fixed 0 0 / cover; - --grad-raised: - linear-gradient(rgb(0 0 0 / 0.58), rgb(0 0 0 / 0.58)) fixed 0 0 / cover, - var(--theme-grad-color) fixed 0 0 / cover; + /* The gradient is painted ONCE, on the fixed `body::before` shell layer + (scrim over `--theme-grad-color`). Every surface above it is either an + alpha-neutral veil (guaranteed monotonic darken/lighten over the shell) + or a fully OPAQUE solid mixed from the surface tint (chrome, floating). */ + --shell-scrim: rgb(0 0 0 / 0.82); + + /* chrome: sidebar darkens the shell; header + mobile sidebar are opaque */ + --surface-chrome: rgb(0 0 0 / 0.25); + --surface-chrome-solid: color-mix( + in oklab, + var(--theme-surface-tint) 65%, + oklch(0.17 0 0) + ); /* neutral hover / selected overlays */ --mod-hover: color-mix(in oklab, white 6%, transparent); --mod-active: color-mix(in oklab, white 12%, transparent); - /* TRANSLUCENT component surfaces: veils over the painted gradient, so every - input / chip / menu / tab picks up the theme instead of reading as flat gray. - Dark veils recess (input, muted, popover); light veils lift (card, chips, - hovers). Legible because they sit over the already-dark gradient. */ + /* Component surfaces. Veils are alpha-neutral so they composite predictably + over the shell; the card lift comes from the near-white foreground tint, + which is STRICTLY lighter than any backdrop — even pure black gradient + stops — so cards always separate. Floating (`--popover`) is opaque: + overlays cover arbitrary content and must never bleed it through. */ --sidebar: color-mix(in oklab, oklch(0.16 0 0) 55%, transparent); --background: color-mix(in oklab, oklch(0.16 0 0) 14%, transparent); - --card: color-mix(in oklab, white 5%, transparent); - --popover: color-mix(in oklab, oklch(0.15 0 0) 82%, transparent); + --card: color-mix(in oklab, var(--theme-foreground-tint) 7%, transparent); + --popover: color-mix( + in oklab, + var(--theme-surface-tint) 55%, + oklch(0.23 0 0) + ); --muted: color-mix(in oklab, oklch(0 0 0) 22%, transparent); --secondary: color-mix(in oklab, white 7%, transparent); --accent: color-mix(in oklab, white 11%, transparent); --sidebar-accent: color-mix(in oklab, white 10%, transparent); - --input: color-mix(in oklab, oklch(0 0 0) 42%, transparent); - --header-bg: color-mix(in oklab, oklch(0.14 0 0) 55%, transparent); + --input: color-mix(in oklab, black 25%, transparent); /* primary: bright surface (buttons), deep-tint text */ --primary: color-mix( @@ -157,13 +161,13 @@ var(--theme-foreground-tint) 22%, white ); - --muted-foreground: color-mix(in oklab, var(--foreground) 62%, transparent); + --muted-foreground: color-mix(in oklab, var(--foreground) 72%, transparent); --secondary-foreground: var(--foreground); --accent-foreground: var(--foreground); --sidebar-accent-foreground: var(--foreground); /* borders / ring: neutral, low alpha */ - --border: color-mix(in oklab, white 10%, transparent); + --border: color-mix(in oklab, white 12%, transparent); --sidebar-border: color-mix(in oklab, white 7%, transparent); --ring: color-mix(in oklab, var(--theme-foreground-tint) 55%, transparent); --sidebar-ring: var(--ring); @@ -204,37 +208,32 @@ .theme-light { color-scheme: light; - /* light mirror: structural surfaces paint the color gradient under a WHITE - scrim (lighter scrim = more color), viewport-fixed + cover. */ - --grad-chrome: - linear-gradient(rgb(255 255 255 / 0.6), rgb(255 255 255 / 0.6)) fixed 0 0 / - cover, - var(--theme-grad-color) fixed 0 0 / cover; - --grad-base: - linear-gradient(rgb(255 255 255 / 0.5), rgb(255 255 255 / 0.5)) fixed 0 0 / - cover, - var(--theme-grad-color) fixed 0 0 / cover; - --grad-raised: - linear-gradient(rgb(255 255 255 / 0.24), rgb(255 255 255 / 0.24)) fixed 0 0 - / cover, - var(--theme-grad-color) fixed 0 0 / cover; + /* light mirror: WHITE shell scrim, white veils lift, black veils recess. + Same roles as the dark layer, so new light palettes are 3-line blocks. */ + --shell-scrim: rgb(255 255 255 / 0.5); + + --surface-chrome: rgb(255 255 255 / 0.22); + --surface-chrome-solid: color-mix( + in oklab, + var(--theme-surface-tint) 55%, + white + ); --mod-hover: color-mix(in oklab, black 5%, transparent); --mod-active: color-mix(in oklab, black 9%, transparent); - /* TRANSLUCENT component surfaces (mirror of the dark theme): veils over the - painted gradient so components pick up the theme. White veils lift, black - veils recess. */ + /* Component surfaces: `--card` (white 60%) is strictly lighter than any + light-theme backdrop, so lift is guaranteed; `--popover` is an opaque + tinted off-white (nothing bleeds through floating layers). */ --sidebar: color-mix(in oklab, white 55%, transparent); --background: color-mix(in oklab, white 16%, transparent); - --card: color-mix(in oklab, white 32%, transparent); - --popover: color-mix(in oklab, white 82%, transparent); + --card: color-mix(in oklab, white 60%, transparent); + --popover: color-mix(in oklab, var(--theme-surface-tint) 35%, white); --muted: color-mix(in oklab, black 6%, transparent); --secondary: color-mix(in oklab, black 5%, transparent); --accent: color-mix(in oklab, black 8%, transparent); --sidebar-accent: color-mix(in oklab, black 8%, transparent); - --input: color-mix(in oklab, white 55%, transparent); - --header-bg: color-mix(in oklab, white 55%, transparent); + --input: color-mix(in oklab, white 45%, transparent); /* primary: dark warm surface (buttons), light text */ --primary: color-mix( @@ -259,12 +258,12 @@ var(--theme-foreground-tint) 82%, black ); - --muted-foreground: color-mix(in oklab, var(--foreground) 58%, transparent); + --muted-foreground: color-mix(in oklab, var(--foreground) 62%, transparent); --secondary-foreground: var(--foreground); --accent-foreground: var(--foreground); --sidebar-accent-foreground: var(--foreground); - --border: color-mix(in oklab, black 12%, transparent); + --border: color-mix(in oklab, black 15%, transparent); --sidebar-border: color-mix(in oklab, black 9%, transparent); --ring: color-mix(in oklab, var(--theme-foreground-tint) 45%, transparent); --sidebar-ring: var(--ring); @@ -313,8 +312,8 @@ /* ========================================================================= Palettes — each declares only its three tints + the raw color gradient - (`--theme-grad-color`, no scrim). The shared blocks above build the fixed - scrim-elevation levels and derive every token from these. + (`--theme-grad-color`, no scrim). The shared blocks above paint the shell + scrim over it and derive every token from these. ========================================================================= */ /* Crimson Moon */ @@ -450,34 +449,42 @@ original stone themes) are never matched. ========================================================================= */ +/* The shell: the theme gradient painted exactly once, on a fixed pseudo behind + everything. No `background-attachment: fixed` (broken on iOS Safari, breaks + under transforms) — surfaces above are alpha veils, so the "one continuous + gradient" look survives by construction. */ :is(.theme-dark, .theme-light) body { - background: var(--grad-base); + background: transparent; } -:is(.theme-dark, .theme-light) [data-slot="sidebar-inner"] { - background: var(--grad-chrome); /* left nav — darkest chrome */ +:is(.theme-dark, .theme-light) body::before { + content: ""; + position: fixed; + inset: 0; + z-index: -1; + pointer-events: none; + background: + linear-gradient(var(--shell-scrim), var(--shell-scrim)), + var(--theme-grad-color); } -:is(.theme-dark, .theme-light) [data-slot="sidebar-inset"] { - background: var(--grad-base); /* main content — same level as the canvas */ +:is(.theme-dark, .theme-light) [data-slot="sidebar-inner"] { + background: var(--surface-chrome); /* left nav — darkens the shell */ } :is(.theme-dark, .theme-light) [data-slot="card"] { - background: var(--grad-raised); /* elevated — lightest scrim, glows */ - /* a hairline ring so cards always separate from the canvas, even where the - gradient is near-black and the scrim difference alone is invisible */ + /* lifted by the near-white tint veil — strictly lighter than any backdrop — + plus a hairline ring as the second separation channel */ + background: var(--card); box-shadow: var(--cm-shadow-sm), inset 0 0 0 1px var(--border); } -/* Header: frosted glass over the fixed gradient (sticky, so content blurs under it). */ +/* Header: opaque chrome, same layer family as the sidebar. */ :is(.theme-dark, .theme-light) header { - background: var(--header-bg); - backdrop-filter: blur(12px) saturate(1.2); - -webkit-backdrop-filter: blur(12px) saturate(1.2); + background: var(--surface-chrome-solid); } -/* Floating overlays: dark glass (translucent `--popover`) + blur — transform-safe - (a `fixed` gradient would break under their translate()); the translucency lets - the theme tint through. */ +/* Floating overlays: fully OPAQUE tinted surface (`--popover`), no blur — + they cover arbitrary content, so nothing may bleed through. */ :is(.theme-dark, .theme-light) :is( [data-slot="popover-content"], @@ -490,8 +497,13 @@ [data-slot="tooltip-content"] ) { background: var(--popover); - backdrop-filter: blur(16px) saturate(1.2); - -webkit-backdrop-filter: blur(16px) saturate(1.2); +} + +/* Mobile sidebar sheet (its popup's data-slot is overridden to "sidebar"): + opaque chrome, never a translucent veil floating over page content. */ +:is(.theme-dark, .theme-light) [data-slot="sidebar"][data-mobile="true"] { + background: var(--surface-chrome-solid); + box-shadow: var(--cm-shadow-xl); } /* Tabs: the default (pill) active tab uses `bg-input/30`, which is invisible over @@ -581,10 +593,8 @@ } } -/* Layered shadows on the shadow-bearing slots (sm cards, lg menus, xl modals). */ -:is(.theme-dark, .theme-light) [data-slot="card"] { - box-shadow: var(--cm-shadow-sm); -} +/* Layered shadows on the shadow-bearing slots (lg menus, xl modals; the card + shadow lives in the card rule above, merged with its hairline ring). */ :is(.theme-dark, .theme-light) :is( [data-slot="popover-content"], @@ -632,12 +642,10 @@ background: var(--mod-active); } -/* Inputs: recessed translucent fill, restrained blur, themed focus glow. */ +/* Inputs: recessed translucent fill, themed focus glow. */ :is(.theme-dark, .theme-light) :is([data-slot="input"], [data-slot="textarea"]) { background: var(--input); - backdrop-filter: blur(6px); - -webkit-backdrop-filter: blur(6px); } :is(.theme-dark, .theme-light) :is([data-slot="input"], [data-slot="textarea"]):focus-visible { From 59200a252dbd43705af40b00e3188237703dcba2 Mon Sep 17 00:00:00 2001 From: Krishna Santosh <75202541+krishna-santosh@users.noreply.github.com> Date: Thu, 2 Jul 2026 23:01:13 +0530 Subject: [PATCH 08/16] fix(dashboard): update header transparency and backdrop filter --- apps/dashboard/src/styles.css | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/dashboard/src/styles.css b/apps/dashboard/src/styles.css index 170077b0..94ae196f 100644 --- a/apps/dashboard/src/styles.css +++ b/apps/dashboard/src/styles.css @@ -107,7 +107,8 @@ or a fully OPAQUE solid mixed from the surface tint (chrome, floating). */ --shell-scrim: rgb(0 0 0 / 0.82); - /* chrome: sidebar darkens the shell; header + mobile sidebar are opaque */ + /* chrome: sidebar darkens the shell; mobile sidebar is opaque; the header + is transparent (shell gradient shows through, blur-on-scroll only) */ --surface-chrome: rgb(0 0 0 / 0.25); --surface-chrome-solid: color-mix( in oklab, @@ -478,9 +479,12 @@ inset 0 0 0 1px var(--border); } -/* Header: opaque chrome, same layer family as the sidebar. */ +/* Header: fully transparent — the shell gradient shows straight through. The + blur only becomes visible when content scrolls underneath (keeps it legible). */ :is(.theme-dark, .theme-light) header { - background: var(--surface-chrome-solid); + background: transparent; + backdrop-filter: blur(12px) saturate(1.2); + -webkit-backdrop-filter: blur(12px) saturate(1.2); } /* Floating overlays: fully OPAQUE tinted surface (`--popover`), no blur — @@ -499,6 +503,12 @@ background: var(--popover); } +/* Tooltip ships inverted colors (`bg-foreground text-background`); with its + background overridden to `--popover` above, its text must follow suit. */ +:is(.theme-dark, .theme-light) [data-slot="tooltip-content"] { + color: var(--popover-foreground); +} + /* Mobile sidebar sheet (its popup's data-slot is overridden to "sidebar"): opaque chrome, never a translucent veil floating over page content. */ :is(.theme-dark, .theme-light) [data-slot="sidebar"][data-mobile="true"] { From eebc8b738fdb165780fbc32fc44e23aa2c58e53b Mon Sep 17 00:00:00 2001 From: Krishna Santosh <75202541+krishna-santosh@users.noreply.github.com> Date: Thu, 2 Jul 2026 23:16:19 +0530 Subject: [PATCH 09/16] feat(dashboard): add new themes --- .../src/components/theme-provider.tsx | 24 +++- .../dashboard/src/components/theme-toggle.tsx | 9 ++ apps/dashboard/src/styles.css | 129 ++++++++++++++++++ 3 files changed, 160 insertions(+), 2 deletions(-) diff --git a/apps/dashboard/src/components/theme-provider.tsx b/apps/dashboard/src/components/theme-provider.tsx index 7c713751..7368c9dd 100644 --- a/apps/dashboard/src/components/theme-provider.tsx +++ b/apps/dashboard/src/components/theme-provider.tsx @@ -11,7 +11,16 @@ export type Theme = | "forest" | "dusk" | "aurora" - | "citrus-sherbet"; + | "sunset" + | "mars" + | "retro-storm" + | "under-the-sea" + | "citrus-sherbet" + | "desert-khaki" + | "sunrise" + | "hanami" + | "cotton-candy" + | "mint-apple"; // Custom palettes are applied to as a `theme-` class (e.g. // `theme-sepia`) — the `theme-` prefix keeps them from colliding with Tailwind @@ -26,8 +35,19 @@ const DARK_THEMES = [ "forest", "dusk", "aurora", + "sunset", + "mars", + "retro-storm", + "under-the-sea", +] as const; +const LIGHT_THEMES = [ + "citrus-sherbet", + "desert-khaki", + "sunrise", + "hanami", + "cotton-candy", + "mint-apple", ] as const; -const LIGHT_THEMES = ["citrus-sherbet"] as const; const CUSTOM_THEMES: readonly string[] = [...DARK_THEMES, ...LIGHT_THEMES]; type ThemeProviderProps = { diff --git a/apps/dashboard/src/components/theme-toggle.tsx b/apps/dashboard/src/components/theme-toggle.tsx index 68b9a8cb..86162067 100644 --- a/apps/dashboard/src/components/theme-toggle.tsx +++ b/apps/dashboard/src/components/theme-toggle.tsx @@ -24,7 +24,16 @@ const NITRO_THEMES: { key: Theme; label: string }[] = [ { key: "forest", label: "Forest" }, { key: "dusk", label: "Dusk" }, { key: "aurora", label: "Aurora" }, + { key: "sunset", label: "Sunset" }, + { key: "mars", label: "Mars" }, + { key: "retro-storm", label: "Retro Storm" }, + { key: "under-the-sea", label: "Under the Sea" }, { key: "citrus-sherbet", label: "Citrus Sherbet" }, + { key: "desert-khaki", label: "Desert Khaki" }, + { key: "sunrise", label: "Sunrise" }, + { key: "hanami", label: "Hanami" }, + { key: "cotton-candy", label: "Cotton Candy" }, + { key: "mint-apple", label: "Mint Apple" }, ]; export function ModeToggle() { diff --git a/apps/dashboard/src/styles.css b/apps/dashboard/src/styles.css index 94ae196f..81f1b1ed 100644 --- a/apps/dashboard/src/styles.css +++ b/apps/dashboard/src/styles.css @@ -380,6 +380,53 @@ ); } +/* Sunset */ +.theme-sunset { + --theme-surface-tint: rgb(8 0 26); + --theme-foreground-tint: rgb(255 239 229); + --theme-grad-color: linear-gradient( + 141.68deg, + #48288c 27.57%, + #db7f4b 71.25% + ); +} + +/* Mars */ +.theme-mars { + --theme-surface-tint: rgb(26 6 0); + --theme-foreground-tint: rgb(255 236 229); + --theme-grad-color: linear-gradient( + 170.82deg, + #895240 14.61%, + #8f4343 74.62% + ); +} + +/* Retro Storm */ +.theme-retro-storm { + --theme-surface-tint: rgb(0 16 26); + --theme-foreground-tint: rgb(153 218 255); + --theme-grad-color: linear-gradient( + 148.71deg, + #3a7ca1 5.64%, + #58579a 26.38%, + #58579a 49.92%, + #3a7ca1 73.12% + ); +} + +/* Under the Sea */ +.theme-under-the-sea { + --theme-surface-tint: rgb(2 26 0); + --theme-foreground-tint: rgb(229 255 253); + --theme-grad-color: linear-gradient( + 179.14deg, + #647962 1.91%, + #588575 48.99%, + #6a8482 96.35% + ); +} + /* Citrus Sherbet (light) */ .theme-citrus-sherbet { --theme-surface-tint: rgb(255 238 204); @@ -387,6 +434,61 @@ --theme-grad-color: linear-gradient(180deg, #f3b336 31.1%, #ee8558 67.09%); } +/* Desert Khaki (light) */ +.theme-desert-khaki { + --theme-surface-tint: rgb(255 228 204); + --theme-foreground-tint: rgb(77 64 0); + --theme-grad-color: linear-gradient( + 38.99deg, + #e7dbd0 12.92%, + #dfd0b2 32.92%, + #e0d6a3 52.11% + ); +} + +/* Sunrise (light) */ +.theme-sunrise { + --theme-surface-tint: rgb(255 247 204); + --theme-foreground-tint: rgb(77 0 42); + --theme-grad-color: linear-gradient( + 154.19deg, + #9f4175 8.62%, + #c49064 48.07%, + #a6953d 76.04% + ); +} + +/* Hanami (light) */ +.theme-hanami { + --theme-surface-tint: rgb(208 255 204); + --theme-foreground-tint: rgb(77 0 10); + --theme-grad-color: linear-gradient( + 38.08deg, + #efaab3 3.56%, + #efd696 35.49%, + #a6daa2 68.78% + ); +} + +/* Cotton Candy (light) */ +.theme-cotton-candy { + --theme-surface-tint: rgb(204 215 255); + --theme-foreground-tint: rgb(77 0 14); + --theme-grad-color: linear-gradient(180.14deg, #f4abb8 8.5%, #b1c2fc 94.28%); +} + +/* Mint Apple (light) */ +.theme-mint-apple { + --theme-surface-tint: rgb(232 255 204); + --theme-foreground-tint: rgb(0 77 58); + --theme-grad-color: linear-gradient( + 180deg, + #56b69f 6.15%, + #63bc61 48.7%, + #9eca67 93.07% + ); +} + @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); @@ -722,3 +824,30 @@ .theme-swatch-citrus-sherbet { background: linear-gradient(180deg, #f3b336, #ee8558); } +.theme-swatch-sunset { + background: linear-gradient(141.68deg, #48288c, #db7f4b); +} +.theme-swatch-mars { + background: linear-gradient(170.82deg, #895240, #8f4343); +} +.theme-swatch-retro-storm { + background: linear-gradient(148.71deg, #3a7ca1, #58579a, #58579a, #3a7ca1); +} +.theme-swatch-under-the-sea { + background: linear-gradient(179.14deg, #647962, #588575, #6a8482); +} +.theme-swatch-desert-khaki { + background: linear-gradient(38.99deg, #e7dbd0, #dfd0b2, #e0d6a3); +} +.theme-swatch-sunrise { + background: linear-gradient(154.19deg, #9f4175, #c49064, #a6953d); +} +.theme-swatch-hanami { + background: linear-gradient(38.08deg, #efaab3, #efd696, #a6daa2); +} +.theme-swatch-cotton-candy { + background: linear-gradient(180.14deg, #f4abb8, #b1c2fc); +} +.theme-swatch-mint-apple { + background: linear-gradient(180deg, #56b69f, #63bc61, #9eca67); +} From 7c1f82d9bb795333e3bf26b9e4e5f4f1d83f170e Mon Sep 17 00:00:00 2001 From: Krishna Santosh <75202541+krishna-santosh@users.noreply.github.com> Date: Thu, 2 Jul 2026 23:58:39 +0530 Subject: [PATCH 10/16] feat(dashboard): add new themes --- .../src/components/theme-provider.tsx | 4 +++ .../dashboard/src/components/theme-toggle.tsx | 2 ++ apps/dashboard/src/styles.css | 30 +++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/apps/dashboard/src/components/theme-provider.tsx b/apps/dashboard/src/components/theme-provider.tsx index 7368c9dd..05caa68a 100644 --- a/apps/dashboard/src/components/theme-provider.tsx +++ b/apps/dashboard/src/components/theme-provider.tsx @@ -15,6 +15,8 @@ export type Theme = | "mars" | "retro-storm" | "under-the-sea" + | "strawberry-lemonade" + | "neon-nights" | "citrus-sherbet" | "desert-khaki" | "sunrise" @@ -39,6 +41,8 @@ const DARK_THEMES = [ "mars", "retro-storm", "under-the-sea", + "strawberry-lemonade", + "neon-nights", ] as const; const LIGHT_THEMES = [ "citrus-sherbet", diff --git a/apps/dashboard/src/components/theme-toggle.tsx b/apps/dashboard/src/components/theme-toggle.tsx index 86162067..88a1e8df 100644 --- a/apps/dashboard/src/components/theme-toggle.tsx +++ b/apps/dashboard/src/components/theme-toggle.tsx @@ -28,6 +28,8 @@ const NITRO_THEMES: { key: Theme; label: string }[] = [ { key: "mars", label: "Mars" }, { key: "retro-storm", label: "Retro Storm" }, { key: "under-the-sea", label: "Under the Sea" }, + { key: "strawberry-lemonade", label: "Strawberry Lemonade" }, + { key: "neon-nights", label: "Neon Nights" }, { key: "citrus-sherbet", label: "Citrus Sherbet" }, { key: "desert-khaki", label: "Desert Khaki" }, { key: "sunrise", label: "Sunrise" }, diff --git a/apps/dashboard/src/styles.css b/apps/dashboard/src/styles.css index 81f1b1ed..b0b1c1a0 100644 --- a/apps/dashboard/src/styles.css +++ b/apps/dashboard/src/styles.css @@ -427,6 +427,30 @@ ); } +/* Strawberry Lemonade */ +.theme-strawberry-lemonade { + --theme-surface-tint: rgb(26 0 14); + --theme-foreground-tint: rgb(255 246 229); + --theme-grad-color: linear-gradient( + 161.03deg, + #af1a6c 18.79%, + #c26b20 49.76%, + #e7a525 80.72% + ); +} + +/* Neon Nights */ +.theme-neon-nights { + --theme-surface-tint: rgb(26 0 20); + --theme-foreground-tint: rgb(229 255 253); + --theme-grad-color: linear-gradient( + 180deg, + #01a89e 0%, + #7d60ba 50%, + #b43898 100% + ); +} + /* Citrus Sherbet (light) */ .theme-citrus-sherbet { --theme-surface-tint: rgb(255 238 204); @@ -833,6 +857,12 @@ .theme-swatch-retro-storm { background: linear-gradient(148.71deg, #3a7ca1, #58579a, #58579a, #3a7ca1); } +.theme-swatch-strawberry-lemonade { + background: linear-gradient(161.03deg, #af1a6c, #c26b20, #e7a525); +} +.theme-swatch-neon-nights { + background: linear-gradient(180deg, #01a89e, #7d60ba, #b43898); +} .theme-swatch-under-the-sea { background: linear-gradient(179.14deg, #647962, #588575, #6a8482); } From 621d5ea277d49277aed347613e66e08b18c5676d Mon Sep 17 00:00:00 2001 From: Krishna Santosh <75202541+krishna-santosh@users.noreply.github.com> Date: Thu, 2 Jul 2026 23:59:49 +0530 Subject: [PATCH 11/16] feat(dashboard): add scroll area to theme dropdown --- apps/dashboard/src/components/theme-toggle.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/dashboard/src/components/theme-toggle.tsx b/apps/dashboard/src/components/theme-toggle.tsx index 88a1e8df..2229b1a9 100644 --- a/apps/dashboard/src/components/theme-toggle.tsx +++ b/apps/dashboard/src/components/theme-toggle.tsx @@ -8,6 +8,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { ScrollArea } from "@/components/ui/scroll-area"; import { cn } from "@/lib/utils"; const BASE_THEMES: { key: Theme; label: string }[] = [ @@ -67,7 +68,9 @@ export function ModeToggle() { {BASE_THEMES.map(renderItem)} - {NITRO_THEMES.map(renderItem)} + + {NITRO_THEMES.map(renderItem)} + ); From 96a4a225c9f7930f82ed571b222a8aceee72343a Mon Sep 17 00:00:00 2001 From: Krishna Santosh <75202541+krishna-santosh@users.noreply.github.com> Date: Fri, 3 Jul 2026 00:00:07 +0530 Subject: [PATCH 12/16] fix(dashboard): hide native scrollbars in scroll area to prevent double bars --- apps/dashboard/src/styles.css | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/dashboard/src/styles.css b/apps/dashboard/src/styles.css index b0b1c1a0..d8eb7f6d 100644 --- a/apps/dashboard/src/styles.css +++ b/apps/dashboard/src/styles.css @@ -696,6 +696,18 @@ background-clip: padding-box; } +/* ScrollArea (base-ui) draws its own themed thumb; the global `*` scrollbar + rules above would re-force the NATIVE scrollbar onto its viewport, giving + double bars (and the reserved 10px width causes phantom horizontal + overflow). Hide native scrollbars there entirely. */ +:is(.theme-dark, .theme-light) [data-slot="scroll-area-viewport"] { + scrollbar-width: none; +} +:is(.theme-dark, .theme-light) + [data-slot="scroll-area-viewport"]::-webkit-scrollbar { + display: none; +} + /* Premium hover timing (150–180ms) on the surfaces users actually touch. */ :is(.theme-dark, .theme-light) :is( From 846d699a1c86d34036d353977aa1a82e875e0a17 Mon Sep 17 00:00:00 2001 From: Krishna Santosh <75202541+krishna-santosh@users.noreply.github.com> Date: Fri, 3 Jul 2026 00:00:28 +0530 Subject: [PATCH 13/16] fix(dashboard): ensure tooltip colors and arrow match popover background --- apps/dashboard/src/styles.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/dashboard/src/styles.css b/apps/dashboard/src/styles.css index d8eb7f6d..527d2993 100644 --- a/apps/dashboard/src/styles.css +++ b/apps/dashboard/src/styles.css @@ -630,10 +630,15 @@ } /* Tooltip ships inverted colors (`bg-foreground text-background`); with its - background overridden to `--popover` above, its text must follow suit. */ + background overridden to `--popover` above, its text and its arrow (the + little `bg-foreground fill-foreground` diamond) must follow suit. */ :is(.theme-dark, .theme-light) [data-slot="tooltip-content"] { color: var(--popover-foreground); } +:is(.theme-dark, .theme-light) [data-slot="tooltip-content"] .bg-foreground { + background: var(--popover); + fill: var(--popover); +} /* Mobile sidebar sheet (its popup's data-slot is overridden to "sidebar"): opaque chrome, never a translucent veil floating over page content. */ From 8119261d69fca8a93e2d1773da89e8ae26ddc530 Mon Sep 17 00:00:00 2001 From: Krishna Santosh <75202541+krishna-santosh@users.noreply.github.com> Date: Fri, 3 Jul 2026 00:08:09 +0530 Subject: [PATCH 14/16] fix(dashboard): add checkmark to active theme in dropdown menu --- .../dashboard/src/components/theme-toggle.tsx | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/apps/dashboard/src/components/theme-toggle.tsx b/apps/dashboard/src/components/theme-toggle.tsx index 2229b1a9..49d4cb76 100644 --- a/apps/dashboard/src/components/theme-toggle.tsx +++ b/apps/dashboard/src/components/theme-toggle.tsx @@ -1,4 +1,4 @@ -import { Palette } from "lucide-react"; +import { Check, Palette } from "lucide-react"; import { type Theme, useTheme } from "@/components/theme-provider"; import { Button } from "@/components/ui/button"; import { @@ -40,20 +40,31 @@ const NITRO_THEMES: { key: Theme; label: string }[] = [ ]; export function ModeToggle() { - const { setTheme } = useTheme(); + const { theme, setTheme } = useTheme(); - const renderItem = ({ key, label }: { key: Theme; label: string }) => ( - setTheme(key)}> - - ); + const renderItem = ({ key, label }: { key: Theme; label: string }) => { + const active = theme === key; + return ( + setTheme(key)} + > + + ); + }; return ( From 93fe670798afa1e94291564f37f6ee9e6f7b72ae Mon Sep 17 00:00:00 2001 From: Krishna Santosh <75202541+krishna-santosh@users.noreply.github.com> Date: Fri, 3 Jul 2026 00:09:47 +0530 Subject: [PATCH 15/16] fix(dashboard): update settings button active state check to use startsWith --- apps/dashboard/src/components/sidebar/app-sidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/src/components/sidebar/app-sidebar.tsx b/apps/dashboard/src/components/sidebar/app-sidebar.tsx index 983b6a3e..1b660582 100644 --- a/apps/dashboard/src/components/sidebar/app-sidebar.tsx +++ b/apps/dashboard/src/components/sidebar/app-sidebar.tsx @@ -130,7 +130,7 @@ export function AppSidebar({ ...props }: ComponentProps) { } > From 7575e216d2d803ed49c0fc97faab616137a60278 Mon Sep 17 00:00:00 2001 From: Krishna Santosh <75202541+krishna-santosh@users.noreply.github.com> Date: Fri, 3 Jul 2026 10:08:17 +0530 Subject: [PATCH 16/16] fix(dashboard): replace DropdownMenuItem with DropdownMenuRadioItem for theme selection --- .../dashboard/src/components/theme-toggle.tsx | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/apps/dashboard/src/components/theme-toggle.tsx b/apps/dashboard/src/components/theme-toggle.tsx index 49d4cb76..f9b8e6e4 100644 --- a/apps/dashboard/src/components/theme-toggle.tsx +++ b/apps/dashboard/src/components/theme-toggle.tsx @@ -1,10 +1,11 @@ -import { Check, Palette } from "lucide-react"; +import { Palette } from "lucide-react"; import { type Theme, useTheme } from "@/components/theme-provider"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, - DropdownMenuItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; @@ -45,11 +46,10 @@ export function ModeToggle() { const renderItem = ({ key, label }: { key: Theme; label: string }) => { const active = theme === key; return ( - setTheme(key)} > + ); }; @@ -77,11 +74,16 @@ export function ModeToggle() { } /> - {BASE_THEMES.map(renderItem)} - - - {NITRO_THEMES.map(renderItem)} - + setTheme(value as Theme)} + > + {BASE_THEMES.map(renderItem)} + + + {NITRO_THEMES.map(renderItem)} + + );