@vamos-ui/react 0.2.0 → 0.4.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 (166) hide show
  1. package/dist/blocks/block-image.js +1 -1
  2. package/dist/blocks/block-link.js +1 -1
  3. package/dist/blocks/index.d.ts +2 -2
  4. package/dist/blocks/index.js +2 -2
  5. package/dist/blocks/site/_kit-1.d.ts +1 -0
  6. package/dist/blocks/site/_kit-1.js +3 -1
  7. package/dist/blocks/site/announcement-bar.js +1 -1
  8. package/dist/blocks/site/article-layout.d.ts +1 -0
  9. package/dist/blocks/site/article-layout.js +2 -1
  10. package/dist/blocks/site/blog-list.d.ts +1 -0
  11. package/dist/blocks/site/blog-list.js +3 -2
  12. package/dist/blocks/site/contact-dock.js +3 -1
  13. package/dist/blocks/site/contact-form.js +1 -1
  14. package/dist/blocks/site/cta.d.ts +2 -2
  15. package/dist/blocks/site/cta.js +8 -5
  16. package/dist/blocks/site/faq.js +2 -1
  17. package/dist/blocks/site/feature-grid.d.ts +1 -0
  18. package/dist/blocks/site/feature-grid.js +7 -5
  19. package/dist/blocks/site/footer.d.ts +1 -1
  20. package/dist/blocks/site/footer.js +8 -7
  21. package/dist/blocks/site/gallery-section.d.ts +6 -1
  22. package/dist/blocks/site/gallery-section.js +6 -4
  23. package/dist/blocks/site/hero.d.ts +3 -2
  24. package/dist/blocks/site/hero.js +14 -6
  25. package/dist/blocks/site/logo-strip.d.ts +6 -3
  26. package/dist/blocks/site/logo-strip.js +8 -7
  27. package/dist/blocks/site/map-hours.d.ts +2 -2
  28. package/dist/blocks/site/map-hours.js +4 -2
  29. package/dist/blocks/site/media-text.d.ts +4 -1
  30. package/dist/blocks/site/media-text.js +6 -4
  31. package/dist/blocks/site/menu-list.d.ts +7 -3
  32. package/dist/blocks/site/menu-list.js +34 -7
  33. package/dist/blocks/site/navbar.d.ts +1 -0
  34. package/dist/blocks/site/navbar.js +6 -5
  35. package/dist/blocks/site/not-found.js +2 -1
  36. package/dist/blocks/site/page-intro.d.ts +1 -0
  37. package/dist/blocks/site/page-intro.js +2 -1
  38. package/dist/blocks/site/pricing-table.js +2 -1
  39. package/dist/blocks/site/process.d.ts +1 -0
  40. package/dist/blocks/site/process.js +4 -2
  41. package/dist/blocks/site/registry-1.d.ts +1 -1
  42. package/dist/blocks/site/registry-2.d.ts +5 -0
  43. package/dist/blocks/site/registry-2.js +2 -2
  44. package/dist/blocks/site/rich-text-section.js +2 -1
  45. package/dist/blocks/site/shared/heading-lines.d.ts +9 -0
  46. package/dist/blocks/site/shared/heading-lines.js +32 -0
  47. package/dist/blocks/site/shared/image-sizes.d.ts +25 -0
  48. package/dist/blocks/site/shared/image-sizes.js +37 -0
  49. package/dist/blocks/site/shared/island-keys.d.ts +1 -1
  50. package/dist/blocks/site/shared/island-keys.js +1 -1
  51. package/dist/blocks/site/shared/labels-2.d.ts +9 -1
  52. package/dist/blocks/site/shared/labels-2.js +24 -6
  53. package/dist/blocks/site/shared/open-now.d.ts +1 -1
  54. package/dist/blocks/site/shared/open-now.js +1 -1
  55. package/dist/blocks/site/shared/opening-hours.d.ts +18 -2
  56. package/dist/blocks/site/shared/opening-hours.js +36 -7
  57. package/dist/blocks/site/shared/tap.d.ts +9 -0
  58. package/dist/blocks/site/shared/tap.js +16 -0
  59. package/dist/blocks/site/site-shell.d.ts +11 -0
  60. package/dist/blocks/site/site-shell.js +43 -16
  61. package/dist/blocks/site/stats-band.d.ts +3 -1
  62. package/dist/blocks/site/stats-band.js +11 -7
  63. package/dist/blocks/site/team.d.ts +1 -1
  64. package/dist/blocks/site/team.js +4 -3
  65. package/dist/blocks/site/testimonials.d.ts +2 -2
  66. package/dist/blocks/site/testimonials.js +5 -4
  67. package/dist/business/index.d.ts +1 -0
  68. package/dist/business/index.js +2 -0
  69. package/dist/business/signer-status.d.ts +53 -0
  70. package/dist/business/signer-status.js +76 -0
  71. package/dist/charts/bar-chart.js +6 -2
  72. package/dist/charts/chart-shell.d.ts +9 -2
  73. package/dist/charts/chart-shell.js +10 -4
  74. package/dist/charts/chart.js +1 -1
  75. package/dist/charts/combo-chart.js +5 -7
  76. package/dist/charts/utils.d.ts +10 -0
  77. package/dist/charts/utils.js +46 -0
  78. package/dist/components/_qr-core.d.ts +26 -0
  79. package/dist/components/_qr-core.js +376 -0
  80. package/dist/components/activity-timeline.js +5 -5
  81. package/dist/components/aspect-ratio.d.ts +26 -0
  82. package/dist/components/aspect-ratio.js +42 -0
  83. package/dist/components/backup-codes.d.ts +42 -0
  84. package/dist/components/backup-codes.js +67 -0
  85. package/dist/components/breadcrumb.d.ts +2 -2
  86. package/dist/components/breadcrumb.js +1 -1
  87. package/dist/components/carousel.d.ts +12 -3
  88. package/dist/components/carousel.js +8 -5
  89. package/dist/components/collapsible.d.ts +36 -0
  90. package/dist/components/collapsible.js +37 -0
  91. package/dist/components/command.d.ts +8 -2
  92. package/dist/components/command.js +7 -2
  93. package/dist/components/image.d.ts +18 -2
  94. package/dist/components/image.js +12 -5
  95. package/dist/components/input.d.ts +1 -1
  96. package/dist/components/input.js +1 -1
  97. package/dist/components/menubar.d.ts +96 -0
  98. package/dist/components/menubar.js +123 -0
  99. package/dist/components/pin-map.d.ts +58 -0
  100. package/dist/components/pin-map.js +167 -0
  101. package/dist/components/placeholder-art.d.ts +51 -0
  102. package/dist/components/placeholder-art.js +82 -0
  103. package/dist/components/qr-code.d.ts +36 -0
  104. package/dist/components/qr-code.js +27 -0
  105. package/dist/components/record-detail-layout.d.ts +2 -2
  106. package/dist/components/record-detail-layout.js +1 -1
  107. package/dist/components/sidebar.js +8 -8
  108. package/dist/components/sla-timer.d.ts +75 -0
  109. package/dist/components/sla-timer.js +142 -0
  110. package/dist/components/slug-input.js +1 -1
  111. package/dist/components/tour.d.ts +60 -0
  112. package/dist/components/tour.js +139 -0
  113. package/dist/index.d.ts +9 -0
  114. package/dist/index.js +9 -0
  115. package/dist/lib/public.d.ts +1 -0
  116. package/dist/lib/public.js +1 -0
  117. package/dist/lib/value-format.js +1 -1
  118. package/dist/locale-data/core/holidays.d.ts +1 -1
  119. package/dist/locale-data/core/holidays.js +1 -1
  120. package/dist/locales/ar.d.ts +2087 -0
  121. package/dist/locales/ar.js +2088 -0
  122. package/dist/locales/de.d.ts +2087 -0
  123. package/dist/locales/de.js +2088 -0
  124. package/dist/locales/en.d.ts +1 -0
  125. package/dist/locales/en.js +1 -0
  126. package/dist/locales/es.d.ts +2087 -0
  127. package/dist/locales/es.js +2087 -0
  128. package/dist/locales/fr.d.ts +2087 -0
  129. package/dist/locales/fr.js +2088 -0
  130. package/dist/locales/tr.d.ts +1 -0
  131. package/dist/locales/tr.js +1 -0
  132. package/dist/media/gallery.js +3 -1
  133. package/dist/media/media-library.js +1 -1
  134. package/dist/motion/core.d.ts +32 -0
  135. package/dist/motion/core.js +77 -0
  136. package/dist/motion/count-up.d.ts +29 -0
  137. package/dist/motion/count-up.js +92 -0
  138. package/dist/motion/index.d.ts +11 -0
  139. package/dist/motion/index.js +13 -0
  140. package/dist/motion/motion-scope.d.ts +17 -0
  141. package/dist/motion/motion-scope.js +16 -0
  142. package/dist/motion/page-transition.d.ts +33 -0
  143. package/dist/motion/page-transition.js +61 -0
  144. package/dist/motion/parallax.d.ts +37 -0
  145. package/dist/motion/parallax.js +90 -0
  146. package/dist/motion/pointer.d.ts +38 -0
  147. package/dist/motion/pointer.js +117 -0
  148. package/dist/motion/reveal.d.ts +72 -0
  149. package/dist/motion/reveal.js +133 -0
  150. package/dist/motion/scroll-shrink.d.ts +18 -0
  151. package/dist/motion/scroll-shrink.js +47 -0
  152. package/dist/motion/skeleton-swap.d.ts +17 -0
  153. package/dist/motion/skeleton-swap.js +43 -0
  154. package/dist/motion/text-reveal.d.ts +32 -0
  155. package/dist/motion/text-reveal.js +31 -0
  156. package/dist/motion/ticker.d.ts +35 -0
  157. package/dist/motion/ticker.js +16 -0
  158. package/dist/render/block-renderer.js +1 -1
  159. package/dist/sortable/sortable-tree.d.ts +4 -0
  160. package/dist/sortable/sortable-tree.js +16 -4
  161. package/dist/styles/blocks.css +21 -12
  162. package/dist/styles/editors.css +2 -1
  163. package/dist/styles/media.css +31 -5
  164. package/dist/styles/motion.css +389 -0
  165. package/dist/styles.css +41 -10
  166. package/package.json +5 -1
