@mkbabb/glass-ui 3.0.0 → 3.1.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 (184) hide show
  1. package/dist/{CardFooter-Yi0xtLLd.js → CardFooter-CSGcJkqa.js} +1 -1
  2. package/dist/{CommandShortcut-BNDzfFnB.js → CommandShortcut-DWT19a2Y.js} +3 -3
  3. package/dist/{ContextMenuSubContent-DLEyeqbh.js → ContextMenuSubContent-gAFxJ-qi.js} +4 -4
  4. package/dist/{DataTable-Ce00dbHD.js → DataTable-R8-Zidms.js} +3 -3
  5. package/dist/{DialogContent-DSo7PKlU.js → DialogContent-2fALDSvc.js} +3 -3
  6. package/dist/{DialogFooter-D5KY6sCX.js → DialogFooter-ClrNEOVU.js} +2 -2
  7. package/dist/{DiscoGlyph-wRA02zAJ.js → DiscoGlyph-C3JfMnRV.js} +1 -1
  8. package/dist/{GlyphFace-BnPMUZ16.js → GlyphFace-BRS8vUb7.js} +1 -1
  9. package/dist/HoverPopover-CWFCfLx3.js +96 -0
  10. package/dist/{IconTooltip-GIeWdo64.js → IconTooltip-BkaA7tZ2.js} +1 -1
  11. package/dist/{Input-CBvqW8kZ.js → Input-DDpFn568.js} +3 -5
  12. package/dist/Label-DJty89bp.js +36 -0
  13. package/dist/{MetricBadge-DRBB18Xq.js → MetricBadge-DmAihkXd.js} +1 -1
  14. package/dist/{Notification-D97JO0fK.js → Notification-OqIpADml.js} +3 -3
  15. package/dist/NumberFieldContent-DTH9gb_N.js +141 -0
  16. package/dist/{PopoverContent-BCH4eYs8.js → PopoverContent-EiklFrna.js} +1 -1
  17. package/dist/{Progress-CCH-2UBR.js → Progress-FApA9fm_.js} +1 -1
  18. package/dist/{ScrollingText-7P8skg5W.js → ScrollingText-BFd0i2zJ.js} +2 -2
  19. package/dist/{SelectScrollDownButton-BwTexKeY.js → SelectScrollDownButton-Dth8-wXQ.js} +4 -4
  20. package/dist/{Toaster-CY8gJu9E.js → Toaster-Bjlunvq4.js} +1 -1
  21. package/dist/UnderlineTabs-DAWMLmJG.js +37 -0
  22. package/dist/animated-digit.js +2 -2
  23. package/dist/api/index.d.ts +2 -0
  24. package/dist/api.js +1 -1
  25. package/dist/aurora.js +3 -3
  26. package/dist/badge.js +1 -1
  27. package/dist/{button-BlOW34DT.js → button-C0aHmBbt.js} +2 -0
  28. package/dist/button.js +1 -1
  29. package/dist/card.js +1 -1
  30. package/dist/carousel.js +5 -5
  31. package/dist/{check-Nuw7H9Yh.js → check-dwgetki8.js} +1 -1
  32. package/dist/{chevron-down-Du2b9vY_.js → chevron-down-DILQA1t6.js} +1 -1
  33. package/dist/{chevron-right-CtDxpE3w.js → chevron-right-fS7fal2t.js} +1 -1
  34. package/dist/{chevron-up-CenYokvI.js → chevron-up-BtYjYQOS.js} +1 -1
  35. package/dist/collapsible.js +1 -1
  36. package/dist/command.js +1 -1
  37. package/dist/components/custom/dialog-native/GlassDialogNative.vue.d.ts +57 -0
  38. package/dist/components/custom/dialog-native/index.d.ts +1 -0
  39. package/dist/components/custom/dock/composables/useLayerTransition.d.ts +20 -10
  40. package/dist/components/custom/hover-popover/HoverPopover.vue.d.ts +26 -4
  41. package/dist/components/custom/labeled-field/LabeledField.vue.d.ts +16 -2
  42. package/dist/components/custom/labeled-field/LabeledInput.vue.d.ts +17 -1
  43. package/dist/components/custom/labeled-field/LabeledSelect.vue.d.ts +2 -0
  44. package/dist/components/custom/labeled-field/LabeledSlider.vue.d.ts +2 -0
  45. package/dist/components/custom/labeled-field/LabeledSwitch.vue.d.ts +2 -0
  46. package/dist/components/ui/input/Input.vue.d.ts +10 -7
  47. package/dist/components/ui/label/Label.vue.d.ts +8 -0
  48. package/dist/components/ui/textarea/Textarea.vue.d.ts +45 -8
  49. package/dist/composables/dom/index.d.ts +1 -0
  50. package/dist/composables/dom/useUserInvalidAria.d.ts +32 -0
  51. package/dist/composables/motion/core/index.d.ts +2 -0
  52. package/dist/composables/motion/supportsCssTimeline.d.ts +8 -0
  53. package/dist/composables/motion/useRAFLoop.d.ts +7 -0
  54. package/dist/composables/motion/useScrollProgress.d.ts +6 -2
  55. package/dist/composables/motion/useStaggerReveal.d.ts +6 -0
  56. package/dist/composables/motion/useViewTransition.d.ts +31 -0
  57. package/dist/composables/motion/useYieldToMain.d.ts +29 -0
  58. package/dist/configurator.js +1 -1
  59. package/dist/confirm-dialog.js +3 -3
  60. package/dist/context-menu.js +2 -2
  61. package/dist/controls.js +2 -2
  62. package/dist/{createLucideIcon-rHu18UQW.js → createLucideIcon-Bn9a1b70.js} +2 -2
  63. package/dist/dark.js +1 -1
  64. package/dist/data-table.js +1 -1
  65. package/dist/dialog.js +2 -2
  66. package/dist/disco-glyph.js +1 -1
  67. package/dist/dock.js +196 -177
  68. package/dist/dom.js +5 -4
  69. package/dist/{dropdown-menu-gHSkffW7.js → dropdown-menu-BvRUamNs.js} +4 -4
  70. package/dist/dropdown-menu.js +1 -1
  71. package/dist/expandable-container.js +4 -4
  72. package/dist/forms.d.ts +1 -0
  73. package/dist/forms.js +47 -42
  74. package/dist/glass-carousel.js +1 -1
  75. package/dist/glass-panel.js +2 -2
  76. package/dist/glass-ui.css +1 -1
  77. package/dist/glass-ui.js +156 -275
  78. package/dist/glyph-face.js +2 -2
  79. package/dist/header-ribbon.js +1 -1
  80. package/dist/hover-card.js +1 -1
  81. package/dist/hover-popover.js +1 -1
  82. package/dist/icon-tooltip.js +1 -1
  83. package/dist/index.d.ts +1 -0
  84. package/dist/instrument-chassis.js +1 -1
  85. package/dist/instrument-rail.js +1 -1
  86. package/dist/keyboard.js +1 -1
  87. package/dist/label.js +1 -1
  88. package/dist/labeled-field.js +96 -57
  89. package/dist/metric-badge.js +1 -1
  90. package/dist/metric-stack.js +1 -1
  91. package/dist/{minimize-2-C_oyKVwZ.js → minimize-2-LsCJ_eNt.js} +1 -1
  92. package/dist/motion-core.js +135 -98
  93. package/dist/motion.js +3 -3
  94. package/dist/notification.js +1 -1
  95. package/dist/number-field.d.ts +1 -0
  96. package/dist/number-field.js +2 -0
  97. package/dist/paper-backdrop.js +1 -1
  98. package/dist/popover.js +1 -1
  99. package/dist/progress.js +1 -1
  100. package/dist/pulse.js +1 -1
  101. package/dist/reactive.js +2 -2
  102. package/dist/responsive-tabs.js +3 -3
  103. package/dist/scrolling-text.js +1 -1
  104. package/dist/{search-7XEx_6hq.js → search-DBAiUABx.js} +1 -1
  105. package/dist/search.js +7 -7
  106. package/dist/select.js +3 -3
  107. package/dist/separator.js +1 -1
  108. package/dist/{sheet-BsBdO5jq.js → sheet-CukNDezz.js} +53 -53
  109. package/dist/sheet.js +1 -1
  110. package/dist/{slider-BQaLYFLh.js → slider-DJvHkTRe.js} +3 -3
  111. package/dist/slider.js +1 -1
  112. package/dist/sortable-list.js +2 -2
  113. package/dist/styles/animations.css +77 -0
  114. package/dist/styles/cards.css +6 -2
  115. package/dist/styles/dock.css +37 -14
  116. package/dist/styles/glass.css +89 -6
  117. package/dist/styles/index.css +10 -1
  118. package/dist/styles/scroll-driven.css +72 -0
  119. package/dist/styles/theme.css +3 -0
  120. package/dist/styles/tokens.css +237 -24
  121. package/dist/styles/typography.css +21 -0
  122. package/dist/styles/utilities.css +176 -23
  123. package/dist/styles/view-transition.css +62 -0
  124. package/dist/switch.d.ts +1 -0
  125. package/dist/switch.js +2 -0
  126. package/dist/tabs.js +40 -36
  127. package/dist/timeline.js +2 -2
  128. package/dist/toast.js +1 -1
  129. package/dist/toggle-group.js +1 -1
  130. package/dist/tooltip.js +1 -1
  131. package/dist/typewriter.js +1 -1
  132. package/dist/{useAnimatedNumber-2l13GibX.js → useAnimatedNumber-DKQYVB7s.js} +1 -1
  133. package/dist/{useConfiguratorState-BpZi8QJu.js → useConfiguratorState-BR5vUDL8.js} +4 -4
  134. package/dist/{useIdleReady-DlzJicQH.js → useIdleReady-Cmkhm03v.js} +1 -1
  135. package/dist/{useTouchGate-BhhEMlwJ.js → useTouchGate-D9Zvrzyc.js} +1 -1
  136. package/dist/useUserInvalidAria-DVu1eTXG.js +29 -0
  137. package/dist/useViewTransition-DYIK6Gzb.js +16 -0
  138. package/dist/utils/index.d.ts +1 -0
  139. package/dist/utils/moveBefore.d.ts +15 -0
  140. package/dist/{x-Cb3NE2Ne.js → x-q7pJa83X.js} +1 -1
  141. package/package.json +17 -3
  142. package/src/styles/animations.css +77 -0
  143. package/src/styles/cards.css +6 -2
  144. package/src/styles/dock.css +37 -14
  145. package/src/styles/glass.css +89 -6
  146. package/src/styles/index.css +10 -1
  147. package/src/styles/scroll-driven.css +72 -0
  148. package/src/styles/theme.css +3 -0
  149. package/src/styles/tokens.css +237 -24
  150. package/src/styles/typography.css +21 -0
  151. package/src/styles/utilities.css +176 -23
  152. package/src/styles/view-transition.css +62 -0
  153. package/dist/HoverPopover-Btv4RQfv.js +0 -80
  154. package/dist/Label-C8QMJSsf.js +0 -32
  155. package/dist/UnderlineTabs-BtrUcXn-.js +0 -64
  156. /package/dist/{CollapsibleContent-DHRuXE3P.js → CollapsibleContent-CVMOcYlV.js} +0 -0
  157. /package/dist/{ContextMenuContent-CvXfU5qz.js → ContextMenuContent-otjFIu8v.js} +0 -0
  158. /package/dist/{HoverCardContent-4nN5-5bz.js → HoverCardContent-DaGrgJBO.js} +0 -0
  159. /package/dist/{InstrumentChassis-DOaVYyWq.js → InstrumentChassis-CnHTMxds.js} +0 -0
  160. /package/dist/{InstrumentRail-jHDqXj70.js → InstrumentRail-C6dEbi8E.js} +0 -0
  161. /package/dist/{ModalOverlay-DKLVY-cj.js → ModalOverlay-iWiAgbYH.js} +0 -0
  162. /package/dist/{PaperBackdrop-Bc2drCqJ.js → PaperBackdrop-CeZ-w0R0.js} +0 -0
  163. /package/dist/{SelectGroup-O69GTQ77.js → SelectGroup-DdR4tdDY.js} +0 -0
  164. /package/dist/{SelectSeparator-GTHxKO0a.js → SelectSeparator-CXm_hlqA.js} +0 -0
  165. /package/dist/{Separator-_NCypg_C.js → Separator-D8AUMhxY.js} +0 -0
  166. /package/dist/{Switch-CL0uxu8F.js → Switch-Cr1t_F_U.js} +0 -0
  167. /package/dist/{ToggleGroupItem-BYG_8M9M.js → ToggleGroupItem-OesUouE7.js} +0 -0
  168. /package/dist/{TooltipProvider-C5QLSPto.js → TooltipProvider-DE78vbEP.js} +0 -0
  169. /package/dist/{_plugin-vue_export-helper-n-_DRHWS.js → _plugin-vue_export-helper-Dq1MygBL.js} +0 -0
  170. /package/dist/{badge-BbxVKZfw.js → badge-x46my_Fo.js} +0 -0
  171. /package/dist/{constants-D-8FN28s.js → constants-DwBwnG8N.js} +0 -0
  172. /package/dist/{dockContext-BDGSrwsV.js → dockContext-D5NZCWJs.js} +0 -0
  173. /package/dist/{keys-DVkcUktU.js → keys-CaTQS-vx.js} +0 -0
  174. /package/dist/{menuItemVariants-B2nDL7zH.js → menuItemVariants-BsbGNq9C.js} +0 -0
  175. /package/dist/{presets-BMzCDrmR.js → presets-a-D93K1S.js} +0 -0
  176. /package/dist/{useGlassRenderer-DMDdMH55.js → useGlassRenderer-Ds-nmrGz.js} +0 -0
  177. /package/dist/{useGlobalDark-PMiP5Jku.js → useGlobalDark-B0WvLJE3.js} +0 -0
  178. /package/dist/{useIntersectionPause-CXYfYg_C.js → useIntersectionPause-IY2CwPQb.js} +0 -0
  179. /package/dist/{useInterval-COlTCeVa.js → useInterval-DVgGUf_y.js} +0 -0
  180. /package/dist/{useKeyboardShortcuts-CPO4AhLx.js → useKeyboardShortcuts-Dpw_RUcB.js} +0 -0
  181. /package/dist/{useResizeObserver-F4aRR4Cj.js → useResizeObserver-Cg9npuM3.js} +0 -0
  182. /package/dist/{useSortable-Ck0rBJ4g.js → useSortable-Cq2Y1JLO.js} +0 -0
  183. /package/dist/{useSpringMount-BTRBNzXP.js → useSpringMount-Cfk1XK1R.js} +0 -0
  184. /package/dist/{useTimer-lp5NlH4w.js → useTimer-NAaj9zNq.js} +0 -0
