From 107d767dadb9b50b59351db156d4c6dd4ea0b4dd Mon Sep 17 00:00:00 2001 From: WeekendsuperHero <4048475+WeekendSuperhero@users.noreply.github.com> Date: Wed, 21 Oct 2026 06:46:35 -0700 Subject: [PATCH] use fake glass --- app/docs/theming/page.tsx | 20 ++++ app/theme/registrations.css | 13 +++ app/theme/styles.css | 53 ++++++++++ bun.lock | 48 +++++---- components/background-provider.tsx | 28 ++++- components/glass-prebaked-demo.tsx | 85 +++++++++++++++ components/gradient-background.tsx | 28 ++--- hooks/use-glass-backdrop.ts | 100 ++++++++++++++++++ lib/material.ts | 15 ++- package.json | 6 +- public/r/accordion.json | 2 +- public/r/alert-dialog.json | 2 +- public/r/alert.json | 2 +- public/r/avatar.json | 2 +- public/r/background-controller.json | 7 +- public/r/badge.json | 2 +- public/r/breadcrumb.json | 2 +- public/r/button-group.json | 2 +- public/r/button.json | 2 +- public/r/calendar.json | 2 +- public/r/card.json | 2 +- public/r/carousel.json | 2 +- public/r/chart.json | 2 +- public/r/checkbox.json | 2 +- public/r/collapsible.json | 2 +- public/r/combobox.json | 2 +- public/r/command.json | 2 +- public/r/context-menu.json | 2 +- public/r/cropper.json | 2 +- public/r/date-picker-input.json | 2 +- public/r/dialog.json | 2 +- public/r/drawer.json | 2 +- public/r/dropdown-menu.json | 2 +- public/r/empty-state.json | 2 +- public/r/empty.json | 2 +- public/r/field.json | 2 +- public/r/gradient-background.json | 5 +- public/r/hover-card.json | 2 +- public/r/input-group.json | 2 +- public/r/input-otp.json | 2 +- public/r/input.json | 2 +- public/r/item.json | 2 +- public/r/kbd.json | 2 +- public/r/label.json | 2 +- public/r/material.json | 2 +- public/r/menu-bar.json | 2 +- public/r/menubar.json | 2 +- public/r/mode-toggle.json | 2 +- public/r/navigation-menu.json | 2 +- public/r/pagination.json | 2 +- public/r/popover.json | 2 +- public/r/progress.json | 2 +- public/r/radio-group.json | 2 +- public/r/registry.json | 21 +++- public/r/scroll-area.json | 2 +- public/r/select.json | 2 +- public/r/separator.json | 2 +- public/r/sheet.json | 2 +- public/r/sidebar.json | 2 +- public/r/skeleton.json | 2 +- public/r/slider.json | 2 +- public/r/sonner.json | 2 +- public/r/spinner.json | 2 +- public/r/switch.json | 2 +- public/r/table.json | 2 +- public/r/tabs.json | 2 +- public/r/textarea.json | 2 +- public/r/theme.json | 2 +- public/r/toggle-group.json | 2 +- public/r/toggle.json | 2 +- public/r/tooltip.json | 2 +- public/r/use-glass-backdrop.json | 17 +++ registry.json | 21 +++- registry/theme/globals.css | 66 ++++++++++++ tests/glass-prebaked.test.tsx | 154 ++++++++++++++++++++++++++++ tests/material.test.tsx | 11 ++ 76 files changed, 704 insertions(+), 110 deletions(-) create mode 100644 components/glass-prebaked-demo.tsx create mode 100644 hooks/use-glass-backdrop.ts create mode 100644 public/r/use-glass-backdrop.json create mode 100644 tests/glass-prebaked.test.tsx diff --git a/app/docs/theming/page.tsx b/app/docs/theming/page.tsx index 063503c2..81a54b85 100644 --- a/app/docs/theming/page.tsx +++ b/app/docs/theming/page.tsx @@ -1,6 +1,7 @@ import type { ReactNode } from "react"; import { DiffuseComparisonDemo } from "@/components/diffuse-comparison-demo"; import { ForegroundTester } from "@/components/foreground-tester"; +import { GlassPrebakedDemo } from "@/components/glass-prebaked-demo"; import { StainedComparisonDemo } from "@/components/stained-comparison-demo"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { VeilComparisonDemo } from "@/components/veil-comparison-demo"; @@ -835,6 +836,25 @@ npx shadcn@latest add @sistine/gradient-background # pure-CSS gradient wallpape prop, with angle, position, and (radial) radialShape / radialSize geometry, e.g.{" "} <GradientBackground shape="conic" position="30% 70%" />.

+

+ A list of glass surfaces can skip the live backdrop-filter. glass-prebaked (the prebaked axis) paints a + viewport-sized copy of --app-bg-blurred behind each surface, shifted by that surface's screen position.{" "} + useGlassBackdrop writes the offsets from one animation frame. <GradientBackground> and the solid backdrop + publish the variable and set --app-bg-filter to blur(12px) saturate(180%). A pre-blurred image is the cheaper source: + set --app-bg-blurred: url(bg.webp) center / cover and leave the filter unset. Pattern scenes and the canvas are not a single + image, so they stay on the live filter. Keep the bubble in normal flow. A transform, a filter, an opacity, or{" "} + isolation on it paints the slice in front of the glass fill. +

+ {`import { useGlassBackdrop } from "@/hooks/use-glass-backdrop" +import { glassMaterial } from "@/lib/material" + +const scrollRef = useRef(null) +useGlassBackdrop(scrollRef) + +
+
…
+
`}
+ diff --git a/app/theme/registrations.css b/app/theme/registrations.css index d0f2b01f..d2d675c8 100644 --- a/app/theme/registrations.css +++ b/app/theme/registrations.css @@ -36,6 +36,19 @@ inherits: false; } +/* Prebaked-frost offsets (hooks/use-glass-backdrop.ts writes these per bubble). Non-inheriting so a + nested bubble never samples its parent's screen position for a frame before its own sync. */ +@property --gx { + syntax: ""; + inherits: false; + initial-value: 0px; +} +@property --gy { + syntax: ""; + inherits: false; + initial-value: 0px; +} + /* Gradient-text tokens are consumed ONLY from JS inline styles (components/gradient-text.tsx) — no CSS rule references them, so Lightning CSS's optimizer would silently drop them (the same failure mode as the harmonic tokens below). Registered here to pin them. DO NOT REMOVE. */ diff --git a/app/theme/styles.css b/app/theme/styles.css index 1e30ea4f..06be1762 100644 --- a/app/theme/styles.css +++ b/app/theme/styles.css @@ -140,3 +140,56 @@ --foreground-ui: var(--foreground-ui-opaque); --muted-foreground: var(--muted-foreground-opaque); } + +/* ── Prebaked frost ─────────────────────────────────────────────────────────── + A stand-in for backdrop-filter when many surfaces are on screen at once (a message list). Each + `.glass-prebaked` paints a viewport-sized copy of the page background, shifted by its own screen + position (--gx / --gy, written by useGlassBackdrop), and clips that copy to its box. A plain + `filter` on that copy blurs pixels the element already owns. backdrop-filter would re-sample + whatever is painted behind every bubble, every frame. + + UNLAYERED on purpose, and it MUST stay that way: + - `@utility` rules live in `@layer utilities`. An unlayered [data-material] / [data-glass] rule + beats a layered custom property no matter the specificity, so `--srf-filter: none` inside an + @utility would lose and the live blur would stay on. + - `backdrop-filter: none` here replaces the property `glass` sets, including the --srf-stain + appendix. Stained optics are a backdrop-filter color matrix; they do not carry over. + - `position: relative` and `overflow: hidden` are part of the trick (the slice is 100vw × 100vh + and has to be clipped, and the bubble has to be the slice's containing block). Unlayered, they + beat `absolute` / `fixed` / `sticky` / `overflow-*` utilities on the same element. Keep this + class on in-flow bubbles. It does not belong on a sticky bar, a popover, or a scrolling panel. + - No `isolation: isolate`. A stacking context paints its own background FIRST and negative + z-index children AFTER it, so the slice would cover the glass fill and the tint would vanish. + Without one, z-index -1 sits behind this element's background and in front of the page + backdrop (z-index -10). A transform, filter, or opacity on the bubble itself re-traps the + slice above the fill — same failure — so don't put those on the bubble. + - `glass-sheen` paints its own ::before / ::after highlights. This slice owns ::before; the two + classes do not combine. + + The app supplies the pixels: + - `--app-bg-blurred`: a pre-blurred image (`url(bg.webp) center / cover`) or the same CSS + background the page paints. GradientBackground and the solid "none" backdrop publish it. + - `--app-bg-filter`: `blur(12px) saturate(180%)` when that background is NOT already blurred. + Defaults to none, so a pre-blurred image is not blurred a second time. + Pattern scenes and the canvas are many layers, not one background, so they do not publish the + variable. A bubble with this class and no variable loses the live blur and shows only its fill. */ +.glass-prebaked { + --srf-filter: none; + backdrop-filter: none; + position: relative; + overflow: hidden; +} +.glass-prebaked::before { + content: ""; + position: absolute; + z-index: -1; + width: 100vw; + height: 100vh; + left: calc(var(--gx, 0px) * -1); + top: calc(var(--gy, 0px) * -1); + background: var(--app-bg-blurred); + background-repeat: no-repeat; + filter: var(--app-bg-filter, none); + border-radius: inherit; + pointer-events: none; +} diff --git a/bun.lock b/bun.lock index 98603113..78ae8f61 100644 --- a/bun.lock +++ b/bun.lock @@ -12,7 +12,7 @@ "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.4.0", - "framer-motion": "^14.4.0", + "framer-motion": "^14.5.0", "input-otp": "^1.5.0", "next": "^16.4.2", "next-themes": "^0.4.6", @@ -21,7 +21,7 @@ "react-day-picker": "^10.0.2", "react-dom": "^19.3.1", "react-easy-crop": "^6.2.4", - "react-resizable-panels": "^4.14.7", + "react-resizable-panels": "^4.14.8", "recharts": "^3.10.1", "shadcn": "^4.22.1", "sonner": "^2.0.8", @@ -31,7 +31,7 @@ "zod": "^4.6.7", }, "devDependencies": { - "@biomejs/biome": "^2.5.15", + "@biomejs/biome": "^2.6.2", "@chromatic-com/storybook": "^5.6.0", "@storybook/addon-a11y": "^10.6.3", "@storybook/addon-docs": "^10.6.3", @@ -278,23 +278,27 @@ "@base-ui/utils": ["@base-ui/utils@0.5.0", "", { "dependencies": { "@babel/runtime": "^7.29.7 || ^8.0.0", "@floating-ui/utils": "^0.2.12", "reselect": "^5.3.0", "use-sync-external-store": "^1.7.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-dYGGcEttjkYiMoa5nHm8vLaJ2UcuTWxo3BFAwOYXeRg/q7UVVvA6cacEIXEs3FgBfK1CGtW1ptElqs7wsx+t9A=="], - "@biomejs/biome": ["@biomejs/biome@2.5.15", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.5.15", "@biomejs/cli-darwin-x64": "2.5.15", "@biomejs/cli-linux-arm64": "2.5.15", "@biomejs/cli-linux-arm64-musl": "2.5.15", "@biomejs/cli-linux-x64": "2.5.15", "@biomejs/cli-linux-x64-musl": "2.5.15", "@biomejs/cli-win32-arm64": "2.5.15", "@biomejs/cli-win32-x64": "2.5.15" }, "bin": { "biome": "bin/biome" } }, "sha512-WZTW4slm/pdkh92K6t/3aEN++44JD1PQ7squ7RCMLI1flHGl43DVOIGXvCjHiZgBMO1V4uYxoFNtqrIA4qGuIQ=="], + "@biomejs/biome": ["@biomejs/biome@2.6.2", "", { "optionalDependencies": { "@biomejs/cli-android-arm64": "2.6.2", "@biomejs/cli-android-x64": "2.6.2", "@biomejs/cli-darwin-arm64": "2.6.2", "@biomejs/cli-darwin-x64": "2.6.2", "@biomejs/cli-linux-arm64": "2.6.2", "@biomejs/cli-linux-arm64-musl": "2.6.2", "@biomejs/cli-linux-x64": "2.6.2", "@biomejs/cli-linux-x64-musl": "2.6.2", "@biomejs/cli-win32-arm64": "2.6.2", "@biomejs/cli-win32-x64": "2.6.2" }, "bin": { "biome": "bin/biome" } }, "sha512-JLTFAFF9vfJcWwLhwuL4ZHflLg+XfTEC4IUnAlJRX1cMRt57++Xd1Ysb9Fdmlh9YUrvyCXfPEHjjEIqKJGdPKw=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.5.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BZVzFhJ/mUvTLMYbc9x6el0o2Uv5zP7bACYyhFb6fSc1giroXm7PL0a5KqMJ9F+/BnkYRjagD/ROSDVOZ6eapg=="], + "@biomejs/cli-android-arm64": ["@biomejs/cli-android-arm64@2.6.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dMpdpH7zFEL5FxuIA9LJW+RoNyC3oeDB7PJyjqVVwtMNoKB5uo8OZYmrVSBikn71txzYwy/3xga7UME6bJQZ6A=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.5.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-V5Kw63V+fVGNFhFrizDxMbGXAzZCh8kYzJpy3GI6gTapWDg7bZK9oYC9CbVgQSEdTnupz0U5Efz8Ev+5vdBBXw=="], + "@biomejs/cli-android-x64": ["@biomejs/cli-android-x64@2.6.2", "", { "os": "android", "cpu": "x64" }, "sha512-XCwEhPBGYzuRA502dRkCcQ+5cefQwuxQR7f9wa4do92dvIcuBL6VMLMFB/fL9hlpZ4dGSfHdpGFSIDlp52mg0A=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.5.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-XaG7P7eeSLYETD3K9grfB0mQmpLQZjygdbsxekWBJAg4am79fGAtRpfRyNnTvXB2fZFNnJZG3nXm7HBat0+VUw=="], + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.6.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rpXtabRgyZNz3aCjd/cRoSZGYi92R9eAXdUUe1sigIsRDyfMwgThaa98Yxt1fiZ7mxquqkAyIHIo08N/e1ToBA=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.5.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-tGzZUTcJCV7tj5Adh/Gn6nR4MycqA3iohq2LEcrarAgRBkU0h0OKd2UzCRkOsOYOuUGaUJb4YmqRqyy2feGGSw=="], + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.6.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-7GGzlm1GxUKoQ9CNV8tQZ+XXad4sz+v+uzNsWlhaGjsnZqUOVBw1OmAMpG3uNV/Mr0UrCpRJvSK0qvfuqSQOjg=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.5.15", "", { "os": "linux", "cpu": "x64" }, "sha512-xE4iEW/3LqlYj9GFgGrFsFSH51dEEpUbYWBfeOv0q87WkUDxK2o/HhreSb7qMJqck70RVM6Lg96hgedcAYcOkA=="], + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.6.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qs4CofCE8MdkjZFfEjswh2NAcbyQ7IVYtGSzQBvLqFlla/CSXroGbktUFndWG/kVSTywadauzAspYWtSfqAc2g=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.5.15", "", { "os": "linux", "cpu": "x64" }, "sha512-IlxUcyxilVGPsE008x13pWdkTbU3nQpP2i9b5UrbOYj6goBkKsRX1XB7yJLcV+O1H9LBchIIm4adOaVLZBX0ZQ=="], + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.6.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-I+FYCLVSmR3cYfKcQ+KNeLOZ5w0wRd9fQFlw3JbSvy7bkegvTqdHaBRRcJzHQB/1ldldTLIcG21ka6X2cTWmJw=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.5.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-2kPKzhNlm8C+Ru3GcO0Me2ODkCBLrVOUNuyi84RJyWDVKAA4+Kjj3jMjL938lF6BRfvltC4vB2nPY9SvN51Ovg=="], + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.6.2", "", { "os": "linux", "cpu": "x64" }, "sha512-FJKsb95418139xDdbv9XnNJsIIwvsgR21Of0aE9B9U+nQlOtRq5gJl2ps0jZUmYBxTaOlP9BKo8N+Ff8+Rui1g=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.5.15", "", { "os": "win32", "cpu": "x64" }, "sha512-yAzh4UqEImV6Hcy0zjUqNfJAsUhoD64FBQCdTG8Md/Z2wQeVP/ZmJ3XRYAs1g8J9fjTFyl2fCtc5FB5til47+g=="], + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.6.2", "", { "os": "linux", "cpu": "x64" }, "sha512-wnjEXAK2pDd2j8r9gKI3L+44sIxjkYzS5aHmD1+K57RNJ3sFyT4n22NH6QFPqqeVdRBxN54H3kckUUCrFKCcJw=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.6.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-/PEOo6evlgrQOfKhMNtC/4Gk8ai3hySXXEtjUzCqZe/4iAbMdqelpp7qvZEDyLxlZS+BjOMQFpq2gXI/BPaAqw=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.6.2", "", { "os": "win32", "cpu": "x64" }, "sha512-FVlWD+mDDz8nN5LWKRWUhhb7wKIK0VE3oXQvQBX7Fwwj00CvqMVhMyiz1iz8O5aZGmnkhFlKSwJPrUvMh5prQw=="], "@bramus/specificity": ["@bramus/specificity@2.4.3", "", { "dependencies": { "css-tree": "^3.2.1" }, "bin": { "specificity": "bin/cli.js" } }, "sha512-0GEz8qJWokkUVYQYRukmkSHIFNoBH8QTreeHrAFbvz1XFYc3Y+puhMs8PQ5x8GNugiC2l5jvDBXKLbBIByeSBw=="], @@ -1068,7 +1072,7 @@ "camel-case": ["camel-case@4.1.2", "", { "dependencies": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" } }, "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw=="], - "caniuse-lite": ["caniuse-lite@1.0.30001817", "", {}, "sha512-uZ1JG80BwhtMalIAGPxPfmNhHyd/B/nTdr3hITWlRamSyAdUStP1Gop01e7SD5q6J90ixJvWDbvmiFVfDgn4bw=="], + "caniuse-lite": ["caniuse-lite@1.0.30001818", "", {}, "sha512-XJbgo4adscw7R4MZU4/qVFkoMyPaNUAWmdNY4o/k9x7fFwmv4frIlndI9lWuaxviW9ObiZel9LuncnQPn6hQ5A=="], "case-sensitive-paths-webpack-plugin": ["case-sensitive-paths-webpack-plugin@2.4.0", "", {}, "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw=="], @@ -1080,7 +1084,7 @@ "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - "chromatic": ["chromatic@18.13.0", "", { "dependencies": { "semver": "^7.3.5" }, "peerDependencies": { "@chromatic-com/cypress": "^0.*.* || ^1.0.0", "@chromatic-com/playwright": "^0.*.* || ^1.0.0", "@chromatic-com/vitest": "^0.*.* || ^1.0.0" }, "optionalPeers": ["@chromatic-com/cypress", "@chromatic-com/playwright", "@chromatic-com/vitest"], "bin": { "chroma": "dist/bin.cjs", "chromatic": "dist/bin.cjs", "chromatic-cli": "dist/bin.cjs" } }, "sha512-AdN6tHB9anx/sdJKIMQ7JkQQV2RhVe1iTvXOFLlwFBRecbl9rp50h/25N0sZJDdpx22gG7Y81HoUALOORfMuxQ=="], + "chromatic": ["chromatic@18.13.1", "", { "dependencies": { "semver": "^7.3.5" }, "peerDependencies": { "@chromatic-com/cypress": "^0.*.* || ^1.0.0", "@chromatic-com/playwright": "^0.*.* || ^1.0.0", "@chromatic-com/vitest": "^0.*.* || ^1.0.0" }, "optionalPeers": ["@chromatic-com/cypress", "@chromatic-com/playwright", "@chromatic-com/vitest"], "bin": { "chroma": "dist/bin.cjs", "chromatic": "dist/bin.cjs", "chromatic-cli": "dist/bin.cjs" } }, "sha512-c/5EoZZI0i0eJpAbwTRF3e19wr61dNKHgUnUM8jkLRf45tnxJFUkT8m+T05Ul1js0rD5e7nb7UwcqAR8bAoIbQ=="], "chrome-trace-event": ["chrome-trace-event@1.0.4", "", {}, "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ=="], @@ -1262,7 +1266,7 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "electron-to-chromium": ["electron-to-chromium@1.5.461", "", {}, "sha512-3WZFARz1By+/rYcqQ0OLj4UcEdhd6tz2LEcDtKBUq7ey77EeJ1OtetY/5hjSUxw7dC5r5mA7FfkhRbqO9vnTCg=="], + "electron-to-chromium": ["electron-to-chromium@1.5.463", "", {}, "sha512-nVWNU6ltFjopvOdD44J4zoBkgO0M+WZiFKKFV7EjebOH9z0KNphk6bCEjplXtJofL21ARIuuwWOCsFkBVklm6w=="], "elliptic": ["elliptic@6.6.1", "", { "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", "hash.js": "^1.0.0", "hmac-drbg": "^1.0.1", "inherits": "^2.0.4", "minimalistic-assert": "^1.0.1", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g=="], @@ -1366,7 +1370,7 @@ "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], - "framer-motion": ["framer-motion@14.4.0", "", { "dependencies": { "motion-dom": "14.4.0", "motion-utils": "14.1.1", "tslib": "^2.4.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-HtO1fxord2rnXYRyr1laG4uY7+OtgwDc9Szcg1wlMq/LF7BO3/S7a+KEo691U7pSi+i0ueRuRqaD1UaLEmZHuQ=="], + "framer-motion": ["framer-motion@14.5.0", "", { "dependencies": { "motion-dom": "14.5.0", "motion-utils": "14.1.1", "tslib": "^2.4.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-pXIj1m9JX//3XExUgQIpS0f5iWcjenO14ekdg9S++Rr0VzfnAaveoNzonigRarD/+AM3umTGS3K/RANGQJitCQ=="], "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], @@ -1450,7 +1454,7 @@ "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - "immer": ["immer@11.1.21", "", {}, "sha512-bZSadeCPGdaY5/qRLgr6mDSH1ra21rD3rqpiWk7+QTr4ayYPTE9qZYdLh/NdPhMe0wSrTvHD/Lk8q5EwhUs0aw=="], + "immer": ["immer@11.2.6", "", {}, "sha512-AI5pkEyzJZLsu91LASM15eZ0L9CK9SJZ+yI/MwO3Gsv9ajEwTwILrzxnxRqquLVKG7oCalEgXpAPsJfyPuWk/w=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], @@ -1640,7 +1644,7 @@ "minimizer-webpack-plugin": ["minimizer-webpack-plugin@5.13.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "jest-worker": "^27.4.5", "schema-utils": "^4.6.0", "terser": "^5.51.0" }, "peerDependencies": { "@minify-html/node": "*", "@napi-rs/image": "*", "@swc/core": "*", "@swc/css": "*", "@swc/html": "*", "clean-css": "*", "cssnano": "*", "csso": "*", "esbuild": "*", "html-minifier-terser": "*", "imagemin": "*", "lightningcss": "*", "postcss": "*", "sharp": "*", "svgo": "*", "uglify-js": "*", "webpack": "^5.1.0" }, "optionalPeers": ["@minify-html/node", "@napi-rs/image", "@swc/core", "@swc/css", "@swc/html", "clean-css", "cssnano", "csso", "esbuild", "html-minifier-terser", "imagemin", "lightningcss", "postcss", "sharp", "svgo", "uglify-js"] }, "sha512-1mhAfvachWYYOm8tpmany5NLuvP+F2wxZWBSX8DC0uFS2wJpM9q3rWgN9wBooAH9d5V12wX0BjyJe4fMjHMarQ=="], - "motion-dom": ["motion-dom@14.4.0", "", { "dependencies": { "motion-utils": "14.1.1" } }, "sha512-1q8RyHSdr5COpzrv3JgJ9XRjOO88ZN9CwflpkGgyO/SR/SNRP/dD6EU8SEo092kcKY34IzHC5diJPKrQRMA8lQ=="], + "motion-dom": ["motion-dom@14.5.0", "", { "dependencies": { "motion-utils": "14.1.1" } }, "sha512-GAdnk1AgdtyrD3I09ZxEpV+Nt5QgwUsxN55qQV8mfk082dFQpcoT07wXkQAHRZjvZWUEBYhBVNeoXvtkYdL8jA=="], "motion-utils": ["motion-utils@14.1.1", "", {}, "sha512-3VvN5Uvf8B6qG+OfOui6Nht5EWp+vLuU3IqcAj7ItZGKqtmTjCebl5sGZF7/08gEHRnEl1AP/fIwbcEyVrcQGQ=="], @@ -1834,7 +1838,7 @@ "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], - "react-resizable-panels": ["react-resizable-panels@4.14.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-UP8lccy1yGA8p8c6UvPaMDnqYePv/CWOv3R6rTAJ9f+UmuaeiTFDJlvcp5rFu5GJcQ1ofCmkjbzp0HceJXZRbw=="], + "react-resizable-panels": ["react-resizable-panels@4.14.8", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-kVot7JCIZct85ZY87vww95R7tI25VSTjHpEAIIy2YcsOtbtwG6l1+vDD6TCYgmgE/JSp0ADqNooHn97Y6dMOFw=="], "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], @@ -2020,9 +2024,9 @@ "tinyspy": ["tinyspy@4.0.6", "", {}, "sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg=="], - "tldts": ["tldts@7.4.18", "", { "dependencies": { "tldts-core": "^7.4.18" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-somxA8xKAUzlAxl0TX/jMSYIbRNFIVGnI96pVGJPeMc5BsHmfVXn6mOJT6U++ELnl04eGfzpfcEuZe8WiASkXg=="], + "tldts": ["tldts@7.4.19", "", { "dependencies": { "tldts-core": "^7.4.19" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-48dH6aSpQnpbzDUc4vIQZ1VjB8tkK+e8c1yx05/SZK+rkssgjEOlxDd/yU8/a4vGYQ9vRgJr9dwMq9TJ0b3X2Q=="], - "tldts-core": ["tldts-core@7.4.18", "", {}, "sha512-KI2PKvCmvQI/xD6ypPqkU0i7oxNXR/O58RLRaHR4svsgVUQ2JE/XC8BtMbG6PSehD27I8sZJRcC1ASnCy6lp3g=="], + "tldts-core": ["tldts-core@7.4.19", "", {}, "sha512-goNXV6Ho9TIOfpJZanTd6CKG5DyYgYAL+k+22T0SLh8l6+KHIfBUPFuePuSZv05UttIaaLmI9tb7TmGIdK5AkQ=="], "to-buffer": ["to-buffer@1.2.2", "", { "dependencies": { "isarray": "^2.0.5", "safe-buffer": "^5.2.1", "typed-array-buffer": "^1.0.3" } }, "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw=="], @@ -2102,7 +2106,7 @@ "victory-vendor": ["victory-vendor@37.3.6", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="], - "vite": ["vite@8.3.4", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", "postcss": "^8.5.29", "rolldown": "~1.2.12", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ=="], + "vite": ["vite@8.3.5", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", "postcss": "^8.5.30", "rolldown": "~1.2.14", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.7.1 || ^0.8.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ=="], "vitest": ["vitest@5.0.5", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/mocker": "5.0.5", "chai": "^6.3.0", "es-module-lexer": "^2.3.2", "expect-type": "^1.4.0", "magic-string": "^1.4.3", "obug": "^3.0.0", "picomatch": "^4.0.7", "std-env": "^4.3.0", "tinybench": "^6.2.2", "tinyexec": "^1.3.1", "tinyglobby": "^0.2.17", "why-is-node-running": "3.2.1" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "5.0.5", "@vitest/browser-preview": "5.0.5", "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", "@vitest/coverage-istanbul": "5.0.5", "@vitest/coverage-v8": "5.0.5", "@vitest/ui": "5.0.5", "happy-dom": "*", "jsdom": "*", "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-cPODOMF8YnkiiYtDdHo6n4wWmBrOBBbOIYH2d33M4l3CdNn72h+qMmO6uXffUN6A5AZFXNND/d+krS9mgkXRfg=="], diff --git a/components/background-provider.tsx b/components/background-provider.tsx index ec70ecd0..e27071d3 100644 --- a/components/background-provider.tsx +++ b/components/background-provider.tsx @@ -4,6 +4,7 @@ import * as React from "react"; import { CanvasBackground } from "@/components/canvas-background"; import { GradientBackground } from "@/components/gradient-background"; import { PATTERN_DENSITIES, PATTERN_STYLES, PatternBackground, type PatternDensity, type PatternStyle } from "@/components/pattern-background"; +import { usePublishAppBackground } from "@/hooks/use-glass-backdrop"; import type { CanvasStyle } from "@/lib/canvas-background-utils"; import type { GradientGeometry, GradientShape, RampGradientAxis } from "@/lib/oklch-utils"; @@ -289,6 +290,30 @@ interface RenderArgs { baseColor: string | null; } +/** The solid "none" backdrop. One pair so the painted floor and the prebaked slice cannot drift. */ +const NONE_LIGHT = "oklch(0.98 0.006 var(--glass-tint-h))"; +const NONE_DARK = "oklch(0.15 0.014 var(--glass-tint-h))"; + +function PublishNoneBackground({ baseColor }: { baseColor: string | null }) { + const [dark, setDark] = React.useState(false); + React.useEffect(() => { + const root = document.documentElement; + const read = () => setDark(root.classList.contains("dark")); + read(); + const mo = new MutationObserver(read); + mo.observe(root, { + attributes: true, + attributeFilter: [ + "class", + ], + }); + return () => mo.disconnect(); + }, []); + // A flat color is already its own blur. filter: none keeps the slice from paying for a no-op. + usePublishAppBackground(baseColor ?? (dark ? NONE_DARK : NONE_LIGHT), "none"); + return null; +} + function renderBackground(background: BackgroundType, args: RenderArgs) { switch (background) { case "canvas": @@ -320,7 +345,8 @@ function renderBackground(background: BackgroundType, args: RenderArgs) { // default, overridable by the user's chosen base color. return ( <> - + +
(null); + useGlassBackdrop(scrollRef); + const { background } = useBackground(); + const fixed = background === "gradient" || background === "none"; + + return ( +
+

+ {fixed + ? "These bubbles sample the backdrop from --app-bg-blurred. Scroll the list: the slice stays put on the page." + : "Set the header backdrop to Gradient (or None). Pattern scenes and the canvas keep the live filter, because they are not one image."} +

+
+ {LINES.map((line) => ( +
+ {line.text} +
+ ))} +
+
+ ); +} diff --git a/components/gradient-background.tsx b/components/gradient-background.tsx index eda1243c..9d5f987a 100644 --- a/components/gradient-background.tsx +++ b/components/gradient-background.tsx @@ -1,5 +1,6 @@ "use client"; +import { APP_BG_FROST_FILTER, usePublishAppBackground } from "@/hooks/use-glass-backdrop"; import { backdropPalette, useBackdropTint } from "@/lib/canvas-background-utils"; import { bandedFrescoStops, @@ -15,7 +16,14 @@ import { * A ramp-driven gradient wallpaper. The gradient is one of our oklch ramps (hue / lightness / tonal * / chroma) centered on the live glass-tint color (so it recolors with the theme) with the center as a * slightly wider plateau, painted as a `linear`, `radial`, or `conic` gradient. Pure CSS — crisp at any DPI. + * + * The highlight veil is a background LAYER on this element, not a child: one background string is the + * whole backdrop, and prebaked glass (`usePublishAppBackground`) samples that same string. A child + * overlay would frost the gradient and leave the highlights out of every bubble's slice. */ +const BACKDROP_HIGHLIGHTS = + "radial-gradient(circle at 20% 25%, oklch(var(--shadow-highlight) / 0.1) 0%, transparent 45%), radial-gradient(circle at 80% 75%, oklch(var(--shadow-ink) / 0.1) 0%, transparent 45%)"; + export function GradientBackground({ axis = "tonal", hue, @@ -56,23 +64,17 @@ export function GradientBackground({ radialSize, }); + /* First layer paints on top. Same highlights the old child overlay drew, still plain alpha — no + mix-blend-mode, which can disable backdrop-filter rendering in Chromium. */ + const stack = `${BACKDROP_HIGHLIGHTS}, ${gradient}`; + usePublishAppBackground(stack, APP_BG_FROST_FILTER); + return (
- {/* Subtle organic highlight/shadow for depth — plain alpha layers, NO mix-blend-mode: a - blend-mode element on the page can disable backdrop-filter RENDERING in Chromium, killing - every glass blur above it (computed styles stay correct, paint silently fails). */} -
-
+ /> ); } diff --git a/hooks/use-glass-backdrop.ts b/hooks/use-glass-backdrop.ts new file mode 100644 index 00000000..76a2624e --- /dev/null +++ b/hooks/use-glass-backdrop.ts @@ -0,0 +1,100 @@ +import * as React from "react"; + +/** + * Prebaked frost — the position half of `.glass-prebaked` (app/theme/styles.css). + * + * A fixed background is the case this handles. Each bubble's blurred slice is a viewport-sized copy + * of `--app-bg-blurred`, shifted by the bubble's viewport position so the copy lines up with the + * page. One rAF reads the visible bubbles and writes `--gx` / `--gy`. No React state. + * + * Call `syncGlassBackdrop` from a motion `onUpdate` while a bubble animates on `y` or `scale`. + * Between syncs the slice drifts, because the offset was measured at the previous position. Keep + * entrance moves small when nothing is calling sync mid-animation. A `translate` lines up again on + * the next sync; a `scale` does not, because the slice's `left` / `top` are in the bubble's local + * pixels and the scale changes how those pixels map to the screen. + * + * `getBoundingClientRect` on the bubbles actually on screen (a few dozen) is cheap. If a profiler + * disagrees, derive the offset from the virtualizer's `start` minus the scroll offset instead of + * reading layout. + */ + +const SELECTOR = ".glass-prebaked"; + +/** Frost for a CSS background that is not already blurred. 12px is the diffuse floor (`--blur-xl`). */ +export const APP_BG_FROST_FILTER = "blur(12px) saturate(180%)"; + +/** Write `--gx` / `--gy` on every prebaked surface under `root` (and on `root`, if it is one). */ +export function syncGlassBackdrop(root: ParentNode) { + const nodes: HTMLElement[] = []; + if (root instanceof HTMLElement && root.matches(SELECTOR)) nodes.push(root); + root.querySelectorAll(SELECTOR).forEach((el) => nodes.push(el)); + for (const el of nodes) { + const r = el.getBoundingClientRect(); + el.style.setProperty("--gx", `${r.left}px`); + el.style.setProperty("--gy", `${r.top}px`); + } +} + +/** + * Keep prebaked slices aligned while `scrollRef` scrolls, the window scrolls, or rows mount. + * The scroll element has to be mounted before this effect runs (`useRef` on an always-rendered node). + */ +export function useGlassBackdrop(scrollRef: React.RefObject) { + React.useEffect(() => { + const root = scrollRef.current; + if (!root) return; + let raf = 0; + const sync = () => { + raf = 0; + syncGlassBackdrop(root); + }; + const queue = () => { + if (!raf) raf = requestAnimationFrame(sync); + }; + root.addEventListener("scroll", queue, { passive: true }); + // Scroll events do not bubble. Capture on window also sees the page scroll, which moves + // bubbles whose own container did not scroll, and any nested scroller under the root. + window.addEventListener("scroll", queue, { capture: true, passive: true }); + window.addEventListener("resize", queue); + const viewport = window.visualViewport; + viewport?.addEventListener("resize", queue); + viewport?.addEventListener("scroll", queue); + // childList: virtualized rows. class: the prebaked class arriving on a node that already existed. + // Not `style` — the sync writes inline style, and watching it would reschedule itself forever. + const mo = new MutationObserver(queue); + mo.observe(root, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"] }); + queue(); + return () => { + root.removeEventListener("scroll", queue); + window.removeEventListener("scroll", queue, { capture: true }); + window.removeEventListener("resize", queue); + viewport?.removeEventListener("resize", queue); + viewport?.removeEventListener("scroll", queue); + mo.disconnect(); + cancelAnimationFrame(raf); + }; + }, [scrollRef]); +} + +/** + * Publish the fixed background prebaked slices sample. `filter` is `APP_BG_FROST_FILTER` for a live + * CSS background, and omitted (or null) when `background` is already a blurred image. Pass null for + * `background` to leave the variables alone. Cleanup removes only the values this effect set. + */ +export function usePublishAppBackground(background: string | null, filter?: string | null) { + React.useEffect(() => { + if (!background) return; + const root = document.documentElement; + root.style.setProperty("--app-bg-blurred", background); + if (filter) root.style.setProperty("--app-bg-filter", filter); + else root.style.removeProperty("--app-bg-filter"); + return () => { + if (root.style.getPropertyValue("--app-bg-blurred") === background) { + root.style.removeProperty("--app-bg-blurred"); + } + if (filter && root.style.getPropertyValue("--app-bg-filter") === filter) { + root.style.removeProperty("--app-bg-filter"); + } + }; + }, [background, filter]); +} diff --git a/lib/material.ts b/lib/material.ts index 14ec9c0c..1bc96119 100644 --- a/lib/material.ts +++ b/lib/material.ts @@ -4,8 +4,8 @@ import type * as React from "react"; * The Sistine material system — the single source of the material → markup mapping. * * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in - * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen) - * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding + * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, + * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes). * * Semantics: @@ -45,6 +45,10 @@ export interface MaterialProps { glow?: boolean | "lg"; /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */ sheen?: boolean; + /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter. + * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces + * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */ + prebaked?: boolean; } /** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material @@ -65,7 +69,7 @@ export function splitAxisProps

( MaterialAxisProps, SplitAxisRest

, ] { - const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props; + const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props; return [ { material, @@ -76,6 +80,7 @@ export function splitAxisProps

( gradient, glow, sheen, + prebaked, }, /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by * construction: rest is exactly P minus the axis keys. */ @@ -91,7 +96,7 @@ export interface MaterialAttrs { } export function glassMaterial(props: MaterialProps = {}): MaterialAttrs { - const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props; + const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props; if (material === "none") { return { className: "", @@ -114,6 +119,7 @@ export function glassMaterial(props: MaterialProps = {}): MaterialAttrs { gradient && "glass-gradient", glow === "lg" ? "glass-glow-lg" : glow ? "glass-glow" : false, sheen && "glass-sheen", + prebaked && "glass-prebaked", ] .filter(Boolean) .join(" "), @@ -141,6 +147,7 @@ export function materialSurface(role: MaterialProps | null, props: MaterialProps gradient: props.gradient ?? role.gradient, glow: props.glow ?? role.glow, sheen: props.sheen ?? role.sheen, + prebaked: props.prebaked ?? role.prebaked, }); } diff --git a/package.json b/package.json index dc0aceb4..2dc07f0a 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.4.0", - "framer-motion": "^14.4.0", + "framer-motion": "^14.5.0", "input-otp": "^1.5.0", "next": "^16.4.2", "next-themes": "^0.4.6", @@ -46,7 +46,7 @@ "react-day-picker": "^10.0.2", "react-dom": "^19.3.1", "react-easy-crop": "^6.2.4", - "react-resizable-panels": "^4.14.7", + "react-resizable-panels": "^4.14.8", "recharts": "^3.10.1", "shadcn": "^4.22.1", "sonner": "^2.0.8", @@ -56,7 +56,7 @@ "zod": "^4.6.7" }, "devDependencies": { - "@biomejs/biome": "^2.5.15", + "@biomejs/biome": "^2.6.2", "@chromatic-com/storybook": "^5.6.0", "@storybook/addon-a11y": "^10.6.3", "@storybook/addon-docs": "^10.6.3", diff --git a/public/r/accordion.json b/public/r/accordion.json index 1c151353..f44fb4e4 100644 --- a/public/r/accordion.json +++ b/public/r/accordion.json @@ -22,7 +22,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/alert-dialog.json b/public/r/alert-dialog.json index 51c44ee7..8287dbde 100644 --- a/public/r/alert-dialog.json +++ b/public/r/alert-dialog.json @@ -22,7 +22,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/alert.json b/public/r/alert.json index bd47af79..446ee8db 100644 --- a/public/r/alert.json +++ b/public/r/alert.json @@ -20,7 +20,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/avatar.json b/public/r/avatar.json index 07da483b..b3a62570 100644 --- a/public/r/avatar.json +++ b/public/r/avatar.json @@ -21,7 +21,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/background-controller.json b/public/r/background-controller.json index 7d65b7e9..c28903d8 100644 --- a/public/r/background-controller.json +++ b/public/r/background-controller.json @@ -11,7 +11,8 @@ "registryDependencies": [ "@sistine/canvas-background-utils", "@sistine/oklch-utils", - "@sistine/theme" + "@sistine/theme", + "@sistine/use-glass-backdrop" ], "files": [ { @@ -34,13 +35,13 @@ }, { "path": "components/gradient-background.tsx", - "content": "\"use client\";\n\nimport { backdropPalette, useBackdropTint } from \"@/lib/canvas-background-utils\";\nimport {\n bandedFrescoStops,\n type GradientGeometry,\n type GradientShape,\n type RampGradientAxis,\n rampGradient,\n readableLightnessBand,\n wrapGradient,\n} from \"@/lib/oklch-utils\";\n\n/**\n * A ramp-driven gradient wallpaper. The gradient is one of our oklch ramps (hue / lightness / tonal\n * / chroma) centered on the live glass-tint color (so it recolors with the theme) with the center as a\n * slightly wider plateau, painted as a `linear`, `radial`, or `conic` gradient. Pure CSS — crisp at any DPI.\n */\nexport function GradientBackground({\n axis = \"tonal\",\n hue,\n angle = 90,\n shape = \"linear\",\n position,\n radialShape,\n radialSize,\n}: {\n axis?: RampGradientAxis;\n /** Hue override (deg). When set, drives the gradient color instead of the live --glass-tint-h. */\n hue?: number;\n angle?: number;\n shape?: GradientShape;\n} & GradientGeometry) {\n const tint = useBackdropTint();\n // Shared with CanvasBackground: theme-tracking base + fresco palette (the `hue` prop overrides both).\n const { base, frescoColors } = backdropPalette(tint, hue);\n /* The ramps span their FULL range by design — as a wallpaper that put pure black at one edge of the\n viewport and pure white at the other, so a single solved foreground could only be readable at one\n end. Fit the ramp into the band where text over it clears APCA 60 instead. Falls back to the whole\n range if the foreground has not resolved yet (first paint, before AutoForeground runs). */\n const band = tint.fg ? readableLightnessBand(tint.fg, base) : undefined;\n const gradient = frescoColors\n ? wrapGradient(shape, bandedFrescoStops(frescoColors, band, shape), {\n angle,\n position,\n radialShape,\n radialSize,\n })\n : rampGradient(axis, base, 5, {\n angle,\n band,\n gamut: tint.p3 ? \"p3\" : \"srgb\",\n shape,\n position,\n radialShape,\n radialSize,\n });\n\n return (\n \n {/* Subtle organic highlight/shadow for depth — plain alpha layers, NO mix-blend-mode: a\n blend-mode element on the page can disable backdrop-filter RENDERING in Chromium, killing\n every glass blur above it (computed styles stay correct, paint silently fails). */}\n \n

\n );\n}\n", + "content": "\"use client\";\n\nimport { APP_BG_FROST_FILTER, usePublishAppBackground } from \"@/hooks/use-glass-backdrop\";\nimport { backdropPalette, useBackdropTint } from \"@/lib/canvas-background-utils\";\nimport {\n bandedFrescoStops,\n type GradientGeometry,\n type GradientShape,\n type RampGradientAxis,\n rampGradient,\n readableLightnessBand,\n wrapGradient,\n} from \"@/lib/oklch-utils\";\n\n/**\n * A ramp-driven gradient wallpaper. The gradient is one of our oklch ramps (hue / lightness / tonal\n * / chroma) centered on the live glass-tint color (so it recolors with the theme) with the center as a\n * slightly wider plateau, painted as a `linear`, `radial`, or `conic` gradient. Pure CSS — crisp at any DPI.\n *\n * The highlight veil is a background LAYER on this element, not a child: one background string is the\n * whole backdrop, and prebaked glass (`usePublishAppBackground`) samples that same string. A child\n * overlay would frost the gradient and leave the highlights out of every bubble's slice.\n */\nconst BACKDROP_HIGHLIGHTS =\n \"radial-gradient(circle at 20% 25%, oklch(var(--shadow-highlight) / 0.1) 0%, transparent 45%), radial-gradient(circle at 80% 75%, oklch(var(--shadow-ink) / 0.1) 0%, transparent 45%)\";\n\nexport function GradientBackground({\n axis = \"tonal\",\n hue,\n angle = 90,\n shape = \"linear\",\n position,\n radialShape,\n radialSize,\n}: {\n axis?: RampGradientAxis;\n /** Hue override (deg). When set, drives the gradient color instead of the live --glass-tint-h. */\n hue?: number;\n angle?: number;\n shape?: GradientShape;\n} & GradientGeometry) {\n const tint = useBackdropTint();\n // Shared with CanvasBackground: theme-tracking base + fresco palette (the `hue` prop overrides both).\n const { base, frescoColors } = backdropPalette(tint, hue);\n /* The ramps span their FULL range by design — as a wallpaper that put pure black at one edge of the\n viewport and pure white at the other, so a single solved foreground could only be readable at one\n end. Fit the ramp into the band where text over it clears APCA 60 instead. Falls back to the whole\n range if the foreground has not resolved yet (first paint, before AutoForeground runs). */\n const band = tint.fg ? readableLightnessBand(tint.fg, base) : undefined;\n const gradient = frescoColors\n ? wrapGradient(shape, bandedFrescoStops(frescoColors, band, shape), {\n angle,\n position,\n radialShape,\n radialSize,\n })\n : rampGradient(axis, base, 5, {\n angle,\n band,\n gamut: tint.p3 ? \"p3\" : \"srgb\",\n shape,\n position,\n radialShape,\n radialSize,\n });\n\n /* First layer paints on top. Same highlights the old child overlay drew, still plain alpha — no\n mix-blend-mode, which can disable backdrop-filter rendering in Chromium. */\n const stack = `${BACKDROP_HIGHLIGHTS}, ${gradient}`;\n usePublishAppBackground(stack, APP_BG_FROST_FILTER);\n\n return (\n \n );\n}\n", "type": "registry:component", "target": "components/gradient-background.tsx" }, { "path": "components/background-provider.tsx", - "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { CanvasBackground } from \"@/components/canvas-background\";\nimport { GradientBackground } from \"@/components/gradient-background\";\nimport { PATTERN_DENSITIES, PATTERN_STYLES, PatternBackground, type PatternDensity, type PatternStyle } from \"@/components/pattern-background\";\nimport type { CanvasStyle } from \"@/lib/canvas-background-utils\";\nimport type { GradientGeometry, GradientShape, RampGradientAxis } from \"@/lib/oklch-utils\";\n\nexport type BackgroundType = \"gradient\" | \"canvas\" | \"pattern\" | \"none\";\n\nconst STORAGE_KEY = \"sistine-background\";\n\n/** Canvas styles (lava renamed from \"blobs\", circle from \"circles\"). */\nexport const CANVAS_STYLES: CanvasStyle[] = [\n \"gradient\",\n \"lava\",\n \"circle\",\n];\n\n/** Ramp axes shared by the Gradient background and the Canvas (lightness = the \"linear\" ramp). */\nexport const RAMP_AXES: RampGradientAxis[] = [\n \"tonal\",\n \"hue\",\n \"lightness\",\n \"chroma\",\n];\n\n/** Gradient painting shapes the Gradient background cycles through. */\nexport const GRADIENT_SHAPES: GradientShape[] = [\n \"linear\",\n \"radial\",\n \"conic\",\n];\n\n/** Center positions (radial + conic) the switcher cycles through. */\nexport const GRADIENT_POSITIONS: {\n value: string;\n label: string;\n}[] = [\n {\n value: \"50% 50%\",\n label: \"center\",\n },\n {\n value: \"0% 0%\",\n label: \"top-left\",\n },\n {\n value: \"100% 0%\",\n label: \"top-right\",\n },\n {\n value: \"100% 100%\",\n label: \"bottom-right\",\n },\n {\n value: \"0% 100%\",\n label: \"bottom-left\",\n },\n];\n\n/** The position values the cycler steps through (labels live in GRADIENT_POSITIONS). */\nconst GRADIENT_POSITION_VALUES = GRADIENT_POSITIONS.map((p) => p.value);\n\n/** Radial shape + size options. */\nexport const RADIAL_SHAPES: NonNullable[] = [\n \"circle\",\n \"ellipse\",\n];\nexport const RADIAL_SIZES: NonNullable[] = [\n \"farthest-corner\",\n \"farthest-side\",\n \"closest-corner\",\n \"closest-side\",\n];\n\n/** Angle presets — 45° steps around the circle, shared by the gradient + canvas angle cyclers. */\nconst ANGLES = [\n 0,\n 45,\n 90,\n 135,\n 180,\n 225,\n 270,\n 315,\n];\n\n/** Steps-per-side presets the canvas/ramps cycle through (4–12). */\nexport const CANVAS_STEPS = [\n 4,\n 6,\n 8,\n 10,\n 12,\n] as const;\n\n/** Animation pace presets. */\nexport const CANVAS_SPEEDS = [\n 0.5,\n 1,\n 2,\n] as const;\n\n/** Animated-pattern pace: loop/scroll duration in SECONDS (2s fastest … 16s slowest); 0 = static.\n * Shared by every animated pattern (synthwave, moonrise, clouds, dune, aurora, forest, chase) via the --pat-dur CSS var. */\nexport const PATTERN_SPEEDS = [\n 2,\n 4,\n 8,\n 12,\n 16,\n 0,\n] as const;\n\n/** Sun/moon horizontal placement for the horizon scenes (synthwave / moonrise / clouds / dune / aurora / forest) — off-center\n * clears the page's centered content. */\nexport const PATTERN_DISCS = [\n \"right\",\n \"center\",\n \"left\",\n] as const;\n\n/** Blowing-sand intensity for the dune scene — \"still\" is a calm desert (no airborne sand at all),\n * distinct from the speed control's \"static\", which only pauses the motion that is already there. */\nexport const PATTERN_SANDS = [\n \"breeze\",\n \"storm\",\n \"still\",\n] as const;\n\nfunction persistBackground(next: BackgroundType) {\n try {\n localStorage.setItem(STORAGE_KEY, next);\n } catch {\n // ignore storage failures (private mode, etc.)\n }\n}\n\nfunction pickRandom(items: readonly T[]): T {\n return items[Math.floor(Math.random() * items.length)];\n}\n\nfunction cycle(items: readonly T[], current: T): T {\n const i = items.indexOf(current);\n return items[(i + 1) % items.length];\n}\n\n/**\n * State that steps through a fixed preset list, wrapping at the end. Returns the value, a stable\n * advance-to-the-next-preset callback, and the raw setter.\n */\nfunction useCycle(\n items: readonly T[],\n initial: T,\n): [\n T,\n () => void,\n React.Dispatch>,\n] {\n const [value, setValue] = React.useState(initial);\n const next = React.useCallback(\n () => setValue((v) => cycle(items, v)),\n [\n items,\n ],\n );\n return [\n value,\n next,\n setValue,\n ];\n}\n\ninterface BackgroundContextValue {\n background: BackgroundType;\n setBackground: (background: BackgroundType) => void;\n /** The ramp axis driving the gradient background. */\n gradientAxis: RampGradientAxis;\n /** Switch to the gradient background and set its ramp axis. */\n setGradientAxis: (axis: RampGradientAxis) => void;\n /** Gradient angle in degrees (90 = left → right). */\n gradientAngle: number;\n /** Rotate the gradient by 45°. */\n cycleGradientAngle: () => void;\n /** The gradient painting shape (linear | radial | conic). */\n gradientShape: GradientShape;\n /** Advance to the next gradient shape. */\n cycleGradientShape: () => void;\n /** Center position for radial + conic gradients (CSS `at `). */\n gradientPosition: string;\n /** Advance to the next center-position preset. */\n cycleGradientPosition: () => void;\n /** Radial shape (circle | ellipse). */\n radialShape: NonNullable;\n /** Toggle the radial shape. */\n cycleRadialShape: () => void;\n /** Radial size keyword. */\n radialSize: NonNullable;\n /** Advance to the next radial size. */\n cycleRadialSize: () => void;\n // ── Canvas ──\n /** The canvas style (gradient | lava | circle). */\n canvasStyle: CanvasStyle;\n /** Switch to the canvas background and set its style. */\n setCanvasStyle: (style: CanvasStyle) => void;\n /** The ramp axis driving the canvas colors. */\n canvasRamp: RampGradientAxis;\n /** Switch to the canvas background and set its ramp axis. */\n setCanvasRamp: (axis: RampGradientAxis) => void;\n /** Steps per side (4–12). */\n canvasSteps: number;\n /** Advance to the next steps-per-side preset. */\n cycleCanvasSteps: () => void;\n /** Canvas gradient angle in degrees. */\n canvasAngle: number;\n /** Rotate the canvas gradient by 45°. */\n cycleCanvasAngle: () => void;\n /** Canvas animation pace. */\n canvasSpeed: number;\n /** Advance to the next animation-pace preset. */\n cycleCanvasSpeed: () => void;\n /** Whether the canvas is currently animating. */\n canvasAnimated: boolean;\n /** Toggle canvas animation on/off. */\n toggleCanvasAnimated: () => void;\n /** Switch to the canvas background and reshuffle it (new style + ramp + layout seed). */\n shuffleCanvas: () => void;\n // ── Pattern (pure-CSS wallpapers) ──\n /** The active CSS pattern style. */\n patternStyle: PatternStyle;\n /** Switch to the pattern background and set its style. */\n setPatternStyle: (style: PatternStyle) => void;\n /** Advance to the next pattern style. */\n cyclePatternStyle: () => void;\n /** Star-field density (sparse | medium | dense). */\n patternDensity: PatternDensity;\n /** Advance to the next star-field density. */\n cyclePatternDensity: () => void;\n /** Horizon-grid scroll pace (synthwave / moonrise); 0 is static. */\n patternSpeed: number;\n /** Advance to the next grid scroll pace (… → static). */\n cyclePatternSpeed: () => void;\n /** Sun/moon placement for the horizon scenes (right | center | left). */\n patternDisc: (typeof PATTERN_DISCS)[number];\n /** Advance to the next sun/moon placement. */\n cyclePatternDisc: () => void;\n /** Blowing-sand intensity for the dune scene (breeze | storm | still). */\n patternSand: (typeof PATTERN_SANDS)[number];\n /** Advance to the next blowing-sand level. */\n cyclePatternSand: () => void;\n // ── Base color (the \"none\" backdrop) ──\n /** Base color for the \"none\" backdrop; null = follow the theme (tint-tinted, light/dark aware). */\n baseColor: string | null;\n /** Override the \"none\" backdrop color; null clears back to the themed default. */\n setBaseColor: (color: string | null) => void;\n}\n\nconst BackgroundContext = React.createContext(null);\n\nexport function useBackground() {\n const context = React.useContext(BackgroundContext);\n if (!context) {\n throw new Error(\"useBackground must be used within a BackgroundProvider\");\n }\n return context;\n}\n\ninterface RenderArgs {\n gradientAxis: RampGradientAxis;\n gradientAngle: number;\n gradientShape: GradientShape;\n gradientPosition: string;\n radialShape: NonNullable;\n radialSize: NonNullable;\n canvasStyle: CanvasStyle;\n canvasRamp: RampGradientAxis;\n canvasSteps: number;\n canvasAngle: number;\n canvasSpeed: number;\n canvasAnimated: boolean;\n canvasSeed: string;\n patternStyle: PatternStyle;\n patternDensity: PatternDensity;\n patternSpeed: number;\n patternDisc: (typeof PATTERN_DISCS)[number];\n patternSand: (typeof PATTERN_SANDS)[number];\n baseColor: string | null;\n}\n\nfunction renderBackground(background: BackgroundType, args: RenderArgs) {\n switch (background) {\n case \"canvas\":\n // key forces a clean redraw when the style/ramp/steps/seed/animation change\n return (\n \n );\n case \"pattern\":\n return (\n \n );\n case \"none\":\n // Forgo the decorative backdrop — a clean solid that honors the theme (tint hue, light/dark) by\n // default, overridable by the user's chosen base color.\n return (\n <>\n \n \n \n );\n default:\n return (\n \n );\n }\n}\n\n/**\n * Holds the site-wide background choice (persisted to localStorage) and renders it\n * behind the app. Pair with to preview + tune each style.\n */\nexport function BackgroundProvider({ children }: { children: React.ReactNode }) {\n // SSR + first client render use \"gradient\" so hydration matches; localStorage is read after mount.\n const [background, setBackgroundState] = React.useState(\"pattern\");\n const [gradientAxis, setGradientAxisState] = React.useState(\"tonal\");\n const [gradientAngle, cycleGradientAngle] = useCycle(ANGLES, 90);\n const [gradientShape, cycleGradientShape] = useCycle(GRADIENT_SHAPES, \"linear\");\n const [gradientPosition, cycleGradientPosition] = useCycle(GRADIENT_POSITION_VALUES, \"50% 50%\");\n const [radialShape, cycleRadialShape] = useCycle(RADIAL_SHAPES, \"circle\");\n const [radialSize, cycleRadialSize] = useCycle(RADIAL_SIZES, \"farthest-corner\");\n const [canvasStyle, setCanvasStyleState] = React.useState(\"gradient\");\n const [canvasRamp, setCanvasRampState] = React.useState(\"tonal\");\n const [canvasSteps, cycleCanvasSteps] = useCycle(CANVAS_STEPS, 6);\n const [canvasAngle, cycleCanvasAngle] = useCycle(ANGLES, 90);\n const [canvasSpeed, cycleCanvasSpeed] = useCycle(CANVAS_SPEEDS, 1);\n const [canvasAnimated, setCanvasAnimated] = React.useState(false);\n const [canvasSeed, setCanvasSeed] = React.useState(\"sistine\");\n const [patternStyle, cyclePatternStyleState, setPatternStyleState] = useCycle(PATTERN_STYLES, \"moonrise\");\n const [patternDensity, cyclePatternDensityState] = useCycle(PATTERN_DENSITIES, \"medium\");\n /* Static by DEFAULT (0 = no animation, the last entry in PATTERN_SPEEDS). The pattern layer is a\n backdrop for reading glass against, and a moving one competes with the content for attention on\n first load — motion should be something you opt into, not something you have to switch off. The\n speed control still cycles 2/4/8/12/16/0, so the animation is one click away. */\n const [patternSpeed, cyclePatternSpeedState] = useCycle(PATTERN_SPEEDS, 0);\n const [baseColor, setBaseColor] = React.useState(null);\n const [patternDisc, cyclePatternDiscState] = useCycle(PATTERN_DISCS, \"right\");\n const [patternSand, cyclePatternSandState] = useCycle(PATTERN_SANDS, \"breeze\");\n\n React.useEffect(() => {\n const stored = localStorage.getItem(STORAGE_KEY);\n if (stored === \"gradient\" || stored === \"canvas\" || stored === \"pattern\" || stored === \"none\") {\n setBackgroundState(stored);\n }\n }, []);\n\n /** The coupling every per-style setter/cycler shares: switch to that background type, apply, persist. */\n const withType =\n (type: BackgroundType, fn: (...args: A) => void) =>\n (...args: A) => {\n setBackgroundState(type);\n fn(...args);\n persistBackground(type);\n };\n\n const value: BackgroundContextValue = {\n background,\n setBackground: (next) => {\n setBackgroundState(next);\n persistBackground(next);\n },\n gradientAxis,\n setGradientAxis: withType(\"gradient\", setGradientAxisState),\n gradientAngle,\n cycleGradientAngle,\n gradientShape,\n cycleGradientShape,\n gradientPosition,\n cycleGradientPosition,\n radialShape,\n cycleRadialShape,\n radialSize,\n cycleRadialSize,\n canvasStyle,\n setCanvasStyle: withType(\"canvas\", setCanvasStyleState),\n canvasRamp,\n setCanvasRamp: withType(\"canvas\", setCanvasRampState),\n canvasSteps,\n cycleCanvasSteps,\n canvasAngle,\n cycleCanvasAngle,\n canvasSpeed,\n cycleCanvasSpeed,\n canvasAnimated,\n toggleCanvasAnimated: () => setCanvasAnimated((on) => !on),\n shuffleCanvas: withType(\"canvas\", () => {\n setCanvasStyleState(pickRandom(CANVAS_STYLES));\n setCanvasRampState(pickRandom(RAMP_AXES));\n setCanvasSeed(Math.random().toString(36).slice(2));\n }),\n patternStyle,\n setPatternStyle: withType(\"pattern\", setPatternStyleState),\n cyclePatternStyle: withType(\"pattern\", cyclePatternStyleState),\n patternDensity,\n cyclePatternDensity: withType(\"pattern\", cyclePatternDensityState),\n patternSpeed,\n cyclePatternSpeed: withType(\"pattern\", cyclePatternSpeedState),\n patternDisc,\n cyclePatternDisc: withType(\"pattern\", cyclePatternDiscState),\n patternSand,\n cyclePatternSand: withType(\"pattern\", cyclePatternSandState),\n baseColor,\n setBaseColor,\n };\n\n return (\n \n {renderBackground(background, {\n gradientAxis,\n gradientAngle,\n gradientShape,\n gradientPosition,\n radialShape,\n radialSize,\n canvasStyle,\n canvasRamp,\n canvasSteps,\n canvasAngle,\n canvasSpeed,\n canvasAnimated,\n canvasSeed,\n patternStyle,\n patternDensity,\n patternSpeed,\n patternDisc,\n patternSand,\n baseColor,\n })}\n {children}\n \n );\n}\n", + "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { CanvasBackground } from \"@/components/canvas-background\";\nimport { GradientBackground } from \"@/components/gradient-background\";\nimport { PATTERN_DENSITIES, PATTERN_STYLES, PatternBackground, type PatternDensity, type PatternStyle } from \"@/components/pattern-background\";\nimport { usePublishAppBackground } from \"@/hooks/use-glass-backdrop\";\nimport type { CanvasStyle } from \"@/lib/canvas-background-utils\";\nimport type { GradientGeometry, GradientShape, RampGradientAxis } from \"@/lib/oklch-utils\";\n\nexport type BackgroundType = \"gradient\" | \"canvas\" | \"pattern\" | \"none\";\n\nconst STORAGE_KEY = \"sistine-background\";\n\n/** Canvas styles (lava renamed from \"blobs\", circle from \"circles\"). */\nexport const CANVAS_STYLES: CanvasStyle[] = [\n \"gradient\",\n \"lava\",\n \"circle\",\n];\n\n/** Ramp axes shared by the Gradient background and the Canvas (lightness = the \"linear\" ramp). */\nexport const RAMP_AXES: RampGradientAxis[] = [\n \"tonal\",\n \"hue\",\n \"lightness\",\n \"chroma\",\n];\n\n/** Gradient painting shapes the Gradient background cycles through. */\nexport const GRADIENT_SHAPES: GradientShape[] = [\n \"linear\",\n \"radial\",\n \"conic\",\n];\n\n/** Center positions (radial + conic) the switcher cycles through. */\nexport const GRADIENT_POSITIONS: {\n value: string;\n label: string;\n}[] = [\n {\n value: \"50% 50%\",\n label: \"center\",\n },\n {\n value: \"0% 0%\",\n label: \"top-left\",\n },\n {\n value: \"100% 0%\",\n label: \"top-right\",\n },\n {\n value: \"100% 100%\",\n label: \"bottom-right\",\n },\n {\n value: \"0% 100%\",\n label: \"bottom-left\",\n },\n];\n\n/** The position values the cycler steps through (labels live in GRADIENT_POSITIONS). */\nconst GRADIENT_POSITION_VALUES = GRADIENT_POSITIONS.map((p) => p.value);\n\n/** Radial shape + size options. */\nexport const RADIAL_SHAPES: NonNullable[] = [\n \"circle\",\n \"ellipse\",\n];\nexport const RADIAL_SIZES: NonNullable[] = [\n \"farthest-corner\",\n \"farthest-side\",\n \"closest-corner\",\n \"closest-side\",\n];\n\n/** Angle presets — 45° steps around the circle, shared by the gradient + canvas angle cyclers. */\nconst ANGLES = [\n 0,\n 45,\n 90,\n 135,\n 180,\n 225,\n 270,\n 315,\n];\n\n/** Steps-per-side presets the canvas/ramps cycle through (4–12). */\nexport const CANVAS_STEPS = [\n 4,\n 6,\n 8,\n 10,\n 12,\n] as const;\n\n/** Animation pace presets. */\nexport const CANVAS_SPEEDS = [\n 0.5,\n 1,\n 2,\n] as const;\n\n/** Animated-pattern pace: loop/scroll duration in SECONDS (2s fastest … 16s slowest); 0 = static.\n * Shared by every animated pattern (synthwave, moonrise, clouds, dune, aurora, forest, chase) via the --pat-dur CSS var. */\nexport const PATTERN_SPEEDS = [\n 2,\n 4,\n 8,\n 12,\n 16,\n 0,\n] as const;\n\n/** Sun/moon horizontal placement for the horizon scenes (synthwave / moonrise / clouds / dune / aurora / forest) — off-center\n * clears the page's centered content. */\nexport const PATTERN_DISCS = [\n \"right\",\n \"center\",\n \"left\",\n] as const;\n\n/** Blowing-sand intensity for the dune scene — \"still\" is a calm desert (no airborne sand at all),\n * distinct from the speed control's \"static\", which only pauses the motion that is already there. */\nexport const PATTERN_SANDS = [\n \"breeze\",\n \"storm\",\n \"still\",\n] as const;\n\nfunction persistBackground(next: BackgroundType) {\n try {\n localStorage.setItem(STORAGE_KEY, next);\n } catch {\n // ignore storage failures (private mode, etc.)\n }\n}\n\nfunction pickRandom(items: readonly T[]): T {\n return items[Math.floor(Math.random() * items.length)];\n}\n\nfunction cycle(items: readonly T[], current: T): T {\n const i = items.indexOf(current);\n return items[(i + 1) % items.length];\n}\n\n/**\n * State that steps through a fixed preset list, wrapping at the end. Returns the value, a stable\n * advance-to-the-next-preset callback, and the raw setter.\n */\nfunction useCycle(\n items: readonly T[],\n initial: T,\n): [\n T,\n () => void,\n React.Dispatch>,\n] {\n const [value, setValue] = React.useState(initial);\n const next = React.useCallback(\n () => setValue((v) => cycle(items, v)),\n [\n items,\n ],\n );\n return [\n value,\n next,\n setValue,\n ];\n}\n\ninterface BackgroundContextValue {\n background: BackgroundType;\n setBackground: (background: BackgroundType) => void;\n /** The ramp axis driving the gradient background. */\n gradientAxis: RampGradientAxis;\n /** Switch to the gradient background and set its ramp axis. */\n setGradientAxis: (axis: RampGradientAxis) => void;\n /** Gradient angle in degrees (90 = left → right). */\n gradientAngle: number;\n /** Rotate the gradient by 45°. */\n cycleGradientAngle: () => void;\n /** The gradient painting shape (linear | radial | conic). */\n gradientShape: GradientShape;\n /** Advance to the next gradient shape. */\n cycleGradientShape: () => void;\n /** Center position for radial + conic gradients (CSS `at `). */\n gradientPosition: string;\n /** Advance to the next center-position preset. */\n cycleGradientPosition: () => void;\n /** Radial shape (circle | ellipse). */\n radialShape: NonNullable;\n /** Toggle the radial shape. */\n cycleRadialShape: () => void;\n /** Radial size keyword. */\n radialSize: NonNullable;\n /** Advance to the next radial size. */\n cycleRadialSize: () => void;\n // ── Canvas ──\n /** The canvas style (gradient | lava | circle). */\n canvasStyle: CanvasStyle;\n /** Switch to the canvas background and set its style. */\n setCanvasStyle: (style: CanvasStyle) => void;\n /** The ramp axis driving the canvas colors. */\n canvasRamp: RampGradientAxis;\n /** Switch to the canvas background and set its ramp axis. */\n setCanvasRamp: (axis: RampGradientAxis) => void;\n /** Steps per side (4–12). */\n canvasSteps: number;\n /** Advance to the next steps-per-side preset. */\n cycleCanvasSteps: () => void;\n /** Canvas gradient angle in degrees. */\n canvasAngle: number;\n /** Rotate the canvas gradient by 45°. */\n cycleCanvasAngle: () => void;\n /** Canvas animation pace. */\n canvasSpeed: number;\n /** Advance to the next animation-pace preset. */\n cycleCanvasSpeed: () => void;\n /** Whether the canvas is currently animating. */\n canvasAnimated: boolean;\n /** Toggle canvas animation on/off. */\n toggleCanvasAnimated: () => void;\n /** Switch to the canvas background and reshuffle it (new style + ramp + layout seed). */\n shuffleCanvas: () => void;\n // ── Pattern (pure-CSS wallpapers) ──\n /** The active CSS pattern style. */\n patternStyle: PatternStyle;\n /** Switch to the pattern background and set its style. */\n setPatternStyle: (style: PatternStyle) => void;\n /** Advance to the next pattern style. */\n cyclePatternStyle: () => void;\n /** Star-field density (sparse | medium | dense). */\n patternDensity: PatternDensity;\n /** Advance to the next star-field density. */\n cyclePatternDensity: () => void;\n /** Horizon-grid scroll pace (synthwave / moonrise); 0 is static. */\n patternSpeed: number;\n /** Advance to the next grid scroll pace (… → static). */\n cyclePatternSpeed: () => void;\n /** Sun/moon placement for the horizon scenes (right | center | left). */\n patternDisc: (typeof PATTERN_DISCS)[number];\n /** Advance to the next sun/moon placement. */\n cyclePatternDisc: () => void;\n /** Blowing-sand intensity for the dune scene (breeze | storm | still). */\n patternSand: (typeof PATTERN_SANDS)[number];\n /** Advance to the next blowing-sand level. */\n cyclePatternSand: () => void;\n // ── Base color (the \"none\" backdrop) ──\n /** Base color for the \"none\" backdrop; null = follow the theme (tint-tinted, light/dark aware). */\n baseColor: string | null;\n /** Override the \"none\" backdrop color; null clears back to the themed default. */\n setBaseColor: (color: string | null) => void;\n}\n\nconst BackgroundContext = React.createContext(null);\n\nexport function useBackground() {\n const context = React.useContext(BackgroundContext);\n if (!context) {\n throw new Error(\"useBackground must be used within a BackgroundProvider\");\n }\n return context;\n}\n\ninterface RenderArgs {\n gradientAxis: RampGradientAxis;\n gradientAngle: number;\n gradientShape: GradientShape;\n gradientPosition: string;\n radialShape: NonNullable;\n radialSize: NonNullable;\n canvasStyle: CanvasStyle;\n canvasRamp: RampGradientAxis;\n canvasSteps: number;\n canvasAngle: number;\n canvasSpeed: number;\n canvasAnimated: boolean;\n canvasSeed: string;\n patternStyle: PatternStyle;\n patternDensity: PatternDensity;\n patternSpeed: number;\n patternDisc: (typeof PATTERN_DISCS)[number];\n patternSand: (typeof PATTERN_SANDS)[number];\n baseColor: string | null;\n}\n\n/** The solid \"none\" backdrop. One pair so the painted floor and the prebaked slice cannot drift. */\nconst NONE_LIGHT = \"oklch(0.98 0.006 var(--glass-tint-h))\";\nconst NONE_DARK = \"oklch(0.15 0.014 var(--glass-tint-h))\";\n\nfunction PublishNoneBackground({ baseColor }: { baseColor: string | null }) {\n const [dark, setDark] = React.useState(false);\n React.useEffect(() => {\n const root = document.documentElement;\n const read = () => setDark(root.classList.contains(\"dark\"));\n read();\n const mo = new MutationObserver(read);\n mo.observe(root, {\n attributes: true,\n attributeFilter: [\n \"class\",\n ],\n });\n return () => mo.disconnect();\n }, []);\n // A flat color is already its own blur. filter: none keeps the slice from paying for a no-op.\n usePublishAppBackground(baseColor ?? (dark ? NONE_DARK : NONE_LIGHT), \"none\");\n return null;\n}\n\nfunction renderBackground(background: BackgroundType, args: RenderArgs) {\n switch (background) {\n case \"canvas\":\n // key forces a clean redraw when the style/ramp/steps/seed/animation change\n return (\n \n );\n case \"pattern\":\n return (\n \n );\n case \"none\":\n // Forgo the decorative backdrop — a clean solid that honors the theme (tint hue, light/dark) by\n // default, overridable by the user's chosen base color.\n return (\n <>\n \n \n \n \n );\n default:\n return (\n \n );\n }\n}\n\n/**\n * Holds the site-wide background choice (persisted to localStorage) and renders it\n * behind the app. Pair with to preview + tune each style.\n */\nexport function BackgroundProvider({ children }: { children: React.ReactNode }) {\n // SSR + first client render use \"gradient\" so hydration matches; localStorage is read after mount.\n const [background, setBackgroundState] = React.useState(\"pattern\");\n const [gradientAxis, setGradientAxisState] = React.useState(\"tonal\");\n const [gradientAngle, cycleGradientAngle] = useCycle(ANGLES, 90);\n const [gradientShape, cycleGradientShape] = useCycle(GRADIENT_SHAPES, \"linear\");\n const [gradientPosition, cycleGradientPosition] = useCycle(GRADIENT_POSITION_VALUES, \"50% 50%\");\n const [radialShape, cycleRadialShape] = useCycle(RADIAL_SHAPES, \"circle\");\n const [radialSize, cycleRadialSize] = useCycle(RADIAL_SIZES, \"farthest-corner\");\n const [canvasStyle, setCanvasStyleState] = React.useState(\"gradient\");\n const [canvasRamp, setCanvasRampState] = React.useState(\"tonal\");\n const [canvasSteps, cycleCanvasSteps] = useCycle(CANVAS_STEPS, 6);\n const [canvasAngle, cycleCanvasAngle] = useCycle(ANGLES, 90);\n const [canvasSpeed, cycleCanvasSpeed] = useCycle(CANVAS_SPEEDS, 1);\n const [canvasAnimated, setCanvasAnimated] = React.useState(false);\n const [canvasSeed, setCanvasSeed] = React.useState(\"sistine\");\n const [patternStyle, cyclePatternStyleState, setPatternStyleState] = useCycle(PATTERN_STYLES, \"moonrise\");\n const [patternDensity, cyclePatternDensityState] = useCycle(PATTERN_DENSITIES, \"medium\");\n /* Static by DEFAULT (0 = no animation, the last entry in PATTERN_SPEEDS). The pattern layer is a\n backdrop for reading glass against, and a moving one competes with the content for attention on\n first load — motion should be something you opt into, not something you have to switch off. The\n speed control still cycles 2/4/8/12/16/0, so the animation is one click away. */\n const [patternSpeed, cyclePatternSpeedState] = useCycle(PATTERN_SPEEDS, 0);\n const [baseColor, setBaseColor] = React.useState(null);\n const [patternDisc, cyclePatternDiscState] = useCycle(PATTERN_DISCS, \"right\");\n const [patternSand, cyclePatternSandState] = useCycle(PATTERN_SANDS, \"breeze\");\n\n React.useEffect(() => {\n const stored = localStorage.getItem(STORAGE_KEY);\n if (stored === \"gradient\" || stored === \"canvas\" || stored === \"pattern\" || stored === \"none\") {\n setBackgroundState(stored);\n }\n }, []);\n\n /** The coupling every per-style setter/cycler shares: switch to that background type, apply, persist. */\n const withType =\n (type: BackgroundType, fn: (...args: A) => void) =>\n (...args: A) => {\n setBackgroundState(type);\n fn(...args);\n persistBackground(type);\n };\n\n const value: BackgroundContextValue = {\n background,\n setBackground: (next) => {\n setBackgroundState(next);\n persistBackground(next);\n },\n gradientAxis,\n setGradientAxis: withType(\"gradient\", setGradientAxisState),\n gradientAngle,\n cycleGradientAngle,\n gradientShape,\n cycleGradientShape,\n gradientPosition,\n cycleGradientPosition,\n radialShape,\n cycleRadialShape,\n radialSize,\n cycleRadialSize,\n canvasStyle,\n setCanvasStyle: withType(\"canvas\", setCanvasStyleState),\n canvasRamp,\n setCanvasRamp: withType(\"canvas\", setCanvasRampState),\n canvasSteps,\n cycleCanvasSteps,\n canvasAngle,\n cycleCanvasAngle,\n canvasSpeed,\n cycleCanvasSpeed,\n canvasAnimated,\n toggleCanvasAnimated: () => setCanvasAnimated((on) => !on),\n shuffleCanvas: withType(\"canvas\", () => {\n setCanvasStyleState(pickRandom(CANVAS_STYLES));\n setCanvasRampState(pickRandom(RAMP_AXES));\n setCanvasSeed(Math.random().toString(36).slice(2));\n }),\n patternStyle,\n setPatternStyle: withType(\"pattern\", setPatternStyleState),\n cyclePatternStyle: withType(\"pattern\", cyclePatternStyleState),\n patternDensity,\n cyclePatternDensity: withType(\"pattern\", cyclePatternDensityState),\n patternSpeed,\n cyclePatternSpeed: withType(\"pattern\", cyclePatternSpeedState),\n patternDisc,\n cyclePatternDisc: withType(\"pattern\", cyclePatternDiscState),\n patternSand,\n cyclePatternSand: withType(\"pattern\", cyclePatternSandState),\n baseColor,\n setBaseColor,\n };\n\n return (\n \n {renderBackground(background, {\n gradientAxis,\n gradientAngle,\n gradientShape,\n gradientPosition,\n radialShape,\n radialSize,\n canvasStyle,\n canvasRamp,\n canvasSteps,\n canvasAngle,\n canvasSpeed,\n canvasAnimated,\n canvasSeed,\n patternStyle,\n patternDensity,\n patternSpeed,\n patternDisc,\n patternSand,\n baseColor,\n })}\n {children}\n \n );\n}\n", "type": "registry:component", "target": "components/background-provider.tsx" }, diff --git a/public/r/badge.json b/public/r/badge.json index be0b132a..b17552b2 100644 --- a/public/r/badge.json +++ b/public/r/badge.json @@ -21,7 +21,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/breadcrumb.json b/public/r/breadcrumb.json index 97278f9b..961716bb 100644 --- a/public/r/breadcrumb.json +++ b/public/r/breadcrumb.json @@ -22,7 +22,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/button-group.json b/public/r/button-group.json index 03e45d57..f3749b4d 100644 --- a/public/r/button-group.json +++ b/public/r/button-group.json @@ -22,7 +22,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/button.json b/public/r/button.json index 76e0e43d..34a51abb 100644 --- a/public/r/button.json +++ b/public/r/button.json @@ -21,7 +21,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/calendar.json b/public/r/calendar.json index 43f7c756..09e4b281 100644 --- a/public/r/calendar.json +++ b/public/r/calendar.json @@ -23,7 +23,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/card.json b/public/r/card.json index 8c11340c..ad737f4c 100644 --- a/public/r/card.json +++ b/public/r/card.json @@ -20,7 +20,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/carousel.json b/public/r/carousel.json index f153ab79..9e562f4b 100644 --- a/public/r/carousel.json +++ b/public/r/carousel.json @@ -22,7 +22,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/chart.json b/public/r/chart.json index 743c9d40..ef6d35be 100644 --- a/public/r/chart.json +++ b/public/r/chart.json @@ -21,7 +21,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/checkbox.json b/public/r/checkbox.json index a0cf6223..8660709b 100644 --- a/public/r/checkbox.json +++ b/public/r/checkbox.json @@ -22,7 +22,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/collapsible.json b/public/r/collapsible.json index b628cbda..0f98c89d 100644 --- a/public/r/collapsible.json +++ b/public/r/collapsible.json @@ -21,7 +21,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/combobox.json b/public/r/combobox.json index 912c1b23..aca826c5 100644 --- a/public/r/combobox.json +++ b/public/r/combobox.json @@ -24,7 +24,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/command.json b/public/r/command.json index fca13ebc..c3e3e1d6 100644 --- a/public/r/command.json +++ b/public/r/command.json @@ -24,7 +24,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/context-menu.json b/public/r/context-menu.json index ae83cee3..12631e19 100644 --- a/public/r/context-menu.json +++ b/public/r/context-menu.json @@ -22,7 +22,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/cropper.json b/public/r/cropper.json index 67cb59f2..ce08d652 100644 --- a/public/r/cropper.json +++ b/public/r/cropper.json @@ -24,7 +24,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/date-picker-input.json b/public/r/date-picker-input.json index 7a277e28..5db815de 100644 --- a/public/r/date-picker-input.json +++ b/public/r/date-picker-input.json @@ -26,7 +26,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/dialog.json b/public/r/dialog.json index 1ad3ad4b..f3329c8d 100644 --- a/public/r/dialog.json +++ b/public/r/dialog.json @@ -23,7 +23,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/drawer.json b/public/r/drawer.json index 59ae5461..f027d7ff 100644 --- a/public/r/drawer.json +++ b/public/r/drawer.json @@ -21,7 +21,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/dropdown-menu.json b/public/r/dropdown-menu.json index 2a3b3825..9e18f100 100644 --- a/public/r/dropdown-menu.json +++ b/public/r/dropdown-menu.json @@ -22,7 +22,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/empty-state.json b/public/r/empty-state.json index dd3f8993..c7db9438 100644 --- a/public/r/empty-state.json +++ b/public/r/empty-state.json @@ -20,7 +20,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/empty.json b/public/r/empty.json index 2bfd2ccd..a67ff0c9 100644 --- a/public/r/empty.json +++ b/public/r/empty.json @@ -20,7 +20,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/field.json b/public/r/field.json index eb90b0a3..b7412224 100644 --- a/public/r/field.json +++ b/public/r/field.json @@ -22,7 +22,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/gradient-background.json b/public/r/gradient-background.json index 07bd254b..22c78e38 100644 --- a/public/r/gradient-background.json +++ b/public/r/gradient-background.json @@ -7,12 +7,13 @@ "registryDependencies": [ "@sistine/canvas-background-utils", "@sistine/oklch-utils", - "@sistine/theme" + "@sistine/theme", + "@sistine/use-glass-backdrop" ], "files": [ { "path": "components/gradient-background.tsx", - "content": "\"use client\";\n\nimport { backdropPalette, useBackdropTint } from \"@/lib/canvas-background-utils\";\nimport {\n bandedFrescoStops,\n type GradientGeometry,\n type GradientShape,\n type RampGradientAxis,\n rampGradient,\n readableLightnessBand,\n wrapGradient,\n} from \"@/lib/oklch-utils\";\n\n/**\n * A ramp-driven gradient wallpaper. The gradient is one of our oklch ramps (hue / lightness / tonal\n * / chroma) centered on the live glass-tint color (so it recolors with the theme) with the center as a\n * slightly wider plateau, painted as a `linear`, `radial`, or `conic` gradient. Pure CSS — crisp at any DPI.\n */\nexport function GradientBackground({\n axis = \"tonal\",\n hue,\n angle = 90,\n shape = \"linear\",\n position,\n radialShape,\n radialSize,\n}: {\n axis?: RampGradientAxis;\n /** Hue override (deg). When set, drives the gradient color instead of the live --glass-tint-h. */\n hue?: number;\n angle?: number;\n shape?: GradientShape;\n} & GradientGeometry) {\n const tint = useBackdropTint();\n // Shared with CanvasBackground: theme-tracking base + fresco palette (the `hue` prop overrides both).\n const { base, frescoColors } = backdropPalette(tint, hue);\n /* The ramps span their FULL range by design — as a wallpaper that put pure black at one edge of the\n viewport and pure white at the other, so a single solved foreground could only be readable at one\n end. Fit the ramp into the band where text over it clears APCA 60 instead. Falls back to the whole\n range if the foreground has not resolved yet (first paint, before AutoForeground runs). */\n const band = tint.fg ? readableLightnessBand(tint.fg, base) : undefined;\n const gradient = frescoColors\n ? wrapGradient(shape, bandedFrescoStops(frescoColors, band, shape), {\n angle,\n position,\n radialShape,\n radialSize,\n })\n : rampGradient(axis, base, 5, {\n angle,\n band,\n gamut: tint.p3 ? \"p3\" : \"srgb\",\n shape,\n position,\n radialShape,\n radialSize,\n });\n\n return (\n \n {/* Subtle organic highlight/shadow for depth — plain alpha layers, NO mix-blend-mode: a\n blend-mode element on the page can disable backdrop-filter RENDERING in Chromium, killing\n every glass blur above it (computed styles stay correct, paint silently fails). */}\n \n

\n );\n}\n", + "content": "\"use client\";\n\nimport { APP_BG_FROST_FILTER, usePublishAppBackground } from \"@/hooks/use-glass-backdrop\";\nimport { backdropPalette, useBackdropTint } from \"@/lib/canvas-background-utils\";\nimport {\n bandedFrescoStops,\n type GradientGeometry,\n type GradientShape,\n type RampGradientAxis,\n rampGradient,\n readableLightnessBand,\n wrapGradient,\n} from \"@/lib/oklch-utils\";\n\n/**\n * A ramp-driven gradient wallpaper. The gradient is one of our oklch ramps (hue / lightness / tonal\n * / chroma) centered on the live glass-tint color (so it recolors with the theme) with the center as a\n * slightly wider plateau, painted as a `linear`, `radial`, or `conic` gradient. Pure CSS — crisp at any DPI.\n *\n * The highlight veil is a background LAYER on this element, not a child: one background string is the\n * whole backdrop, and prebaked glass (`usePublishAppBackground`) samples that same string. A child\n * overlay would frost the gradient and leave the highlights out of every bubble's slice.\n */\nconst BACKDROP_HIGHLIGHTS =\n \"radial-gradient(circle at 20% 25%, oklch(var(--shadow-highlight) / 0.1) 0%, transparent 45%), radial-gradient(circle at 80% 75%, oklch(var(--shadow-ink) / 0.1) 0%, transparent 45%)\";\n\nexport function GradientBackground({\n axis = \"tonal\",\n hue,\n angle = 90,\n shape = \"linear\",\n position,\n radialShape,\n radialSize,\n}: {\n axis?: RampGradientAxis;\n /** Hue override (deg). When set, drives the gradient color instead of the live --glass-tint-h. */\n hue?: number;\n angle?: number;\n shape?: GradientShape;\n} & GradientGeometry) {\n const tint = useBackdropTint();\n // Shared with CanvasBackground: theme-tracking base + fresco palette (the `hue` prop overrides both).\n const { base, frescoColors } = backdropPalette(tint, hue);\n /* The ramps span their FULL range by design — as a wallpaper that put pure black at one edge of the\n viewport and pure white at the other, so a single solved foreground could only be readable at one\n end. Fit the ramp into the band where text over it clears APCA 60 instead. Falls back to the whole\n range if the foreground has not resolved yet (first paint, before AutoForeground runs). */\n const band = tint.fg ? readableLightnessBand(tint.fg, base) : undefined;\n const gradient = frescoColors\n ? wrapGradient(shape, bandedFrescoStops(frescoColors, band, shape), {\n angle,\n position,\n radialShape,\n radialSize,\n })\n : rampGradient(axis, base, 5, {\n angle,\n band,\n gamut: tint.p3 ? \"p3\" : \"srgb\",\n shape,\n position,\n radialShape,\n radialSize,\n });\n\n /* First layer paints on top. Same highlights the old child overlay drew, still plain alpha — no\n mix-blend-mode, which can disable backdrop-filter rendering in Chromium. */\n const stack = `${BACKDROP_HIGHLIGHTS}, ${gradient}`;\n usePublishAppBackground(stack, APP_BG_FROST_FILTER);\n\n return (\n \n );\n}\n", "type": "registry:component", "target": "components/gradient-background.tsx" } diff --git a/public/r/hover-card.json b/public/r/hover-card.json index fd3fc42b..a59807ce 100644 --- a/public/r/hover-card.json +++ b/public/r/hover-card.json @@ -21,7 +21,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/input-group.json b/public/r/input-group.json index 21445c5f..cd35e56b 100644 --- a/public/r/input-group.json +++ b/public/r/input-group.json @@ -23,7 +23,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/input-otp.json b/public/r/input-otp.json index 1a00ce8c..a2607547 100644 --- a/public/r/input-otp.json +++ b/public/r/input-otp.json @@ -22,7 +22,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/input.json b/public/r/input.json index 2961556c..2798768c 100644 --- a/public/r/input.json +++ b/public/r/input.json @@ -20,7 +20,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/item.json b/public/r/item.json index 558fc707..cc9657df 100644 --- a/public/r/item.json +++ b/public/r/item.json @@ -22,7 +22,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/kbd.json b/public/r/kbd.json index 9f8e6df2..94d7aa92 100644 --- a/public/r/kbd.json +++ b/public/r/kbd.json @@ -20,7 +20,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/label.json b/public/r/label.json index d67f1f34..7dc2ec3f 100644 --- a/public/r/label.json +++ b/public/r/label.json @@ -21,7 +21,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/material.json b/public/r/material.json index 8731dbf9..c1399028 100644 --- a/public/r/material.json +++ b/public/r/material.json @@ -8,7 +8,7 @@ "files": [ { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" } diff --git a/public/r/menu-bar.json b/public/r/menu-bar.json index d8128fd9..0ae31b4e 100644 --- a/public/r/menu-bar.json +++ b/public/r/menu-bar.json @@ -21,7 +21,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/menubar.json b/public/r/menubar.json index e2264282..beb94a43 100644 --- a/public/r/menubar.json +++ b/public/r/menubar.json @@ -22,7 +22,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/mode-toggle.json b/public/r/mode-toggle.json index 047c5e2f..b002e5c2 100644 --- a/public/r/mode-toggle.json +++ b/public/r/mode-toggle.json @@ -23,7 +23,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/navigation-menu.json b/public/r/navigation-menu.json index f4176733..dac21c26 100644 --- a/public/r/navigation-menu.json +++ b/public/r/navigation-menu.json @@ -22,7 +22,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/pagination.json b/public/r/pagination.json index c4a6e02b..f646834e 100644 --- a/public/r/pagination.json +++ b/public/r/pagination.json @@ -22,7 +22,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/popover.json b/public/r/popover.json index 7e46e5de..319fe056 100644 --- a/public/r/popover.json +++ b/public/r/popover.json @@ -21,7 +21,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/progress.json b/public/r/progress.json index 9097e62d..661ecd85 100644 --- a/public/r/progress.json +++ b/public/r/progress.json @@ -21,7 +21,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/radio-group.json b/public/r/radio-group.json index 80c42280..a77ab2e8 100644 --- a/public/r/radio-group.json +++ b/public/r/radio-group.json @@ -22,7 +22,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/registry.json b/public/r/registry.json index d6ddb832..a62507e5 100644 --- a/public/r/registry.json +++ b/public/r/registry.json @@ -2365,6 +2365,21 @@ ], "dependencies": [] }, + { + "name": "use-glass-backdrop", + "type": "registry:hook", + "title": "use-glass-backdrop", + "description": "Keeps prebaked glass slices aligned to a fixed background. Writes --gx and --gy on each .glass-prebaked surface from one animation frame, and publishes --app-bg-blurred for a CSS backdrop.", + "registryDependencies": [], + "files": [ + { + "path": "hooks/use-glass-backdrop.ts", + "type": "registry:hook", + "target": "hooks/use-glass-backdrop.ts" + } + ], + "dependencies": [] + }, { "name": "oklch-utils", "type": "registry:lib", @@ -2522,7 +2537,8 @@ "registryDependencies": [ "@sistine/canvas-background-utils", "@sistine/oklch-utils", - "@sistine/theme" + "@sistine/theme", + "@sistine/use-glass-backdrop" ], "files": [ { @@ -2541,7 +2557,8 @@ "registryDependencies": [ "@sistine/canvas-background-utils", "@sistine/oklch-utils", - "@sistine/theme" + "@sistine/theme", + "@sistine/use-glass-backdrop" ], "files": [ { diff --git a/public/r/scroll-area.json b/public/r/scroll-area.json index 34f9c400..b5999976 100644 --- a/public/r/scroll-area.json +++ b/public/r/scroll-area.json @@ -21,7 +21,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/select.json b/public/r/select.json index 0e1e893a..8cb4ab09 100644 --- a/public/r/select.json +++ b/public/r/select.json @@ -22,7 +22,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/separator.json b/public/r/separator.json index 39d30f4d..36a48a4d 100644 --- a/public/r/separator.json +++ b/public/r/separator.json @@ -21,7 +21,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/sheet.json b/public/r/sheet.json index 28f23f6d..345700c5 100644 --- a/public/r/sheet.json +++ b/public/r/sheet.json @@ -22,7 +22,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/sidebar.json b/public/r/sidebar.json index 266706c7..c76b98d8 100644 --- a/public/r/sidebar.json +++ b/public/r/sidebar.json @@ -29,7 +29,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/skeleton.json b/public/r/skeleton.json index fb66a425..c325f13d 100644 --- a/public/r/skeleton.json +++ b/public/r/skeleton.json @@ -20,7 +20,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/slider.json b/public/r/slider.json index 453cc08a..075caf95 100644 --- a/public/r/slider.json +++ b/public/r/slider.json @@ -21,7 +21,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/sonner.json b/public/r/sonner.json index b2a5239c..ac32ca04 100644 --- a/public/r/sonner.json +++ b/public/r/sonner.json @@ -23,7 +23,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/spinner.json b/public/r/spinner.json index e038714e..fa8da02e 100644 --- a/public/r/spinner.json +++ b/public/r/spinner.json @@ -20,7 +20,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/switch.json b/public/r/switch.json index bbe0ba71..fc08ceaa 100644 --- a/public/r/switch.json +++ b/public/r/switch.json @@ -21,7 +21,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/table.json b/public/r/table.json index d7c6e628..36a30946 100644 --- a/public/r/table.json +++ b/public/r/table.json @@ -20,7 +20,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/tabs.json b/public/r/tabs.json index 6e46caab..43cd98a5 100644 --- a/public/r/tabs.json +++ b/public/r/tabs.json @@ -21,7 +21,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/textarea.json b/public/r/textarea.json index 1607a191..81f82205 100644 --- a/public/r/textarea.json +++ b/public/r/textarea.json @@ -20,7 +20,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/theme.json b/public/r/theme.json index f8439c6d..68204aee 100644 --- a/public/r/theme.json +++ b/public/r/theme.json @@ -10,7 +10,7 @@ "files": [ { "path": "registry/theme/globals.css", - "content": "/*\n Sistine theme — aggregator. The theme is authored as focused partials under app/theme/; Tailwind v4\n bundles these relative imports itself (no postcss-import needed). ORDER MATTERS:\n - package imports come first (CSS requires @import before other rules),\n - engine.css must precede presets.css / frescoes.css (a preset's --glass-tint-h overrides the\n engine default by SOURCE ORDER at equal specificity),\n - inside tokens.css, .dark comes after :root (same-element ties resolve by source order).\n Consumers install a FLATTENED single-file build of this graph (registry/theme/globals.css, generated\n by scripts/build-theme.mjs) — run `bun run registry:check` after editing any partial and commit\n registry/theme + public/r. Site-only CSS (the Geist font mapping) lives in app/site.css, which imports\n this file, so nothing the documentation site defines leaks into the shipped theme.\n*/\n@import \"tailwindcss\";\n@import \"tw-animate-css\";\n/* ── Registrations — @custom-variant + @property pins for JS-only tokens ──────\n Loaded first so the dark variant and typed properties exist before anything composes them. */\n\n/* Class-based dark mode, v4-standard form: matches the `.dark` element ITSELF and its descendants, so\n consumers can toggle `.dark` on any wrapper (not just / next-themes), at zero specificity\n (`:where`) so `dark:` overrides by cascade order like every other variant. */\n@custom-variant dark (&:where(.dark, .dark *));\n\n/* Composable, NON-inheriting glow layer: the `glow` effect/prop sets it, and every glass box-shadow\n folds in `var(--glow-layer, …)` so the glow rides INSIDE each material's and page style's shadow\n (they set --srf-shadow; the glow is appended after it) instead of being replaced by it. inherits:false\n keeps a glowing surface from lighting up its glass children. */\n@property --glow-layer {\n syntax: \"*\";\n inherits: false;\n}\n\n/* The veil floor, NON-inheriting for the same reason as the glow: `glass-veil` sets it on the element\n and `glass` paints it as that element's background-color. Inherited, a plain glass surface nested in\n a veiled menu, popover, tooltip, select or card painted its PARENT's veil as its own floor (under the\n Opaque page style: flat white in light, black in dark). A child without a veil of its own now falls\n back to its material's --srf-bg-color, as it would anywhere else. */\n@property --veil-floor {\n syntax: \"*\";\n inherits: false;\n}\n/* Its twin for the solidify layer (see @utility glass-veil): a veiled parent must not restore the layer\n on a child that dropped it. */\n@property --veil-solidify {\n syntax: \"*\";\n inherits: false;\n}\n/* The ink border colour (glass-border-ink): an inked panel must not ink the borders of the glass inside it. */\n@property --brd-ink {\n syntax: \"*\";\n inherits: false;\n}\n\n/* Gradient-text tokens are consumed ONLY from JS inline styles (components/gradient-text.tsx) — no CSS\n rule references them, so Lightning CSS's optimizer would silently drop them (the same failure mode as\n the harmonic tokens below). Registered here to pin them. DO NOT REMOVE. */\n@property --gradient-text {\n syntax: \"*\";\n inherits: true;\n}\n@property --gradient-text-contrast {\n syntax: \"*\";\n inherits: true;\n}\n/* ── Harmonic color registrations — compacted one-liners (biome's CSS formatter is excluded for this\n file; see biome.json). WHY these exist: the comment below. */\n\n/* ── Harmonic token registration ──────────────────────────────────────────────────────────────\n The harmonic tokens (--harmony-h, --hue-*, --color-*, --mono-*, defined in engine.css) are\n consumed ONLY from JS / inline styles (HarmonicSwatchesDemo, AutoForeground) — never referenced by\n a CSS rule. Tailwind v4's Lightning CSS optimizer constant-folds + dead-code-eliminates any custom\n property no CSS rule uses, which silently dropped this ENTIRE subgraph from the COMPILED stylesheet\n (so every `oklch(L C var(--hue-*))` / `var(--color-*)` resolved to nothing at runtime). Registering\n them via @property marks them as first-class typed API the optimizer must preserve. DO NOT REMOVE. */\n@property --harmony-h { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --color-chroma { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --mono-chroma { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-base { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-complement { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-analogous-1 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-analogous-2 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-split-1 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-split-2 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-triad-1 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-triad-2 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-tetrad-1 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-tetrad-2 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-tetrad-3 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-square-1 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-square-2 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-square-3 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --color-complement { syntax: \"\"; inherits: true; initial-value: transparent; }\n@property --color-analogous-1 { syntax: \"\"; inherits: true; initial-value: transparent; }\n@property --color-analogous-2 { syntax: \"\"; inherits: true; initial-value: transparent; }\n@property --color-split-1 { syntax: \"\"; inherits: true; initial-value: transparent; }\n@property --color-split-2 { syntax: \"\"; inherits: true; initial-value: transparent; }\n@property --color-triad-1 { syntax: \"\"; inherits: true; initial-value: transparent; }\n@property --color-triad-2 { syntax: \"\"; inherits: true; initial-value: transparent; }\n@property --mono-1 { syntax: \"\"; inherits: true; initial-value: transparent; }\n@property --mono-2 { syntax: \"\"; inherits: true; initial-value: transparent; }\n@property --mono-3 { syntax: \"\"; inherits: true; initial-value: transparent; }\n/* ── Design tokens — @theme mapping, base shadcn colors, mode knobs + shadow twins, base layer ─────\n :root carries light values + the LEAF mode knobs the engine composes; .dark carries the dark twins.\n .dark must come after :root (same-element ties resolve by source order on html.dark). */\n\n@theme inline {\n /* Easing. Tailwind's built-ins are deliberately gentle; these are the stronger variants UI motion\n wants, exposed as `ease-out` / `ease-in-out` / `ease-drawer` utilities.\n out — anything ENTERING or EXITING. Starts fast, so the first frame (the one the user is\n actually watching) already moves.\n in-out — things MOVING on screen that neither enter nor leave: carousels, sliders.\n drawer — a slower settle for panels that slide in from an edge, so a large surface arrives with\n weight instead of snapping.\n There is deliberately no `ease-in` token: it delays the first frame, which is the one moment the\n interface most needs to feel responsive. */\n --ease-out: cubic-bezier(0.23, 1, 0.32, 1);\n --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);\n --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);\n /* Anything with a bare `transition` and no `ease-*` inherits this. Tailwind ships\n cubic-bezier(0.4, 0, 0.2, 1) — symmetric, so it eases IN as well, and the first frame barely\n moves. Same curve as --ease-out, written literally because @theme inline doesn't emit the vars\n it inlines. */\n --default-transition-timing-function: cubic-bezier(0.23, 1, 0.32, 1);\n --color-background: var(--background);\n --color-foreground: var(--foreground);\n --color-foreground-soft: var(--foreground-soft);\n --color-foreground-strong: var(--foreground-strong);\n --color-foreground-ui: var(--foreground-ui);\n --color-sidebar-ring: var(--sidebar-ring);\n --color-sidebar-border: var(--sidebar-border);\n --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);\n --color-sidebar-accent: var(--sidebar-accent);\n --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);\n --color-sidebar-primary: var(--sidebar-primary);\n --color-sidebar-foreground: var(--sidebar-foreground);\n --color-sidebar: var(--sidebar);\n --color-chart-5: var(--chart-5);\n --color-chart-4: var(--chart-4);\n --color-chart-3: var(--chart-3);\n --color-chart-2: var(--chart-2);\n --color-chart-1: var(--chart-1);\n --color-ring: var(--ring);\n --color-input: var(--input);\n --color-border: var(--border);\n --color-destructive: var(--destructive);\n --color-accent-foreground: var(--accent-foreground);\n --color-accent: var(--accent);\n --color-muted-foreground: var(--muted-foreground);\n --color-muted: var(--muted);\n --color-secondary-foreground: var(--secondary-foreground);\n --color-secondary: var(--secondary);\n --color-primary-foreground: var(--primary-foreground);\n --color-primary: var(--primary);\n --color-popover-foreground: var(--popover-foreground);\n --color-popover: var(--popover);\n --color-card-foreground: var(--card-foreground);\n --color-card: var(--card);\n --radius-sm: calc(var(--radius) - 4px);\n --radius-md: calc(var(--radius) - 2px);\n --radius-lg: var(--radius);\n --radius-xl: calc(var(--radius) + 4px);\n}\n\n:root {\n --radius: 1.25rem;\n /* Shadow ink — the base black/white that every shadow, pressed-inset, and bevel highlight is built\n from. Mode-invariant (only the ALPHA changes per surface/mode), so apply alpha at the call site:\n oklch(var(--shadow-ink) / ). One knob to retint every shadow at once (e.g. a cooler, less-flat\n ink) instead of hunting down dozens of oklch(0% 0 0) / oklch(100% 0 0) literals. */\n --shadow-ink: 0% 0 0; /* drop + pressed-inset shadow color */\n --shadow-highlight: 100% 0 0; /* inner bevel / top highlight */\n /* Active/pressed shadow SCALE, built from the ink above and mode-aware (deeper in dark; overridden in\n .dark), so components reference these with NO dark: twin. Depth ladder — all inset \"push-in\" presses\n except --active-shadow, which LIFTS the selected tab/toggle. --press-shadow-strong shares the\n standard press's 0.2 primary and stacks a contact layer (was 0.15; raised so the two-layer presses\n build ON the single-layer one instead of undercutting it):\n --press-shadow-sm — subtle press (ghost, outline)\n --press-shadow — standard press (default, gradient, opaque)\n --press-shadow-strong — firm two-layer press (glass, destructive, secondary, frosted)\n --press-shadow-deep — deepest press (crystal)\n --active-shadow — selected-control LIFT (tabs, toggle) */\n --press-shadow-sm: inset 0 1px 2px oklch(var(--shadow-ink) / 0.1);\n --press-shadow: inset 0 2px 4px oklch(var(--shadow-ink) / 0.2);\n --press-shadow-strong: inset 0 2px 4px oklch(var(--shadow-ink) / 0.2), inset 0 1px 2px oklch(var(--shadow-ink) / 0.15);\n --press-shadow-deep: inset 0 2px 4px oklch(var(--shadow-ink) / 0.25), inset 0 1px 2px oklch(var(--shadow-ink) / 0.2);\n --active-shadow: 0 1px 3px oklch(var(--shadow-ink) / 0.1), inset 0 1px 1px oklch(var(--shadow-highlight) / 0.5);\n /* Selected-control FILL — the twin of --active-shadow, which had the lift but no surface, so tabs and\n toggle-group each hardcoded `bg-white/80 dark:bg-white/20`. The colour is composed in engine.css;\n these are its knobs.\n The two modes source their hue by DIFFERENT routes, and that asymmetry is the whole design.\n DARK is structurally theme-driven: the fill is chroma 0 at alpha 0.2, so 100% of the colour is the\n tinted surface showing through — the chip literally IS the theme surface, lifted. Nothing can make\n it drift off-theme.\n LIGHT cannot use that route. Show-through only carries hue if the surface has hue to give, and a\n light glass surface measures around chroma 0.018 (the sheet is itself capped by --glass-tint-c-hi\n at 0.025 near white). So at alpha 0.9 the fill supplies ~94% of the chip's colour and has to carry\n the tint ITSELF.\n Which makes the fill's own lightness the binding constraint, because the near-white ceiling\n collapses hue-dependently — 0.0323 at L 93, 0.061 at L 87, and only 0.0091 by L 98. At the old\n L 93 the cap was 0.031 and the chip delivered an RGB spread of 20: nominally theme-tinted, visibly\n neutral, which is why day did not read as theme-driven while night did. L 87 roughly doubles the\n available chroma and the delivered spread (37), at the cost of sitting 3.6 L BELOW the tab list\n rather than 1.8 above it — the lift is carried by --active-shadow, so the chip still reads\n selected, now as a tinted fill rather than a white one.\n Capped with min() like --border and the chakra body, so selenite (chroma 0) stays exactly neutral\n and no preset can push the fill out of gamut. */\n --active-l: 87%;\n --active-c-max: 0.055; /* ≤ 0.061, the L-87 ceiling floor (lapis h268) */\n --active-a: 0.9;\n /* ── Mode knobs ──────────────────────────────────────────────────────────\n Single-number dials the glass engine's mode-derived block composes (light values here, dark twins in\n .dark). LEAVES ONLY — none composes a tint var (check-theme invariant 1), so they may live on the bare\n blocks and inherit into tint scopes, where the engine re-resolves the derived tokens. */\n --glass-sheet-a: 0.11; /* sheer glass sheet base alpha (per-stop offsets ride on top) */\n --glass-sheet-a1: 0.04; /* the sheet's TOP-stop offset (dark tapers it to 0.03) */\n --glass-frost-boost: 0.1; /* frosted = the sheet lifted by this much */\n --glass-border-a: 0.16; /* glass edge alpha — a white rim needs more presence on light backdrops */\n /* Ink border alpha (borderColor=\"ink\" / glass-border-ink): how much of --foreground the line takes.\n Mode-invariant because --foreground already flips polarity per mode; 0.5 keeps the line clearly\n softer than text while clearing the APCA non-text floor on every preset (check-contrast [ink]); 0.55 since\n opaque cards carry the wash, whose lighter dark surface left goldstone's line at 29.9 Lc. */\n --glass-border-ink-a: 0.55;\n /* Crystal gloss defaults (the demo's Light/Tint sliders override inline). --glass-gloss-tint is\n mode-shared; --glass-gloss-l is NOT — see its twin below. AutoForeground's crystal tiers mirror\n --glass-gloss-l (97 light / 66 dark) as their snapshot-path fallback — keep all three in step. */\n --glass-diffuse: 12px; /* readability blur FLOOR the glass-diffuse axis raises surfaces to (opaque ignores it) */\n /* Gloss ink lightness (\"Light\" in the demo) — a MODE TWIN, because a gloss is defined relative to\n the surface under it and those surfaces are 68 L apart. The crystal floor computes to L 96.5 in\n light and L 28 in dark, so a single shared value cannot be a highlight in both: at 66 the top stop\n lifts dark by +15.2 L (a specular) and darkens light by −12.2 L (a smudge — the same \"grey\n speculars read as grime\" failure the shadow bevel comment warns about).\n 97 is the light twin: composited it lands +0.2 L over the surface, which is all the headroom that\n exists above L 96.5. The cost is a chroma ceiling, and it is strongly HUE-dependent rather than\n uniform — at L 97 it ranges from 0.0686 for peridot (h128) down to 0.0136 for lapis (h268).\n CORRECTION, measured in WebKit: an over-ceiling oklch() is NOT gamut-mapped down to that ceiling.\n It is CLIPPED per channel, which lands FURTHER from the request, not nearer:\n oklch(97% 0.0686 128) -> 232,255,205 (the ceiling — what the model predicts)\n oklch(97% 0.2975 128) -> 185,255,0 (what actually shipped: pure chartreuse)\n So the Tint multiplier did not \"do real work up to each hue's own ceiling\"; above the ceiling it\n drove the ink to a fully saturated primary. Every preset was affected — turquoise and aquamarine\n both clipped to 0,255,255, i.e. the two were indistinguishable — and moonstone reached 255,222,33,\n the egg-yolk cast that made light mode read yellow. --glass-gloss-c-max below is the fix, sized the\n way --glass-chakra-c-max is: the TIGHTEST ceiling across every preset hue, so no preset clips and\n all of them deliver 100% of what they ask for. */\n --glass-gloss-l: 97;\n /* Gloss chroma cap — the L-97 twin. 0.0136 is lapis (h268), the tightest of the 17 presets; 0.013\n sits just under it. Light gloss is therefore near-achromatic by necessity, not by choice: sRGB\n simply holds no more colour that close to white, and asking for more clips rather than tints. */\n --glass-gloss-c-max: 0.013;\n /* Gloss chroma multiplier over the tint (\"Tint\"). Calibrated for the DEFAULT tint (0.018 × 4.25 =\n 0.077, comfortably in gamut); a preset multiplies up from a much higher base, so the product is\n clamped to 0.3 where --glass-gloss-ink composes it — sRGB holds ~0.32 chroma at its very best hue\n and far less at L=66, so beyond that the browser, not the theme, is picking the color. Jewels\n (0.07 × 4.25 = 0.298) sit under the clamp and are unaffected; only the status tints hit it. */\n --glass-gloss-tint: 4.25;\n --glass-wash-l: 72%; /* tint wash lightness — deeper in dark so the tint reads over dark backdrops */\n --glass-wash-c-mult: 2.5; /* tint wash chroma multiplier (moonstone night quiets it to 2) */\n --glass-accent-l: 0.6; /* accent envelope — lifted in dark so the hue pops */\n --glass-accent-c: 0.15;\n --glass-glow-lc: 0.62 0.2; /* glow L+C pair */\n --glass-glow-a: 0.45;\n --harmonic-l: 0.6; /* --color-* accent-tier lightness */\n --mono-l-1: 0.75; /* mono ramp steps (irregular light↔dark lifts — three dials, not one) */\n --mono-l-2: 0.6;\n --mono-l-3: 0.45;\n --gradient-l: 0.6; /* brand-gradient envelope (matches --glass-accent-l/-c) */\n --gradient-c: 0.15;\n --gradient-contrast-l: 0.32; /* contrast gradient text: dark-on-light here, light-on-dark in .dark */\n --gradient-contrast-c: 0.14;\n --glass-crystal-l: 96; /* crystal body lightness — see --glass-crystal-bg; 100 carries zero chroma */\n --glass-crystal-bg-a: 0.3; /* crystal floor alpha (near-clear in dark) */\n /* glass-solid floor — the \"how solid\" dial (0 = sheer, 1 = fully solid; shared by light/dark) + the\n mode floor lightness; composed in @utility glass-veil so a scoped --glass-solid-a resolves there. */\n --glass-solid-a: 0.65;\n --glass-solid-l: 99%;\n /* Opaque tint BODY lightness (plain number → % in the engine). The header L slider drives this; the\n opaque floor color AND AutoForeground's --foreground-opaque banding both read it, so they can't\n drift. LOWER = deeper tint.\n L AND the c-scale below together absorb the retired opaque SHEET (see materials.css). --glass-bg was a\n near-white gradient at ~0.11 alpha, so it did two things to this floor: lightened it AND desaturated\n it. Compensating with lightness alone reproduced only half of that — it left the floor too saturated,\n and pushing L further toward white also SHRINKS the sRGB chroma ceiling, which blew the opaque body\n past check-gamut's 4.6× budget (lapis hit 5.46×). Moving both knobs reproduces both effects and stays\n inside the budget at 4.47×. Fitted across the 14 presets: worst OKLab dE 0.0021, mean 0.0009 — an\n order of magnitude under a just-noticeable difference, so the surface is unchanged to the eye.\n Moonstone pins its own pair (presets.css); its floor is near-white, where the sheet did far less. */\n /* L93. The opaque card is the glass stack at full opacity: this floor plus the same tint WASH every\n other material carries (materials.css). It used to be L88 with no wash, which made the floor carry\n the tint on its own, and near white that cannot work: the sRGB ceiling collapses, so the jewels sat\n at 97% of it and still read pastel, while greens and teals got half the chroma of their glass\n siblings. With the wash doing the tinting, the floor's job is the one the backing under sheer glass\n does (L92, --glass-solidify-l), and 93 is where floor + wash lands within 0.7 L of each preset's\n glass surface (it has no lighter page showing through to make up the last point) while body text\n keeps the 76.5 margin on every preset (tightest: destructive 76.6). */\n --glass-opaque-l: 93;\n /* 0.85, down from 1.26. The old value only looked large: it was immediately clipped to the shared\n 0.042 cap, so every preset rendered the SAME chroma regardless of its tint. Presets now pin their\n own --glass-opaque-c-max (their hue's real ceiling at L88), and 0.85 is the multiplier that lets\n each one reach it. Net effect vs the old flat 0.042: +7% to +88% depending on hue. */\n --glass-opaque-c-scale: 0.85;\n /* Opaque floor chroma cap — the near-white sibling of --glass-tint-c-hi / --glass-chakra-c-max /\n --glass-gloss-c-max, and the last surface that lacked one. Without a cap the floor multiplied raw\n tint chroma this close to white and clipped for most of the palette.\n FALLBACK ONLY: the 12 jewels each pin their own above (their hue's real ceiling at L88); this\n covers the 9 scopes that don't — the 4 frescoes, the 4 status tints and moonstone. Sizing the\n SHARED cap to the tightest hue is the mistake that came before: one number low enough for amber\n (h75, ceiling 0.036 at L88) left peridot at 18% of its own much larger ceiling. So this is\n deliberately not the tightest — per-hue pins are, and scripts/check-gamut.mjs sweeps every scope\n against its real ceiling rather than trusting this number. */\n --glass-opaque-c-max: 0.055;\n /* The share of that cap the opaque FLOOR may use. The caps above are each hue's ceiling at L88 (the\n solidify backing derives its own from them too), and at the floor's L93 the ceiling is 55–90% of\n that depending on hue; 0.55 keeps the tightest (tourmaline) at 0.96 of its ceiling. The floor\n needs little chroma now that the wash carries the tint. Dark sets 1: its floor sits mid-dark, where\n every preset's ask is far inside its ceiling. */\n --glass-opaque-cap-scale: 0.55;\n /* ── Solidify floor — the opaque backing UNDER sheer glass ───────────────────\n `glass` paints --glass-solidify (this colour at --glass-opacity) as the bottom background-image\n layer of every SHEER material, so it is ~70% of what page/crystal/chakra text actually sits on.\n It used to be --glass-opaque-bg itself, which conflated two different jobs: the colour an OPAQUE\n CARD shows, and the backing that sets sheer text contrast. Opaque cards explicitly drop this layer\n (materials.css, --srf-solidify: none — it would be C over C), so the two were never actually the\n same surface; they only shared a token.\n Splitting them is what lets the sheer floor rise without repainting opaque cards. It is the right\n lever for text contrast because it is the only one with real WEIGHT in the composite (0.7) that is\n not part of a preset's declared identity: --glass-wash-l is the theme COLOUR (moving it changes\n what lapis IS), and --glass-tint-a is already per-mode and desaturates. Measured across all 17\n scopes, L92 is what puts every preset's body text over the 75 floor with the 1.5 Lc margin, with\n no preset's tint tokens touched. DARK needs none of this (it clears everywhere already), so it\n pins both back to the opaque floor below and nothing changes there. */\n --glass-solidify-l: 92;\n /* The floor's chroma ceiling collapses as it lightens, so the L88 caps above would clip here. Rather\n than 17 new per-preset numbers, derive from the ones each preset already declares: across the\n palette the L92/L88 ceiling ratio runs 0.60–0.90, and 0.65 lands every scope INSIDE its real\n ceiling (worst 0.99×, verified by scripts/check-gamut.mjs). Hues that peak light — peridot,\n turquoise, aventurine, amber, aquamarine — are not bound by it at all and keep 100% of their\n surface chroma; the dark-peaking arc keeps 80–86%. */\n --glass-solidify-c-max: calc(var(--glass-opaque-c-max, 0.055) * 0.65);\n /* ── Chakra — the step-cut material (between opaque and crystal) ─────────────\n Every other material is lit from ONE direction: --glass-shadow carries a bright top bevel and a\n dark bottom shade, so surfaces read as a lit dome. Chakra lights all FOUR edges as mitered facets\n (a conic-gradient with hard stops on the diagonals, composed in engine.css), so it reads as a\n step-cut gem instead.\n The facets are pure LUMINANCE overlays — --shadow-highlight / --shadow-ink at these alphas, chroma\n 0 — not tinted colors. Two reasons, and they are the same reason: (1) the theme hue already comes\n through from the tinted body floor beneath, and a translucent white/black over it shifts lightness\n while preserving hue; (2) a bright facet sits near L96 in light mode, where the sRGB chroma ceiling\n across the 17 presets collapses to 0.0182 — a tinted highlight there would be gamut-mapped by the\n browser at nearly every preset. This is the theme's existing rule for the top bevel (\"a specular,\n not a shadow; grey speculars read as grime\"), applied to all four sides.\n The alphas are irregular mode TWINS, not one knob: the same alpha buys a very different lightness\n step over an L88 body than over an L28 one. They are solved so BOTH modes span 18 L end to end —\n light 94.0 > 90.5 > 88.0 > 83.0 > 76.0, dark 38.0 > 32.0 > 28.0 > 24.0 > 20.0 (top, left, table,\n right, bottom). Light comes from the top-left, matching the 135° axis of --glass-bg. */\n --glass-chakra-l: 88; /* body/table lightness */\n /* Body alpha — the \"between crystal and opaque\" dial, and now genuinely that across the WHOLE\n surface: crystal's floor sits at 0.3, opaque at 1. Nothing is painted over the middle any more\n (the cut moved into box-shadow), so this alone decides how much backdrop shows, and glass-diffuse\n / --glass-opacity modulate it the same way they do on crystal. */\n --glass-chakra-a: 0.62;\n --glass-chakra-c-max: 0.055; /* body chroma cap — ≤ 0.0564, the L-88 ceiling floor (lapis h268) */\n --glass-chakra-step: 3px; /* width of ONE bevel step (the frame is two) */\n --glass-chakra-facet-t-a: 0.5; /* top — highlight, +6.0 L */\n --glass-chakra-facet-l-a: 0.208; /* left — highlight, +2.5 L */\n --glass-chakra-facet-r-a: 0.057; /* right — ink, −5.0 L */\n --glass-chakra-facet-b-a: 0.136; /* bottom — ink, −12.0 L */\n /* ── Glass shadows — mode TWINS (not knobs: the light↔dark alpha ratios AND layer structures are\n irregular). They compose only --shadow-ink/-highlight, never tint vars, so bare placement is legal.\n LIGHT twins carry extra definition layers dark mode gets for free (a light surface on a dark page\n separates itself): a tight CONTACT drop + 1px dark outer HAIRLINE define the edge on same-lightness\n backdrops, and a directional BEVEL (bright top inner edge, ink bottom inner edge) makes pills read\n convex instead of printed. The last layer flips family per mode: dark's white inner GLOW (luminous\n glass) becomes an ink VIGNETTE in light — a white wash on a light surface brightens without\n defining, while a faint vignette reads as glass thickness. (Frosted keeps its white glow in both\n modes: milkiness IS that material.) The top bevel stays WHITE in both — it's a specular, not a\n shadow; grey speculars read as grime. Layer order: ambient drop, contact drop, hairline, top\n bevel, bottom shade, inner glow/vignette. */\n --glass-shadow:\n 0 4px 16px oklch(var(--shadow-ink) / 0.13), 0 1px 3px oklch(var(--shadow-ink) / 0.11), 0 0 0 1px oklch(var(--shadow-ink) / 0.05),\n 0 1px 0 oklch(var(--shadow-highlight) / 0.4) inset, 0 -1px 0 oklch(var(--shadow-ink) / 0.08) inset,\n 0 0 12px oklch(var(--shadow-ink) / 0.05) inset;\n --glass-shadow-lg:\n 0 8px 32px oklch(var(--shadow-ink) / 0.16), 0 2px 6px oklch(var(--shadow-ink) / 0.12), 0 0 0 1px oklch(var(--shadow-ink) / 0.05),\n 0 1px 0 oklch(var(--shadow-highlight) / 0.45) inset, 0 -1px 0 oklch(var(--shadow-ink) / 0.09) inset,\n 0 0 20px oklch(var(--shadow-ink) / 0.06) inset;\n --glass-shadow-sm:\n 0 2px 8px oklch(var(--shadow-ink) / 0.1), 0 1px 2px oklch(var(--shadow-ink) / 0.09), 0 0 0 1px oklch(var(--shadow-ink) / 0.04),\n 0 1px 0 oklch(var(--shadow-highlight) / 0.35) inset, 0 -1px 0 oklch(var(--shadow-ink) / 0.07) inset,\n 0 0 8px oklch(var(--shadow-ink) / 0.04) inset;\n --glass-frosted-border: oklch(100% 0 0 / 0.3);\n --glass-frosted-shadow:\n 0 8px 32px oklch(var(--shadow-ink) / 0.17), 0 2px 6px oklch(var(--shadow-ink) / 0.13), 0 0 0 1px oklch(var(--shadow-ink) / 0.05),\n 0 1px 0 oklch(var(--shadow-highlight) / 0.5) inset, 0 -1px 0 oklch(var(--shadow-ink) / 0.09) inset,\n 0 0 20px oklch(var(--shadow-highlight) / 0.18) inset;\n --glass-crystal-shadow: var(--glass-shadow), 0 1px 1px oklch(var(--shadow-highlight) / 0.4) inset;\n --glass-crystal-shadow-hover: var(--glass-shadow-lg), 0 1px 1px oklch(var(--shadow-highlight) / 0.45) inset;\n /* Base colors - Light mode with dark text */\n --background: transparent;\n --foreground: oklch(\n 0.2 0.035 var(--glass-fg-h)\n ); /* CSS baseline (tinted, ~body target) so first paint has no gray→tint flash; AutoForeground refines to the exact APCA solve after */\n /* Size-tiered foregrounds (ARC Bronze Simple Mode bands) — static fallback; AutoForeground re-bands\n these against the live glass surface at runtime. text-foreground-soft / -strong utilities. */\n --foreground-soft: oklch(\n 0.45 0.077 var(--glass-fg-h)\n ); /* large/heading — baseline matched to AutoForeground's median pick; chroma is the LOWEST any preset hue solves to, so no hue clips before JS refines */\n --foreground-strong: oklch(\n 0.2 0.034 var(--glass-fg-h)\n ); /* fine/small — the ramp's tonal clip put this at L20, not L12; the old value flashed dark-then-tinted on every load */\n --foreground-ui: oklch(0.42 0.071 var(--glass-fg-h)); /* icons — AutoForeground re-bands (+ optional hue) */\n /* Static baselines for the OPAQUE + CRYSTAL tier sets (the [data-material] remaps read these) —\n WITHOUT them, a no-JS consumer (or SSR first paint) would substitute an unset var and INHERIT the\n page foreground onto those floors. Light mode: the opaque floor (L90) and crystal surface share\n the page's direction, so the normal literals apply; AutoForeground refines when mounted. */\n --foreground-opaque: oklch(0.22 0.04 var(--glass-fg-h));\n --foreground-soft-opaque: oklch(0.4 0.04 var(--glass-fg-h));\n --foreground-strong-opaque: oklch(0.12 0.04 var(--glass-fg-h));\n --foreground-ui-opaque: oklch(0.46 0.05 var(--glass-fg-h));\n --muted-foreground-opaque: oklch(0.14 0.04 var(--glass-fg-h));\n --foreground-crystal: oklch(0.22 0.04 var(--glass-fg-h));\n --foreground-soft-crystal: oklch(0.4 0.04 var(--glass-fg-h));\n --foreground-strong-crystal: oklch(0.12 0.04 var(--glass-fg-h));\n --foreground-ui-crystal: oklch(0.46 0.05 var(--glass-fg-h));\n --muted-foreground-crystal: oklch(0.14 0.04 var(--glass-fg-h));\n /* Chakra tier baselines — text sits on the TABLE (the flat centre, L 88), not on the facets, so in\n light mode the direction matches the page and the normal literals apply. AutoForeground re-bands\n against the composited table when mounted. */\n --foreground-chakra: oklch(0.22 0.04 var(--glass-fg-h));\n --foreground-soft-chakra: oklch(0.4 0.04 var(--glass-fg-h));\n --foreground-strong-chakra: oklch(0.12 0.04 var(--glass-fg-h));\n --foreground-ui-chakra: oklch(0.46 0.05 var(--glass-fg-h));\n --muted-foreground-chakra: oklch(0.14 0.04 var(--glass-fg-h));\n --card: transparent;\n --card-foreground: var(--foreground);\n --popover: transparent;\n --popover-foreground: var(--foreground);\n\n /* Primary colors - Light mode */\n --primary-l: 0.42; /* primary ink lightness (dark pill on light pages) — the color itself is composed in the engine, following the theme hue */\n /* Ceiling on the primary ink's chroma. The ×1.2 in the engine is intent — primary should read MORE\n saturated than the base tint — but uncapped it asked past the sRGB edge for five presets in light\n (turquoise, aquamarine, success, warning, aurora) and two in dark (lapis, info), where the browser\n gamut-maps and the delivered colour stops tracking the authored one.\n Measured against --glass-fg-h, not --glass-tint-h: --primary follows the FOREGROUND hue, and the\n Muse/Sistine frescoes decouple the two (fg 290 / 18), so those hues are in the sweep — aurora only\n shows up as out-of-gamut once you check the fg hue. */\n --primary-c-max: 0.069; /* ≤ 0.0697, the L-42 ceiling floor — h205 (aurora) is now tighter than h190 */\n --primary-foreground: oklch(0.98 0 0); /* White text on the primary ink — Lc 88.1 at the worst hue, turquoise (measured) */\n\n /* Secondary colors - Light mode */\n /* ── Flat-neutral shadcn SURFACES, now theme-tinted ──────────────────────────\n --accent, --secondary, --muted and --sidebar were the last chroma-0 tokens in the theme, which is\n why a toggle's on-state read as near-white in light and near-black in dark no matter which preset\n was active: the fill simply had no hue to give. Same treatment as --border — lightness stays put\n (pure recolor), the colour moves to engine.css so it re-resolves per tint scope, and chroma is\n min(the preset's own, a cap) so a preset that declares less keeps its restraint and selenite stays\n exactly neutral.\n Each cap is measured against the tightest sRGB ceiling across all 17 presets AT THAT LIGHTNESS, and\n they differ by a factor of eight because the ceiling collapses toward white: 0.0714 at L 85, but\n only 0.0091 at L 98. One shared cap would have to be the smallest, tinting nothing. */\n --secondary-l: 0.96;\n --secondary-c-max: 0.018; /* ≤ 0.0182, the L-96 ceiling floor (lapis h268) */\n --secondary-foreground: var(--foreground);\n\n /* Muted colors - Light mode */\n --muted-l: 0.85;\n --muted-c-max: 0.071; /* ≤ 0.0714, the L-85 ceiling floor (lapis h268) */\n --muted-foreground: oklch(\n 0.35 0.06 var(--glass-fg-h)\n ); /* Level 1 — L35, not L14: the widest gap of the set, and the one doing most of the first-paint flash */\n\n /* Accent colors - Light mode. NOTE the knob prefix: --accent-c / --accent-h are already taken as the\n user-facing overrides for --glass-accent (the vivid switch/slider fill), a different role. */\n /* L93, down from 0.96, and the cap raised with it. The hover fill was reading as plain white: at L96 the\n sRGB gamut allows a chroma of only 0.0182, an RGB spread of 19, so \"tinted\" was arithmetically\n invisible. Chroma near the extremes is not a free parameter — it is whatever the gamut leaves — so the\n only way to a visible tint is to step off the extreme. Three points of lightness buys 1.7x the spread\n (19 -> 32) and moves the fill DARKER than the surface, the direction a highlight should go on a light\n page. It cannot go much further: --accent is also painted at 50% (bg-accent/50 on calendar's \"today\",\n button's active state, ToggleGroup hover), and that composite over an L88 card is what binds — at L92\n it drops to 76.0 Lc, under the floor+margin, while L93 holds 76.9. */\n --accent-bg-l: 0.93;\n --accent-bg-c-max: 0.032; /* ≤ 0.0323, the L-93 ceiling floor (lapis h268) */\n /* Stays --foreground: this token is text on a SURFACE-lightness background. Its callers all have that\n shape — the ghost button's hover, toggles, and the highlighted row of every menu (dropdown, context,\n navigation, command, menu-bar, select), which paint bg-accent so a consumer's --accent reaches them\n (the shadcn contract). Text and fill only stay legible together while --accent sits near the\n surface, which is precisely why the fill was moved by chroma rather than by weight. */\n --accent-foreground: var(--foreground);\n\n /* Destructive colors - WCAG AA compliant (4.5:1 contrast) */\n --destructive: oklch(0.55 0.22 25); /* Red - 4.5:1 with white */\n --destructive-foreground: oklch(0.98 0 0); /* White text on red */\n\n /* Border / input LIGHTNESS + chroma-CAP knobs — the COLOR is composed in engine.css so the edge\n follows the theme tint like every other surface token (leaves, so they inherit into tint scopes\n where the engine re-resolves).\n L 0.85, not the 0.88 the flat neutral carried: sRGB's chroma ceiling collapses as lightness climbs,\n and at 0.88 the binding ceiling across all 17 presets is 0.0564 — the most colorful in-gamut edge\n possible there still reads as grey (mean RGB spread 51). Dropping 3 points of L raises the ceiling\n to 0.0714 and nearly doubles delivered colorfulness (mean spread 65) while moving the grey\n reference only #d7d7d7 → #cecece, which is below the threshold of a perceived weight change.\n --border-c-max is the CAP, not the value: chroma is min(tint chroma, cap), so a preset that\n declares less than the cap keeps its own restraint — selenite (c 0) stays exactly neutral, which is\n the whole point of that theme — while lapis (c 0.15) clamps instead of being gamut-mapped by the\n browser. A flat multiplier cannot do both: any factor small enough to keep lapis in gamut drags the\n 0.07 jewels below where they are today. */\n --border-l: 0.85;\n --input-l: 0.85;\n --border-c-max: 0.07; /* ≤ 0.0714, the L-0.85 ceiling floor (lapis h268) */\n --ring: oklch(0.5 0.2 250); /* Focus ring */\n\n /* Chart colors - WCAG AA compliant */\n --chart-1: oklch(0.6 0.2 250); /* Blue */\n --chart-2: oklch(0.55 0.15 150); /* Green */\n --chart-3: oklch(0.5 0.2 50); /* Yellow */\n --chart-4: oklch(0.65 0.2 300); /* Purple */\n --chart-5: oklch(0.6 0.2 20); /* Orange */\n\n /* Sidebar colors - Light mode */\n --sidebar-l: 0.98;\n --sidebar-c-max: 0.009; /* ≤ 0.0091, the L-98 ceiling floor — near white holds almost no hue */\n --sidebar-foreground: var(--foreground);\n --sidebar-primary: oklch(0.5 0.2 250); /* Blue */\n --sidebar-primary-foreground: oklch(0.98 0 0); /* White text on primary */\n --sidebar-accent: oklch(0.15 0 0 / 0.1); /* Subtle glass overlay (≈ foreground/10), not a solid block */\n --sidebar-accent-foreground: var(--foreground);\n --sidebar-border: var(--border); /* Same divider role, same value it already carried — follow the role token */\n --sidebar-ring: oklch(0.5 0.2 250); /* Blue ring - 3:1 contrast */\n}\n\n.dark {\n /* Base colors — light text on a dark ground */\n --background: transparent;\n /* Pressed/active shadow SCALE — dark overrides: primaries deepen +0.1, contact layers +0.05, so the\n push-in still reads on dark surfaces (see :root for the light ladder + rationale). */\n --press-shadow-sm: inset 0 1px 2px oklch(var(--shadow-ink) / 0.2);\n --press-shadow: inset 0 2px 4px oklch(var(--shadow-ink) / 0.3);\n --press-shadow-strong: inset 0 2px 4px oklch(var(--shadow-ink) / 0.3), inset 0 1px 2px oklch(var(--shadow-ink) / 0.2);\n --press-shadow-deep: inset 0 2px 4px oklch(var(--shadow-ink) / 0.35), inset 0 1px 2px oklch(var(--shadow-ink) / 0.25);\n --active-shadow:\n 0 2px 6px oklch(var(--shadow-ink) / 0.4), inset 0 1px 2px oklch(var(--shadow-highlight) / 0.15),\n inset 0 0 8px oklch(var(--shadow-highlight) / 0.1);\n /* Selected-control fill — dark twin. Deliberately UNCHANGED in behaviour from the `bg-white/20` it\n replaces: chroma cap 0 makes min() resolve to 0, so this is a pure white lift and the hue arrives\n by show-through from the surface below, which is exactly why dark already read correctly. Light\n has to source its hue directly (see :root); the two modes reach the same result by different\n routes because the near-white gamut only permits one of them. */\n --active-l: 100%;\n --active-c-max: 0;\n --active-a: 0.2;\n /* ── Mode knobs — dark twins (see :root for what each dial does). */\n --glass-sheet-a: 0.05;\n --glass-sheet-a1: 0.03;\n --glass-frost-boost: 0.06;\n --glass-border-a: 0.15;\n --glass-wash-l: 58%;\n --glass-accent-l: 0.68;\n --glass-accent-c: 0.14;\n --glass-glow-lc: 0.7 0.18;\n --glass-glow-a: 0.5;\n --harmonic-l: 0.68;\n --mono-l-1: 0.82;\n --mono-l-2: 0.68;\n --mono-l-3: 0.5;\n --gradient-l: 0.68;\n --gradient-c: 0.14;\n --gradient-contrast-l: 0.9;\n --gradient-contrast-c: 0.09;\n --glass-crystal-l: 96; /* shared with light: the ceiling that forces 96 is hue-driven, not mode-driven */\n --glass-crystal-bg-a: 0.1;\n /* Gloss ink lightness — dark twin. Over an L-28 floor there is 72 L of headroom, so 66 lifts +15.2\n and still holds 59% of the tonal ink's chroma: a specular that is genuinely tinted. Light cannot\n have both (see :root). */\n --glass-gloss-l: 66;\n /* Gloss chroma cap — the L-66 twin. Far more headroom this far from white: 0.1094 is aurora (h205),\n the tightest across all 23 preset scopes — the FRESCOES count here too, which is why this is not\n turquoise's 0.1106. Dark gloss keeps real colour; it was still clipping before (turquoise\n 0,193,186 and carnelian 255,0,0 both had a pinned channel). */\n --glass-gloss-c-max: 0.109;\n /* L lifted from 22% (less extreme light↔dark switch); the opaque chroma scale (0.9) keeps warm hues\n like amber in-gamut instead of hard-clipping to rust. */\n --glass-solid-l: 18%;\n /* The retired sheet's compensation, dark twin (see :root). Much the larger move of the two modes: a\n near-white sheet over a DARK floor lifts it ~12 codes where over the light floor it lifts ~2–5. Same\n two-knob fit, worst OKLab dE 0.0045 across the 14 presets.\n 33.4 since opaque carries the tint wash (materials.css). The wash lifts the card, and unlike sheer\n glass no dark page shows through it, so at the backing's own 36.4 an opaque card sat 3–5 L above its\n glass siblings, and on the light-wash presets (aventurine, peridot, turquoise) only pure white text\n still reached the body floor. Three points down puts it within ~2 L of the glass beside it and keeps\n tinted text above the 76.5 margin everywhere (tightest: moonstone 76.6). The light floor makes the\n mirror-image move (L93 over a L92 backing) for the mirror-image reason. */\n --glass-opaque-l: 33.4;\n /* 0.98. Was 1.05 (up from 0.85): the dark floor was never cap-limited — every preset sat at 31–79% of\n its own ceiling, so the MULTIPLIER was the throttle and dark opaque cards lost their tint. It is\n still the most the tightest hue (turquoise, ×0.984) can take without clipping, now at the floor's\n L33.4, where the ceilings are lower than at L36.4. The wash carries most of the colour on top of it.\n The sheer backing shares this scale (engine.css), so dark glass gives up 7% of its backing chroma\n with it, under a wash that dominates it. */\n --glass-opaque-c-scale: 0.98;\n --glass-opaque-cap-scale: 1;\n /* Opaque floor chroma cap, dark twin (see :root). A GUARD, not a throttle — the distinction matters\n and this value used to get it wrong. It was 0.06, the tightest ceiling across the preset scopes,\n which was correct while every jewel shared --glass-tint-c: 0.07 and the floor multiplied it blind.\n Once each preset tuned its own chroma to its own hue, that shared floor-of-the-tightest started\n costing the hues WITH headroom: at 0.06 amethyst lost 30% of its ask, tourmaline 33%, rose 26% —\n against own ceilings of 0.189, 0.152 and 0.142. Measured, every preset's dark ask now sits at\n 0.31–0.79× of its own ceiling, so nothing clips with no cap at all. 0.095 clears the largest ask\n (tourmaline 0.0901) and therefore throttles nobody, while still bounding a future preset that\n pushes chroma far past today's values. check-gamut scores each preset against its own ceiling. */\n /* Guard, not a throttle: the largest dark ask is tourmaline at 0.111, so this binds nobody. Declared\n in its own rule right after this block (see there for why). */\n /* The dark backing under sheer glass, at the lightness the opaque floor had before opaque gained the\n wash: dark glass is unchanged by that move. It is its own value now, as light's L92 is, so the tint\n switcher's Lightness slider (which writes --glass-opaque-l) moves the opaque card in both modes and\n the sheer backing in neither. The cap stays the opaque one: dark already clears the body floor on\n every preset with margin, and a lighter or greyer backing would wash the whole mode out. */\n --glass-solidify-l: 36.4;\n --glass-solidify-c-max: var(--glass-opaque-c-max);\n /* ── Chakra dark twins (see :root for what each dial does + how the alphas were solved).\n The facet alphas invert in character: over an L28 body there is 72 L of headroom UP and only 28\n DOWN, so the highlights need far less alpha and the inks far more than their light-mode twins to\n land the same 18 L span. The targets also skew brighter here (+10/−8 rather than +6/−12) — a dark\n gem reads as cut by catching light, where a light one reads by casting shade. */\n --glass-chakra-l: 28;\n --glass-chakra-a: 0.58;\n --glass-chakra-c-max: 0.046; /* ≤ 0.0471, the L-28 ceiling floor (turquoise h190) */\n --glass-chakra-facet-t-a: 0.139; /* top — highlight, +10.0 L */\n --glass-chakra-facet-l-a: 0.056; /* left — highlight, +4.0 L */\n --glass-chakra-facet-r-a: 0.143; /* right — ink, −4.0 L */\n --glass-chakra-facet-b-a: 0.286; /* bottom — ink, −8.0 L */\n /* ── Glass shadows — dark twins (deeper drops, brighter bevels). */\n --glass-shadow:\n 0 4px 16px oklch(var(--shadow-ink) / 0.3), 0 1px 4px oklch(var(--shadow-ink) / 0.15), 0 0 0 0.5px oklch(var(--shadow-highlight) / 0.12) inset,\n 0 0 12px oklch(var(--shadow-highlight) / 0.15) inset;\n --glass-shadow-lg:\n 0 8px 32px oklch(var(--shadow-ink) / 0.4), 0 2px 8px oklch(var(--shadow-ink) / 0.2), 0 0 0 0.5px oklch(var(--shadow-highlight) / 0.15) inset,\n 0 0 20px oklch(var(--shadow-highlight) / 0.18) inset;\n --glass-shadow-sm:\n 0 2px 8px oklch(var(--shadow-ink) / 0.25), 0 1px 2px oklch(var(--shadow-ink) / 0.12), 0 0 0 0.5px oklch(var(--shadow-highlight) / 0.1) inset,\n 0 0 8px oklch(var(--shadow-highlight) / 0.12) inset;\n --glass-frosted-border: oklch(100% 0 0 / 0.25);\n --glass-frosted-shadow:\n 0 8px 32px oklch(var(--shadow-ink) / 0.5), 0 2px 8px oklch(var(--shadow-ink) / 0.3), 0 0 0 0.5px oklch(var(--shadow-highlight) / 0.2) inset,\n 0 0 20px oklch(var(--shadow-highlight) / 0.22) inset;\n --glass-crystal-shadow: var(--glass-shadow), 0 1px 1px oklch(var(--shadow-highlight) / 0.2) inset;\n --glass-crystal-shadow-hover: var(--glass-shadow-lg), 0 1px 1px oklch(var(--shadow-highlight) / 0.25) inset;\n --foreground: oklch(\n 0.95 0.03 var(--glass-fg-h)\n ); /* CSS baseline (tinted, ~body target) so first paint has no gray→tint flash; AutoForeground refines to the exact APCA solve after */\n /* Size-tiered foregrounds (ARC Bronze Simple Mode bands) — static fallback; AutoForeground re-bands\n these against the live glass surface at runtime. text-foreground-soft / -strong utilities. */\n --foreground-soft: oklch(0.83 0.065 var(--glass-fg-h)); /* large/heading — refined at runtime */\n --foreground-strong: oklch(0.97 0.015 var(--glass-fg-h)); /* fine/small — refined at runtime */\n --foreground-ui: oklch(0.87 0.057 var(--glass-fg-h)); /* icons — AutoForeground re-bands (+ optional hue) */\n /* Static OPAQUE + CRYSTAL tier baselines, dark twins — the standard dark opaque floor (L32) and\n crystal surface keep the page's light-text direction. Presets whose dark floors flip LIGHT (moonstone\n cream) override the -opaque set in their own block; AutoForeground refines when mounted. */\n --foreground-opaque: oklch(0.92 0.04 var(--glass-fg-h));\n --foreground-soft-opaque: oklch(0.78 0.04 var(--glass-fg-h));\n --foreground-strong-opaque: oklch(0.96 0.035 var(--glass-fg-h));\n --foreground-ui-opaque: oklch(0.75 0.05 var(--glass-fg-h));\n --muted-foreground-opaque: oklch(0.89 0.04 var(--glass-fg-h));\n --foreground-crystal: oklch(0.92 0.04 var(--glass-fg-h));\n --foreground-soft-crystal: oklch(0.78 0.04 var(--glass-fg-h));\n --foreground-strong-crystal: oklch(0.96 0.035 var(--glass-fg-h));\n --foreground-ui-crystal: oklch(0.75 0.05 var(--glass-fg-h));\n --muted-foreground-crystal: oklch(0.89 0.04 var(--glass-fg-h));\n /* Chakra tier baselines, dark twins — the L-28 table keeps the page's light-text direction. */\n --foreground-chakra: oklch(0.92 0.04 var(--glass-fg-h));\n --foreground-soft-chakra: oklch(0.78 0.04 var(--glass-fg-h));\n --foreground-strong-chakra: oklch(0.96 0.035 var(--glass-fg-h));\n --foreground-ui-chakra: oklch(0.75 0.05 var(--glass-fg-h));\n --muted-foreground-chakra: oklch(0.89 0.04 var(--glass-fg-h));\n --card: transparent;\n --card-foreground: var(--foreground);\n --popover: transparent;\n --popover-foreground: var(--foreground);\n\n /* Primary colors — dark mode */\n --primary-l: 0.78; /* primary ink lightness in dark mode (light pill) — color composed in the engine */\n --primary-c-max: 0.107; /* ≤ 0.1077, the L-78 ceiling floor (h268 lapis) */\n /* Near-black text on the primary ink — Lc 62.9 at the worst hue, sistine (measured). It was 60.9\n before --primary-c-max: capping the chroma the browser used to gamut-map moved the worst case back\n above the 62 this comment had been claiming. */\n --primary-foreground: oklch(0.15 0 0);\n\n /* Secondary colors — dark mode */\n /* Flat-neutral surfaces — dark twins (see :root for why these are knobs now). Lightness unchanged;\n the caps are the dark ceilings, which are tightest at the DARKEST step, not the lightest. */\n --secondary-l: 0.25;\n --secondary-c-max: 0.041; /* ≤ 0.0417, the L-25 ceiling floor — h205 (aurora) is now tighter than h190 */\n --secondary-foreground: var(--foreground);\n\n /* Muted colors — dark mode */\n --muted-l: 0.18;\n --muted-c-max: 0.03; /* ≤ 0.0307, the L-18 ceiling floor (turquoise h190) */\n --muted-foreground: oklch(0.9 0.031 var(--glass-fg-h)); /* Level 1 — one ramp step toward the chosen color */\n\n /* Accent colors — dark mode */\n /* L34, up from 0.25 — the same fix as light, mirrored. At L25 the ceiling was 0.0417 (spread 29), so the\n fill read as near-black; L34 allows 0.0565 (spread 42) and moves it BRIGHTER than the surface, which is\n the direction a highlight goes on a dark page. Dark has more room than light here (its 50% composite\n sits at 77.5 Lc rather than 76.0), because the fill and the L36.4 card floor are close together. */\n --accent-bg-l: 0.34;\n --accent-bg-c-max: 0.056; /* ≤ 0.0565, the L-34 ceiling floor (aurora h205) */\n --accent-foreground: var(--foreground);\n\n /* Destructive colors - WCAG AA compliant (4.5:1 contrast) */\n --destructive: oklch(0.65 0.22 25); /* Light red - 4.5:1 with dark background */\n --destructive-foreground: oklch(1 0 0); /* White text on red — APCA Lc ~67 (near-black was Lc 42) */\n\n /* Border / input lightness + chroma-cap — dark twins (see :root; color composed in engine.css).\n L is UNCHANGED here: in dark mode the edge sits at 0.30/0.25 on a dark page, so lowering it would\n cost contrast rather than buy color. Only the cap moves, up to the tightest dark ceiling — 0.0422\n at L 0.25 (turquoise h190), which --input hits first, so one cap serves both. */\n --border-l: 0.3;\n --input-l: 0.25;\n --border-c-max: 0.041; /* ≤ 0.0422, the L-0.25 ceiling floor (turquoise h190) */\n --ring: oklch(0.6 0.2 250); /* Focus ring */\n\n /* Chart colors - WCAG AA compliant */\n --chart-1: oklch(0.65 0.2 250); /* Light blue */\n --chart-2: oklch(0.7 0.15 150); /* Light green */\n --chart-3: oklch(0.75 0.2 50); /* Light yellow */\n --chart-4: oklch(0.7 0.2 300); /* Light purple */\n --chart-5: oklch(0.68 0.2 20); /* Light orange */\n\n /* Sidebar colors — dark mode */\n --sidebar-l: 0.18;\n --sidebar-c-max: 0.03; /* ≤ 0.0307, the L-18 ceiling floor (turquoise h190) */\n --sidebar-foreground: var(--foreground);\n --sidebar-primary: oklch(0.56 0.16 250); /* Match --primary; white text clears APCA */\n --sidebar-primary-foreground: oklch(1 0 0); /* White on the sidebar's brand fill */\n --sidebar-accent: oklch(1 0 0 / 0.1); /* Subtle glass overlay (≈ foreground/10), not a solid block */\n --sidebar-accent-foreground: var(--foreground);\n --sidebar-border: var(--border); /* Same divider role, same value it already carried — follow the role token */\n --sidebar-ring: oklch(0.6 0.2 250); /* Light blue ring - 3:1 contrast */\n}\n\n/* The dark opaque-floor chroma guard, in its own rule so it can OUTRANK the presets. Each jewel pins\n --glass-opaque-c-max to its hue's sRGB ceiling at the LIGHT floor (L88, presets.css), in an unscoped\n block. On that block and `.dark` tie at (0,1,0) and presets.css loads later, so the light caps\n used to win in dark too: amethyst's dark floor rendered C 0.065 against an ask of 0.106 and a ceiling\n of 0.189, lapis 0.055 against 0.236, and every blue/purple/red jewel's dark opaque card came out a dull,\n greyed version of its tint (23–53% of the chroma its hue can hold at L36.4). The light caps describe\n the light floor only; at L36.4 every preset's ask sits well inside its own ceiling, so this guard is\n the only cap dark needs. (0,2,0) beats a preset block; `.dark [data-glass-tint]` covers a scoped tint\n inside a dark page, and a preset's own `.dark[data-glass-tint=\"x\"]` block still wins by source order. */\n.dark,\n.dark[data-glass-tint],\n.dark [data-glass-tint] {\n --glass-opaque-c-max: 0.12;\n}\n\n@layer base {\n * {\n @apply border-border outline-ring/50;\n scrollbar-width: thin;\n scrollbar-color: color-mix(in oklch, var(--foreground) 22%, transparent) transparent;\n }\n body {\n /* Text uses the band-picked --foreground (set on by AutoForeground on theme/tint change;\n globals carries a static fallback so there's no flash). */\n color: var(--foreground);\n background-color: transparent;\n }\n /* Thin, glass-friendly scrollbars (replaces the chunky native bar) */\n ::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n }\n ::-webkit-scrollbar-track {\n background: transparent;\n }\n ::-webkit-scrollbar-thumb {\n background-color: color-mix(in oklch, var(--foreground) 22%, transparent);\n border-radius: 9999px;\n }\n ::-webkit-scrollbar-thumb:hover {\n background-color: color-mix(in oklch, var(--foreground) 38%, transparent);\n }\n}\n\n/* ── Below the floor ──────────────────────────────────────────────────────────\n The supported floor is Safari 17.5 / Chrome 120 / Firefox 128 / Edge 120, declared as the browserslist\n in SISTINE's package.json. It is set by what this theme actually uses, not by preference — the binding\n constraints are text-wrap:balance (Safari 17.5), bare-`&` CSS nesting and unprefixed mask-composite\n (Chrome 120), and @property (Firefox 128). Tailwind v4's own baseline sits just under it. That\n browserslist does not ship with this file: a consuming app should declare its own, at or above the\n floor, or its toolchain targets whatever its defaults are.\n\n Every colour in this theme is oklch(), fed through custom properties. On an engine without oklch the\n custom property still PARSES (custom props accept anything) and only fails when substituted — so\n `background-color: var(--color-background)` resolves to an invalid value and computes to `unset`,\n i.e. transparent, rather than to a fallback. Surfaces disappear; text usually survives on the UA\n default. This is not a polyfill and does not try to reproduce the palette — it guarantees a legible\n page instead of transparent panels on stacked backgrounds.\n\n UNVERIFIED IN A REAL ENGINE: no browser in the test matrix lacks oklch, so this branch has only been\n confirmed to compile, not to render. Treat it as a floor, not a tested path. */\n@supports not (color: oklch(0% 0 0)) {\n :root {\n color-scheme: light;\n background-color: #f7f7fb;\n color: #17161c;\n }\n body {\n background-color: #f7f7fb;\n color: #17161c;\n }\n .glass,\n [data-surface],\n [data-material] {\n background-color: #ffffffe6;\n border: 1px solid #17161c26;\n backdrop-filter: none;\n }\n}\n/* ── Glass engine ─────────────────────────────────────────────────────────────\n THREE grouped blocks, split by what must re-resolve where (the var-composition-resolves-at-declaration\n gotcha — see scripts/check-theme.mjs):\n\n A. TINT ANCHORS (:root, [data-glass-tint], …) — the tint knobs plus everything mode-INVARIANT that\n composes them, declared on :root AND every tint scope so a scoped data-glass-tint re-resolves the\n whole subgraph. Deliberately NOT on `.dark`: re-declaring --glass-tint-h / --glass-fg-h at a bare\n `.dark` wrapper would reset a page tint inherited from above.\n\n B. MODE-DERIVED SURFACES (:root, .dark, [data-glass-tint], …) — every tint-composing token whose\n light/dark difference is a handful of numbers. Each is declared ONCE, composing the mode knobs\n (--glass-sheet-a, --glass-wash-l, … — see :root/.dark) which are LEAVES that inherit into tint\n scopes. `.dark` IS in this selector so a dark wrapper inside a light page re-composes its surfaces\n from the wrapper's own dark knobs (same-element cascade) — \"toggle .dark anywhere\" keeps working.\n\n C. GLOSS (:root, .dark, [data-glass-tint], [data-gloss]) — the crystal gloss and the stacks that bake\n it, which also have to recompose wherever a `data-gloss` flavor is set. See block C itself.\n\n Foreground tokens stay on bare :root/.dark (AutoForeground owns them). Preset blocks below override\n --glass-tint-h/c/a by source order. Shadow composites are mode-TWINNED on bare :root/.dark (their\n light↔dark ratios are irregular — knobbing them would change values). */\n:root,\n[data-glass-tint] {\n /* Deliberately restrained glassmorphism — enough to read as glass, never enough to fog what is behind it. */\n /* Glass tint — recolor ALL glass by changing these; presets set them via [data-glass-tint].\n -h = hue, -c = chroma (gradient), -a = wash alpha. The gradient alone is too sheer\n (~6–12% opacity) to show a hue, so -a paints a solid colored floor (--glass-tint-wash)\n that reads over any backdrop. Neutral keeps -a:0, so the default glass is unchanged. */\n --glass-tint-h: 250;\n --glass-tint-c: 0.018;\n --glass-tint-a: 0;\n /* The same chroma, capped for NEAR-WHITE surfaces. The sheet stops, the borders and the crystal\n floor sit at L 95–100%, where sRGB's chroma ceiling collapses AND becomes strongly hue-dependent:\n at L=97% it is ~0.071 for peridot (h128) but ~0.014 for lapis (h268). Feeding those the raw\n --glass-tint-c means each preset lands on whatever fraction of its declared value its hue happens\n to permit — lapis was delivering 9% of its 0.15 where peridot delivered 98% of its 0.07 — and the\n shortfall is then resolved by the browser's gamut mapping rather than by us. That is the same\n class of bug the 3% margin in lib/oklch-utils guards against on the computed path (\"foregrounds\n went grey in Safari while staying tinted in Chrome\"); this is its CSS-side counterpart.\n Capping the BASE (rather than lowering any preset) keeps chroma where it has headroom — a jewel's\n depth lives at mid lightness, in --primary and --glass-tint-wash, which are untouched by this.\n Guarded by scripts/check-gamut.mjs.\n 0.025 was still too generous, because the sheet MULTIPLIES this cap: its stops are\n calc(--glass-tint-c-hi * 0.3…1.3), so the top stop asked 0.0325 against an L-95 ceiling of 0.0229\n at the tightest hue — the cap itself clipped. 0.017 is that ceiling divided by the 1.3 multiplier,\n so the sheet now lands inside the gamut at every preset hue instead of at whatever the browser\n chops it to. Cap-then-multiply is the order that matters here; see check-gamut's SCALE/CAP/BOTH\n shapes, which model each form separately for exactly this reason. */\n --glass-tint-c-hi: min(var(--glass-tint-c), 0.017);\n /* Foreground hue — drives every FOREGROUND-keyed token (body/heading text via AutoForeground,\n --muted-foreground, --glass-accent, --glass-glow, --gradient*), kept SEPARATE from the surface\n hue so tuning text never shifts the glass/fresco surface. Defaults to the tint hue (jewels: text\n == surface); frescoes override it to anchor text off their multi-hue surface (Muse: surface\n 230, text 290). AutoForeground reads this, falling back to --glass-tint-h for older consumers. */\n --glass-fg-h: var(--glass-tint-h);\n /* ── Harmonic color system ────────────────────────────────────────────────────────────────────\n Color-wheel relationships derived from the CONTENT hue (--glass-fg-h) — the same hue --glass-accent\n and --gradient track — so they rotate with the theme/tint automatically (a generalization of the\n --gradient's +63.53° offset). Hues are mode-invariant ANGLES; CSS Color 4 wraps them past 360°, so use\n anywhere as oklch(L C var(--hue-*)); choosing your own L/C also keeps them in-gamut. The ready --color-*\n tokens sit at the --glass-accent envelope (0.6/0.15; .dark lifts them). NOTE: a fixed-chroma harmonic can\n land at the sRGB edge at some hues and get browser-gamut-mapped — prefer the hue tokens + a conservative\n chroma when that matters. */\n /* Harmonic anchor hue — the wheel origin the harmonies rotate from. Normally the content hue, but the two\n \"hue-less\" themes (selenite + moonstone) anchor at 0° instead — each preset block in presets.css declares\n it, so a CSS-only consumer gets the anchor too (the tint switcher also inlines it, for a custom tint\n dragged to chroma 0) — and their accents come out a colorful red-based harmony, not a warp of a\n near-grey hue. */\n --harmony-h: var(--glass-fg-h);\n --hue-base: var(--harmony-h);\n --hue-complement: calc(var(--harmony-h) + 180);\n --hue-analogous-1: calc(var(--harmony-h) - 30);\n --hue-analogous-2: calc(var(--harmony-h) + 30);\n --hue-split-1: calc(var(--harmony-h) + 150);\n --hue-split-2: calc(var(--harmony-h) + 210);\n --hue-triad-1: calc(var(--harmony-h) + 120);\n --hue-triad-2: calc(var(--harmony-h) + 240);\n --hue-tetrad-1: calc(var(--harmony-h) + 60); /* rectangle: base, +60, +180, +240 */\n --hue-tetrad-2: calc(var(--harmony-h) + 180);\n --hue-tetrad-3: calc(var(--harmony-h) + 240);\n --hue-square-1: calc(var(--harmony-h) + 90); /* square: base, +90, +180, +270 */\n --hue-square-2: calc(var(--harmony-h) + 180);\n --hue-square-3: calc(var(--harmony-h) + 270);\n /* Harmonic chroma — a fixed vivid accent chroma so the palette stays colorful in EVERY theme (selenite +\n moonstone included; they get their color from the hue-0 anchor above, not from chroma). --mono-chroma is\n quieter (0.6×) for the subtle mono ramp. */\n --color-chroma: 0.15;\n --mono-chroma: calc(var(--color-chroma) * 0.6);\n /* (--color-* / --mono-* ready colors live in the mode-derived surfaces block below — their L is the\n mode envelope; the hue angles above are the mode-invariant part.) */\n /* (The solidify floor — the --glass-opacity \"component opacity\" dial — is composed INSIDE each sheer\n glass utility, at the element. Composing it here would bake :root's --glass-opacity into the value\n and silently ignore a per-element glass={{opacity}} — the var-composition gotcha again.) */\n /* Blur values — moderate by design; per-variant sizes (mode-invariant). */\n --blur: 2px;\n --blur-sm: 1px;\n --blur-lg: 8px;\n --blur-xl: 12px;\n --blur-frosted: 25px;\n /* Chakra reads as a THICK stone, so it diffuses more than crystal's near-clear 2px even though its\n body is more solid — the two axes are independent (how much light passes vs how scattered it is). */\n --blur-chakra: 6px;\n /* Crystal edge — identical in both modes, so it lives here with the anchors (the crystal floor +\n shadows are mode-varying: --glass-crystal-bg in the surfaces block below, shadows on :root/.dark). */\n --glass-crystal-border: oklch(100% calc(var(--glass-tint-c-hi) * 0.6) var(--glass-tint-h) / 0.3);\n /* Crystal gloss KNOBS — leaves only. The gloss itself (ink, the three gradients) and every stack that\n bakes it are composed in block C below, which also recomposes at each [data-gloss] scope.\n --glass-gloss-l / --glass-gloss-tint live in tokens.css (66 / 4.25) — do NOT re-declare here:\n engine.css imports AFTER tokens.css, so a duplicate at equal :root specificity silently wins and\n pure-CSS consumers get a stale gloss. */\n --glass-gloss-hue-span: 40; /* hue: degrees the iridescent sweep shifts up & down from the tint hue */\n /* hue: chroma of the iridescent stops, at the flavor's own L 74. Was 0.16, chosen believing an\n over-ceiling request is gamut-MAPPED down to the ceiling (\"holds 81% of that chroma\"). WebKit clips\n per channel instead (see --glass-gloss-c-max in tokens.css), so 0.16 pinned a channel on the\n tightest hues rather than delivering 81% of itself. 0.12 sits just under 0.1226 — aurora (h205) at\n L 74, the tightest across all 23 preset scopes (frescoes included) — so every hue in the sweep now\n renders what it asks for. */\n --glass-gloss-c: 0.12;\n --glass-gloss-hue-dir: 1; /* gloss direction (±1) — flips the TONAL streak diagonal (calc angle) + the HUE sweep order (span × dir); toggled in the crystal demo */\n /* The fresco slot, reset at every tint scope. The four fresco presets (frescoes.css, later in source\n order) point it at their multi-hue sheet; every other preset leaves it at `none`. Without the reset a\n single-hue preset scoped inside a fresco page (

on a muse page)\n inherited the page's sheet, and its crystal, chakra and opaque surfaces painted muse's sweep. */\n --glass-crystal-fresco: none;\n --blur-crystal: 2px;\n}\n\n/* B. MODE-DERIVED SURFACES — see the engine header above. Each tint-composing token is declared ONCE,\n parameterized by the mode knobs on bare :root/.dark; `.dark` in this selector keeps dark wrappers\n re-composing with their own knobs. */\n:root,\n.dark,\n[data-glass-tint] {\n /* Solid colored floor under the sheer gradient (see --glass-tint-a) — mode via --glass-wash-l/-c-mult. */\n --glass-tint-wash: oklch(var(--glass-wash-l) calc(var(--glass-tint-c) * var(--glass-wash-c-mult)) var(--glass-tint-h) / var(--glass-tint-a));\n /* Solid accent that tracks the FOREGROUND hue at a fixed vivid chroma — used by active controls\n (switch \"on\", slider range). The --accent-h/-c knob overrides apply in BOTH modes (the old dark\n twin dropped them — that was a bug). */\n --glass-accent: oklch(var(--glass-accent-l) var(--accent-c, var(--glass-accent-c)) var(--accent-h, var(--glass-fg-h)));\n /* Stained-glass — grayscale collapses the backdrop to pure LUMINANCE (akin to alpha: only lighter/\n darker survives), and the glass's OWN tint stack supplies the color — OKLCH-true to the theme, no\n hue-rotate approximation. --glass-stain-amount (0..1) sets how completely the backdrop's color is\n stripped; --glass-stain-invert (0..1, default 1) flips the surviving luminance so bright backdrop\n patches stop fighting dark tinted glass (set 0 to disable). The glass-stained utility and the switcher's Stained toggle both reference THIS token. */\n --glass-stain: grayscale(var(--glass-stain-amount, 1)) invert(var(--glass-stain-invert, 0));\n /* Colored glow for the `glow` prop — follows the foreground hue; override to recolor the halo. */\n --glass-glow: oklch(var(--glass-glow-lc) var(--glass-fg-h) / var(--glass-glow-a));\n /* Selected-control FILL (active tab, pressed toggle) — the surface twin of --active-shadow. Composed\n here, not on :root, because it reads the tint: a scoped data-glass-tint must re-resolve it, and it\n is why the chip tracks whichever theme the subtree carries. Mode knobs in tokens.css carry the two\n routes to the same look (light tints the fill, dark lets the surface show through a white lift). */\n --active-bg: oklch(var(--active-l) min(var(--glass-tint-c), var(--active-c-max)) var(--glass-tint-h) / var(--active-a));\n /* Solid PRIMARY ink (default Button, link text) — themed at the tint's OWN chroma (×1.2): selenite\n stays grey, moonstone whispers warm, jewels read clearly tinted. Lightness is the --primary-l mode knob\n (0.42 light / 0.78 dark). Chroma is min(tint × 1.2, --primary-c-max): the ×1.2 is intent — primary\n should read more saturated than the base tint — but uncapped it asked past the sRGB edge at five\n presets in light and two in dark, where the browser, not the theme, picked the delivered colour.\n Measured after capping: --primary-foreground holds Lc 88.1 light / 62.9 dark at the worst hue. */\n --primary: oklch(var(--primary-l) min(calc(var(--glass-tint-c) * 1.2), var(--primary-c-max)) var(--glass-fg-h));\n /* Ready accent-tier colors (hue angles from the anchors block; L is the mode envelope --harmonic-l). */\n --color-complement: oklch(var(--harmonic-l) var(--color-chroma) var(--hue-complement));\n --color-analogous-1: oklch(var(--harmonic-l) var(--color-chroma) var(--hue-analogous-1));\n --color-analogous-2: oklch(var(--harmonic-l) var(--color-chroma) var(--hue-analogous-2));\n --color-split-1: oklch(var(--harmonic-l) var(--color-chroma) var(--hue-split-1));\n --color-split-2: oklch(var(--harmonic-l) var(--color-chroma) var(--hue-split-2));\n --color-triad-1: oklch(var(--harmonic-l) var(--color-chroma) var(--hue-triad-1));\n --color-triad-2: oklch(var(--harmonic-l) var(--color-chroma) var(--hue-triad-2));\n /* Monochromatic — the harmony anchor hue at stepped lightness (a subtle, low-chroma ramp). */\n --mono-1: oklch(var(--mono-l-1) var(--mono-chroma) var(--harmony-h));\n --mono-2: oklch(var(--mono-l-2) var(--mono-chroma) var(--harmony-h));\n --mono-3: oklch(var(--mono-l-3) var(--mono-chroma) var(--harmony-h));\n /* Opaque glass — a fully solid, faintly tinted floor (no see-through) for the \"opaque\" style.\n --glass-opaque-l is the body-lightness knob (:root/.dark; the header L slider drives it; the floor\n AND AutoForeground's --foreground-opaque banding both read it, so they can't drift). */\n /* Chroma is CAPPED now (--glass-opaque-c-max, tokens.css). This floor sat at L 90.9 in light (it is\n L88 now, for the same reason) — near-white territory where the sRGB ceiling collapses — yet it was\n the one near-white surface\n multiplying RAW --glass-tint-c (×1.26) with no cap, while the sheet had --glass-tint-c-hi, chakra\n had --glass-chakra-c-max and the gloss had --glass-gloss-c-max. That made it the binding surface\n for 7 of the 12 jewels: it clipped first, and every preset's chroma budget was set by it. Capping\n it is what let the tints below be normalised to their hue's real capacity rather than to this\n floor's. Multiply-THEN-cap, matching --glass-gloss-ink. */\n /* The cap is scaled per mode (--glass-opaque-cap-scale): the per-preset caps are L88 ceilings, and the\n light floor now sits at L93, where they would overshoot. Composed here, not on :root, so a scoped\n preset's own cap is the one scaled. */\n --glass-opaque-bg: oklch(\n calc(var(--glass-opaque-l) * 1%)\n min(calc(var(--glass-tint-c) * var(--glass-opaque-c-scale)), calc(var(--glass-opaque-c-max, 0.3) * var(--glass-opaque-cap-scale, 1)))\n var(--glass-tint-h)\n );\n /* The same floor at the SOLIDIFY lightness/cap — the backing `glass` paints under every sheer\n material (see --glass-solidify in utilities.css). Split from --glass-opaque-bg because an opaque\n CARD and the backing under sheer glass are different surfaces with different jobs; tokens.css has\n the reasoning. Same scale, so a preset's tint still drives it; only the lightness and cap differ. */\n --glass-solidify-bg: oklch(\n calc(var(--glass-solidify-l, var(--glass-opaque-l)) * 1%)\n min(calc(var(--glass-tint-c) * var(--glass-opaque-c-scale)), var(--glass-solidify-c-max, var(--glass-opaque-c-max, 0.3))) var(--glass-tint-h)\n );\n /* Sheer glass sheet — hue/chroma from the tint vars, chroma scaled per stop so the highlights stay\n glassy and the body carries the color. Stop alphas ride the mode base --glass-sheet-a (+ fixed\n offsets; the top stop's offset is its own knob --glass-sheet-a1 — dark tapers it to 0.03);\n frosted = the same sheet lifted by --glass-frost-boost. */\n --glass-bg: linear-gradient(\n 135deg,\n oklch(100% calc(var(--glass-tint-c-hi) * 0.3) var(--glass-tint-h) / calc(var(--glass-sheet-a) + var(--glass-sheet-a1))) 0%,\n oklch(99% calc(var(--glass-tint-c-hi) * 0.5) var(--glass-tint-h) / calc(var(--glass-sheet-a) + 0.01)) 25%,\n oklch(97% calc(var(--glass-tint-c-hi) * 1) var(--glass-tint-h) / calc(var(--glass-sheet-a) + 0.02)) 50%,\n oklch(99% calc(var(--glass-tint-c-hi) * 0.5) var(--glass-tint-h) / var(--glass-sheet-a)) 75%,\n oklch(95% calc(var(--glass-tint-c-hi) * 1.3) var(--glass-tint-h) / calc(var(--glass-sheet-a) + 0.01)) 100%\n );\n --glass-frosted-bg: linear-gradient(\n 135deg,\n oklch(100% calc(var(--glass-tint-c-hi) * 0.3) var(--glass-tint-h) / calc(var(--glass-sheet-a) + var(--glass-frost-boost) + 0.04)) 0%,\n oklch(99% calc(var(--glass-tint-c-hi) * 0.5) var(--glass-tint-h) / calc(var(--glass-sheet-a) + var(--glass-frost-boost) + 0.01)) 25%,\n oklch(97% calc(var(--glass-tint-c-hi) * 1) var(--glass-tint-h) / calc(var(--glass-sheet-a) + var(--glass-frost-boost) + 0.02)) 50%,\n oklch(99% calc(var(--glass-tint-c-hi) * 0.5) var(--glass-tint-h) / calc(var(--glass-sheet-a) + var(--glass-frost-boost))) 75%,\n oklch(95% calc(var(--glass-tint-c-hi) * 1.3) var(--glass-tint-h) / calc(var(--glass-sheet-a) + var(--glass-frost-boost) + 0.01)) 100%\n );\n /* Very subtle white border — the lit edge of a cut sheet (alpha is the mode dial) */\n --glass-border: oklch(100% calc(var(--glass-tint-c-hi) * 0.5) var(--glass-tint-h) / var(--glass-border-a));\n /* Opaque CHROME edge — the shadcn --border/--input role tokens (dividers, ghost/input outlines, any\n bare `border-border`). Previously flat neutral (oklch(0.88 0 0) / oklch(0.3 0 0)), which made them\n the ONLY chrome in the theme that did not move with the tint: a ghost Button kept a grey edge under\n lapis while its `outline` sibling (border-foreground/20) tracked the theme. Lightness moves only in\n LIGHT mode (0.88 → --border-l 0.85) to buy chroma headroom; see the knob comment in tokens.css.\n Chroma is min(tint chroma, --border-c-max) — a CAP, deliberately not --glass-tint-c-hi and not a\n multiplier. --glass-tint-c-hi (0.025) is the NEAR-WHITE cap, sized for the L 95–100% sheet stops\n and crystal floor; borrowing it here spent only 66% of the ceiling on amethyst and 17% on peridot,\n which is why the edge still read grey. A flat multiplier fails the other way: lapis declares\n c 0.15, so any factor small enough to keep it in gamut pushes the 0.07 jewels below where they\n started. min() gives every preset its full declared chroma up to the ceiling floor and clamps only\n the ones that overreach — and leaves c 0 at exactly 0, so selenite stays the neutral theme it is.\n Declaring them HERE (rather than on :root) also puts them under scripts/check-gamut.mjs, which\n scrapes engine.css for both the calc() and min() chroma shapes — the suite went 20 → 24 surface/mode\n pairs when these landed, so a later retune of the caps is measured, not asserted.\n SURFACE hue (--glass-tint-h), not --glass-fg-h: an edge belongs to the surface it bounds, matching\n --glass-border / --glass-crystal-border. In a fresco that splits the two (Muse: surface 230,\n text 290) the border follows the panel, not the type. */\n --border: oklch(var(--border-l) min(var(--glass-tint-c), var(--border-c-max)) var(--glass-tint-h));\n --input: oklch(var(--input-l) min(var(--glass-tint-c), var(--border-c-max)) var(--glass-tint-h));\n /* The remaining shadcn SURFACE tokens — the last chroma-0 values in the theme. Same shape as --border\n for the same reason: a toggle's on-state, a hover wash and a sidebar panel all take their fill from\n these, so while they were flat neutrals those surfaces read near-white in light and near-black in\n dark in every preset. Each carries its OWN cap because the sRGB ceiling is a function of lightness\n and these sit from L 18 to L 98 — a shared cap would have to be the smallest of them (0.009, at the\n near-white sidebar) and would tint nothing. See the knobs in tokens.css. */\n --accent: oklch(var(--accent-bg-l) min(var(--glass-tint-c), var(--accent-bg-c-max)) var(--glass-tint-h));\n /* The hover wash as an IMAGE layer (glass-hover-accent). Translucent so the material still reads\n through it — a hover that paints a flat fill stops looking like glass. */\n --glass-hover-wash: color-mix(in oklch, var(--accent) 70%, transparent);\n --secondary: oklch(var(--secondary-l) min(var(--glass-tint-c), var(--secondary-c-max)) var(--glass-tint-h));\n --muted: oklch(var(--muted-l) min(var(--glass-tint-c), var(--muted-c-max)) var(--glass-tint-h));\n --sidebar: oklch(var(--sidebar-l) min(var(--glass-tint-c), var(--sidebar-c-max)) var(--glass-tint-h));\n /* Theme-aware brand gradient — follows --glass-fg-h, from the hue 3 ramp-steps out\n (8/side → step 360/17 ≈ 63.53°) to the base hue, at the mode envelope --gradient-l/-c + 0.5 alpha.\n (Axis/direction variants are computed in JS from lib/oklch-utils.ts — see the /components demo.) */\n --gradient: linear-gradient(\n 135deg,\n oklch(var(--gradient-l) var(--gradient-c) calc(var(--glass-fg-h) + 63.53) / 0.5) 0%,\n oklch(var(--gradient-l) var(--gradient-c) var(--glass-fg-h) / 0.5) 100%\n );\n /* Full-opacity twin of --gradient for gradient TEXT (background-clip: text) — no wash alpha so glyphs stay crisp. */\n --gradient-text: linear-gradient(\n 135deg,\n oklch(var(--gradient-l) var(--gradient-c) calc(var(--glass-fg-h) + 63.53)) 0%,\n oklch(var(--gradient-l) var(--gradient-c) var(--glass-fg-h)) 100%\n );\n /* High-contrast gradient text for printing ON a tinted theme surface: DARK tint-hue gradient in light\n mode, LIGHT one in .dark (--gradient-contrast-l/-c), so legibility comes from lightness. */\n --gradient-text-contrast: linear-gradient(\n 135deg,\n oklch(var(--gradient-contrast-l) var(--gradient-contrast-c) calc(var(--glass-fg-h) + 63.53)) 0%,\n oklch(var(--gradient-contrast-l) var(--gradient-contrast-c) var(--glass-fg-h)) 100%\n );\n /* (The crystal floor, --glass-crystal-bg, is in block C: the hue gloss flavor repaints it.) */\n /* ── Chakra — the step-cut gem surface ────────────────────────────────────────────────────────\n BODY: the stone itself. The one tint-composing token here, so it is the only one with a gamut\n budget — min() against --glass-chakra-c-max (not a multiplier) for the same reason --border uses\n one: lapis declares c 0.15 and would be gamut-mapped, while any multiplier small enough to hold it\n drags the 0.07 jewels below where they should sit. Caps are measured per mode against the tightest\n ceiling across all 17 presets, so every preset delivers 100% of its declared chroma. */\n --glass-chakra-bg: oklch(\n calc(var(--glass-chakra-l) * 1%) min(var(--glass-tint-c), var(--glass-chakra-c-max)) var(--glass-tint-h) /\n var(--glass-chakra-a)\n );\n /* (The chakra SURFACE STACK, --glass-chakra-stack-bg, is in block C: it bakes the gloss.) */\n /* THE CUT — four sides, two steps, as INSET SHADOWS rather than inset background layers.\n A background layer is always a RECTANGLE: background-size shrinks it but gives it no radius of its\n own, so an inset gradient paints square corners over a rounded surface — a card with hard-cornered\n rectangles stacked on it. box-shadow has no such problem; an inset shadow is the border box's own\n rounded rect, offset, so every band follows --radius at whatever value the element carries, for\n free and at any size.\n Each side is a pair. The step-1 band (offset --glass-chakra-step) paints OVER the step-2 band\n (offset twice that), so the visible result is two concentric bands per side: 0→N shows step 1\n composited on step 2, N→2N shows step 2 alone. That is the step cut, and because step 2 is\n literally the same colour at half alpha it cannot drift out of sync with step 1.\n Order matters — box-shadow paints first-listed on top, so every step-1 band precedes its step-2\n twin. Corners: adjacent bands overlap and composite rather than mitering to a hard 45° line, which\n is the trade for following the radius. On a gem that reads as the corner facet catching both\n neighbours, and it is what the existing --glass-shadow bevel already does at top and bottom. */\n --glass-chakra-facets:\n inset 0 var(--glass-chakra-step) 0 oklch(var(--shadow-highlight) / var(--glass-chakra-facet-t-a)),\n inset var(--glass-chakra-step) 0 0 oklch(var(--shadow-highlight) / var(--glass-chakra-facet-l-a)),\n inset calc(var(--glass-chakra-step) * -1) 0 0 oklch(var(--shadow-ink) / var(--glass-chakra-facet-r-a)),\n inset 0 calc(var(--glass-chakra-step) * -1) 0 oklch(var(--shadow-ink) / var(--glass-chakra-facet-b-a)),\n inset 0 calc(var(--glass-chakra-step) * 2) 0 oklch(var(--shadow-highlight) / calc(var(--glass-chakra-facet-t-a) * 0.5)),\n inset calc(var(--glass-chakra-step) * 2) 0 0 oklch(var(--shadow-highlight) / calc(var(--glass-chakra-facet-l-a) * 0.5)),\n inset calc(var(--glass-chakra-step) * -2) 0 0 oklch(var(--shadow-ink) / calc(var(--glass-chakra-facet-r-a) * 0.5)),\n inset 0 calc(var(--glass-chakra-step) * -2) 0 oklch(var(--shadow-ink) / calc(var(--glass-chakra-facet-b-a) * 0.5));\n /* Edge + depth: the standard drop plus the cut. --glass-shadow's own top/bottom bevel is left out —\n the facet bands ARE the bevel, and doubling them would flatten the step read. */\n --glass-chakra-border: oklch(100% calc(var(--glass-tint-c-hi) * 0.5) var(--glass-tint-h) / var(--glass-border-a));\n --glass-chakra-shadow: var(--glass-shadow), var(--glass-chakra-facets);\n --glass-chakra-shadow-hover: var(--glass-shadow-lg), var(--glass-chakra-facets);\n /* Precomposed background STACKS — the background-image compositions, single-sourced. The surface\n utilities consume them via --srf-* fallbacks (sheer utilities append the element-composed solidify\n layer underneath), and the [data-glass] style remaps below swap them wholesale. Live here so they\n re-resolve per tint scope and per mode wrapper. The crystal stack bakes the gloss into the\n background (the site-wide re-skin can't use ::before/::after — those need position:relative, which\n would break sticky/fixed surfaces). */\n --glass-wash-layer: linear-gradient(var(--glass-tint-wash), var(--glass-tint-wash));\n --glass-stack-bg: var(--glass-bg), var(--glass-wash-layer);\n /* Frosted rides the wash in the IMAGE stack (sheet + wash), leaving the background-color channel\n free for the veil floor. */\n --frosted-stack-bg: var(--glass-frosted-bg), var(--glass-wash-layer);\n /* (--crystal-stack-bg is in block C: it bakes the gloss.) */\n}\n\n/* C. GLOSS — the crystal highlight and everything that bakes it: the ink, the three gradients, the\n crystal floor, and the crystal + chakra stacks. Declared on every scope that can CHANGE it: :root and\n .dark (the mode knobs), [data-glass-tint] (the tint), and [data-gloss] (the flavor). The last is what\n lets `data-gloss` work below : these tokens are composed values, which bake where they are\n declared, so a [data-gloss] section that only re-pinned an input (as the flavor blocks used to) had\n its children inherit 's gradients and stacks pre-composed, and painted the page's gloss.\n THREE FLAVORS, chosen by two plain switches the [data-gloss] blocks in materials.css set to `1` or\n `initial` (unset = tonal):\n white → flat white specular (the classic glass shine) --glass-gloss-is-white: 1\n tonal → a tonally-close single-hue tint of the theme (--glass-gloss-l / --glass-gloss-tint)\n hue → iridescent: the highlight sweeps hues shifted ± --glass-gloss-hue-span around the tint\n hue, and repaints the crystal floor in the sweep --glass-gloss-is-hue: 1\n A switch selects nothing by itself: each flavor's variant (block C′ below) multiplies an alpha by it,\n so with the switch `initial` the variant is invalid at computed-value time and the var() fallback here,\n the tonal shape, applies. The switches are leaves, so they inherit, and a nested [data-gloss] sets both\n to pick its own flavor; a nested [data-glass-tint] recomposes from the inherited switches and keeps the\n page's flavor (it used to reset it to tonal). Each layer applies its own alpha. */\n:root,\n.dark,\n[data-glass-tint],\n[data-gloss] {\n /* The tonal ink as its own token (not inline in the fallback below) so check-gamut's scraper, which\n reads `--x: ` declarations, still sweeps it. */\n --glass-gloss-ink-tonal: calc(var(--glass-gloss-l, 94) * 1%)\n min(calc(var(--glass-tint-c) * var(--glass-gloss-tint, 2)), var(--glass-gloss-c-max, 0.3)) var(--glass-tint-h);\n --glass-gloss-ink: var(--glass-gloss-ink-white, var(--glass-gloss-ink-tonal));\n --glass-gloss-top: var(--glass-gloss-top-hue, linear-gradient(to bottom, oklch(var(--glass-gloss-ink, 100% 0 0) / 0.4), transparent 30%));\n --glass-gloss-streak: var(\n --glass-gloss-streak-hue,\n linear-gradient(calc(135deg * var(--glass-gloss-hue-dir, 1)), oklch(var(--glass-gloss-ink, 100% 0 0) / 0.15), transparent 50%)\n );\n --glass-gloss-glow: var(\n --glass-gloss-glow-hue,\n radial-gradient(circle at 50% 120%, oklch(var(--glass-gloss-ink, 100% 0 0) / 0.2), transparent 70%)\n );\n /* Crystal floor — clear glass; the alpha is the mode dial (--glass-crystal-bg-a).\n Crystal body. The lightness is a KNOB, not 100%, and that is the whole point: at L100 sRGB holds\n ZERO chroma, so this surface asked for 0.015 and delivered exactly none — crystal rendered pure\n white under every preset, in both modes (measured: #fffdff amethyst, #fffef4 moonstone,\n #fbffff lapis; RGB spread 2–11). check-gamut even reported it as \"no attainable chroma at all\"\n and skipped the pair rather than failing it.\n --glass-crystal-l is 96 because that is the lowest lightness at which the TIGHTEST of the 21 hues\n this theme uses (h268, lapis) can still carry the 0.015 this asks for: ceiling 0.0182, 1.22x\n headroom. L96.5 is the exact crossover at 1.06x, too thin given maxChromaFor already sits 3% inside\n the boundary to stop Safari clamping toward grey. The cost is small — at the 0.3 light-mode alpha\n the composite moves ~1.2 L, and 0.4 L in dark. */\n --glass-crystal-bg: var(\n --glass-crystal-bg-hue,\n oklch(calc(var(--glass-crystal-l) * 1%) calc(var(--glass-tint-c-hi) * 0.6) var(--glass-tint-h) / var(--glass-crystal-bg-a))\n );\n /* SURFACE STACKS: crystal and chakra bake the same gloss triple, so `data-gloss` white / tonal / hue\n reskin both, over a fresco slot and the tint wash. Nothing in the chakra stack is inset, so nothing\n has a corner of its own — the element's border-radius clips the whole stack (see the facet bands in\n block B). Baked into the background, not ::before/::after: the crystal PAGE style re-skins every\n surface, and pseudo layers would need position:relative, which breaks sticky/fixed surfaces. */\n --crystal-stack-bg:\n var(--glass-gloss-top), var(--glass-gloss-glow), var(--glass-gloss-streak), var(--glass-crystal-fresco, none), var(--glass-wash-layer);\n --glass-chakra-stack-bg:\n var(--glass-gloss-top), var(--glass-gloss-glow), var(--glass-gloss-streak), var(--glass-crystal-fresco, none), var(--glass-wash-layer);\n}\n\n/* C′. The FLAVOR VARIANTS block C falls back from, each valid only while its switch is `1`. Kept in a\n sibling block (same scopes) so scripts/check-gamut.mjs can model the hue variants at the hue flavor's\n own L74 (the [data-gloss=\"hue\"] block pins --glass-gloss-l there) without misreading block C's tonal\n ink at it.\n hue: each highlight sweeps (tint hue − span) → tint hue → (+ span), interpolated IN OKLCH so the arc\n passes through the base hue. Top leans to the low end, glow to the high end, streak spans both. The\n floor is replaced by a hue-carrying one at the gloss lightness (so Light deepens it): the default\n crystal floor sits near white, where chroma collapses, so the sweep would read over a white sheet. */\n:root,\n.dark,\n[data-glass-tint],\n[data-gloss] {\n --glass-gloss-ink-white: calc(var(--glass-gloss-is-white) * 100%) 0 0;\n --glass-gloss-top-hue: linear-gradient(\n to bottom in oklch,\n oklch(\n calc(var(--glass-gloss-l, 94) * 1%) var(--glass-gloss-c, 0.12)\n calc(var(--glass-tint-h) - var(--glass-gloss-hue-span, 40) * var(--glass-gloss-hue-dir, 1)) /\n calc(0.4 * var(--glass-gloss-is-hue))\n ),\n transparent 35%\n );\n --glass-gloss-streak-hue: linear-gradient(\n 115deg in oklch,\n oklch(\n calc(var(--glass-gloss-l, 94) * 1%) var(--glass-gloss-c, 0.12)\n calc(var(--glass-tint-h) - var(--glass-gloss-hue-span, 40) * var(--glass-gloss-hue-dir, 1)) /\n calc(0.32 * var(--glass-gloss-is-hue))\n )\n 0%,\n oklch(calc(var(--glass-gloss-l, 94) * 1%) var(--glass-gloss-c, 0.12) var(--glass-tint-h) / calc(0.12 * var(--glass-gloss-is-hue))) 45%,\n oklch(\n calc(var(--glass-gloss-l, 94) * 1%) var(--glass-gloss-c, 0.12)\n calc(var(--glass-tint-h) + var(--glass-gloss-hue-span, 40) * var(--glass-gloss-hue-dir, 1)) /\n calc(0.32 * var(--glass-gloss-is-hue))\n )\n 85%,\n transparent\n );\n --glass-gloss-glow-hue: radial-gradient(\n circle at 50% 120%,\n oklch(\n calc(var(--glass-gloss-l, 94) * 1%) var(--glass-gloss-c, 0.12)\n calc(var(--glass-tint-h) + var(--glass-gloss-hue-span, 40) * var(--glass-gloss-hue-dir, 1)) /\n calc(0.25 * var(--glass-gloss-is-hue))\n ),\n transparent 70%\n );\n --glass-crystal-bg-hue: oklch(\n calc(var(--glass-gloss-l, 94) * 1%) calc(var(--glass-tint-c) * 1.2) var(--glass-tint-h) /\n calc(0.4 * var(--glass-gloss-is-hue))\n );\n}\n/* ── Glass surface utilities (@utility — tree-shaken per consumer usage) ──────\n The material system: ONE structural `glass` utility + orthogonal axis classes (border, veil, sm/lg,\n gradient, glow, sheen). Materials live in materials.css as [data-material] token sets; page styles\n in styles.css remap the same --srf-* tokens. No recipe utilities, no !important. */\n\n/* The glow itself — a punchy two-layer colored halo, centralized so its strength is tunable in ONE\n place. `glass-glow` is the resting glow; `glass-glow-lg` (used on hover) intensifies it. Both set\n --glow-layer, which every glass box-shadow folds in, so the glow shows under EVERY glass style.\n Full mechanism + tuning + troubleshooting: docs/glow.md */\n@utility glass-glow {\n --glow-layer: 0 0 8px 1px var(--glass-glow), 0 0 18px 3px var(--glass-glow);\n}\n@utility glass-glow-lg {\n --glow-layer: 0 0 12px 2px var(--glass-glow), 0 0 28px 5px var(--glass-glow);\n}\n\n/* ── THE MATERIAL SYSTEM (Sistine 2.0) ────────────────────────────────────────\n ONE structural utility (`glass`) + orthogonal axis classes. The material (a token set) comes from\n [data-material] (materials.css) or the inherited [data-glass] page remap (styles.css); with\n neither, the --srf-* fallbacks render the default glass material. Axis utilities only SET custom\n props (or properties `glass` never touches), so Tailwind's emission order can never flip a result.\n (The legacy recipe utilities below coexist during the migration and die with it.) */\n\n/* Structural glass — background/filter/shadow plumbing only; borderless (add glass-border).\n Channels, outermost first:\n background-image = [--srf-hover state slot] + [--acc-gradient accent slot] + [--srf-bg-image material stack] + [solidify]\n background-color = --veil-floor (element veil) > --srf-bg-color (material floor) > transparent\n backdrop-filter = --srf-filter (material) > blur(--srf-blur > --blur) saturate(180%)\n box-shadow = --srf-shadow (material) > --srf-elev (size axis) > --glass-shadow, + glow\n The leading gradient slot is a no-op layer so `glass-gradient` composes over ANY material (same\n pattern as the fresco slot inside --crystal-stack-bg). Both no-op slots resolve to `none` rather\n than linear-gradient(transparent, transparent): `none` is a valid , holds the layer\n position identically, and paints nothing — where a transparent GRADIENT still goes through the\n gradient rasteriser (and its dither) to deliver zero pixels.\n The solidify floor (--glass-opacity dial) is composed HERE, at the element — never in a token\n context (the var-composition-resolves-at-declaration gotcha; see scripts/check-theme.mjs). The\n --srf-solidify slot lets a material DROP that layer when it is provably a no-op (the opaque material,\n and the opaque PAGE style, which sets it on its own scope so it reaches `@apply glass` surfaces that\n carry no `.glass` class); materials that want the normal floor pin the slot back to `initial`\n (guaranteed-invalid → the var() fallback composes --glass-solidify at the element). --veil-solidify\n leads the chain: glass-veil sets it (non-inheriting), because a VEILED surface paints the neutral\n veil floor in its background-colour channel, so the layer is real there whatever the slot says. */\n@utility glass {\n /* Fallback 0.7, not 0: every glass surface now sits on a 70% solidify floor unless a consumer dials\n it back. Measured before changing it, because AutoForeground does NOT read --glass-opacity — it\n bands text against the sheer floor, so a more solid surface than it modelled could have cost\n contrast. Body text goes 91.5 -> 86.5 Lc in light and 88.0 -> 85.8 in dark, both far above the\n 75 body floor (it still clears at a full 1.0). Set --glass-opacity: 0 for the old sheer default. */\n /* --glass-solidify-BG, not --glass-opaque-bg: the backing under sheer glass is its own surface now\n (engine.css / tokens.css). In dark the two resolve identically, so this is a light-mode change. */\n --glass-solidify-fill: color-mix(in oklch, var(--glass-solidify-bg, var(--glass-opaque-bg)) calc(var(--glass-opacity, 0.7) * 100%), transparent);\n --glass-solidify: linear-gradient(var(--glass-solidify-fill), var(--glass-solidify-fill));\n /* --srf-hover leads the stack because a hover/selection wash has to sit ON the material, not under it.\n `hover:bg-accent` sets background-COLOR, which is the bottom of the box — beneath all 14 image layers\n the glass stack paints, of which the solidify floor alone is 70% opaque. At most ~30% of the accent\n survived to the eye, so the hover read as the material's own colour (white in light mode) no matter\n what --accent was set to. No amount of tuning the token could fix that; it is a layer-order problem.\n Same no-op `none` slot pattern as --acc-gradient below it. */\n background-image:\n var(--srf-hover, none), var(--acc-gradient, none), var(--srf-bg-image, var(--glass-stack-bg)),\n var(--veil-solidify, var(--srf-solidify, var(--glass-solidify)));\n background-color: var(--veil-floor, var(--srf-bg-color, transparent));\n /* Author ONLY the standard backdrop-filter (no -webkit- twin: Lightning dedupes the pair keeping\n the PREFIXED one, which Chromium never aliased — the [no-webkit-twin] invariant guards this).\n The max() composes the diffuse FLOOR for adaptive surfaces. */\n backdrop-filter: var(--srf-filter, blur(max(var(--srf-blur, var(--blur)), var(--dif, 0px))) saturate(180%)) var(--srf-stain,);\n box-shadow: var(--srf-shadow, var(--srf-elev, var(--glass-shadow))), var(--glow-layer, 0 0 0 0 transparent);\n /* THE EDGE, resolved once per surface for everything that draws one (glass-border, the opaque page's\n uniform edge, the page outline). Precedence, strongest first: the element's own ink colour or\n rim/frame weight, then the PAGE OUTLINE (--glass-outline / --glass-outline-w, set on any ancestor;\n --glass-opaque-outline[-w] are the old opaque-only names, still read), then the material's own edge\n (--srf-border-color / --srf-border-w), then the plain glass hairline. Element-composed, so an outline\n set on a subtree resolves where it is read. */\n --srf-edge: var(--brd-ink, var(--glass-outline, var(--glass-opaque-outline, var(--srf-border-color, var(--glass-border)))));\n --srf-edge-w: var(--brd-w, var(--glass-outline-w, var(--glass-opaque-outline-w, var(--srf-border-w, 1px))));\n}\n\n/* Border axis — the material picks the color unless the element asks for ink (--brd-ink, the\n borderColor axis below); width resolves material pin (--srf-border-w, which itself reads the --brd-w\n weight slot) → weight slot directly (adaptive surfaces) → 1px hairline. */\n@utility glass-border {\n border: var(--srf-edge-w, 1px) solid var(--srf-edge, var(--glass-border));\n}\n/* Border WEIGHTS — token setters for the --brd-w slot (order-safe; same-element composition, like\n glass-sm's --srf-blur). Hairline is the unmarked default (1px; 0.5px under frosted); rim = 2px, frame = 4px. */\n@utility glass-border-rim {\n --brd-w: 2px;\n}\n@utility glass-border-frame {\n --brd-w: 4px;\n}\n/* Border COLOUR — ink. The material edge is a near-white rim at the material's alpha: it reads as cut\n glass on a dark page and as nothing at all on most light themes, at hairline, rim or frame weight\n alike, so a weight axis whose colour disappears went unused. Ink is the element's own text colour\n (--foreground, after any per-material text remap on this element), mixed toward transparent by\n --glass-border-ink-a: a visible line anywhere text is legible. Element-composed and registered\n non-inheriting (registrations.css), so a child border keeps its own material colour. */\n@utility glass-border-ink {\n --brd-ink: color-mix(in oklch, var(--foreground) calc(var(--glass-border-ink-a, 0.5) * 100%), transparent);\n}\n\n/* Diffuse axis — a READABILITY BLUR FLOOR (not a blur value): every translucent material computes\n blur(max(, --dif)), so crystal stays crystal until a text-dense surface opts in.\n Token-setter (order-safe); OPAQUE never reads the slot (--srf-filter: none), so the short-circuit\n is inherited — no GPU spend on solid floors. Tune the floor via --glass-diffuse. */\n@utility glass-diffuse {\n --dif: var(--glass-diffuse, 12px);\n}\n\n/* Veil axis — the legibility floor for read-through overlays (menus, tooltips, toasts).\n Composed AT THE ELEMENT so a scoped --glass-solid-a resolves here (check-theme invariant 2b).\n The veil rides the background-COLOR channel, under the material's image stack, and — being\n element-declared — beats any inherited --srf-bg-color remap. */\n/* Stained axis — REAL stained-glass optics: grayscale collapses the backdrop to pure luminance\n (lighter/darker only, akin to alpha), so the glass's OWN tint supplies every drop of color and\n whatever sits behind renders as tonal shades of the theme. One color-matrix op — free next to the\n blur. --glass-stain-amount (0..1) dials how completely the backdrop's color is stripped. */\n@utility glass-stained {\n --srf-stain: var(--glass-stain);\n}\n\n@utility glass-veil {\n --veil-floor: oklch(var(--glass-solid-l) 0 0 / var(--glass-solid-a));\n /* The veil keeps the solidify layer even where the material or page dropped it (--srf-solidify: none):\n those drops are only no-ops over the opaque floor, and a veiled surface paints the veil instead. */\n --veil-solidify: var(--glass-solidify);\n}\n\n/* THE PAGE BACKDROP LAYER — the fixed, full-viewport ground every sheer material blurs. Put this on the\n `fixed inset-0 -z-10` container each background component renders.\n It exists to force a compositing layer. `backdrop-filter` re-samples everything painted beneath it,\n and this layer sits at z-index -10 under the entire document; unpromoted, the compositor has to\n re-rasterise it against the scroll position every frame, which is where the scroll artifact on the\n sheer materials comes from. `opaque` is the ONE material that sets --srf-filter: none — and the one\n material that never showed the artifact, which is the correlation that points here.\n Measured on the deployed page, this element resolved will-change:auto / transform:none /\n contain:none — nothing was promoting it. Promoting is safe specifically because it has ZERO\n position:fixed descendants and no background-attachment:fixed; a transform/will-change would\n otherwise become their containing block and reparent them. Re-check that before adding children.\n will-change (rather than a translateZ hack) because the hint is honest here: this layer is on screen\n and being sampled for the whole session, so there is nothing to hint OFF later. */\n@utility glass-backdrop-layer {\n will-change: transform;\n}\n\n/* Size axes — blur + elevation tiers. These feed the GLASS material only: frosted/crystal/opaque\n (and page remaps) set --srf-filter/--srf-shadow wholesale, which flattens the tiers — matching\n the old system, where the [data-glass] styles flattened glass-surface-sm/-lg. */\n@utility glass-sm {\n --srf-blur: var(--blur-sm);\n --srf-elev: var(--glass-shadow-sm);\n}\n@utility glass-lg {\n --srf-blur: var(--blur-lg);\n --srf-elev: var(--glass-shadow-lg);\n}\n@utility glass-xl {\n --srf-blur: var(--blur-xl);\n --srf-elev: var(--glass-shadow-lg);\n}\n\n/* ── Gradient ACCENT — a brand wash ON a material, not a material. Fills the no-op --acc-gradient\n slot at the top of every `glass` background, so it composes over glass, frosted, crystal, and\n opaque alike. A token-setter (no property `glass` sets), so it's immune to utility emission order. */\n@utility glass-gradient {\n --acc-gradient: var(--gradient);\n}\n\n/* ── Hover/selection WASH — the interactive twin of glass-gradient. Fills the --srf-hover slot at the\n very top of the background stack, which is the only place a state colour is actually visible on a\n glass surface: `hover:bg-accent` writes background-COLOR, and every one of the material's image\n layers paints over it (solidify alone is 70% opaque), so the accent arrived at ~30% strength blended\n into the material and read as plain white on a light theme.\n The wash is deliberately translucent rather than a flat fill — glass that turns solid on hover stops\n looking like glass. 70% of --accent over the material keeps the wash, gloss and blur reading through\n it while still shifting the surface unmistakably toward the theme colour.\n Pair with `hover:bg-accent` rather than replacing it: the background-color still does the work on the\n NON-glass variants (outline/default paint no material, so nothing covers it there). */\n@utility glass-hover-accent {\n &:hover {\n --srf-hover: linear-gradient(var(--glass-hover-wash), var(--glass-hover-wash));\n }\n}\n\n/* Sheen — the OPT-IN hover shimmer (the gloss slides across on hover). Self-contained (highlight + diagonal\n + glow + the 200%/hover shift), so it works on any glass surface AND, layered on glass-crystal (which\n already has the static gloss), its ::before/::after override crystal's to add the motion. Pseudo-element\n based → position:relative; avoid on sticky/fixed surfaces. */\n@utility glass-sheen {\n position: relative;\n\n &::before,\n &::after {\n content: \"\";\n position: absolute;\n inset: 0;\n border-radius: inherit;\n pointer-events: none;\n }\n\n &::before {\n background: var(--glass-gloss-top), var(--glass-gloss-streak);\n background-size: 200% 200%;\n background-position: 0% 0%;\n opacity: 0.7;\n transition: background-position 0.3s ease;\n }\n\n &::after {\n background: var(--glass-gloss-glow);\n opacity: 0.8;\n }\n\n &:hover::before {\n background-position: 100% 100%;\n }\n}\n\n/* Namespaced: a bare `shimmer` is a global name, and a consumer's own @keyframes shimmer (or another\n library's) replaced this one for everyone, whichever stylesheet loaded last. Animates `translate`, not\n `transform`: Tailwind v4's `-translate-x-full` (the skeleton's resting position) writes the `translate`\n property, which COMPOSES with an animated transform rather than being replaced by it, so the sweep ran\n from -200% to 0% and parked the highlight on top of the skeleton. Animating the same property replaces\n the resting value while running, and with the animation off it falls back to that resting value. */\n@keyframes sistine-shimmer {\n from {\n translate: -100% 0;\n }\n to {\n translate: 100% 0;\n }\n}\n\n/* ── Reduced motion ───────────────────────────────────────────────────────────\n CONTRIBUTING says \"Respect prefers-reduced-motion\"; the pattern scenes did and nothing else did.\n This closes that, but NOT with the blunt global `*, ::before, ::after { animation: none !important }`\n reset — that would also flatten the pattern scenes' own carefully scoped rules and every enter/exit\n transition in the library.\n\n The treatment differs by what the animation actually IS, because the preference is about MOTION, not\n about animation in general:\n - shimmer → translation across a surface. Real motion, purely decorative: stopped. The skeleton\n still reads as a skeleton from its base fill.\n - spin → rotation. Real motion, but it is the ONLY signal that a loader is alive, so freezing\n it reads as \"hung\" rather than \"calm\". Slowed hard instead of stopped.\n - pulse → opacity only. Nothing moves, so there is nothing to trigger a vestibular response.\n Left alone deliberately; it also carries meaning (the input-otp caret is a blink).\n Unlayered on purpose: Tailwind's generated utilities sit inside @layer utilities, and an unlayered\n rule outranks any layered one regardless of order, so this needs neither !important nor a specificity\n hack. */\n@media (prefers-reduced-motion: reduce) {\n .animate-\\[sistine-shimmer_2s_infinite\\],\n .before\\:animate-\\[sistine-shimmer_2s_infinite\\]::before {\n animation: none;\n }\n\n .animate-spin {\n animation-duration: 3s;\n }\n}\n/* ── Materials — [data-material] token sets + gloss flavors ───────────────────\n Four materials, one attribute. Each block declares the FULL --srf-* pin set ON the element, so an\n explicit material always beats an inherited [data-glass] page remap (element-declared custom props\n beat inherited ones). One material per element is structural: attributes can't stack, so there is\n no !important anywhere in the system.\n NOTE these are inherited custom properties — a data-material element also acts as a SCOPED default\n for adaptive `glass` descendants (the mechanism DialogContent's old data-glass hack hand-rolled).\n Pin a child back with its own data-material.\n Import order: after tokens.css — [data-gloss=\"hue\"] re-pins --glass-gloss-l and the crystal shadows,\n which tokens.css declares on :root/.dark at equal specificity, so source order decides on . */\n\n[data-material=\"glass\"] {\n /* Solidify slot: `initial` = guaranteed-invalid, so `glass` falls back to composing --glass-solidify\n AT THE ELEMENT (never inherited pre-composed — the var-composition gotcha). Pinned so an opaque\n PAGE remap can never leak its dropped layer into an explicit material. */\n --srf-solidify: initial;\n --srf-bg-image: var(--glass-stack-bg);\n --srf-bg-color: transparent;\n /* Composed on the element, so the glass-sm/glass-lg axes still resolve here (same-element\n setter/reader is safe — the composition gotcha only bites pre-composed inherited values). */\n --srf-filter: blur(max(var(--srf-blur, var(--blur)), var(--dif, 0px))) saturate(180%);\n --srf-border-color: var(--glass-border);\n --srf-border-w: var(--brd-w, 1px);\n --srf-shadow: var(--srf-elev, var(--glass-shadow));\n}\n\n/* Frosted — the tint wash rides the IMAGE stack (--frosted-stack-bg = sheet + wash), leaving the\n background-color channel free for the veil floor. */\n[data-material=\"frosted\"] {\n /* Solidify slot: `initial` = guaranteed-invalid, so `glass` falls back to composing --glass-solidify\n AT THE ELEMENT (never inherited pre-composed — the var-composition gotcha). Pinned so an opaque\n PAGE remap can never leak its dropped layer into an explicit material. */\n --srf-solidify: initial;\n --srf-bg-image: var(--frosted-stack-bg);\n --srf-bg-color: transparent;\n --srf-filter: blur(max(var(--blur-frosted), var(--dif, 0px))) saturate(200%);\n --srf-border-color: var(--glass-frosted-border);\n --srf-border-w: var(--brd-w, 0.5px);\n --srf-shadow: var(--glass-frosted-shadow);\n}\n\n/* Crystal — gloss BAKED into the background stack (sticky/fixed-safe; no pseudo-elements, no\n position:relative). --crystal-stack-bg carries the --glass-crystal-fresco pass-through. The hover\n sparkle is a plain token swap; box-shadow transitions on the real property. No saturate(),\n matching the old variant. */\n[data-material=\"crystal\"] {\n /* Solidify slot: `initial` = guaranteed-invalid, so `glass` falls back to composing --glass-solidify\n AT THE ELEMENT (never inherited pre-composed — the var-composition gotcha). Pinned so an opaque\n PAGE remap can never leak its dropped layer into an explicit material. */\n --srf-solidify: initial;\n --srf-bg-image: var(--crystal-stack-bg);\n --srf-bg-color: var(--glass-crystal-bg);\n --srf-filter: blur(max(var(--blur-crystal), var(--dif, 0px)));\n --srf-border-color: var(--glass-crystal-border);\n --srf-border-w: var(--brd-w, 1px);\n --srf-shadow: var(--glass-crystal-shadow);\n transition: box-shadow 0.3s ease;\n}\n[data-material=\"crystal\"]:hover {\n --srf-shadow: var(--glass-crystal-shadow-hover);\n}\n/* Crystal text tiers — the baked gloss locally LIGHTENS the title zone (worst in dark mode), so text\n inside crystal remaps to the -crystal set AutoForeground bands against the glossed crystal surface.\n VEILED crystal is excluded: the veil floor is exactly what the normal tiers are banded for. */\n[data-material=\"crystal\"]:not(.glass-veil, [data-surface=\"veil\"]) {\n --foreground: var(--foreground-crystal);\n --foreground-soft: var(--foreground-soft-crystal);\n --foreground-strong: var(--foreground-strong-crystal);\n --foreground-ui: var(--foreground-ui-crystal);\n --muted-foreground: var(--muted-foreground-crystal);\n}\n\n/* Chakra — the step-cut gem: a translucent tinted body with a two-step facet frame on all four edges,\n sitting between crystal (near-clear) and opaque (solid). Like crystal it bakes everything into the\n background stack and the shadow — no pseudo-elements, so it is safe on sticky/fixed surfaces, and it\n carries the same gloss triple, so `data-gloss` white / tonal / hue reskin it too.\n The cut lives entirely in --srf-shadow (see --glass-chakra-facets in engine.css). Nothing here is an\n inset BACKGROUND layer, deliberately: background layers are rectangles with no radius of their own,\n so an inset gradient stacks square corners on a rounded surface. Inset shadows follow --radius.\n Because the stack is plain full-bleed layers, the sheer-glass axes work untouched — glass-diffuse\n raises --dif into the blur below, and the --glass-opacity solidify floor composes under the stack\n exactly as it does for crystal. */\n[data-material=\"chakra\"] {\n /* Solidify slot: `initial` = guaranteed-invalid, so `glass` falls back to composing --glass-solidify\n AT THE ELEMENT (never inherited pre-composed — the var-composition gotcha). Pinned so an opaque\n PAGE remap can never leak its dropped layer into an explicit material. */\n --srf-solidify: initial;\n --srf-bg-image: var(--glass-chakra-stack-bg);\n --srf-bg-color: var(--glass-chakra-bg);\n --srf-filter: blur(max(var(--blur-chakra), var(--dif, 0px)));\n --srf-border-color: var(--glass-chakra-border);\n --srf-border-w: var(--brd-w, 1px);\n --srf-shadow: var(--glass-chakra-shadow);\n transition: box-shadow 0.3s ease;\n}\n[data-material=\"chakra\"]:hover {\n --srf-shadow: var(--glass-chakra-shadow-hover);\n}\n/* Chakra text tiers — content sits on the body, whose lightness (--glass-chakra-l) is its own dial and\n can sit well away from the page's. Same reasoning as opaque/crystal; veiled chakra is excluded\n because the veil floor is exactly what the NORMAL tiers are banded for. */\n[data-material=\"chakra\"]:not(.glass-veil, [data-surface=\"veil\"]) {\n --foreground: var(--foreground-chakra);\n --foreground-soft: var(--foreground-soft-chakra);\n --foreground-strong: var(--foreground-strong-chakra);\n --foreground-ui: var(--foreground-ui-chakra);\n --muted-foreground: var(--muted-foreground-chakra);\n}\n\n/* Opaque — solid tinted floor, nothing to blur through. --glass-opaque-outline stays settable on any\n ancestor: it's composed HERE, on the element, so it resolves where it's read. The floor can be\n LIGHT while the page is dark, so the foreground tiers remap to the -opaque set AutoForeground\n bands against that floor (inherits into the card's content). */\n[data-material=\"opaque\"] {\n /* DROP the solidify layer: it is `--glass-opaque-bg` at --glass-opacity alpha painted directly over a\n background-COLOR of the same --glass-opaque-bg at alpha 1 — i.e. C over C, which composites to C for\n ANY alpha. Measured across tints and both modes it moves every channel by exactly 0. So the layer was\n one full gradient rasterisation (and one dither pass) for provably zero pixels, on the one material\n with no backdrop-filter behind it to mask that noise. Restored when the surface is VEILED (glass-veil\n sets --veil-solidify, which `glass` reads first) — there the background-color is the neutral veil\n floor, not --glass-opaque-bg, so the layer is real (dropping it there would shift up to 31/255 in\n light and 42/255 in dark). */\n --srf-solidify: none;\n /* The WASH, and only the wash. Every other material's stack ends in --glass-wash-layer, the solid film\n of the preset's colour that carries the tint over any backdrop; opaque used to drop it with the\n sheet, so its floor had to carry the tint alone and the opaque card read as a darker, greyer cousin\n of the glass beside it (in dark mode most of all: amethyst glass L44 C0.18, opaque L36 C0.11). With\n the wash, opaque is the glass stack at full opacity: within 0.7 L of its glass siblings in light and\n 2–4 L above them in dark, where no darker page shows through. The wash is one flat colour (both\n stops equal), so it rasterises without the dither the sheet's gradient caused (below).\n NO sheet — --glass-bg is a 5-stop\n gradient whose total range, composited over this floor, is 2–4 sRGB codes across an entire card in\n light and 5–7 in dark: one code per ~100–200px, far too shallow to draw cleanly in 8-bit, so the\n rasteriser dithers rather than bands. On every other material a real backdrop-filter supplies blurred\n page content that masks that noise; opaque sets --srf-filter: none, so there is nothing behind it and\n the dither is the only variation on the card — read as grain, worst over text.\n The sheet's GRADIENT was therefore invisible, but its uniform LIFT was not (+1.7 L light, +4.8 L dark),\n so it is folded into --glass-opaque-l rather than dropped: see tokens.css. AutoForeground reads that\n same token, so the -opaque text tiers re-band against the new floor automatically. */\n /* The fresco slot leads, as in the crystal and chakra stacks: unset (`none`) on a single-hue preset, the\n fresco's own multi-hue gradient on muse / sistine / aurora / gloaming, which opaque used to lose\n entirely and render as one flat anchor hue. Its stops have a real range, unlike the sheet's 2–7\n codes, so it rasterises without the dither the sheet was dropped for. */\n --srf-bg-image: var(--glass-crystal-fresco, none), var(--glass-wash-layer);\n --srf-bg-color: var(--glass-opaque-bg);\n --srf-filter: none;\n /* The material's own edge. A page outline (--glass-outline / -w) overrides it in `glass`'s --srf-edge,\n the same as for every other material. */\n --srf-border-color: var(--glass-border);\n --srf-border-w: var(--brd-w, 1px);\n --srf-shadow: var(--glass-shadow);\n /* Veiled overlays INSIDE an opaque element compute a fully solid floor — same guarantee the opaque\n PAGE style makes (menus/toasts must stay legible on a solid ground). */\n --glass-solid-a: 1;\n --foreground: var(--foreground-opaque);\n --foreground-soft: var(--foreground-soft-opaque);\n --foreground-strong: var(--foreground-strong-opaque);\n --foreground-ui: var(--foreground-ui-opaque);\n --muted-foreground: var(--muted-foreground-opaque);\n}\n\n/* ── Gloss flavors ────────────────────────────────────────────────────────────\n data-gloss on or ANY element swaps the crystal (and chakra) gloss for that subtree. Default\n (unset) = tonal. These blocks set LEAVES only — the two flavor switches, plus the hue flavor's own\n lightness and shadows — and engine.css block C recomposes the gloss on every [data-gloss] element from\n them. That split is what makes a scoped `
` work: a composed value bakes\n where it is declared, so a block here that declared one would hand its children 's gloss.\n Each block sets BOTH switches, so a nested flavor overrides an ancestor's instead of combining with it.\n They compose with [data-material=\"crystal\"] AND the [data-glass=\"crystal\"] page style, in both modes.\n Known edge: hue's pinned --glass-gloss-l and bevel-less crystal shadows are mode-twinned leaves that a\n white/tonal section nested INSIDE a hue scope inherits (no mode-aware reset exists for them here). */\n[data-gloss=\"white\"] {\n --glass-gloss-is-white: 1; /* the ink becomes the flat white specular, 100% 0 0 */\n --glass-gloss-is-hue: initial;\n}\n[data-gloss=\"tonal\"] {\n /* Explicit tonal (also the default) — both switches off, so it can override a white/hue ancestor. */\n --glass-gloss-is-white: initial;\n --glass-gloss-is-hue: initial;\n}\n[data-gloss=\"hue\"] {\n --glass-gloss-is-white: initial;\n --glass-gloss-is-hue: 1;\n /* Hue OWNS its lightness rather than sharing the tonal twin. The two flavors want opposite things\n from the same number: tonal wants the brightest value the surface allows (97 in light) so it reads\n as a specular, while hue needs chroma headroom — its stops ask --glass-gloss-c 0.16, and the sRGB\n ceiling at L 97 is 0.0136, so the iridescent sweep would deliver 8% of its colour and wash out to\n near-white exactly where it is supposed to be most vivid.\n 74 is mode-invariant on purpose: it holds 81% of that chroma, and it lands on the right side of\n both surfaces anyway (+18.4 L over the dark floor, −9.0 over the light one — for an iridescent\n sweep, being SEEN matters more than which direction it moves). The demo's flavor switcher already\n seeded 74 at runtime for this reason; this makes a no-JS consumer get it too.\n \"Holds 81% of that chroma\" assumed the browser gamut-MAPS an over-ceiling request down to the\n ceiling. Measured in WebKit it CLIPS instead (see --glass-gloss-c-max in tokens.css), so 0.16 did\n not deliver 81% of itself — it drove the tightest hues to a pinned channel. 0.12 sits just under\n 0.1240, the L-74 ceiling for turquoise (h190) and the tightest of the presets, so the sweep now\n renders every hue it asks for. scripts/check-gamut.mjs reads this 74 to score the variants. */\n --glass-gloss-l: 74;\n /* Kill the white bevel: the crystal shadow carries a white inset highlight, which would sit on top of\n the swept hue. (The white floor is replaced in engine.css block C′.) */\n --glass-crystal-shadow: var(--glass-shadow);\n --glass-crystal-shadow-hover: var(--glass-shadow-lg);\n}\n/* ── Site-wide style remaps + vendor cascade fixes ──────────────────────────── */\n\n/* Keep react-day-picker from painting over the glass background */\n@layer utilities {\n .glass[class*=\"rdp\"],\n .glass .rdp,\n .glass .rdp-month {\n background-color: transparent !important;\n }\n}\n\n/* ── Site-wide glass style: token remaps ─────────────────────────────────────\n data-glass on (frosted | crystal | opaque) re-skins every ADAPTIVE surface (`glass` with no\n data-material) by remapping the --srf-* tokens it reads. An element-level [data-material] pins its\n own tokens and beats these by cascade physics — reinforced by the :where() wrapper, which drops\n every remap (incl. its cross-product legs) to specificity (0,0,0), so a co-located data-material on\n a tint-scope element always wins. The cross-product legs keep the swapped stacks re-resolving inside\n scoped tints, status toasts included (a bare [data-glass] rule would bake 's tint into\n descendants' surfaces). Veiled surfaces (glass-veil) keep their element-composed floor.\n The ELEMENT-level rules further down (text tiers, the opaque edge) need to know which elements are\n surfaces. `.glass` says so for the utility class; an element that wears the material through\n `@apply glass` has no class, so it opts in with a `data-surface` attribute (`data-surface=\"veil\"`\n when it also applies glass-veil). Everything ABOVE is inherited tokens and needs no marker. */\n:where([data-glass=\"frosted\"], [data-glass=\"frosted\"] [data-glass-tint], [data-glass=\"frosted\"][data-glass-tint]) {\n --srf-bg-image: var(--frosted-stack-bg);\n /* NO --srf-bg-color wash here: the wash already rides the IMAGE stack (--frosted-stack-bg), same\n as the frosted material — a color-channel copy double-washed tinted presets and stole the veil\n floor's channel. */\n --srf-filter: blur(max(var(--blur-frosted), var(--dif, 0px))) saturate(200%);\n --srf-border-color: var(--glass-frosted-border);\n --srf-border-w: var(--brd-w, 0.5px);\n --srf-shadow: var(--glass-frosted-shadow);\n}\n:where([data-glass=\"crystal\"], [data-glass=\"crystal\"] [data-glass-tint], [data-glass=\"crystal\"][data-glass-tint]) {\n /* Gloss rides inside --crystal-stack-bg (baked — sticky/fixed-safe). Shimmer stays the opt-in\n glass-sheen. No saturate(), matching the material. */\n --srf-bg-image: var(--crystal-stack-bg);\n --srf-bg-color: var(--glass-crystal-bg);\n --srf-filter: blur(max(var(--blur-crystal), var(--dif, 0px)));\n --srf-border-color: var(--glass-crystal-border);\n --srf-shadow: var(--glass-crystal-shadow);\n}\n/* Crystal page: ADAPTIVE glass (no explicit material, not veiled) gets the -crystal text tiers, same\n as the material remap in materials.css — the baked gloss lightens title zones there too. */\n[data-glass=\"crystal\"] :where(.glass, [data-surface]):not([data-material]):not(.glass-veil, [data-surface=\"veil\"]) {\n --foreground: var(--foreground-crystal);\n --foreground-soft: var(--foreground-soft-crystal);\n --foreground-strong: var(--foreground-strong-crystal);\n --foreground-ui: var(--foreground-ui-crystal);\n --muted-foreground: var(--muted-foreground-crystal);\n}\n/* Chakra page — every adaptive surface becomes a step-cut gem. The cut rides --srf-shadow, so it\n follows each surface's own border-radius with nothing to keep in sync. */\n:where([data-glass=\"chakra\"], [data-glass=\"chakra\"] [data-glass-tint], [data-glass=\"chakra\"][data-glass-tint]) {\n --srf-bg-image: var(--glass-chakra-stack-bg);\n --srf-bg-color: var(--glass-chakra-bg);\n --srf-filter: blur(max(var(--blur-chakra), var(--dif, 0px)));\n --srf-border-color: var(--glass-chakra-border);\n --srf-shadow: var(--glass-chakra-shadow);\n}\n/* Chakra page: adaptive glass (no explicit material, not veiled) takes the -chakra text tiers, same\n as the material remap in materials.css. */\n[data-glass=\"chakra\"] :where(.glass, [data-surface]):not([data-material]):not(.glass-veil, [data-surface=\"veil\"]) {\n --foreground: var(--foreground-chakra);\n --foreground-soft: var(--foreground-soft-chakra);\n --foreground-strong: var(--foreground-strong-chakra);\n --foreground-ui: var(--foreground-ui-chakra);\n --muted-foreground: var(--muted-foreground-chakra);\n}\n/* Opaque — solid tinted panels for adaptive surfaces. --glass-solid-a: 1 makes any veiled overlay\n (glass-veil) compute a FULLY solid floor here too, so menus/toasts stay legible on an opaque page. */\n:where([data-glass=\"opaque\"], [data-glass=\"opaque\"] [data-glass-tint], [data-glass=\"opaque\"][data-glass-tint]) {\n /* Floor + the tint wash, no sheet — same as [data-material=\"opaque\"] in materials.css: the wash is what\n makes opaque the glass stack at full opacity rather than a greyer cousin of it, and the sheet stays\n out because with --srf-filter: none nothing masks the dither of its 2–7 code gradient. */\n --srf-bg-image: var(--glass-crystal-fresco, none), var(--glass-wash-layer);\n --srf-bg-color: var(--glass-opaque-bg);\n --srf-filter: none;\n --srf-shadow: var(--glass-shadow);\n /* Same provable no-op as [data-material=\"opaque\"]: --glass-opaque-bg sits in the background-COLOR\n channel, so the solidify layer would paint that colour over itself. A TOKEN here (it used to be an\n element rule keyed on `.glass`), so it reaches an `@apply glass` surface too, which kept the light\n solidify backing at 70% over the opaque floor and read nearly white. An explicit material pins the\n slot back (`initial`), and a veiled surface restores the layer with --veil-solidify. */\n --srf-solidify: none;\n}\n/* --glass-solid-a CANNOT ride in the :where() block above. :where() is specificity ZERO by design (so a\n page style never beats an explicit material), and every other declaration up there survives that\n because nothing else declares them — they are material SLOTS with no competing value. This one is\n different: tokens.css declares --glass-solid-a on :root, which is (0,1,0), so the :where() version\n lost every time and the opaque page never actually got its solid floor. Measured: --glass-solid-a\n resolved to 0.65 on under [data-glass=\"opaque\"], identical to every other page style.\n The visible symptom is any VEILED surface on an opaque page staying translucent while the solid\n panels around it go opaque — menus, toasts and popovers, which is exactly what the block above says\n this is for. A bare attribute selector is (0,1,0), which ties :root and wins on source order because\n globals.css imports styles.css after tokens.css. It is a plain literal, so it simply inherits into\n the tint / toast scopes and needs none of their re-resolution. */\n[data-glass=\"opaque\"] {\n --glass-solid-a: 1;\n}\n/* Opaque page: element-level touches for ADAPTIVE surfaces only (a `.glass` or `[data-surface]` element\n with no explicit material — an explicit frosted/crystal card keeps its translucency AND its normal\n foreground). Declared ON the element because (a) the page outline is settable on any ancestor and\n must resolve where it's read, and (b) the foreground remap must not leak into explicit materials.\n The uniform EDGE applies to every adaptive panel, veiled or not — a veiled card under the opaque page\n still reads as a solid panel and needs the same outline. */\n[data-glass=\"opaque\"] :where(.glass, [data-surface]):not([data-material]) {\n --srf-border-color: var(--glass-border);\n --srf-border-w: var(--brd-w, 1px);\n /* Paint the border HERE, not just the tokens: borderless roles (Card default) never render\n glass-border, so without this only bordered roles showed the outline — \"some cards get it,\n some don't\". Under the opaque page every solid panel takes the uniform edge; bordered elements\n resolve the same vars, so nothing double-draws. */\n border: var(--srf-edge-w, 1px) solid var(--srf-edge, var(--glass-border));\n}\n\n/* ── The page OUTLINE — every material ───────────────────────────────────────\n data-glass-outline on (or any ancestor) gives every glass surface below it an edge in the\n theme accent: glass, frosted, crystal, chakra and opaque alike, bordered roles and borderless ones,\n which is what the opaque page already does for its own panels. --glass-outline recolours it (on its\n own, without the attribute, it recolours only the edges surfaces already draw) and --glass-outline-w\n sets the weight (1 / 2 / 4px = hairline / rim / frame); an element's own border=\"rim\" or \"frame\" and\n borderColor=\"ink\" still win. :is(), not :where(), so this ties the opaque page's edge rule on\n specificity and wins by coming after it. */\n[data-glass-outline] :is(.glass, [data-surface]) {\n border: var(--srf-edge-w, 1px) solid var(--brd-ink, var(--glass-outline, var(--glass-opaque-outline, var(--glass-accent))));\n}\n/* The FOREGROUND remap is :not(.glass-veil) — the same carve-out crystal and chakra make, and the same\n one --veil-solidify makes for the solidify layer on this page style. A veiled card does not paint\n --glass-opaque-bg: glass-veil moves the background-COLOR channel to the neutral --veil-floor, so its\n text sits on the veil surface the BASE tokens are banded for, not on the opaque floor. Remapping it\n here handed those cards ink solved for a surface they do not have — visible as a hue jump, since the\n opaque floor is the one surface with nothing composited over it and so sits at the raw --glass-tint-h\n while every sheer surface lands a few degrees off it (measured on the /colors panel: 22.0 vs 14.6).\n Splitting the rule is what keeps the edge treatment while dropping the foreground one. */\n[data-glass=\"opaque\"] :where(.glass, [data-surface]):not([data-material]):not(.glass-veil, [data-surface=\"veil\"]) {\n --foreground: var(--foreground-opaque);\n --foreground-soft: var(--foreground-soft-opaque);\n --foreground-strong: var(--foreground-strong-opaque);\n --foreground-ui: var(--foreground-ui-opaque);\n --muted-foreground: var(--muted-foreground-opaque);\n}\n/* ── Glass tint presets ─────────────────────────────────────────────────────\n Set data-glass-tint on any element (commonly ) to recolor the glass in that subtree.\n Most presets just move the tint hue/chroma; \"sistine\" is a bespoke multi-hue fresco (gilded\n gold → terracotta flesh tones → azure sky), echoing the Sistine Chapel ceiling.\n Wash + all tint-derived tokens re-resolve per-scope via the :root,[data-glass-tint] split above. */\n/* Selenite — the neutral stone: no chroma and no wash, so the glass is the plain clear sheet. The tint\n values equal the engine default's hue with the colour switched off; the block exists so a CSS-only\n consumer (no tint switcher) gets selenite's colour-wheel anchor, which a hue-less theme needs. */\n[data-glass-tint=\"selenite\"] {\n --glass-tint-h: 250;\n --glass-tint-c: 0;\n --glass-tint-a: 0;\n --harmony-h: 0;\n}\n[data-glass-tint=\"sapphire\"] {\n --glass-tint-h: 255;\n --glass-tint-c: 0.075;\n --glass-tint-a: 0.19;\n --glass-wash-l: 63%;\n --glass-opaque-c-max: 0.056;\n}\n[data-glass-tint=\"aventurine\"] {\n --glass-tint-h: 158;\n --glass-tint-c: 0.076;\n --glass-tint-a: 0.54;\n --glass-wash-l: 88%;\n --glass-opaque-c-max: 0.195;\n}\n[data-glass-tint=\"amethyst\"] {\n --glass-tint-h: 300;\n --glass-tint-c: 0.101;\n --glass-tint-a: 0.15;\n --glass-wash-l: 55%;\n --glass-opaque-c-max: 0.065;\n}\n[data-glass-tint=\"rose\"] {\n --glass-tint-h: 8;\n --glass-tint-c: 0.095;\n --glass-tint-a: 0.17;\n --glass-wash-l: 64%;\n --glass-opaque-c-max: 0.062;\n}\n[data-glass-tint=\"amber\"] {\n --glass-tint-h: 75;\n --glass-tint-c: 0.062;\n --glass-tint-a: 0.38;\n --glass-wash-l: 81%;\n --glass-opaque-c-max: 0.096;\n}\n[data-glass-tint=\"carnelian\"] {\n --glass-tint-h: 38;\n --glass-tint-c: 0.081;\n --glass-tint-a: 0.19;\n --glass-wash-l: 67%;\n --glass-opaque-c-max: 0.062;\n}\n[data-glass-tint=\"peridot\"] {\n --glass-tint-h: 128;\n --glass-tint-c: 0.083;\n --glass-tint-a: 0.54;\n --glass-wash-l: 92%;\n --glass-opaque-c-max: 0.218;\n}\n[data-glass-tint=\"turquoise\"] {\n --glass-tint-h: 190;\n --glass-tint-c: 0.057;\n --glass-tint-a: 0.54;\n --glass-wash-l: 90%;\n --glass-opaque-c-max: 0.144;\n}\n[data-glass-tint=\"aquamarine\"] {\n --glass-tint-h: 215;\n --glass-tint-c: 0.053;\n --glass-tint-a: 0.5;\n --glass-wash-l: 82%;\n --glass-opaque-c-max: 0.091;\n}\n[data-glass-tint=\"tourmaline\"] {\n --glass-tint-h: 342;\n --glass-tint-c: 0.106;\n --glass-tint-a: 0.18;\n --glass-wash-l: 67%;\n --glass-opaque-c-max: 0.079;\n}\n[data-glass-tint=\"lapis\"] {\n /* Lapis lazuli IS the deep saturated ultramarine, and at plain jewel chroma it reads as just another\n blue next to sapphire. It used to buy that depth by declaring c 0.15 — far past the sRGB ceiling —\n and letting the browser clip. That DID deliver a deep blue, but by accident rather than by design:\n clipping chops channels, which drops LIGHTNESS, and the saturation came from landing darker.\n Measured, oklch(58% 0.375 268) renders L48.1 C0.2941 h271 — a colour nobody wrote down.\n The wash lightness below asks for that colour on purpose instead. h268's chroma ceiling PEAKS at\n L46 (0.2979) and collapses to 0.1293 by L74, so the shared 72/58 wash was sitting on the downslope;\n at L46 the same depth is available inside the gamut. oklch(45% 0.291 268) renders L44.9 C0.2909\n h267.9 — the same deep ultramarine, hue exact, nothing pinned.\n So lapis is no longer an exception to anything: it is the clearest case of the rule the whole\n palette now follows — put the wash where the hue can actually hold chroma, then ask for it. */\n --glass-tint-h: 268;\n --glass-tint-c: 0.085;\n --glass-tint-a: 0.13;\n --glass-wash-l: 46%;\n --glass-opaque-c-max: 0.055;\n}\n[data-glass-tint=\"goldstone\"] {\n --glass-tint-h: 22;\n --glass-tint-c: 0.094;\n --glass-tint-a: 0.17;\n --glass-wash-l: 63%;\n --glass-opaque-c-max: 0.06;\n}\n/* ── Per-mode wash ALPHA ──────────────────────────────────────────────────────\n Alpha slides the surface along a straight line from the material floor toward the wash colour, so\n how much a preset can take is set by how far its wash sits from THAT MODE's floor (L36.4 dark /\n L88 light). A wash near the floor barely moves the surface and tolerates a lot; a wash far from it\n crosses the mid-tone band where no text polarity reaches the body floor, and tolerates little.\n Because every wash now sits at its hue's chroma PEAK, dark-peaking hues (lapis L46, amethyst L55)\n sit near the DARK floor and take high alpha there, while light-peaking hues (peridot L92, turquoise\n L90) sit near the LIGHT floor and take high alpha there. The two columns are near mirrors.\n Each value is HALF that mode's measured headroom, so every preset keeps body text >= 75 Lc in both\n modes with room to spare. A single shared alpha cannot serve both ends: at 0.55 everywhere, five\n presets lose dark-mode text and lapis/amethyst lose light-mode text.\n The switcher no longer inlines --glass-tint-a for a named preset (see applyTint), so THESE are the\n values that render — nothing shadows them.\n Every night block carries two selectors: `.dark[data-glass-tint=\"x\"]` for a preset on the same element\n as `.dark` (normally ), and `.dark [data-glass-tint=\"x\"]` for a preset scoped to an element INSIDE\n a dark page (`
`), which the compound form never matched, so a scoped\n preset in dark mode used to render its DAY values. check-theme's [night-scope] keeps them paired. */\n.dark[data-glass-tint=\"rose\"],\n.dark [data-glass-tint=\"rose\"] {\n --glass-tint-a: 0.49;\n}\n.dark[data-glass-tint=\"goldstone\"],\n.dark [data-glass-tint=\"goldstone\"] {\n --glass-tint-a: 0.5;\n}\n.dark[data-glass-tint=\"carnelian\"],\n.dark [data-glass-tint=\"carnelian\"] {\n --glass-tint-a: 0.45;\n}\n.dark[data-glass-tint=\"amber\"],\n.dark [data-glass-tint=\"amber\"] {\n --glass-tint-a: 0.34;\n}\n.dark[data-glass-tint=\"peridot\"],\n.dark [data-glass-tint=\"peridot\"] {\n --glass-tint-a: 0.28;\n}\n.dark[data-glass-tint=\"aventurine\"],\n.dark [data-glass-tint=\"aventurine\"] {\n --glass-tint-a: 0.29;\n}\n.dark[data-glass-tint=\"turquoise\"],\n.dark [data-glass-tint=\"turquoise\"] {\n --glass-tint-a: 0.28;\n}\n.dark[data-glass-tint=\"aquamarine\"],\n.dark [data-glass-tint=\"aquamarine\"] {\n --glass-tint-a: 0.31;\n}\n.dark[data-glass-tint=\"sapphire\"],\n.dark [data-glass-tint=\"sapphire\"] {\n --glass-tint-a: 0.48;\n}\n.dark[data-glass-tint=\"lapis\"],\n.dark [data-glass-tint=\"lapis\"] {\n --glass-tint-a: 0.54;\n}\n.dark[data-glass-tint=\"amethyst\"],\n.dark [data-glass-tint=\"amethyst\"] {\n --glass-tint-a: 0.54;\n}\n.dark[data-glass-tint=\"tourmaline\"],\n.dark [data-glass-tint=\"tourmaline\"] {\n --glass-tint-a: 0.46;\n}\n[data-glass-tint=\"moonstone\"] {\n /* Moonstone — a warm, very-low-chroma off-white glass (reads as cream, not yellow). A normal translucent\n tint: texture-preserving and light-text in both modes, just a desaturated warm hue. Anchors its harmony\n wheel at 0° (like selenite) so accents come out a colorful set, not a warp of the near-neutral hue. */\n --glass-tint-h: 75;\n --glass-tint-c: 0.047;\n --glass-tint-a: 0.18;\n --harmony-h: 0;\n /* Gloss chroma, moonstone's own. The global --glass-gloss-tint is 4.25 — sized for the JEWELS, where a\n 0.07–0.1 tint wants a vivid gem highlight. Moonstone declares 0.047 precisely so it \"reads as cream,\n not yellow\" (above), and 4.25× asks for 0.1998.\n --glass-gloss-c-max (tokens.css) is what stops that becoming a clipped primary, and in LIGHT it does\n the whole job: 0.013 caps both 0.1998 and 0.047 to the same value, so this override changes nothing\n there. It earns its place in DARK, where the cap is a roomy 0.11 — enough that the multiplier would\n still put moonstone's highlight at 2.3× its declared chroma, i.e. amber on a stone that is supposed\n to be pale. 1× holds the specular at the tint's own chroma and lets its LIGHTNESS do the work. */\n --glass-gloss-tint: 1;\n /* Moonstone daytime body lightness. Fitted for the retired opaque sheet (tokens.css) on moonstone's OWN\n floor rather than the global pair: this cream is already near-white, where a near-white sheet moved it\n far less than it moved the standard L90 floor. dE 0.0002 — indistinguishable from the old surface. */\n --glass-opaque-l: 94.5;\n /* NO pinned --glass-opaque-bg: the floor must stay DERIVED from --glass-opaque-l (engine), because the\n lightness slider / stored per-mode L land as an INLINE --glass-opaque-l that shadows this block — a\n literal pin would freeze the paint while AutoForeground keeps banding text against the token (light\n text on cream). Was c-scale 1 — the value that made the derived color exactly the original cream when\n the sheet still painted over it; 0.92 is that same cream now that the sheet is gone (it desaturated as\n well as lightened, so both knobs carry part of it). */\n --glass-opaque-c-scale: 0.92;\n}\n.dark[data-glass-tint=\"moonstone\"],\n.dark [data-glass-tint=\"moonstone\"] {\n /* Moonstone night. The wash sits a few points above the standard 58% (warmth without glare; h75\n carries more real luminance per OKLCH point than the cool tints, so pale-warm washes read brighter\n than their numbers). AutoForeground reads BOTH wash knobs live (+ the [moonstone-sync] mirrored\n fallback), so page text and per-card dark-cream text re-band automatically.\n\n The FLOOR is what you actually see: at the default opacity every surface — cards, inputs, the nav\n pill — paints --glass-opaque-bg, and the wash only tints what shows through. It used to derive\n oklch(0.8 0.047 75), which is a sand/tobacco tan, so moonstone night read as brown rather than as\n the pale silvery stone it is named for. Lightness alone does not fix that: h75 is warm, so every\n lightness along it lands somewhere in the tan family. Cutting the CHROMA is what moves it off that\n family. Light mode is untouched (it keeps c-scale 1 at L94, where the same hue reads as cream).\n\n L52, down from a cream L84.9 — moonstone night is a DARK stone now, and that is forced rather than\n preferred. This floor feeds two surfaces at once: opaque cards paint it directly, and it composites\n at --glass-opacity over the dark veil to become the floor sheer glass sits on. Both have to clear\n the body floor, and for h75 nothing in L55–L84 reaches it in EITHER text polarity — that band is the\n mid-tone dead zone. A cream L84.9 put the opaque card just above it (Lc 77.1) but dragged the sheer\n surface to L65, dead centre, where body text topped out at 65.4 Lc and the preset had to be pinned\n as a known exception. Going lighter cannot fix it: the veil is 30% of the composite, which caps the\n solidify floor at L75.6 no matter how light this value gets, so the sheer surface can never climb\n out the top. Only the bottom exit is reachable. L52 is the PALEST value where both surfaces clear:\n sheer body text lands 80.0 Lc (was 65.4) and the opaque card 82.9 (was 77.1), and it also restores\n --foreground-strong, which could not reach its 90 target on the cream floor either. Still stone —\n oklch(0.52 0.021 75) is #70675c, a warm grey — and still well clear of the jewels' L36.4. */\n --glass-wash-l: 64%;\n --glass-wash-c-mult: 2;\n /* Fitted for the retired opaque sheet, like the daytime floor above and likewise not the global dark\n pair: moonstone night is a LIGHT floor, so the near-white sheet barely moved it. dE 0.0002.\n The backing under sheer glass stays at that L52; the opaque card sits three points lower (49) for the\n same reason every dark opaque card does (tokens.css): it carries the wash now, with no page showing\n through it. */\n --glass-solidify-l: 52;\n --glass-opaque-l: 49;\n --glass-opaque-c-scale: 0.44;\n /* Static -opaque tier baselines. These flipped polarity with the floor: on the old cream L84.9 they were\n dark warm literals, and on an L52 stone they have to be light or SSR's first paint is dark-on-dark.\n Values are what AutoForeground itself solves for this surface, so the mounted refine is a no-op. */\n --foreground-opaque: oklch(1 0 75);\n --foreground-soft-opaque: oklch(0.9 0.05 75);\n --foreground-strong-opaque: oklch(1 0 75);\n --foreground-ui-opaque: oklch(0.91 0.05 75);\n --muted-foreground-opaque: oklch(0.97 0.02 75);\n /* Chakra needs the SAME treatment opaque gets above, and did not have it: chakra was added after this\n block was written, so it kept the global dark body (L28) and rendered oklch(0.28 0.046 75) = #36250c,\n a dark brown — the one surface where moonstone still read as tobacco rather than stone. Matching\n opaque's pair — now L51, chroma x0.45 → 0.021, landing on #6d6559, the same stone as the opaque\n floor one point above it.\n The hue stays 75 deliberately. Cutting CHROMA is what moves this off the tan family; lightness alone\n does not, and a cool hue would contradict the warm cream light mode keeps at L94. */\n --glass-chakra-l: 51;\n --glass-chakra-c-max: 0.021;\n /* Same reasoning as the -opaque baselines above, and flipped with them: an L51 body needs LIGHT text. */\n --foreground-chakra: oklch(1 0 75);\n --foreground-soft-chakra: oklch(0.9 0.05 75);\n --foreground-strong-chakra: oklch(1 0 75);\n --foreground-ui-chakra: oklch(0.91 0.05 75);\n --muted-foreground-chakra: oklch(0.96 0.03 75);\n}\n\n/* Semantic surfaces (Alert info/success/warning/destructive; destructive Button/Badge; status toasts,\n which the Toaster stamps from sonner's data-type — see TOAST_TINT in components/ui/sonner.tsx) render\n data-glass-tint=\"\" — one block per status, joining the grouped engine re-resolution above so\n --glass-bg / wash re-resolve at the alert/toast. */\n[data-glass-tint=\"info\"] {\n --glass-tint-h: 255;\n --glass-tint-c: 0.075;\n --glass-tint-a: 0.18;\n --glass-wash-l: 63%;\n}\n[data-glass-tint=\"success\"] {\n --glass-tint-h: 150;\n --glass-tint-c: 0.088;\n --glass-tint-a: 0.18;\n --glass-wash-l: 87%;\n}\n[data-glass-tint=\"warning\"] {\n --glass-tint-h: 85;\n --glass-tint-c: 0.063;\n --glass-tint-a: 0.18;\n --glass-wash-l: 84%;\n}\n[data-glass-tint=\"destructive\"] {\n --glass-tint-h: 25;\n --glass-tint-c: 0.094;\n --glass-tint-a: 0.18;\n --glass-wash-l: 63%;\n}\n/* FRESCOES (muse / sistine / aurora / gloaming): each block's --glass-tint-h/c/a MUST mirror its\n GlassTintSwitcher preset (components/glass-tint-switcher.tsx). The switcher INLINES those onto ,\n shadowing these blocks on any page with the switcher mounted — so these are the static-consumer fallback.\n Keep them identical, or the demo and a static page render different surfaces. (--glass-bg gradients and\n --glass-fg-h are CSS-only — no preset counterpart.) */\n\n/* Muse — the neon city: cyan → violet → magenta → pink → amber, ONE definition for both modes. Its old\n day sweep (a pale blue → cyan → teal cityscape) lives on in Aurora, so Muse no longer needs a night\n twin: no .dark block, and the light/dark toggle changes the page under the sheet, not the sheet.\n\n Lightness is shared because the stops are already bright (L 78–85), so they read on either page.\n Stop 2's chroma is 0.095 because h290 at L80 has an sRGB ceiling of 0.1052; the 0.17 it once shipped\n with was browser-gamut-mapped (and rendered differently in Safari than in Chrome). 0.095 is 90% of\n its own ceiling, the rule every stop follows.\n\n One alpha has to hold on BOTH pages, and the dark opaque card is the bind: there the sheet's bright\n stops lift an L33 floor toward the text, and at the old day alphas (0.28–0.32) light text needed pure\n white, past the tonal clip, so the ink lost the theme's hue. The sheet sits at 3/4 of the way from\n the old night alphas to the old day ones, and the wash makes the room: L66 at 0.10 (was L76 at 0.16)\n lands a light surface where it was (≈ L90) and a dark one 3.5 L darker. Measured: body 86.6 Lc light,\n 92.4 dark; the dark opaque ink stays tinted.\n\n No pinned --glass-opaque-bg: the opaque floor DERIVES from --glass-opaque-l like every jewel, so the\n switcher's Lightness slider moves a Muse card and AutoForeground models the floor it actually paints\n (the old pins were h288 violet at L91 / L34, against a model of h230 at L93 / L33.4). */\n[data-glass-tint=\"muse\"] {\n --glass-tint-h: 230;\n --glass-fg-h: 290; /* text/accent anchor: violet, the sheet's second stop */\n --glass-tint-c: 0.056;\n --glass-tint-a: 0.1;\n --glass-wash-l: 66%;\n --glass-bg: linear-gradient(\n 135deg in oklch,\n oklch(85% 0.1 222 / 0.3) 0%,\n oklch(80% 0.095 290 / 0.28) 22%,\n oklch(78% 0.2 326 / 0.26) 45%,\n oklch(79% 0.12 350 / 0.26) 70%,\n oklch(84% 0.12 74 / 0.28) 100%\n );\n --glass-frosted-bg: linear-gradient(\n 135deg in oklch,\n oklch(85% 0.1 222 / 0.42) 0%,\n oklch(80% 0.095 290 / 0.4) 22%,\n oklch(78% 0.2 326 / 0.38) 45%,\n oklch(79% 0.12 350 / 0.38) 70%,\n oklch(84% 0.12 74 / 0.4) 100%\n );\n --glass-crystal-fresco: var(--glass-bg);\n}\n\n/* Sistine — the signature: a painterly fresco of four jewels (amber → rose → sapphire → aventurine,\n in that order), interpolated `in oklch` so the warm→cool transitions stay vivid instead of\n averaging to grey. Border + crystal + accent pick up the rose base via the tint vars. */\n[data-glass-tint=\"sistine\"] {\n --glass-tint-h: 75; /* surface: mirrors the Sistine switcher preset */\n --glass-fg-h: 18; /* text/accent anchor: crimson (hand-selected) — hue 18 reads as deep red, not the rose at h8 */\n --glass-tint-c: 0.062;\n --glass-tint-a: 0.16;\n --glass-wash-l: 81%;\n --glass-bg: linear-gradient(\n 135deg in oklch,\n oklch(90% 0.11 75 / 0.34) 0%,\n oklch(86% 0.11 8 / 0.3) 33%,\n oklch(86% 0.11 255 / 0.3) 66%,\n oklch(87% 0.11 158 / 0.32) 100%\n );\n --glass-frosted-bg: linear-gradient(\n 135deg in oklch,\n oklch(90% 0.11 75 / 0.46) 0%,\n oklch(86% 0.11 8 / 0.42) 33%,\n oklch(86% 0.11 255 / 0.42) 66%,\n oklch(87% 0.11 158 / 0.44) 100%\n );\n /* Crystal composes its own background (not --glass-bg), so hand it the fresco explicitly. */\n --glass-crystal-fresco: var(--glass-bg);\n}\n.dark[data-glass-tint=\"sistine\"],\n.dark [data-glass-tint=\"sistine\"] {\n --glass-bg: linear-gradient(\n 135deg in oklch,\n oklch(86% 0.11 75 / 0.22) 0%,\n oklch(82% 0.11 8 / 0.2) 33%,\n oklch(82% 0.11 255 / 0.2) 66%,\n oklch(83% 0.11 158 / 0.22) 100%\n );\n --glass-frosted-bg: linear-gradient(\n 135deg in oklch,\n oklch(86% 0.11 75 / 0.32) 0%,\n oklch(82% 0.11 8 / 0.3) 33%,\n oklch(82% 0.11 255 / 0.3) 66%,\n oklch(83% 0.11 158 / 0.32) 100%\n );\n --glass-crystal-fresco: var(--glass-bg);\n}\n\n/* Aurora — northern lights: spring-green → teal → violet, interpolated `in oklch` so the cool sweep\n stays vivid. Border + crystal + accent pick up the teal base via the tint vars.\n DAY was authored at L 90/89/84 — within ~5 L of the L-95 page it composites onto — so after the\n alpha only 2.2 L of separation survived and the bands read as haze rather than bands. Night works\n because its stops sit 60+ L above an L-20 page and land 13.6 L clear of it. The fix is that\n separation, not more colour: day already delivered MORE chroma than night (spread 26/29/19 against\n 14/15/13) and still looked washed out, because colour without lightness structure reads as fog.\n Stops now sit at L 76.3/74.7/71.8, which also buys chroma headroom, so each takes ~90% of its OWN\n hue's sRGB ceiling rather than one shared number — h292 violet was clipped to 65% at L 84 and is\n fully in gamut here. Composited: spread 48/40/38 (was 26/29/19), separation 8.0 L (was 2.2).\n How far this could go is bound by TEXT, not by taste. AutoForeground models a fresco surface from\n the tint wash (--glass-tint-c/-a), never from this gradient literal, so it does not re-band when\n these stops move and the contrast has to hold statically. --foreground-soft (the L-40 heading tier)\n is the weakest pair on a light surface and sets the floor: at a composite of L 84 it measures 59.5\n and fails, so the stops are placed to land no darker than ~L 86. Worst measured here is 63.5.\n Frosted gets its OWN alphas rather than the old ~1.37x boost over --glass-bg: with stops this much\n darker, that multiplier drove the violet composite to L 83.4 and soft to 58.4. These land all three\n near L 86.4 instead. The authored shape is unchanged — green, then teal, then violet darkest. */\n/* Aurora now sweeps the retired Muse day sweep's hues — blue 235 → cyan 205 → teal-green 155 — instead of the old\n green → cyan → violet. LIGHTNESS is untouched, because it is not a style choice here: the day stops\n sit at L 71.8–76.3 to satisfy the --foreground-soft ≥60 Lc bind described above, and that sweep's own\n L 91–94 is precisely the band these were moved away from when day read as haze.\n\n CHROMA had to be re-derived, it could not travel with the hues. Each stop is 90% of that hue's OWN\n sRGB ceiling at its lightness, which is the rule the previous stops already followed (all three\n measured exactly 90%). A flat number cannot survive a hue change: the old 0.175 is 90% of green's\n ceiling at L76.3 but 127% of blue's, so carrying it over would have clipped stop 1 outright.\n\n Night is re-derived the same way, which also fixes a pre-existing clip: its violet stop asked 0.13\n against a 0.1068 ceiling at h292/L80 — 122% — so that stop has been gamut-mapped by the browser all\n along. */\n[data-glass-tint=\"aurora\"] {\n --glass-tint-h: 205; /* the sweep's midpoint, was 178 */\n --glass-tint-c: 0.054;\n --glass-tint-a: 0.16;\n --glass-wash-l: 86%;\n --glass-bg: linear-gradient(\n 135deg in oklch,\n oklch(76.3% 0.124 235 / 0.4) 0%,\n oklch(74.7% 0.111 205 / 0.38) 50%,\n oklch(71.8% 0.155 155 / 0.38) 100%\n );\n --glass-frosted-bg: linear-gradient(\n 135deg in oklch,\n oklch(76.3% 0.124 235 / 0.46) 0%,\n oklch(74.7% 0.111 205 / 0.43) 50%,\n oklch(71.8% 0.155 155 / 0.37) 100%\n );\n --glass-crystal-fresco: var(--glass-bg);\n}\n.dark[data-glass-tint=\"aurora\"],\n.dark [data-glass-tint=\"aurora\"] {\n --glass-bg: linear-gradient(135deg in oklch, oklch(86% 0.071 235 / 0.22) 0%, oklch(85% 0.127 205 / 0.2) 50%, oklch(80% 0.173 155 / 0.22) 100%);\n --glass-frosted-bg: linear-gradient(\n 135deg in oklch,\n oklch(86% 0.071 235 / 0.32) 0%,\n oklch(85% 0.127 205 / 0.3) 50%,\n oklch(80% 0.173 155 / 0.32) 100%\n );\n --glass-crystal-fresco: var(--glass-bg);\n}\n\n/* Gloaming — dusk: gold → rose → indigo, interpolated `in oklch` so the warm→cool fade keeps its\n glow. Night keeps the warm glow; day uses a dusk-rose anchor (the warm hue reads muddy as light-mode text). */\n[data-glass-tint=\"gloaming\"] {\n --glass-tint-h: 32; /* surface: mirrors the Gloaming switcher preset; text shares it (--glass-fg-h defaults to it) */\n --glass-tint-c: 0.089;\n /* 0.12, down from 0.16: with text solved against the gradient's hardest stop (the L78 violet, over a\n dusk-rose wash that already sits dark), the light opaque card could not reach the 75 Lc body floor\n even in pure black (74.6). 0.14 reached it only in black; 0.12 keeps the ink tinted. */\n --glass-tint-a: 0.12;\n --glass-wash-l: 64%;\n --glass-bg: linear-gradient(135deg in oklch, oklch(89% 0.12 62 / 0.32) 0%, oklch(84% 0.14 350 / 0.3) 50%, oklch(78% 0.13 278 / 0.3) 100%);\n --glass-frosted-bg: linear-gradient(135deg in oklch, oklch(89% 0.12 62 / 0.44) 0%, oklch(84% 0.14 350 / 0.42) 50%, oklch(78% 0.13 278 / 0.42) 100%);\n --glass-crystal-fresco: var(--glass-bg);\n}\n.dark[data-glass-tint=\"gloaming\"],\n.dark [data-glass-tint=\"gloaming\"] {\n --glass-tint-h: 32; /* night keeps the warm glow; day uses the dusk-rose above */\n --glass-bg: linear-gradient(135deg in oklch, oklch(86% 0.12 62 / 0.22) 0%, oklch(80% 0.14 350 / 0.2) 50%, oklch(74% 0.13 278 / 0.22) 100%);\n --glass-frosted-bg: linear-gradient(135deg in oklch, oklch(86% 0.12 62 / 0.32) 0%, oklch(80% 0.14 350 / 0.3) 50%, oklch(74% 0.13 278 / 0.32) 100%);\n --glass-crystal-fresco: var(--glass-bg);\n}\n", + "content": "/*\n Sistine theme — aggregator. The theme is authored as focused partials under app/theme/; Tailwind v4\n bundles these relative imports itself (no postcss-import needed). ORDER MATTERS:\n - package imports come first (CSS requires @import before other rules),\n - engine.css must precede presets.css / frescoes.css (a preset's --glass-tint-h overrides the\n engine default by SOURCE ORDER at equal specificity),\n - inside tokens.css, .dark comes after :root (same-element ties resolve by source order).\n Consumers install a FLATTENED single-file build of this graph (registry/theme/globals.css, generated\n by scripts/build-theme.mjs) — run `bun run registry:check` after editing any partial and commit\n registry/theme + public/r. Site-only CSS (the Geist font mapping) lives in app/site.css, which imports\n this file, so nothing the documentation site defines leaks into the shipped theme.\n*/\n@import \"tailwindcss\";\n@import \"tw-animate-css\";\n/* ── Registrations — @custom-variant + @property pins for JS-only tokens ──────\n Loaded first so the dark variant and typed properties exist before anything composes them. */\n\n/* Class-based dark mode, v4-standard form: matches the `.dark` element ITSELF and its descendants, so\n consumers can toggle `.dark` on any wrapper (not just / next-themes), at zero specificity\n (`:where`) so `dark:` overrides by cascade order like every other variant. */\n@custom-variant dark (&:where(.dark, .dark *));\n\n/* Composable, NON-inheriting glow layer: the `glow` effect/prop sets it, and every glass box-shadow\n folds in `var(--glow-layer, …)` so the glow rides INSIDE each material's and page style's shadow\n (they set --srf-shadow; the glow is appended after it) instead of being replaced by it. inherits:false\n keeps a glowing surface from lighting up its glass children. */\n@property --glow-layer {\n syntax: \"*\";\n inherits: false;\n}\n\n/* The veil floor, NON-inheriting for the same reason as the glow: `glass-veil` sets it on the element\n and `glass` paints it as that element's background-color. Inherited, a plain glass surface nested in\n a veiled menu, popover, tooltip, select or card painted its PARENT's veil as its own floor (under the\n Opaque page style: flat white in light, black in dark). A child without a veil of its own now falls\n back to its material's --srf-bg-color, as it would anywhere else. */\n@property --veil-floor {\n syntax: \"*\";\n inherits: false;\n}\n/* Its twin for the solidify layer (see @utility glass-veil): a veiled parent must not restore the layer\n on a child that dropped it. */\n@property --veil-solidify {\n syntax: \"*\";\n inherits: false;\n}\n/* The ink border colour (glass-border-ink): an inked panel must not ink the borders of the glass inside it. */\n@property --brd-ink {\n syntax: \"*\";\n inherits: false;\n}\n\n/* Prebaked-frost offsets (hooks/use-glass-backdrop.ts writes these per bubble). Non-inheriting so a\n nested bubble never samples its parent's screen position for a frame before its own sync. */\n@property --gx {\n syntax: \"\";\n inherits: false;\n initial-value: 0px;\n}\n@property --gy {\n syntax: \"\";\n inherits: false;\n initial-value: 0px;\n}\n\n/* Gradient-text tokens are consumed ONLY from JS inline styles (components/gradient-text.tsx) — no CSS\n rule references them, so Lightning CSS's optimizer would silently drop them (the same failure mode as\n the harmonic tokens below). Registered here to pin them. DO NOT REMOVE. */\n@property --gradient-text {\n syntax: \"*\";\n inherits: true;\n}\n@property --gradient-text-contrast {\n syntax: \"*\";\n inherits: true;\n}\n/* ── Harmonic color registrations — compacted one-liners (biome's CSS formatter is excluded for this\n file; see biome.json). WHY these exist: the comment below. */\n\n/* ── Harmonic token registration ──────────────────────────────────────────────────────────────\n The harmonic tokens (--harmony-h, --hue-*, --color-*, --mono-*, defined in engine.css) are\n consumed ONLY from JS / inline styles (HarmonicSwatchesDemo, AutoForeground) — never referenced by\n a CSS rule. Tailwind v4's Lightning CSS optimizer constant-folds + dead-code-eliminates any custom\n property no CSS rule uses, which silently dropped this ENTIRE subgraph from the COMPILED stylesheet\n (so every `oklch(L C var(--hue-*))` / `var(--color-*)` resolved to nothing at runtime). Registering\n them via @property marks them as first-class typed API the optimizer must preserve. DO NOT REMOVE. */\n@property --harmony-h { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --color-chroma { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --mono-chroma { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-base { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-complement { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-analogous-1 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-analogous-2 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-split-1 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-split-2 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-triad-1 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-triad-2 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-tetrad-1 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-tetrad-2 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-tetrad-3 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-square-1 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-square-2 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --hue-square-3 { syntax: \"\"; inherits: true; initial-value: 0; }\n@property --color-complement { syntax: \"\"; inherits: true; initial-value: transparent; }\n@property --color-analogous-1 { syntax: \"\"; inherits: true; initial-value: transparent; }\n@property --color-analogous-2 { syntax: \"\"; inherits: true; initial-value: transparent; }\n@property --color-split-1 { syntax: \"\"; inherits: true; initial-value: transparent; }\n@property --color-split-2 { syntax: \"\"; inherits: true; initial-value: transparent; }\n@property --color-triad-1 { syntax: \"\"; inherits: true; initial-value: transparent; }\n@property --color-triad-2 { syntax: \"\"; inherits: true; initial-value: transparent; }\n@property --mono-1 { syntax: \"\"; inherits: true; initial-value: transparent; }\n@property --mono-2 { syntax: \"\"; inherits: true; initial-value: transparent; }\n@property --mono-3 { syntax: \"\"; inherits: true; initial-value: transparent; }\n/* ── Design tokens — @theme mapping, base shadcn colors, mode knobs + shadow twins, base layer ─────\n :root carries light values + the LEAF mode knobs the engine composes; .dark carries the dark twins.\n .dark must come after :root (same-element ties resolve by source order on html.dark). */\n\n@theme inline {\n /* Easing. Tailwind's built-ins are deliberately gentle; these are the stronger variants UI motion\n wants, exposed as `ease-out` / `ease-in-out` / `ease-drawer` utilities.\n out — anything ENTERING or EXITING. Starts fast, so the first frame (the one the user is\n actually watching) already moves.\n in-out — things MOVING on screen that neither enter nor leave: carousels, sliders.\n drawer — a slower settle for panels that slide in from an edge, so a large surface arrives with\n weight instead of snapping.\n There is deliberately no `ease-in` token: it delays the first frame, which is the one moment the\n interface most needs to feel responsive. */\n --ease-out: cubic-bezier(0.23, 1, 0.32, 1);\n --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);\n --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);\n /* Anything with a bare `transition` and no `ease-*` inherits this. Tailwind ships\n cubic-bezier(0.4, 0, 0.2, 1) — symmetric, so it eases IN as well, and the first frame barely\n moves. Same curve as --ease-out, written literally because @theme inline doesn't emit the vars\n it inlines. */\n --default-transition-timing-function: cubic-bezier(0.23, 1, 0.32, 1);\n --color-background: var(--background);\n --color-foreground: var(--foreground);\n --color-foreground-soft: var(--foreground-soft);\n --color-foreground-strong: var(--foreground-strong);\n --color-foreground-ui: var(--foreground-ui);\n --color-sidebar-ring: var(--sidebar-ring);\n --color-sidebar-border: var(--sidebar-border);\n --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);\n --color-sidebar-accent: var(--sidebar-accent);\n --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);\n --color-sidebar-primary: var(--sidebar-primary);\n --color-sidebar-foreground: var(--sidebar-foreground);\n --color-sidebar: var(--sidebar);\n --color-chart-5: var(--chart-5);\n --color-chart-4: var(--chart-4);\n --color-chart-3: var(--chart-3);\n --color-chart-2: var(--chart-2);\n --color-chart-1: var(--chart-1);\n --color-ring: var(--ring);\n --color-input: var(--input);\n --color-border: var(--border);\n --color-destructive: var(--destructive);\n --color-accent-foreground: var(--accent-foreground);\n --color-accent: var(--accent);\n --color-muted-foreground: var(--muted-foreground);\n --color-muted: var(--muted);\n --color-secondary-foreground: var(--secondary-foreground);\n --color-secondary: var(--secondary);\n --color-primary-foreground: var(--primary-foreground);\n --color-primary: var(--primary);\n --color-popover-foreground: var(--popover-foreground);\n --color-popover: var(--popover);\n --color-card-foreground: var(--card-foreground);\n --color-card: var(--card);\n --radius-sm: calc(var(--radius) - 4px);\n --radius-md: calc(var(--radius) - 2px);\n --radius-lg: var(--radius);\n --radius-xl: calc(var(--radius) + 4px);\n}\n\n:root {\n --radius: 1.25rem;\n /* Shadow ink — the base black/white that every shadow, pressed-inset, and bevel highlight is built\n from. Mode-invariant (only the ALPHA changes per surface/mode), so apply alpha at the call site:\n oklch(var(--shadow-ink) / ). One knob to retint every shadow at once (e.g. a cooler, less-flat\n ink) instead of hunting down dozens of oklch(0% 0 0) / oklch(100% 0 0) literals. */\n --shadow-ink: 0% 0 0; /* drop + pressed-inset shadow color */\n --shadow-highlight: 100% 0 0; /* inner bevel / top highlight */\n /* Active/pressed shadow SCALE, built from the ink above and mode-aware (deeper in dark; overridden in\n .dark), so components reference these with NO dark: twin. Depth ladder — all inset \"push-in\" presses\n except --active-shadow, which LIFTS the selected tab/toggle. --press-shadow-strong shares the\n standard press's 0.2 primary and stacks a contact layer (was 0.15; raised so the two-layer presses\n build ON the single-layer one instead of undercutting it):\n --press-shadow-sm — subtle press (ghost, outline)\n --press-shadow — standard press (default, gradient, opaque)\n --press-shadow-strong — firm two-layer press (glass, destructive, secondary, frosted)\n --press-shadow-deep — deepest press (crystal)\n --active-shadow — selected-control LIFT (tabs, toggle) */\n --press-shadow-sm: inset 0 1px 2px oklch(var(--shadow-ink) / 0.1);\n --press-shadow: inset 0 2px 4px oklch(var(--shadow-ink) / 0.2);\n --press-shadow-strong: inset 0 2px 4px oklch(var(--shadow-ink) / 0.2), inset 0 1px 2px oklch(var(--shadow-ink) / 0.15);\n --press-shadow-deep: inset 0 2px 4px oklch(var(--shadow-ink) / 0.25), inset 0 1px 2px oklch(var(--shadow-ink) / 0.2);\n --active-shadow: 0 1px 3px oklch(var(--shadow-ink) / 0.1), inset 0 1px 1px oklch(var(--shadow-highlight) / 0.5);\n /* Selected-control FILL — the twin of --active-shadow, which had the lift but no surface, so tabs and\n toggle-group each hardcoded `bg-white/80 dark:bg-white/20`. The colour is composed in engine.css;\n these are its knobs.\n The two modes source their hue by DIFFERENT routes, and that asymmetry is the whole design.\n DARK is structurally theme-driven: the fill is chroma 0 at alpha 0.2, so 100% of the colour is the\n tinted surface showing through — the chip literally IS the theme surface, lifted. Nothing can make\n it drift off-theme.\n LIGHT cannot use that route. Show-through only carries hue if the surface has hue to give, and a\n light glass surface measures around chroma 0.018 (the sheet is itself capped by --glass-tint-c-hi\n at 0.025 near white). So at alpha 0.9 the fill supplies ~94% of the chip's colour and has to carry\n the tint ITSELF.\n Which makes the fill's own lightness the binding constraint, because the near-white ceiling\n collapses hue-dependently — 0.0323 at L 93, 0.061 at L 87, and only 0.0091 by L 98. At the old\n L 93 the cap was 0.031 and the chip delivered an RGB spread of 20: nominally theme-tinted, visibly\n neutral, which is why day did not read as theme-driven while night did. L 87 roughly doubles the\n available chroma and the delivered spread (37), at the cost of sitting 3.6 L BELOW the tab list\n rather than 1.8 above it — the lift is carried by --active-shadow, so the chip still reads\n selected, now as a tinted fill rather than a white one.\n Capped with min() like --border and the chakra body, so selenite (chroma 0) stays exactly neutral\n and no preset can push the fill out of gamut. */\n --active-l: 87%;\n --active-c-max: 0.055; /* ≤ 0.061, the L-87 ceiling floor (lapis h268) */\n --active-a: 0.9;\n /* ── Mode knobs ──────────────────────────────────────────────────────────\n Single-number dials the glass engine's mode-derived block composes (light values here, dark twins in\n .dark). LEAVES ONLY — none composes a tint var (check-theme invariant 1), so they may live on the bare\n blocks and inherit into tint scopes, where the engine re-resolves the derived tokens. */\n --glass-sheet-a: 0.11; /* sheer glass sheet base alpha (per-stop offsets ride on top) */\n --glass-sheet-a1: 0.04; /* the sheet's TOP-stop offset (dark tapers it to 0.03) */\n --glass-frost-boost: 0.1; /* frosted = the sheet lifted by this much */\n --glass-border-a: 0.16; /* glass edge alpha — a white rim needs more presence on light backdrops */\n /* Ink border alpha (borderColor=\"ink\" / glass-border-ink): how much of --foreground the line takes.\n Mode-invariant because --foreground already flips polarity per mode; 0.5 keeps the line clearly\n softer than text while clearing the APCA non-text floor on every preset (check-contrast [ink]); 0.55 since\n opaque cards carry the wash, whose lighter dark surface left goldstone's line at 29.9 Lc. */\n --glass-border-ink-a: 0.55;\n /* Crystal gloss defaults (the demo's Light/Tint sliders override inline). --glass-gloss-tint is\n mode-shared; --glass-gloss-l is NOT — see its twin below. AutoForeground's crystal tiers mirror\n --glass-gloss-l (97 light / 66 dark) as their snapshot-path fallback — keep all three in step. */\n --glass-diffuse: 12px; /* readability blur FLOOR the glass-diffuse axis raises surfaces to (opaque ignores it) */\n /* Gloss ink lightness (\"Light\" in the demo) — a MODE TWIN, because a gloss is defined relative to\n the surface under it and those surfaces are 68 L apart. The crystal floor computes to L 96.5 in\n light and L 28 in dark, so a single shared value cannot be a highlight in both: at 66 the top stop\n lifts dark by +15.2 L (a specular) and darkens light by −12.2 L (a smudge — the same \"grey\n speculars read as grime\" failure the shadow bevel comment warns about).\n 97 is the light twin: composited it lands +0.2 L over the surface, which is all the headroom that\n exists above L 96.5. The cost is a chroma ceiling, and it is strongly HUE-dependent rather than\n uniform — at L 97 it ranges from 0.0686 for peridot (h128) down to 0.0136 for lapis (h268).\n CORRECTION, measured in WebKit: an over-ceiling oklch() is NOT gamut-mapped down to that ceiling.\n It is CLIPPED per channel, which lands FURTHER from the request, not nearer:\n oklch(97% 0.0686 128) -> 232,255,205 (the ceiling — what the model predicts)\n oklch(97% 0.2975 128) -> 185,255,0 (what actually shipped: pure chartreuse)\n So the Tint multiplier did not \"do real work up to each hue's own ceiling\"; above the ceiling it\n drove the ink to a fully saturated primary. Every preset was affected — turquoise and aquamarine\n both clipped to 0,255,255, i.e. the two were indistinguishable — and moonstone reached 255,222,33,\n the egg-yolk cast that made light mode read yellow. --glass-gloss-c-max below is the fix, sized the\n way --glass-chakra-c-max is: the TIGHTEST ceiling across every preset hue, so no preset clips and\n all of them deliver 100% of what they ask for. */\n --glass-gloss-l: 97;\n /* Gloss chroma cap — the L-97 twin. 0.0136 is lapis (h268), the tightest of the 17 presets; 0.013\n sits just under it. Light gloss is therefore near-achromatic by necessity, not by choice: sRGB\n simply holds no more colour that close to white, and asking for more clips rather than tints. */\n --glass-gloss-c-max: 0.013;\n /* Gloss chroma multiplier over the tint (\"Tint\"). Calibrated for the DEFAULT tint (0.018 × 4.25 =\n 0.077, comfortably in gamut); a preset multiplies up from a much higher base, so the product is\n clamped to 0.3 where --glass-gloss-ink composes it — sRGB holds ~0.32 chroma at its very best hue\n and far less at L=66, so beyond that the browser, not the theme, is picking the color. Jewels\n (0.07 × 4.25 = 0.298) sit under the clamp and are unaffected; only the status tints hit it. */\n --glass-gloss-tint: 4.25;\n --glass-wash-l: 72%; /* tint wash lightness — deeper in dark so the tint reads over dark backdrops */\n --glass-wash-c-mult: 2.5; /* tint wash chroma multiplier (moonstone night quiets it to 2) */\n --glass-accent-l: 0.6; /* accent envelope — lifted in dark so the hue pops */\n --glass-accent-c: 0.15;\n --glass-glow-lc: 0.62 0.2; /* glow L+C pair */\n --glass-glow-a: 0.45;\n --harmonic-l: 0.6; /* --color-* accent-tier lightness */\n --mono-l-1: 0.75; /* mono ramp steps (irregular light↔dark lifts — three dials, not one) */\n --mono-l-2: 0.6;\n --mono-l-3: 0.45;\n --gradient-l: 0.6; /* brand-gradient envelope (matches --glass-accent-l/-c) */\n --gradient-c: 0.15;\n --gradient-contrast-l: 0.32; /* contrast gradient text: dark-on-light here, light-on-dark in .dark */\n --gradient-contrast-c: 0.14;\n --glass-crystal-l: 96; /* crystal body lightness — see --glass-crystal-bg; 100 carries zero chroma */\n --glass-crystal-bg-a: 0.3; /* crystal floor alpha (near-clear in dark) */\n /* glass-solid floor — the \"how solid\" dial (0 = sheer, 1 = fully solid; shared by light/dark) + the\n mode floor lightness; composed in @utility glass-veil so a scoped --glass-solid-a resolves there. */\n --glass-solid-a: 0.65;\n --glass-solid-l: 99%;\n /* Opaque tint BODY lightness (plain number → % in the engine). The header L slider drives this; the\n opaque floor color AND AutoForeground's --foreground-opaque banding both read it, so they can't\n drift. LOWER = deeper tint.\n L AND the c-scale below together absorb the retired opaque SHEET (see materials.css). --glass-bg was a\n near-white gradient at ~0.11 alpha, so it did two things to this floor: lightened it AND desaturated\n it. Compensating with lightness alone reproduced only half of that — it left the floor too saturated,\n and pushing L further toward white also SHRINKS the sRGB chroma ceiling, which blew the opaque body\n past check-gamut's 4.6× budget (lapis hit 5.46×). Moving both knobs reproduces both effects and stays\n inside the budget at 4.47×. Fitted across the 14 presets: worst OKLab dE 0.0021, mean 0.0009 — an\n order of magnitude under a just-noticeable difference, so the surface is unchanged to the eye.\n Moonstone pins its own pair (presets.css); its floor is near-white, where the sheet did far less. */\n /* L93. The opaque card is the glass stack at full opacity: this floor plus the same tint WASH every\n other material carries (materials.css). It used to be L88 with no wash, which made the floor carry\n the tint on its own, and near white that cannot work: the sRGB ceiling collapses, so the jewels sat\n at 97% of it and still read pastel, while greens and teals got half the chroma of their glass\n siblings. With the wash doing the tinting, the floor's job is the one the backing under sheer glass\n does (L92, --glass-solidify-l), and 93 is where floor + wash lands within 0.7 L of each preset's\n glass surface (it has no lighter page showing through to make up the last point) while body text\n keeps the 76.5 margin on every preset (tightest: destructive 76.6). */\n --glass-opaque-l: 93;\n /* 0.85, down from 1.26. The old value only looked large: it was immediately clipped to the shared\n 0.042 cap, so every preset rendered the SAME chroma regardless of its tint. Presets now pin their\n own --glass-opaque-c-max (their hue's real ceiling at L88), and 0.85 is the multiplier that lets\n each one reach it. Net effect vs the old flat 0.042: +7% to +88% depending on hue. */\n --glass-opaque-c-scale: 0.85;\n /* Opaque floor chroma cap — the near-white sibling of --glass-tint-c-hi / --glass-chakra-c-max /\n --glass-gloss-c-max, and the last surface that lacked one. Without a cap the floor multiplied raw\n tint chroma this close to white and clipped for most of the palette.\n FALLBACK ONLY: the 12 jewels each pin their own above (their hue's real ceiling at L88); this\n covers the 9 scopes that don't — the 4 frescoes, the 4 status tints and moonstone. Sizing the\n SHARED cap to the tightest hue is the mistake that came before: one number low enough for amber\n (h75, ceiling 0.036 at L88) left peridot at 18% of its own much larger ceiling. So this is\n deliberately not the tightest — per-hue pins are, and scripts/check-gamut.mjs sweeps every scope\n against its real ceiling rather than trusting this number. */\n --glass-opaque-c-max: 0.055;\n /* The share of that cap the opaque FLOOR may use. The caps above are each hue's ceiling at L88 (the\n solidify backing derives its own from them too), and at the floor's L93 the ceiling is 55–90% of\n that depending on hue; 0.55 keeps the tightest (tourmaline) at 0.96 of its ceiling. The floor\n needs little chroma now that the wash carries the tint. Dark sets 1: its floor sits mid-dark, where\n every preset's ask is far inside its ceiling. */\n --glass-opaque-cap-scale: 0.55;\n /* ── Solidify floor — the opaque backing UNDER sheer glass ───────────────────\n `glass` paints --glass-solidify (this colour at --glass-opacity) as the bottom background-image\n layer of every SHEER material, so it is ~70% of what page/crystal/chakra text actually sits on.\n It used to be --glass-opaque-bg itself, which conflated two different jobs: the colour an OPAQUE\n CARD shows, and the backing that sets sheer text contrast. Opaque cards explicitly drop this layer\n (materials.css, --srf-solidify: none — it would be C over C), so the two were never actually the\n same surface; they only shared a token.\n Splitting them is what lets the sheer floor rise without repainting opaque cards. It is the right\n lever for text contrast because it is the only one with real WEIGHT in the composite (0.7) that is\n not part of a preset's declared identity: --glass-wash-l is the theme COLOUR (moving it changes\n what lapis IS), and --glass-tint-a is already per-mode and desaturates. Measured across all 17\n scopes, L92 is what puts every preset's body text over the 75 floor with the 1.5 Lc margin, with\n no preset's tint tokens touched. DARK needs none of this (it clears everywhere already), so it\n pins both back to the opaque floor below and nothing changes there. */\n --glass-solidify-l: 92;\n /* The floor's chroma ceiling collapses as it lightens, so the L88 caps above would clip here. Rather\n than 17 new per-preset numbers, derive from the ones each preset already declares: across the\n palette the L92/L88 ceiling ratio runs 0.60–0.90, and 0.65 lands every scope INSIDE its real\n ceiling (worst 0.99×, verified by scripts/check-gamut.mjs). Hues that peak light — peridot,\n turquoise, aventurine, amber, aquamarine — are not bound by it at all and keep 100% of their\n surface chroma; the dark-peaking arc keeps 80–86%. */\n --glass-solidify-c-max: calc(var(--glass-opaque-c-max, 0.055) * 0.65);\n /* ── Chakra — the step-cut material (between opaque and crystal) ─────────────\n Every other material is lit from ONE direction: --glass-shadow carries a bright top bevel and a\n dark bottom shade, so surfaces read as a lit dome. Chakra lights all FOUR edges as mitered facets\n (a conic-gradient with hard stops on the diagonals, composed in engine.css), so it reads as a\n step-cut gem instead.\n The facets are pure LUMINANCE overlays — --shadow-highlight / --shadow-ink at these alphas, chroma\n 0 — not tinted colors. Two reasons, and they are the same reason: (1) the theme hue already comes\n through from the tinted body floor beneath, and a translucent white/black over it shifts lightness\n while preserving hue; (2) a bright facet sits near L96 in light mode, where the sRGB chroma ceiling\n across the 17 presets collapses to 0.0182 — a tinted highlight there would be gamut-mapped by the\n browser at nearly every preset. This is the theme's existing rule for the top bevel (\"a specular,\n not a shadow; grey speculars read as grime\"), applied to all four sides.\n The alphas are irregular mode TWINS, not one knob: the same alpha buys a very different lightness\n step over an L88 body than over an L28 one. They are solved so BOTH modes span 18 L end to end —\n light 94.0 > 90.5 > 88.0 > 83.0 > 76.0, dark 38.0 > 32.0 > 28.0 > 24.0 > 20.0 (top, left, table,\n right, bottom). Light comes from the top-left, matching the 135° axis of --glass-bg. */\n --glass-chakra-l: 88; /* body/table lightness */\n /* Body alpha — the \"between crystal and opaque\" dial, and now genuinely that across the WHOLE\n surface: crystal's floor sits at 0.3, opaque at 1. Nothing is painted over the middle any more\n (the cut moved into box-shadow), so this alone decides how much backdrop shows, and glass-diffuse\n / --glass-opacity modulate it the same way they do on crystal. */\n --glass-chakra-a: 0.62;\n --glass-chakra-c-max: 0.055; /* body chroma cap — ≤ 0.0564, the L-88 ceiling floor (lapis h268) */\n --glass-chakra-step: 3px; /* width of ONE bevel step (the frame is two) */\n --glass-chakra-facet-t-a: 0.5; /* top — highlight, +6.0 L */\n --glass-chakra-facet-l-a: 0.208; /* left — highlight, +2.5 L */\n --glass-chakra-facet-r-a: 0.057; /* right — ink, −5.0 L */\n --glass-chakra-facet-b-a: 0.136; /* bottom — ink, −12.0 L */\n /* ── Glass shadows — mode TWINS (not knobs: the light↔dark alpha ratios AND layer structures are\n irregular). They compose only --shadow-ink/-highlight, never tint vars, so bare placement is legal.\n LIGHT twins carry extra definition layers dark mode gets for free (a light surface on a dark page\n separates itself): a tight CONTACT drop + 1px dark outer HAIRLINE define the edge on same-lightness\n backdrops, and a directional BEVEL (bright top inner edge, ink bottom inner edge) makes pills read\n convex instead of printed. The last layer flips family per mode: dark's white inner GLOW (luminous\n glass) becomes an ink VIGNETTE in light — a white wash on a light surface brightens without\n defining, while a faint vignette reads as glass thickness. (Frosted keeps its white glow in both\n modes: milkiness IS that material.) The top bevel stays WHITE in both — it's a specular, not a\n shadow; grey speculars read as grime. Layer order: ambient drop, contact drop, hairline, top\n bevel, bottom shade, inner glow/vignette. */\n --glass-shadow:\n 0 4px 16px oklch(var(--shadow-ink) / 0.13), 0 1px 3px oklch(var(--shadow-ink) / 0.11), 0 0 0 1px oklch(var(--shadow-ink) / 0.05),\n 0 1px 0 oklch(var(--shadow-highlight) / 0.4) inset, 0 -1px 0 oklch(var(--shadow-ink) / 0.08) inset,\n 0 0 12px oklch(var(--shadow-ink) / 0.05) inset;\n --glass-shadow-lg:\n 0 8px 32px oklch(var(--shadow-ink) / 0.16), 0 2px 6px oklch(var(--shadow-ink) / 0.12), 0 0 0 1px oklch(var(--shadow-ink) / 0.05),\n 0 1px 0 oklch(var(--shadow-highlight) / 0.45) inset, 0 -1px 0 oklch(var(--shadow-ink) / 0.09) inset,\n 0 0 20px oklch(var(--shadow-ink) / 0.06) inset;\n --glass-shadow-sm:\n 0 2px 8px oklch(var(--shadow-ink) / 0.1), 0 1px 2px oklch(var(--shadow-ink) / 0.09), 0 0 0 1px oklch(var(--shadow-ink) / 0.04),\n 0 1px 0 oklch(var(--shadow-highlight) / 0.35) inset, 0 -1px 0 oklch(var(--shadow-ink) / 0.07) inset,\n 0 0 8px oklch(var(--shadow-ink) / 0.04) inset;\n --glass-frosted-border: oklch(100% 0 0 / 0.3);\n --glass-frosted-shadow:\n 0 8px 32px oklch(var(--shadow-ink) / 0.17), 0 2px 6px oklch(var(--shadow-ink) / 0.13), 0 0 0 1px oklch(var(--shadow-ink) / 0.05),\n 0 1px 0 oklch(var(--shadow-highlight) / 0.5) inset, 0 -1px 0 oklch(var(--shadow-ink) / 0.09) inset,\n 0 0 20px oklch(var(--shadow-highlight) / 0.18) inset;\n --glass-crystal-shadow: var(--glass-shadow), 0 1px 1px oklch(var(--shadow-highlight) / 0.4) inset;\n --glass-crystal-shadow-hover: var(--glass-shadow-lg), 0 1px 1px oklch(var(--shadow-highlight) / 0.45) inset;\n /* Base colors - Light mode with dark text */\n --background: transparent;\n --foreground: oklch(\n 0.2 0.035 var(--glass-fg-h)\n ); /* CSS baseline (tinted, ~body target) so first paint has no gray→tint flash; AutoForeground refines to the exact APCA solve after */\n /* Size-tiered foregrounds (ARC Bronze Simple Mode bands) — static fallback; AutoForeground re-bands\n these against the live glass surface at runtime. text-foreground-soft / -strong utilities. */\n --foreground-soft: oklch(\n 0.45 0.077 var(--glass-fg-h)\n ); /* large/heading — baseline matched to AutoForeground's median pick; chroma is the LOWEST any preset hue solves to, so no hue clips before JS refines */\n --foreground-strong: oklch(\n 0.2 0.034 var(--glass-fg-h)\n ); /* fine/small — the ramp's tonal clip put this at L20, not L12; the old value flashed dark-then-tinted on every load */\n --foreground-ui: oklch(0.42 0.071 var(--glass-fg-h)); /* icons — AutoForeground re-bands (+ optional hue) */\n /* Static baselines for the OPAQUE + CRYSTAL tier sets (the [data-material] remaps read these) —\n WITHOUT them, a no-JS consumer (or SSR first paint) would substitute an unset var and INHERIT the\n page foreground onto those floors. Light mode: the opaque floor (L90) and crystal surface share\n the page's direction, so the normal literals apply; AutoForeground refines when mounted. */\n --foreground-opaque: oklch(0.22 0.04 var(--glass-fg-h));\n --foreground-soft-opaque: oklch(0.4 0.04 var(--glass-fg-h));\n --foreground-strong-opaque: oklch(0.12 0.04 var(--glass-fg-h));\n --foreground-ui-opaque: oklch(0.46 0.05 var(--glass-fg-h));\n --muted-foreground-opaque: oklch(0.14 0.04 var(--glass-fg-h));\n --foreground-crystal: oklch(0.22 0.04 var(--glass-fg-h));\n --foreground-soft-crystal: oklch(0.4 0.04 var(--glass-fg-h));\n --foreground-strong-crystal: oklch(0.12 0.04 var(--glass-fg-h));\n --foreground-ui-crystal: oklch(0.46 0.05 var(--glass-fg-h));\n --muted-foreground-crystal: oklch(0.14 0.04 var(--glass-fg-h));\n /* Chakra tier baselines — text sits on the TABLE (the flat centre, L 88), not on the facets, so in\n light mode the direction matches the page and the normal literals apply. AutoForeground re-bands\n against the composited table when mounted. */\n --foreground-chakra: oklch(0.22 0.04 var(--glass-fg-h));\n --foreground-soft-chakra: oklch(0.4 0.04 var(--glass-fg-h));\n --foreground-strong-chakra: oklch(0.12 0.04 var(--glass-fg-h));\n --foreground-ui-chakra: oklch(0.46 0.05 var(--glass-fg-h));\n --muted-foreground-chakra: oklch(0.14 0.04 var(--glass-fg-h));\n --card: transparent;\n --card-foreground: var(--foreground);\n --popover: transparent;\n --popover-foreground: var(--foreground);\n\n /* Primary colors - Light mode */\n --primary-l: 0.42; /* primary ink lightness (dark pill on light pages) — the color itself is composed in the engine, following the theme hue */\n /* Ceiling on the primary ink's chroma. The ×1.2 in the engine is intent — primary should read MORE\n saturated than the base tint — but uncapped it asked past the sRGB edge for five presets in light\n (turquoise, aquamarine, success, warning, aurora) and two in dark (lapis, info), where the browser\n gamut-maps and the delivered colour stops tracking the authored one.\n Measured against --glass-fg-h, not --glass-tint-h: --primary follows the FOREGROUND hue, and the\n Muse/Sistine frescoes decouple the two (fg 290 / 18), so those hues are in the sweep — aurora only\n shows up as out-of-gamut once you check the fg hue. */\n --primary-c-max: 0.069; /* ≤ 0.0697, the L-42 ceiling floor — h205 (aurora) is now tighter than h190 */\n --primary-foreground: oklch(0.98 0 0); /* White text on the primary ink — Lc 88.1 at the worst hue, turquoise (measured) */\n\n /* Secondary colors - Light mode */\n /* ── Flat-neutral shadcn SURFACES, now theme-tinted ──────────────────────────\n --accent, --secondary, --muted and --sidebar were the last chroma-0 tokens in the theme, which is\n why a toggle's on-state read as near-white in light and near-black in dark no matter which preset\n was active: the fill simply had no hue to give. Same treatment as --border — lightness stays put\n (pure recolor), the colour moves to engine.css so it re-resolves per tint scope, and chroma is\n min(the preset's own, a cap) so a preset that declares less keeps its restraint and selenite stays\n exactly neutral.\n Each cap is measured against the tightest sRGB ceiling across all 17 presets AT THAT LIGHTNESS, and\n they differ by a factor of eight because the ceiling collapses toward white: 0.0714 at L 85, but\n only 0.0091 at L 98. One shared cap would have to be the smallest, tinting nothing. */\n --secondary-l: 0.96;\n --secondary-c-max: 0.018; /* ≤ 0.0182, the L-96 ceiling floor (lapis h268) */\n --secondary-foreground: var(--foreground);\n\n /* Muted colors - Light mode */\n --muted-l: 0.85;\n --muted-c-max: 0.071; /* ≤ 0.0714, the L-85 ceiling floor (lapis h268) */\n --muted-foreground: oklch(\n 0.35 0.06 var(--glass-fg-h)\n ); /* Level 1 — L35, not L14: the widest gap of the set, and the one doing most of the first-paint flash */\n\n /* Accent colors - Light mode. NOTE the knob prefix: --accent-c / --accent-h are already taken as the\n user-facing overrides for --glass-accent (the vivid switch/slider fill), a different role. */\n /* L93, down from 0.96, and the cap raised with it. The hover fill was reading as plain white: at L96 the\n sRGB gamut allows a chroma of only 0.0182, an RGB spread of 19, so \"tinted\" was arithmetically\n invisible. Chroma near the extremes is not a free parameter — it is whatever the gamut leaves — so the\n only way to a visible tint is to step off the extreme. Three points of lightness buys 1.7x the spread\n (19 -> 32) and moves the fill DARKER than the surface, the direction a highlight should go on a light\n page. It cannot go much further: --accent is also painted at 50% (bg-accent/50 on calendar's \"today\",\n button's active state, ToggleGroup hover), and that composite over an L88 card is what binds — at L92\n it drops to 76.0 Lc, under the floor+margin, while L93 holds 76.9. */\n --accent-bg-l: 0.93;\n --accent-bg-c-max: 0.032; /* ≤ 0.0323, the L-93 ceiling floor (lapis h268) */\n /* Stays --foreground: this token is text on a SURFACE-lightness background. Its callers all have that\n shape — the ghost button's hover, toggles, and the highlighted row of every menu (dropdown, context,\n navigation, command, menu-bar, select), which paint bg-accent so a consumer's --accent reaches them\n (the shadcn contract). Text and fill only stay legible together while --accent sits near the\n surface, which is precisely why the fill was moved by chroma rather than by weight. */\n --accent-foreground: var(--foreground);\n\n /* Destructive colors - WCAG AA compliant (4.5:1 contrast) */\n --destructive: oklch(0.55 0.22 25); /* Red - 4.5:1 with white */\n --destructive-foreground: oklch(0.98 0 0); /* White text on red */\n\n /* Border / input LIGHTNESS + chroma-CAP knobs — the COLOR is composed in engine.css so the edge\n follows the theme tint like every other surface token (leaves, so they inherit into tint scopes\n where the engine re-resolves).\n L 0.85, not the 0.88 the flat neutral carried: sRGB's chroma ceiling collapses as lightness climbs,\n and at 0.88 the binding ceiling across all 17 presets is 0.0564 — the most colorful in-gamut edge\n possible there still reads as grey (mean RGB spread 51). Dropping 3 points of L raises the ceiling\n to 0.0714 and nearly doubles delivered colorfulness (mean spread 65) while moving the grey\n reference only #d7d7d7 → #cecece, which is below the threshold of a perceived weight change.\n --border-c-max is the CAP, not the value: chroma is min(tint chroma, cap), so a preset that\n declares less than the cap keeps its own restraint — selenite (c 0) stays exactly neutral, which is\n the whole point of that theme — while lapis (c 0.15) clamps instead of being gamut-mapped by the\n browser. A flat multiplier cannot do both: any factor small enough to keep lapis in gamut drags the\n 0.07 jewels below where they are today. */\n --border-l: 0.85;\n --input-l: 0.85;\n --border-c-max: 0.07; /* ≤ 0.0714, the L-0.85 ceiling floor (lapis h268) */\n --ring: oklch(0.5 0.2 250); /* Focus ring */\n\n /* Chart colors - WCAG AA compliant */\n --chart-1: oklch(0.6 0.2 250); /* Blue */\n --chart-2: oklch(0.55 0.15 150); /* Green */\n --chart-3: oklch(0.5 0.2 50); /* Yellow */\n --chart-4: oklch(0.65 0.2 300); /* Purple */\n --chart-5: oklch(0.6 0.2 20); /* Orange */\n\n /* Sidebar colors - Light mode */\n --sidebar-l: 0.98;\n --sidebar-c-max: 0.009; /* ≤ 0.0091, the L-98 ceiling floor — near white holds almost no hue */\n --sidebar-foreground: var(--foreground);\n --sidebar-primary: oklch(0.5 0.2 250); /* Blue */\n --sidebar-primary-foreground: oklch(0.98 0 0); /* White text on primary */\n --sidebar-accent: oklch(0.15 0 0 / 0.1); /* Subtle glass overlay (≈ foreground/10), not a solid block */\n --sidebar-accent-foreground: var(--foreground);\n --sidebar-border: var(--border); /* Same divider role, same value it already carried — follow the role token */\n --sidebar-ring: oklch(0.5 0.2 250); /* Blue ring - 3:1 contrast */\n}\n\n.dark {\n /* Base colors — light text on a dark ground */\n --background: transparent;\n /* Pressed/active shadow SCALE — dark overrides: primaries deepen +0.1, contact layers +0.05, so the\n push-in still reads on dark surfaces (see :root for the light ladder + rationale). */\n --press-shadow-sm: inset 0 1px 2px oklch(var(--shadow-ink) / 0.2);\n --press-shadow: inset 0 2px 4px oklch(var(--shadow-ink) / 0.3);\n --press-shadow-strong: inset 0 2px 4px oklch(var(--shadow-ink) / 0.3), inset 0 1px 2px oklch(var(--shadow-ink) / 0.2);\n --press-shadow-deep: inset 0 2px 4px oklch(var(--shadow-ink) / 0.35), inset 0 1px 2px oklch(var(--shadow-ink) / 0.25);\n --active-shadow:\n 0 2px 6px oklch(var(--shadow-ink) / 0.4), inset 0 1px 2px oklch(var(--shadow-highlight) / 0.15),\n inset 0 0 8px oklch(var(--shadow-highlight) / 0.1);\n /* Selected-control fill — dark twin. Deliberately UNCHANGED in behaviour from the `bg-white/20` it\n replaces: chroma cap 0 makes min() resolve to 0, so this is a pure white lift and the hue arrives\n by show-through from the surface below, which is exactly why dark already read correctly. Light\n has to source its hue directly (see :root); the two modes reach the same result by different\n routes because the near-white gamut only permits one of them. */\n --active-l: 100%;\n --active-c-max: 0;\n --active-a: 0.2;\n /* ── Mode knobs — dark twins (see :root for what each dial does). */\n --glass-sheet-a: 0.05;\n --glass-sheet-a1: 0.03;\n --glass-frost-boost: 0.06;\n --glass-border-a: 0.15;\n --glass-wash-l: 58%;\n --glass-accent-l: 0.68;\n --glass-accent-c: 0.14;\n --glass-glow-lc: 0.7 0.18;\n --glass-glow-a: 0.5;\n --harmonic-l: 0.68;\n --mono-l-1: 0.82;\n --mono-l-2: 0.68;\n --mono-l-3: 0.5;\n --gradient-l: 0.68;\n --gradient-c: 0.14;\n --gradient-contrast-l: 0.9;\n --gradient-contrast-c: 0.09;\n --glass-crystal-l: 96; /* shared with light: the ceiling that forces 96 is hue-driven, not mode-driven */\n --glass-crystal-bg-a: 0.1;\n /* Gloss ink lightness — dark twin. Over an L-28 floor there is 72 L of headroom, so 66 lifts +15.2\n and still holds 59% of the tonal ink's chroma: a specular that is genuinely tinted. Light cannot\n have both (see :root). */\n --glass-gloss-l: 66;\n /* Gloss chroma cap — the L-66 twin. Far more headroom this far from white: 0.1094 is aurora (h205),\n the tightest across all 23 preset scopes — the FRESCOES count here too, which is why this is not\n turquoise's 0.1106. Dark gloss keeps real colour; it was still clipping before (turquoise\n 0,193,186 and carnelian 255,0,0 both had a pinned channel). */\n --glass-gloss-c-max: 0.109;\n /* L lifted from 22% (less extreme light↔dark switch); the opaque chroma scale (0.9) keeps warm hues\n like amber in-gamut instead of hard-clipping to rust. */\n --glass-solid-l: 18%;\n /* The retired sheet's compensation, dark twin (see :root). Much the larger move of the two modes: a\n near-white sheet over a DARK floor lifts it ~12 codes where over the light floor it lifts ~2–5. Same\n two-knob fit, worst OKLab dE 0.0045 across the 14 presets.\n 33.4 since opaque carries the tint wash (materials.css). The wash lifts the card, and unlike sheer\n glass no dark page shows through it, so at the backing's own 36.4 an opaque card sat 3–5 L above its\n glass siblings, and on the light-wash presets (aventurine, peridot, turquoise) only pure white text\n still reached the body floor. Three points down puts it within ~2 L of the glass beside it and keeps\n tinted text above the 76.5 margin everywhere (tightest: moonstone 76.6). The light floor makes the\n mirror-image move (L93 over a L92 backing) for the mirror-image reason. */\n --glass-opaque-l: 33.4;\n /* 0.98. Was 1.05 (up from 0.85): the dark floor was never cap-limited — every preset sat at 31–79% of\n its own ceiling, so the MULTIPLIER was the throttle and dark opaque cards lost their tint. It is\n still the most the tightest hue (turquoise, ×0.984) can take without clipping, now at the floor's\n L33.4, where the ceilings are lower than at L36.4. The wash carries most of the colour on top of it.\n The sheer backing shares this scale (engine.css), so dark glass gives up 7% of its backing chroma\n with it, under a wash that dominates it. */\n --glass-opaque-c-scale: 0.98;\n --glass-opaque-cap-scale: 1;\n /* Opaque floor chroma cap, dark twin (see :root). A GUARD, not a throttle — the distinction matters\n and this value used to get it wrong. It was 0.06, the tightest ceiling across the preset scopes,\n which was correct while every jewel shared --glass-tint-c: 0.07 and the floor multiplied it blind.\n Once each preset tuned its own chroma to its own hue, that shared floor-of-the-tightest started\n costing the hues WITH headroom: at 0.06 amethyst lost 30% of its ask, tourmaline 33%, rose 26% —\n against own ceilings of 0.189, 0.152 and 0.142. Measured, every preset's dark ask now sits at\n 0.31–0.79× of its own ceiling, so nothing clips with no cap at all. 0.095 clears the largest ask\n (tourmaline 0.0901) and therefore throttles nobody, while still bounding a future preset that\n pushes chroma far past today's values. check-gamut scores each preset against its own ceiling. */\n /* Guard, not a throttle: the largest dark ask is tourmaline at 0.111, so this binds nobody. Declared\n in its own rule right after this block (see there for why). */\n /* The dark backing under sheer glass, at the lightness the opaque floor had before opaque gained the\n wash: dark glass is unchanged by that move. It is its own value now, as light's L92 is, so the tint\n switcher's Lightness slider (which writes --glass-opaque-l) moves the opaque card in both modes and\n the sheer backing in neither. The cap stays the opaque one: dark already clears the body floor on\n every preset with margin, and a lighter or greyer backing would wash the whole mode out. */\n --glass-solidify-l: 36.4;\n --glass-solidify-c-max: var(--glass-opaque-c-max);\n /* ── Chakra dark twins (see :root for what each dial does + how the alphas were solved).\n The facet alphas invert in character: over an L28 body there is 72 L of headroom UP and only 28\n DOWN, so the highlights need far less alpha and the inks far more than their light-mode twins to\n land the same 18 L span. The targets also skew brighter here (+10/−8 rather than +6/−12) — a dark\n gem reads as cut by catching light, where a light one reads by casting shade. */\n --glass-chakra-l: 28;\n --glass-chakra-a: 0.58;\n --glass-chakra-c-max: 0.046; /* ≤ 0.0471, the L-28 ceiling floor (turquoise h190) */\n --glass-chakra-facet-t-a: 0.139; /* top — highlight, +10.0 L */\n --glass-chakra-facet-l-a: 0.056; /* left — highlight, +4.0 L */\n --glass-chakra-facet-r-a: 0.143; /* right — ink, −4.0 L */\n --glass-chakra-facet-b-a: 0.286; /* bottom — ink, −8.0 L */\n /* ── Glass shadows — dark twins (deeper drops, brighter bevels). */\n --glass-shadow:\n 0 4px 16px oklch(var(--shadow-ink) / 0.3), 0 1px 4px oklch(var(--shadow-ink) / 0.15), 0 0 0 0.5px oklch(var(--shadow-highlight) / 0.12) inset,\n 0 0 12px oklch(var(--shadow-highlight) / 0.15) inset;\n --glass-shadow-lg:\n 0 8px 32px oklch(var(--shadow-ink) / 0.4), 0 2px 8px oklch(var(--shadow-ink) / 0.2), 0 0 0 0.5px oklch(var(--shadow-highlight) / 0.15) inset,\n 0 0 20px oklch(var(--shadow-highlight) / 0.18) inset;\n --glass-shadow-sm:\n 0 2px 8px oklch(var(--shadow-ink) / 0.25), 0 1px 2px oklch(var(--shadow-ink) / 0.12), 0 0 0 0.5px oklch(var(--shadow-highlight) / 0.1) inset,\n 0 0 8px oklch(var(--shadow-highlight) / 0.12) inset;\n --glass-frosted-border: oklch(100% 0 0 / 0.25);\n --glass-frosted-shadow:\n 0 8px 32px oklch(var(--shadow-ink) / 0.5), 0 2px 8px oklch(var(--shadow-ink) / 0.3), 0 0 0 0.5px oklch(var(--shadow-highlight) / 0.2) inset,\n 0 0 20px oklch(var(--shadow-highlight) / 0.22) inset;\n --glass-crystal-shadow: var(--glass-shadow), 0 1px 1px oklch(var(--shadow-highlight) / 0.2) inset;\n --glass-crystal-shadow-hover: var(--glass-shadow-lg), 0 1px 1px oklch(var(--shadow-highlight) / 0.25) inset;\n --foreground: oklch(\n 0.95 0.03 var(--glass-fg-h)\n ); /* CSS baseline (tinted, ~body target) so first paint has no gray→tint flash; AutoForeground refines to the exact APCA solve after */\n /* Size-tiered foregrounds (ARC Bronze Simple Mode bands) — static fallback; AutoForeground re-bands\n these against the live glass surface at runtime. text-foreground-soft / -strong utilities. */\n --foreground-soft: oklch(0.83 0.065 var(--glass-fg-h)); /* large/heading — refined at runtime */\n --foreground-strong: oklch(0.97 0.015 var(--glass-fg-h)); /* fine/small — refined at runtime */\n --foreground-ui: oklch(0.87 0.057 var(--glass-fg-h)); /* icons — AutoForeground re-bands (+ optional hue) */\n /* Static OPAQUE + CRYSTAL tier baselines, dark twins — the standard dark opaque floor (L32) and\n crystal surface keep the page's light-text direction. Presets whose dark floors flip LIGHT (moonstone\n cream) override the -opaque set in their own block; AutoForeground refines when mounted. */\n --foreground-opaque: oklch(0.92 0.04 var(--glass-fg-h));\n --foreground-soft-opaque: oklch(0.78 0.04 var(--glass-fg-h));\n --foreground-strong-opaque: oklch(0.96 0.035 var(--glass-fg-h));\n --foreground-ui-opaque: oklch(0.75 0.05 var(--glass-fg-h));\n --muted-foreground-opaque: oklch(0.89 0.04 var(--glass-fg-h));\n --foreground-crystal: oklch(0.92 0.04 var(--glass-fg-h));\n --foreground-soft-crystal: oklch(0.78 0.04 var(--glass-fg-h));\n --foreground-strong-crystal: oklch(0.96 0.035 var(--glass-fg-h));\n --foreground-ui-crystal: oklch(0.75 0.05 var(--glass-fg-h));\n --muted-foreground-crystal: oklch(0.89 0.04 var(--glass-fg-h));\n /* Chakra tier baselines, dark twins — the L-28 table keeps the page's light-text direction. */\n --foreground-chakra: oklch(0.92 0.04 var(--glass-fg-h));\n --foreground-soft-chakra: oklch(0.78 0.04 var(--glass-fg-h));\n --foreground-strong-chakra: oklch(0.96 0.035 var(--glass-fg-h));\n --foreground-ui-chakra: oklch(0.75 0.05 var(--glass-fg-h));\n --muted-foreground-chakra: oklch(0.89 0.04 var(--glass-fg-h));\n --card: transparent;\n --card-foreground: var(--foreground);\n --popover: transparent;\n --popover-foreground: var(--foreground);\n\n /* Primary colors — dark mode */\n --primary-l: 0.78; /* primary ink lightness in dark mode (light pill) — color composed in the engine */\n --primary-c-max: 0.107; /* ≤ 0.1077, the L-78 ceiling floor (h268 lapis) */\n /* Near-black text on the primary ink — Lc 62.9 at the worst hue, sistine (measured). It was 60.9\n before --primary-c-max: capping the chroma the browser used to gamut-map moved the worst case back\n above the 62 this comment had been claiming. */\n --primary-foreground: oklch(0.15 0 0);\n\n /* Secondary colors — dark mode */\n /* Flat-neutral surfaces — dark twins (see :root for why these are knobs now). Lightness unchanged;\n the caps are the dark ceilings, which are tightest at the DARKEST step, not the lightest. */\n --secondary-l: 0.25;\n --secondary-c-max: 0.041; /* ≤ 0.0417, the L-25 ceiling floor — h205 (aurora) is now tighter than h190 */\n --secondary-foreground: var(--foreground);\n\n /* Muted colors — dark mode */\n --muted-l: 0.18;\n --muted-c-max: 0.03; /* ≤ 0.0307, the L-18 ceiling floor (turquoise h190) */\n --muted-foreground: oklch(0.9 0.031 var(--glass-fg-h)); /* Level 1 — one ramp step toward the chosen color */\n\n /* Accent colors — dark mode */\n /* L34, up from 0.25 — the same fix as light, mirrored. At L25 the ceiling was 0.0417 (spread 29), so the\n fill read as near-black; L34 allows 0.0565 (spread 42) and moves it BRIGHTER than the surface, which is\n the direction a highlight goes on a dark page. Dark has more room than light here (its 50% composite\n sits at 77.5 Lc rather than 76.0), because the fill and the L36.4 card floor are close together. */\n --accent-bg-l: 0.34;\n --accent-bg-c-max: 0.056; /* ≤ 0.0565, the L-34 ceiling floor (aurora h205) */\n --accent-foreground: var(--foreground);\n\n /* Destructive colors - WCAG AA compliant (4.5:1 contrast) */\n --destructive: oklch(0.65 0.22 25); /* Light red - 4.5:1 with dark background */\n --destructive-foreground: oklch(1 0 0); /* White text on red — APCA Lc ~67 (near-black was Lc 42) */\n\n /* Border / input lightness + chroma-cap — dark twins (see :root; color composed in engine.css).\n L is UNCHANGED here: in dark mode the edge sits at 0.30/0.25 on a dark page, so lowering it would\n cost contrast rather than buy color. Only the cap moves, up to the tightest dark ceiling — 0.0422\n at L 0.25 (turquoise h190), which --input hits first, so one cap serves both. */\n --border-l: 0.3;\n --input-l: 0.25;\n --border-c-max: 0.041; /* ≤ 0.0422, the L-0.25 ceiling floor (turquoise h190) */\n --ring: oklch(0.6 0.2 250); /* Focus ring */\n\n /* Chart colors - WCAG AA compliant */\n --chart-1: oklch(0.65 0.2 250); /* Light blue */\n --chart-2: oklch(0.7 0.15 150); /* Light green */\n --chart-3: oklch(0.75 0.2 50); /* Light yellow */\n --chart-4: oklch(0.7 0.2 300); /* Light purple */\n --chart-5: oklch(0.68 0.2 20); /* Light orange */\n\n /* Sidebar colors — dark mode */\n --sidebar-l: 0.18;\n --sidebar-c-max: 0.03; /* ≤ 0.0307, the L-18 ceiling floor (turquoise h190) */\n --sidebar-foreground: var(--foreground);\n --sidebar-primary: oklch(0.56 0.16 250); /* Match --primary; white text clears APCA */\n --sidebar-primary-foreground: oklch(1 0 0); /* White on the sidebar's brand fill */\n --sidebar-accent: oklch(1 0 0 / 0.1); /* Subtle glass overlay (≈ foreground/10), not a solid block */\n --sidebar-accent-foreground: var(--foreground);\n --sidebar-border: var(--border); /* Same divider role, same value it already carried — follow the role token */\n --sidebar-ring: oklch(0.6 0.2 250); /* Light blue ring - 3:1 contrast */\n}\n\n/* The dark opaque-floor chroma guard, in its own rule so it can OUTRANK the presets. Each jewel pins\n --glass-opaque-c-max to its hue's sRGB ceiling at the LIGHT floor (L88, presets.css), in an unscoped\n block. On that block and `.dark` tie at (0,1,0) and presets.css loads later, so the light caps\n used to win in dark too: amethyst's dark floor rendered C 0.065 against an ask of 0.106 and a ceiling\n of 0.189, lapis 0.055 against 0.236, and every blue/purple/red jewel's dark opaque card came out a dull,\n greyed version of its tint (23–53% of the chroma its hue can hold at L36.4). The light caps describe\n the light floor only; at L36.4 every preset's ask sits well inside its own ceiling, so this guard is\n the only cap dark needs. (0,2,0) beats a preset block; `.dark [data-glass-tint]` covers a scoped tint\n inside a dark page, and a preset's own `.dark[data-glass-tint=\"x\"]` block still wins by source order. */\n.dark,\n.dark[data-glass-tint],\n.dark [data-glass-tint] {\n --glass-opaque-c-max: 0.12;\n}\n\n@layer base {\n * {\n @apply border-border outline-ring/50;\n scrollbar-width: thin;\n scrollbar-color: color-mix(in oklch, var(--foreground) 22%, transparent) transparent;\n }\n body {\n /* Text uses the band-picked --foreground (set on by AutoForeground on theme/tint change;\n globals carries a static fallback so there's no flash). */\n color: var(--foreground);\n background-color: transparent;\n }\n /* Thin, glass-friendly scrollbars (replaces the chunky native bar) */\n ::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n }\n ::-webkit-scrollbar-track {\n background: transparent;\n }\n ::-webkit-scrollbar-thumb {\n background-color: color-mix(in oklch, var(--foreground) 22%, transparent);\n border-radius: 9999px;\n }\n ::-webkit-scrollbar-thumb:hover {\n background-color: color-mix(in oklch, var(--foreground) 38%, transparent);\n }\n}\n\n/* ── Below the floor ──────────────────────────────────────────────────────────\n The supported floor is Safari 17.5 / Chrome 120 / Firefox 128 / Edge 120, declared as the browserslist\n in SISTINE's package.json. It is set by what this theme actually uses, not by preference — the binding\n constraints are text-wrap:balance (Safari 17.5), bare-`&` CSS nesting and unprefixed mask-composite\n (Chrome 120), and @property (Firefox 128). Tailwind v4's own baseline sits just under it. That\n browserslist does not ship with this file: a consuming app should declare its own, at or above the\n floor, or its toolchain targets whatever its defaults are.\n\n Every colour in this theme is oklch(), fed through custom properties. On an engine without oklch the\n custom property still PARSES (custom props accept anything) and only fails when substituted — so\n `background-color: var(--color-background)` resolves to an invalid value and computes to `unset`,\n i.e. transparent, rather than to a fallback. Surfaces disappear; text usually survives on the UA\n default. This is not a polyfill and does not try to reproduce the palette — it guarantees a legible\n page instead of transparent panels on stacked backgrounds.\n\n UNVERIFIED IN A REAL ENGINE: no browser in the test matrix lacks oklch, so this branch has only been\n confirmed to compile, not to render. Treat it as a floor, not a tested path. */\n@supports not (color: oklch(0% 0 0)) {\n :root {\n color-scheme: light;\n background-color: #f7f7fb;\n color: #17161c;\n }\n body {\n background-color: #f7f7fb;\n color: #17161c;\n }\n .glass,\n [data-surface],\n [data-material] {\n background-color: #ffffffe6;\n border: 1px solid #17161c26;\n backdrop-filter: none;\n }\n}\n/* ── Glass engine ─────────────────────────────────────────────────────────────\n THREE grouped blocks, split by what must re-resolve where (the var-composition-resolves-at-declaration\n gotcha — see scripts/check-theme.mjs):\n\n A. TINT ANCHORS (:root, [data-glass-tint], …) — the tint knobs plus everything mode-INVARIANT that\n composes them, declared on :root AND every tint scope so a scoped data-glass-tint re-resolves the\n whole subgraph. Deliberately NOT on `.dark`: re-declaring --glass-tint-h / --glass-fg-h at a bare\n `.dark` wrapper would reset a page tint inherited from above.\n\n B. MODE-DERIVED SURFACES (:root, .dark, [data-glass-tint], …) — every tint-composing token whose\n light/dark difference is a handful of numbers. Each is declared ONCE, composing the mode knobs\n (--glass-sheet-a, --glass-wash-l, … — see :root/.dark) which are LEAVES that inherit into tint\n scopes. `.dark` IS in this selector so a dark wrapper inside a light page re-composes its surfaces\n from the wrapper's own dark knobs (same-element cascade) — \"toggle .dark anywhere\" keeps working.\n\n C. GLOSS (:root, .dark, [data-glass-tint], [data-gloss]) — the crystal gloss and the stacks that bake\n it, which also have to recompose wherever a `data-gloss` flavor is set. See block C itself.\n\n Foreground tokens stay on bare :root/.dark (AutoForeground owns them). Preset blocks below override\n --glass-tint-h/c/a by source order. Shadow composites are mode-TWINNED on bare :root/.dark (their\n light↔dark ratios are irregular — knobbing them would change values). */\n:root,\n[data-glass-tint] {\n /* Deliberately restrained glassmorphism — enough to read as glass, never enough to fog what is behind it. */\n /* Glass tint — recolor ALL glass by changing these; presets set them via [data-glass-tint].\n -h = hue, -c = chroma (gradient), -a = wash alpha. The gradient alone is too sheer\n (~6–12% opacity) to show a hue, so -a paints a solid colored floor (--glass-tint-wash)\n that reads over any backdrop. Neutral keeps -a:0, so the default glass is unchanged. */\n --glass-tint-h: 250;\n --glass-tint-c: 0.018;\n --glass-tint-a: 0;\n /* The same chroma, capped for NEAR-WHITE surfaces. The sheet stops, the borders and the crystal\n floor sit at L 95–100%, where sRGB's chroma ceiling collapses AND becomes strongly hue-dependent:\n at L=97% it is ~0.071 for peridot (h128) but ~0.014 for lapis (h268). Feeding those the raw\n --glass-tint-c means each preset lands on whatever fraction of its declared value its hue happens\n to permit — lapis was delivering 9% of its 0.15 where peridot delivered 98% of its 0.07 — and the\n shortfall is then resolved by the browser's gamut mapping rather than by us. That is the same\n class of bug the 3% margin in lib/oklch-utils guards against on the computed path (\"foregrounds\n went grey in Safari while staying tinted in Chrome\"); this is its CSS-side counterpart.\n Capping the BASE (rather than lowering any preset) keeps chroma where it has headroom — a jewel's\n depth lives at mid lightness, in --primary and --glass-tint-wash, which are untouched by this.\n Guarded by scripts/check-gamut.mjs.\n 0.025 was still too generous, because the sheet MULTIPLIES this cap: its stops are\n calc(--glass-tint-c-hi * 0.3…1.3), so the top stop asked 0.0325 against an L-95 ceiling of 0.0229\n at the tightest hue — the cap itself clipped. 0.017 is that ceiling divided by the 1.3 multiplier,\n so the sheet now lands inside the gamut at every preset hue instead of at whatever the browser\n chops it to. Cap-then-multiply is the order that matters here; see check-gamut's SCALE/CAP/BOTH\n shapes, which model each form separately for exactly this reason. */\n --glass-tint-c-hi: min(var(--glass-tint-c), 0.017);\n /* Foreground hue — drives every FOREGROUND-keyed token (body/heading text via AutoForeground,\n --muted-foreground, --glass-accent, --glass-glow, --gradient*), kept SEPARATE from the surface\n hue so tuning text never shifts the glass/fresco surface. Defaults to the tint hue (jewels: text\n == surface); frescoes override it to anchor text off their multi-hue surface (Muse: surface\n 230, text 290). AutoForeground reads this, falling back to --glass-tint-h for older consumers. */\n --glass-fg-h: var(--glass-tint-h);\n /* ── Harmonic color system ────────────────────────────────────────────────────────────────────\n Color-wheel relationships derived from the CONTENT hue (--glass-fg-h) — the same hue --glass-accent\n and --gradient track — so they rotate with the theme/tint automatically (a generalization of the\n --gradient's +63.53° offset). Hues are mode-invariant ANGLES; CSS Color 4 wraps them past 360°, so use\n anywhere as oklch(L C var(--hue-*)); choosing your own L/C also keeps them in-gamut. The ready --color-*\n tokens sit at the --glass-accent envelope (0.6/0.15; .dark lifts them). NOTE: a fixed-chroma harmonic can\n land at the sRGB edge at some hues and get browser-gamut-mapped — prefer the hue tokens + a conservative\n chroma when that matters. */\n /* Harmonic anchor hue — the wheel origin the harmonies rotate from. Normally the content hue, but the two\n \"hue-less\" themes (selenite + moonstone) anchor at 0° instead — each preset block in presets.css declares\n it, so a CSS-only consumer gets the anchor too (the tint switcher also inlines it, for a custom tint\n dragged to chroma 0) — and their accents come out a colorful red-based harmony, not a warp of a\n near-grey hue. */\n --harmony-h: var(--glass-fg-h);\n --hue-base: var(--harmony-h);\n --hue-complement: calc(var(--harmony-h) + 180);\n --hue-analogous-1: calc(var(--harmony-h) - 30);\n --hue-analogous-2: calc(var(--harmony-h) + 30);\n --hue-split-1: calc(var(--harmony-h) + 150);\n --hue-split-2: calc(var(--harmony-h) + 210);\n --hue-triad-1: calc(var(--harmony-h) + 120);\n --hue-triad-2: calc(var(--harmony-h) + 240);\n --hue-tetrad-1: calc(var(--harmony-h) + 60); /* rectangle: base, +60, +180, +240 */\n --hue-tetrad-2: calc(var(--harmony-h) + 180);\n --hue-tetrad-3: calc(var(--harmony-h) + 240);\n --hue-square-1: calc(var(--harmony-h) + 90); /* square: base, +90, +180, +270 */\n --hue-square-2: calc(var(--harmony-h) + 180);\n --hue-square-3: calc(var(--harmony-h) + 270);\n /* Harmonic chroma — a fixed vivid accent chroma so the palette stays colorful in EVERY theme (selenite +\n moonstone included; they get their color from the hue-0 anchor above, not from chroma). --mono-chroma is\n quieter (0.6×) for the subtle mono ramp. */\n --color-chroma: 0.15;\n --mono-chroma: calc(var(--color-chroma) * 0.6);\n /* (--color-* / --mono-* ready colors live in the mode-derived surfaces block below — their L is the\n mode envelope; the hue angles above are the mode-invariant part.) */\n /* (The solidify floor — the --glass-opacity \"component opacity\" dial — is composed INSIDE each sheer\n glass utility, at the element. Composing it here would bake :root's --glass-opacity into the value\n and silently ignore a per-element glass={{opacity}} — the var-composition gotcha again.) */\n /* Blur values — moderate by design; per-variant sizes (mode-invariant). */\n --blur: 2px;\n --blur-sm: 1px;\n --blur-lg: 8px;\n --blur-xl: 12px;\n --blur-frosted: 25px;\n /* Chakra reads as a THICK stone, so it diffuses more than crystal's near-clear 2px even though its\n body is more solid — the two axes are independent (how much light passes vs how scattered it is). */\n --blur-chakra: 6px;\n /* Crystal edge — identical in both modes, so it lives here with the anchors (the crystal floor +\n shadows are mode-varying: --glass-crystal-bg in the surfaces block below, shadows on :root/.dark). */\n --glass-crystal-border: oklch(100% calc(var(--glass-tint-c-hi) * 0.6) var(--glass-tint-h) / 0.3);\n /* Crystal gloss KNOBS — leaves only. The gloss itself (ink, the three gradients) and every stack that\n bakes it are composed in block C below, which also recomposes at each [data-gloss] scope.\n --glass-gloss-l / --glass-gloss-tint live in tokens.css (66 / 4.25) — do NOT re-declare here:\n engine.css imports AFTER tokens.css, so a duplicate at equal :root specificity silently wins and\n pure-CSS consumers get a stale gloss. */\n --glass-gloss-hue-span: 40; /* hue: degrees the iridescent sweep shifts up & down from the tint hue */\n /* hue: chroma of the iridescent stops, at the flavor's own L 74. Was 0.16, chosen believing an\n over-ceiling request is gamut-MAPPED down to the ceiling (\"holds 81% of that chroma\"). WebKit clips\n per channel instead (see --glass-gloss-c-max in tokens.css), so 0.16 pinned a channel on the\n tightest hues rather than delivering 81% of itself. 0.12 sits just under 0.1226 — aurora (h205) at\n L 74, the tightest across all 23 preset scopes (frescoes included) — so every hue in the sweep now\n renders what it asks for. */\n --glass-gloss-c: 0.12;\n --glass-gloss-hue-dir: 1; /* gloss direction (±1) — flips the TONAL streak diagonal (calc angle) + the HUE sweep order (span × dir); toggled in the crystal demo */\n /* The fresco slot, reset at every tint scope. The four fresco presets (frescoes.css, later in source\n order) point it at their multi-hue sheet; every other preset leaves it at `none`. Without the reset a\n single-hue preset scoped inside a fresco page (
on a muse page)\n inherited the page's sheet, and its crystal, chakra and opaque surfaces painted muse's sweep. */\n --glass-crystal-fresco: none;\n --blur-crystal: 2px;\n}\n\n/* B. MODE-DERIVED SURFACES — see the engine header above. Each tint-composing token is declared ONCE,\n parameterized by the mode knobs on bare :root/.dark; `.dark` in this selector keeps dark wrappers\n re-composing with their own knobs. */\n:root,\n.dark,\n[data-glass-tint] {\n /* Solid colored floor under the sheer gradient (see --glass-tint-a) — mode via --glass-wash-l/-c-mult. */\n --glass-tint-wash: oklch(var(--glass-wash-l) calc(var(--glass-tint-c) * var(--glass-wash-c-mult)) var(--glass-tint-h) / var(--glass-tint-a));\n /* Solid accent that tracks the FOREGROUND hue at a fixed vivid chroma — used by active controls\n (switch \"on\", slider range). The --accent-h/-c knob overrides apply in BOTH modes (the old dark\n twin dropped them — that was a bug). */\n --glass-accent: oklch(var(--glass-accent-l) var(--accent-c, var(--glass-accent-c)) var(--accent-h, var(--glass-fg-h)));\n /* Stained-glass — grayscale collapses the backdrop to pure LUMINANCE (akin to alpha: only lighter/\n darker survives), and the glass's OWN tint stack supplies the color — OKLCH-true to the theme, no\n hue-rotate approximation. --glass-stain-amount (0..1) sets how completely the backdrop's color is\n stripped; --glass-stain-invert (0..1, default 1) flips the surviving luminance so bright backdrop\n patches stop fighting dark tinted glass (set 0 to disable). The glass-stained utility and the switcher's Stained toggle both reference THIS token. */\n --glass-stain: grayscale(var(--glass-stain-amount, 1)) invert(var(--glass-stain-invert, 0));\n /* Colored glow for the `glow` prop — follows the foreground hue; override to recolor the halo. */\n --glass-glow: oklch(var(--glass-glow-lc) var(--glass-fg-h) / var(--glass-glow-a));\n /* Selected-control FILL (active tab, pressed toggle) — the surface twin of --active-shadow. Composed\n here, not on :root, because it reads the tint: a scoped data-glass-tint must re-resolve it, and it\n is why the chip tracks whichever theme the subtree carries. Mode knobs in tokens.css carry the two\n routes to the same look (light tints the fill, dark lets the surface show through a white lift). */\n --active-bg: oklch(var(--active-l) min(var(--glass-tint-c), var(--active-c-max)) var(--glass-tint-h) / var(--active-a));\n /* Solid PRIMARY ink (default Button, link text) — themed at the tint's OWN chroma (×1.2): selenite\n stays grey, moonstone whispers warm, jewels read clearly tinted. Lightness is the --primary-l mode knob\n (0.42 light / 0.78 dark). Chroma is min(tint × 1.2, --primary-c-max): the ×1.2 is intent — primary\n should read more saturated than the base tint — but uncapped it asked past the sRGB edge at five\n presets in light and two in dark, where the browser, not the theme, picked the delivered colour.\n Measured after capping: --primary-foreground holds Lc 88.1 light / 62.9 dark at the worst hue. */\n --primary: oklch(var(--primary-l) min(calc(var(--glass-tint-c) * 1.2), var(--primary-c-max)) var(--glass-fg-h));\n /* Ready accent-tier colors (hue angles from the anchors block; L is the mode envelope --harmonic-l). */\n --color-complement: oklch(var(--harmonic-l) var(--color-chroma) var(--hue-complement));\n --color-analogous-1: oklch(var(--harmonic-l) var(--color-chroma) var(--hue-analogous-1));\n --color-analogous-2: oklch(var(--harmonic-l) var(--color-chroma) var(--hue-analogous-2));\n --color-split-1: oklch(var(--harmonic-l) var(--color-chroma) var(--hue-split-1));\n --color-split-2: oklch(var(--harmonic-l) var(--color-chroma) var(--hue-split-2));\n --color-triad-1: oklch(var(--harmonic-l) var(--color-chroma) var(--hue-triad-1));\n --color-triad-2: oklch(var(--harmonic-l) var(--color-chroma) var(--hue-triad-2));\n /* Monochromatic — the harmony anchor hue at stepped lightness (a subtle, low-chroma ramp). */\n --mono-1: oklch(var(--mono-l-1) var(--mono-chroma) var(--harmony-h));\n --mono-2: oklch(var(--mono-l-2) var(--mono-chroma) var(--harmony-h));\n --mono-3: oklch(var(--mono-l-3) var(--mono-chroma) var(--harmony-h));\n /* Opaque glass — a fully solid, faintly tinted floor (no see-through) for the \"opaque\" style.\n --glass-opaque-l is the body-lightness knob (:root/.dark; the header L slider drives it; the floor\n AND AutoForeground's --foreground-opaque banding both read it, so they can't drift). */\n /* Chroma is CAPPED now (--glass-opaque-c-max, tokens.css). This floor sat at L 90.9 in light (it is\n L88 now, for the same reason) — near-white territory where the sRGB ceiling collapses — yet it was\n the one near-white surface\n multiplying RAW --glass-tint-c (×1.26) with no cap, while the sheet had --glass-tint-c-hi, chakra\n had --glass-chakra-c-max and the gloss had --glass-gloss-c-max. That made it the binding surface\n for 7 of the 12 jewels: it clipped first, and every preset's chroma budget was set by it. Capping\n it is what let the tints below be normalised to their hue's real capacity rather than to this\n floor's. Multiply-THEN-cap, matching --glass-gloss-ink. */\n /* The cap is scaled per mode (--glass-opaque-cap-scale): the per-preset caps are L88 ceilings, and the\n light floor now sits at L93, where they would overshoot. Composed here, not on :root, so a scoped\n preset's own cap is the one scaled. */\n --glass-opaque-bg: oklch(\n calc(var(--glass-opaque-l) * 1%)\n min(calc(var(--glass-tint-c) * var(--glass-opaque-c-scale)), calc(var(--glass-opaque-c-max, 0.3) * var(--glass-opaque-cap-scale, 1)))\n var(--glass-tint-h)\n );\n /* The same floor at the SOLIDIFY lightness/cap — the backing `glass` paints under every sheer\n material (see --glass-solidify in utilities.css). Split from --glass-opaque-bg because an opaque\n CARD and the backing under sheer glass are different surfaces with different jobs; tokens.css has\n the reasoning. Same scale, so a preset's tint still drives it; only the lightness and cap differ. */\n --glass-solidify-bg: oklch(\n calc(var(--glass-solidify-l, var(--glass-opaque-l)) * 1%)\n min(calc(var(--glass-tint-c) * var(--glass-opaque-c-scale)), var(--glass-solidify-c-max, var(--glass-opaque-c-max, 0.3))) var(--glass-tint-h)\n );\n /* Sheer glass sheet — hue/chroma from the tint vars, chroma scaled per stop so the highlights stay\n glassy and the body carries the color. Stop alphas ride the mode base --glass-sheet-a (+ fixed\n offsets; the top stop's offset is its own knob --glass-sheet-a1 — dark tapers it to 0.03);\n frosted = the same sheet lifted by --glass-frost-boost. */\n --glass-bg: linear-gradient(\n 135deg,\n oklch(100% calc(var(--glass-tint-c-hi) * 0.3) var(--glass-tint-h) / calc(var(--glass-sheet-a) + var(--glass-sheet-a1))) 0%,\n oklch(99% calc(var(--glass-tint-c-hi) * 0.5) var(--glass-tint-h) / calc(var(--glass-sheet-a) + 0.01)) 25%,\n oklch(97% calc(var(--glass-tint-c-hi) * 1) var(--glass-tint-h) / calc(var(--glass-sheet-a) + 0.02)) 50%,\n oklch(99% calc(var(--glass-tint-c-hi) * 0.5) var(--glass-tint-h) / var(--glass-sheet-a)) 75%,\n oklch(95% calc(var(--glass-tint-c-hi) * 1.3) var(--glass-tint-h) / calc(var(--glass-sheet-a) + 0.01)) 100%\n );\n --glass-frosted-bg: linear-gradient(\n 135deg,\n oklch(100% calc(var(--glass-tint-c-hi) * 0.3) var(--glass-tint-h) / calc(var(--glass-sheet-a) + var(--glass-frost-boost) + 0.04)) 0%,\n oklch(99% calc(var(--glass-tint-c-hi) * 0.5) var(--glass-tint-h) / calc(var(--glass-sheet-a) + var(--glass-frost-boost) + 0.01)) 25%,\n oklch(97% calc(var(--glass-tint-c-hi) * 1) var(--glass-tint-h) / calc(var(--glass-sheet-a) + var(--glass-frost-boost) + 0.02)) 50%,\n oklch(99% calc(var(--glass-tint-c-hi) * 0.5) var(--glass-tint-h) / calc(var(--glass-sheet-a) + var(--glass-frost-boost))) 75%,\n oklch(95% calc(var(--glass-tint-c-hi) * 1.3) var(--glass-tint-h) / calc(var(--glass-sheet-a) + var(--glass-frost-boost) + 0.01)) 100%\n );\n /* Very subtle white border — the lit edge of a cut sheet (alpha is the mode dial) */\n --glass-border: oklch(100% calc(var(--glass-tint-c-hi) * 0.5) var(--glass-tint-h) / var(--glass-border-a));\n /* Opaque CHROME edge — the shadcn --border/--input role tokens (dividers, ghost/input outlines, any\n bare `border-border`). Previously flat neutral (oklch(0.88 0 0) / oklch(0.3 0 0)), which made them\n the ONLY chrome in the theme that did not move with the tint: a ghost Button kept a grey edge under\n lapis while its `outline` sibling (border-foreground/20) tracked the theme. Lightness moves only in\n LIGHT mode (0.88 → --border-l 0.85) to buy chroma headroom; see the knob comment in tokens.css.\n Chroma is min(tint chroma, --border-c-max) — a CAP, deliberately not --glass-tint-c-hi and not a\n multiplier. --glass-tint-c-hi (0.025) is the NEAR-WHITE cap, sized for the L 95–100% sheet stops\n and crystal floor; borrowing it here spent only 66% of the ceiling on amethyst and 17% on peridot,\n which is why the edge still read grey. A flat multiplier fails the other way: lapis declares\n c 0.15, so any factor small enough to keep it in gamut pushes the 0.07 jewels below where they\n started. min() gives every preset its full declared chroma up to the ceiling floor and clamps only\n the ones that overreach — and leaves c 0 at exactly 0, so selenite stays the neutral theme it is.\n Declaring them HERE (rather than on :root) also puts them under scripts/check-gamut.mjs, which\n scrapes engine.css for both the calc() and min() chroma shapes — the suite went 20 → 24 surface/mode\n pairs when these landed, so a later retune of the caps is measured, not asserted.\n SURFACE hue (--glass-tint-h), not --glass-fg-h: an edge belongs to the surface it bounds, matching\n --glass-border / --glass-crystal-border. In a fresco that splits the two (Muse: surface 230,\n text 290) the border follows the panel, not the type. */\n --border: oklch(var(--border-l) min(var(--glass-tint-c), var(--border-c-max)) var(--glass-tint-h));\n --input: oklch(var(--input-l) min(var(--glass-tint-c), var(--border-c-max)) var(--glass-tint-h));\n /* The remaining shadcn SURFACE tokens — the last chroma-0 values in the theme. Same shape as --border\n for the same reason: a toggle's on-state, a hover wash and a sidebar panel all take their fill from\n these, so while they were flat neutrals those surfaces read near-white in light and near-black in\n dark in every preset. Each carries its OWN cap because the sRGB ceiling is a function of lightness\n and these sit from L 18 to L 98 — a shared cap would have to be the smallest of them (0.009, at the\n near-white sidebar) and would tint nothing. See the knobs in tokens.css. */\n --accent: oklch(var(--accent-bg-l) min(var(--glass-tint-c), var(--accent-bg-c-max)) var(--glass-tint-h));\n /* The hover wash as an IMAGE layer (glass-hover-accent). Translucent so the material still reads\n through it — a hover that paints a flat fill stops looking like glass. */\n --glass-hover-wash: color-mix(in oklch, var(--accent) 70%, transparent);\n --secondary: oklch(var(--secondary-l) min(var(--glass-tint-c), var(--secondary-c-max)) var(--glass-tint-h));\n --muted: oklch(var(--muted-l) min(var(--glass-tint-c), var(--muted-c-max)) var(--glass-tint-h));\n --sidebar: oklch(var(--sidebar-l) min(var(--glass-tint-c), var(--sidebar-c-max)) var(--glass-tint-h));\n /* Theme-aware brand gradient — follows --glass-fg-h, from the hue 3 ramp-steps out\n (8/side → step 360/17 ≈ 63.53°) to the base hue, at the mode envelope --gradient-l/-c + 0.5 alpha.\n (Axis/direction variants are computed in JS from lib/oklch-utils.ts — see the /components demo.) */\n --gradient: linear-gradient(\n 135deg,\n oklch(var(--gradient-l) var(--gradient-c) calc(var(--glass-fg-h) + 63.53) / 0.5) 0%,\n oklch(var(--gradient-l) var(--gradient-c) var(--glass-fg-h) / 0.5) 100%\n );\n /* Full-opacity twin of --gradient for gradient TEXT (background-clip: text) — no wash alpha so glyphs stay crisp. */\n --gradient-text: linear-gradient(\n 135deg,\n oklch(var(--gradient-l) var(--gradient-c) calc(var(--glass-fg-h) + 63.53)) 0%,\n oklch(var(--gradient-l) var(--gradient-c) var(--glass-fg-h)) 100%\n );\n /* High-contrast gradient text for printing ON a tinted theme surface: DARK tint-hue gradient in light\n mode, LIGHT one in .dark (--gradient-contrast-l/-c), so legibility comes from lightness. */\n --gradient-text-contrast: linear-gradient(\n 135deg,\n oklch(var(--gradient-contrast-l) var(--gradient-contrast-c) calc(var(--glass-fg-h) + 63.53)) 0%,\n oklch(var(--gradient-contrast-l) var(--gradient-contrast-c) var(--glass-fg-h)) 100%\n );\n /* (The crystal floor, --glass-crystal-bg, is in block C: the hue gloss flavor repaints it.) */\n /* ── Chakra — the step-cut gem surface ────────────────────────────────────────────────────────\n BODY: the stone itself. The one tint-composing token here, so it is the only one with a gamut\n budget — min() against --glass-chakra-c-max (not a multiplier) for the same reason --border uses\n one: lapis declares c 0.15 and would be gamut-mapped, while any multiplier small enough to hold it\n drags the 0.07 jewels below where they should sit. Caps are measured per mode against the tightest\n ceiling across all 17 presets, so every preset delivers 100% of its declared chroma. */\n --glass-chakra-bg: oklch(\n calc(var(--glass-chakra-l) * 1%) min(var(--glass-tint-c), var(--glass-chakra-c-max)) var(--glass-tint-h) /\n var(--glass-chakra-a)\n );\n /* (The chakra SURFACE STACK, --glass-chakra-stack-bg, is in block C: it bakes the gloss.) */\n /* THE CUT — four sides, two steps, as INSET SHADOWS rather than inset background layers.\n A background layer is always a RECTANGLE: background-size shrinks it but gives it no radius of its\n own, so an inset gradient paints square corners over a rounded surface — a card with hard-cornered\n rectangles stacked on it. box-shadow has no such problem; an inset shadow is the border box's own\n rounded rect, offset, so every band follows --radius at whatever value the element carries, for\n free and at any size.\n Each side is a pair. The step-1 band (offset --glass-chakra-step) paints OVER the step-2 band\n (offset twice that), so the visible result is two concentric bands per side: 0→N shows step 1\n composited on step 2, N→2N shows step 2 alone. That is the step cut, and because step 2 is\n literally the same colour at half alpha it cannot drift out of sync with step 1.\n Order matters — box-shadow paints first-listed on top, so every step-1 band precedes its step-2\n twin. Corners: adjacent bands overlap and composite rather than mitering to a hard 45° line, which\n is the trade for following the radius. On a gem that reads as the corner facet catching both\n neighbours, and it is what the existing --glass-shadow bevel already does at top and bottom. */\n --glass-chakra-facets:\n inset 0 var(--glass-chakra-step) 0 oklch(var(--shadow-highlight) / var(--glass-chakra-facet-t-a)),\n inset var(--glass-chakra-step) 0 0 oklch(var(--shadow-highlight) / var(--glass-chakra-facet-l-a)),\n inset calc(var(--glass-chakra-step) * -1) 0 0 oklch(var(--shadow-ink) / var(--glass-chakra-facet-r-a)),\n inset 0 calc(var(--glass-chakra-step) * -1) 0 oklch(var(--shadow-ink) / var(--glass-chakra-facet-b-a)),\n inset 0 calc(var(--glass-chakra-step) * 2) 0 oklch(var(--shadow-highlight) / calc(var(--glass-chakra-facet-t-a) * 0.5)),\n inset calc(var(--glass-chakra-step) * 2) 0 0 oklch(var(--shadow-highlight) / calc(var(--glass-chakra-facet-l-a) * 0.5)),\n inset calc(var(--glass-chakra-step) * -2) 0 0 oklch(var(--shadow-ink) / calc(var(--glass-chakra-facet-r-a) * 0.5)),\n inset 0 calc(var(--glass-chakra-step) * -2) 0 oklch(var(--shadow-ink) / calc(var(--glass-chakra-facet-b-a) * 0.5));\n /* Edge + depth: the standard drop plus the cut. --glass-shadow's own top/bottom bevel is left out —\n the facet bands ARE the bevel, and doubling them would flatten the step read. */\n --glass-chakra-border: oklch(100% calc(var(--glass-tint-c-hi) * 0.5) var(--glass-tint-h) / var(--glass-border-a));\n --glass-chakra-shadow: var(--glass-shadow), var(--glass-chakra-facets);\n --glass-chakra-shadow-hover: var(--glass-shadow-lg), var(--glass-chakra-facets);\n /* Precomposed background STACKS — the background-image compositions, single-sourced. The surface\n utilities consume them via --srf-* fallbacks (sheer utilities append the element-composed solidify\n layer underneath), and the [data-glass] style remaps below swap them wholesale. Live here so they\n re-resolve per tint scope and per mode wrapper. The crystal stack bakes the gloss into the\n background (the site-wide re-skin can't use ::before/::after — those need position:relative, which\n would break sticky/fixed surfaces). */\n --glass-wash-layer: linear-gradient(var(--glass-tint-wash), var(--glass-tint-wash));\n --glass-stack-bg: var(--glass-bg), var(--glass-wash-layer);\n /* Frosted rides the wash in the IMAGE stack (sheet + wash), leaving the background-color channel\n free for the veil floor. */\n --frosted-stack-bg: var(--glass-frosted-bg), var(--glass-wash-layer);\n /* (--crystal-stack-bg is in block C: it bakes the gloss.) */\n}\n\n/* C. GLOSS — the crystal highlight and everything that bakes it: the ink, the three gradients, the\n crystal floor, and the crystal + chakra stacks. Declared on every scope that can CHANGE it: :root and\n .dark (the mode knobs), [data-glass-tint] (the tint), and [data-gloss] (the flavor). The last is what\n lets `data-gloss` work below : these tokens are composed values, which bake where they are\n declared, so a [data-gloss] section that only re-pinned an input (as the flavor blocks used to) had\n its children inherit 's gradients and stacks pre-composed, and painted the page's gloss.\n THREE FLAVORS, chosen by two plain switches the [data-gloss] blocks in materials.css set to `1` or\n `initial` (unset = tonal):\n white → flat white specular (the classic glass shine) --glass-gloss-is-white: 1\n tonal → a tonally-close single-hue tint of the theme (--glass-gloss-l / --glass-gloss-tint)\n hue → iridescent: the highlight sweeps hues shifted ± --glass-gloss-hue-span around the tint\n hue, and repaints the crystal floor in the sweep --glass-gloss-is-hue: 1\n A switch selects nothing by itself: each flavor's variant (block C′ below) multiplies an alpha by it,\n so with the switch `initial` the variant is invalid at computed-value time and the var() fallback here,\n the tonal shape, applies. The switches are leaves, so they inherit, and a nested [data-gloss] sets both\n to pick its own flavor; a nested [data-glass-tint] recomposes from the inherited switches and keeps the\n page's flavor (it used to reset it to tonal). Each layer applies its own alpha. */\n:root,\n.dark,\n[data-glass-tint],\n[data-gloss] {\n /* The tonal ink as its own token (not inline in the fallback below) so check-gamut's scraper, which\n reads `--x: ` declarations, still sweeps it. */\n --glass-gloss-ink-tonal: calc(var(--glass-gloss-l, 94) * 1%)\n min(calc(var(--glass-tint-c) * var(--glass-gloss-tint, 2)), var(--glass-gloss-c-max, 0.3)) var(--glass-tint-h);\n --glass-gloss-ink: var(--glass-gloss-ink-white, var(--glass-gloss-ink-tonal));\n --glass-gloss-top: var(--glass-gloss-top-hue, linear-gradient(to bottom, oklch(var(--glass-gloss-ink, 100% 0 0) / 0.4), transparent 30%));\n --glass-gloss-streak: var(\n --glass-gloss-streak-hue,\n linear-gradient(calc(135deg * var(--glass-gloss-hue-dir, 1)), oklch(var(--glass-gloss-ink, 100% 0 0) / 0.15), transparent 50%)\n );\n --glass-gloss-glow: var(\n --glass-gloss-glow-hue,\n radial-gradient(circle at 50% 120%, oklch(var(--glass-gloss-ink, 100% 0 0) / 0.2), transparent 70%)\n );\n /* Crystal floor — clear glass; the alpha is the mode dial (--glass-crystal-bg-a).\n Crystal body. The lightness is a KNOB, not 100%, and that is the whole point: at L100 sRGB holds\n ZERO chroma, so this surface asked for 0.015 and delivered exactly none — crystal rendered pure\n white under every preset, in both modes (measured: #fffdff amethyst, #fffef4 moonstone,\n #fbffff lapis; RGB spread 2–11). check-gamut even reported it as \"no attainable chroma at all\"\n and skipped the pair rather than failing it.\n --glass-crystal-l is 96 because that is the lowest lightness at which the TIGHTEST of the 21 hues\n this theme uses (h268, lapis) can still carry the 0.015 this asks for: ceiling 0.0182, 1.22x\n headroom. L96.5 is the exact crossover at 1.06x, too thin given maxChromaFor already sits 3% inside\n the boundary to stop Safari clamping toward grey. The cost is small — at the 0.3 light-mode alpha\n the composite moves ~1.2 L, and 0.4 L in dark. */\n --glass-crystal-bg: var(\n --glass-crystal-bg-hue,\n oklch(calc(var(--glass-crystal-l) * 1%) calc(var(--glass-tint-c-hi) * 0.6) var(--glass-tint-h) / var(--glass-crystal-bg-a))\n );\n /* SURFACE STACKS: crystal and chakra bake the same gloss triple, so `data-gloss` white / tonal / hue\n reskin both, over a fresco slot and the tint wash. Nothing in the chakra stack is inset, so nothing\n has a corner of its own — the element's border-radius clips the whole stack (see the facet bands in\n block B). Baked into the background, not ::before/::after: the crystal PAGE style re-skins every\n surface, and pseudo layers would need position:relative, which breaks sticky/fixed surfaces. */\n --crystal-stack-bg:\n var(--glass-gloss-top), var(--glass-gloss-glow), var(--glass-gloss-streak), var(--glass-crystal-fresco, none), var(--glass-wash-layer);\n --glass-chakra-stack-bg:\n var(--glass-gloss-top), var(--glass-gloss-glow), var(--glass-gloss-streak), var(--glass-crystal-fresco, none), var(--glass-wash-layer);\n}\n\n/* C′. The FLAVOR VARIANTS block C falls back from, each valid only while its switch is `1`. Kept in a\n sibling block (same scopes) so scripts/check-gamut.mjs can model the hue variants at the hue flavor's\n own L74 (the [data-gloss=\"hue\"] block pins --glass-gloss-l there) without misreading block C's tonal\n ink at it.\n hue: each highlight sweeps (tint hue − span) → tint hue → (+ span), interpolated IN OKLCH so the arc\n passes through the base hue. Top leans to the low end, glow to the high end, streak spans both. The\n floor is replaced by a hue-carrying one at the gloss lightness (so Light deepens it): the default\n crystal floor sits near white, where chroma collapses, so the sweep would read over a white sheet. */\n:root,\n.dark,\n[data-glass-tint],\n[data-gloss] {\n --glass-gloss-ink-white: calc(var(--glass-gloss-is-white) * 100%) 0 0;\n --glass-gloss-top-hue: linear-gradient(\n to bottom in oklch,\n oklch(\n calc(var(--glass-gloss-l, 94) * 1%) var(--glass-gloss-c, 0.12)\n calc(var(--glass-tint-h) - var(--glass-gloss-hue-span, 40) * var(--glass-gloss-hue-dir, 1)) /\n calc(0.4 * var(--glass-gloss-is-hue))\n ),\n transparent 35%\n );\n --glass-gloss-streak-hue: linear-gradient(\n 115deg in oklch,\n oklch(\n calc(var(--glass-gloss-l, 94) * 1%) var(--glass-gloss-c, 0.12)\n calc(var(--glass-tint-h) - var(--glass-gloss-hue-span, 40) * var(--glass-gloss-hue-dir, 1)) /\n calc(0.32 * var(--glass-gloss-is-hue))\n )\n 0%,\n oklch(calc(var(--glass-gloss-l, 94) * 1%) var(--glass-gloss-c, 0.12) var(--glass-tint-h) / calc(0.12 * var(--glass-gloss-is-hue))) 45%,\n oklch(\n calc(var(--glass-gloss-l, 94) * 1%) var(--glass-gloss-c, 0.12)\n calc(var(--glass-tint-h) + var(--glass-gloss-hue-span, 40) * var(--glass-gloss-hue-dir, 1)) /\n calc(0.32 * var(--glass-gloss-is-hue))\n )\n 85%,\n transparent\n );\n --glass-gloss-glow-hue: radial-gradient(\n circle at 50% 120%,\n oklch(\n calc(var(--glass-gloss-l, 94) * 1%) var(--glass-gloss-c, 0.12)\n calc(var(--glass-tint-h) + var(--glass-gloss-hue-span, 40) * var(--glass-gloss-hue-dir, 1)) /\n calc(0.25 * var(--glass-gloss-is-hue))\n ),\n transparent 70%\n );\n --glass-crystal-bg-hue: oklch(\n calc(var(--glass-gloss-l, 94) * 1%) calc(var(--glass-tint-c) * 1.2) var(--glass-tint-h) /\n calc(0.4 * var(--glass-gloss-is-hue))\n );\n}\n/* ── Glass surface utilities (@utility — tree-shaken per consumer usage) ──────\n The material system: ONE structural `glass` utility + orthogonal axis classes (border, veil, sm/lg,\n gradient, glow, sheen). Materials live in materials.css as [data-material] token sets; page styles\n in styles.css remap the same --srf-* tokens. No recipe utilities, no !important. */\n\n/* The glow itself — a punchy two-layer colored halo, centralized so its strength is tunable in ONE\n place. `glass-glow` is the resting glow; `glass-glow-lg` (used on hover) intensifies it. Both set\n --glow-layer, which every glass box-shadow folds in, so the glow shows under EVERY glass style.\n Full mechanism + tuning + troubleshooting: docs/glow.md */\n@utility glass-glow {\n --glow-layer: 0 0 8px 1px var(--glass-glow), 0 0 18px 3px var(--glass-glow);\n}\n@utility glass-glow-lg {\n --glow-layer: 0 0 12px 2px var(--glass-glow), 0 0 28px 5px var(--glass-glow);\n}\n\n/* ── THE MATERIAL SYSTEM (Sistine 2.0) ────────────────────────────────────────\n ONE structural utility (`glass`) + orthogonal axis classes. The material (a token set) comes from\n [data-material] (materials.css) or the inherited [data-glass] page remap (styles.css); with\n neither, the --srf-* fallbacks render the default glass material. Axis utilities only SET custom\n props (or properties `glass` never touches), so Tailwind's emission order can never flip a result.\n (The legacy recipe utilities below coexist during the migration and die with it.) */\n\n/* Structural glass — background/filter/shadow plumbing only; borderless (add glass-border).\n Channels, outermost first:\n background-image = [--srf-hover state slot] + [--acc-gradient accent slot] + [--srf-bg-image material stack] + [solidify]\n background-color = --veil-floor (element veil) > --srf-bg-color (material floor) > transparent\n backdrop-filter = --srf-filter (material) > blur(--srf-blur > --blur) saturate(180%)\n box-shadow = --srf-shadow (material) > --srf-elev (size axis) > --glass-shadow, + glow\n The leading gradient slot is a no-op layer so `glass-gradient` composes over ANY material (same\n pattern as the fresco slot inside --crystal-stack-bg). Both no-op slots resolve to `none` rather\n than linear-gradient(transparent, transparent): `none` is a valid , holds the layer\n position identically, and paints nothing — where a transparent GRADIENT still goes through the\n gradient rasteriser (and its dither) to deliver zero pixels.\n The solidify floor (--glass-opacity dial) is composed HERE, at the element — never in a token\n context (the var-composition-resolves-at-declaration gotcha; see scripts/check-theme.mjs). The\n --srf-solidify slot lets a material DROP that layer when it is provably a no-op (the opaque material,\n and the opaque PAGE style, which sets it on its own scope so it reaches `@apply glass` surfaces that\n carry no `.glass` class); materials that want the normal floor pin the slot back to `initial`\n (guaranteed-invalid → the var() fallback composes --glass-solidify at the element). --veil-solidify\n leads the chain: glass-veil sets it (non-inheriting), because a VEILED surface paints the neutral\n veil floor in its background-colour channel, so the layer is real there whatever the slot says. */\n@utility glass {\n /* Fallback 0.7, not 0: every glass surface now sits on a 70% solidify floor unless a consumer dials\n it back. Measured before changing it, because AutoForeground does NOT read --glass-opacity — it\n bands text against the sheer floor, so a more solid surface than it modelled could have cost\n contrast. Body text goes 91.5 -> 86.5 Lc in light and 88.0 -> 85.8 in dark, both far above the\n 75 body floor (it still clears at a full 1.0). Set --glass-opacity: 0 for the old sheer default. */\n /* --glass-solidify-BG, not --glass-opaque-bg: the backing under sheer glass is its own surface now\n (engine.css / tokens.css). In dark the two resolve identically, so this is a light-mode change. */\n --glass-solidify-fill: color-mix(in oklch, var(--glass-solidify-bg, var(--glass-opaque-bg)) calc(var(--glass-opacity, 0.7) * 100%), transparent);\n --glass-solidify: linear-gradient(var(--glass-solidify-fill), var(--glass-solidify-fill));\n /* --srf-hover leads the stack because a hover/selection wash has to sit ON the material, not under it.\n `hover:bg-accent` sets background-COLOR, which is the bottom of the box — beneath all 14 image layers\n the glass stack paints, of which the solidify floor alone is 70% opaque. At most ~30% of the accent\n survived to the eye, so the hover read as the material's own colour (white in light mode) no matter\n what --accent was set to. No amount of tuning the token could fix that; it is a layer-order problem.\n Same no-op `none` slot pattern as --acc-gradient below it. */\n background-image:\n var(--srf-hover, none), var(--acc-gradient, none), var(--srf-bg-image, var(--glass-stack-bg)),\n var(--veil-solidify, var(--srf-solidify, var(--glass-solidify)));\n background-color: var(--veil-floor, var(--srf-bg-color, transparent));\n /* Author ONLY the standard backdrop-filter (no -webkit- twin: Lightning dedupes the pair keeping\n the PREFIXED one, which Chromium never aliased — the [no-webkit-twin] invariant guards this).\n The max() composes the diffuse FLOOR for adaptive surfaces. */\n backdrop-filter: var(--srf-filter, blur(max(var(--srf-blur, var(--blur)), var(--dif, 0px))) saturate(180%)) var(--srf-stain,);\n box-shadow: var(--srf-shadow, var(--srf-elev, var(--glass-shadow))), var(--glow-layer, 0 0 0 0 transparent);\n /* THE EDGE, resolved once per surface for everything that draws one (glass-border, the opaque page's\n uniform edge, the page outline). Precedence, strongest first: the element's own ink colour or\n rim/frame weight, then the PAGE OUTLINE (--glass-outline / --glass-outline-w, set on any ancestor;\n --glass-opaque-outline[-w] are the old opaque-only names, still read), then the material's own edge\n (--srf-border-color / --srf-border-w), then the plain glass hairline. Element-composed, so an outline\n set on a subtree resolves where it is read. */\n --srf-edge: var(--brd-ink, var(--glass-outline, var(--glass-opaque-outline, var(--srf-border-color, var(--glass-border)))));\n --srf-edge-w: var(--brd-w, var(--glass-outline-w, var(--glass-opaque-outline-w, var(--srf-border-w, 1px))));\n}\n\n/* Border axis — the material picks the color unless the element asks for ink (--brd-ink, the\n borderColor axis below); width resolves material pin (--srf-border-w, which itself reads the --brd-w\n weight slot) → weight slot directly (adaptive surfaces) → 1px hairline. */\n@utility glass-border {\n border: var(--srf-edge-w, 1px) solid var(--srf-edge, var(--glass-border));\n}\n/* Border WEIGHTS — token setters for the --brd-w slot (order-safe; same-element composition, like\n glass-sm's --srf-blur). Hairline is the unmarked default (1px; 0.5px under frosted); rim = 2px, frame = 4px. */\n@utility glass-border-rim {\n --brd-w: 2px;\n}\n@utility glass-border-frame {\n --brd-w: 4px;\n}\n/* Border COLOUR — ink. The material edge is a near-white rim at the material's alpha: it reads as cut\n glass on a dark page and as nothing at all on most light themes, at hairline, rim or frame weight\n alike, so a weight axis whose colour disappears went unused. Ink is the element's own text colour\n (--foreground, after any per-material text remap on this element), mixed toward transparent by\n --glass-border-ink-a: a visible line anywhere text is legible. Element-composed and registered\n non-inheriting (registrations.css), so a child border keeps its own material colour. */\n@utility glass-border-ink {\n --brd-ink: color-mix(in oklch, var(--foreground) calc(var(--glass-border-ink-a, 0.5) * 100%), transparent);\n}\n\n/* Diffuse axis — a READABILITY BLUR FLOOR (not a blur value): every translucent material computes\n blur(max(, --dif)), so crystal stays crystal until a text-dense surface opts in.\n Token-setter (order-safe); OPAQUE never reads the slot (--srf-filter: none), so the short-circuit\n is inherited — no GPU spend on solid floors. Tune the floor via --glass-diffuse. */\n@utility glass-diffuse {\n --dif: var(--glass-diffuse, 12px);\n}\n\n/* Veil axis — the legibility floor for read-through overlays (menus, tooltips, toasts).\n Composed AT THE ELEMENT so a scoped --glass-solid-a resolves here (check-theme invariant 2b).\n The veil rides the background-COLOR channel, under the material's image stack, and — being\n element-declared — beats any inherited --srf-bg-color remap. */\n/* Stained axis — REAL stained-glass optics: grayscale collapses the backdrop to pure luminance\n (lighter/darker only, akin to alpha), so the glass's OWN tint supplies every drop of color and\n whatever sits behind renders as tonal shades of the theme. One color-matrix op — free next to the\n blur. --glass-stain-amount (0..1) dials how completely the backdrop's color is stripped. */\n@utility glass-stained {\n --srf-stain: var(--glass-stain);\n}\n\n@utility glass-veil {\n --veil-floor: oklch(var(--glass-solid-l) 0 0 / var(--glass-solid-a));\n /* The veil keeps the solidify layer even where the material or page dropped it (--srf-solidify: none):\n those drops are only no-ops over the opaque floor, and a veiled surface paints the veil instead. */\n --veil-solidify: var(--glass-solidify);\n}\n\n/* THE PAGE BACKDROP LAYER — the fixed, full-viewport ground every sheer material blurs. Put this on the\n `fixed inset-0 -z-10` container each background component renders.\n It exists to force a compositing layer. `backdrop-filter` re-samples everything painted beneath it,\n and this layer sits at z-index -10 under the entire document; unpromoted, the compositor has to\n re-rasterise it against the scroll position every frame, which is where the scroll artifact on the\n sheer materials comes from. `opaque` is the ONE material that sets --srf-filter: none — and the one\n material that never showed the artifact, which is the correlation that points here.\n Measured on the deployed page, this element resolved will-change:auto / transform:none /\n contain:none — nothing was promoting it. Promoting is safe specifically because it has ZERO\n position:fixed descendants and no background-attachment:fixed; a transform/will-change would\n otherwise become their containing block and reparent them. Re-check that before adding children.\n will-change (rather than a translateZ hack) because the hint is honest here: this layer is on screen\n and being sampled for the whole session, so there is nothing to hint OFF later. */\n@utility glass-backdrop-layer {\n will-change: transform;\n}\n\n/* Size axes — blur + elevation tiers. These feed the GLASS material only: frosted/crystal/opaque\n (and page remaps) set --srf-filter/--srf-shadow wholesale, which flattens the tiers — matching\n the old system, where the [data-glass] styles flattened glass-surface-sm/-lg. */\n@utility glass-sm {\n --srf-blur: var(--blur-sm);\n --srf-elev: var(--glass-shadow-sm);\n}\n@utility glass-lg {\n --srf-blur: var(--blur-lg);\n --srf-elev: var(--glass-shadow-lg);\n}\n@utility glass-xl {\n --srf-blur: var(--blur-xl);\n --srf-elev: var(--glass-shadow-lg);\n}\n\n/* ── Gradient ACCENT — a brand wash ON a material, not a material. Fills the no-op --acc-gradient\n slot at the top of every `glass` background, so it composes over glass, frosted, crystal, and\n opaque alike. A token-setter (no property `glass` sets), so it's immune to utility emission order. */\n@utility glass-gradient {\n --acc-gradient: var(--gradient);\n}\n\n/* ── Hover/selection WASH — the interactive twin of glass-gradient. Fills the --srf-hover slot at the\n very top of the background stack, which is the only place a state colour is actually visible on a\n glass surface: `hover:bg-accent` writes background-COLOR, and every one of the material's image\n layers paints over it (solidify alone is 70% opaque), so the accent arrived at ~30% strength blended\n into the material and read as plain white on a light theme.\n The wash is deliberately translucent rather than a flat fill — glass that turns solid on hover stops\n looking like glass. 70% of --accent over the material keeps the wash, gloss and blur reading through\n it while still shifting the surface unmistakably toward the theme colour.\n Pair with `hover:bg-accent` rather than replacing it: the background-color still does the work on the\n NON-glass variants (outline/default paint no material, so nothing covers it there). */\n@utility glass-hover-accent {\n &:hover {\n --srf-hover: linear-gradient(var(--glass-hover-wash), var(--glass-hover-wash));\n }\n}\n\n/* Sheen — the OPT-IN hover shimmer (the gloss slides across on hover). Self-contained (highlight + diagonal\n + glow + the 200%/hover shift), so it works on any glass surface AND, layered on glass-crystal (which\n already has the static gloss), its ::before/::after override crystal's to add the motion. Pseudo-element\n based → position:relative; avoid on sticky/fixed surfaces. */\n@utility glass-sheen {\n position: relative;\n\n &::before,\n &::after {\n content: \"\";\n position: absolute;\n inset: 0;\n border-radius: inherit;\n pointer-events: none;\n }\n\n &::before {\n background: var(--glass-gloss-top), var(--glass-gloss-streak);\n background-size: 200% 200%;\n background-position: 0% 0%;\n opacity: 0.7;\n transition: background-position 0.3s ease;\n }\n\n &::after {\n background: var(--glass-gloss-glow);\n opacity: 0.8;\n }\n\n &:hover::before {\n background-position: 100% 100%;\n }\n}\n\n/* Namespaced: a bare `shimmer` is a global name, and a consumer's own @keyframes shimmer (or another\n library's) replaced this one for everyone, whichever stylesheet loaded last. Animates `translate`, not\n `transform`: Tailwind v4's `-translate-x-full` (the skeleton's resting position) writes the `translate`\n property, which COMPOSES with an animated transform rather than being replaced by it, so the sweep ran\n from -200% to 0% and parked the highlight on top of the skeleton. Animating the same property replaces\n the resting value while running, and with the animation off it falls back to that resting value. */\n@keyframes sistine-shimmer {\n from {\n translate: -100% 0;\n }\n to {\n translate: 100% 0;\n }\n}\n\n/* ── Reduced motion ───────────────────────────────────────────────────────────\n CONTRIBUTING says \"Respect prefers-reduced-motion\"; the pattern scenes did and nothing else did.\n This closes that, but NOT with the blunt global `*, ::before, ::after { animation: none !important }`\n reset — that would also flatten the pattern scenes' own carefully scoped rules and every enter/exit\n transition in the library.\n\n The treatment differs by what the animation actually IS, because the preference is about MOTION, not\n about animation in general:\n - shimmer → translation across a surface. Real motion, purely decorative: stopped. The skeleton\n still reads as a skeleton from its base fill.\n - spin → rotation. Real motion, but it is the ONLY signal that a loader is alive, so freezing\n it reads as \"hung\" rather than \"calm\". Slowed hard instead of stopped.\n - pulse → opacity only. Nothing moves, so there is nothing to trigger a vestibular response.\n Left alone deliberately; it also carries meaning (the input-otp caret is a blink).\n Unlayered on purpose: Tailwind's generated utilities sit inside @layer utilities, and an unlayered\n rule outranks any layered one regardless of order, so this needs neither !important nor a specificity\n hack. */\n@media (prefers-reduced-motion: reduce) {\n .animate-\\[sistine-shimmer_2s_infinite\\],\n .before\\:animate-\\[sistine-shimmer_2s_infinite\\]::before {\n animation: none;\n }\n\n .animate-spin {\n animation-duration: 3s;\n }\n}\n/* ── Materials — [data-material] token sets + gloss flavors ───────────────────\n Four materials, one attribute. Each block declares the FULL --srf-* pin set ON the element, so an\n explicit material always beats an inherited [data-glass] page remap (element-declared custom props\n beat inherited ones). One material per element is structural: attributes can't stack, so there is\n no !important anywhere in the system.\n NOTE these are inherited custom properties — a data-material element also acts as a SCOPED default\n for adaptive `glass` descendants (the mechanism DialogContent's old data-glass hack hand-rolled).\n Pin a child back with its own data-material.\n Import order: after tokens.css — [data-gloss=\"hue\"] re-pins --glass-gloss-l and the crystal shadows,\n which tokens.css declares on :root/.dark at equal specificity, so source order decides on . */\n\n[data-material=\"glass\"] {\n /* Solidify slot: `initial` = guaranteed-invalid, so `glass` falls back to composing --glass-solidify\n AT THE ELEMENT (never inherited pre-composed — the var-composition gotcha). Pinned so an opaque\n PAGE remap can never leak its dropped layer into an explicit material. */\n --srf-solidify: initial;\n --srf-bg-image: var(--glass-stack-bg);\n --srf-bg-color: transparent;\n /* Composed on the element, so the glass-sm/glass-lg axes still resolve here (same-element\n setter/reader is safe — the composition gotcha only bites pre-composed inherited values). */\n --srf-filter: blur(max(var(--srf-blur, var(--blur)), var(--dif, 0px))) saturate(180%);\n --srf-border-color: var(--glass-border);\n --srf-border-w: var(--brd-w, 1px);\n --srf-shadow: var(--srf-elev, var(--glass-shadow));\n}\n\n/* Frosted — the tint wash rides the IMAGE stack (--frosted-stack-bg = sheet + wash), leaving the\n background-color channel free for the veil floor. */\n[data-material=\"frosted\"] {\n /* Solidify slot: `initial` = guaranteed-invalid, so `glass` falls back to composing --glass-solidify\n AT THE ELEMENT (never inherited pre-composed — the var-composition gotcha). Pinned so an opaque\n PAGE remap can never leak its dropped layer into an explicit material. */\n --srf-solidify: initial;\n --srf-bg-image: var(--frosted-stack-bg);\n --srf-bg-color: transparent;\n --srf-filter: blur(max(var(--blur-frosted), var(--dif, 0px))) saturate(200%);\n --srf-border-color: var(--glass-frosted-border);\n --srf-border-w: var(--brd-w, 0.5px);\n --srf-shadow: var(--glass-frosted-shadow);\n}\n\n/* Crystal — gloss BAKED into the background stack (sticky/fixed-safe; no pseudo-elements, no\n position:relative). --crystal-stack-bg carries the --glass-crystal-fresco pass-through. The hover\n sparkle is a plain token swap; box-shadow transitions on the real property. No saturate(),\n matching the old variant. */\n[data-material=\"crystal\"] {\n /* Solidify slot: `initial` = guaranteed-invalid, so `glass` falls back to composing --glass-solidify\n AT THE ELEMENT (never inherited pre-composed — the var-composition gotcha). Pinned so an opaque\n PAGE remap can never leak its dropped layer into an explicit material. */\n --srf-solidify: initial;\n --srf-bg-image: var(--crystal-stack-bg);\n --srf-bg-color: var(--glass-crystal-bg);\n --srf-filter: blur(max(var(--blur-crystal), var(--dif, 0px)));\n --srf-border-color: var(--glass-crystal-border);\n --srf-border-w: var(--brd-w, 1px);\n --srf-shadow: var(--glass-crystal-shadow);\n transition: box-shadow 0.3s ease;\n}\n[data-material=\"crystal\"]:hover {\n --srf-shadow: var(--glass-crystal-shadow-hover);\n}\n/* Crystal text tiers — the baked gloss locally LIGHTENS the title zone (worst in dark mode), so text\n inside crystal remaps to the -crystal set AutoForeground bands against the glossed crystal surface.\n VEILED crystal is excluded: the veil floor is exactly what the normal tiers are banded for. */\n[data-material=\"crystal\"]:not(.glass-veil, [data-surface=\"veil\"]) {\n --foreground: var(--foreground-crystal);\n --foreground-soft: var(--foreground-soft-crystal);\n --foreground-strong: var(--foreground-strong-crystal);\n --foreground-ui: var(--foreground-ui-crystal);\n --muted-foreground: var(--muted-foreground-crystal);\n}\n\n/* Chakra — the step-cut gem: a translucent tinted body with a two-step facet frame on all four edges,\n sitting between crystal (near-clear) and opaque (solid). Like crystal it bakes everything into the\n background stack and the shadow — no pseudo-elements, so it is safe on sticky/fixed surfaces, and it\n carries the same gloss triple, so `data-gloss` white / tonal / hue reskin it too.\n The cut lives entirely in --srf-shadow (see --glass-chakra-facets in engine.css). Nothing here is an\n inset BACKGROUND layer, deliberately: background layers are rectangles with no radius of their own,\n so an inset gradient stacks square corners on a rounded surface. Inset shadows follow --radius.\n Because the stack is plain full-bleed layers, the sheer-glass axes work untouched — glass-diffuse\n raises --dif into the blur below, and the --glass-opacity solidify floor composes under the stack\n exactly as it does for crystal. */\n[data-material=\"chakra\"] {\n /* Solidify slot: `initial` = guaranteed-invalid, so `glass` falls back to composing --glass-solidify\n AT THE ELEMENT (never inherited pre-composed — the var-composition gotcha). Pinned so an opaque\n PAGE remap can never leak its dropped layer into an explicit material. */\n --srf-solidify: initial;\n --srf-bg-image: var(--glass-chakra-stack-bg);\n --srf-bg-color: var(--glass-chakra-bg);\n --srf-filter: blur(max(var(--blur-chakra), var(--dif, 0px)));\n --srf-border-color: var(--glass-chakra-border);\n --srf-border-w: var(--brd-w, 1px);\n --srf-shadow: var(--glass-chakra-shadow);\n transition: box-shadow 0.3s ease;\n}\n[data-material=\"chakra\"]:hover {\n --srf-shadow: var(--glass-chakra-shadow-hover);\n}\n/* Chakra text tiers — content sits on the body, whose lightness (--glass-chakra-l) is its own dial and\n can sit well away from the page's. Same reasoning as opaque/crystal; veiled chakra is excluded\n because the veil floor is exactly what the NORMAL tiers are banded for. */\n[data-material=\"chakra\"]:not(.glass-veil, [data-surface=\"veil\"]) {\n --foreground: var(--foreground-chakra);\n --foreground-soft: var(--foreground-soft-chakra);\n --foreground-strong: var(--foreground-strong-chakra);\n --foreground-ui: var(--foreground-ui-chakra);\n --muted-foreground: var(--muted-foreground-chakra);\n}\n\n/* Opaque — solid tinted floor, nothing to blur through. --glass-opaque-outline stays settable on any\n ancestor: it's composed HERE, on the element, so it resolves where it's read. The floor can be\n LIGHT while the page is dark, so the foreground tiers remap to the -opaque set AutoForeground\n bands against that floor (inherits into the card's content). */\n[data-material=\"opaque\"] {\n /* DROP the solidify layer: it is `--glass-opaque-bg` at --glass-opacity alpha painted directly over a\n background-COLOR of the same --glass-opaque-bg at alpha 1 — i.e. C over C, which composites to C for\n ANY alpha. Measured across tints and both modes it moves every channel by exactly 0. So the layer was\n one full gradient rasterisation (and one dither pass) for provably zero pixels, on the one material\n with no backdrop-filter behind it to mask that noise. Restored when the surface is VEILED (glass-veil\n sets --veil-solidify, which `glass` reads first) — there the background-color is the neutral veil\n floor, not --glass-opaque-bg, so the layer is real (dropping it there would shift up to 31/255 in\n light and 42/255 in dark). */\n --srf-solidify: none;\n /* The WASH, and only the wash. Every other material's stack ends in --glass-wash-layer, the solid film\n of the preset's colour that carries the tint over any backdrop; opaque used to drop it with the\n sheet, so its floor had to carry the tint alone and the opaque card read as a darker, greyer cousin\n of the glass beside it (in dark mode most of all: amethyst glass L44 C0.18, opaque L36 C0.11). With\n the wash, opaque is the glass stack at full opacity: within 0.7 L of its glass siblings in light and\n 2–4 L above them in dark, where no darker page shows through. The wash is one flat colour (both\n stops equal), so it rasterises without the dither the sheet's gradient caused (below).\n NO sheet — --glass-bg is a 5-stop\n gradient whose total range, composited over this floor, is 2–4 sRGB codes across an entire card in\n light and 5–7 in dark: one code per ~100–200px, far too shallow to draw cleanly in 8-bit, so the\n rasteriser dithers rather than bands. On every other material a real backdrop-filter supplies blurred\n page content that masks that noise; opaque sets --srf-filter: none, so there is nothing behind it and\n the dither is the only variation on the card — read as grain, worst over text.\n The sheet's GRADIENT was therefore invisible, but its uniform LIFT was not (+1.7 L light, +4.8 L dark),\n so it is folded into --glass-opaque-l rather than dropped: see tokens.css. AutoForeground reads that\n same token, so the -opaque text tiers re-band against the new floor automatically. */\n /* The fresco slot leads, as in the crystal and chakra stacks: unset (`none`) on a single-hue preset, the\n fresco's own multi-hue gradient on muse / sistine / aurora / gloaming, which opaque used to lose\n entirely and render as one flat anchor hue. Its stops have a real range, unlike the sheet's 2–7\n codes, so it rasterises without the dither the sheet was dropped for. */\n --srf-bg-image: var(--glass-crystal-fresco, none), var(--glass-wash-layer);\n --srf-bg-color: var(--glass-opaque-bg);\n --srf-filter: none;\n /* The material's own edge. A page outline (--glass-outline / -w) overrides it in `glass`'s --srf-edge,\n the same as for every other material. */\n --srf-border-color: var(--glass-border);\n --srf-border-w: var(--brd-w, 1px);\n --srf-shadow: var(--glass-shadow);\n /* Veiled overlays INSIDE an opaque element compute a fully solid floor — same guarantee the opaque\n PAGE style makes (menus/toasts must stay legible on a solid ground). */\n --glass-solid-a: 1;\n --foreground: var(--foreground-opaque);\n --foreground-soft: var(--foreground-soft-opaque);\n --foreground-strong: var(--foreground-strong-opaque);\n --foreground-ui: var(--foreground-ui-opaque);\n --muted-foreground: var(--muted-foreground-opaque);\n}\n\n/* ── Gloss flavors ────────────────────────────────────────────────────────────\n data-gloss on or ANY element swaps the crystal (and chakra) gloss for that subtree. Default\n (unset) = tonal. These blocks set LEAVES only — the two flavor switches, plus the hue flavor's own\n lightness and shadows — and engine.css block C recomposes the gloss on every [data-gloss] element from\n them. That split is what makes a scoped `
` work: a composed value bakes\n where it is declared, so a block here that declared one would hand its children 's gloss.\n Each block sets BOTH switches, so a nested flavor overrides an ancestor's instead of combining with it.\n They compose with [data-material=\"crystal\"] AND the [data-glass=\"crystal\"] page style, in both modes.\n Known edge: hue's pinned --glass-gloss-l and bevel-less crystal shadows are mode-twinned leaves that a\n white/tonal section nested INSIDE a hue scope inherits (no mode-aware reset exists for them here). */\n[data-gloss=\"white\"] {\n --glass-gloss-is-white: 1; /* the ink becomes the flat white specular, 100% 0 0 */\n --glass-gloss-is-hue: initial;\n}\n[data-gloss=\"tonal\"] {\n /* Explicit tonal (also the default) — both switches off, so it can override a white/hue ancestor. */\n --glass-gloss-is-white: initial;\n --glass-gloss-is-hue: initial;\n}\n[data-gloss=\"hue\"] {\n --glass-gloss-is-white: initial;\n --glass-gloss-is-hue: 1;\n /* Hue OWNS its lightness rather than sharing the tonal twin. The two flavors want opposite things\n from the same number: tonal wants the brightest value the surface allows (97 in light) so it reads\n as a specular, while hue needs chroma headroom — its stops ask --glass-gloss-c 0.16, and the sRGB\n ceiling at L 97 is 0.0136, so the iridescent sweep would deliver 8% of its colour and wash out to\n near-white exactly where it is supposed to be most vivid.\n 74 is mode-invariant on purpose: it holds 81% of that chroma, and it lands on the right side of\n both surfaces anyway (+18.4 L over the dark floor, −9.0 over the light one — for an iridescent\n sweep, being SEEN matters more than which direction it moves). The demo's flavor switcher already\n seeded 74 at runtime for this reason; this makes a no-JS consumer get it too.\n \"Holds 81% of that chroma\" assumed the browser gamut-MAPS an over-ceiling request down to the\n ceiling. Measured in WebKit it CLIPS instead (see --glass-gloss-c-max in tokens.css), so 0.16 did\n not deliver 81% of itself — it drove the tightest hues to a pinned channel. 0.12 sits just under\n 0.1240, the L-74 ceiling for turquoise (h190) and the tightest of the presets, so the sweep now\n renders every hue it asks for. scripts/check-gamut.mjs reads this 74 to score the variants. */\n --glass-gloss-l: 74;\n /* Kill the white bevel: the crystal shadow carries a white inset highlight, which would sit on top of\n the swept hue. (The white floor is replaced in engine.css block C′.) */\n --glass-crystal-shadow: var(--glass-shadow);\n --glass-crystal-shadow-hover: var(--glass-shadow-lg);\n}\n/* ── Site-wide style remaps + vendor cascade fixes ──────────────────────────── */\n\n/* Keep react-day-picker from painting over the glass background */\n@layer utilities {\n .glass[class*=\"rdp\"],\n .glass .rdp,\n .glass .rdp-month {\n background-color: transparent !important;\n }\n}\n\n/* ── Site-wide glass style: token remaps ─────────────────────────────────────\n data-glass on (frosted | crystal | opaque) re-skins every ADAPTIVE surface (`glass` with no\n data-material) by remapping the --srf-* tokens it reads. An element-level [data-material] pins its\n own tokens and beats these by cascade physics — reinforced by the :where() wrapper, which drops\n every remap (incl. its cross-product legs) to specificity (0,0,0), so a co-located data-material on\n a tint-scope element always wins. The cross-product legs keep the swapped stacks re-resolving inside\n scoped tints, status toasts included (a bare [data-glass] rule would bake 's tint into\n descendants' surfaces). Veiled surfaces (glass-veil) keep their element-composed floor.\n The ELEMENT-level rules further down (text tiers, the opaque edge) need to know which elements are\n surfaces. `.glass` says so for the utility class; an element that wears the material through\n `@apply glass` has no class, so it opts in with a `data-surface` attribute (`data-surface=\"veil\"`\n when it also applies glass-veil). Everything ABOVE is inherited tokens and needs no marker. */\n:where([data-glass=\"frosted\"], [data-glass=\"frosted\"] [data-glass-tint], [data-glass=\"frosted\"][data-glass-tint]) {\n --srf-bg-image: var(--frosted-stack-bg);\n /* NO --srf-bg-color wash here: the wash already rides the IMAGE stack (--frosted-stack-bg), same\n as the frosted material — a color-channel copy double-washed tinted presets and stole the veil\n floor's channel. */\n --srf-filter: blur(max(var(--blur-frosted), var(--dif, 0px))) saturate(200%);\n --srf-border-color: var(--glass-frosted-border);\n --srf-border-w: var(--brd-w, 0.5px);\n --srf-shadow: var(--glass-frosted-shadow);\n}\n:where([data-glass=\"crystal\"], [data-glass=\"crystal\"] [data-glass-tint], [data-glass=\"crystal\"][data-glass-tint]) {\n /* Gloss rides inside --crystal-stack-bg (baked — sticky/fixed-safe). Shimmer stays the opt-in\n glass-sheen. No saturate(), matching the material. */\n --srf-bg-image: var(--crystal-stack-bg);\n --srf-bg-color: var(--glass-crystal-bg);\n --srf-filter: blur(max(var(--blur-crystal), var(--dif, 0px)));\n --srf-border-color: var(--glass-crystal-border);\n --srf-shadow: var(--glass-crystal-shadow);\n}\n/* Crystal page: ADAPTIVE glass (no explicit material, not veiled) gets the -crystal text tiers, same\n as the material remap in materials.css — the baked gloss lightens title zones there too. */\n[data-glass=\"crystal\"] :where(.glass, [data-surface]):not([data-material]):not(.glass-veil, [data-surface=\"veil\"]) {\n --foreground: var(--foreground-crystal);\n --foreground-soft: var(--foreground-soft-crystal);\n --foreground-strong: var(--foreground-strong-crystal);\n --foreground-ui: var(--foreground-ui-crystal);\n --muted-foreground: var(--muted-foreground-crystal);\n}\n/* Chakra page — every adaptive surface becomes a step-cut gem. The cut rides --srf-shadow, so it\n follows each surface's own border-radius with nothing to keep in sync. */\n:where([data-glass=\"chakra\"], [data-glass=\"chakra\"] [data-glass-tint], [data-glass=\"chakra\"][data-glass-tint]) {\n --srf-bg-image: var(--glass-chakra-stack-bg);\n --srf-bg-color: var(--glass-chakra-bg);\n --srf-filter: blur(max(var(--blur-chakra), var(--dif, 0px)));\n --srf-border-color: var(--glass-chakra-border);\n --srf-shadow: var(--glass-chakra-shadow);\n}\n/* Chakra page: adaptive glass (no explicit material, not veiled) takes the -chakra text tiers, same\n as the material remap in materials.css. */\n[data-glass=\"chakra\"] :where(.glass, [data-surface]):not([data-material]):not(.glass-veil, [data-surface=\"veil\"]) {\n --foreground: var(--foreground-chakra);\n --foreground-soft: var(--foreground-soft-chakra);\n --foreground-strong: var(--foreground-strong-chakra);\n --foreground-ui: var(--foreground-ui-chakra);\n --muted-foreground: var(--muted-foreground-chakra);\n}\n/* Opaque — solid tinted panels for adaptive surfaces. --glass-solid-a: 1 makes any veiled overlay\n (glass-veil) compute a FULLY solid floor here too, so menus/toasts stay legible on an opaque page. */\n:where([data-glass=\"opaque\"], [data-glass=\"opaque\"] [data-glass-tint], [data-glass=\"opaque\"][data-glass-tint]) {\n /* Floor + the tint wash, no sheet — same as [data-material=\"opaque\"] in materials.css: the wash is what\n makes opaque the glass stack at full opacity rather than a greyer cousin of it, and the sheet stays\n out because with --srf-filter: none nothing masks the dither of its 2–7 code gradient. */\n --srf-bg-image: var(--glass-crystal-fresco, none), var(--glass-wash-layer);\n --srf-bg-color: var(--glass-opaque-bg);\n --srf-filter: none;\n --srf-shadow: var(--glass-shadow);\n /* Same provable no-op as [data-material=\"opaque\"]: --glass-opaque-bg sits in the background-COLOR\n channel, so the solidify layer would paint that colour over itself. A TOKEN here (it used to be an\n element rule keyed on `.glass`), so it reaches an `@apply glass` surface too, which kept the light\n solidify backing at 70% over the opaque floor and read nearly white. An explicit material pins the\n slot back (`initial`), and a veiled surface restores the layer with --veil-solidify. */\n --srf-solidify: none;\n}\n/* --glass-solid-a CANNOT ride in the :where() block above. :where() is specificity ZERO by design (so a\n page style never beats an explicit material), and every other declaration up there survives that\n because nothing else declares them — they are material SLOTS with no competing value. This one is\n different: tokens.css declares --glass-solid-a on :root, which is (0,1,0), so the :where() version\n lost every time and the opaque page never actually got its solid floor. Measured: --glass-solid-a\n resolved to 0.65 on under [data-glass=\"opaque\"], identical to every other page style.\n The visible symptom is any VEILED surface on an opaque page staying translucent while the solid\n panels around it go opaque — menus, toasts and popovers, which is exactly what the block above says\n this is for. A bare attribute selector is (0,1,0), which ties :root and wins on source order because\n globals.css imports styles.css after tokens.css. It is a plain literal, so it simply inherits into\n the tint / toast scopes and needs none of their re-resolution. */\n[data-glass=\"opaque\"] {\n --glass-solid-a: 1;\n}\n/* Opaque page: element-level touches for ADAPTIVE surfaces only (a `.glass` or `[data-surface]` element\n with no explicit material — an explicit frosted/crystal card keeps its translucency AND its normal\n foreground). Declared ON the element because (a) the page outline is settable on any ancestor and\n must resolve where it's read, and (b) the foreground remap must not leak into explicit materials.\n The uniform EDGE applies to every adaptive panel, veiled or not — a veiled card under the opaque page\n still reads as a solid panel and needs the same outline. */\n[data-glass=\"opaque\"] :where(.glass, [data-surface]):not([data-material]) {\n --srf-border-color: var(--glass-border);\n --srf-border-w: var(--brd-w, 1px);\n /* Paint the border HERE, not just the tokens: borderless roles (Card default) never render\n glass-border, so without this only bordered roles showed the outline — \"some cards get it,\n some don't\". Under the opaque page every solid panel takes the uniform edge; bordered elements\n resolve the same vars, so nothing double-draws. */\n border: var(--srf-edge-w, 1px) solid var(--srf-edge, var(--glass-border));\n}\n\n/* ── The page OUTLINE — every material ───────────────────────────────────────\n data-glass-outline on (or any ancestor) gives every glass surface below it an edge in the\n theme accent: glass, frosted, crystal, chakra and opaque alike, bordered roles and borderless ones,\n which is what the opaque page already does for its own panels. --glass-outline recolours it (on its\n own, without the attribute, it recolours only the edges surfaces already draw) and --glass-outline-w\n sets the weight (1 / 2 / 4px = hairline / rim / frame); an element's own border=\"rim\" or \"frame\" and\n borderColor=\"ink\" still win. :is(), not :where(), so this ties the opaque page's edge rule on\n specificity and wins by coming after it. */\n[data-glass-outline] :is(.glass, [data-surface]) {\n border: var(--srf-edge-w, 1px) solid var(--brd-ink, var(--glass-outline, var(--glass-opaque-outline, var(--glass-accent))));\n}\n/* The FOREGROUND remap is :not(.glass-veil) — the same carve-out crystal and chakra make, and the same\n one --veil-solidify makes for the solidify layer on this page style. A veiled card does not paint\n --glass-opaque-bg: glass-veil moves the background-COLOR channel to the neutral --veil-floor, so its\n text sits on the veil surface the BASE tokens are banded for, not on the opaque floor. Remapping it\n here handed those cards ink solved for a surface they do not have — visible as a hue jump, since the\n opaque floor is the one surface with nothing composited over it and so sits at the raw --glass-tint-h\n while every sheer surface lands a few degrees off it (measured on the /colors panel: 22.0 vs 14.6).\n Splitting the rule is what keeps the edge treatment while dropping the foreground one. */\n[data-glass=\"opaque\"] :where(.glass, [data-surface]):not([data-material]):not(.glass-veil, [data-surface=\"veil\"]) {\n --foreground: var(--foreground-opaque);\n --foreground-soft: var(--foreground-soft-opaque);\n --foreground-strong: var(--foreground-strong-opaque);\n --foreground-ui: var(--foreground-ui-opaque);\n --muted-foreground: var(--muted-foreground-opaque);\n}\n\n/* ── Prebaked frost ───────────────────────────────────────────────────────────\n A stand-in for backdrop-filter when many surfaces are on screen at once (a message list). Each\n `.glass-prebaked` paints a viewport-sized copy of the page background, shifted by its own screen\n position (--gx / --gy, written by useGlassBackdrop), and clips that copy to its box. A plain\n `filter` on that copy blurs pixels the element already owns. backdrop-filter would re-sample\n whatever is painted behind every bubble, every frame.\n\n UNLAYERED on purpose, and it MUST stay that way:\n - `@utility` rules live in `@layer utilities`. An unlayered [data-material] / [data-glass] rule\n beats a layered custom property no matter the specificity, so `--srf-filter: none` inside an\n @utility would lose and the live blur would stay on.\n - `backdrop-filter: none` here replaces the property `glass` sets, including the --srf-stain\n appendix. Stained optics are a backdrop-filter color matrix; they do not carry over.\n - `position: relative` and `overflow: hidden` are part of the trick (the slice is 100vw × 100vh\n and has to be clipped, and the bubble has to be the slice's containing block). Unlayered, they\n beat `absolute` / `fixed` / `sticky` / `overflow-*` utilities on the same element. Keep this\n class on in-flow bubbles. It does not belong on a sticky bar, a popover, or a scrolling panel.\n - No `isolation: isolate`. A stacking context paints its own background FIRST and negative\n z-index children AFTER it, so the slice would cover the glass fill and the tint would vanish.\n Without one, z-index -1 sits behind this element's background and in front of the page\n backdrop (z-index -10). A transform, filter, or opacity on the bubble itself re-traps the\n slice above the fill — same failure — so don't put those on the bubble.\n - `glass-sheen` paints its own ::before / ::after highlights. This slice owns ::before; the two\n classes do not combine.\n\n The app supplies the pixels:\n - `--app-bg-blurred`: a pre-blurred image (`url(bg.webp) center / cover`) or the same CSS\n background the page paints. GradientBackground and the solid \"none\" backdrop publish it.\n - `--app-bg-filter`: `blur(12px) saturate(180%)` when that background is NOT already blurred.\n Defaults to none, so a pre-blurred image is not blurred a second time.\n Pattern scenes and the canvas are many layers, not one background, so they do not publish the\n variable. A bubble with this class and no variable loses the live blur and shows only its fill. */\n.glass-prebaked {\n --srf-filter: none;\n backdrop-filter: none;\n position: relative;\n overflow: hidden;\n}\n.glass-prebaked::before {\n content: \"\";\n position: absolute;\n z-index: -1;\n width: 100vw;\n height: 100vh;\n left: calc(var(--gx, 0px) * -1);\n top: calc(var(--gy, 0px) * -1);\n background: var(--app-bg-blurred);\n background-repeat: no-repeat;\n filter: var(--app-bg-filter, none);\n border-radius: inherit;\n pointer-events: none;\n}\n/* ── Glass tint presets ─────────────────────────────────────────────────────\n Set data-glass-tint on any element (commonly ) to recolor the glass in that subtree.\n Most presets just move the tint hue/chroma; \"sistine\" is a bespoke multi-hue fresco (gilded\n gold → terracotta flesh tones → azure sky), echoing the Sistine Chapel ceiling.\n Wash + all tint-derived tokens re-resolve per-scope via the :root,[data-glass-tint] split above. */\n/* Selenite — the neutral stone: no chroma and no wash, so the glass is the plain clear sheet. The tint\n values equal the engine default's hue with the colour switched off; the block exists so a CSS-only\n consumer (no tint switcher) gets selenite's colour-wheel anchor, which a hue-less theme needs. */\n[data-glass-tint=\"selenite\"] {\n --glass-tint-h: 250;\n --glass-tint-c: 0;\n --glass-tint-a: 0;\n --harmony-h: 0;\n}\n[data-glass-tint=\"sapphire\"] {\n --glass-tint-h: 255;\n --glass-tint-c: 0.075;\n --glass-tint-a: 0.19;\n --glass-wash-l: 63%;\n --glass-opaque-c-max: 0.056;\n}\n[data-glass-tint=\"aventurine\"] {\n --glass-tint-h: 158;\n --glass-tint-c: 0.076;\n --glass-tint-a: 0.54;\n --glass-wash-l: 88%;\n --glass-opaque-c-max: 0.195;\n}\n[data-glass-tint=\"amethyst\"] {\n --glass-tint-h: 300;\n --glass-tint-c: 0.101;\n --glass-tint-a: 0.15;\n --glass-wash-l: 55%;\n --glass-opaque-c-max: 0.065;\n}\n[data-glass-tint=\"rose\"] {\n --glass-tint-h: 8;\n --glass-tint-c: 0.095;\n --glass-tint-a: 0.17;\n --glass-wash-l: 64%;\n --glass-opaque-c-max: 0.062;\n}\n[data-glass-tint=\"amber\"] {\n --glass-tint-h: 75;\n --glass-tint-c: 0.062;\n --glass-tint-a: 0.38;\n --glass-wash-l: 81%;\n --glass-opaque-c-max: 0.096;\n}\n[data-glass-tint=\"carnelian\"] {\n --glass-tint-h: 38;\n --glass-tint-c: 0.081;\n --glass-tint-a: 0.19;\n --glass-wash-l: 67%;\n --glass-opaque-c-max: 0.062;\n}\n[data-glass-tint=\"peridot\"] {\n --glass-tint-h: 128;\n --glass-tint-c: 0.083;\n --glass-tint-a: 0.54;\n --glass-wash-l: 92%;\n --glass-opaque-c-max: 0.218;\n}\n[data-glass-tint=\"turquoise\"] {\n --glass-tint-h: 190;\n --glass-tint-c: 0.057;\n --glass-tint-a: 0.54;\n --glass-wash-l: 90%;\n --glass-opaque-c-max: 0.144;\n}\n[data-glass-tint=\"aquamarine\"] {\n --glass-tint-h: 215;\n --glass-tint-c: 0.053;\n --glass-tint-a: 0.5;\n --glass-wash-l: 82%;\n --glass-opaque-c-max: 0.091;\n}\n[data-glass-tint=\"tourmaline\"] {\n --glass-tint-h: 342;\n --glass-tint-c: 0.106;\n --glass-tint-a: 0.18;\n --glass-wash-l: 67%;\n --glass-opaque-c-max: 0.079;\n}\n[data-glass-tint=\"lapis\"] {\n /* Lapis lazuli IS the deep saturated ultramarine, and at plain jewel chroma it reads as just another\n blue next to sapphire. It used to buy that depth by declaring c 0.15 — far past the sRGB ceiling —\n and letting the browser clip. That DID deliver a deep blue, but by accident rather than by design:\n clipping chops channels, which drops LIGHTNESS, and the saturation came from landing darker.\n Measured, oklch(58% 0.375 268) renders L48.1 C0.2941 h271 — a colour nobody wrote down.\n The wash lightness below asks for that colour on purpose instead. h268's chroma ceiling PEAKS at\n L46 (0.2979) and collapses to 0.1293 by L74, so the shared 72/58 wash was sitting on the downslope;\n at L46 the same depth is available inside the gamut. oklch(45% 0.291 268) renders L44.9 C0.2909\n h267.9 — the same deep ultramarine, hue exact, nothing pinned.\n So lapis is no longer an exception to anything: it is the clearest case of the rule the whole\n palette now follows — put the wash where the hue can actually hold chroma, then ask for it. */\n --glass-tint-h: 268;\n --glass-tint-c: 0.085;\n --glass-tint-a: 0.13;\n --glass-wash-l: 46%;\n --glass-opaque-c-max: 0.055;\n}\n[data-glass-tint=\"goldstone\"] {\n --glass-tint-h: 22;\n --glass-tint-c: 0.094;\n --glass-tint-a: 0.17;\n --glass-wash-l: 63%;\n --glass-opaque-c-max: 0.06;\n}\n/* ── Per-mode wash ALPHA ──────────────────────────────────────────────────────\n Alpha slides the surface along a straight line from the material floor toward the wash colour, so\n how much a preset can take is set by how far its wash sits from THAT MODE's floor (L36.4 dark /\n L88 light). A wash near the floor barely moves the surface and tolerates a lot; a wash far from it\n crosses the mid-tone band where no text polarity reaches the body floor, and tolerates little.\n Because every wash now sits at its hue's chroma PEAK, dark-peaking hues (lapis L46, amethyst L55)\n sit near the DARK floor and take high alpha there, while light-peaking hues (peridot L92, turquoise\n L90) sit near the LIGHT floor and take high alpha there. The two columns are near mirrors.\n Each value is HALF that mode's measured headroom, so every preset keeps body text >= 75 Lc in both\n modes with room to spare. A single shared alpha cannot serve both ends: at 0.55 everywhere, five\n presets lose dark-mode text and lapis/amethyst lose light-mode text.\n The switcher no longer inlines --glass-tint-a for a named preset (see applyTint), so THESE are the\n values that render — nothing shadows them.\n Every night block carries two selectors: `.dark[data-glass-tint=\"x\"]` for a preset on the same element\n as `.dark` (normally ), and `.dark [data-glass-tint=\"x\"]` for a preset scoped to an element INSIDE\n a dark page (`
`), which the compound form never matched, so a scoped\n preset in dark mode used to render its DAY values. check-theme's [night-scope] keeps them paired. */\n.dark[data-glass-tint=\"rose\"],\n.dark [data-glass-tint=\"rose\"] {\n --glass-tint-a: 0.49;\n}\n.dark[data-glass-tint=\"goldstone\"],\n.dark [data-glass-tint=\"goldstone\"] {\n --glass-tint-a: 0.5;\n}\n.dark[data-glass-tint=\"carnelian\"],\n.dark [data-glass-tint=\"carnelian\"] {\n --glass-tint-a: 0.45;\n}\n.dark[data-glass-tint=\"amber\"],\n.dark [data-glass-tint=\"amber\"] {\n --glass-tint-a: 0.34;\n}\n.dark[data-glass-tint=\"peridot\"],\n.dark [data-glass-tint=\"peridot\"] {\n --glass-tint-a: 0.28;\n}\n.dark[data-glass-tint=\"aventurine\"],\n.dark [data-glass-tint=\"aventurine\"] {\n --glass-tint-a: 0.29;\n}\n.dark[data-glass-tint=\"turquoise\"],\n.dark [data-glass-tint=\"turquoise\"] {\n --glass-tint-a: 0.28;\n}\n.dark[data-glass-tint=\"aquamarine\"],\n.dark [data-glass-tint=\"aquamarine\"] {\n --glass-tint-a: 0.31;\n}\n.dark[data-glass-tint=\"sapphire\"],\n.dark [data-glass-tint=\"sapphire\"] {\n --glass-tint-a: 0.48;\n}\n.dark[data-glass-tint=\"lapis\"],\n.dark [data-glass-tint=\"lapis\"] {\n --glass-tint-a: 0.54;\n}\n.dark[data-glass-tint=\"amethyst\"],\n.dark [data-glass-tint=\"amethyst\"] {\n --glass-tint-a: 0.54;\n}\n.dark[data-glass-tint=\"tourmaline\"],\n.dark [data-glass-tint=\"tourmaline\"] {\n --glass-tint-a: 0.46;\n}\n[data-glass-tint=\"moonstone\"] {\n /* Moonstone — a warm, very-low-chroma off-white glass (reads as cream, not yellow). A normal translucent\n tint: texture-preserving and light-text in both modes, just a desaturated warm hue. Anchors its harmony\n wheel at 0° (like selenite) so accents come out a colorful set, not a warp of the near-neutral hue. */\n --glass-tint-h: 75;\n --glass-tint-c: 0.047;\n --glass-tint-a: 0.18;\n --harmony-h: 0;\n /* Gloss chroma, moonstone's own. The global --glass-gloss-tint is 4.25 — sized for the JEWELS, where a\n 0.07–0.1 tint wants a vivid gem highlight. Moonstone declares 0.047 precisely so it \"reads as cream,\n not yellow\" (above), and 4.25× asks for 0.1998.\n --glass-gloss-c-max (tokens.css) is what stops that becoming a clipped primary, and in LIGHT it does\n the whole job: 0.013 caps both 0.1998 and 0.047 to the same value, so this override changes nothing\n there. It earns its place in DARK, where the cap is a roomy 0.11 — enough that the multiplier would\n still put moonstone's highlight at 2.3× its declared chroma, i.e. amber on a stone that is supposed\n to be pale. 1× holds the specular at the tint's own chroma and lets its LIGHTNESS do the work. */\n --glass-gloss-tint: 1;\n /* Moonstone daytime body lightness. Fitted for the retired opaque sheet (tokens.css) on moonstone's OWN\n floor rather than the global pair: this cream is already near-white, where a near-white sheet moved it\n far less than it moved the standard L90 floor. dE 0.0002 — indistinguishable from the old surface. */\n --glass-opaque-l: 94.5;\n /* NO pinned --glass-opaque-bg: the floor must stay DERIVED from --glass-opaque-l (engine), because the\n lightness slider / stored per-mode L land as an INLINE --glass-opaque-l that shadows this block — a\n literal pin would freeze the paint while AutoForeground keeps banding text against the token (light\n text on cream). Was c-scale 1 — the value that made the derived color exactly the original cream when\n the sheet still painted over it; 0.92 is that same cream now that the sheet is gone (it desaturated as\n well as lightened, so both knobs carry part of it). */\n --glass-opaque-c-scale: 0.92;\n}\n.dark[data-glass-tint=\"moonstone\"],\n.dark [data-glass-tint=\"moonstone\"] {\n /* Moonstone night. The wash sits a few points above the standard 58% (warmth without glare; h75\n carries more real luminance per OKLCH point than the cool tints, so pale-warm washes read brighter\n than their numbers). AutoForeground reads BOTH wash knobs live (+ the [moonstone-sync] mirrored\n fallback), so page text and per-card dark-cream text re-band automatically.\n\n The FLOOR is what you actually see: at the default opacity every surface — cards, inputs, the nav\n pill — paints --glass-opaque-bg, and the wash only tints what shows through. It used to derive\n oklch(0.8 0.047 75), which is a sand/tobacco tan, so moonstone night read as brown rather than as\n the pale silvery stone it is named for. Lightness alone does not fix that: h75 is warm, so every\n lightness along it lands somewhere in the tan family. Cutting the CHROMA is what moves it off that\n family. Light mode is untouched (it keeps c-scale 1 at L94, where the same hue reads as cream).\n\n L52, down from a cream L84.9 — moonstone night is a DARK stone now, and that is forced rather than\n preferred. This floor feeds two surfaces at once: opaque cards paint it directly, and it composites\n at --glass-opacity over the dark veil to become the floor sheer glass sits on. Both have to clear\n the body floor, and for h75 nothing in L55–L84 reaches it in EITHER text polarity — that band is the\n mid-tone dead zone. A cream L84.9 put the opaque card just above it (Lc 77.1) but dragged the sheer\n surface to L65, dead centre, where body text topped out at 65.4 Lc and the preset had to be pinned\n as a known exception. Going lighter cannot fix it: the veil is 30% of the composite, which caps the\n solidify floor at L75.6 no matter how light this value gets, so the sheer surface can never climb\n out the top. Only the bottom exit is reachable. L52 is the PALEST value where both surfaces clear:\n sheer body text lands 80.0 Lc (was 65.4) and the opaque card 82.9 (was 77.1), and it also restores\n --foreground-strong, which could not reach its 90 target on the cream floor either. Still stone —\n oklch(0.52 0.021 75) is #70675c, a warm grey — and still well clear of the jewels' L36.4. */\n --glass-wash-l: 64%;\n --glass-wash-c-mult: 2;\n /* Fitted for the retired opaque sheet, like the daytime floor above and likewise not the global dark\n pair: moonstone night is a LIGHT floor, so the near-white sheet barely moved it. dE 0.0002.\n The backing under sheer glass stays at that L52; the opaque card sits three points lower (49) for the\n same reason every dark opaque card does (tokens.css): it carries the wash now, with no page showing\n through it. */\n --glass-solidify-l: 52;\n --glass-opaque-l: 49;\n --glass-opaque-c-scale: 0.44;\n /* Static -opaque tier baselines. These flipped polarity with the floor: on the old cream L84.9 they were\n dark warm literals, and on an L52 stone they have to be light or SSR's first paint is dark-on-dark.\n Values are what AutoForeground itself solves for this surface, so the mounted refine is a no-op. */\n --foreground-opaque: oklch(1 0 75);\n --foreground-soft-opaque: oklch(0.9 0.05 75);\n --foreground-strong-opaque: oklch(1 0 75);\n --foreground-ui-opaque: oklch(0.91 0.05 75);\n --muted-foreground-opaque: oklch(0.97 0.02 75);\n /* Chakra needs the SAME treatment opaque gets above, and did not have it: chakra was added after this\n block was written, so it kept the global dark body (L28) and rendered oklch(0.28 0.046 75) = #36250c,\n a dark brown — the one surface where moonstone still read as tobacco rather than stone. Matching\n opaque's pair — now L51, chroma x0.45 → 0.021, landing on #6d6559, the same stone as the opaque\n floor one point above it.\n The hue stays 75 deliberately. Cutting CHROMA is what moves this off the tan family; lightness alone\n does not, and a cool hue would contradict the warm cream light mode keeps at L94. */\n --glass-chakra-l: 51;\n --glass-chakra-c-max: 0.021;\n /* Same reasoning as the -opaque baselines above, and flipped with them: an L51 body needs LIGHT text. */\n --foreground-chakra: oklch(1 0 75);\n --foreground-soft-chakra: oklch(0.9 0.05 75);\n --foreground-strong-chakra: oklch(1 0 75);\n --foreground-ui-chakra: oklch(0.91 0.05 75);\n --muted-foreground-chakra: oklch(0.96 0.03 75);\n}\n\n/* Semantic surfaces (Alert info/success/warning/destructive; destructive Button/Badge; status toasts,\n which the Toaster stamps from sonner's data-type — see TOAST_TINT in components/ui/sonner.tsx) render\n data-glass-tint=\"\" — one block per status, joining the grouped engine re-resolution above so\n --glass-bg / wash re-resolve at the alert/toast. */\n[data-glass-tint=\"info\"] {\n --glass-tint-h: 255;\n --glass-tint-c: 0.075;\n --glass-tint-a: 0.18;\n --glass-wash-l: 63%;\n}\n[data-glass-tint=\"success\"] {\n --glass-tint-h: 150;\n --glass-tint-c: 0.088;\n --glass-tint-a: 0.18;\n --glass-wash-l: 87%;\n}\n[data-glass-tint=\"warning\"] {\n --glass-tint-h: 85;\n --glass-tint-c: 0.063;\n --glass-tint-a: 0.18;\n --glass-wash-l: 84%;\n}\n[data-glass-tint=\"destructive\"] {\n --glass-tint-h: 25;\n --glass-tint-c: 0.094;\n --glass-tint-a: 0.18;\n --glass-wash-l: 63%;\n}\n/* FRESCOES (muse / sistine / aurora / gloaming): each block's --glass-tint-h/c/a MUST mirror its\n GlassTintSwitcher preset (components/glass-tint-switcher.tsx). The switcher INLINES those onto ,\n shadowing these blocks on any page with the switcher mounted — so these are the static-consumer fallback.\n Keep them identical, or the demo and a static page render different surfaces. (--glass-bg gradients and\n --glass-fg-h are CSS-only — no preset counterpart.) */\n\n/* Muse — the neon city: cyan → violet → magenta → pink → amber, ONE definition for both modes. Its old\n day sweep (a pale blue → cyan → teal cityscape) lives on in Aurora, so Muse no longer needs a night\n twin: no .dark block, and the light/dark toggle changes the page under the sheet, not the sheet.\n\n Lightness is shared because the stops are already bright (L 78–85), so they read on either page.\n Stop 2's chroma is 0.095 because h290 at L80 has an sRGB ceiling of 0.1052; the 0.17 it once shipped\n with was browser-gamut-mapped (and rendered differently in Safari than in Chrome). 0.095 is 90% of\n its own ceiling, the rule every stop follows.\n\n One alpha has to hold on BOTH pages, and the dark opaque card is the bind: there the sheet's bright\n stops lift an L33 floor toward the text, and at the old day alphas (0.28–0.32) light text needed pure\n white, past the tonal clip, so the ink lost the theme's hue. The sheet sits at 3/4 of the way from\n the old night alphas to the old day ones, and the wash makes the room: L66 at 0.10 (was L76 at 0.16)\n lands a light surface where it was (≈ L90) and a dark one 3.5 L darker. Measured: body 86.6 Lc light,\n 92.4 dark; the dark opaque ink stays tinted.\n\n No pinned --glass-opaque-bg: the opaque floor DERIVES from --glass-opaque-l like every jewel, so the\n switcher's Lightness slider moves a Muse card and AutoForeground models the floor it actually paints\n (the old pins were h288 violet at L91 / L34, against a model of h230 at L93 / L33.4). */\n[data-glass-tint=\"muse\"] {\n --glass-tint-h: 230;\n --glass-fg-h: 290; /* text/accent anchor: violet, the sheet's second stop */\n --glass-tint-c: 0.056;\n --glass-tint-a: 0.1;\n --glass-wash-l: 66%;\n --glass-bg: linear-gradient(\n 135deg in oklch,\n oklch(85% 0.1 222 / 0.3) 0%,\n oklch(80% 0.095 290 / 0.28) 22%,\n oklch(78% 0.2 326 / 0.26) 45%,\n oklch(79% 0.12 350 / 0.26) 70%,\n oklch(84% 0.12 74 / 0.28) 100%\n );\n --glass-frosted-bg: linear-gradient(\n 135deg in oklch,\n oklch(85% 0.1 222 / 0.42) 0%,\n oklch(80% 0.095 290 / 0.4) 22%,\n oklch(78% 0.2 326 / 0.38) 45%,\n oklch(79% 0.12 350 / 0.38) 70%,\n oklch(84% 0.12 74 / 0.4) 100%\n );\n --glass-crystal-fresco: var(--glass-bg);\n}\n\n/* Sistine — the signature: a painterly fresco of four jewels (amber → rose → sapphire → aventurine,\n in that order), interpolated `in oklch` so the warm→cool transitions stay vivid instead of\n averaging to grey. Border + crystal + accent pick up the rose base via the tint vars. */\n[data-glass-tint=\"sistine\"] {\n --glass-tint-h: 75; /* surface: mirrors the Sistine switcher preset */\n --glass-fg-h: 18; /* text/accent anchor: crimson (hand-selected) — hue 18 reads as deep red, not the rose at h8 */\n --glass-tint-c: 0.062;\n --glass-tint-a: 0.16;\n --glass-wash-l: 81%;\n --glass-bg: linear-gradient(\n 135deg in oklch,\n oklch(90% 0.11 75 / 0.34) 0%,\n oklch(86% 0.11 8 / 0.3) 33%,\n oklch(86% 0.11 255 / 0.3) 66%,\n oklch(87% 0.11 158 / 0.32) 100%\n );\n --glass-frosted-bg: linear-gradient(\n 135deg in oklch,\n oklch(90% 0.11 75 / 0.46) 0%,\n oklch(86% 0.11 8 / 0.42) 33%,\n oklch(86% 0.11 255 / 0.42) 66%,\n oklch(87% 0.11 158 / 0.44) 100%\n );\n /* Crystal composes its own background (not --glass-bg), so hand it the fresco explicitly. */\n --glass-crystal-fresco: var(--glass-bg);\n}\n.dark[data-glass-tint=\"sistine\"],\n.dark [data-glass-tint=\"sistine\"] {\n --glass-bg: linear-gradient(\n 135deg in oklch,\n oklch(86% 0.11 75 / 0.22) 0%,\n oklch(82% 0.11 8 / 0.2) 33%,\n oklch(82% 0.11 255 / 0.2) 66%,\n oklch(83% 0.11 158 / 0.22) 100%\n );\n --glass-frosted-bg: linear-gradient(\n 135deg in oklch,\n oklch(86% 0.11 75 / 0.32) 0%,\n oklch(82% 0.11 8 / 0.3) 33%,\n oklch(82% 0.11 255 / 0.3) 66%,\n oklch(83% 0.11 158 / 0.32) 100%\n );\n --glass-crystal-fresco: var(--glass-bg);\n}\n\n/* Aurora — northern lights: spring-green → teal → violet, interpolated `in oklch` so the cool sweep\n stays vivid. Border + crystal + accent pick up the teal base via the tint vars.\n DAY was authored at L 90/89/84 — within ~5 L of the L-95 page it composites onto — so after the\n alpha only 2.2 L of separation survived and the bands read as haze rather than bands. Night works\n because its stops sit 60+ L above an L-20 page and land 13.6 L clear of it. The fix is that\n separation, not more colour: day already delivered MORE chroma than night (spread 26/29/19 against\n 14/15/13) and still looked washed out, because colour without lightness structure reads as fog.\n Stops now sit at L 76.3/74.7/71.8, which also buys chroma headroom, so each takes ~90% of its OWN\n hue's sRGB ceiling rather than one shared number — h292 violet was clipped to 65% at L 84 and is\n fully in gamut here. Composited: spread 48/40/38 (was 26/29/19), separation 8.0 L (was 2.2).\n How far this could go is bound by TEXT, not by taste. AutoForeground models a fresco surface from\n the tint wash (--glass-tint-c/-a), never from this gradient literal, so it does not re-band when\n these stops move and the contrast has to hold statically. --foreground-soft (the L-40 heading tier)\n is the weakest pair on a light surface and sets the floor: at a composite of L 84 it measures 59.5\n and fails, so the stops are placed to land no darker than ~L 86. Worst measured here is 63.5.\n Frosted gets its OWN alphas rather than the old ~1.37x boost over --glass-bg: with stops this much\n darker, that multiplier drove the violet composite to L 83.4 and soft to 58.4. These land all three\n near L 86.4 instead. The authored shape is unchanged — green, then teal, then violet darkest. */\n/* Aurora now sweeps the retired Muse day sweep's hues — blue 235 → cyan 205 → teal-green 155 — instead of the old\n green → cyan → violet. LIGHTNESS is untouched, because it is not a style choice here: the day stops\n sit at L 71.8–76.3 to satisfy the --foreground-soft ≥60 Lc bind described above, and that sweep's own\n L 91–94 is precisely the band these were moved away from when day read as haze.\n\n CHROMA had to be re-derived, it could not travel with the hues. Each stop is 90% of that hue's OWN\n sRGB ceiling at its lightness, which is the rule the previous stops already followed (all three\n measured exactly 90%). A flat number cannot survive a hue change: the old 0.175 is 90% of green's\n ceiling at L76.3 but 127% of blue's, so carrying it over would have clipped stop 1 outright.\n\n Night is re-derived the same way, which also fixes a pre-existing clip: its violet stop asked 0.13\n against a 0.1068 ceiling at h292/L80 — 122% — so that stop has been gamut-mapped by the browser all\n along. */\n[data-glass-tint=\"aurora\"] {\n --glass-tint-h: 205; /* the sweep's midpoint, was 178 */\n --glass-tint-c: 0.054;\n --glass-tint-a: 0.16;\n --glass-wash-l: 86%;\n --glass-bg: linear-gradient(\n 135deg in oklch,\n oklch(76.3% 0.124 235 / 0.4) 0%,\n oklch(74.7% 0.111 205 / 0.38) 50%,\n oklch(71.8% 0.155 155 / 0.38) 100%\n );\n --glass-frosted-bg: linear-gradient(\n 135deg in oklch,\n oklch(76.3% 0.124 235 / 0.46) 0%,\n oklch(74.7% 0.111 205 / 0.43) 50%,\n oklch(71.8% 0.155 155 / 0.37) 100%\n );\n --glass-crystal-fresco: var(--glass-bg);\n}\n.dark[data-glass-tint=\"aurora\"],\n.dark [data-glass-tint=\"aurora\"] {\n --glass-bg: linear-gradient(135deg in oklch, oklch(86% 0.071 235 / 0.22) 0%, oklch(85% 0.127 205 / 0.2) 50%, oklch(80% 0.173 155 / 0.22) 100%);\n --glass-frosted-bg: linear-gradient(\n 135deg in oklch,\n oklch(86% 0.071 235 / 0.32) 0%,\n oklch(85% 0.127 205 / 0.3) 50%,\n oklch(80% 0.173 155 / 0.32) 100%\n );\n --glass-crystal-fresco: var(--glass-bg);\n}\n\n/* Gloaming — dusk: gold → rose → indigo, interpolated `in oklch` so the warm→cool fade keeps its\n glow. Night keeps the warm glow; day uses a dusk-rose anchor (the warm hue reads muddy as light-mode text). */\n[data-glass-tint=\"gloaming\"] {\n --glass-tint-h: 32; /* surface: mirrors the Gloaming switcher preset; text shares it (--glass-fg-h defaults to it) */\n --glass-tint-c: 0.089;\n /* 0.12, down from 0.16: with text solved against the gradient's hardest stop (the L78 violet, over a\n dusk-rose wash that already sits dark), the light opaque card could not reach the 75 Lc body floor\n even in pure black (74.6). 0.14 reached it only in black; 0.12 keeps the ink tinted. */\n --glass-tint-a: 0.12;\n --glass-wash-l: 64%;\n --glass-bg: linear-gradient(135deg in oklch, oklch(89% 0.12 62 / 0.32) 0%, oklch(84% 0.14 350 / 0.3) 50%, oklch(78% 0.13 278 / 0.3) 100%);\n --glass-frosted-bg: linear-gradient(135deg in oklch, oklch(89% 0.12 62 / 0.44) 0%, oklch(84% 0.14 350 / 0.42) 50%, oklch(78% 0.13 278 / 0.42) 100%);\n --glass-crystal-fresco: var(--glass-bg);\n}\n.dark[data-glass-tint=\"gloaming\"],\n.dark [data-glass-tint=\"gloaming\"] {\n --glass-tint-h: 32; /* night keeps the warm glow; day uses the dusk-rose above */\n --glass-bg: linear-gradient(135deg in oklch, oklch(86% 0.12 62 / 0.22) 0%, oklch(80% 0.14 350 / 0.2) 50%, oklch(74% 0.13 278 / 0.22) 100%);\n --glass-frosted-bg: linear-gradient(135deg in oklch, oklch(86% 0.12 62 / 0.32) 0%, oklch(80% 0.14 350 / 0.3) 50%, oklch(74% 0.13 278 / 0.32) 100%);\n --glass-crystal-fresco: var(--glass-bg);\n}\n", "type": "registry:style", "target": "app/globals.css" } diff --git a/public/r/toggle-group.json b/public/r/toggle-group.json index b9347994..2a61705c 100644 --- a/public/r/toggle-group.json +++ b/public/r/toggle-group.json @@ -21,7 +21,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/toggle.json b/public/r/toggle.json index 7a5e09bc..8bd96fc9 100644 --- a/public/r/toggle.json +++ b/public/r/toggle.json @@ -21,7 +21,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/tooltip.json b/public/r/tooltip.json index 187210e7..87ebd84c 100644 --- a/public/r/tooltip.json +++ b/public/r/tooltip.json @@ -21,7 +21,7 @@ }, { "path": "lib/material.ts", - "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", + "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FIVE materials (glass, frosted, crystal, chakra, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border + its colour, veil, size, gradient, glow,\n * sheen, prebaked) over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"chakra\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** The border's COLOUR. \"glass\" (the default) = the material's own edge, a near-white rim at the\n * material's alpha, which reads as glass but disappears on light themes at any weight. \"ink\" = the\n * page's text colour (--foreground, at --glass-border-ink-a), a visible line wherever text is legible.\n * Pairs with `border`; under the opaque page style it also colours the uniform panel edge. */\n borderColor?: \"glass\" | \"ink\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n /** Fake the backdrop blur from a fixed background (`--app-bg-blurred`) instead of backdrop-filter.\n * Needs `useGlassBackdrop` on the scroll container. In-flow bubbles only — the class forces\n * `position: relative` and `overflow: hidden`, and it does not combine with `sheen`. */\n prebaked?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, borderColor, veil, diffuse, gradient, glow, sheen, prebaked, ...rest } = props;\n return [\n {\n material,\n border,\n borderColor,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n prebaked,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, borderColor, veil, size, diffuse, gradient, glow, sheen, prebaked } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n borderColor === \"ink\" && \"glass-border-ink\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n prebaked && \"glass-prebaked\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n borderColor: props.borderColor ?? role.borderColor,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n prebaked: props.prebaked ?? role.prebaked,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, diff --git a/public/r/use-glass-backdrop.json b/public/r/use-glass-backdrop.json new file mode 100644 index 00000000..334e912b --- /dev/null +++ b/public/r/use-glass-backdrop.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "use-glass-backdrop", + "title": "use-glass-backdrop", + "description": "Keeps prebaked glass slices aligned to a fixed background. Writes --gx and --gy on each .glass-prebaked surface from one animation frame, and publishes --app-bg-blurred for a CSS backdrop.", + "dependencies": [], + "registryDependencies": [], + "files": [ + { + "path": "hooks/use-glass-backdrop.ts", + "content": "import * as React from \"react\";\n\n/**\n * Prebaked frost — the position half of `.glass-prebaked` (app/theme/styles.css).\n *\n * A fixed background is the case this handles. Each bubble's blurred slice is a viewport-sized copy\n * of `--app-bg-blurred`, shifted by the bubble's viewport position so the copy lines up with the\n * page. One rAF reads the visible bubbles and writes `--gx` / `--gy`. No React state.\n *\n * Call `syncGlassBackdrop` from a motion `onUpdate` while a bubble animates on `y` or `scale`.\n * Between syncs the slice drifts, because the offset was measured at the previous position. Keep\n * entrance moves small when nothing is calling sync mid-animation. A `translate` lines up again on\n * the next sync; a `scale` does not, because the slice's `left` / `top` are in the bubble's local\n * pixels and the scale changes how those pixels map to the screen.\n *\n * `getBoundingClientRect` on the bubbles actually on screen (a few dozen) is cheap. If a profiler\n * disagrees, derive the offset from the virtualizer's `start` minus the scroll offset instead of\n * reading layout.\n */\n\nconst SELECTOR = \".glass-prebaked\";\n\n/** Frost for a CSS background that is not already blurred. 12px is the diffuse floor (`--blur-xl`). */\nexport const APP_BG_FROST_FILTER = \"blur(12px) saturate(180%)\";\n\n/** Write `--gx` / `--gy` on every prebaked surface under `root` (and on `root`, if it is one). */\nexport function syncGlassBackdrop(root: ParentNode) {\n const nodes: HTMLElement[] = [];\n if (root instanceof HTMLElement && root.matches(SELECTOR)) nodes.push(root);\n root.querySelectorAll(SELECTOR).forEach((el) => nodes.push(el));\n for (const el of nodes) {\n const r = el.getBoundingClientRect();\n el.style.setProperty(\"--gx\", `${r.left}px`);\n el.style.setProperty(\"--gy\", `${r.top}px`);\n }\n}\n\n/**\n * Keep prebaked slices aligned while `scrollRef` scrolls, the window scrolls, or rows mount.\n * The scroll element has to be mounted before this effect runs (`useRef` on an always-rendered node).\n */\nexport function useGlassBackdrop(scrollRef: React.RefObject) {\n React.useEffect(() => {\n const root = scrollRef.current;\n if (!root) return;\n let raf = 0;\n const sync = () => {\n raf = 0;\n syncGlassBackdrop(root);\n };\n const queue = () => {\n if (!raf) raf = requestAnimationFrame(sync);\n };\n root.addEventListener(\"scroll\", queue, { passive: true });\n // Scroll events do not bubble. Capture on window also sees the page scroll, which moves\n // bubbles whose own container did not scroll, and any nested scroller under the root.\n window.addEventListener(\"scroll\", queue, { capture: true, passive: true });\n window.addEventListener(\"resize\", queue);\n const viewport = window.visualViewport;\n viewport?.addEventListener(\"resize\", queue);\n viewport?.addEventListener(\"scroll\", queue);\n // childList: virtualized rows. class: the prebaked class arriving on a node that already existed.\n // Not `style` — the sync writes inline style, and watching it would reschedule itself forever.\n const mo = new MutationObserver(queue);\n mo.observe(root, { childList: true, subtree: true, attributes: true, attributeFilter: [\"class\"] });\n queue();\n return () => {\n root.removeEventListener(\"scroll\", queue);\n window.removeEventListener(\"scroll\", queue, { capture: true });\n window.removeEventListener(\"resize\", queue);\n viewport?.removeEventListener(\"resize\", queue);\n viewport?.removeEventListener(\"scroll\", queue);\n mo.disconnect();\n cancelAnimationFrame(raf);\n };\n }, [scrollRef]);\n}\n\n/**\n * Publish the fixed background prebaked slices sample. `filter` is `APP_BG_FROST_FILTER` for a live\n * CSS background, and omitted (or null) when `background` is already a blurred image. Pass null for\n * `background` to leave the variables alone. Cleanup removes only the values this effect set.\n */\nexport function usePublishAppBackground(background: string | null, filter?: string | null) {\n React.useEffect(() => {\n if (!background) return;\n const root = document.documentElement;\n root.style.setProperty(\"--app-bg-blurred\", background);\n if (filter) root.style.setProperty(\"--app-bg-filter\", filter);\n else root.style.removeProperty(\"--app-bg-filter\");\n return () => {\n if (root.style.getPropertyValue(\"--app-bg-blurred\") === background) {\n root.style.removeProperty(\"--app-bg-blurred\");\n }\n if (filter && root.style.getPropertyValue(\"--app-bg-filter\") === filter) {\n root.style.removeProperty(\"--app-bg-filter\");\n }\n };\n }, [background, filter]);\n}\n", + "type": "registry:hook", + "target": "hooks/use-glass-backdrop.ts" + } + ], + "type": "registry:hook" +} \ No newline at end of file diff --git a/registry.json b/registry.json index d6ddb832..a62507e5 100644 --- a/registry.json +++ b/registry.json @@ -2365,6 +2365,21 @@ ], "dependencies": [] }, + { + "name": "use-glass-backdrop", + "type": "registry:hook", + "title": "use-glass-backdrop", + "description": "Keeps prebaked glass slices aligned to a fixed background. Writes --gx and --gy on each .glass-prebaked surface from one animation frame, and publishes --app-bg-blurred for a CSS backdrop.", + "registryDependencies": [], + "files": [ + { + "path": "hooks/use-glass-backdrop.ts", + "type": "registry:hook", + "target": "hooks/use-glass-backdrop.ts" + } + ], + "dependencies": [] + }, { "name": "oklch-utils", "type": "registry:lib", @@ -2522,7 +2537,8 @@ "registryDependencies": [ "@sistine/canvas-background-utils", "@sistine/oklch-utils", - "@sistine/theme" + "@sistine/theme", + "@sistine/use-glass-backdrop" ], "files": [ { @@ -2541,7 +2557,8 @@ "registryDependencies": [ "@sistine/canvas-background-utils", "@sistine/oklch-utils", - "@sistine/theme" + "@sistine/theme", + "@sistine/use-glass-backdrop" ], "files": [ { diff --git a/registry/theme/globals.css b/registry/theme/globals.css index 653fde2a..0196b340 100644 --- a/registry/theme/globals.css +++ b/registry/theme/globals.css @@ -50,6 +50,19 @@ inherits: false; } +/* Prebaked-frost offsets (hooks/use-glass-backdrop.ts writes these per bubble). Non-inheriting so a + nested bubble never samples its parent's screen position for a frame before its own sync. */ +@property --gx { + syntax: ""; + inherits: false; + initial-value: 0px; +} +@property --gy { + syntax: ""; + inherits: false; + initial-value: 0px; +} + /* Gradient-text tokens are consumed ONLY from JS inline styles (components/gradient-text.tsx) — no CSS rule references them, so Lightning CSS's optimizer would silently drop them (the same failure mode as the harmonic tokens below). Registered here to pin them. DO NOT REMOVE. */ @@ -1863,6 +1876,59 @@ --foreground-ui: var(--foreground-ui-opaque); --muted-foreground: var(--muted-foreground-opaque); } + +/* ── Prebaked frost ─────────────────────────────────────────────────────────── + A stand-in for backdrop-filter when many surfaces are on screen at once (a message list). Each + `.glass-prebaked` paints a viewport-sized copy of the page background, shifted by its own screen + position (--gx / --gy, written by useGlassBackdrop), and clips that copy to its box. A plain + `filter` on that copy blurs pixels the element already owns. backdrop-filter would re-sample + whatever is painted behind every bubble, every frame. + + UNLAYERED on purpose, and it MUST stay that way: + - `@utility` rules live in `@layer utilities`. An unlayered [data-material] / [data-glass] rule + beats a layered custom property no matter the specificity, so `--srf-filter: none` inside an + @utility would lose and the live blur would stay on. + - `backdrop-filter: none` here replaces the property `glass` sets, including the --srf-stain + appendix. Stained optics are a backdrop-filter color matrix; they do not carry over. + - `position: relative` and `overflow: hidden` are part of the trick (the slice is 100vw × 100vh + and has to be clipped, and the bubble has to be the slice's containing block). Unlayered, they + beat `absolute` / `fixed` / `sticky` / `overflow-*` utilities on the same element. Keep this + class on in-flow bubbles. It does not belong on a sticky bar, a popover, or a scrolling panel. + - No `isolation: isolate`. A stacking context paints its own background FIRST and negative + z-index children AFTER it, so the slice would cover the glass fill and the tint would vanish. + Without one, z-index -1 sits behind this element's background and in front of the page + backdrop (z-index -10). A transform, filter, or opacity on the bubble itself re-traps the + slice above the fill — same failure — so don't put those on the bubble. + - `glass-sheen` paints its own ::before / ::after highlights. This slice owns ::before; the two + classes do not combine. + + The app supplies the pixels: + - `--app-bg-blurred`: a pre-blurred image (`url(bg.webp) center / cover`) or the same CSS + background the page paints. GradientBackground and the solid "none" backdrop publish it. + - `--app-bg-filter`: `blur(12px) saturate(180%)` when that background is NOT already blurred. + Defaults to none, so a pre-blurred image is not blurred a second time. + Pattern scenes and the canvas are many layers, not one background, so they do not publish the + variable. A bubble with this class and no variable loses the live blur and shows only its fill. */ +.glass-prebaked { + --srf-filter: none; + backdrop-filter: none; + position: relative; + overflow: hidden; +} +.glass-prebaked::before { + content: ""; + position: absolute; + z-index: -1; + width: 100vw; + height: 100vh; + left: calc(var(--gx, 0px) * -1); + top: calc(var(--gy, 0px) * -1); + background: var(--app-bg-blurred); + background-repeat: no-repeat; + filter: var(--app-bg-filter, none); + border-radius: inherit; + pointer-events: none; +} /* ── Glass tint presets ───────────────────────────────────────────────────── Set data-glass-tint on any element (commonly ) to recolor the glass in that subtree. Most presets just move the tint hue/chroma; "sistine" is a bespoke multi-hue fresco (gilded diff --git a/tests/glass-prebaked.test.tsx b/tests/glass-prebaked.test.tsx new file mode 100644 index 00000000..a2ac035d --- /dev/null +++ b/tests/glass-prebaked.test.tsx @@ -0,0 +1,154 @@ +/** + * Prebaked frost: the hook writes viewport offsets, the publisher sets the background variables and + * removes them on unmount, and the material axis emits the class without leaking the prop onto the DOM. + */ +import { act, render } from "@testing-library/react"; +import { useRef } from "react"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { Card } from "@/components/ui/card"; +import { syncGlassBackdrop, useGlassBackdrop, usePublishAppBackground } from "@/hooks/use-glass-backdrop"; +import { glassMaterial } from "@/lib/material"; + +function rect(left: number, top: number): DOMRect { + return { + x: left, + y: top, + left, + top, + right: left + 40, + bottom: top + 20, + width: 40, + height: 20, + toJSON() { + return {}; + }, + }; +} + +describe("syncGlassBackdrop", () => { + it("writes each bubble's viewport origin into --gx and --gy", () => { + const root = document.createElement("div"); + const bubble = document.createElement("div"); + bubble.className = "glass-prebaked"; + bubble.getBoundingClientRect = () => rect(24, 80); + root.appendChild(bubble); + document.body.appendChild(root); + + syncGlassBackdrop(root); + + expect(bubble.style.getPropertyValue("--gx")).toBe("24px"); + expect(bubble.style.getPropertyValue("--gy")).toBe("80px"); + root.remove(); + }); + + it("includes the root when the root itself is prebaked", () => { + const root = document.createElement("div"); + root.className = "glass-prebaked"; + root.getBoundingClientRect = () => rect(4, 6); + document.body.appendChild(root); + + syncGlassBackdrop(root); + + expect(root.style.getPropertyValue("--gx")).toBe("4px"); + expect(root.style.getPropertyValue("--gy")).toBe("6px"); + root.remove(); + }); +}); + +function ScrollProbe() { + const ref = useRef(null); + useGlassBackdrop(ref); + return ( +

+
+
+ ); +} + +describe("useGlassBackdrop", () => { + const frames: FrameRequestCallback[] = []; + + afterEach(() => { + frames.length = 0; + vi.unstubAllGlobals(); + }); + + function installFrameQueue() { + frames.length = 0; + vi.stubGlobal("requestAnimationFrame", (cb: FrameRequestCallback) => { + frames.push(cb); + return frames.length; + }); + vi.stubGlobal("cancelAnimationFrame", () => {}); + } + + function flushFrames() { + const batch = frames.splice(0, frames.length); + for (const cb of batch) cb(0); + } + + it("measures on mount and again when a bubble's class arrives", async () => { + installFrameQueue(); + const { getByTestId } = render(); + const bubble = getByTestId("bubble"); + bubble.getBoundingClientRect = () => rect(12, 30); + + act(() => flushFrames()); + expect(bubble.style.getPropertyValue("--gx")).toBe("12px"); + expect(bubble.style.getPropertyValue("--gy")).toBe("30px"); + + bubble.getBoundingClientRect = () => rect(50, 60); + bubble.className = "glass-prebaked rounded-lg"; + await act(async () => { + await Promise.resolve(); + }); + act(() => flushFrames()); + + expect(bubble.style.getPropertyValue("--gx")).toBe("50px"); + expect(bubble.style.getPropertyValue("--gy")).toBe("60px"); + }); +}); + +function Publisher({ background, filter }: { background: string | null; filter?: string | null }) { + usePublishAppBackground(background, filter); + return null; +} + +describe("usePublishAppBackground", () => { + afterEach(() => { + document.documentElement.style.removeProperty("--app-bg-blurred"); + document.documentElement.style.removeProperty("--app-bg-filter"); + }); + + it("sets the background and the frost filter, then removes both on unmount", () => { + const view = render(); + expect(document.documentElement.style.getPropertyValue("--app-bg-blurred")).toBe("linear-gradient(red, blue)"); + expect(document.documentElement.style.getPropertyValue("--app-bg-filter")).toBe("blur(12px) saturate(180%)"); + + view.unmount(); + expect(document.documentElement.style.getPropertyValue("--app-bg-blurred")).toBe(""); + expect(document.documentElement.style.getPropertyValue("--app-bg-filter")).toBe(""); + }); + + it("leaves a pre-blurred image unfiltered when no filter is passed", () => { + render(); + expect(document.documentElement.style.getPropertyValue("--app-bg-blurred")).toBe("url(bg.webp) center / cover"); + expect(document.documentElement.style.getPropertyValue("--app-bg-filter")).toBe(""); + }); +}); + +describe("glassMaterial prebaked", () => { + it("adds the class and keeps the prop off the DOM", () => { + expect( + glassMaterial({ + prebaked: true, + border: true, + }).className, + ).toBe("glass glass-border glass-prebaked"); + + const { container } = render(Body); + const card = container.querySelector('[data-slot="card"]') as HTMLElement; + expect(card).toHaveClass("glass-prebaked"); + expect(card.getAttribute("prebaked")).toBeNull(); + }); +}); diff --git a/tests/material.test.tsx b/tests/material.test.tsx index b6a7cabd..fe90d30d 100644 --- a/tests/material.test.tsx +++ b/tests/material.test.tsx @@ -114,6 +114,17 @@ describe("splitAxisProps", () => { }); }); + it("takes prebaked with the other axes", () => { + const [axes, rest] = splitAxisProps({ + prebaked: true, + id: "bubble", + }); + expect(axes.prebaked).toBe(true); + expect(rest).toEqual({ + id: "bubble", + }); + }); + it("does not leak borderColor onto the DOM through a component", () => { const { container } = render(