@mkbabb/glass-ui 3.5.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/{DataTable-DyKc_26a.js → DataTable-BGVXx0Ww.js} +1 -1
  2. package/dist/{NumberFieldContent-jO-zGOHf.js → NumberFieldContent-Bhhmk95p.js} +1 -1
  3. package/dist/animated-digit.js +1 -1
  4. package/dist/api/index.d.ts +2 -1
  5. package/dist/api.js +1 -1
  6. package/dist/aurora.js +259 -81
  7. package/dist/{button-i8Ek5vvg.js → button-BcMzVrqs.js} +2 -2
  8. package/dist/button.js +1 -1
  9. package/dist/carousel.js +1 -1
  10. package/dist/color.js +1 -1
  11. package/dist/components/custom/aurora/composables/color.d.ts +35 -2
  12. package/dist/components/custom/aurora/composables/glSetup.d.ts +1 -1
  13. package/dist/components/custom/aurora/composables/uniformBridge.d.ts +6 -0
  14. package/dist/components/custom/aurora/constants/presets.d.ts +9 -0
  15. package/dist/components/custom/aurora/constants/shaders/composition.glsl.d.ts +1 -1
  16. package/dist/components/custom/aurora/index.d.ts +3 -2
  17. package/dist/components/custom/constellation/Constellation.vue.d.ts +45 -0
  18. package/dist/components/custom/constellation/constellationField.d.ts +85 -0
  19. package/dist/components/custom/constellation/index.d.ts +3 -0
  20. package/dist/components/custom/dock/GlassDock.vue.d.ts +34 -3
  21. package/dist/composables/glass/canvas2d/index.d.ts +2 -0
  22. package/dist/composables/glass/canvas2d/useCanvas2D.d.ts +48 -0
  23. package/dist/composables/glass/index.d.ts +2 -0
  24. package/dist/composables/motion/core/index.d.ts +1 -0
  25. package/dist/composables/motion/index.d.ts +1 -0
  26. package/dist/composables/motion/useAnimatedNumberMap.d.ts +29 -0
  27. package/dist/composables/motion/useStagger.d.ts +50 -0
  28. package/dist/confirm-dialog.js +1 -1
  29. package/dist/constellation.d.ts +1 -0
  30. package/dist/constellation.js +182 -0
  31. package/dist/controls.js +1 -1
  32. package/dist/dark.js +1 -1
  33. package/dist/data-table.js +1 -1
  34. package/dist/dock.js +121 -102
  35. package/dist/expandable-container.js +1 -1
  36. package/dist/forms.js +1 -1
  37. package/dist/glass-ui.css +1 -1
  38. package/dist/glass-ui.js +79 -78
  39. package/dist/goo-blob.js +3 -3
  40. package/dist/icon-tooltip.js +1 -1
  41. package/dist/keyboard.js +1 -1
  42. package/dist/labeled-field.js +2 -2
  43. package/dist/motion-core.js +90 -49
  44. package/dist/motion.js +31 -21
  45. package/dist/number-field.js +1 -1
  46. package/dist/responsive-tabs.js +1 -1
  47. package/dist/search.js +1 -1
  48. package/dist/sortable-list.js +1 -1
  49. package/dist/styles/dock.css +185 -1
  50. package/dist/styles/glass-refract.css +60 -0
  51. package/dist/styles/glass-specular-track.css +43 -130
  52. package/dist/styles/glass.css +243 -27
  53. package/dist/styles/index.css +1 -0
  54. package/dist/styles/theme.css +6 -0
  55. package/dist/styles/tokens.css +33 -0
  56. package/dist/tabs.js +2 -2
  57. package/dist/{useAnimatedNumber-BF6r64lA.js → useAnimatedNumber-DfVgP4te.js} +1 -1
  58. package/dist/useCanvas2D-BeNJqqIS.js +95 -0
  59. package/dist/watercolor-dot.js +1 -1
  60. package/package.json +21 -1
  61. package/src/styles/dock.css +185 -1
  62. package/src/styles/glass-refract.css +60 -0
  63. package/src/styles/glass-specular-track.css +43 -130
  64. package/src/styles/glass.css +243 -27
  65. package/src/styles/index.css +1 -0
  66. package/src/styles/theme.css +6 -0
  67. package/src/styles/tokens.css +33 -0
  68. /package/dist/{IconTooltip-BTyYn4mr.js → IconTooltip-DZlCkU-0.js} +0 -0
  69. /package/dist/{Input-CU9CNKmo.js → Input-CRJ2owjN.js} +0 -0
  70. /package/dist/{UnderlineTabs-Cq_AD03t.js → UnderlineTabs-CuIK16JI.js} +0 -0
  71. /package/dist/{color-rkK4RMx2.js → color-DS-iLRVM.js} +0 -0
  72. /package/dist/{compile-DVgAxagk.js → compile-C62K_FpC.js} +0 -0
  73. /package/dist/{constants-DsCdlK9I.js → constants-C8NMonuo.js} +0 -0
  74. /package/dist/{presets-1OhFpaIC.js → presets-Dyv9Zl7W.js} +0 -0
  75. /package/dist/{prng-Bz_1Tydc.js → prng-CjVgzjQT.js} +0 -0
  76. /package/dist/{useGlobalDark-CWiaCoEw.js → useGlobalDark-D7R9OypB.js} +0 -0
  77. /package/dist/{useIntersectionPause-DAdVPVp4.js → useIntersectionPause-DsQYGc8V.js} +0 -0
  78. /package/dist/{useKeyboardShortcuts-BQfnAHHW.js → useKeyboardShortcuts-i8tHd5WO.js} +0 -0
  79. /package/dist/{useSortable-DnyGXKKY.js → useSortable-muI29agz.js} +0 -0
  80. /package/dist/{useViewTransition-CJnqg6wl.js → useViewTransition-DI0Rs2rV.js} +0 -0