@@ -0,0 +1,29 @@
1
+ //#region src/composables/dom/useUserInvalidAria.ts
2
+ function e() {
3
+ return typeof CSS < "u" && typeof CSS.supports == "function" && CSS.supports("selector(:user-invalid)");
4
+ }
5
+ function t(t = {}) {
6
+ let n = t.fallbackClasses ?? !e();
7
+ return { bind: (e) => {
8
+ let t = /* @__PURE__ */ new WeakSet(), r = (e) => {
9
+ let t = e;
10
+ if (!t?.checkValidity) return;
11
+ let r = t.checkValidity();
12
+ t.setAttribute("aria-invalid", r ? "false" : "true"), n && (t.classList.toggle("user-invalid-fallback", !r), t.classList.toggle("user-valid-fallback", r));
13
+ }, i = (e) => {
14
+ let n = e.target;
15
+ n?.checkValidity && (t.add(n), r(n));
16
+ }, a = (e) => {
17
+ let n = e.target;
18
+ !n?.checkValidity || !t.has(n) || n.checkValidity() && r(n);
19
+ }, o = () => {
20
+ let n = e.querySelectorAll("input, textarea, select");
21
+ for (let e of n) e.checkValidity && (t.add(e), r(e));
22
+ };
23
+ return e.addEventListener("blur", i, !0), e.addEventListener("input", a, !0), e.addEventListener("submit", o, !0), () => {
24
+ e.removeEventListener("blur", i, !0), e.removeEventListener("input", a, !0), e.removeEventListener("submit", o, !0);
25
+ };
26
+ } };
27
+ }
28
+ //#endregion
29
+ export { t };
@@ -0,0 +1,16 @@
1
+ //#region src/composables/motion/useViewTransition.ts
2
+ function e() {
3
+ return typeof document < "u" && typeof document.startViewTransition == "function";
4
+ }
5
+ function t(e) {
6
+ let t = typeof document > "u" ? void 0 : document;
7
+ return !t || typeof t.startViewTransition != "function" ? (e(), {
8
+ finished: Promise.resolve(),
9
+ transitioned: !1
10
+ }) : {
11
+ finished: t.startViewTransition(() => e()).finished.then(() => void 0, () => void 0),
12
+ transitioned: !0
13
+ };
14
+ }
15
+ //#endregion
16
+ export { e as n, t };
@@ -1 +1,2 @@
1
1
  export { cn } from "./cn";
