From 8ba09b5db3dbbedd9eab5f63506244792f637282 Mon Sep 17 00:00:00 2001 From: jules Date: Mon, 18 May 2026 22:02:38 +1000 Subject: [PATCH] =?UTF-8?q?feat:=20initial=20=E2=80=94=20fork=20skyrise=20?= =?UTF-8?q?for=20finance=20product?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bakes in --warning / --warning-foreground (light + dark). - Adds --text-hero size step (3.5rem) above display, for KPI heroes. - Ships a .num utility (tabular-nums + slashed-zero + zero tracking). - Redesigned dark-mode background: corner glows pushed to L≈0.30 with lower chroma over a clean near-black base — reads as soft brand-accent light instead of skyrise's muddy gamut-fringe blobs. - Drops 11 background atmospheres + 4 surface tints — focused product theme. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitea/workflows/validate-theme.yml | 31 ++ .gitignore | 2 + README.md | 69 ++++ theme.css | 607 ++++++++++++++++++++++++++++ 4 files changed, 709 insertions(+) create mode 100644 .gitea/workflows/validate-theme.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 theme.css diff --git a/.gitea/workflows/validate-theme.yml b/.gitea/workflows/validate-theme.yml new file mode 100644 index 0000000..5c7bca9 --- /dev/null +++ b/.gitea/workflows/validate-theme.yml @@ -0,0 +1,31 @@ +name: validate theme + +on: + push: + branches: [main] + pull_request: + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "22" + + - name: Fetch validator + reference + run: | + mkdir -p .ci-scripts + curl -fsSL -o .ci-scripts/check-theme.mjs \ + https://git.sky-ai.com/CremaUIStudio/core-interface/raw/branch/main/scripts/check-theme.mjs + git clone --depth 1 \ + https://git.sky-ai.com/CremaUIStudio/lib-theme-pristine.git \ + .ci-scripts/lib-theme-pristine + + - name: Validate against pristine contract + run: | + node .ci-scripts/check-theme.mjs \ + --target=theme.css \ + --reference=.ci-scripts/lib-theme-pristine/theme.css \ + --name=skyai-finance diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..88c8bfc --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.ci-scripts/ +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..1b1a936 --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# lib-theme-skyai-finance + +Finance-focused premium AI glass theme. Forked from [`lib-theme-skyrise`](../lib-theme-skyrise/) with four product-driven changes: + +1. **Bakes in `--warning` + `--warning-foreground`** — finance KPIs need warning semantics out of the box. Pristine had `--success` but no warning; skyrise had both. This theme guarantees them in light and dark. +2. **Adds a `--text-hero` size step** (`3.5rem`) above `display`, for KPI hero numbers. Exposed as `text-hero` once the consuming app maps it into Tailwind's `@theme inline`. +3. **Ships a `.num` utility** that sets `font-feature-settings: "tnum","ss01","cv01"`, `font-variant-numeric: tabular-nums`, and `letter-spacing: 0`. Apply to any element that displays money or counts — never inherit body's negative tracking on digits, never tolerate the ambiguous round-zero glyph in a finance context. +4. **Redesigned dark-mode background.** Skyrise's dark blobs sat at `L≈0.18` with chroma `≈0.12`, which clipped to muddy gray near OKLCH gamut edges. Here the four corner glows are pushed to `L≈0.30` with lower chroma (`0.06–0.09`), paired with a cleaner near-black base. Reads as soft brand-accent light rather than tinted fog. + +Also **dropped**: the 11 background atmospheres and 4 surface tints skyrise shipped for white-label flexibility. A focused product picks one brand surface and ships it. Re-import them from skyrise if needs change. + +## Install + +Sibling-clone like the other Crema themes: + +``` +your-workspace/ + skyai-finance/ + lib-theme-skyai-finance/ ← this dir + lib-action-bus/ + ... +``` + +In the consuming app's `app/app.css`, swap the active theme import: + +```css +@import "../../lib-theme-skyai-finance/theme.css"; /* CREMA:THEME */ +``` + +Then load the fonts at the very top of `app.css` (Google Fonts URL): + +```css +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400..700&family=Instrument+Sans:wght@400..700&family=Geist+Mono:wght@400..600&display=swap"); +``` + +And expose the hero size step + `--font-heading` to Tailwind in your `@theme inline` block: + +```css +@theme inline { + --font-heading: "Instrument Sans", "Inter", sans-serif; + --text-hero: var(--text-hero-size); + --text-hero--line-height: var(--text-hero-lh); + /* … plus the usual --color-* mappings the lib expects */ +} +``` + +## What's in the box + +- Iridescent lavender→peach→cyan→mint light surface (the brand) +- Clean deep-violet dark surface with four soft corner glows +- Glass surfaces (blur + saturate + brightness stack) on every shadcn `[data-slot]` +- Apple-style spring physics motion tokens +- Premium primary button gradient + wet-sheen highlight +- AI orb avatar (rainbow conic) — opt in via `` +- Heading face: **Instrument Sans 600 / -0.02em** +- Body face: **Inter** 17px (1.0625rem), `letter-spacing: -0.011em` +- Mono: **Geist Mono** +- `.num` utility — slashed zero, tabular nums, zero tracking +- `--text-hero` / `--text-display` / `--text-headline` / `--text-title` / `--text-body` / `--text-label` / `--text-caption` +- Full semantic colour set: primary, secondary, accent, muted, destructive, **success, warning** + foregrounds +- 5-step `--elevation-{1..5}` shadow scale (primary-tinted at e4/e5) + +## What's not + +- Background atmosphere variants (`pearl`, `linen`, `mist`, …) — strip from `DisplayMenu` too. +- Surface tints (`snow`, `stone`, `sage`, `slate`) — same. +- Multiple syntax themes. + +If you need either, fork back to `lib-theme-skyrise`. diff --git a/theme.css b/theme.css new file mode 100644 index 0000000..194b837 --- /dev/null +++ b/theme.css @@ -0,0 +1,607 @@ +/* + * skyai-finance — finance-focused premium AI glass theme. + * Forked from skyrise (lib-theme-skyrise) with four product-driven changes: + * + * 1. Bakes in --warning + --warning-foreground (skyrise had them; pristine + * did not — finance KPIs need warning semantics out of the box). + * 2. Adds a --text-hero size step (3.5rem) above display, for KPI heroes. + * 3. Ships a `.num` utility that sets tabular-nums + slashed-zero + + * letter-spacing:0 — non-negotiable on money numbers. + * 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. A focused product picks one brand + * surface and ships it; if those 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); + + /* ── 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; +} + +/* + * 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); + + --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%; +} + +@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.06–0.09 (vs 0.12–0.14) to stay inside OKLCH + * dark-gamut sweet-spot and avoid the gray clip. Base gradient is + * near-monochrome (chroma 0.01–0.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: skyfin-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: skyfin-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%); + } + + @keyframes skyfin-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 skyfin-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; } +}