@@ -0,0 +1,60 @@
1
+ /* AW.W23 — the #glass-refract convex-lens SVG filter, shipped as a library
2
+ * asset so `.glass-refract` works OUT OF THE BOX (no consumer hand-mounts a
3
+ * filter node).
4
+ *
5
+ * The refraction recipe is the canonical 3-primitive graph (kube.io /
6
+ * LogRocket, 2025):
7
+ *
8
+ * feImage — a pre-baked displacement map computed from the convex-
9
+ * squircle surface profile y = ⁴√(1 − (1 − x)⁴) (Apple's
10
+ * preferred corner curve) via Snell's law (n₁ = 1 air,
11
+ * n₂ = 1.5 glass). The map is authored as an embedded SVG
12
+ * whose R channel carries the x-shift and G channel the
13
+ * y-shift (neutral 128 = no shift; bright edges bend light
14
+ * inward like a lens), expressed as two crossed linear
15
+ * gradients over the squircle silhouette.
16
+ * feDisplacementMap— displaces the backdrop by the baked map (R = x-shift,
17
+ * G = y-shift, `scale` = max px, neutral 128).
18
+ * feImage specular — a soft top-left specular highlight…
19
+ * feBlend screen — …blended `screen` over the displaced backdrop for the
20
+ * polished-glass catch.
21
+ *
22
+ * The filter ships INLINE as a data-URI in the `backdrop-filter` declaration, so
23
+ * the consumer gets it with zero markup. `backdrop-filter: url(#…)` is
24
+ * Chromium-only (WebKit bug 245510 open, Firefox not shipping), so the whole
25
+ * decl sits behind `@supports (backdrop-filter: url(#…))`; a non-supporting
26
+ * engine paints the un-gated blur base alone (no broken `url()` reference).
27
+ *
28
+ * RE-HOME (AW.W23 — decouple from the W22-folded compound). At HEAD the
29
+ * refraction rule was bound to `.glass-specular-track.glass-refract`. W22 folds
30
+ * `glass-specular-track` into `.glass-material`, retiring the compound's left
31
+ * operand, so the rule is re-homed onto `.glass-material.glass-refract` — the
32
+ * refraction garnish opts in on ANY `.glass-material` surface, no dependency on
33
+ * the now-aliased `glass-specular-track` class. The garnish stays PE-only over
34
+ * the blur base; it is NEVER promoted to the card/primitive substrate
35
+ * (feDisplacementMap is resize-expensive — only the `scale` attr animates
36
+ * without rebuilding the map).
37
+ */
38
+
39
+ @layer components {
40
+ /* The shipped filter graph as a data-URI SVG. A `0×0` host SVG mounted via
41
+ the CSS asset keeps the `#glass-refract` id resolvable in-document. */
42
+ .glass-refract {
43
+ /* The named convex-lens displacement filter, baked from the squircle
44
+ surface profile. Composed as a custom property so the @supports arm
45
+ below references one source. */
46
+ --glass-refract-filter: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='glass-refract' x='0' y='0' width='100%25' height='100%25'%3E%3CfeImage href='data:image/svg+xml,%253Csvg xmlns=%2522http://www.w3.org/2000/svg%2522 width=%2522100%2522 height=%2522100%2522%253E%253CradialGradient id=%2522m%2522 cx=%252250%2525%2522 cy=%252250%2525%2522 r=%252250%2525%2522%253E%253Cstop offset=%25220%2525%2522 stop-color=%2522rgb(128,128,128)%2522/%253E%253Cstop offset=%252270%2525%2522 stop-color=%2522rgb(128,128,128)%2522/%253E%253Cstop offset=%2522100%2525%2522 stop-color=%2522rgb(255,255,128)%2522/%253E%253C/radialGradient%253E%253Crect width=%2522100%2522 height=%2522100%2522 fill=%2522url(%2523m)%2522/%253E%253C/svg%253E' result='map'/%3E%3CfeDisplacementMap in='SourceGraphic' in2='map' scale='28' xChannelSelector='R' yChannelSelector='G' result='disp'/%3E%3CfeImage href='data:image/svg+xml,%253Csvg xmlns=%2522http://www.w3.org/2000/svg%2522 width=%2522100%2522 height=%2522100%2522%253E%253CradialGradient id=%2522s%2522 cx=%252230%2525%2522 cy=%252225%2525%2522 r=%252260%2525%2522%253E%253Cstop offset=%25220%2525%2522 stop-color=%2522rgba(255,255,255,0.5)%2522/%253E%253Cstop offset=%2522100%2525%2522 stop-color=%2522rgba(255,255,255,0)%2522/%253E%253C/radialGradient%253E%253Crect width=%2522100%2522 height=%2522100%2522 fill=%2522url(%2523s)%2522/%253E%253C/svg%253E' result='spec'/%3E%3CfeBlend in='disp' in2='spec' mode='screen'/%3E%3C/filter%3E%3C/svg%3E#glass-refract");
47
+ }
48
+
49
+ /* The gated consuming selector — RE-HOMED onto `.glass-material.glass-refract`
50
+ (decoupled from the W22-folded `.glass-specular-track` compound). The
51
+ refraction composes the shipped `#glass-refract` filter OVER the host's
52
+ existing blur; it is additive (the blur already painted, this sharpens the
53
+ refraction edge where the engine supports it). */
54
+ @supports (backdrop-filter: url("#glass-refract")) {
55
+ .glass-material.glass-refract,
56
+ .glass-refract {
57
+ backdrop-filter: var(--glass-blur-resting) var(--glass-refract-filter);
58
+ }
59
+ }
60
+ }
@@ -1,120 +1,45 @@
1
1
  /* Pointer-anchored moving specular — the iOS-26 Liquid Glass illuminate-from-
2
- * within behaviour (AV.W15 M2, the HEADLINE fold).
2
+ * within behaviour.
3
3
  *
4
- * A masked radial-gradient catch-light rides the material surface at the
5
- * pointer position. The consumer writes the pointer location onto the element
6
- * as `--mouse-x` / `--mouse-y` (percentages) via a thin pointer-listener; this
7
- * rung maps those onto the typed `--specular-x` / `--specular-y` properties
8
- * (registered in tokens.css §11b) and paints a `radial-gradient(circle at …)`
9
- * over a `mask-image` so the glow stays clipped to the glass plate and travels
10
- * with the cursor — "the light travels around the material / illuminate under
11
- * your fingertip".
4
+ * AW.W22 — the moving-specular `::before` body (the masked radial catch-light,
5
+ * the rest/hover/active intensity steps, the var() fallback) was FOLDED into the
6
+ * single `.glass-material` mixin in glass.css, where the five ladder rungs +
7
+ * `.glass-card` + the `.dock-icon-button` control + `.glass-specular-track`
8
+ * compose it from ONE source. There is NO duplicate `::before` body here.
12
9
  *
13
- * Baseline floors: `@property` 2024-07-09 (the smooth position interpolation),
14
- * CSS `mask-image` 2023-12-07 (the surface-clip). Both carry a `var()` fallback
15
- * so an engine without typed-property animation still paints a centred catch-
16
- * light, and an engine without `mask` still paints the radial (unmasked but
17
- * surface-bounded by the `::before` `inset: 0` + `border-radius: inherit`).
10
+ * `.glass-specular-track` is now a thin ALIAS — it is listed in the
11
+ * `.glass-material` selector GROUP in glass.css, so a `.glass-specular-track`
12
+ * element gets the unified material (the `::before` catch-light, the rim, the
13
+ * interaction-light) with zero duplicate paint. This keeps the historical
14
+ * opt-in call sites (Card.vue, any consumer that still reaches for the class)
15
+ * painting the catch-light without a legacy shim — the alias is a live
16
+ * composition onto the unified mixin, not a dead orphan.
18
17
  *
19
- * The pointer-position WRITE (`--mouse-x` / `--mouse-y`) is the consumer's seam
20
- * (DockIconButton wires it; Button glass + Card hover opt in). The CSS half —
21
- * the masked radial, the reduced-motion static bracket, the var() fallback, the
22
- * @supports-gated refraction garnish — is the deliverable here.
18
+ * This file carries ONLY:
19
+ * 1. The reduced-motion + reduced-transparency a11y brackets, pointed at the
20
+ * `.glass-material` group (the canonical specular owner) so the guards
21
+ * cover every band surface, not just the opt-in class.
22
+ * 2. The `@supports`-gated `#glass-refract` refraction garnish — RE-HOMED at
23
+ * AW.W23 off the (now-folded) `.glass-specular-track.glass-refract`
24
+ * compound onto `.glass-material.glass-refract` (see glass-refract.css for
25
+ * the shipped filter asset + the re-homed consuming selector).
23
26
  */