2
+ export { moveBeforeSafe, supportsMoveBefore } from "./moveBefore";
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Move `node` into `parent` immediately before `ref` (or to the end when `ref`
3
+ * is `null`), preserving the node's live state when `Element.moveBefore` is
4
+ * supported, and falling back to `insertBefore` otherwise.
5
+ *
6
+ * @param parent the destination parent element.
7
+ * @param node the node to move (may currently live elsewhere in the tree).
8
+ * @param ref the child to insert before, or `null` to append.
9
+ */
10
+ export declare function moveBeforeSafe(parent: Element, node: Node, ref: Node | null): void;
11
+ /**
12
+ * True when `Element.moveBefore` is available — the feature-detected predicate
13
+ * for consumers that want to branch their own re-parent logic.
14
+ */
15
+ export declare function supportsMoveBefore(): boolean;
@@ -1,4 +1,4 @@
1
- import { t as e } from "./createLucideIcon-rHu18UQW.js";
1
+ import { t as e } from "./createLucideIcon-Bn9a1b70.js";
2
2
  var t = e("x", [["path", {
3
3
  d: "M18 6 6 18",
4
4
  key: "1bl5f8"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mkbabb/glass-ui",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "Glassmorphic design system — Vue 3.5 components, reka-ui primitives, Tailwind CSS v4 tokens",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -201,6 +201,12 @@
201
201
  ],
202
202
  "command": [
203
203
  "dist/command.d.ts"
204
+ ],
205
+ "number-field": [
206
+ "dist/number-field.d.ts"
207
+ ],
208
+ "switch": [
209
+ "dist/switch.d.ts"
204
210
  ]
205
211
  }
206
212
  },
@@ -476,6 +482,14 @@
476
482
  "./command": {
477
483
  "types": "./dist/command.d.ts",
478
484
  "import": "./dist/command.js"
485
+ },
486
+ "./number-field": {
487
+ "types": "./dist/number-field.d.ts",
488
+ "import": "./dist/number-field.js"
489
+ },
490
+ "./switch": {
491
+ "types": "./dist/switch.d.ts",
492
+ "import": "./dist/switch.js"
479
493
  }
480
494
  },