@@ -110,7 +110,7 @@ const allParents = (nodes) => {
110
110
  * ```
111
111
  */
112
112
  export function SortableTree(props) {
113
- const { nodes: nodesProp, onMove, getLabel = defaultLabel, maxDepth, canDrop, disabled, expanded: expandedProp, defaultExpanded, onExpandedChange, indent = 24, guides = true, size = "md", renderItem, renderActions, moveMenu = true, empty, labels, className, ...aria } = props;
113
+ const { nodes: nodesProp, onMove, getLabel = defaultLabel, maxDepth, canDrop, disabled, expanded: expandedProp, defaultExpanded, onExpandedChange, indent = 24, guides = true, size = "md", renderItem, renderActions, moveMenu = true, empty, selected, onSelect, labels, className, ...aria } = props;
114
114
  const t = useLabels("sortableTree", sortableTreeLabels, labels);
115
115
  const { dir } = useLocale();
116
116
  const reducedMotion = useReducedMotion();
@@ -325,6 +325,11 @@ export function SortableTree(props) {
325
325
  announce(t("invalid", { item: labelOf(f.id), reason: op.ok }));
326
326
  return;
327
327
  }
328
+ if (e.key === "Enter" && onSelect && !e.altKey && !e.ctrlKey && !e.metaKey) {
329
+ e.preventDefault();
330
+ onSelect(f.node);
331
+ return;
332
+ }
328
333
  if ((e.shiftKey && e.key === "F10") || e.key === "ContextMenu") {
329
334
  e.preventDefault();
330
335
  setMenuId(f.id);
@@ -388,7 +393,7 @@ export function SortableTree(props) {
388
393
  const me = info.get(f.id);
389
394
  const isActive = f.id === activeId;
390
395
  const depth = isActive && projection ? projection.depth : f.depth;
391
- return (_jsx(TreeRow, { item: f, depth: depth, info: me, t: t, label: labelOf(f.id), expanded: openSet.has(f.id), onToggle: () => setOpen(f.id, !openSet.has(f.id)), tabbable: tabId === f.id, onFocus: () => setFocusedId(f.id), onKeyDown: onRowKeyDown, rowRef: (el) => {
396
+ return (_jsx(TreeRow, { item: f, depth: depth, info: me, t: t, label: labelOf(f.id), expanded: openSet.has(f.id), onToggle: () => setOpen(f.id, !openSet.has(f.id)), tabbable: tabId === f.id, onFocus: () => setFocusedId(f.id), onKeyDown: onRowKeyDown, selected: onSelect ? selected === f.id : undefined, onSelect: onSelect ? () => onSelect(f.node) : undefined, rowRef: (el) => {
392
397
  if (el)
393
398
  rowRefs.current.set(f.id, el);
394
399
  else
@@ -434,10 +439,17 @@ function TreeRow(p) {
434
439
  s.setNodeRef(el);
435
440
  s.setActivatorNodeRef(el);
436
441
  p.rowRef(el);
437
- }, role: "treeitem", "aria-level": item.depth + 1, "aria-setsize": info.siblings, "aria-posinset": info.index + 1, "aria-expanded": hasKids ? expanded : undefined, "aria-labelledby": labelId, "aria-disabled": disabled || undefined, tabIndex: tabbable ? 0 : -1, "aria-keyshortcuts": "Alt+ArrowUp Alt+ArrowDown Alt+ArrowLeft Alt+ArrowRight Shift+F10", ...attrs, ...(disabled ? {} : pointerListeners), onKeyDown: (e) => p.onKeyDown(e, item, disabled ? undefined : dndKeyDown), onFocus: (e) => {
442
+ }, role: "treeitem", "aria-level": item.depth + 1, "aria-setsize": info.siblings, "aria-posinset": info.index + 1, "aria-expanded": hasKids ? expanded : undefined, "aria-labelledby": labelId, "aria-disabled": disabled || undefined, "aria-selected": p.selected, tabIndex: tabbable ? 0 : -1, "aria-keyshortcuts": "Alt+ArrowUp Alt+ArrowDown Alt+ArrowLeft Alt+ArrowRight Shift+F10", ...attrs, ...(disabled ? {} : pointerListeners), onKeyDown: (e) => p.onKeyDown(e, item, disabled ? undefined : dndKeyDown), onFocus: (e) => {
438
443
  if (e.target === e.currentTarget)
439
444
  p.onFocus();
440
- }, "data-slot": "sortable-tree-item", "data-depth": depth, "data-dragging": s.isDragging ? "" : undefined, "data-invalid": invalid ? "" : undefined, "data-disabled": disabled ? "" : undefined, style: { transform: CSS.Translate.toString(s.transform), transition: reducedMotion ? undefined : s.transition, paddingInlineStart: depth * indent, "--depth-px": `${depth * indent}px` }, className: cn("vui-tree-row group/row relative flex min-w-0 items-center gap-1 rounded-ui-sm pe-1 text-fg outline-none", "transition-colors hover:bg-hover focus-visible:bg-hover focus-visible:shadow-[inset_0_0_0_2px_var(--ring)]", size === "sm" ? "h-8 text-[13px]" : "h-9 text-sm", !disabled && "cursor-grab active:cursor-grabbing"), children: [guides && !s.isDragging
445
+ }, onClick: p.onSelect
446
+ ? (e) => {
447
+ // The expand toggle, row actions and the Move menu keep their own click.
448
+ if (e.target.closest('button, a, input, [data-slot="sortable-tree-toggle"]'))
449
+ return;
450
+ p.onSelect?.();
451
+ }
452
+ : undefined, "data-slot": "sortable-tree-item", "data-selected": p.selected ? "" : undefined, "data-depth": depth, "data-dragging": s.isDragging ? "" : undefined, "data-invalid": invalid ? "" : undefined, "data-disabled": disabled ? "" : undefined, style: { transform: CSS.Translate.toString(s.transform), transition: reducedMotion ? undefined : s.transition, paddingInlineStart: depth * indent, "--depth-px": `${depth * indent}px` }, className: cn("vui-tree-row group/row relative flex min-w-0 items-center gap-1 rounded-ui-sm pe-1 text-fg outline-none", "transition-colors hover:bg-hover focus-visible:bg-hover focus-visible:shadow-[inset_0_0_0_2px_var(--ring)]", size === "sm" ? "h-8 text-[13px]" : "h-9 text-sm", !disabled && "cursor-grab active:cursor-grabbing"), children: [guides && !s.isDragging
441
453
  ? Array.from({ length: depth }, (_, l) => (_jsx("span", { "aria-hidden": true, "data-slot": "sortable-tree-guide", className: "pointer-events-none absolute inset-y-0 w-px bg-line", style: { insetInlineStart: l * indent + 12 } }, l)))
442
454
  : null, s.isDragging ? _jsx("span", { "aria-hidden": true, "data-slot": "sortable-tree-indicator", className: "vui-tree-indicator" }) : null, _jsx("span", { className: "inline-flex size-6 shrink-0 items-center justify-center", children: hasKids ? (_jsx("span", { "aria-hidden": true, "data-slot": "sortable-tree-toggle", onClick: onToggle, onMouseDown: (e) => e.stopPropagation(), title: expanded ? t("collapse") : t("expand"), className: "inline-flex size-6 cursor-pointer items-center justify-center rounded-ui-sm text-fg-subtle hover:bg-hover hover:text-fg", children: _jsx(ChevronRight, { className: cn("size-3.5 transition-transform motion-reduce:transition-none rtl:-scale-x-100", expanded && "rotate-90 rtl:rotate-90") }) })) : null }), disabled ? (_jsx("span", { "aria-hidden": true, className: "w-4 shrink-0" })) : (_jsx(GripVertical, { "aria-hidden": true, className: "size-4 shrink-0 text-fg-subtle opacity-50 transition-opacity group-hover/row:opacity-100 group-focus-visible/row:opacity-100" })), _jsx("span", { id: labelId, className: "flex min-w-0 flex-1 items-center gap-2 ps-1", children: p.renderItem ? p.renderItem(item.node, { depth, isDragging: s.isDragging, overlay: false, expanded, childCount }) : _jsx("span", { className: "truncate", children: label }) }), p.actions, p.menu] }));
443
455
  }
@@ -11,6 +11,11 @@
11
11
  --scrim: linear-gradient(to top, rgb(6 8 4 / 0.8), rgb(6 8 4 / 0.58) 55%, rgb(6 8 4 / 0.5));
12
12
  --vb-rhythm: 1;
13
13
  }
14
+ /* Rhythm overrides sit next to the default, outside @layer: an unlayered declaration always beats a
15
+ layered one, so inside `@layer components` these never applied. */
16
+ .vb-section[data-rhythm="tight"] { --vb-rhythm: 0.55; }
17
+ .vb-section[data-rhythm="loose"] { --vb-rhythm: 1.35; }
18
+ .vb-section[data-rhythm="none"] { --vb-rhythm: 0; }
14
19
  /* Emphasis in sans presets reads as accent ink (italic stays the serif's job). */
15
20
  [data-font="grotesk"],
16
21
  [data-font="corporate"],
@@ -27,9 +32,6 @@
27
32
  color: var(--fg);
28
33
  scroll-margin-top: calc(var(--topbar-h) + 1rem);
29
34
  }
30
- .vb-section[data-rhythm="tight"] { --vb-rhythm: 0.55; }
31
- .vb-section[data-rhythm="loose"] { --vb-rhythm: 1.35; }
32
- .vb-section[data-rhythm="none"] { --vb-rhythm: 0; }
33
35
 
34
36
  .vb-section[data-surface="scene"] { background-color: var(--scene); }
35
37
  /* Surface tokens are translucent under glass: lay them over the section's own scene colour so a
@@ -99,15 +101,18 @@
99
101
  }
100
102
 
101
103
  /* Container widths */
104
+ /* Full width up to the measure. An explicit width (not `auto`) matters: inside a flex column the
105
+ auto inline margins switch off stretching, and the container shrank to its content (a grid of
106
+ lazy images collapsed to almost nothing). */
102
107
  .vb-container {
103
- box-sizing: content-box;
104
- width: auto;
105
- max-width: var(--container);
108
+ box-sizing: border-box;
109
+ width: 100%;
110
+ max-width: calc(var(--container) + 2 * var(--gutter));
106
111
  margin-inline: auto;
107
112
  padding-inline: var(--gutter);
108
113
  }
109
- .vb-container[data-size="wide"] { max-width: var(--container-wide); }
110
- .vb-container[data-size="prose"] { max-width: var(--prose); }
114
+ .vb-container[data-size="wide"] { max-width: calc(var(--container-wide) + 2 * var(--gutter)); }
115
+ .vb-container[data-size="prose"] { max-width: calc(var(--prose) + 2 * var(--gutter)); }
111
116
  .vb-container[data-size="full"] { max-width: none; }
112
117
 
113
118
  /* Headings */
@@ -126,6 +131,9 @@
126
131
  background-size: cover;
127
132
  background-position: var(--vb-focal, 50% 50%);
128
133
  }
134
+ /* No ratio: the frame follows the image. Was a Tailwind h-auto class, which (utilities layer)
135
+ silently beat component sizes such as .vb-nav-logo's 1.75rem height. */
136
+ .vb-image:where(:not([data-ratio])) { height: auto; }
129
137
  .vb-image > img,
130
138
  .vb-image > picture > img {
131
139
  display: block;
@@ -163,11 +171,12 @@
163
171
  /* Edit-mode placeholders for unknown / invalid blocks. */
164
172
  .vb-notice {
165
173
  --tone: var(--danger);
174
+ --tone-ink: var(--danger-ink);
166
175
  border: 1px dashed color-mix(in srgb, var(--tone) 55%, transparent);
167
176
  background: color-mix(in srgb, var(--tone) 7%, var(--scene));
168
177
  color: var(--fg);
169
178
  }
170
- .vb-notice[data-tone="warning"] { --tone: var(--warning); }
179
+ .vb-notice[data-tone="warning"] { --tone: var(--warning); --tone-ink: var(--warning-ink); }
171
180
  }
172
181
 
173
182
  /* The primary action can't be accent-on-accent: it inverts. Unlayered on purpose, so it wins
@@ -259,7 +268,7 @@
259
268
  }
260
269
  [data-density="compact"] .vb-navbar,
261
270
  .vb-navbar[data-density="compact"] { --vb-nav-h: 3.75rem; }
262
- [data-material="glass"] .vb-navbar { --vb-nav-fill: color-mix(in oklch, var(--scene) 72%, transparent); }
271
+ [data-material="glass"] .vb-navbar { --vb-nav-fill: color-mix(in oklch, var(--scene) 90%, transparent); }
263
272
  .vb-navbar[data-variant="rail"],
264
273
  .vb-navbar[data-variant="minimal"] { border-block-end-color: transparent; }
265
274
  .vb-navbar-row { min-height: var(--vb-nav-h); }
@@ -540,8 +549,8 @@
540
549
  }
541
550
  .vb-navbar[data-sticky]:not([data-overlay]) { background-color: var(--scene); }
542
551
  [data-material="glass"] .vb-navbar[data-sticky] {
543
- -webkit-backdrop-filter: blur(var(--blur, 0px)) saturate(160%);
544
- backdrop-filter: blur(var(--blur, 0px)) saturate(160%);
552
+ -webkit-backdrop-filter: blur(max(var(--blur, 0px), 14px)) saturate(160%);
553
+ backdrop-filter: blur(max(var(--blur, 0px), 14px)) saturate(160%);
545
554
  }
546
555
  }
547
556
  @supports not (animation-timeline: scroll()) {
@@ -589,7 +589,8 @@
589
589
  .vui-cblock-unknown p { margin: 0; }
590
590
 
591
591
  /* Images */
592
- .vui-image { margin-block: 1.5rem; }
592
+ /* Editor image node only: the kit <Image> shares the class name. */
593
+ .ProseMirror .vui-image { margin-block: 1.5rem; }
593
594
  .vui-image-frame { position: relative; border-radius: var(--radius-ui-sm); }
594
595
  .vui-image-frame img { display: block; max-width: 100%; height: auto; border-radius: inherit; }
595
596
  .vui-image[data-selected] .vui-image-frame { outline: 1.5px solid var(--ring); outline-offset: 4px; }
@@ -169,7 +169,8 @@
169
169
  /* ------------------------------------------------------------------ */
170
170
  @keyframes vui-marquee { to { transform: translateX(calc(-100% * var(--dir))); } }
171
171
  @layer components {
172
- .vui-marquee { gap: var(--marquee-gap); }
172
+ .vui-marquee-viewport { gap: var(--marquee-gap); }
173
+ .vui-marquee[data-controls] .vui-marquee-viewport { margin-inline-end: 2.25rem; }
173
174
  .vui-marquee-track {
174
175
  gap: var(--marquee-gap);
175
176
  min-width: 100%;
@@ -178,15 +179,40 @@
178
179
  }
179
180
  .vui-marquee[data-reverse] .vui-marquee-track { animation-direction: reverse; }
180
181
  .vui-marquee[data-pause-on-hover]:hover .vui-marquee-track,
181
- .vui-marquee[data-pause-on-hover]:focus-within .vui-marquee-track { animation-play-state: paused; }
182
- .vui-marquee[data-fade] {
182
+ .vui-marquee[data-pause-on-hover]:focus-within .vui-marquee-track,
183
+ .vui-marquee:has(.vui-marquee-pause:checked) .vui-marquee-track { animation-play-state: paused; }
184
+ .vui-marquee-toggle {
185
+ position: absolute;
186
+ inset-inline-end: 0;
187
+ inset-block-start: 50%;
188
+ translate: 0 -50%;
189
+ display: grid;
190
+ place-items: center;
191
+ width: 1.75rem;
192
+ height: 1.75rem;
193
+ border-radius: 999px;
194
+ border: 1px solid var(--surface-border);
195
+ background: var(--surface);
196
+ color: var(--fg-muted);
197
+ cursor: pointer;
198
+ }
199
+ .vui-marquee-toggle:hover { color: var(--fg); background: color-mix(in srgb, var(--fg) 5%, var(--surface)); }
200
+ .vui-marquee-toggle:has(:focus-visible) { color: var(--fg); outline: 2px solid var(--ring); outline-offset: 2px; }
201
+ .vui-marquee-toggle svg { width: 0.75rem; height: 0.75rem; }
202
+ .vui-marquee-icon-play,
203
+ .vui-marquee:has(.vui-marquee-pause:checked) .vui-marquee-icon-pause { display: none; }
204
+ .vui-marquee:has(.vui-marquee-pause:checked) .vui-marquee-icon-play { display: block; }
205
+ [data-motion="off"] .vui-marquee-toggle { display: none; }
206
+ [data-motion="off"] .vui-marquee[data-controls] .vui-marquee-viewport { margin-inline-end: 0; }
207
+ .vui-marquee[data-fade] .vui-marquee-viewport {
183
208
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
184
209
  mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
185
210
  }
186
211
  @media (prefers-reduced-motion: reduce) {
187
- .vui-marquee { overflow-x: auto; }
212
+ .vui-marquee-viewport { overflow-x: auto; margin-inline-end: 0; }
188
213
  .vui-marquee-track { animation: none; }
189
- .vui-marquee-track[aria-hidden] { display: none; }
214
+ .vui-marquee-track[aria-hidden],
215
+ .vui-marquee-toggle { display: none; }
190
216
  }
191
217
  }
192
218
 
@@ -0,0 +1,389 @@
1
+ /* ------------------------------------------------------------------ */
2
+ /* Motion kit (@vamos-ui/react/motion) */
3
+ /* */
4
+ /* Levels: data-motion="full | subtle | off" on any ancestor (SiteShell */
5
+ /* sets it); "off" on <html> beats every scope. No attribute = subtle. */
6
+ /* Every hidden or moving state below sits behind */
7
+ /* @media screen and (prefers-reduced-motion: no-preference) */
8
+ /* and the ON guard :where(:not([data-motion="off"], [data-motion="off"] *))
9
+ /* so reduced motion, motion-off and print always get the final state. */
10
+ /* Transform / opacity / filter / clip-path only: no layout shift. */
11
+ /* ------------------------------------------------------------------ */
12
+
13
+ :root {
14
+ --motion-fast: 140ms;
15
+ --motion-base: 240ms;
16
+ --motion-slow: 420ms;
17
+ --motion-reveal: 680ms;
18
+ --ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1);
19
+ --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
20
+ --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
21
+ /* A damped spring: ~4% overshoot, settles by 70%. */
22
+ --ease-spring: linear(0, 0.034 1.6%, 0.135 3.4%, 0.474 8.5%, 0.73 13%, 0.896 17.8%, 0.988 22.9%, 1.033 28.6%, 1.045 34.2%, 1.039 40.4%, 1.014 52.8%, 0.999 68.4%, 1);
23
+ --motion-distance: 14px;
24
+ --motion-blur: 6px;
25
+ --motion-stagger: 60ms;
26
+ /* Density factor: compact moves shorter and quicker, spacious a touch slower. */
27
+ --motion-k: 1;
28
+ }
29
+ [data-motion="subtle"] {
30
+ --motion-reveal: 680ms;
31
+ --motion-distance: 14px;
32
+ --motion-blur: 6px;
33
+ --motion-stagger: 60ms;
34
+ }
35
+ [data-motion="full"] {
36
+ --motion-reveal: 960ms;
37
+ --motion-distance: 32px;
38
+ --motion-blur: 12px;
39
+ --motion-stagger: 90ms;
40
+ }
41
+ [data-density="compact"] { --motion-k: 0.8; }
42
+ [data-density="comfortable"] { --motion-k: 1; }
43
+ [data-density="spacious"] { --motion-k: 1.12; }
44
+
45
+ @keyframes vm-fade-in { from { opacity: 0; } }
46
+ @keyframes vm-fade-out { to { opacity: 0; } }
47
+ @keyframes vm-enter { from { opacity: 0; transform: translate3d(0, calc(var(--motion-distance) * var(--motion-k)), 0); } }
48
+ @keyframes vm-enter-title {
49
+ from { opacity: 0; transform: translate3d(0, 0.3em, 0); clip-path: inset(0 -0.2em 100% -0.2em); }
50
+ to { clip-path: inset(-0.2em -0.2em -0.3em -0.2em); }
51
+ }
52
+ @keyframes vm-enter-media { from { opacity: 0; clip-path: inset(10% 0 0 0); } to { clip-path: inset(0 0 0 0); } }
53
+ @keyframes vm-enter-zoom { from { transform: scale(1.06); } }
54
+ @keyframes vm-page-in { from { opacity: 0; transform: translate3d(0, 10px, 0); } }
55
+ @keyframes vm-vt-in { from { opacity: 0; transform: translate3d(0, 10px, 0); } }
56
+ @keyframes vm-marquee { to { transform: translate3d(calc(-50% * var(--dir, 1)), 0, 0); } }
57
+ @keyframes vm-parallax {
58
+ from { transform: translate3d(0, var(--vm-parallax-shift), 0); }
59
+ to { transform: translate3d(0, calc(var(--vm-parallax-shift) * -1), 0); }
60
+ }
61
+ @keyframes vm-grow-x { from { transform: scaleX(0); } to { transform: scaleX(1); } }
62
+
63
+ /* ------------------------------------------------------- Reveal states */
64
+ /* useReveal flips data-reveal-state: pending → in → done. */
65
+
66
+ [data-reveal] { --_from: translate3d(0, calc(var(--motion-distance) * var(--motion-k)), 0); }
67
+ [data-reveal="fade"] { --_from: none; }
68
+ [data-reveal="scale"] { --_from: scale(0.94); }
69
+ [data-reveal="blur"] { --_from: translate3d(0, calc(var(--motion-distance) * 0.5 * var(--motion-k)), 0); }
70
+ [data-reveal="clip"] { --_from: translate3d(0, calc(var(--motion-distance) * 0.6 * var(--motion-k)), 0); }
71
+
72
+ /* Stagger: --vm-i per child, capped at 12 so long lists never drag. */
73
+ [data-reveal-stagger] > :nth-child(2) { --vm-i: 1; }
74
+ [data-reveal-stagger] > :nth-child(3) { --vm-i: 2; }
75
+ [data-reveal-stagger] > :nth-child(4) { --vm-i: 3; }
76
+ [data-reveal-stagger] > :nth-child(5) { --vm-i: 4; }
77
+ [data-reveal-stagger] > :nth-child(6) { --vm-i: 5; }
78
+ [data-reveal-stagger] > :nth-child(7) { --vm-i: 6; }
79
+ [data-reveal-stagger] > :nth-child(8) { --vm-i: 7; }
80
+ [data-reveal-stagger] > :nth-child(9) { --vm-i: 8; }
81
+ [data-reveal-stagger] > :nth-child(10) { --vm-i: 9; }
82
+ [data-reveal-stagger] > :nth-child(11) { --vm-i: 10; }
83
+ [data-reveal-stagger] > :nth-child(12) { --vm-i: 11; }
84
+ [data-reveal-stagger] > :nth-child(n + 13) { --vm-i: 12; }
85
+
86
+ /* Text reveal structure (also without motion: plain inline text). */
87
+ .vm-tr-mask { display: inline-block; vertical-align: top; }
88
+ .vm-tr-unit { display: inline-block; }
89
+ [data-text-reveal="line"] .vm-tr-mask { display: block; }
90
+ [data-reveal="text"] .vm-tr-mask { overflow: clip; overflow-clip-margin: 0.1em; padding-block-end: 0.12em; margin-block-end: -0.12em; }
91
+
92
+ @media screen and (prefers-reduced-motion: no-preference) {
93
+ /* Hidden (pending) */
94
+ [data-reveal][data-reveal-state="pending"]:not([data-reveal-stagger], [data-text-reveal]):where(:not([data-motion="off"], [data-motion="off"] *)),
95
+ [data-reveal-stagger][data-reveal-state="pending"]:where(:not([data-motion="off"], [data-motion="off"] *)) > * {
96
+ opacity: 0;
97
+ transform: var(--_from);
98
+ }
99
+ [data-reveal="blur"][data-reveal-state="pending"]:not([data-reveal-stagger], [data-text-reveal]):where(:not([data-motion="off"], [data-motion="off"] *)),
100
+ [data-reveal="blur"][data-reveal-stagger][data-reveal-state="pending"]:where(:not([data-motion="off"], [data-motion="off"] *)) > * {
101
+ filter: blur(var(--motion-blur));
102
+ }
103
+ [data-reveal="clip"][data-reveal-state="pending"]:not([data-reveal-stagger], [data-text-reveal]):where(:not([data-motion="off"], [data-motion="off"] *)),
104
+ [data-reveal="clip"][data-reveal-stagger][data-reveal-state="pending"]:where(:not([data-motion="off"], [data-motion="off"] *)) > * {
105
+ opacity: 1;
106
+ clip-path: inset(0 0 100% 0);
107
+ }
108
+
109
+ /* Revealing (in) */
110
+ [data-reveal][data-reveal-state="in"]:not([data-reveal-stagger], [data-text-reveal]):where(:not([data-motion="off"], [data-motion="off"] *)),
111
+ [data-reveal-stagger][data-reveal-state="in"]:where(:not([data-motion="off"], [data-motion="off"] *)) > * {
112
+ --_d: calc(var(--vm-duration, var(--motion-reveal)) * var(--motion-k));
113
+ transition-property: opacity, transform, filter, clip-path;
114
+ transition-duration: calc(var(--_d) * 0.6), var(--_d), var(--_d), var(--_d);
115
+ transition-timing-function: linear, var(--ease-out-quint), var(--ease-out-quint), var(--ease-out-expo);
116
+ transition-delay: var(--vm-delay, 0ms);
117
+ }
118
+ [data-reveal-stagger][data-reveal-state="in"]:where(:not([data-motion="off"], [data-motion="off"] *)) > * {
119
+ transition-delay: calc(var(--vm-delay, 0ms) + var(--vm-i, 0) * var(--vm-step, var(--motion-stagger)) * var(--motion-k));
120
+ }
121
+
122
+ /* TextReveal: units hide inside their masks; the heading box never moves. */
123
+ [data-text-reveal][data-reveal-state="pending"]:where(:not([data-motion="off"], [data-motion="off"] *)) .vm-tr-unit { transform: translate3d(0, 108%, 0); }
124
+ [data-text-reveal][data-reveal="fade"][data-reveal-state="pending"]:where(:not([data-motion="off"], [data-motion="off"] *)) .vm-tr-unit { transform: none; opacity: 0; }
125
+ [data-text-reveal][data-reveal="blur"][data-reveal-state="pending"]:where(:not([data-motion="off"], [data-motion="off"] *)) .vm-tr-unit { transform: translate3d(0, 0.2em, 0); opacity: 0; filter: blur(var(--motion-blur)); }
126
+ [data-text-reveal]:is([data-reveal-state="in"], [data-reveal-state="done"]):where(:not([data-motion="off"], [data-motion="off"] *)) .vm-tr-unit {
127
+ transition:
128
+ transform calc(var(--vm-duration, var(--motion-reveal)) * var(--motion-k)) var(--ease-out-quint),
129
+ opacity calc(var(--vm-duration, var(--motion-reveal)) * 0.6 * var(--motion-k)) linear,
130
+ filter calc(var(--vm-duration, var(--motion-reveal)) * var(--motion-k)) var(--ease-out-quint);
131
+ transition-delay: calc(var(--vm-delay, 0ms) + var(--vm-i, 0) * var(--vm-step, 45ms) * var(--motion-k));
132
+ }
133
+
134
+ /* ------------------------------------------------ Enter (CSS only) */
135
+ /* Plays from the first paint — no JavaScript involved — so hero copy */
136
+ /* is never held back by hydration. Opt in with .vm-enter + --vm-i. */
137
+ .vm-enter:where(:not([data-motion="off"], [data-motion="off"] *)) {
138
+ animation: vm-enter calc(var(--motion-reveal) * var(--motion-k)) var(--ease-out-quint) backwards;
139
+ animation-delay: calc(60ms + var(--vm-i, 0) * var(--motion-stagger) * 1.3 * var(--motion-k));
140
+ }
141
+ [data-motion="full"] .vm-enter-title:where(:not([data-motion="off"] *)) { animation-name: vm-enter-title; }
142
+ .vm-enter-media:where(:not([data-motion="off"], [data-motion="off"] *)) { animation-name: vm-enter-media; animation-timing-function: var(--ease-out-expo); }
143
+ [data-motion="full"] .vm-enter-media:where(:not([data-motion="off"] *)) :is(img, video) {
144
+ animation: vm-enter-zoom calc(var(--motion-reveal) * 1.8 * var(--motion-k)) var(--ease-out-quint) backwards;
145
+ }
146
+
147
+ /* --------------------------------------------------- Page switches */
148
+ .vm-page[data-page-enter]:where(:not([data-motion="off"], [data-motion="off"] *)),
149
+ [data-site-page][data-page-enter]:where(:not([data-motion="off"] *)) > * {
150
+ animation: vm-page-in calc(var(--motion-slow) * var(--motion-k)) var(--ease-out-quint) backwards;
151
+ }
152
+
153
+ /* ---------------------------------------------------- Ticker / marquee */
154
+ .vm-marquee:where(:not([data-motion="off"], [data-motion="off"] *)) .vm-marquee-track {
155
+ animation: vm-marquee var(--vm-marquee-duration, 40s) linear infinite;
156
+ }
157
+ .vm-marquee[data-direction="end"] .vm-marquee-track { animation-direction: reverse; }
158
+ .vm-marquee-track:hover,
159
+ .vm-marquee-track:focus-within,
160
+ .vm-marquee:has(.vm-marquee-pause:checked) .vm-marquee-track { animation-play-state: paused; }
161
+
162
+ /* --------------------------------------------------------- Parallax */
163
+ @supports (animation-timeline: view()) {
164
+ .vm-parallax { view-timeline: --vm-parallax block; }
165
+ .vm-parallax:where(:not([data-motion="off"], [data-motion="off"] *)) > .vm-parallax-layer {
166
+ animation: vm-parallax linear both;
167
+ animation-timeline: --vm-parallax;
168
+ }
169
+ }
170
+ .vm-parallax[data-js]:where(:not([data-motion="off"], [data-motion="off"] *)) > .vm-parallax-layer {
171
+ transform: translate3d(0, calc((0.5 - var(--vm-progress, 0.5)) * 2 * var(--vm-parallax-shift)), 0);
172
+ }
173
+
174
+ /* ---------------------------------------------- Pointer: tilt, magnetic */
175
+ .vm-tilt:where(:not([data-motion="off"], [data-motion="off"] *)) {
176
+ transition: transform var(--motion-slow) var(--ease-out-quint);
177
+ }
178
+ .vm-tilt[data-pointer]:where(:not([data-motion="off"], [data-motion="off"] *)) {
179
+ transform: perspective(60rem) rotateX(var(--vm-tilt-x, 0deg)) rotateY(var(--vm-tilt-y, 0deg));
180
+ transition-duration: 120ms;
181
+ }
182
+ .vm-magnetic:where(:not([data-motion="off"], [data-motion="off"] *)) {
183
+ transition: translate calc(var(--motion-slow) * 1.4) var(--ease-spring);
184
+ }
185
+ .vm-magnetic[data-pointer]:where(:not([data-motion="off"], [data-motion="off"] *)) {
186
+ translate: var(--vm-mag-x, 0px) var(--vm-mag-y, 0px);
187
+ transition: translate 140ms var(--ease-out-quint);
188
+ }
189
+
190
+ /* --------------------------------------------- Micro-interactions */
191
+ .vm-press:where(:not([data-motion="off"], [data-motion="off"] *)) { transition: scale var(--motion-fast) var(--ease-out-quint); }
192
+ .vm-press:where(:not([data-motion="off"], [data-motion="off"] *)):active:not(:disabled, [aria-disabled="true"]) { scale: 0.97; }
193
+ @media (hover: hover) and (pointer: fine) {
194
+ .vm-lift:where(:not([data-motion="off"], [data-motion="off"] *)) {
195
+ transition: translate var(--motion-base) var(--ease-out-quint), box-shadow var(--motion-base) var(--ease-out-quint);
196
+ }
197
+ .vm-lift:where(:not([data-motion="off"], [data-motion="off"] *)):hover { translate: 0 -2px; box-shadow: var(--popover-shadow); }
198
+ .vm-nudge:where(:not([data-motion="off"], [data-motion="off"] *)) > svg:last-child { transition: translate var(--motion-base) var(--ease-out-quint); }
199
+ .vm-nudge:where(:not([data-motion="off"], [data-motion="off"] *)):hover > svg:last-child { translate: calc(3px * var(--dir, 1)) 0; }
200
+ }
201
+ .vm-underline:where(:not([data-motion="off"], [data-motion="off"] *)) { transition: background-size var(--motion-slow) var(--ease-out-quint); }
202
+
203
+ /* -------------------------------------------- Skeleton → content */
204
+ .vm-swap-skeleton[data-leaving]:where(:not([data-motion="off"], [data-motion="off"] *)) {
205
+ animation: vm-fade-out calc(var(--motion-slow) * var(--motion-k)) var(--ease-out-quint) forwards;
206
+ }
207
+ .vm-swap-content[data-entering]:where(:not([data-motion="off"], [data-motion="off"] *)) {
208
+ animation: vm-fade-in calc(var(--motion-slow) * var(--motion-k)) var(--ease-out-quint) backwards;
209
+ }
210
+
211
+ /* ------------------------------------------- Expand / accordion height */
212
+ .vm-expand:where(:not([data-motion="off"], [data-motion="off"] *)) {
213
+ transition: grid-template-rows calc(var(--motion-slow) * var(--motion-k)) var(--ease-out-quint);
214
+ }
215
+ @supports selector(::details-content) {
216
+ details.vm-details:where(:not([data-motion="off"], [data-motion="off"] *))::details-content {
217
+ transition:
218
+ block-size calc(var(--motion-slow) * var(--motion-k)) var(--ease-out-quint),
219
+ content-visibility calc(var(--motion-slow) * var(--motion-k)) allow-discrete;
220
+ }
221
+ }
222
+
223
+ /* -------------------------------------------- Sticky header shrink */
224
+ .vm-shrink:where(:not([data-motion="off"], [data-motion="off"] *))::before { transition: transform var(--motion-base) var(--ease-out-quint), border-color var(--motion-base) ease; }
225
+ .vm-shrink:where(:not([data-motion="off"], [data-motion="off"] *)) > * { transition: translate var(--motion-base) var(--ease-out-quint); }
226
+ }
227
+
228
+ /* -------------------------------------------------- View transitions */
229
+ /* startPageTransition names the switching element "vm-page" for the */
230
+ /* length of the transition; the rest of the page does not cross-fade. */
231
+ ::view-transition-group(vm-page) { animation-duration: 0s; }
232
+ ::view-transition-old(vm-page) { animation: vm-fade-out 140ms var(--ease-in-out) both; }
233
+ ::view-transition-new(vm-page) { animation: vm-vt-in 340ms var(--ease-out-quint) 60ms both; }
234
+ html[data-vm-transition]::view-transition-old(root),
235
+ html[data-vm-transition]::view-transition-new(root) { animation: none; }
236
+ @media (prefers-reduced-motion: reduce) {
237
+ ::view-transition-old(vm-page),
238
+ ::view-transition-new(vm-page) { animation: none; }
239
+ }
240
+
241
+ /* -------------------------------------------------------- CountUp */
242
+ .vm-count { display: inline-grid; font-variant-numeric: tabular-nums; }
243
+ .vm-count::before { content: attr(data-ghost); grid-area: 1 / 1; visibility: hidden; }
244
+ .vm-count > :not(.sr-only) { grid-area: 1 / 1; }
245
+
246
+ /* --------------------------------------------------- Ticker / marquee */
247
+ .vm-marquee { --vm-marquee-gap: clamp(2.5rem, 6cqi, 4.5rem); position: relative; }
248
+ .vm-marquee-viewport { overflow: hidden; }
249
+ .vm-marquee[data-fade] .vm-marquee-viewport {
250
+ -webkit-mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
251
+ mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
252
+ }
253
+ .vm-marquee-track { display: flex; width: max-content; }
254
+ .vm-marquee-group {
255
+ display: flex;
256
+ flex-shrink: 0;
257
+ align-items: center;
258
+ gap: var(--vm-marquee-gap);
259
+ margin: 0;
260
+ padding: 0;
261
+ padding-inline-end: var(--vm-marquee-gap);
262
+ list-style: none;
263
+ }
264
+ .vm-marquee-toggle {
265
+ position: absolute;
266
+ inset-inline-end: 0;
267
+ inset-block-start: 50%;
268
+ translate: 0 -50%;
269
+ display: grid;
270
+ place-items: center;
271
+ width: 1.75rem;
272
+ height: 1.75rem;
273
+ border-radius: 999px;
274
+ border: 1px solid var(--surface-border);
275
+ background: var(--surface);
276
+ color: var(--fg-muted);
277
+ cursor: pointer;
278
+ opacity: 0.7;
279
+ transition: opacity var(--motion-fast) ease, color var(--motion-fast) ease;
280
+ }
281
+ .vm-marquee-toggle:hover,
282
+ .vm-marquee-toggle:has(:focus-visible) { opacity: 1; color: var(--fg); }
283
+ .vm-marquee-toggle:has(:focus-visible) { outline: 2px solid var(--ring); outline-offset: 2px; }
284
+ .vm-marquee-toggle svg { width: 0.75rem; height: 0.75rem; fill: currentColor; }
285
+ .vm-marquee-icon-play,
286
+ .vm-marquee:has(.vm-marquee-pause:checked) .vm-marquee-icon-pause { display: none; }
287
+ .vm-marquee:has(.vm-marquee-pause:checked) .vm-marquee-icon-play { display: block; }
288
+
289
+ /* ------------------------------------------------- Parallax frame */
290
+ .vm-parallax[data-clip] { position: relative; overflow: clip; }
291
+ .vm-parallax[data-clip] > .vm-parallax-layer { position: absolute; inset: calc(var(--vm-parallax-shift) * -1) 0; }
292
+ .vm-parallax[data-clip] > .vm-parallax-layer > :is(img, video, picture) { display: block; width: 100%; height: 100%; object-fit: cover; }
293
+
294
+ /* ------------------------------------------------- Scroll progress */
295
+ .vm-scroll-progress { height: 2px; overflow: hidden; pointer-events: none; }
296
+ .vm-scroll-progress[data-fixed] { position: fixed; inset-inline: 0; inset-block-start: 0; z-index: var(--z-sticky, 20); }
297
+ .vm-scroll-progress-bar { height: 100%; background: var(--accent); transform-origin: left center; transform: scaleX(var(--vm-progress, 0)); }
298
+ [dir="rtl"] .vm-scroll-progress-bar { transform-origin: right center; }
299
+ @supports (animation-timeline: scroll()) {
300
+ .vm-scroll-progress:not([data-js]) .vm-scroll-progress-bar { animation: vm-grow-x linear both; animation-timeline: scroll(root block); }
301
+ }
302
+
303
+ /* ----------------------------------------------------- Tilt glare */
304
+ .vm-tilt[data-glare] { position: relative; }
305
+ .vm-tilt[data-glare]::after {
306
+ content: "";
307
+ position: absolute;
308
+ inset: 0;
309
+ border-radius: inherit;
310
+ pointer-events: none;
311
+ background: radial-gradient(circle at var(--vm-glare-x, 50%) var(--vm-glare-y, 50%), color-mix(in oklch, var(--fg) 7%, transparent), transparent 60%);
312
+ opacity: 0;
313
+ transition: opacity var(--motion-base) ease;
314
+ }
315
+ .vm-tilt[data-glare][data-pointer]::after { opacity: 1; }
316
+ .vm-magnetic { display: inline-block; }
317
+
318
+ /* --------------------------------------------- Micro-interactions */
319
+ /* A quiet resting underline (links stay recognisable without colour), drawn over in full on hover. */
320
+ .vm-underline {
321
+ text-decoration: none;
322
+ background-image: linear-gradient(currentColor, currentColor), linear-gradient(color-mix(in oklch, currentColor 28%, transparent), color-mix(in oklch, currentColor 28%, transparent));
323
+ background-repeat: no-repeat;
324
+ background-position: 0 100%, 0 100%;
325
+ background-size: 0% 1px, 100% 1px;
326
+ }
327
+ [dir="rtl"] .vm-underline { background-position: 100% 100%, 100% 100%; }
328
+ .vm-underline:hover,
329
+ .vm-underline:focus-visible,
330
+ .vm-underline[aria-current="page"] { background-size: 100% 1px, 100% 1px; }
331
+
332
+ /* ------------------------------------------------ Skeleton swap */
333
+ .vm-swap { display: grid; }
334
+ .vm-swap > * { grid-area: 1 / 1; min-width: 0; }
335
+ .vm-swap-skeleton[data-leaving] { pointer-events: none; }
336
+
337
+ /* ------------------------------------------ Expand / accordion height */
338
+ .vm-expand { display: grid; grid-template-rows: 0fr; }
339
+ .vm-expand > * { min-height: 0; overflow: hidden; }
340
+ .vm-expand:is([data-state="open"], [data-open]) { grid-template-rows: 1fr; }
341
+ @supports selector(::details-content) {
342
+ details.vm-details { interpolate-size: allow-keywords; }
343
+ details.vm-details::details-content { block-size: 0; overflow: clip; }
344
+ details.vm-details[open]::details-content { block-size: auto; }
345
+ }
346
+
347
+ /* ---------------------------------------- Sticky header shrink */
348
+ /* The bar's background is a pseudo-element that slides up; the content */
349
+ /* moves up half as much. Transforms only, the header box keeps its size. */
350
+ .vm-shrink { --vm-shrink-by: 0.625rem; isolation: isolate; }
351
+ .vm-shrink::before {
352
+ content: "";
353
+ position: absolute;
354
+ inset: calc(var(--vm-shrink-by) * -2) 0 0;
355
+ z-index: -1;
356
+ background: var(--vm-shrink-bg, var(--scene));
357
+ border-block-end: 1px solid transparent;
358
+ }
359
+ .vm-shrink[data-scrolled]::before { transform: translate3d(0, calc(var(--vm-shrink-by) * -2), 0); border-block-end-color: var(--line); }
360
+ .vm-shrink[data-scrolled] > * { translate: 0 calc(var(--vm-shrink-by) * -1); }
361
+
362
+ /* ----------------------------------------------- Motion off / reduced */
363
+ /* data-motion="off" also stills the kit's other built-in motion. */
364
+ [data-motion="off"] :is(.vb-reveal, .vb-marquee-track, .vui-marquee-track, .vb-feature-panel, .vb-form-success, .vb-sticky-frame),
365
+ html[data-motion="off"] :is(.ui-fade, .ui-pop, .ui-dialog, .ui-sheet-end, .ui-sheet-start, .ui-sheet-bottom, .ui-sheet-top, .ui-collapse, .ui-rise) {
366
+ animation: none !important;
367
+ }
368
+ [data-motion="off"] .vb-arrow { transition: none !important; }
369
+ [data-motion="off"] .vb-faq-marker::before,
370
+ [data-motion="off"] .vb-faq-marker::after,
371
+ [data-motion="off"] .vb-faq-item::details-content { transition: none !important; }
372
+ [data-motion="off"] .vb-marquee-track > [data-dup],
373
+ [data-motion="off"] .vui-marquee-track[aria-hidden] { display: none; }
374
+ [data-motion="off"] .vb-marquee { -webkit-mask-image: none; mask-image: none; }
375
+ [data-motion="off"] .vb-marquee-track { width: auto; flex-wrap: wrap; justify-content: center; row-gap: 1.5rem; padding-inline: var(--gutter); }
376
+
377
+ /* Static ticker: one wrapped, centred row, no copy, no pause control. */
378
+ .vm-marquee:is([data-motion="off"], [data-motion="off"] *) :is(.vm-marquee-toggle, [data-marquee-copy]) { display: none; }
379
+ .vm-marquee:is([data-motion="off"], [data-motion="off"] *) .vm-marquee-viewport { -webkit-mask-image: none; mask-image: none; }
380
+ .vm-marquee:is([data-motion="off"], [data-motion="off"] *) .vm-marquee-track { width: auto; }
381
+ .vm-marquee:is([data-motion="off"], [data-motion="off"] *) .vm-marquee-group { flex: 1; flex-wrap: wrap; justify-content: center; row-gap: 1.5rem; padding-inline: 0; }
382
+ @media (prefers-reduced-motion: reduce) {
383
+ .vm-marquee :is(.vm-marquee-toggle, [data-marquee-copy]) { display: none; }
384
+ .vm-marquee .vm-marquee-viewport { -webkit-mask-image: none; mask-image: none; }
385
+ .vm-marquee .vm-marquee-track { width: auto; }
386
+ .vm-marquee .vm-marquee-group { flex: 1; flex-wrap: wrap; justify-content: center; row-gap: 1.5rem; padding-inline: 0; }
387
+ .vm-shrink::before,
388
+ .vm-shrink > * { transition: none; }
389
+ }