Files
lib-theme-prism/theme.css
jules f688b2b343
Some checks failed
validate theme / validate (push) Has been cancelled
Add calm background variant (body[data-bg=calm])
Full-strength drift floods short consumer pages — the white glass
surfaces cluster and the app reads as a card floating on a poster
(diagnosed on me.sky-ai.com). Calm keeps the same hue story at about
half the stain: corner gradients ~45% alpha, drift blobs at 0.4. Dark
mode keeps its standard gradients (already calm); only the drift layer
eases to 0.7.

Opt-in per consumer app; drift stays the default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:47:54 +10:00

778 lines
30 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* prism — crystal-clear glass theme.
*
* Iridescent multi-hue palette (lavender → peach → cyan → mint) refracted
* through frosted-glass surfaces. Earns its name twice: once for the
* literal glass refraction, once for the hue spectrum the corner glows
* span. Born in skyai-finance; lifted into a standalone theme when the
* shape stabilised.
*
* Forked from skyrise (lib-theme-skyrise) with four product-driven
* changes that became theme-wide:
*
* 1. Bakes in --warning + --warning-foreground (skyrise had them;
* pristine did not). Any product that displays health/risk needs
* warning semantics in both modes.
* 2. Adds a --text-hero size step (3.5rem) above display, for KPI
* heroes and editorial mastheads.
* 3. Ships a `.num` utility that sets tabular-nums + slashed-zero +
* letter-spacing:0 — non-negotiable on money numbers, useful
* anywhere digits matter.
* 4. Redesigns the dark-mode background. Skyrise's dark blobs sat at
* L≈0.18 with chroma≈0.12, near OKLCH gamut edges, which clipped
* to muddy gray. Here the corner glows are pushed to L≈0.30 with
* lower chroma, paired with a cleaner near-black base — they read
* as soft brand-accent light instead of tinted fog.
*
* Also dropped: the 11 background atmospheres and 4 surface tints
* skyrise shipped for white-label flexibility. Prism picks one brand
* surface (light + dark) and ships it. If alternates return, re-import
* from skyrise.
*
* Fonts loaded by the consuming app via Google Fonts URL @import at the
* very top of its entry CSS — see README. Do NOT @import them here.
*/
:root {
/* ── Colors ──
* Light mode rests on an iridescent lavender→peach→mint→rose gradient
* (defined in @layer base below). Glass surfaces sit at low alpha so the
* gradient reads through; foreground is pushed to L=0.16 for vivid,
* never-faint text against translucent surfaces. */
--background: oklch(0.97 0.015 295);
--foreground: oklch(0.16 0.025 285);
--card: oklch(1 0 0 / 0.58);
--card-foreground: oklch(0.16 0.025 285);
--popover: oklch(1 0 0 / 0.74);
--popover-foreground: oklch(0.16 0.025 285);
--primary: oklch(0.55 0.22 295);
--primary-foreground: oklch(1 0 0);
--secondary: oklch(0.96 0.012 290);
--secondary-foreground: oklch(0.22 0.025 285);
--muted: oklch(0.95 0.012 290);
--muted-foreground: oklch(0.42 0.02 285);
--accent: oklch(0.93 0.04 295);
--accent-foreground: oklch(0.25 0.05 295);
--destructive: oklch(0.6 0.24 25);
--success: oklch(0.5 0.18 150);
--success-foreground: oklch(1 0 0);
--warning: oklch(0.7 0.18 75);
--warning-foreground: oklch(0.18 0.04 75);
--border: oklch(0 0 0 / 0.09);
--input: oklch(0 0 0 / 0.09);
--ring: oklch(0.55 0.22 295 / 0.55);
/* Charts: violet → blue-cyan → amber → rose → emerald. Hues spread ≥60°
* apart and lightness is varied between adjacent indices so adjacent
* series stay perceptually distinct in multi-line / stacked charts. */
--chart-1: oklch(0.55 0.24 295);
--chart-2: oklch(0.62 0.2 230);
--chart-3: oklch(0.82 0.18 80);
--chart-4: oklch(0.72 0.2 350);
--chart-5: oklch(0.5 0.18 150);
--sidebar: oklch(1 0 0 / 0.5);
--sidebar-foreground: oklch(0.16 0.025 285);
--sidebar-primary: oklch(0.55 0.22 295);
--sidebar-primary-foreground: oklch(1 0 0);
--sidebar-accent: oklch(0.93 0.04 295);
--sidebar-accent-foreground: oklch(0.25 0.05 295);
--sidebar-border: oklch(0 0 0 / 0.07);
--sidebar-ring: oklch(0.55 0.22 295 / 0.55);
--syntax-keyword: oklch(0.5 0.22 295);
--syntax-string: oklch(0.5 0.16 150);
--syntax-number: oklch(0.55 0.18 35);
--syntax-comment: oklch(0.52 0.02 285);
--syntax-function: oklch(0.48 0.18 235);
--syntax-type: oklch(0.48 0.14 195);
/* ── Chat surfaces ──
* Contract for lib-agent-chat-ui / lib-agent-dock-ui. User bubble is
* a filled, deep, high-contrast panel with light text; assistant
* bubble is a card-like surface with a hairline border. */
--chat-user-bg: oklch(0.48 0.2 295);
--chat-user-fg: oklch(1 0 0);
--chat-assistant-bg: oklch(1 0 0 / 0.7);
--chat-assistant-fg: oklch(0.16 0.025 285);
--chat-assistant-border: oklch(0 0 0 / 0.09);
/* ── Radius ── Generous, premium feel. */
--radius: 1rem;
/* ── Motion ── Apple-style spring physics. */
--duration-fast: 180ms;
--duration-base: 320ms;
--duration-slow: 450ms;
--duration-slower: 600ms;
--duration-spring: 520ms;
--ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
--ease-decelerate: cubic-bezier(0, 0, 0.2, 1);
--ease-accelerate: cubic-bezier(0.4, 0, 1, 1);
--ease-spring-gentle: linear(0, 0.04 8%, 0.12 15%, 0.23 22%, 0.37 30%, 0.51 38%, 0.63 46%, 0.74 54%, 0.83 62%, 0.89 70%, 0.94 78%, 0.97 86%, 0.99 94%, 1);
--ease-spring-snappy: linear(0, 0.06 6%, 0.18 12%, 0.33 18%, 0.5 25%, 0.65 32%, 0.8 40%, 0.9 48%, 0.98 56%, 1.015 64%, 1.02 72%, 1.015 80%, 1.005 88%, 1);
--ease-spring-bouncy: linear(0, 0.08 8%, 0.22 16%, 0.4 24%, 0.58 32%, 0.75 40%, 0.88 48%, 0.98 56%, 1.04 64%, 1.06 72%, 1.04 80%, 1.01 86%, 0.995 92%, 1);
--ease-emphasized: var(--ease-spring-snappy);
/* ── Typography ── Bumped one notch from typical sizes for readability.
* Body sits at 17px (1.0625rem) — never-faint text. All scales ride
* along proportionally. Hero is the finance KPI step above display. */
--text-hero-size: 3.5rem;
--text-hero-lh: 3.75rem;
--text-display-size: 3rem;
--text-display-lh: 3.25rem;
--text-headline-size: 2rem;
--text-headline-lh: 2.375rem;
--text-title-size: 1.5rem;
--text-title-lh: 1.875rem;
--text-body-size: 1.0625rem;
--text-body-lh: 1.625rem;
--text-label-size: 0.9375rem;
--text-label-lh: 1.25rem;
--text-caption-size: 0.8125rem;
--text-caption-lh: 1.125rem;
/* ── Elevation ── Soft layered shadows in line with glass philosophy. */
--elevation-0: none;
--elevation-1: 0 1px 2px oklch(0 0 0 / 0.06);
--elevation-2: 0 2px 8px oklch(0 0 0 / 0.07), 0 1px 2px oklch(0 0 0 / 0.04);
--elevation-3: 0 4px 16px oklch(0 0 0 / 0.08), 0 2px 4px oklch(0 0 0 / 0.04);
--elevation-4: 0 12px 36px oklch(0.5 0.18 295 / 0.14), 0 4px 12px oklch(0 0 0 / 0.06);
--elevation-5: 0 20px 56px oklch(0.5 0.18 295 / 0.18), 0 8px 20px oklch(0 0 0 / 0.08);
--spacing: 0.25rem;
--border-width-thin: 0.5px;
--border-width-base: 1px;
--border-width-thick: 2px;
--border-width-heavy: 4px;
/* ── Glass vibrancy ── Heavy frost, like the skyrise reference. */
--glass-blur: 32px;
--glass-saturate: 220%;
--glass-brightness: 1.06;
--glass-contrast: 1.02;
/* ── Font stacks ── Loaded by the consuming app. */
--font-sans: "Inter", "Inter Variable", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
--font-heading: "Instrument Sans", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, "Cascadia Mono", Consolas, monospace;
/* Tailwind utility families. Distinct names so a consuming app's
* `@theme inline` can indirect to these (--font-heading/--font-sans collide
* with Tailwind's own font-* namespace). Mirror --font-heading/--font-sans. */
--heading-family: "Instrument Sans", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--body-family: "Inter", "Inter Variable", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
/*
* Dark mode — clean deep-violet base. Foreground stays high-L for the
* vivid, never-faint feel. Corner accents live in the body bg block below.
*/
.dark {
--background: oklch(0.10 0.015 285);
--foreground: oklch(0.97 0.005 280);
--card: oklch(0.22 0.04 290 / 0.55);
--card-foreground: oklch(0.97 0.005 280);
--popover: oklch(0.22 0.04 290 / 0.78);
--popover-foreground: oklch(0.97 0.005 280);
--primary: oklch(0.7 0.2 295);
--primary-foreground: oklch(0.14 0.04 295);
--secondary: oklch(0.26 0.03 290);
--secondary-foreground: oklch(0.95 0.005 280);
--muted: oklch(0.26 0.03 290);
--muted-foreground: oklch(0.78 0.015 285);
--accent: oklch(0.32 0.06 295);
--accent-foreground: oklch(0.95 0.04 295);
--destructive: oklch(0.7 0.22 25);
--success: oklch(0.74 0.16 150);
--success-foreground: oklch(0.12 0.02 150);
--warning: oklch(0.78 0.16 75);
--warning-foreground: oklch(0.16 0.04 75);
--border: oklch(1 0 0 / 0.1);
--input: oklch(1 0 0 / 0.1);
--ring: oklch(0.7 0.2 295 / 0.55);
--chart-1: oklch(0.68 0.24 295);
--chart-2: oklch(0.72 0.2 230);
--chart-3: oklch(0.88 0.18 80);
--chart-4: oklch(0.82 0.2 350);
--chart-5: oklch(0.6 0.18 150);
--sidebar: oklch(0.2 0.04 290 / 0.55);
--sidebar-foreground: oklch(0.97 0.005 280);
--sidebar-primary: oklch(0.7 0.2 295);
--sidebar-primary-foreground: oklch(0.14 0.04 295);
--sidebar-accent: oklch(0.32 0.06 295);
--sidebar-accent-foreground: oklch(0.95 0.04 295);
--sidebar-border: oklch(1 0 0 / 0.08);
--sidebar-ring: oklch(0.7 0.2 295 / 0.55);
--syntax-keyword: oklch(0.78 0.18 295);
--syntax-string: oklch(0.8 0.16 150);
--syntax-number: oklch(0.82 0.16 35);
--syntax-comment: oklch(0.65 0.02 285);
--syntax-function: oklch(0.78 0.16 235);
--syntax-type: oklch(0.78 0.14 195);
/* ── Chat surfaces (dark) ── */
--chat-user-bg: oklch(0.54 0.2 295);
--chat-user-fg: oklch(1 0 0);
--chat-assistant-bg: oklch(0.24 0.04 290 / 0.7);
--chat-assistant-fg: oklch(0.97 0.005 280);
--chat-assistant-border: oklch(1 0 0 / 0.1);
--elevation-1: 0 1px 2px oklch(0 0 0 / 0.4);
--elevation-2: 0 2px 8px oklch(0 0 0 / 0.4), 0 1px 2px oklch(0 0 0 / 0.3);
--elevation-3: 0 4px 16px oklch(0 0 0 / 0.45), 0 2px 4px oklch(0 0 0 / 0.3);
--elevation-4: 0 12px 36px oklch(0.4 0.16 295 / 0.18), 0 4px 12px oklch(0 0 0 / 0.5);
--elevation-5: 0 20px 56px oklch(0.4 0.16 295 / 0.22), 0 8px 20px oklch(0 0 0 / 0.55);
--glass-brightness: 0.92;
--glass-saturate: 200%;
}
/* ── Accent palettes ──────────────────────────────────────────────
* Apply by setting `data-palette="sky|rose|sage"` on `<html>` (or any
* subtree). Pairs with `.dark`. Violet is the default — no attribute
* needed. Calibrated to Prism's L/C scale so the crystal-glass surfaces
* stay coherent; we don't try to match Avalon's flatter palette.
* Consumed by arcadia-personal-cloud-web's accent picker. */
/* Sky — calm blue, the default consumer accent on me.sky-ai.com. */
[data-palette="sky"] {
--primary: oklch(0.55 0.18 235);
--primary-foreground: oklch(1 0 0);
--accent: oklch(0.93 0.04 235);
--accent-foreground: oklch(0.25 0.05 235);
--ring: oklch(0.55 0.18 235 / 0.55);
--chart-1: oklch(0.55 0.2 235);
--sidebar-primary: oklch(0.55 0.18 235);
--sidebar-accent: oklch(0.93 0.04 235);
--sidebar-accent-foreground: oklch(0.25 0.05 235);
--sidebar-ring: oklch(0.55 0.18 235 / 0.55);
--chat-user-bg: oklch(0.48 0.18 235);
--syntax-keyword: oklch(0.5 0.18 235);
}
/* Rose — warm coral-pink. */
[data-palette="rose"] {
--primary: oklch(0.6 0.2 10);
--primary-foreground: oklch(1 0 0);
--accent: oklch(0.93 0.05 10);
--accent-foreground: oklch(0.28 0.08 10);
--ring: oklch(0.6 0.2 10 / 0.55);
--chart-1: oklch(0.6 0.22 10);
--sidebar-primary: oklch(0.6 0.2 10);
--sidebar-accent: oklch(0.93 0.05 10);
--sidebar-accent-foreground: oklch(0.28 0.08 10);
--sidebar-ring: oklch(0.6 0.2 10 / 0.55);
--chat-user-bg: oklch(0.52 0.2 10);
--syntax-keyword: oklch(0.55 0.18 10);
}
/* Sage — calm green, personal and organic. */
[data-palette="sage"] {
--primary: oklch(0.55 0.13 155);
--primary-foreground: oklch(1 0 0);
--accent: oklch(0.92 0.045 150);
--accent-foreground: oklch(0.3 0.08 155);
--ring: oklch(0.55 0.13 155 / 0.55);
--chart-1: oklch(0.55 0.15 155);
--sidebar-primary: oklch(0.55 0.13 155);
--sidebar-accent: oklch(0.92 0.045 150);
--sidebar-accent-foreground: oklch(0.3 0.08 155);
--sidebar-ring: oklch(0.55 0.13 155 / 0.55);
--chat-user-bg: oklch(0.48 0.13 155);
--syntax-keyword: oklch(0.5 0.13 155);
}
.dark[data-palette="sky"],
.dark [data-palette="sky"] {
--primary: oklch(0.72 0.18 235);
--primary-foreground: oklch(0.14 0.04 235);
--accent: oklch(0.32 0.06 235);
--accent-foreground: oklch(0.95 0.04 235);
--ring: oklch(0.72 0.18 235 / 0.55);
--chart-1: oklch(0.7 0.2 235);
--sidebar-primary: oklch(0.72 0.18 235);
--sidebar-primary-foreground: oklch(0.14 0.04 235);
--sidebar-accent: oklch(0.32 0.06 235);
--sidebar-accent-foreground: oklch(0.95 0.04 235);
--sidebar-ring: oklch(0.72 0.18 235 / 0.55);
--chat-user-bg: oklch(0.56 0.18 235);
--syntax-keyword: oklch(0.78 0.16 235);
}
.dark[data-palette="rose"],
.dark [data-palette="rose"] {
--primary: oklch(0.74 0.18 10);
--primary-foreground: oklch(0.16 0.04 10);
--accent: oklch(0.32 0.06 10);
--accent-foreground: oklch(0.95 0.04 10);
--ring: oklch(0.74 0.18 10 / 0.55);
--chart-1: oklch(0.72 0.2 10);
--sidebar-primary: oklch(0.74 0.18 10);
--sidebar-primary-foreground: oklch(0.16 0.04 10);
--sidebar-accent: oklch(0.32 0.06 10);
--sidebar-accent-foreground: oklch(0.95 0.04 10);
--sidebar-ring: oklch(0.74 0.18 10 / 0.55);
--chat-user-bg: oklch(0.58 0.2 10);
--syntax-keyword: oklch(0.8 0.16 10);
}
.dark[data-palette="sage"],
.dark [data-palette="sage"] {
--primary: oklch(0.72 0.13 155);
--primary-foreground: oklch(0.14 0.04 155);
--accent: oklch(0.3 0.05 155);
--accent-foreground: oklch(0.95 0.04 155);
--ring: oklch(0.72 0.13 155 / 0.55);
--chart-1: oklch(0.7 0.15 155);
--sidebar-primary: oklch(0.72 0.13 155);
--sidebar-primary-foreground: oklch(0.14 0.04 155);
--sidebar-accent: oklch(0.3 0.05 155);
--sidebar-accent-foreground: oklch(0.95 0.04 155);
--sidebar-ring: oklch(0.72 0.13 155 / 0.55);
--chat-user-bg: oklch(0.56 0.13 155);
--syntax-keyword: oklch(0.8 0.13 155);
}
@layer base {
body {
background-attachment: fixed;
min-height: 100vh;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
letter-spacing: -0.011em;
font-family: var(--font-sans);
font-size: var(--text-body-size);
line-height: var(--text-body-lh);
}
/* Themed text selection — keeps the violet identity. */
::selection {
background-color: oklch(0.55 0.22 295 / 0.28);
color: var(--foreground);
}
.dark ::selection {
background-color: oklch(0.7 0.2 295 / 0.4);
color: var(--foreground);
}
/* Themed scrollbars (Firefox + Chromium; macOS overlay ignores both). */
* {
scrollbar-width: thin;
scrollbar-color: oklch(0 0 0 / 0.18) transparent;
}
.dark * {
scrollbar-color: oklch(1 0 0 / 0.18) transparent;
}
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
background-color: oklch(0 0 0 / 0.18);
border-radius: 999px;
border: 2px solid transparent;
background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover {
background-color: oklch(0 0 0 / 0.28);
background-clip: padding-box;
}
.dark ::-webkit-scrollbar-thumb {
background-color: oklch(1 0 0 / 0.18);
background-clip: padding-box;
}
.dark ::-webkit-scrollbar-thumb:hover {
background-color: oklch(1 0 0 / 0.28);
background-clip: padding-box;
}
/* Headings get the display family — Instrument Sans's tighter counters
* against Inter's open body feel. */
h1, h2, h3, h4, h5, h6,
[data-slot="card-title"],
[data-slot="dialog-title"],
[data-slot="alert-dialog-title"],
[data-slot="sheet-title"],
[data-slot="drawer-title"] {
font-family: var(--font-heading);
letter-spacing: -0.02em;
font-weight: 600;
}
/*
* ── Light-mode body ── iridescent drift, the brand surface.
*/
body,
body[data-bg="drift"],
.bg-variant-drift {
background-image:
radial-gradient(ellipse 80% 60% at 12% 8%, oklch(0.92 0.09 295 / 0.7), transparent 60%),
radial-gradient(ellipse 70% 60% at 88% 14%, oklch(0.93 0.1 350 / 0.7), transparent 55%),
radial-gradient(ellipse 80% 60% at 92% 88%, oklch(0.93 0.1 200 / 0.7), transparent 60%),
radial-gradient(ellipse 70% 60% at 8% 92%, oklch(0.94 0.09 145 / 0.65), transparent 55%),
linear-gradient(180deg, oklch(0.985 0.02 295), oklch(0.96 0.025 320));
}
/*
* ── Dark-mode body ── deep clean base + four soft corner glows.
*
* Design: blob L pushed to ~0.30 (vs skyrise's 0.18) so they sit well
* above the L=0.10 base — perceived as *light* rather than coloured
* fog. Chroma dropped to 0.060.09 (vs 0.120.14) to stay inside OKLCH
* dark-gamut sweet-spot and avoid the gray clip. Base gradient is
* near-monochrome (chroma 0.010.02) so the accents do all the colour
* work — same hue palette as light (purple→pink→cyan→mint), but
* articulated through light, not stain.
*/
html.dark body,
html.dark body[data-bg="drift"],
html.dark .bg-variant-drift {
background-image:
radial-gradient(ellipse 70% 55% at 10% 8%, oklch(0.32 0.09 295 / 0.65), transparent 60%),
radial-gradient(ellipse 60% 50% at 90% 14%, oklch(0.30 0.09 340 / 0.60), transparent 55%),
radial-gradient(ellipse 70% 55% at 92% 90%, oklch(0.28 0.07 200 / 0.55), transparent 60%),
radial-gradient(ellipse 60% 50% at 8% 92%, oklch(0.30 0.06 145 / 0.50), transparent 55%),
linear-gradient(180deg, oklch(0.10 0.012 285), oklch(0.08 0.018 295));
}
/*
* Animated drift layer — two large radial-blob fields that translate
* and rotate slowly, driving the chroma the glass refracts.
*/
[data-slot="aurora-field"] { display: none; }
body[data-bg="drift"] [data-slot="aurora-field"],
body:not([data-bg]) [data-slot="aurora-field"] { display: block; }
.aurora-blob {
position: absolute;
inset: -25%;
background-repeat: no-repeat;
will-change: transform;
}
.aurora-blob-1 {
background-image:
radial-gradient(ellipse 44% 36% at 22% 28%, oklch(0.9 0.13 295 / 0.85), transparent 60%),
radial-gradient(ellipse 38% 32% at 78% 22%, oklch(0.9 0.13 350 / 0.8), transparent 55%);
animation: prism-drift-a 32s ease-in-out infinite;
}
.aurora-blob-2 {
background-image:
radial-gradient(ellipse 48% 38% at 82% 74%, oklch(0.9 0.14 200 / 0.8), transparent 60%),
radial-gradient(ellipse 42% 34% at 16% 80%, oklch(0.92 0.12 145 / 0.75), transparent 55%);
animation: prism-drift-b 40s ease-in-out infinite;
}
/* Dark variant of the drifting aurora — same L/chroma discipline as
* the static dark base above. Same hues, tame chroma, well above base. */
html.dark .aurora-blob-1 {
background-image:
radial-gradient(ellipse 44% 36% at 22% 28%, oklch(0.32 0.09 295 / 0.55), transparent 60%),
radial-gradient(ellipse 38% 32% at 78% 22%, oklch(0.30 0.10 340 / 0.50), transparent 55%);
}
html.dark .aurora-blob-2 {
background-image:
radial-gradient(ellipse 48% 38% at 82% 74%, oklch(0.28 0.08 200 / 0.50), transparent 60%),
radial-gradient(ellipse 42% 34% at 16% 80%, oklch(0.30 0.07 145 / 0.45), transparent 55%);
}
/*
* ── Calm variant ── opt-in via body[data-bg="calm"]. The same hue
* story at roughly half the stain: corner gradients drop to ~45%
* alpha and the drift layer dims to match. For consumer surfaces
* (me.sky-ai.com) where full-strength drift floods short pages and
* the app reads as a white card floating on a poster. Dark mode is
* already calm — it keeps the standard dark gradients, only the
* drift layer eases.
*/
body[data-bg="calm"] {
background-image:
radial-gradient(ellipse 80% 60% at 12% 8%, oklch(0.92 0.09 295 / 0.32), transparent 60%),
radial-gradient(ellipse 70% 60% at 88% 14%, oklch(0.93 0.1 350 / 0.30), transparent 55%),
radial-gradient(ellipse 80% 60% at 92% 88%, oklch(0.93 0.1 200 / 0.30), transparent 60%),
radial-gradient(ellipse 70% 60% at 8% 92%, oklch(0.94 0.09 145 / 0.28), transparent 55%),
linear-gradient(180deg, oklch(0.985 0.012 295), oklch(0.97 0.015 320));
}
html.dark body[data-bg="calm"] {
background-image:
radial-gradient(ellipse 70% 55% at 10% 8%, oklch(0.32 0.09 295 / 0.65), transparent 60%),
radial-gradient(ellipse 60% 50% at 90% 14%, oklch(0.30 0.09 340 / 0.60), transparent 55%),
radial-gradient(ellipse 70% 55% at 92% 90%, oklch(0.28 0.07 200 / 0.55), transparent 60%),
radial-gradient(ellipse 60% 50% at 8% 92%, oklch(0.30 0.06 145 / 0.50), transparent 55%),
linear-gradient(180deg, oklch(0.10 0.012 285), oklch(0.08 0.018 295));
}
body[data-bg="calm"] [data-slot="aurora-field"] { display: block; }
body[data-bg="calm"] .aurora-blob { opacity: 0.4; }
html.dark body[data-bg="calm"] .aurora-blob { opacity: 0.7; }
@keyframes prism-drift-a {
0%, 100% { transform: translate3d(0, 0, 0) rotate(0deg) scale(1); }
33% { transform: translate3d(5%, -3%, 0) rotate(10deg) scale(1.07); }
66% { transform: translate3d(-3%, 4%, 0) rotate(-8deg) scale(0.96); }
}
@keyframes prism-drift-b {
0%, 100% { transform: translate3d(0, 0, 0) rotate(0deg) scale(1); }
33% { transform: translate3d(-5%, 4%, 0) rotate(-12deg) scale(1.09); }
66% { transform: translate3d(4%, -3%, 0) rotate(8deg) scale(0.94); }
}
@media (prefers-reduced-motion: reduce) {
.aurora-blob { animation: none; }
}
/*
* ── Glass surfaces ── Apple-vibrancy stack on every shadcn surface
* that exposes a [data-slot]. blur softens, saturate pumps the
* gradient chroma the glass is sampling, brightness/contrast keep
* edges legible.
*/
[data-slot="card"],
[data-slot="popover-content"],
[data-slot="dropdown-menu-content"],
[data-slot="context-menu-content"],
[data-slot="menubar-content"],
[data-slot="hover-card-content"],
[data-slot="select-content"],
[data-slot="combobox-content"],
[data-slot="command"],
[data-slot="tooltip-content"],
[data-slot="sheet-content"],
[data-slot="dialog-content"],
[data-slot="alert-dialog-content"],
[data-slot="drawer-content"],
[data-slot="sidebar"] {
position: relative;
backdrop-filter:
blur(var(--glass-blur))
saturate(var(--glass-saturate))
brightness(var(--glass-brightness))
contrast(var(--glass-contrast));
-webkit-backdrop-filter:
blur(var(--glass-blur))
saturate(var(--glass-saturate))
brightness(var(--glass-brightness))
contrast(var(--glass-contrast));
transition-timing-function: var(--ease-spring-snappy);
animation-timing-function: var(--ease-spring-snappy);
animation-duration: var(--duration-spring);
}
/* Springy press feel on interactive bits. */
[data-slot="button"],
[data-slot="chip"],
[data-slot="toggle"],
[data-slot="item"] {
transition-timing-function: var(--ease-spring-bouncy);
transition-duration: var(--duration-fast);
}
/*
* Premium primary button — violet→fuchsia gradient + top-edge sheen.
*/
[data-slot="button"].bg-primary {
background-image: linear-gradient(
135deg,
oklch(0.56 0.24 290) 0%,
oklch(0.6 0.26 320) 50%,
oklch(0.58 0.24 350) 100%
);
position: relative;
isolation: isolate;
}
[data-slot="button"].bg-primary::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
pointer-events: none;
background: linear-gradient(
180deg,
oklch(1 0 0 / 0.35) 0,
oklch(1 0 0 / 0.08) 45%,
oklch(1 0 0 / 0) 60%
);
mix-blend-mode: plus-lighter;
}
.dark [data-slot="button"].bg-primary {
background-image: linear-gradient(
135deg,
oklch(0.66 0.22 290) 0%,
oklch(0.7 0.24 320) 50%,
oklch(0.66 0.22 350) 100%
);
}
.dark [data-slot="button"].bg-primary::after {
background: linear-gradient(
180deg,
oklch(1 0 0 / 0.18) 0,
oklch(1 0 0 / 0.04) 45%,
oklch(1 0 0 / 0) 60%
);
}
/*
* Premium card padding — roomy 24px vs the lib's compact 16px.
*/
[data-slot="card"],
[data-slot="dialog-content"],
[data-slot="alert-dialog-content"],
[data-slot="sheet-content"],
[data-slot="drawer-content"],
[data-slot="popover-content"] {
--card-padding: calc(var(--spacing) * 6);
gap: calc(var(--spacing) * 5);
}
[data-slot="card-header"],
[data-slot="card-content"],
[data-slot="card-footer"] {
padding-inline: var(--card-padding, calc(var(--spacing) * 6));
}
/*
* AI orb avatar — any avatar fallback marked `data-ai` gets the
* rainbow gradient signature.
*/
[data-slot="avatar-fallback"][data-ai] {
background-image: conic-gradient(
from 210deg at 50% 50%,
oklch(0.6 0.24 295) 0deg,
oklch(0.7 0.22 350) 90deg,
oklch(0.78 0.18 35) 180deg,
oklch(0.7 0.18 200) 270deg,
oklch(0.6 0.24 295) 360deg
);
background-color: transparent;
color: oklch(1 0 0);
font-weight: 600;
position: relative;
}
[data-slot="avatar-fallback"][data-ai]::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
pointer-events: none;
background: linear-gradient(
180deg,
oklch(1 0 0 / 0.3) 0,
oklch(1 0 0 / 0) 50%
);
mix-blend-mode: plus-lighter;
}
.dark [data-slot="avatar-fallback"][data-ai] {
background-image: conic-gradient(
from 210deg at 50% 50%,
oklch(0.7 0.22 295) 0deg,
oklch(0.78 0.2 350) 90deg,
oklch(0.84 0.16 35) 180deg,
oklch(0.78 0.16 200) 270deg,
oklch(0.7 0.22 295) 360deg
);
}
/*
* Inner top highlight — the wet-glass sheen Apple uses on surfaces.
*/
[data-slot="card"]::after,
[data-slot="popover-content"]::after,
[data-slot="dropdown-menu-content"]::after,
[data-slot="context-menu-content"]::after,
[data-slot="menubar-content"]::after,
[data-slot="hover-card-content"]::after,
[data-slot="select-content"]::after,
[data-slot="combobox-content"]::after,
[data-slot="command"]::after,
[data-slot="tooltip-content"]::after,
[data-slot="sheet-content"]::after,
[data-slot="dialog-content"]::after,
[data-slot="alert-dialog-content"]::after,
[data-slot="drawer-content"]::after,
[data-slot="sidebar"]::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
pointer-events: none;
background:
linear-gradient(
180deg,
oklch(1 0 0 / 0.6) 0,
oklch(1 0 0 / 0) 1.5px,
oklch(1 0 0 / 0.14) 1.5px,
oklch(1 0 0 / 0) 35%
);
mix-blend-mode: plus-lighter;
}
.dark [data-slot="card"]::after,
.dark [data-slot="popover-content"]::after,
.dark [data-slot="dropdown-menu-content"]::after,
.dark [data-slot="context-menu-content"]::after,
.dark [data-slot="menubar-content"]::after,
.dark [data-slot="hover-card-content"]::after,
.dark [data-slot="select-content"]::after,
.dark [data-slot="combobox-content"]::after,
.dark [data-slot="command"]::after,
.dark [data-slot="tooltip-content"]::after,
.dark [data-slot="sheet-content"]::after,
.dark [data-slot="dialog-content"]::after,
.dark [data-slot="alert-dialog-content"]::after,
.dark [data-slot="drawer-content"]::after,
.dark [data-slot="sidebar"]::after {
background:
linear-gradient(
180deg,
oklch(1 0 0 / 0.2) 0,
oklch(1 0 0 / 0) 1.5px,
oklch(1 0 0 / 0.05) 1.5px,
oklch(1 0 0 / 0) 35%
);
}
/*
* ── `.num` utility ── Apply to any element that displays money or
* counts. Forces tabular monospaced digits, the unambiguous slashed-
* zero stylistic set Inter ships, and resets the body's negative
* tracking that would otherwise crash digits into each other.
*/
.num {
font-feature-settings: "tnum", "ss01", "cv01";
font-variant-numeric: tabular-nums;
letter-spacing: 0;
}
}
/* ── Root size & font-scale ──
* 18px root by default. Body lands ~19px once the --text-body-size
* 1.0625rem multiplier applies. The data-font-scale hook lets the
* consuming app expose a user-facing size toggle. */
@layer base {
html { font-size: 18px; }
html[data-font-scale="sm"] { font-size: 16px; }
html[data-font-scale="md"] { font-size: 18px; }
html[data-font-scale="lg"] { font-size: 20px; }
html[data-font-scale="xl"] { font-size: 22px; }
}