481
495
  "files": [
@@ -518,7 +532,7 @@
518
532
  },
519
533
  "peerDependencies": {
520
534
  "@lucide/vue": "^1.16.0",
521
- "@mkbabb/keyframes.js": "^2.1.1",
535
+ "@mkbabb/keyframes.js": "^2.2.0",
522
536
  "@vueuse/core": "^14.0",
523
537
  "class-variance-authority": "^0.7",
524
538
  "clsx": "^2.0",
@@ -534,7 +548,7 @@
534
548
  },
535
549
  "devDependencies": {
536
550
  "@lucide/vue": "^1.16.0",
537
- "@mkbabb/keyframes.js": "^2.1.1",
551
+ "@mkbabb/keyframes.js": "^2.2.0",
538
552
  "@mkbabb/value.js": "^0.10.0",
539
553
  "@tailwindcss/postcss": "^4.3.0",
540
554
  "@tailwindcss/vite": "^4.3.0",
@@ -296,3 +296,80 @@
296
296
  animation: none;
297
297
  }
298
298
  }
299
+
300
+ /* ── §TOP-LAYER ENTRY/EXIT (AQ.W5 §Design 2) ───────────────────────────────
301
+ *
302
+ * A pure-CSS entry/exit grammar for NATIVE top-layer surfaces — `[popover]`
303
+ * elements and native `<dialog>` — keyed off `:popover-open` / `dialog[open]`,
304
+ * plus a glass `::backdrop`. This replaces the per-frame inline-style entry the
305
+ * platform now owns: `@starting-style` + `transition` + `transition-behavior:
306
+ * allow-discrete` + `overlay`.
307
+ *
308
+ * SCOPE: this applies ONLY to elements tagged `.glass-top-layer`. It does NOT
309
+ * touch reka-ui's portaled overlays — those keep their `tw-animate-css`
310
+ * data-state fade (distinct selectors, no cascade fight, mirroring the
311
+ * `[data-scrim-animation]` discipline above). `DialogContent`'s `spring` opt-in
312
+ * (`useSpringMount`) is also untouched — a legitimate iOS-physics variant, not a
313
+ * duplicate of this fade.
314
+ *
315
+ * Baseline: `@starting-style` = Widely Available; `transition-behavior:
316
+ * allow-discrete` + `overlay` = Newly Available → adopt natively, gate the
317
+ * whole grammar on `@supports (overlay: auto)`. A non-supporting engine shows
318
+ * the element instantly (acceptable for a native top-layer surface; glass-ui
319
+ * ships no JS top-layer coordinator — that is reka-ui Dialog's remit).
320
+ *
321
+ * Uses `scale:` (the longhand) not `transform: scale()` so it composes with the
322
+ * identity-base transform work and does not mint a stacking context. Every
323
+ * visual axis reads a `--top-layer-*` token (tokens.css §20).
324
+ */
325
+ @supports (overlay: auto) {
326
+ /* Base (closed / exit) + the transition list. `allow-discrete` MUST come
327
+ * after the property longhands, never negated. */
328
+ .glass-top-layer[popover],
329
+ dialog.glass-top-layer {
330
+ opacity: 0;
331
+ scale: var(--top-layer-enter-scale, 0.96);
332
+ transition-property: opacity, scale, display, overlay;
333
+ transition-duration: var(--duration-normal);
334
+ transition-timing-function: var(--ease-apple-spring);
335
+ transition-behavior: allow-discrete;
336
+ }
337
+
338
+ /* Open state + `@starting-style` entry (starting-style AFTER the open
339
+ * selector so the entry interpolates from the closed values). */
340
+ .glass-top-layer[popover]:popover-open,
341
+ dialog.glass-top-layer[open] {
342
+ opacity: 1;
343
+ scale: 1;
344
+ @starting-style {
345
+ opacity: 0;
346
+ scale: var(--top-layer-enter-scale, 0.96);
347
+ }
348
+ }
349
+
350
+ /* Glass backdrop — blur + dim, animated discretely. `::backdrop` only
351
+ * exists for `showModal()` dialogs, NOT `[popover]` (popovers are
352
+ * non-modal — correct, no scrim). */
353
+ dialog.glass-top-layer::backdrop {
354
+ background-color: hsl(var(--background) / 0);
355
+ backdrop-filter: blur(var(--top-layer-backdrop-blur, 8px));
356
+ transition: display var(--duration-normal) allow-discrete,
357
+ overlay var(--duration-normal) allow-discrete,
358
+ background-color var(--duration-normal) var(--ease-standard);
359
+ }
360
+ dialog.glass-top-layer[open]::backdrop {
361
+ background-color: hsl(var(--background) / var(--top-layer-backdrop-dim, 0.5));
362
+ @starting-style {
363
+ background-color: hsl(var(--background) / 0);
364
+ }
365
+ }
366
+ }
367
+
368
+ /* Reduced-motion carve — flatten the scale to a fade, shorten the duration. */
369
+ @media (prefers-reduced-motion: reduce) {
370
+ .glass-top-layer[popover],
371
+ dialog.glass-top-layer {
372
+ scale: none;
373
+ transition-duration: var(--duration-fast);
374
+ }
375
+ }
@@ -33,12 +33,16 @@
33
33
  @utility cartoon-surface {
34
34
  border-width: 2px;
35
35
  box-shadow: var(--shadow-cartoon-md);
36
+ /* Individual-transform identity base (AQ.W3 §W3.2) — state-driven 2D lift on
37
+ a card surface that often hosts portaled content; `translate: 0` at rest
38
+ mints the stacking context once. Longhand maps `translate(x, y)` 1:1. */
39
+ translate: 0;
36
40
  transition:
37
- transform var(--duration-normal) var(--ease-apple-spring),
41
+ translate var(--duration-normal) var(--ease-apple-spring),
38
42
  box-shadow var(--duration-normal) var(--ease-apple);
39
43
 
40
44
  &:hover:not(:disabled) {
41
- transform: translate(var(--lift-sm), var(--lift-sm));
45
+ translate: var(--lift-sm) var(--lift-sm);
42
46
  box-shadow: var(--shadow-cartoon-lg);
43
47
  }
44
48
  }