24
27
 
25
- @layer components {
26
- /* The opt-in class. A `::before` pseudo carries the moving catch-light so it
27
- composes over the host's own glass background/blur without a second
28
- backdrop-filter (no glass-on-glass — the specular is a LIGHT layer, not a
29
- second plate). The host need not be positioned-relative by us; the five
30
- glass tiers + the dock + Button already mint `position: relative`. */
31
- .glass-specular-track::before {
32
- content: "";
33
- position: absolute;
34
- inset: 0;
35
- border-radius: inherit;
36
- pointer-events: none;
37
- z-index: 1;
38
- /* Map the consumer's pointer write onto the typed properties, ON THE
39
- PSEUDO. The `--mouse-x/--mouse-y` write lands on the HOST (inline
40
- style); those are UNREGISTERED custom properties, so they inherit
41
- down to this `::before` naturally. The registered `--specular-*`
42
- props are `inherits: false` (per-surface ownership), which means a
43
- `::before` does NOT see a value set on its originating element — so
44
- the mapping must be declared HERE, on the pseudo, where it owns its
45
- own typed value. The `var(--mouse-x, 50%)` floor is the centred
46
- fallback: an engine that never receives a pointer write paints a
47
- centred catch-light, never a broken/absent one. */
48
- --specular-x: var(--mouse-x, 50%);
49
- --specular-y: var(--mouse-y, 50%);
50
- /* The catch-light intensity floor at rest; hover/active lift it (the
51
- pseudo-targeting rules below). Registered `<number>`, so the layer
52
- opacity it drives interpolates smoothly. */
53
- --specular-intensity: 0.35;
54
- /* The travelling catch-light. A soft circular glow centred on the
55
- tracked pointer position; the warm-white tint keeps the warm-cream
56
- identity (NOT iOS blue). The gradient carries FIXED stop alphas — the
57
- `--specular-intensity` multiplier drives the layer `opacity` below
58
- (NOT a `calc()` in the stop alpha: a registered-`@property` `var()`
59
- nested in `calc()` inside an `hsl()` alpha inside a gradient does not
60
- substitute in Chromium — it computes to 0. Driving the whole layer's
61
- `opacity` is both correct AND the cleaner model — intensity IS a
62
- layer brightness, not a per-stop axis). */
63
- background: radial-gradient(
64
- circle at var(--specular-x, 50%) var(--specular-y, 50%),
65
- hsl(40 30% 100% / 0.55) 0%,
66
- hsl(40 30% 100% / 0.22) 22%,
67
- transparent 55%
68
- );
69
- opacity: var(--specular-intensity, 0.35);
70
- /* Clip the glow to the plate so it rides the surface (the "light travels
71
- AROUND the material" read). `mask-image` Baseline 2023-12-07; a
72
- non-supporting engine ignores it and the `inset: 0` + radius-inherit
73
- already bound the glow to the host rectangle. */
74
- mask-image: radial-gradient(
75
- circle at var(--specular-x, 50%) var(--specular-y, 50%),
76
- black 0%,
77
- black 55%,
78
- transparent 75%
79
- );
80
- mix-blend-mode: screen;
81
- /* Smooth the position + intensity between pointer samples. The typed
82
- `@property` registrations make the percentage stops interpolable, so
83
- the catch-light position SETTLES rather than snapping frame-to-frame;
84
- the intensity rides the layer `opacity` transition. */
85
- transition:
86
- --specular-x var(--duration-fast, 150ms) var(--ease-standard, ease-out),
87
- --specular-y var(--duration-fast, 150ms) var(--ease-standard, ease-out),
88
- opacity var(--duration-normal, 240ms) var(--ease-standard, ease-out);
89
- }
90
-
91
- .dark .glass-specular-track::before {
92
- /* Softer over the deep canvas — the screen blend lifts harder on dark. */
93
- --specular-intensity: 0.22;
94
- }
95
-
96
- /* Hover/active lift the intensity — the "illuminate under your fingertip"
97
- brighten on engagement; settles back on leave via the opacity transition
98
- above. The rules target the `::before` (the intensity lives on the pseudo
99
- — see the mapping note above). */
100
- .glass-specular-track:hover::before {
101
- --specular-intensity: 0.6;
102
- }
103
- .glass-specular-track:active::before {
104
- --specular-intensity: 0.85;
105
- }
106
- }
107
-
108
28
  /* ── Reduced-motion: pin the specular STATIC ───────────────────────────────
109
29
  The MANDATORY guard. Under `prefers-reduced-motion: reduce` the catch-light
110
- paints static at rest position — no pointer-tracking (the position pins to
111
- centre, ignoring the consumer's `--mouse-x/--mouse-y` write), no transition
112
- animation. The centred catch-light + the warm-cream tint survive; only the
113
- MOTION is removed. The pin + the `transition: none` live on the `::before`
114
- (where the typed `--specular-*` props are owned), overriding the pointer-map
115
- above by order — the `var(--mouse-x)` write is ignored, the catch-light pins
116
- centred. */
30
+ paints static at rest position — no pointer-tracking, no transition. The pin
31
+ covers the unified `.glass-material` group AND the `.glass-specular-track`
32
+ alias so EVERY band surface (not just the opt-in class) freezes the position
33
+ while keeping the centred catch-light + the warm-cream tint. */
117
34
  @media (prefers-reduced-motion: reduce) {
35
+ .glass-material::before,
36
+ .glass-wash::before,
37
+ .glass-quiet::before,
38
+ .glass-resting::before,
39
+ .glass-floating::before,
40
+ .glass-overlay::before,
41
+ .glass-card::before,
42
+ .dock-icon-button::before,
118
43
  .glass-specular-track::before {
119
44
  --specular-x: 50%;
120
45
  --specular-y: 50%;
@@ -125,30 +50,18 @@
125
50
  /* ── Reduced-transparency: drop the specular ───────────────────────────────
126
51
  When the user requests reduced transparency the surface goes opaque (the
127
52
  glass.css bracket maps the blurs to `none`); a travelling catch-light on a
128
- flat opaque surface is decoration without substrate, so it drops too. */
53
+ flat opaque surface is decoration without substrate, so it drops too — across
54
+ the whole material group. */
129
55
  @media (prefers-reduced-transparency: reduce) {
56
+ .glass-material::before,
57
+ .glass-wash::before,
58
+ .glass-quiet::before,
59
+ .glass-resting::before,
60
+ .glass-floating::before,
61
+ .glass-overlay::before,
62
+ .glass-card::before,
63
+ .dock-icon-button::before,
130
64
  .glass-specular-track::before {
131
65
  display: none;
132
66
  }
133
67
  }
134
-
135
- /* ── feDisplacementMap refraction garnish — PE-only, @supports-gated ────────
136
- True SVG `feDisplacementMap` lensing (`backdrop-filter: url(#…)`) is
137
- Chromium-only (WebKit bug #245510, Firefox not shipping) and resize-
138
- expensive. It lands ONLY as a progressive-enhancement garnish layered OVER
139
- the blur base, NEVER the substrate — gated `@supports (backdrop-filter:
140
- url(#…))` so a non-supporting engine paints the blur base alone (no broken
141
- `url(#…)` reference). A consumer opts a glass surface into the refraction by
142
- adding `.glass-refract` AND mounting the named SVG filter; the gate asserts
143
- the garnish never reaches the substrate. The blur base paints WITHOUT it. */
144
- @supports (backdrop-filter: url("#glass-refract")) {
145
- @layer components {
146
- .glass-specular-track.glass-refract {
147
- /* The garnish composes the consumer-mounted `#glass-refract` SVG
148
- filter OVER the host's existing blur. It is additive — the host's
149
- `backdrop-filter` blur already painted; this only sharpens the
150
- refraction edge where the engine supports it. */
151
- backdrop-filter: var(--glass-blur-resting) url("#glass-refract");
152
- }
153
- }
154
- }
@@ -29,6 +29,150 @@
29
29
  */
30
30
 
31
31
  @layer components {
32
+ /* ── The unified glass material (AW.W22) ──────────────────────────────
33
+ ONE source for the three material atoms that AV.W15 scattered onto
34
+ opt-in classes + single tiers:
35
+ (a) the moving-specular `::before` catch-light (folded here from
36
+ glass-specular-track.css — that file now keeps only the thin
37
+ `.glass-specular-track` ALIAS + the brackets that point HERE),
38
+ (b) the `--glass-edge-light` full-perimeter rim (moved off the
39
+ floating-only block so EVERY rung carries it),
40
+ (c) the pointer interaction-light hooks (the `--specular-*`
41
+ `@property` channel mapped from the consumer's --mouse-x/--mouse-y
42
+ write).
43
+ The five ladder rungs + `.glass-card` + the `.dock-icon-button` CONTROL
44
+ compose `.glass-material`, so a Switch thumb, a glass Button, a Dialog,
45
+ and a Sheet all read as the SAME material without per-component wiring.
46
+
47
+ The `.glass-dock` SHELL is OUT of this group BY DESIGN — it hand-rolls a
48
+ parallel surface (its own `--glass-*-dock` family + a local rim + an
49
+ instrument-strip `::before`) the dock arm owns; the dock's catch-light
50
+ lives on its CONTROL (`.dock-icon-button`, listed below), not the shell.
51
+
52
+ The selector GROUP is the single source. Adding a surface to the band
53
+ material is one comma-entry here, not a new opt-in class string. */
54
+ .glass-material,
55
+ .glass-wash,
56
+ .glass-quiet,
57
+ .glass-resting,
58
+ .glass-floating,
59
+ .glass-overlay,
60
+ .glass-card,
61
+ .glass-specular-track,
62
+ .dock-icon-button {
63
+ position: relative;
64
+ }
65
+
66
+ /* (b) The rim — UNIFORM across all rungs (ONE `--glass-edge-light` token,
67
+ not a per-rung family). The floating/overlay tiers previously wired it
68
+ into their box-shadow stack; it now rides the material group as a
69
+ dedicated `::after` inset ring so it composes alongside (not inside) each
70
+ tier's own shadow recipe. The `::after` grain overlay already claims the
71
+ five rungs' `::after`; the rim is folded INTO that same overlay's
72
+ box-shadow below (the grain `::after` gets the rim inset ring) so no
73
+ pseudo-element collides. */
74
+
75
+ /* (a)+(c) The moving-specular `::before` catch-light — the iOS-26
76
+ illuminate-from-within behaviour. A masked radial-gradient glow rides the
77
+ surface at the pointer position (--mouse-x/--mouse-y → the typed
78
+ --specular-* channel). Folded VERBATIM from glass-specular-track.css so
79
+ there is exactly ONE specular source. */
80
+ .glass-material::before,
81
+ .glass-wash::before,
82
+ .glass-quiet::before,
83
+ .glass-resting::before,
84
+ .glass-floating::before,
85
+ .glass-overlay::before,
86
+ .glass-card::before,
87
+ .glass-specular-track::before,
88
+ .dock-icon-button::before {
89
+ content: "";
90
+ position: absolute;
91
+ inset: 0;
92
+ border-radius: inherit;
93
+ pointer-events: none;
94
+ z-index: 1;
95
+ /* Map the consumer's pointer write onto the typed properties, ON THE
96
+ PSEUDO. The `--mouse-x/--mouse-y` write lands on the HOST (inline
97
+ style) as UNREGISTERED props that inherit down to this `::before`;
98
+ the registered `--specular-*` props are `inherits: false`, so the
99
+ mapping is declared HERE where the pseudo owns its typed value. The
100
+ `var(--mouse-x, 50%)` floor is the centred fallback. */
101
+ --specular-x: var(--mouse-x, 50%);
102
+ --specular-y: var(--mouse-y, 50%);
103
+ /* The catch-light intensity floor at rest; hover/active lift it (the
104
+ interaction-light state machine below). */
105
+ --specular-intensity: 0.35;
106
+ /* The travelling catch-light — a soft warm-white circular glow centred
107
+ on the tracked pointer position (NOT iOS blue; keeps the warm-cream
108
+ identity). The `--specular-intensity` multiplier drives the layer
109
+ `opacity`, not a per-stop alpha (a registered-`@property` var() nested
110
+ in an hsl() alpha computes to 0 in Chromium). */
111
+ background: radial-gradient(
112
+ circle at var(--specular-x, 50%) var(--specular-y, 50%),
113
+ hsl(40 30% 100% / 0.55) 0%,
114
+ hsl(40 30% 100% / 0.22) 22%,
115
+ transparent 55%
116
+ );
117
+ opacity: var(--specular-intensity, 0.35);
118
+ /* Clip the glow to the plate so it rides the surface. `mask-image`
119
+ Baseline 2023-12-07; a non-supporting engine ignores it and the
120
+ `inset: 0` + radius-inherit already bound the glow. */
121
+ mask-image: radial-gradient(
122
+ circle at var(--specular-x, 50%) var(--specular-y, 50%),
123
+ black 0%,
124
+ black 55%,
125
+ transparent 75%
126
+ );
127
+ mix-blend-mode: screen;
128
+ /* Smooth the position + intensity between pointer samples (the typed
129
+ @property regs make the percentage stops interpolable). */
130
+ transition:
131
+ --specular-x var(--duration-fast, 150ms) var(--ease-standard, ease-out),
132
+ --specular-y var(--duration-fast, 150ms) var(--ease-standard, ease-out),
133
+ opacity var(--duration-normal, 240ms) var(--ease-standard, ease-out);
134
+ }
135
+
136
+ .dark .glass-material::before,
137
+ .dark .glass-wash::before,
138
+ .dark .glass-quiet::before,
139
+ .dark .glass-resting::before,
140
+ .dark .glass-floating::before,
141
+ .dark .glass-overlay::before,
142
+ .dark .glass-card::before,
143
+ .dark .glass-specular-track::before,
144
+ .dark .dock-icon-button::before {
145
+ /* Softer over the deep canvas — the screen blend lifts harder on dark. */
146
+ --specular-intensity: 0.22;
147
+ }
148
+
149
+ /* (c) Interaction-light lockstep — hover/active lift the intensity in the
150
+ SAME rungs the scale-press squish keys off, so material-light fires in
151
+ lockstep with the press across every glass surface. Settles back on leave
152
+ via the opacity transition above. */
153
+ .glass-material:hover::before,
154
+ .glass-wash:hover::before,
155
+ .glass-quiet:hover::before,
156
+ .glass-resting:hover::before,
157
+ .glass-floating:hover::before,
158
+ .glass-overlay:hover::before,
159
+ .glass-card:hover::before,
160
+ .glass-specular-track:hover::before,
161
+ .dock-icon-button:hover::before {
162
+ --specular-intensity: 0.6;
163
+ }
164
+ .glass-material:active::before,
165
+ .glass-wash:active::before,
166
+ .glass-quiet:active::before,
167
+ .glass-resting:active::before,
168
+ .glass-floating:active::before,
169
+ .glass-overlay:active::before,
170
+ .glass-card:active::before,
171
+ .glass-specular-track:active::before,
172
+ .dock-icon-button:active::before {
173
+ --specular-intensity: 0.85;
174
+ }
175
+
32
176
  /* ── Five-tier surface ladder (v0.8.0; visual only, no geometry) ──
33
177
  Tier semantics, alpha-monotonic per R3-spec §A:
34
178
  wash ~0.30α + light blur (was `subtle`)
@@ -45,12 +189,19 @@
45
189
  which modern Chromium then dropped from the CSSOM (the prefixed
46
190
  alias is parser-rejected post-Chrome 113). Single-source-of-truth
47
191
  authorship lets the build pipeline deliver the right matrix. */
192
+ /* AW.W23 — every rung's background composes the adaptive-tint recipe
193
+ (`color-mix(in oklab, <rung bg>, var(--glass-tint-source) var(--glass-tint-
194
+ strength))`). With the default `--glass-tint-strength: 0%` the mix is a
195
+ no-op = byte-identical to the un-tinted rung; a consumer biases the surface
196
+ toward a sampled backdrop hue by setting `--glass-tint-source` + a
197
+ strength ≤30%. oklab is the mwg-preferred tint space (NEVER an
198
+ `oklch(from …)` lightness-shift — browsers don't gamut-map yet). */
48
199
  .glass-wash {
49
200
  position: relative;
50
- background: var(--glass-bg-wash);
201
+ background: color-mix(in oklab, var(--glass-bg-wash), var(--glass-tint-source) var(--glass-tint-strength));
51
202
  backdrop-filter: var(--glass-blur-wash);
52
203
  border: 1px solid var(--glass-border-wash);
53
- box-shadow: var(--glass-shadow-wash);
204
+ box-shadow: var(--glass-material-rim), var(--glass-shadow-wash);
54
205
  }
55
206
 
56
207
  /* AL-W10 SLIM under-shadow wiring (tokens.css §AL-W10 SLIM —
@@ -67,42 +218,48 @@
67
218
  Spine tier wires its own rung at instrument-chassis.css. */
68
219
  .glass-quiet {
69
220
  position: relative;
70
- background: var(--glass-bg-quiet);
221
+ background: color-mix(in oklab, var(--glass-bg-quiet), var(--glass-tint-source) var(--glass-tint-strength));
71
222
  backdrop-filter: var(--glass-blur-quiet);
72
223
  border: 1px solid var(--glass-border-quiet);
73
- box-shadow: var(--glass-under-shadow-quiet), var(--glass-shadow-quiet);
224
+ box-shadow:
225
+ var(--glass-material-rim),
226
+ var(--glass-under-shadow-quiet),
227
+ var(--glass-shadow-quiet);
74
228
  }
75
229
 
76
230
  .glass-resting {
77
231
  position: relative;
78
- background: var(--glass-bg-resting);
232
+ background: color-mix(in oklab, var(--glass-bg-resting), var(--glass-tint-source) var(--glass-tint-strength));
79
233
  backdrop-filter: var(--glass-blur-resting);
80
234
  border: 1px solid var(--glass-border-resting);
81
- box-shadow: var(--glass-under-shadow-default), var(--glass-shadow-resting);
235
+ box-shadow:
236
+ var(--glass-material-rim),
237
+ var(--glass-under-shadow-default),
238
+ var(--glass-shadow-resting);
82
239
  }
83
240
 
84
- /* AV.W15 M3 — the floating tier wires the full-perimeter `--glass-edge-light`
85
- rim as an inset layer alongside the under-shadow + lift stack. The rim is
86
- the catch-light that "defines the silhouette" (WWDC25 sess. 219), distinct
87
- from the top-only `--glass-highlight`. It rides INSIDE the box-shadow stack
88
- so it composes with the under-shadow floor and the elevation lift. */
241
+ /* AV.W15 M3 / AW.W22 — the floating tier carries the full-perimeter rim (the
242
+ catch-light that "defines the silhouette", WWDC25 sess. 219). The rim now
243
+ comes from the unified `--glass-material-rim` (= `--glass-edge-light`) so
244
+ it shares ONE source with the rest of the ladder; it rides INSIDE the
245
+ box-shadow stack so it composes with the under-shadow floor + the lift. */
89
246
  .glass-floating {
90
247
  position: relative;
91
- background: var(--glass-bg-floating);
248
+ background: color-mix(in oklab, var(--glass-bg-floating), var(--glass-tint-source) var(--glass-tint-strength));
92
249
  backdrop-filter: var(--glass-blur-floating);
93
250
  border: 1px solid var(--glass-border-floating);
94
251
  box-shadow:
95
- var(--glass-edge-light),
252
+ var(--glass-material-rim),
96
253
  var(--glass-under-shadow-vivid),
97
254
  var(--glass-shadow-floating);
98
255
  }
99
256
 
100
257
  .glass-overlay {
101
258
  position: relative;
102
- background: var(--glass-bg-overlay);
259
+ background: color-mix(in oklab, var(--glass-bg-overlay), var(--glass-tint-source) var(--glass-tint-strength));
103
260
  backdrop-filter: var(--glass-blur-overlay);
104
261
  border: 1px solid var(--glass-border-overlay);
105
- box-shadow: var(--glass-shadow-overlay);
262
+ box-shadow: var(--glass-material-rim), var(--glass-shadow-overlay);
106
263
  }
107
264
 
108
265
  /* ── Content-aware under-shadow modifier (AV.W15 M4) ──────────────
@@ -136,7 +293,7 @@
136
293
  }
137
294
  .glass-floating[data-over-content="solid"] {
138
295
  box-shadow:
139
- var(--glass-edge-light),
296
+ var(--glass-material-rim),
140
297
  var(--glass-under-shadow-default),
141
298
  var(--glass-shadow-floating);
142
299
  }
@@ -159,6 +316,28 @@
159
316
  mix-blend-mode: overlay;
160
317
  }
161
318
 
319
+ /* (b) The unified rim — `--glass-edge-light` full-perimeter inset ring on
320
+ EVERY material surface (AW.W22). Previously the rim was wired only into
321
+ the floating/overlay tiers' box-shadow stack; promoting it to the material
322
+ group makes wash/quiet/resting/card/dock-control all carry the same
323
+ silhouette catch-light from ONE token. The rim is its OWN `box-shadow`
324
+ declaration on the material group so it composes uniformly without
325
+ perturbing each tier's elevation recipe (the floating/overlay tiers drop
326
+ their now-redundant inline `--glass-edge-light` layer below). The alpha is
327
+ deliberately low so the rim never eats AA legibility over a busy
328
+ backdrop. */
329
+ .glass-material,
330
+ .glass-wash,
331
+ .glass-quiet,
332
+ .glass-resting,
333
+ .glass-floating,
334
+ .glass-overlay,
335
+ .glass-card,
336
+ .glass-specular-track,
337
+ .dock-icon-button {
338
+ --glass-material-rim: var(--glass-edge-light);
339
+ }
340
+
162
341
  .dark .glass-wash::after,
163
342
  .dark .glass-quiet::after,
164
343
  .dark .glass-resting::after,
@@ -179,11 +358,11 @@
179
358
  --card-focus-shadow: var(--shadow-card);
180
359
  --card-focus-border: var(--glass-border-quiet);
181
360
  position: relative;
182
- background: var(--glass-bg-quiet);
361
+ background: color-mix(in oklab, var(--glass-bg-quiet), var(--glass-tint-source) var(--glass-tint-strength));
183
362
  backdrop-filter: var(--glass-blur-quiet);
184
363
  border: 1px solid var(--card-focus-border);
185
364
  border-radius: var(--radius-card);
186
- box-shadow: var(--card-focus-shadow);
365
+ box-shadow: var(--glass-material-rim), var(--card-focus-shadow);
187
366
  contain: layout style;
188
367
  transition:
189
368
  box-shadow var(--duration-fast) var(--ease-standard),
@@ -322,16 +501,22 @@
322
501
  }
323
502
 
324
503
  /* AQ.W4 §W4.1 — validity vocabulary on `.input-pill` (Input + Textarea).
325
- The forgiving `:where(:user-invalid, .user-invalid-fallback)` group is
326
- ONE rule covering native + the `useUserInvalidAria` fallback class (css
327
- §3.2; NOT a live+dead alias — the class is the SOLE fallback for engines
328
- without `:user-invalid`). The destructive/success tints reuse the
329
- semantic tokens via `color-mix` per the house pattern (W2). Non-color
504
+ The forgiving `:where(:user-invalid, .user-invalid-fallback,
505
+ [aria-invalid="true"])` group is ONE rule covering THREE trigger surfaces:
506
+ native constraint-validation (`:user-invalid`), the `useUserInvalidAria`
507
+ fallback class for engines without `:user-invalid` (css §3.2; NOT a
508
+ live+dead alias — the class is the SOLE fallback), AND (AW.W18) the
509
+ imperative `[aria-invalid="true"]` attribute. The `[aria-invalid]` arm
510
+ serves APP-DRIVEN validity — a custom key match, an async server check —
511
+ where there is no native constraint to trip `:user-invalid`, so the app
512
+ sets the ARIA attribute and the library's own ring paints with no
513
+ `:deep()` re-paint at the consumer. The destructive/success tints reuse
514
+ the semantic tokens via `color-mix` per the house pattern (W2). Non-color
330
515
  reinforcement is the error TEXT (W4.5 `error` slot) + the `required`
331
516
  asterisk — the border/bg shift is the supplementary cue, never the sole
332
517
  one. `:user-valid` is intentionally subtle (border only, no bg fill) so
333
518
  a long valid form is not a wall of green. */
334
- .input-pill:where(:user-invalid, .user-invalid-fallback) {
519
+ .input-pill:where(:user-invalid, .user-invalid-fallback, [aria-invalid="true"]) {
335
520
  border-color: var(--destructive);
336
521
  background: color-mix(in srgb, var(--destructive) 8%, var(--glass-bg-quiet));
337
522
  }
@@ -339,9 +524,10 @@
339
524
  border-color: var(--success);
340
525
  }
341
526
  /* Invalid + keyboard-focused → destructive ring (overrides the accent
342
- `:focus-visible` ring above by order + the extra `:user-invalid`
343
- compound). */
344
- .input-pill:where(:user-invalid, .user-invalid-fallback):focus-visible {
527
+ `:focus-visible` ring above by order + the extra invalid compound). The
528
+ `[aria-invalid]` arm rides the same widened group so an app-driven
529
+ invalid field shows the destructive ring on focus too. */
530
+ .input-pill:where(:user-invalid, .user-invalid-fallback, [aria-invalid="true"]):focus-visible {
345
531
  border-color: var(--destructive);
346
532
  box-shadow: 0 0 0 var(--focus-ring-width)
347
533
  color-mix(in srgb, var(--destructive) 35%, transparent);
@@ -404,6 +590,36 @@
404
590
  var(--progress-rail-glow-x, 4px) 0 var(--progress-rail-glow-blur, 8px)
405
591
  color-mix(in srgb, var(--progress-rail-fill, var(--primary)) var(--progress-rail-glow-strength, 55%), transparent);
406
592
  }
593
+
594
+ /* ── AW.W23 — chromatic edge dispersion (the polished-glass rim fringe) ──
595
+ An opt-in `.glass-chromatic` composes the warm/cool oklab fringe OVER the
596
+ W22 uniform rim (it does NOT replace it — the rim still rides each rung's
597
+ box-shadow). The fringe is a transparency garnish, so it composes ONLY
598
+ under `prefers-reduced-transparency: no-preference` and drops under
599
+ `reduce` (handled by the bracket below). The fringe lands as a dedicated
600
+ inset box-shadow pair on the material surface so it adds onto the rim. */
601
+ @media (prefers-reduced-transparency: no-preference) {
602
+ .glass-chromatic {
603
+ box-shadow: var(--glass-edge-dispersion), var(--glass-material-rim);
604
+ }
605
+ }
606
+ }
607
+
608
+ /* ── AW.W23 — corner-shape: squircle PE (Chrome 139+) ───────────────────────
609
+ The literal iOS-26 superellipse corner geometry that `backdrop-filter`,
610
+ borders, and shadows follow. `@supports (corner-shape: squircle)`-gated over
611
+ the un-gated `border-radius` round, which stays the fallback at zero cost on
612
+ every other engine (Safari/Firefox). The `corner-shape` decl sits ONLY inside
613
+ the `@supports` block — a leak onto the un-gated base would break the round
614
+ fallback on a partial-support engine. */
615
+ @supports (corner-shape: squircle) {
616
+ @layer components {
617
+ .glass-card,
618
+ .glass-btn,
619
+ .btn-pill {
620
+ corner-shape: squircle;
621
+ }
622
+ }
407
623
  }
408
624
 
409
625
  /* ── Accessibility: reduced transparency ────────── */
@@ -114,6 +114,7 @@
114
114
  @import "./theme.css";
115
115
  @import "./glass.css";
116
116
  @import "./glass-specular-track.css";
117
+ @import "./glass-refract.css";
117
118
  @import "./paper.css";
118
119
  @import "./dock.css";
119
120
  @import "./dock-controls.css";
@@ -32,6 +32,9 @@
32
32
  --radius-md: 6px;
33
33
  --radius-xl: 12px;
34
34
  --radius-2xl: 1rem;
35
+ /* AW.W3b — a finite radius rung above --radius-2xl (16px), below
36
+ --radius-pill (the stadium). The big-dock card shell's corner geometry. */
37
+ --radius-3xl: 1.5rem;
35
38
  --radius-pill: 9999px;
36
39
 
37
40
  /* Semantic radius aliases — plain @theme (not inline; see header note).
@@ -46,6 +49,9 @@
46
49
  --radius-button: var(--radius);
47
50
  --radius-badge: var(--radius-pill);
48
51
  --radius-dock: var(--radius-pill);
52
+ /* AW.W3b — the big-dock card shell's default (comfortable-density) radius.
53
+ The density-scaled --dock-card-radius (dock.css) resolves through this. */
54
+ --radius-dock-card: var(--radius-3xl);
49
55
  --radius-tooltip: var(--radius-lg);
50
56
  }
51
57