diff --git a/src/components/_shared/material.css b/src/components/_shared/material.css index 4684647a..7194695d 100644 --- a/src/components/_shared/material.css +++ b/src/components/_shared/material.css @@ -148,6 +148,57 @@ ); } + /* + * Depth is paint on the existing pane, not another backdrop boundary. The + * resolver scales these tokens together from the public depth value, so zero + * removes the sheen, edge light, inner glow and shadow while larger values + * make the same surface read as thicker glass. + */ + :is( + :root.glass [data-material="solid"]:not([data-material-explicit]), + [data-material="glass"] + )::before { + background-image: + linear-gradient( + to bottom, + color-mix( + in oklab, + var(--glass-highlight-color, white), + transparent calc(100% - var(--glass-depth-top-glow-opacity, 8%)) + ), + transparent 28%, + color-mix( + in oklab, + var(--glass-highlight-color, white), + transparent calc(100% - var(--glass-depth-bottom-glow-opacity, 12%)) + ) + ), + linear-gradient( + 115deg, + transparent 20%, + color-mix( + in oklab, + var(--glass-highlight-color, white), + transparent calc(100% - var(--glass-depth-sheen-opacity, 7.5%)) + ) 50%, + transparent var(--glass-depth-sheen-size, 70%) + ); + box-shadow: + inset 0 var(--border, 1px) 0 0 + color-mix( + in oklab, + var(--glass-highlight-color, white), + transparent calc(100% - var(--glass-highlight-opacity, 18%)) + ), + inset 0 0 var(--glass-inner-glow-blur, 0px) + var(--glass-inner-glow-spread, 0px) + rgb( + var(--glass-inner-glow-rgb, 255 255 255) / + var(--glass-inner-glow-alpha, 0) + ), + var(--glass-shadow-depth, 0 8px 32px rgb(0 0 0 / 10%)); + } + /* * Glass inside glass is one pane, not two. * @@ -174,6 +225,7 @@ } [data-material="glass"] [data-material="glass"]::before { + background-image: none; box-shadow: none; } diff --git a/tests/styles/glass.test.ts b/tests/styles/glass.test.ts index 0d565f7a..d0191726 100644 --- a/tests/styles/glass.test.ts +++ b/tests/styles/glass.test.ts @@ -1,4 +1,6 @@ import { describe, expect, it } from "bun:test"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; import { applyGlassTokens, GLASS_DEFAULTS, @@ -21,6 +23,22 @@ const MODES = ["light", "dark"] as const; const DERIVED = Object.keys(resolveGlassTokens(GLASS_DEFAULTS.dark, "dark")); describe("glass tuning", () => { + it("connects the public depth axis to shared material paint", () => { + const materialCss = readFileSync( + join(import.meta.dir, "..", "..", "src", "components", "_shared", "material.css"), + "utf8", + ); + for (const token of [ + "--glass-depth-top-glow-opacity", + "--glass-depth-bottom-glow-opacity", + "--glass-depth-sheen-opacity", + "--glass-inner-glow-alpha", + "--glass-shadow-depth", + ]) { + expect(materialCss, `${token} has no material consumer`).toContain(`var(${token}`); + } + }); + it("derives the same token set whatever the input", () => { for (const mode of MODES) { for (const tuning of [