@@ -56,6 +56,11 @@
56
56
  --dock-wrap-max-width: calc(100vw - var(--dock-viewport-inline-gutter, 1rem));
57
57
  --dock-separator-height: calc(var(--dock-h, var(--size-icon-btn)) * 0.5);
58
58
  --dock-collapsed-hover-scale: var(--scale-hover-dock);
59
+ /* Individual-transform identity base (AQ.W3 §W3.2). `scale: 1` at rest
60
+ mints the stacking context once so a portaled popover over `--z-dock`
61
+ stays above the dock during a hover scale (the load-bearing
62
+ stacking-context guarantee). Covers every dock scale site. */
63
+ scale: 1;
59
64
  position: relative;
60
65
  display: inline-flex;
61
66
  align-items: center;
@@ -308,7 +313,7 @@
308
313
  background: var(--glass-bg-wash);
309
314
  border-color: var(--glass-border-floating);
310
315
  box-shadow: var(--shadow-dock-override, var(--shadow-dock));
311
- transform: scale(var(--dock-collapsed-hover-scale));
316
+ scale: var(--dock-collapsed-hover-scale);
312
317
  }
313
318
 
314
319
  /* J.W5.C — held-state substrate response.
@@ -331,6 +336,15 @@
331
336
  box-shadow var(--duration-fast) var(--ease-standard);
332
337
  }
333
338
 
339
+ /* AQ.W3 §W3.1b — open-descendant surface response. When a dock-hosted
340
+ trigger (`[data-state="open"]`) is open, the dock root holds its elevated
341
+ read natively (no consumer JS). Idempotent with `[data-held]` above (same
342
+ floating paint, different trigger). Decorative; `:has()` Baseline Widely. */
343
+ .glass-dock:has([data-state="open"]) {
344
+ background: var(--glass-bg-floating, var(--glass-bg-resting));
345
+ border-color: var(--glass-border-floating, var(--glass-border-resting));
346
+ }
347
+
334
348
  .glass-dock:where(.fixed) {
335
349
  z-index: var(--z-dock);
336
350
  }
@@ -647,6 +661,8 @@
647
661
  align-items: center;
648
662
  justify-content: center;
649
663
  flex-shrink: 0;
664
+ /* AQ.W3 §W3.2 identity base */
665
+ scale: 1;
650
666
  width: var(--dock-control-size, var(--size-icon-btn));
651
667
  height: var(--dock-control-size, var(--size-icon-btn));
652
668
  padding: var(--dock-icon-padding, 0);
@@ -659,7 +675,7 @@
659
675
  transition:
660
676
  background-color var(--dock-motion-fast),
661
677
  color var(--dock-motion-fast),
662
- transform var(--dock-motion-fast),
678
+ scale var(--dock-motion-fast),
663
679
  opacity var(--dock-motion-fast);
664
680
  }
665
681
 
@@ -673,11 +689,11 @@
673
689
  .dock-icon-button:hover:not(:disabled) {
674
690
  background: var(--muted);
675
691
  color: var(--btn-hover-color, var(--foreground));
676
- transform: scale(var(--scale-hover-dock));
692
+ scale: var(--scale-hover-dock);
677
693
  }
678
694
 
679
695
  .dock-icon-button:active:not(:disabled) {
680
- transform: scale(var(--scale-press-dock));
696
+ scale: var(--scale-press-dock);
681
697
  }
682
698
 
683
699
  .dock-icon-button:focus:not(:focus-visible) {
@@ -691,7 +707,7 @@
691
707
  transition:
692
708
  background-color var(--dock-motion-fast),
693
709
  color var(--dock-motion-fast),
694
- transform var(--dock-motion-fast),
710
+ scale var(--dock-motion-fast),
695
711
  opacity var(--dock-motion-fast),
696
712
  box-shadow var(--dock-motion-fast);
697
713
  }
@@ -705,7 +721,7 @@
705
721
  .dock-icon-button:is(.is-active, .active, [aria-expanded="true"], [aria-pressed="true"]) {
706
722
  background: var(--dock-active-bg);
707
723
  color: var(--dock-active-color);
708
- transform: scale(var(--dock-active-scale));
724
+ scale: var(--dock-active-scale);
709
725
  border: var(--dock-active-border);
710
726
  box-shadow: var(--dock-active-shadow);
711
727
  }
@@ -809,10 +825,12 @@
809
825
  cursor: pointer;
810
826
  white-space: nowrap;
811
827
  outline: none;
828
+ /* AQ.W3 §W3.2 identity base */
829
+ scale: 1;
812
830
  transition:
813
831
  background-color var(--dock-motion-fast),
814
832
  color var(--dock-motion-fast),
815
- transform var(--dock-motion-fast);
833
+ scale var(--dock-motion-fast);
816
834
  }
817
835
 
818
836
  .dock-tab-button:hover:not(:disabled) {
@@ -821,7 +839,7 @@
821
839
  }
822
840
 
823
841
  .dock-tab-button:active:not(:disabled) {
824
- transform: scale(var(--scale-press-dock));
842
+ scale: var(--scale-press-dock);
825
843
  }
826
844
 
827
845
  .dock-tab-button:focus:not(:focus-visible) {
@@ -938,6 +956,8 @@
938
956
  align-items: center;
939
957
  justify-content: center;
940
958
  flex-shrink: 0;
959
+ /* AQ.W3 §W3.2 identity base */
960
+ scale: 1;
941
961
  gap: var(--dock-trigger-gap, 0.25rem);
942
962
  min-height: var(--dock-trigger-min-height, auto);
943
963
  padding:
@@ -953,7 +973,7 @@
953
973
  transition:
954
974
  background-color var(--dock-motion-fast),
955
975
  color var(--dock-motion-fast),
956
- transform var(--dock-motion-fast),
976
+ scale var(--dock-motion-fast),
957
977
  opacity var(--dock-motion-fast);
958
978
  }
959
979
 
@@ -964,12 +984,12 @@
964
984
  }
965
985
 
966
986
  .dock-dropdown-trigger:hover:not(:disabled) {
967
- transform: scale(var(--scale-hover-dock));
987
+ scale: var(--scale-hover-dock);
968
988
  }
969
989
 
970
990
  .dock-select-trigger:active:not(:disabled),
971
991
  .dock-dropdown-trigger:active:not(:disabled) {
972
- transform: scale(var(--scale-press-dock));
992
+ scale: var(--scale-press-dock);
973
993
  }
974
994
 
975
995
  .dock-select-trigger:focus:not(:focus-visible),
@@ -985,7 +1005,7 @@
985
1005
  transition:
986
1006
  background-color var(--dock-motion-fast),
987
1007
  color var(--dock-motion-fast),
988
- transform var(--dock-motion-fast),
1008
+ scale var(--dock-motion-fast),
989
1009
  opacity var(--dock-motion-fast),
990
1010
  box-shadow var(--dock-motion-fast);
991
1011
  }
@@ -1001,11 +1021,14 @@
1001
1021
  height: var(--dock-trigger-icon-size, 0.75rem);
1002
1022
  flex-shrink: 0;
1003
1023
  opacity: 0.5;
1004
- transition: transform var(--dock-motion-fast);
1024
+ /* Individual-transform identity base (AQ.W3 §W3.2) — `rotate:` longhand
1025
+ for family consistency (not a stacking-context hazard: child glyph). */
1026
+ rotate: 0deg;
1027
+ transition: rotate var(--dock-motion-fast);
1005
1028
  }
1006
1029
 
1007
1030
  .dock-select-trigger[data-state="open"] .dock-select-trigger__chevron {
1008
- transform: rotate(180deg);
1031
+ rotate: 180deg;
1009
1032
  }
1010
1033
 
1011
1034
  @media (min-width: 640px) {
@@ -101,13 +101,30 @@
101
101
  Interactive cards live in <Card> (which owns its own hover composition)
102
102
  or in components that explicitly opt into a hover variant. */
103
103
  .glass-card {
104
+ /* Focus-elevation rungs (AQ.W3 §W3.1a) — identity = at-rest paint; the
105
+ `:has(:focus-visible)` rule redefines them (one source, shared with
106
+ the fallback). */
107
+ --card-focus-shadow: var(--shadow-card);
108
+ --card-focus-border: var(--glass-border-quiet);
104
109
  position: relative;
105
110
  background: var(--glass-bg-quiet);
106
111
  backdrop-filter: var(--glass-blur-quiet);
107
- border: 1px solid var(--glass-border-quiet);
112
+ border: 1px solid var(--card-focus-border);
108
113
  border-radius: var(--radius-card);
109
- box-shadow: var(--shadow-card);
114
+ box-shadow: var(--card-focus-shadow);
110
115
  contain: layout style;
116
+ transition:
117
+ box-shadow var(--duration-fast) var(--ease-standard),
118
+ border-color var(--duration-fast) var(--ease-standard);
119
+ }
120
+
121
+ /* Elevate one rung on descendant KEYBOARD focus (`:focus-visible`, not
122
+ pointer). Scoped to `.glass-card`. `:has()` Baseline Widely; the
123
+ `@supports not selector(:has(*))` class fallback (below) ships anyway
124
+ (focus-elevation is UX-critical — child-state-styling MANDATORY clause). */
125
+ .glass-card:has(:focus-visible) {
126
+ --card-focus-shadow: var(--shadow-md);
127
+ --card-focus-border: var(--glass-border-floating);
111
128
  }
112
129
 
113
130
  /* ── Glassmorphic circular icon button ── */
@@ -115,6 +132,10 @@
115
132
  .glass-btn {
116
133
  @apply flex items-center justify-center cursor-pointer;
117
134
 
135
+ /* Individual-transform identity base (AQ.W3 §W3.2) — `scale: 1` at rest
136
+ mints the stacking context once so the hover/press scale below does
137
+ not flicker it into being (the z-index/anchor hazard). */
138
+ scale: 1;
118
139
  width: var(--size-icon-btn);
119
140
  height: var(--size-icon-btn);
120
141
  border-radius: var(--radius-pill);
@@ -126,18 +147,18 @@
126
147
  background var(--duration-fast) var(--ease-standard),
127
148
  border-color var(--duration-fast) var(--ease-standard),
128
149
  color var(--duration-fast) var(--ease-standard),
129
- transform var(--duration-normal) var(--spring-snappy);
150
+ scale var(--duration-normal) var(--spring-snappy);
130
151
  }
131
152
 
132
153
  .glass-btn:hover:not(:disabled) {
133
154
  background: color-mix(in srgb, var(--background) 85%, transparent);
134
155
  border-color: var(--surface-tint-22);
135
156
  color: var(--foreground);
136
- transform: scale(var(--scale-hover));
157
+ scale: var(--scale-hover);
137
158
  }
138
159
 
139
160
  .glass-btn:active:not(:disabled) {
140
- transform: scale(var(--scale-press));
161
+ scale: var(--scale-press);
141
162
  }
142
163
 
143
164
  .glass-btn:focus-visible {
@@ -210,12 +231,60 @@
210
231
  color: var(--surface-tint-35);
211
232
  }
212
233
 
213
- .input-pill:focus {
234
+ /* AQ.W4 §W4.6 — keyboard-focus ring only. Was bare `:focus` (showed the
235
+ accent ring on a mouse click); `:focus-visible` matches the
236
+ `.focus-ring` / `.glass-btn` / dock-control discipline. Ordered BEFORE
237
+ the `:user-invalid` rungs so an invalid focused field shows destructive,
238
+ not accent (the `:user-invalid:focus-visible` rule below wins by order +
239
+ specificity). `:focus-visible` Baseline Widely — no fallback. */
240
+ .input-pill:focus-visible {
214
241
  outline: none;
215
242
  border-color: var(--color-accent-opaque, var(--ring));
216
243
  box-shadow: 0 0 0 2px var(--color-accent, color-mix(in srgb, var(--ring) 30%, transparent));
217
244
  }
218
245
 
246
+ /* AQ.W4 §W4.1 — validity vocabulary on `.input-pill` (Input + Textarea).
247
+ The forgiving `:where(:user-invalid, .user-invalid-fallback)` group is
248
+ ONE rule covering native + the `useUserInvalidAria` fallback class (css
249
+ §3.2; NOT a live+dead alias — the class is the SOLE fallback for engines
250
+ without `:user-invalid`). The destructive/success tints reuse the
251
+ semantic tokens via `color-mix` per the house pattern (W2). Non-color
252
+ reinforcement is the error TEXT (W4.5 `error` slot) + the `required`
253
+ asterisk — the border/bg shift is the supplementary cue, never the sole
254
+ one. `:user-valid` is intentionally subtle (border only, no bg fill) so
255
+ a long valid form is not a wall of green. */
256
+ .input-pill:where(:user-invalid, .user-invalid-fallback) {
257
+ border-color: var(--destructive);
258
+ background: color-mix(in srgb, var(--destructive) 8%, var(--glass-bg-quiet));
259
+ }
260
+ .input-pill:where(:user-valid, .user-valid-fallback) {
261
+ border-color: var(--success);
262
+ }
263
+ /* Invalid + keyboard-focused → destructive ring (overrides the accent
264
+ `:focus-visible` ring above by order + the extra `:user-invalid`
265
+ compound). */
266
+ .input-pill:where(:user-invalid, .user-invalid-fallback):focus-visible {
267
+ border-color: var(--destructive);
268
+ box-shadow: 0 0 0 var(--focus-ring-width)
269
+ color-mix(in srgb, var(--destructive) 35%, transparent);
270
+ }
271
+
272
+ /* AQ.W4 §W4.3 — `field-sizing: content` autosize (opt-in via the Textarea
273
+ `autosize` prop → `[data-autosize]`). The textarea grows vertically with
274
+ content between a 3-line floor and a token-driven ceiling, then scrolls.
275
+ `field-sizing` Baseline Newly (Chromium 2024) — on a non-supporting
276
+ engine the declaration is ignored and the element keeps the
277
+ `min/max-block-size` bounds + standard scroll (no JS, no `@supports`,
278
+ no grid-mirror polyfill). The fixed-size path (default, `min-h-20`) is
279
+ the unchanged no-op fallback. */
280
+ .input-pill[data-autosize] {
281
+ field-sizing: content;
282
+ width: 100%;
283
+ min-block-size: 3lh;
284
+ max-block-size: var(--textarea-autosize-max, 12lh);
285
+ resize: vertical;
286
+ }
287
+
219
288
  .input-pill:disabled {
220
289
  opacity: 0.5;
221
290
  cursor: not-allowed;
@@ -275,3 +344,17 @@
275
344
  }
276
345
  }
277
346
  }
347
+
348
+ /* ── :has() focus-elevation fallback (AQ.W3 §W3.1a) ──────────────────────
349
+ SOLE fallback for engines without `:has()`. `.is-focus-within` is toggled
350
+ by a ≤6-LOC focusin/focusout guard in `Card.vue` that runs ONLY when
351
+ `!CSS.supports('selector(:has(*))')` (modern engines run zero JS). The
352
+ class redefines the SAME `--card-focus-*` rungs as the `:has()` rule, so the
353
+ override values have one source. NOT a live+dead alias — `:has()` is the
354
+ enhancement, this the documented degradation path. */
355
+ @supports not selector(:has(*)) {
356
+ .glass-card.is-focus-within {
357
+ --card-focus-shadow: var(--shadow-md);
358
+ --card-focus-border: var(--glass-border-floating);
359
+ }
360
+ }
@@ -63,7 +63,14 @@
63
63
  * pop, dialog-scale, dropdown, tab-fade).
64
64
  * 10. animations.css — @keyframes for components consumed via
65
65
  * `animation:` properties in (9) and component
66
- * <style> blocks.
66
+ * <style> blocks. Includes the §TOP-LAYER
67
+ * `@starting-style` entry/exit grammar (AQ.W5).
68
+ * 10a. scroll-driven.css — native scroll-driven recipes (.scroll-progress
69
+ * + [data-scroll-reveal]); the `@supports`-gated
70
+ * primary over the JS-composable fallback (AQ.W5).
71
+ * 10b. view-transition.css — the `.gl-list-item` View-Transitions group
72
+ * recipe + `--vt-*` axes (AQ.W5; the
73
+ * `useViewTransition` substrate's CSS half).
67
74
  * 11. utilities.css — focus-ring, btn-audacious + btn-interactive
68
75
  * (press cluster; K W6 + Q.W3 Lane E),
69
76
  * rainbow-vivid + rainbow-pastel. Consumes earlier
@@ -95,6 +102,8 @@
95
102
  @import "./floating-panel.css";
96
103
  @import "./transitions.css";
97
104
  @import "./animations.css";
105
+ @import "./scroll-driven.css";
106
+ @import "./view-transition.css";
98
107
  @import "./utilities.css";
99
108
  @import "./instrument-chassis.css";
100
109
  @import "./instrument-rail.css";
@@ -0,0 +1,72 @@
1
+ /*
2
+ * scroll-driven.css — native scroll-driven CSS animations (AQ.W5 §Design 1).
3
+ *
4
+ * Cascade rung after animations.css. Two token-first recipe families that move
5
+ * the scroll-progress + stagger-reveal work OFF the main thread (the consumer
6
+ * INP lever): the browser runs them on the compositor instead of a rAF/IO
7
+ * listener loop in JS.
8
+ *
9
+ * .scroll-progress — a 0..1 scaleX bar driven by a `scroll()` timeline.
10
+ * The `useScrollProgress` composable replacement.
11
+ * [data-scroll-reveal] — fade + lift on entry, one `view()` timeline PER
12
+ * child (the stagger is implicit — no setTimeout
13
+ * cascade). The `useStaggerReveal` composable
14
+ * replacement.
15
+ *
16
+ * DUAL-PATH with a SINGLE writer (no double-run): when the engine supports
17
+ * `animation-timeline`, the CSS here owns the visual axis and the composables
18
+ * gate their listener/observer machinery OFF (so CSS and JS never both write
19
+ * the same target). When the engine does NOT support it, this block is skipped
20
+ * by `@supports` and the composable is the sole writer. See the gate at the top
21
+ * of `useScrollProgress.ts` / `useStaggerReveal.ts`.
22
+ *
23
+ * Baseline: `scroll-driven-animations` = Newly Available → adopt CSS as
24
+ * primary, keep the composable as the ≤ 20-LOC feature-detected fallback.
25
+ *
26
+ * Reduced-motion is the OUTER gate: the whole `@supports` block sits under
27
+ * `prefers-reduced-motion: no-preference`, so under PRM no scroll animation
28
+ * attaches at all and content renders in its terminal state (the `from`
29
+ * keyframe is never applied because the animation never binds).
30
+ */
31
+
32
+ /* ── Scroll progress (scroll() timeline) — drives a 0..1 scaleX bar.
33
+ * Consumers add `.scroll-progress` on a position:fixed/absolute element; the
34
+ * scroller is the nearest ancestor block scroller (`root` by default; override
35
+ * via `--scroll-progress-scroller`). */
36
+ @media (prefers-reduced-motion: no-preference) {
37
+ @supports (animation-timeline: scroll()) {
38
+ @keyframes gl-scroll-grow {
39
+ from { transform: scaleX(0); }
40
+ to { transform: scaleX(1); }
41
+ }
42
+ .scroll-progress {
43
+ transform-origin: 0 50%;
44
+ animation: gl-scroll-grow auto linear;
45
+ animation-timeline: scroll(var(--scroll-progress-scroller, root) block);
46
+ }
47
+ }
48
+ }
49
+
50
+ /* ── View-driven entry (view() timeline) — the useStaggerReveal replacement.
51
+ * Each child of a `[data-scroll-reveal]` scroller fades + lifts on entry. The
52
+ * stagger is implicit in the per-element view-timeline (each child owns its
53
+ * own timeline), so NO setTimeout cascade is needed. Horizontal scrollers add
54
+ * `.scroll-reveal--inline` for a `view(inline)` axis. */
55
+ @media (prefers-reduced-motion: no-preference) {
56
+ @supports ((animation-timeline: view()) and (animation-range: entry)) {
57
+ @keyframes gl-reveal-in {
58
+ from {
59
+ opacity: 0;
60
+ transform: translateY(var(--scroll-reveal-rise, 6px));
61
+ }
62
+ }
63
+ [data-scroll-reveal] > * {
64
+ animation: gl-reveal-in auto linear both;
65
+ animation-timeline: view(block);
66
+ animation-range: entry 0% entry var(--scroll-reveal-range-end, 60%);
67
+ }
68
+ [data-scroll-reveal].scroll-reveal--inline > * {
69
+ animation-timeline: view(inline);
70
+ }
71
+ }
72
+ }
@@ -55,6 +55,9 @@
55
55
  Colors — core
56
56
  ═══════════════════════════════════════════════ */
57
57
  --color-border: var(--border);
58
+ /* AQ.W2 §3d — the whisper-alpha border hairline rung (border-border-soft /
59
+ bg-border-soft utilities). Auto-dark via --border. */
60
+ --color-border-soft: var(--border-soft);
58
61
  --color-input: var(--input);
59
62
  --color-ring: var(--ring);
60
63
  --color-background: var(--background);