@dreamtree-org/twreact-ui 2.1.2 → 2.2.1

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 (93) hide show
  1. package/README.md +47 -4
  2. package/ai-skills/dreamtree-ui.md +20 -3
  3. package/dist/_shared/{cn-DPPQUJu1.js → cn-TMJ-_w1a.js} +14 -1
  4. package/dist/_shared/useLatestRef-CdtlnPUz.js +21 -0
  5. package/dist/_shared/useReducedMotion-B_PMM5ER.js +77 -0
  6. package/dist/ai-schema.json +513 -0
  7. package/dist/charts/index.js +6 -76
  8. package/dist/components/motion/BentoExpandGrid.d.ts +10 -0
  9. package/dist/components/motion/BentoExpandGrid.d.ts.map +1 -0
  10. package/dist/components/motion/CountUp.d.ts +9 -0
  11. package/dist/components/motion/CountUp.d.ts.map +1 -0
  12. package/dist/components/motion/DeviceFrame.d.ts +8 -0
  13. package/dist/components/motion/DeviceFrame.d.ts.map +1 -0
  14. package/dist/components/motion/DeviceMorphHero.d.ts +11 -0
  15. package/dist/components/motion/DeviceMorphHero.d.ts.map +1 -0
  16. package/dist/components/motion/LogoMarquee.d.ts +9 -0
  17. package/dist/components/motion/LogoMarquee.d.ts.map +1 -0
  18. package/dist/components/motion/PhotoCollage.d.ts +9 -0
  19. package/dist/components/motion/PhotoCollage.d.ts.map +1 -0
  20. package/dist/components/motion/Reveal.d.ts +9 -0
  21. package/dist/components/motion/Reveal.d.ts.map +1 -0
  22. package/dist/components/motion/RevealGroup.d.ts +5 -0
  23. package/dist/components/motion/RevealGroup.d.ts.map +1 -0
  24. package/dist/components/motion/RotatingBadge.d.ts +9 -0
  25. package/dist/components/motion/RotatingBadge.d.ts.map +1 -0
  26. package/dist/components/motion/ScrollStage.d.ts +11 -0
  27. package/dist/components/motion/ScrollStage.d.ts.map +1 -0
  28. package/dist/components/motion/SmoothScrollProvider.d.ts +18 -0
  29. package/dist/components/motion/SmoothScrollProvider.d.ts.map +1 -0
  30. package/dist/components/motion/StackedCards.d.ts +10 -0
  31. package/dist/components/motion/StackedCards.d.ts.map +1 -0
  32. package/dist/components/motion/StatStrip.d.ts +8 -0
  33. package/dist/components/motion/StatStrip.d.ts.map +1 -0
  34. package/dist/components/motion/TestimonialSpotlight.d.ts +10 -0
  35. package/dist/components/motion/TestimonialSpotlight.d.ts.map +1 -0
  36. package/dist/components/motion/storyData.d.ts +52 -0
  37. package/dist/components/motion/storyData.d.ts.map +1 -0
  38. package/dist/esm/components/motion/BentoExpandGrid.js +144 -0
  39. package/dist/esm/components/motion/CountUp.js +97 -0
  40. package/dist/esm/components/motion/DeviceFrame.js +102 -0
  41. package/dist/esm/components/motion/DeviceMorphHero.js +228 -0
  42. package/dist/esm/components/motion/LogoMarquee.js +144 -0
  43. package/dist/esm/components/motion/PhotoCollage.js +92 -0
  44. package/dist/esm/components/motion/Reveal.js +115 -0
  45. package/dist/esm/components/motion/RevealGroup.js +43 -0
  46. package/dist/esm/components/motion/RotatingBadge.js +66 -0
  47. package/dist/esm/components/motion/ScrollStage.js +62 -0
  48. package/dist/esm/components/motion/SmoothScrollProvider.js +63 -0
  49. package/dist/esm/components/motion/StackedCards.js +166 -0
  50. package/dist/esm/components/motion/StatStrip.js +58 -0
  51. package/dist/esm/components/motion/TestimonialSpotlight.js +209 -0
  52. package/dist/esm/hooks/useScrollProgress.js +107 -0
  53. package/dist/esm/hooks/useSmoothScroll.js +33 -0
  54. package/dist/esm/motion/engine/inView.js +54 -0
  55. package/dist/esm/motion/engine/progress.js +45 -0
  56. package/dist/esm/motion/engine/scheduler.js +67 -0
  57. package/dist/esm/motion/engine/useHydrated.js +17 -0
  58. package/dist/esm/motion/engine/useIsomorphicLayoutEffect.js +7 -0
  59. package/dist/esm/motion/engine/useMergedRef.js +12 -0
  60. package/dist/esm/motion/index.js +16 -0
  61. package/dist/esm/motion/tokens.js +10 -0
  62. package/dist/hooks/useScrollProgress.d.ts +17 -0
  63. package/dist/hooks/useScrollProgress.d.ts.map +1 -0
  64. package/dist/hooks/useSmoothScroll.d.ts +11 -0
  65. package/dist/hooks/useSmoothScroll.d.ts.map +1 -0
  66. package/dist/index.css +1 -1
  67. package/dist/index.esm.css +1 -1
  68. package/dist/motion/engine/inView.d.ts +5 -0
  69. package/dist/motion/engine/inView.d.ts.map +1 -0
  70. package/dist/motion/engine/progress.d.ts +18 -0
  71. package/dist/motion/engine/progress.d.ts.map +1 -0
  72. package/dist/motion/engine/scheduler.d.ts +10 -0
  73. package/dist/motion/engine/scheduler.d.ts.map +1 -0
  74. package/dist/motion/engine/useHydrated.d.ts +3 -0
  75. package/dist/motion/engine/useHydrated.d.ts.map +1 -0
  76. package/dist/motion/engine/useIsomorphicLayoutEffect.d.ts +4 -0
  77. package/dist/motion/engine/useIsomorphicLayoutEffect.d.ts.map +1 -0
  78. package/dist/motion/engine/useMergedRef.d.ts +3 -0
  79. package/dist/motion/engine/useMergedRef.d.ts.map +1 -0
  80. package/dist/motion/index.d.ts +20 -0
  81. package/dist/motion/index.d.ts.map +1 -0
  82. package/dist/motion/index.js +1813 -0
  83. package/dist/motion/tokens.d.ts +6 -0
  84. package/dist/motion/tokens.d.ts.map +1 -0
  85. package/dist/rich-text/index.js +3 -18
  86. package/dist/tokens/tokens.json +21 -0
  87. package/dist/types/motion.d.ts +290 -0
  88. package/dist/types/motion.d.ts.map +1 -0
  89. package/doc/README.md +17 -1
  90. package/mcp/catalog.snapshot.json +952 -5
  91. package/motion/package.json +5 -0
  92. package/package.json +14 -3
  93. package/tokens/tokens.json +6 -0
@@ -0,0 +1,1813 @@
1
+ 'use strict';
2
+
3
+ var cn = require('../_shared/cn-TMJ-_w1a.js');
4
+ var React = require('react');
5
+ var useLatestRef = require('../_shared/useLatestRef-CdtlnPUz.js');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+ var useReducedMotion = require('../_shared/useReducedMotion-B_PMM5ER.js');
8
+ require('clsx');
9
+ require('tailwind-merge');
10
+
11
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
+
13
+ var React__default = /*#__PURE__*/_interopDefault(React);
14
+
15
+ // Internal motion engine: ONE requestAnimationFrame loop shared by every motion
16
+ // component. Each frame runs every subscriber's `measure()` (DOM reads) first,
17
+ // then every `write(measured, now)` (DOM writes), so reads and writes never
18
+ // interleave and the browser lays out at most once per frame.
19
+ //
20
+ // The loop is idle unless something asks for a frame: a scroll or resize event
21
+ // (passive listeners), a new subscriber, or a subscriber with `loop: true`
22
+ // (time-based work such as CountUp or Lenis). Scenes subscribe only while they
23
+ // are near the viewport (see inView.js), so off-screen sections cost nothing.
24
+ var subscribers = new Set();
25
+ var frameId = 0;
26
+ var listening = false;
27
+ function hasWindow() {
28
+ return typeof window !== "undefined" && typeof window.requestAnimationFrame === "function";
29
+ }
30
+ function runFrame(now) {
31
+ frameId = 0;
32
+ var list = Array.from(subscribers);
33
+ var measured = list.map(function (s) {
34
+ return s.measure ? s.measure() : undefined;
35
+ });
36
+ var again = false;
37
+ list.forEach(function (s, i) {
38
+ if (!subscribers.has(s)) return; // unsubscribed by an earlier write this frame
39
+ if (s.write) s.write(measured[i], now);
40
+ if (s.loop && subscribers.has(s)) again = true;
41
+ });
42
+ if (again) requestFrame();
43
+ }
44
+ /** Ask for one frame (coalesced: many calls before the frame run it once). */
45
+ function requestFrame() {
46
+ if (frameId || !hasWindow()) return;
47
+ frameId = requestAnimationFrame(runFrame);
48
+ }
49
+ var onScrollOrResize = function onScrollOrResize() {
50
+ return requestFrame();
51
+ };
52
+ function listen(on) {
53
+ if (on === listening || typeof window === "undefined") return;
54
+ listening = on;
55
+ var method = on ? "addEventListener" : "removeEventListener";
56
+ window[method]("scroll", onScrollOrResize, {
57
+ passive: true
58
+ });
59
+ window[method]("resize", onScrollOrResize, {
60
+ passive: true
61
+ });
62
+ }
63
+ /**
64
+ * Register `{ measure?, write?, loop? }`. Returns the unsubscribe function.
65
+ * The subscriber gets a frame right away, so it can paint its first state.
66
+ */
67
+ function subscribe(subscriber) {
68
+ subscribers.add(subscriber);
69
+ listen(true);
70
+ requestFrame();
71
+ return function () {
72
+ subscribers["delete"](subscriber);
73
+ if (subscribers.size === 0) {
74
+ listen(false);
75
+ if (frameId && hasWindow()) cancelAnimationFrame(frameId);
76
+ frameId = 0;
77
+ }
78
+ };
79
+ }
80
+
81
+ // Internal: one shared IntersectionObserver per (rootMargin, threshold) pair.
82
+ // `observe(el, cb)` calls `cb(isIntersecting, entry)` on every change and
83
+ // returns a function that stops observing. Without IntersectionObserver (old
84
+ // browsers, some test environments) the element counts as always in view.
85
+ var observers = new Map();
86
+ function getObserver(rootMargin, threshold) {
87
+ var key = "".concat(rootMargin, "|").concat(threshold);
88
+ var record = observers.get(key);
89
+ if (!record) {
90
+ var callbacks = new Map();
91
+ var io = new IntersectionObserver(function (entries) {
92
+ entries.forEach(function (entry) {
93
+ var cb = callbacks.get(entry.target);
94
+ if (cb) cb(entry.isIntersecting, entry);
95
+ });
96
+ }, {
97
+ rootMargin: rootMargin,
98
+ threshold: threshold
99
+ });
100
+ record = {
101
+ io: io,
102
+ callbacks: callbacks,
103
+ key: key
104
+ };
105
+ observers.set(key, record);
106
+ }
107
+ return record;
108
+ }
109
+ function observe(el, callback) {
110
+ var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
111
+ _ref$rootMargin = _ref.rootMargin,
112
+ rootMargin = _ref$rootMargin === void 0 ? "0px" : _ref$rootMargin,
113
+ _ref$threshold = _ref.threshold,
114
+ threshold = _ref$threshold === void 0 ? 0 : _ref$threshold;
115
+ if (!el) return function () {};
116
+ if (typeof IntersectionObserver === "undefined") {
117
+ callback(true, null);
118
+ return function () {};
119
+ }
120
+ var record = getObserver(rootMargin, threshold);
121
+ record.callbacks.set(el, callback);
122
+ record.io.observe(el);
123
+ return function () {
124
+ if (!record.callbacks.has(el)) return;
125
+ record.callbacks["delete"](el);
126
+ record.io.unobserve(el);
127
+ if (record.callbacks.size === 0) {
128
+ record.io.disconnect();
129
+ observers["delete"](record.key);
130
+ }
131
+ };
132
+ }
133
+
134
+ // Internal: pure scroll-progress maths (unit-tested, no DOM).
135
+ var clamp01 = function clamp01(v) {
136
+ return v > 0 ? v < 1 ? v : 1 : 0;
137
+ };
138
+ var lerp = function lerp(a, b, t) {
139
+ return a + (b - a) * t;
140
+ };
141
+ /** Ease-out cubic: fast start, gentle stop. */
142
+ var easeOut = function easeOut(t) {
143
+ return 1 - Math.pow(1 - t, 3);
144
+ };
145
+ /** Ease-in-out cubic. */
146
+ var easeInOut = function easeInOut(t) {
147
+ return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
148
+ };
149
+ /** Re-map `t` from the window [from, to] to 0–1 (clamped). */
150
+ var segment = function segment(t, from, to) {
151
+ return clamp01((t - from) / (to - from || 1));
152
+ };
153
+ /**
154
+ * Scroll progress of an element.
155
+ * - "view": 0 when its top meets the viewport bottom, 1 when its bottom leaves the viewport top.
156
+ * - "pin": 0 when its top meets the viewport top, 1 when its bottom meets the viewport bottom
157
+ * (the span a `position: sticky` child stays pinned).
158
+ * `start` / `end` narrow the range: progress is re-mapped from [start, end] to 0–1.
159
+ */
160
+ function computeProgress(top, height, viewport) {
161
+ var _ref = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {},
162
+ _ref$mode = _ref.mode,
163
+ mode = _ref$mode === void 0 ? "view" : _ref$mode,
164
+ _ref$start = _ref.start,
165
+ start = _ref$start === void 0 ? 0 : _ref$start,
166
+ _ref$end = _ref.end,
167
+ end = _ref$end === void 0 ? 1 : _ref$end;
168
+ var raw;
169
+ if (mode === "pin") {
170
+ var span = height - viewport;
171
+ raw = span > 0 ? -top / span : top <= 0 ? 1 : 0;
172
+ } else {
173
+ raw = (viewport - top) / (viewport + height || 1);
174
+ }
175
+ return segment(clamp01(raw), start, end);
176
+ }
177
+
178
+ function createStore(initial) {
179
+ var value = initial;
180
+ var listeners = new Set();
181
+ return {
182
+ get: function get() {
183
+ return value;
184
+ },
185
+ set: function set(next) {
186
+ if (next === value) return;
187
+ value = next;
188
+ listeners.forEach(function (fn) {
189
+ return fn(next);
190
+ });
191
+ },
192
+ subscribe: function subscribe(fn) {
193
+ listeners.add(fn);
194
+ return function () {
195
+ listeners["delete"](fn);
196
+ };
197
+ }
198
+ };
199
+ }
200
+ /**
201
+ * Scroll progress (0–1) of `ref`'s element, without re-rendering React on every frame.
202
+ *
203
+ * Returns a subscription (`get()` / `subscribe(fn)`), writes the value to the element as a
204
+ * CSS custom property (`--dt-p` by default) so CSS can map it to transforms, and calls
205
+ * `onChange`. Work runs on the shared motion frame loop, and only while the element is
206
+ * near the viewport.
207
+ *
208
+ * The value starts at 1 (the final state) and the CSS variable is unset on the server, under
209
+ * reduced motion (pass `enabled: false`) and after unmount, so write CSS with a final-state
210
+ * fallback: `opacity: var(--dt-p, 1)`.
211
+ */
212
+ function useScrollProgress(ref) {
213
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
214
+ var _options$mode = options.mode,
215
+ mode = _options$mode === void 0 ? "view" : _options$mode,
216
+ _options$start = options.start,
217
+ start = _options$start === void 0 ? 0 : _options$start,
218
+ _options$end = options.end,
219
+ end = _options$end === void 0 ? 1 : _options$end,
220
+ _options$cssVar = options.cssVar,
221
+ cssVar = _options$cssVar === void 0 ? "--dt-p" : _options$cssVar,
222
+ _options$enabled = options.enabled,
223
+ enabled = _options$enabled === void 0 ? true : _options$enabled;
224
+ var _useState = React.useState(function () {
225
+ return createStore(1);
226
+ }),
227
+ _useState2 = cn._slicedToArray(_useState, 1),
228
+ store = _useState2[0];
229
+ var onChangeRef = useLatestRef.useLatestRef(options.onChange);
230
+ React.useEffect(function () {
231
+ var el = ref.current;
232
+ if (!el || !enabled) return undefined;
233
+ var measure = function measure() {
234
+ var rect = el.getBoundingClientRect();
235
+ return computeProgress(rect.top, rect.height, innerHeight, {
236
+ mode: mode,
237
+ start: start,
238
+ end: end
239
+ });
240
+ };
241
+ var _write = function write(p) {
242
+ if (cssVar) el.style.setProperty(cssVar, p.toFixed(4));
243
+ if (p !== store.get()) {
244
+ var _onChangeRef$current;
245
+ store.set(p);
246
+ (_onChangeRef$current = onChangeRef.current) === null || _onChangeRef$current === void 0 || _onChangeRef$current.call(onChangeRef, p);
247
+ }
248
+ };
249
+ var unsubscribe = null;
250
+ var stopObserving = observe(el, function (inView) {
251
+ if (inView && !unsubscribe) {
252
+ unsubscribe = subscribe({
253
+ measure: measure,
254
+ write: function write(p) {
255
+ return _write(p);
256
+ }
257
+ });
258
+ } else if (!inView && unsubscribe) {
259
+ _write(measure()); // settle at 0 or 1 even after a fast fling
260
+ unsubscribe();
261
+ unsubscribe = null;
262
+ }
263
+ }, {
264
+ rootMargin: "25% 0px"
265
+ });
266
+ return function () {
267
+ stopObserving();
268
+ if (unsubscribe) unsubscribe();
269
+ if (cssVar) el.style.removeProperty(cssVar);
270
+ store.set(1);
271
+ };
272
+ }, [ref, enabled, mode, start, end, cssVar, store, onChangeRef]);
273
+ return store;
274
+ }
275
+
276
+ /** Assigns one DOM node to several refs (callback or object). */
277
+ function setRefs(node) {
278
+ for (var _len = arguments.length, refs = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
279
+ refs[_key - 1] = arguments[_key];
280
+ }
281
+ refs.forEach(function (ref) {
282
+ if (!ref) return;
283
+ if (typeof ref === "function") ref(node);else ref.current = node;
284
+ });
285
+ }
286
+
287
+ // Internal: one callback ref that feeds a forwarded ref and a local ref.
288
+ function useMergedRef(forwardedRef, localRef) {
289
+ return React.useCallback(function (node) {
290
+ return setRefs(node, localRef, forwardedRef);
291
+ }, [forwardedRef, localRef]);
292
+ }
293
+
294
+ // Defaults from the `scroll` group of tokens/tokens.json (kept in sync by
295
+ // src/motion/engine/__tests__/tokens.test.js). Plain numbers so the motion
296
+ // entry does not bundle the token file.
297
+ var SCROLL_STICKY_TOP = 80; // px — scroll.stickyTop
298
+ var SCENE_LENGTH = 300; // vh — scroll.sceneLength
299
+ var SCENE_LENGTH_MOBILE = 150; // vh — scroll.sceneLengthMobile
300
+ /** Below Tailwind's `md` breakpoint. */
301
+ var MOBILE_QUERY = "(max-width: 767px)";
302
+
303
+ var _excluded$c = ["length", "mobileLength", "pin", "children", "onProgress", "className", "contentClassName", "style", "as"];
304
+ /**
305
+ * A pinned scroll scene: a tall section whose content sticks to the viewport while the
306
+ * user scrolls through `length` vh. Progress (0–1) is written to `--dt-p` on the root, so
307
+ * children animate with CSS (`opacity: var(--dt-p, 1)`), and passed to `children(progress)`
308
+ * and `onProgress`. Under reduced motion, or with `pin={false}`, it renders in normal flow
309
+ * at its final state.
310
+ */
311
+ var ScrollStage = /*#__PURE__*/React__default.default.forwardRef(function (_ref, ref) {
312
+ var _ref$length = _ref.length,
313
+ length = _ref$length === void 0 ? SCENE_LENGTH : _ref$length,
314
+ _ref$mobileLength = _ref.mobileLength,
315
+ mobileLength = _ref$mobileLength === void 0 ? SCENE_LENGTH_MOBILE : _ref$mobileLength,
316
+ _ref$pin = _ref.pin,
317
+ pin = _ref$pin === void 0 ? true : _ref$pin,
318
+ children = _ref.children,
319
+ onProgress = _ref.onProgress,
320
+ className = _ref.className,
321
+ contentClassName = _ref.contentClassName,
322
+ style = _ref.style,
323
+ _ref$as = _ref.as,
324
+ Tag = _ref$as === void 0 ? "section" : _ref$as,
325
+ rest = cn._objectWithoutProperties(_ref, _excluded$c);
326
+ var local = React.useRef(null);
327
+ var setRef = useMergedRef(ref, local);
328
+ var reduced = useReducedMotion.useReducedMotion();
329
+ var mobile = useReducedMotion.useMediaQuery(MOBILE_QUERY);
330
+ var pinned = pin && !reduced;
331
+ var progress = useScrollProgress(local, {
332
+ mode: "pin",
333
+ enabled: pinned,
334
+ onChange: onProgress
335
+ });
336
+ var vh = mobile ? mobileLength : length;
337
+ return jsxRuntime.jsx(Tag, cn._objectSpread2(cn._objectSpread2({
338
+ ref: setRef,
339
+ "data-dt-stage": pinned ? "pinned" : "static",
340
+ className: cn.cn("relative", className),
341
+ style: pinned ? cn._objectSpread2({
342
+ height: "".concat(Math.max(vh, 100), "vh")
343
+ }, style) : style
344
+ }, rest), {}, {
345
+ children: jsxRuntime.jsx("div", {
346
+ className: cn.cn(pinned && "sticky top-0 h-screen overflow-hidden", contentClassName),
347
+ children: typeof children === "function" ? children(progress) : children
348
+ })
349
+ }));
350
+ });
351
+ ScrollStage.displayName = "ScrollStage";
352
+
353
+ // useLayoutEffect warns during SSR; fall back to useEffect on the server.
354
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
355
+
356
+ var _excluded$b = ["effect", "delay", "stagger", "once", "as", "className", "style", "children"];
357
+ var BASE = "transition-[opacity,transform,clip-path] duration-700 ease-emphasized motion-reduce:transition-none";
358
+ // Classes for the hidden state of each effect (the shown state is the element's natural style).
359
+ var HIDDEN = {
360
+ "fade-up": "opacity-0 translate-y-6",
361
+ fade: "opacity-0",
362
+ scale: "opacity-0 scale-95",
363
+ clip: "[clip-path:inset(0_0_100%_0)]",
364
+ "split-words": ""
365
+ };
366
+ var WORD_HIDDEN = "opacity-0 translate-y-[0.6em]";
367
+ /**
368
+ * Plays an entrance when the element scrolls into view. The server, no-JS readers and
369
+ * reduced-motion users get the content as-is; elements already on screen at load are not
370
+ * hidden (no flash, no LCP delay). Only content below the fold animates.
371
+ */
372
+ var Reveal = /*#__PURE__*/React__default.default.forwardRef(function (_ref, ref) {
373
+ var _ref$effect = _ref.effect,
374
+ effect = _ref$effect === void 0 ? "fade-up" : _ref$effect,
375
+ _ref$delay = _ref.delay,
376
+ delay = _ref$delay === void 0 ? 0 : _ref$delay,
377
+ _ref$stagger = _ref.stagger,
378
+ stagger = _ref$stagger === void 0 ? 40 : _ref$stagger,
379
+ _ref$once = _ref.once,
380
+ once = _ref$once === void 0 ? true : _ref$once,
381
+ _ref$as = _ref.as,
382
+ Tag = _ref$as === void 0 ? "div" : _ref$as,
383
+ className = _ref.className,
384
+ style = _ref.style,
385
+ children = _ref.children,
386
+ rest = cn._objectWithoutProperties(_ref, _excluded$b);
387
+ var local = React.useRef(null);
388
+ var setRef = useMergedRef(ref, local);
389
+ var reduced = useReducedMotion.useReducedMotion();
390
+ // "static": final state (server, reduced motion, above the fold) · "hidden" · "shown"
391
+ var _useState = React.useState("static"),
392
+ _useState2 = cn._slicedToArray(_useState, 2),
393
+ state = _useState2[0],
394
+ setState = _useState2[1];
395
+ useIsomorphicLayoutEffect(function () {
396
+ var el = local.current;
397
+ if (reduced || !el) {
398
+ setState("static");
399
+ return undefined;
400
+ }
401
+ var belowFold = el.getBoundingClientRect().top > innerHeight;
402
+ if (!belowFold && once) return undefined;
403
+ if (belowFold) setState("hidden");
404
+ var stop = function stop() {};
405
+ stop = observe(el, function (inView) {
406
+ if (inView) {
407
+ setState("shown");
408
+ if (once) stop();
409
+ } else if (!once) {
410
+ setState("hidden");
411
+ }
412
+ }, {
413
+ threshold: 0.15
414
+ });
415
+ return function () {
416
+ return stop();
417
+ };
418
+ }, [reduced, once]);
419
+ var hidden = state === "hidden";
420
+ var animated = state !== "static";
421
+ if (effect === "split-words" && typeof children === "string") {
422
+ var words = children.split(/\s+/).filter(Boolean);
423
+ return jsxRuntime.jsxs(Tag, cn._objectSpread2(cn._objectSpread2({
424
+ ref: setRef,
425
+ className: className,
426
+ style: style
427
+ }, rest), {}, {
428
+ children: [jsxRuntime.jsx("span", {
429
+ className: "sr-only",
430
+ children: children
431
+ }), jsxRuntime.jsx("span", {
432
+ "aria-hidden": "true",
433
+ children: words.map(function (word, i) {
434
+ return jsxRuntime.jsxs(React__default.default.Fragment, {
435
+ children: [jsxRuntime.jsx("span", {
436
+ className: cn.cn("inline-block", animated && BASE, hidden && WORD_HIDDEN),
437
+ style: animated ? {
438
+ transitionDelay: "".concat(delay + i * stagger, "ms")
439
+ } : undefined,
440
+ children: word
441
+ }), i < words.length - 1 ? " " : null]
442
+ }, i);
443
+ })
444
+ })]
445
+ }));
446
+ }
447
+ return jsxRuntime.jsx(Tag, cn._objectSpread2(cn._objectSpread2({
448
+ ref: setRef,
449
+ "data-reveal": state,
450
+ className: cn.cn(animated && BASE, hidden && (HIDDEN[effect] || HIDDEN["fade-up"]), className),
451
+ style: animated && delay ? cn._objectSpread2({
452
+ transitionDelay: "".concat(delay, "ms")
453
+ }, style) : style
454
+ }, rest), {}, {
455
+ children: children
456
+ }));
457
+ });
458
+ Reveal.displayName = "Reveal";
459
+
460
+ var _excluded$a = ["effect", "delay", "stagger", "once", "as", "itemAs", "children"];
461
+ /** Reveals each child in turn: child `i` waits `delay + i * stagger` ms. */
462
+ var RevealGroup = /*#__PURE__*/React__default.default.forwardRef(function (_ref, ref) {
463
+ var _ref$effect = _ref.effect,
464
+ effect = _ref$effect === void 0 ? "fade-up" : _ref$effect,
465
+ _ref$delay = _ref.delay,
466
+ delay = _ref$delay === void 0 ? 0 : _ref$delay,
467
+ _ref$stagger = _ref.stagger,
468
+ stagger = _ref$stagger === void 0 ? 80 : _ref$stagger,
469
+ _ref$once = _ref.once,
470
+ once = _ref$once === void 0 ? true : _ref$once,
471
+ _ref$as = _ref.as,
472
+ Tag = _ref$as === void 0 ? "div" : _ref$as,
473
+ _ref$itemAs = _ref.itemAs,
474
+ itemAs = _ref$itemAs === void 0 ? "div" : _ref$itemAs,
475
+ children = _ref.children,
476
+ rest = cn._objectWithoutProperties(_ref, _excluded$a);
477
+ var index = 0;
478
+ return jsxRuntime.jsx(Tag, cn._objectSpread2(cn._objectSpread2({
479
+ ref: ref
480
+ }, rest), {}, {
481
+ children: React__default.default.Children.map(children, function (child) {
482
+ if (child == null || child === false) return child;
483
+ var i = index++;
484
+ return jsxRuntime.jsx(Reveal, {
485
+ as: itemAs,
486
+ effect: effect === "split-words" ? "fade-up" : effect,
487
+ delay: delay + i * stagger,
488
+ once: once,
489
+ children: child
490
+ });
491
+ })
492
+ }));
493
+ });
494
+ RevealGroup.displayName = "RevealGroup";
495
+
496
+ function nativeScrollTo(target) {
497
+ var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
498
+ _ref$offset = _ref.offset,
499
+ offset = _ref$offset === void 0 ? 0 : _ref$offset,
500
+ _ref$immediate = _ref.immediate,
501
+ immediate = _ref$immediate === void 0 ? false : _ref$immediate;
502
+ if (typeof window === "undefined") return;
503
+ var el = typeof target === "string" ? document.querySelector(target) : target;
504
+ var top = typeof el === "number" ? el : el ? el.getBoundingClientRect().top + window.scrollY : null;
505
+ if (top == null) return;
506
+ window.scrollTo({
507
+ top: top + offset,
508
+ behavior: immediate ? "auto" : "smooth"
509
+ });
510
+ }
511
+ /** Native scrolling: the value outside a SmoothScrollProvider (or while Lenis is off). */
512
+ var NATIVE_SCROLL = {
513
+ lenis: null,
514
+ scrollTo: nativeScrollTo
515
+ };
516
+ var SmoothScrollContext = /*#__PURE__*/React.createContext(NATIVE_SCROLL);
517
+ /**
518
+ * `{ lenis, scrollTo }` from the nearest SmoothScrollProvider. Outside one, or while smooth
519
+ * scroll is off, `lenis` is `null` and `scrollTo` uses native `window.scrollTo`.
520
+ */
521
+ function useSmoothScroll() {
522
+ return React.useContext(SmoothScrollContext);
523
+ }
524
+
525
+ /**
526
+ * Opt-in smooth (inertial) scrolling with Lenis. Lenis is an optional peer dependency
527
+ * and is never imported by this package: install it and pass the class —
528
+ * `<SmoothScrollProvider lenis={Lenis}>`. Without `lenis`, with `enabled={false}` or when
529
+ * the user prefers reduced motion, the page keeps native scrolling. Lenis runs on the same
530
+ * frame loop as the other motion components.
531
+ */
532
+ function SmoothScrollProvider(_ref) {
533
+ var LenisClass = _ref.lenis,
534
+ options = _ref.options,
535
+ _ref$enabled = _ref.enabled,
536
+ enabled = _ref$enabled === void 0 ? true : _ref$enabled,
537
+ children = _ref.children;
538
+ var reduced = useReducedMotion.useReducedMotion();
539
+ var _useState = React.useState(null),
540
+ _useState2 = cn._slicedToArray(_useState, 2),
541
+ instance = _useState2[0],
542
+ setInstance = _useState2[1];
543
+ React.useEffect(function () {
544
+ if (!enabled || reduced || typeof LenisClass !== "function") return undefined;
545
+ var lenis = new LenisClass(cn._objectSpread2({
546
+ autoRaf: false
547
+ }, options));
548
+ var unsubscribe = subscribe({
549
+ loop: true,
550
+ write: function write(_, now) {
551
+ return lenis.raf(now);
552
+ }
553
+ });
554
+ setInstance(lenis);
555
+ return function () {
556
+ unsubscribe();
557
+ lenis.destroy();
558
+ setInstance(null);
559
+ };
560
+ // `options` is read once per Lenis instance; change `enabled` or `lenis` to rebuild it.
561
+ // eslint-disable-next-line react-hooks/exhaustive-deps
562
+ }, [LenisClass, enabled, reduced]);
563
+ var value = React.useMemo(function () {
564
+ return instance ? {
565
+ lenis: instance,
566
+ scrollTo: function scrollTo(target) {
567
+ var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
568
+ return instance.scrollTo(target, opts);
569
+ }
570
+ } : NATIVE_SCROLL;
571
+ }, [instance]);
572
+ return jsxRuntime.jsx(SmoothScrollContext.Provider, {
573
+ value: value,
574
+ children: children
575
+ });
576
+ }
577
+ SmoothScrollProvider.displayName = "SmoothScrollProvider";
578
+
579
+ var _excluded$9 = ["device", "url", "screenClassName", "screenRef", "className", "children"];
580
+ // Bezel parts read `--dt-chrome` (opacity, default 1) so DeviceMorphHero can fade them in.
581
+ var CHROME = "opacity-[var(--dt-chrome,1)]";
582
+ // Bezels are borders (not filled boxes) so the screen area stays see-through: DeviceMorphHero
583
+ // slides its full-bleed media layer under a transparent screen.
584
+ var FRAMES = {
585
+ phone: {
586
+ root: "w-[220px] md:w-[262px] aspect-[262/562]",
587
+ bezel: "inset-0 rounded-[2.75rem] md:rounded-[3.25rem] border-[10px] border-shell shadow-2xl",
588
+ screen: "inset-[10px] rounded-[2.1rem] md:rounded-[2.6rem]",
589
+ extras: jsxRuntime.jsxs(jsxRuntime.Fragment, {
590
+ children: [jsxRuntime.jsx("span", {
591
+ "aria-hidden": "true",
592
+ className: cn.cn("absolute left-1/2 top-[18px] h-[22px] w-[30%] -translate-x-1/2 rounded-pill bg-shell", CHROME)
593
+ }), jsxRuntime.jsx("span", {
594
+ "aria-hidden": "true",
595
+ className: cn.cn("absolute bottom-[16px] left-1/2 h-[4px] w-[34%] -translate-x-1/2 rounded-pill bg-shell-foreground/70", CHROME)
596
+ }), jsxRuntime.jsx("span", {
597
+ "aria-hidden": "true",
598
+ className: cn.cn("absolute -left-[3px] top-[22%] h-[9%] w-[3px] rounded-l bg-shell-active", CHROME)
599
+ }), jsxRuntime.jsx("span", {
600
+ "aria-hidden": "true",
601
+ className: cn.cn("absolute -right-[3px] top-[28%] h-[14%] w-[3px] rounded-r bg-shell-active", CHROME)
602
+ })]
603
+ })
604
+ },
605
+ tablet: {
606
+ root: "w-[320px] md:w-[440px] aspect-[3/4]",
607
+ bezel: "inset-0 rounded-[2rem] border-[14px] border-shell shadow-2xl",
608
+ screen: "inset-[14px] rounded-[1rem]",
609
+ extras: jsxRuntime.jsx("span", {
610
+ "aria-hidden": "true",
611
+ className: cn.cn("absolute left-1/2 top-[5px] h-[4px] w-[4px] -translate-x-1/2 rounded-full bg-shell-active", CHROME)
612
+ })
613
+ },
614
+ laptop: {
615
+ root: "w-[340px] md:w-[640px] aspect-[16/10.6]",
616
+ bezel: "inset-x-0 top-0 bottom-[4%] rounded-t-[1.25rem] border-[10px] border-b-[22px] border-shell shadow-2xl",
617
+ screen: "left-[10px] right-[10px] top-[10px] bottom-[calc(4%+22px)] rounded-[0.25rem]",
618
+ extras: jsxRuntime.jsx("span", {
619
+ "aria-hidden": "true",
620
+ className: cn.cn("absolute -inset-x-[6%] bottom-0 h-[4%] rounded-b-[1rem] bg-line shadow-lg", CHROME)
621
+ })
622
+ },
623
+ browser: {
624
+ root: "w-[340px] md:w-[720px] aspect-[16/10.5]",
625
+ bezel: "inset-0 rounded-card border border-t-[36px] border-line border-t-gray-100 shadow-2xl dark:border-t-gray-800",
626
+ screen: "inset-x-px bottom-px top-[36px] rounded-b-card",
627
+ extras: null
628
+ }
629
+ };
630
+ /**
631
+ * A CSS-only device mock-up (phone, tablet, laptop or browser window) around any
632
+ * screen content. Purely presentational: no motion, works anywhere.
633
+ */
634
+ var DeviceFrame = /*#__PURE__*/React__default.default.forwardRef(function (_ref, ref) {
635
+ var _ref$device = _ref.device,
636
+ device = _ref$device === void 0 ? "phone" : _ref$device,
637
+ url = _ref.url,
638
+ screenClassName = _ref.screenClassName,
639
+ screenRef = _ref.screenRef,
640
+ className = _ref.className,
641
+ children = _ref.children,
642
+ rest = cn._objectWithoutProperties(_ref, _excluded$9);
643
+ var f = FRAMES[device] || FRAMES.phone;
644
+ return jsxRuntime.jsxs("div", cn._objectSpread2(cn._objectSpread2({
645
+ ref: ref,
646
+ "data-device": device,
647
+ className: cn.cn("relative shrink-0", f.root, className)
648
+ }, rest), {}, {
649
+ children: [jsxRuntime.jsx("div", {
650
+ "aria-hidden": "true",
651
+ className: cn.cn("pointer-events-none absolute", f.bezel, CHROME)
652
+ }), device === "browser" ? jsxRuntime.jsxs("div", {
653
+ "aria-hidden": "true",
654
+ className: cn.cn("absolute inset-x-0 top-0 flex h-[36px] items-center gap-1.5 px-3", CHROME),
655
+ children: [jsxRuntime.jsx("span", {
656
+ className: "h-2.5 w-2.5 rounded-full bg-error-400"
657
+ }), jsxRuntime.jsx("span", {
658
+ className: "h-2.5 w-2.5 rounded-full bg-warning-400"
659
+ }), jsxRuntime.jsx("span", {
660
+ className: "h-2.5 w-2.5 rounded-full bg-success-400"
661
+ }), url ? jsxRuntime.jsx("span", {
662
+ className: "ml-3 flex-1 truncate rounded-pill bg-surface px-3 py-0.5 text-micro text-muted dark:bg-background",
663
+ children: url
664
+ }) : null]
665
+ }) : null, jsxRuntime.jsx("div", {
666
+ ref: screenRef,
667
+ className: cn.cn("absolute overflow-hidden bg-surface dark:bg-background", f.screen, screenClassName),
668
+ children: children
669
+ }), f.extras]
670
+ }));
671
+ });
672
+ DeviceFrame.displayName = "DeviceFrame";
673
+
674
+ var _excluded$8 = ["items", "speed", "direction", "pauseOnHover", "fadeEdges", "rows", "label", "className"];
675
+ var isLogo = function isLogo(item) {
676
+ return item && cn._typeof(item) === "object" && ! /*#__PURE__*/React__default.default.isValidElement(item) && "src" in item;
677
+ };
678
+ function MarqueeItem(_ref) {
679
+ var item = _ref.item;
680
+ if (isLogo(item)) {
681
+ var img = jsxRuntime.jsx("img", {
682
+ src: item.src,
683
+ alt: item.alt,
684
+ loading: "lazy",
685
+ className: "h-8 w-auto max-w-[10rem] object-contain"
686
+ });
687
+ return item.href ? jsxRuntime.jsx("a", {
688
+ href: item.href,
689
+ className: "rounded-control focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500",
690
+ children: img
691
+ }) : img;
692
+ }
693
+ if (typeof item === "string") {
694
+ return jsxRuntime.jsx("span", {
695
+ className: "whitespace-nowrap text-lg font-semibold tracking-tight text-muted",
696
+ children: item
697
+ });
698
+ }
699
+ return item;
700
+ }
701
+ function List(_ref2) {
702
+ var items = _ref2.items,
703
+ clone = _ref2.clone;
704
+ return jsxRuntime.jsx("ul", {
705
+ role: clone ? undefined : "list",
706
+ "aria-hidden": clone ? "true" : undefined,
707
+ className: cn.cn("flex shrink-0 items-center gap-x-12 pr-12", clone && "motion-reduce:hidden", "motion-reduce:flex-wrap motion-reduce:justify-center motion-reduce:gap-y-6 motion-reduce:pr-0"),
708
+ children: items.map(function (item, i) {
709
+ return jsxRuntime.jsx("li", {
710
+ className: "flex shrink-0 items-center",
711
+ children: clone && isLogo(item) && item.href ? jsxRuntime.jsx(MarqueeItem, {
712
+ item: {
713
+ src: item.src,
714
+ alt: item.alt
715
+ }
716
+ }) : jsxRuntime.jsx(MarqueeItem, {
717
+ item: item
718
+ })
719
+ }, i);
720
+ })
721
+ });
722
+ }
723
+ /**
724
+ * An endless strip of logos or names. The second copy of the list (needed for a seamless
725
+ * loop) is `aria-hidden`, so screen readers read each item once. Pauses on hover, while
726
+ * focus is inside and while off screen; under reduced motion it is a static, wrapped row.
727
+ */
728
+ var LogoMarquee = /*#__PURE__*/React__default.default.forwardRef(function (_ref3, ref) {
729
+ var _ref3$items = _ref3.items,
730
+ items = _ref3$items === void 0 ? [] : _ref3$items,
731
+ _ref3$speed = _ref3.speed,
732
+ speed = _ref3$speed === void 0 ? 40 : _ref3$speed,
733
+ _ref3$direction = _ref3.direction,
734
+ direction = _ref3$direction === void 0 ? "left" : _ref3$direction,
735
+ _ref3$pauseOnHover = _ref3.pauseOnHover,
736
+ pauseOnHover = _ref3$pauseOnHover === void 0 ? true : _ref3$pauseOnHover,
737
+ _ref3$fadeEdges = _ref3.fadeEdges,
738
+ fadeEdges = _ref3$fadeEdges === void 0 ? true : _ref3$fadeEdges,
739
+ _ref3$rows = _ref3.rows,
740
+ rows = _ref3$rows === void 0 ? 1 : _ref3$rows,
741
+ _ref3$label = _ref3.label,
742
+ label = _ref3$label === void 0 ? "Clients" : _ref3$label,
743
+ className = _ref3.className,
744
+ rest = cn._objectWithoutProperties(_ref3, _excluded$8);
745
+ var local = React.useRef(null);
746
+ var setRef = useMergedRef(ref, local);
747
+ var _useState = React.useState(true),
748
+ _useState2 = cn._slicedToArray(_useState, 2),
749
+ inView = _useState2[0],
750
+ setInView = _useState2[1];
751
+ // Seconds per loop. A rough guess for the server render; measured on the client.
752
+ var _useState3 = React.useState(Math.max(items.length, 4) * 3),
753
+ _useState4 = cn._slicedToArray(_useState3, 2),
754
+ seconds = _useState4[0],
755
+ setSeconds = _useState4[1];
756
+ React.useEffect(function () {
757
+ return observe(local.current, function (v) {
758
+ return setInView(v);
759
+ });
760
+ }, []);
761
+ React.useEffect(function () {
762
+ var list = local.current && local.current.querySelector("ul");
763
+ if (!list) return undefined;
764
+ var measure = function measure() {
765
+ var width = list.getBoundingClientRect().width;
766
+ if (width > 0 && speed > 0) setSeconds(width / speed);
767
+ };
768
+ measure();
769
+ if (typeof ResizeObserver === "undefined") return undefined;
770
+ var ro = new ResizeObserver(measure);
771
+ ro.observe(list);
772
+ return function () {
773
+ return ro.disconnect();
774
+ };
775
+ }, [speed, items]);
776
+ var rowCount = rows === 2 ? 2 : 1;
777
+ return jsxRuntime.jsx("div", cn._objectSpread2(cn._objectSpread2({
778
+ ref: setRef,
779
+ role: "region",
780
+ "aria-label": label,
781
+ className: cn.cn("group relative overflow-hidden py-2", fadeEdges && "[mask-image:linear-gradient(to_right,transparent,rgb(0_0_0)_8%,rgb(0_0_0)_92%,transparent)] motion-reduce:[mask-image:none]", className)
782
+ }, rest), {}, {
783
+ children: jsxRuntime.jsx("div", {
784
+ className: "flex flex-col gap-y-6",
785
+ children: Array.from({
786
+ length: rowCount
787
+ }, function (_, row) {
788
+ var reverse = direction === "right" !== (row === 1);
789
+ return jsxRuntime.jsxs("div", {
790
+ "data-marquee-track": "",
791
+ className: cn.cn("flex w-max animate-dt-marquee motion-reduce:w-full motion-reduce:animate-none motion-reduce:justify-center", pauseOnHover && "group-hover:[animation-play-state:paused]", "group-focus-within:[animation-play-state:paused]", !inView && "[animation-play-state:paused]"),
792
+ style: {
793
+ animationDuration: "".concat(seconds, "s"),
794
+ animationDirection: reverse ? "reverse" : "normal"
795
+ },
796
+ children: [jsxRuntime.jsx(List, {
797
+ items: items
798
+ }), jsxRuntime.jsx(List, {
799
+ items: items,
800
+ clone: true
801
+ })]
802
+ }, row);
803
+ })
804
+ })
805
+ }));
806
+ });
807
+ LogoMarquee.displayName = "LogoMarquee";
808
+
809
+ var _excluded$7 = ["value", "from", "prefix", "suffix", "duration", "decimals", "locale", "format", "className"];
810
+ var decimalsOf = function decimalsOf(n) {
811
+ var s = String(n);
812
+ var dot = s.indexOf(".");
813
+ return dot === -1 ? 0 : s.length - dot - 1;
814
+ };
815
+ /**
816
+ * A number that counts up when it scrolls into view. The server (and reduced motion)
817
+ * renders the real final value, so crawlers and no-JS readers never see "0". Screen readers
818
+ * read the final value once from a visually hidden copy; the animating digits are hidden.
819
+ */
820
+ var CountUp = /*#__PURE__*/React__default.default.forwardRef(function (_ref, ref) {
821
+ var value = _ref.value,
822
+ _ref$from = _ref.from,
823
+ from = _ref$from === void 0 ? 0 : _ref$from,
824
+ prefix = _ref.prefix,
825
+ suffix = _ref.suffix,
826
+ _ref$duration = _ref.duration,
827
+ duration = _ref$duration === void 0 ? 1600 : _ref$duration,
828
+ decimals = _ref.decimals,
829
+ _ref$locale = _ref.locale,
830
+ locale = _ref$locale === void 0 ? "en-IN" : _ref$locale,
831
+ format = _ref.format,
832
+ className = _ref.className,
833
+ rest = cn._objectWithoutProperties(_ref, _excluded$7);
834
+ var live = React.useRef(null);
835
+ var reduced = useReducedMotion.useReducedMotion();
836
+ var digits = decimals !== null && decimals !== void 0 ? decimals : decimalsOf(value);
837
+ var fmt = React.useMemo(function () {
838
+ if (format) return format;
839
+ var nf = new Intl.NumberFormat(locale, {
840
+ minimumFractionDigits: digits,
841
+ maximumFractionDigits: digits
842
+ });
843
+ return function (n) {
844
+ return nf.format(n);
845
+ };
846
+ }, [format, locale, digits]);
847
+ var finalText = fmt(value);
848
+ React.useEffect(function () {
849
+ var el = live.current;
850
+ if (!el || reduced || duration <= 0) return undefined;
851
+ // Below the fold: show the start value until it scrolls in. On screen: count right away.
852
+ if (el.getBoundingClientRect().top > innerHeight) el.textContent = fmt(from);
853
+ var unsubscribe = null;
854
+ var stop = function stop() {};
855
+ stop = observe(el, function (inView) {
856
+ if (!inView || unsubscribe) return;
857
+ stop();
858
+ var t0 = null;
859
+ unsubscribe = subscribe({
860
+ loop: true,
861
+ write: function write(_, now) {
862
+ if (t0 === null) t0 = now;
863
+ var t = Math.min(1, (now - t0) / duration);
864
+ el.textContent = t < 1 ? fmt(from + (value - from) * easeOut(t)) : fmt(value);
865
+ if (t >= 1) unsubscribe();
866
+ }
867
+ });
868
+ }, {
869
+ threshold: 0.4
870
+ });
871
+ return function () {
872
+ stop();
873
+ if (unsubscribe) unsubscribe();
874
+ el.textContent = fmt(value);
875
+ };
876
+ }, [value, from, duration, fmt, reduced]);
877
+ return jsxRuntime.jsxs("span", cn._objectSpread2(cn._objectSpread2({
878
+ ref: ref,
879
+ className: cn.cn("tabular-nums", className)
880
+ }, rest), {}, {
881
+ children: [jsxRuntime.jsxs("span", {
882
+ "aria-hidden": "true",
883
+ children: [prefix, jsxRuntime.jsx("span", {
884
+ ref: live,
885
+ children: finalText
886
+ }), suffix]
887
+ }), jsxRuntime.jsxs("span", {
888
+ className: "sr-only",
889
+ children: [prefix, finalText, suffix]
890
+ })]
891
+ }));
892
+ });
893
+ CountUp.displayName = "CountUp";
894
+
895
+ var _excluded$6 = ["items", "variant", "duration", "locale", "className"];
896
+ var COLS = {
897
+ 1: "sm:grid-cols-1",
898
+ 2: "sm:grid-cols-2",
899
+ 3: "sm:grid-cols-3",
900
+ 4: "sm:grid-cols-2 lg:grid-cols-4"
901
+ };
902
+ /**
903
+ * A row of headline numbers that count up on view — as bordered cards or one band with
904
+ * dividers. A description list: each label is a `<dt>`, each number its `<dd>`.
905
+ */
906
+ var StatStrip = /*#__PURE__*/React__default.default.forwardRef(function (_ref, ref) {
907
+ var _ref$items = _ref.items,
908
+ items = _ref$items === void 0 ? [] : _ref$items,
909
+ _ref$variant = _ref.variant,
910
+ variant = _ref$variant === void 0 ? "cards" : _ref$variant,
911
+ duration = _ref.duration,
912
+ locale = _ref.locale,
913
+ className = _ref.className,
914
+ rest = cn._objectWithoutProperties(_ref, _excluded$6);
915
+ var cards = variant !== "band";
916
+ var cols = COLS[Math.min(items.length, 4)] || COLS[4];
917
+ return jsxRuntime.jsx("dl", cn._objectSpread2(cn._objectSpread2({
918
+ ref: ref,
919
+ className: cn.cn("grid grid-cols-2", cols, cards ? "gap-4" : "gap-y-6 rounded-card border border-line bg-surface py-6 dark:bg-background sm:divide-x sm:divide-line", className)
920
+ }, rest), {}, {
921
+ children: items.map(function (item, i) {
922
+ return jsxRuntime.jsxs("div", {
923
+ className: cn.cn("flex flex-col-reverse gap-1", cards ? "rounded-card border border-line bg-surface p-6 shadow-xs dark:bg-background" : "px-6 text-center"),
924
+ children: [jsxRuntime.jsx("dt", {
925
+ className: "text-caption text-muted",
926
+ children: item.label
927
+ }), jsxRuntime.jsx("dd", {
928
+ className: "text-kpi font-semibold tracking-tight text-foreground",
929
+ children: jsxRuntime.jsx(CountUp, {
930
+ value: item.value,
931
+ prefix: item.prefix,
932
+ suffix: item.suffix,
933
+ decimals: item.decimals,
934
+ format: item.format,
935
+ duration: duration,
936
+ locale: locale
937
+ })
938
+ })]
939
+ }, i);
940
+ })
941
+ }));
942
+ });
943
+ StatStrip.displayName = "StatStrip";
944
+
945
+ var _excluded$5 = ["items", "stickyTop", "tilt", "scaleBack", "dimBack", "mediaSide", "renderItem", "className", "style"];
946
+ var isImage$2 = function isImage(media) {
947
+ return media && cn._typeof(media) === "object" && ! /*#__PURE__*/React__default.default.isValidElement(media) && "src" in media;
948
+ };
949
+ function CardBody(_ref) {
950
+ var item = _ref.item,
951
+ flip = _ref.flip;
952
+ return jsxRuntime.jsxs("div", {
953
+ className: "grid gap-8 md:grid-cols-2 md:items-center",
954
+ children: [jsxRuntime.jsxs("div", {
955
+ className: cn.cn("flex flex-col gap-4", flip && "md:order-2"),
956
+ children: [item.kicker ? jsxRuntime.jsx("p", {
957
+ className: "text-micro uppercase tracking-widest text-primary-600 dark:text-primary-400",
958
+ children: item.kicker
959
+ }) : null, jsxRuntime.jsx("h3", {
960
+ className: "text-section-title font-semibold tracking-tight text-foreground",
961
+ children: item.title
962
+ }), item.body ? jsxRuntime.jsx("div", {
963
+ className: "text-body text-muted",
964
+ children: item.body
965
+ }) : null, item.bullets && item.bullets.length ? jsxRuntime.jsx("ul", {
966
+ className: "grid gap-2 sm:grid-cols-2",
967
+ children: item.bullets.map(function (b, i) {
968
+ return jsxRuntime.jsxs("li", {
969
+ className: "flex items-start gap-2 text-caption text-foreground",
970
+ children: [jsxRuntime.jsx("span", {
971
+ "aria-hidden": "true",
972
+ className: "mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-primary-500"
973
+ }), b]
974
+ }, i);
975
+ })
976
+ }) : null, item.metric || item.cta ? jsxRuntime.jsxs("div", {
977
+ className: "mt-2 flex flex-wrap items-end justify-between gap-4",
978
+ children: [item.metric ? jsxRuntime.jsxs("p", {
979
+ className: "flex flex-col",
980
+ children: [jsxRuntime.jsx("span", {
981
+ className: "text-kpi font-semibold text-foreground",
982
+ children: item.metric.value
983
+ }), jsxRuntime.jsx("span", {
984
+ className: "text-caption text-muted",
985
+ children: item.metric.label
986
+ })]
987
+ }) : null, item.cta]
988
+ }) : null]
989
+ }), item.media ? jsxRuntime.jsx("div", {
990
+ className: cn.cn("overflow-hidden rounded-card", flip && "md:order-1"),
991
+ children: isImage$2(item.media) ? jsxRuntime.jsx("img", {
992
+ src: item.media.src,
993
+ alt: item.media.alt,
994
+ loading: "lazy",
995
+ className: "aspect-[4/3] h-full w-full object-cover"
996
+ }) : item.media
997
+ }) : null]
998
+ });
999
+ }
1000
+ /**
1001
+ * Cards that stick one over another as the page scrolls (sticky stacking). Cards
1002
+ * underneath shrink and dim as the next one covers them, and each has a slight
1003
+ * alternating tilt so the stack looks hand-placed. Tilt is off below `md` and under
1004
+ * reduced motion; the scale/dim effect is off under reduced motion.
1005
+ */
1006
+ var StackedCards = /*#__PURE__*/React__default.default.forwardRef(function (_ref2, ref) {
1007
+ var _ref2$items = _ref2.items,
1008
+ items = _ref2$items === void 0 ? [] : _ref2$items,
1009
+ _ref2$stickyTop = _ref2.stickyTop,
1010
+ stickyTop = _ref2$stickyTop === void 0 ? SCROLL_STICKY_TOP : _ref2$stickyTop,
1011
+ _ref2$tilt = _ref2.tilt,
1012
+ tilt = _ref2$tilt === void 0 ? 1 : _ref2$tilt,
1013
+ _ref2$scaleBack = _ref2.scaleBack,
1014
+ scaleBack = _ref2$scaleBack === void 0 ? 0.94 : _ref2$scaleBack,
1015
+ _ref2$dimBack = _ref2.dimBack,
1016
+ dimBack = _ref2$dimBack === void 0 ? 0.15 : _ref2$dimBack,
1017
+ _ref2$mediaSide = _ref2.mediaSide,
1018
+ mediaSide = _ref2$mediaSide === void 0 ? "alternate" : _ref2$mediaSide,
1019
+ renderItem = _ref2.renderItem,
1020
+ className = _ref2.className,
1021
+ style = _ref2.style,
1022
+ rest = cn._objectWithoutProperties(_ref2, _excluded$5);
1023
+ var local = React.useRef(null);
1024
+ var setRef = useMergedRef(ref, local);
1025
+ var reduced = useReducedMotion.useReducedMotion();
1026
+ React.useEffect(function () {
1027
+ var list = local.current;
1028
+ if (!list || reduced || scaleBack >= 1 && dimBack <= 0) return undefined;
1029
+ var slots = Array.from(list.children);
1030
+ var cards = slots.map(function (li) {
1031
+ return li.firstElementChild;
1032
+ });
1033
+ var measure = function measure() {
1034
+ return slots.map(function (li) {
1035
+ return li.getBoundingClientRect();
1036
+ });
1037
+ };
1038
+ var write = function write(rects) {
1039
+ cards.forEach(function (card, i) {
1040
+ var next = rects[i + 1];
1041
+ // 0 while the next card is a full card-height away, 1 once it has reached the sticky line.
1042
+ var cover = next ? clamp01(1 - (next.top - stickyTop) / Math.max(rects[i].height, 1)) : 0;
1043
+ card.style.setProperty("--dt-scale", String(1 - (1 - scaleBack) * cover));
1044
+ card.style.setProperty("--dt-dim", String(1 - dimBack * cover));
1045
+ });
1046
+ };
1047
+ var unsubscribe = null;
1048
+ var stop = observe(list, function (inView) {
1049
+ if (inView && !unsubscribe) unsubscribe = subscribe({
1050
+ measure: measure,
1051
+ write: write
1052
+ });else if (!inView && unsubscribe) {
1053
+ unsubscribe();
1054
+ unsubscribe = null;
1055
+ }
1056
+ });
1057
+ return function () {
1058
+ stop();
1059
+ if (unsubscribe) unsubscribe();
1060
+ cards.forEach(function (card) {
1061
+ card.style.removeProperty("--dt-scale");
1062
+ card.style.removeProperty("--dt-dim");
1063
+ });
1064
+ };
1065
+ }, [items, stickyTop, scaleBack, dimBack, reduced]);
1066
+ return jsxRuntime.jsx("ol", cn._objectSpread2(cn._objectSpread2({
1067
+ ref: setRef,
1068
+ className: cn.cn("relative flex flex-col gap-8", className),
1069
+ style: style
1070
+ }, rest), {}, {
1071
+ children: items.map(function (item, i) {
1072
+ var _item$id;
1073
+ var deg = tilt ? (i % 2 === 0 ? -1 : 1) * tilt * (1 + i % 3 * 0.5) : 0;
1074
+ var flip = mediaSide === "alternate" && i % 2 === 1;
1075
+ return jsxRuntime.jsx("li", {
1076
+ className: "sticky",
1077
+ style: {
1078
+ top: "".concat(stickyTop, "px")
1079
+ },
1080
+ children: jsxRuntime.jsx("article", {
1081
+ className: cn.cn("origin-top rounded-card border border-line bg-surface p-6 shadow-lg dark:bg-background md:p-10",
1082
+ // motion-safe: none of this applies when the OS asks for reduced motion (even before hydration).
1083
+ "motion-safe:[filter:brightness(var(--dt-dim,1))] motion-safe:[transform:scale(var(--dt-scale,1))]", "motion-safe:md:[transform:rotate(var(--dt-tilt,0deg))_scale(var(--dt-scale,1))]"),
1084
+ style: deg ? {
1085
+ "--dt-tilt": "".concat(deg, "deg")
1086
+ } : undefined,
1087
+ children: renderItem ? renderItem(item, i) : jsxRuntime.jsx(CardBody, {
1088
+ item: item,
1089
+ flip: flip
1090
+ })
1091
+ })
1092
+ }, (_item$id = item.id) !== null && _item$id !== void 0 ? _item$id : i);
1093
+ })
1094
+ }));
1095
+ });
1096
+ StackedCards.displayName = "StackedCards";
1097
+
1098
+ var subscribeNever = function subscribeNever() {
1099
+ return function () {};
1100
+ };
1101
+ // Internal: false on the server and while hydrating, true after — without a
1102
+ // setState-in-effect. Lets a component render its static final state first.
1103
+ function useHydrated() {
1104
+ return React.useSyncExternalStore(subscribeNever, function () {
1105
+ return true;
1106
+ }, function () {
1107
+ return false;
1108
+ });
1109
+ }
1110
+
1111
+ var _excluded$4 = ["media", "device", "length", "mobileLength", "side", "content", "scrollHint", "className"];
1112
+ var MAX_LENGTH = 400;
1113
+ var COVER = "h-full w-full object-cover";
1114
+ function Media(_ref) {
1115
+ var media = _ref.media,
1116
+ videoRef = _ref.videoRef;
1117
+ var decorative = !media.alt;
1118
+ if (media.video) {
1119
+ return jsxRuntime.jsx("video", {
1120
+ ref: videoRef,
1121
+ className: COVER,
1122
+ src: media.video,
1123
+ poster: media.poster,
1124
+ muted: true,
1125
+ loop: true,
1126
+ autoPlay: true,
1127
+ playsInline: true,
1128
+ preload: "metadata",
1129
+ "aria-label": decorative ? undefined : media.alt,
1130
+ "aria-hidden": decorative ? "true" : undefined
1131
+ });
1132
+ }
1133
+ return jsxRuntime.jsx("img", {
1134
+ className: COVER,
1135
+ src: media.image || media.poster,
1136
+ alt: media.alt || ""
1137
+ });
1138
+ }
1139
+ /**
1140
+ * The signature scroll hero: full-bleed media shrinks into a device frame, the bezel fades
1141
+ * in, then the device slides aside while the copy (`content`) fades in beside it. The
1142
+ * timeline maps the scene's scroll progress: 0–45 % morph, 40–60 % bezel, 60–90 % slide.
1143
+ * The server, no-JS readers and reduced-motion users get the final layout (copy beside the
1144
+ * device) with no pinning. Below `md` the device rises and shrinks and the copy appears under it.
1145
+ */
1146
+ var DeviceMorphHero = /*#__PURE__*/React__default.default.forwardRef(function (_ref2, ref) {
1147
+ var media = _ref2.media,
1148
+ _ref2$device = _ref2.device,
1149
+ device = _ref2$device === void 0 ? "phone" : _ref2$device,
1150
+ _ref2$length = _ref2.length,
1151
+ length = _ref2$length === void 0 ? SCENE_LENGTH : _ref2$length,
1152
+ _ref2$mobileLength = _ref2.mobileLength,
1153
+ mobileLength = _ref2$mobileLength === void 0 ? SCENE_LENGTH_MOBILE : _ref2$mobileLength,
1154
+ _ref2$side = _ref2.side,
1155
+ side = _ref2$side === void 0 ? "right" : _ref2$side,
1156
+ content = _ref2.content,
1157
+ _ref2$scrollHint = _ref2.scrollHint,
1158
+ scrollHint = _ref2$scrollHint === void 0 ? "Scroll to explore" : _ref2$scrollHint,
1159
+ className = _ref2.className,
1160
+ rest = cn._objectWithoutProperties(_ref2, _excluded$4);
1161
+ var reduced = useReducedMotion.useReducedMotion();
1162
+ var mobile = useReducedMotion.useMediaQuery(MOBILE_QUERY);
1163
+ var armed = useHydrated() && !reduced;
1164
+ var stageRef = React.useRef(null);
1165
+ var setRef = useMergedRef(ref, stageRef);
1166
+ var layerRef = React.useRef(null);
1167
+ var frameRef = React.useRef(null);
1168
+ var screenRef = React.useRef(null);
1169
+ var contentRef = React.useRef(null);
1170
+ var hintRef = React.useRef(null);
1171
+ var videoRef = React.useRef(null);
1172
+ var geo = React.useRef(null);
1173
+ var _useSmoothScroll = useSmoothScroll(),
1174
+ scrollTo = _useSmoothScroll.scrollTo;
1175
+ // Re-measure the frame geometry after a resize or a layout change.
1176
+ React.useEffect(function () {
1177
+ var reset = function reset() {
1178
+ geo.current = null;
1179
+ };
1180
+ reset();
1181
+ window.addEventListener("resize", reset);
1182
+ return function () {
1183
+ return window.removeEventListener("resize", reset);
1184
+ };
1185
+ }, [mobile, device, armed]);
1186
+ // Play the video only while the hero is on screen.
1187
+ React.useEffect(function () {
1188
+ return observe(stageRef.current, function (inView) {
1189
+ var v = videoRef.current;
1190
+ if (!v) return;
1191
+ if (inView && !reduced) {
1192
+ var played = v.play && v.play();
1193
+ if (played && played["catch"]) played["catch"](function () {});
1194
+ } else if (v.pause) v.pause();
1195
+ });
1196
+ }, [armed, reduced]);
1197
+ var onProgress = React.useCallback(function (p) {
1198
+ var layer = layerRef.current;
1199
+ var frame = frameRef.current;
1200
+ var screen = screenRef.current;
1201
+ var stage = stageRef.current && stageRef.current.firstElementChild;
1202
+ if (!layer || !frame || !screen || !stage) return;
1203
+ if (!geo.current) {
1204
+ geo.current = {
1205
+ vw: stage.clientWidth,
1206
+ vh: stage.clientHeight,
1207
+ sx: frame.offsetLeft + screen.offsetLeft,
1208
+ sy: frame.offsetTop + screen.offsetTop,
1209
+ sw: screen.offsetWidth,
1210
+ sh: screen.offsetHeight,
1211
+ r: parseFloat(getComputedStyle(screen).borderTopLeftRadius) || 0
1212
+ };
1213
+ }
1214
+ var g = geo.current;
1215
+ if (!g.sw || !g.sh || !g.vw || !g.vh) return;
1216
+ var q = easeInOut(segment(p, 0, 0.45)); // full-bleed → screen
1217
+ var chrome = segment(p, 0.4, 0.6); // bezel fades in
1218
+ var c = easeOut(segment(p, 0.6, 0.9)); // slide aside + copy in
1219
+ var fit = Math.max(g.sw / g.vw, g.sh / g.vh);
1220
+ var insetX = (g.vw - g.sw / fit) / 2 * q;
1221
+ var insetY = (g.vh - g.sh / fit) / 2 * q;
1222
+ var dx = (g.sx + g.sw / 2 - g.vw / 2) * q;
1223
+ var dy = (g.sy + g.sh / 2 - g.vh / 2) * q;
1224
+ var tx = mobile ? 0 : (side === "left" ? -1 : 1) * g.vw * 0.22 * c;
1225
+ var ty = mobile ? -g.vh * 0.2 * c : 0;
1226
+ var k = mobile ? lerp(1, 0.62, c) : 1;
1227
+ layer.style.transform = "translate3d(".concat(dx + tx, "px,").concat(dy + ty, "px,0) scale(").concat(lerp(1, fit, q) * k, ")");
1228
+ layer.style.clipPath = "inset(".concat(insetY, "px ").concat(insetX, "px round ").concat(g.r / fit * q, "px)");
1229
+ frame.style.transform = "translate3d(".concat(tx, "px,").concat(ty, "px,0) scale(").concat(k, ")");
1230
+ frame.style.setProperty("--dt-chrome", chrome.toFixed(3));
1231
+ if (contentRef.current) {
1232
+ contentRef.current.style.opacity = c.toFixed(3);
1233
+ contentRef.current.style.transform = "translateY(".concat((1 - c) * 24, "px)");
1234
+ contentRef.current.style.pointerEvents = c > 0.5 ? "auto" : "none";
1235
+ }
1236
+ if (hintRef.current) hintRef.current.style.opacity = (1 - segment(p, 0, 0.12)).toFixed(3);
1237
+ }, [mobile, side]);
1238
+ var skip = function skip() {
1239
+ var stage = stageRef.current;
1240
+ if (!stage) return;
1241
+ var end = window.scrollY + stage.getBoundingClientRect().bottom - innerHeight;
1242
+ scrollTo(end, {
1243
+ immediate: reduced
1244
+ });
1245
+ };
1246
+ var deviceLeft = side === "left";
1247
+ if (!armed) {
1248
+ return jsxRuntime.jsx("section", cn._objectSpread2(cn._objectSpread2({
1249
+ ref: setRef,
1250
+ "data-dt-hero": "static",
1251
+ className: cn.cn("relative", className)
1252
+ }, rest), {}, {
1253
+ children: jsxRuntime.jsxs("div", {
1254
+ className: "mx-auto grid max-w-7xl items-center gap-12 px-4 py-16 md:grid-cols-2 md:px-8",
1255
+ children: [jsxRuntime.jsx("div", {
1256
+ className: cn.cn(deviceLeft && "md:order-2"),
1257
+ children: content
1258
+ }), jsxRuntime.jsx("div", {
1259
+ className: cn.cn("flex justify-center", deviceLeft && "md:order-1"),
1260
+ children: jsxRuntime.jsx(DeviceFrame, {
1261
+ device: device,
1262
+ children: jsxRuntime.jsx(Media, {
1263
+ media: media,
1264
+ videoRef: videoRef
1265
+ })
1266
+ })
1267
+ })]
1268
+ })
1269
+ }));
1270
+ }
1271
+ return jsxRuntime.jsxs(ScrollStage, cn._objectSpread2(cn._objectSpread2({
1272
+ ref: setRef,
1273
+ "data-dt-hero": "morph",
1274
+ length: Math.min(length, MAX_LENGTH),
1275
+ mobileLength: Math.min(mobileLength, MAX_LENGTH),
1276
+ onProgress: onProgress,
1277
+ className: className
1278
+ }, rest), {}, {
1279
+ children: [jsxRuntime.jsx("div", {
1280
+ ref: layerRef,
1281
+ className: "absolute inset-0 origin-center will-change-transform",
1282
+ children: jsxRuntime.jsx(Media, {
1283
+ media: media,
1284
+ videoRef: videoRef
1285
+ })
1286
+ }), jsxRuntime.jsx("div", {
1287
+ className: "pointer-events-none absolute inset-0 flex items-center justify-center",
1288
+ children: jsxRuntime.jsx(DeviceFrame, {
1289
+ ref: frameRef,
1290
+ screenRef: screenRef,
1291
+ device: device,
1292
+ screenClassName: "bg-transparent dark:bg-transparent",
1293
+ className: "will-change-transform [--dt-chrome:0]"
1294
+ })
1295
+ }), content ? jsxRuntime.jsx("div", {
1296
+ ref: contentRef,
1297
+ // The copy stays in the accessibility tree while faded out. Tabbing into it jumps the
1298
+ // scene to its end, so a focused link is never invisible.
1299
+ onFocus: function onFocus() {
1300
+ if (contentRef.current && parseFloat(contentRef.current.style.opacity || "0") < 1) skip();
1301
+ },
1302
+ className: cn.cn("pointer-events-none absolute opacity-0", "inset-x-0 bottom-0 max-h-[45%] overflow-y-auto px-4 pb-8", "md:inset-y-0 md:bottom-auto md:flex md:max-h-none md:w-1/2 md:items-center md:px-12 md:pb-0", deviceLeft ? "md:left-auto md:right-0" : "md:left-0 md:right-auto"),
1303
+ children: jsxRuntime.jsx("div", {
1304
+ className: "w-full",
1305
+ children: content
1306
+ })
1307
+ }) : null, scrollHint !== false ? jsxRuntime.jsxs("button", {
1308
+ ref: hintRef,
1309
+ type: "button",
1310
+ onClick: skip,
1311
+ className: "absolute bottom-6 left-1/2 -translate-x-1/2 rounded-pill bg-shell/70 px-4 py-2 text-caption font-medium text-shell-foreground backdrop-blur focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-400",
1312
+ children: [scrollHint, jsxRuntime.jsx("span", {
1313
+ "aria-hidden": "true",
1314
+ className: "ml-2 inline-block motion-safe:animate-bounce",
1315
+ children: "\u2193"
1316
+ })]
1317
+ }) : null]
1318
+ }));
1319
+ });
1320
+ DeviceMorphHero.displayName = "DeviceMorphHero";
1321
+
1322
+ var _excluded$3 = ["items", "cols", "expand", "trigger", "label", "className", "style", "onMouseLeave"];
1323
+ var isImage$1 = function isImage(media) {
1324
+ return media && cn._typeof(media) === "object" && ! /*#__PURE__*/React__default.default.isValidElement(media) && "src" in media;
1325
+ };
1326
+ var tracks = function tracks(count, active, expand) {
1327
+ return Array.from({
1328
+ length: count
1329
+ }, function (_, i) {
1330
+ return i === active ? "".concat(expand, "fr") : "1fr";
1331
+ }).join(" ");
1332
+ };
1333
+ /**
1334
+ * A photo grid whose active tile grows (its row and column tracks widen) while the
1335
+ * neighbours shrink. Hover, keyboard focus and — with `trigger="click"` — tap expand a
1336
+ * tile, and arrow keys move between tiles, so it never depends on a mouse. Below `md` it
1337
+ * becomes a horizontal snap scroller. Under reduced motion the resize is instant.
1338
+ */
1339
+ var BentoExpandGrid = /*#__PURE__*/React__default.default.forwardRef(function (_ref, ref) {
1340
+ var _ref$items = _ref.items,
1341
+ items = _ref$items === void 0 ? [] : _ref$items,
1342
+ _ref$cols = _ref.cols,
1343
+ cols = _ref$cols === void 0 ? 3 : _ref$cols,
1344
+ _ref$expand = _ref.expand,
1345
+ expand = _ref$expand === void 0 ? 2 : _ref$expand,
1346
+ _ref$trigger = _ref.trigger,
1347
+ trigger = _ref$trigger === void 0 ? "hover" : _ref$trigger,
1348
+ label = _ref.label,
1349
+ className = _ref.className,
1350
+ style = _ref.style,
1351
+ _onMouseLeave = _ref.onMouseLeave,
1352
+ rest = cn._objectWithoutProperties(_ref, _excluded$3);
1353
+ var local = React.useRef(null);
1354
+ var setRef = useMergedRef(ref, local);
1355
+ var _useState = React.useState(null),
1356
+ _useState2 = cn._slicedToArray(_useState, 2),
1357
+ active = _useState2[0],
1358
+ setActive = _useState2[1];
1359
+ var rows = Math.max(1, Math.ceil(items.length / cols));
1360
+ var activeCol = active == null ? -1 : active % cols;
1361
+ var activeRow = active == null ? -1 : Math.floor(active / cols);
1362
+ var focusTile = function focusTile(index) {
1363
+ var tiles = local.current ? local.current.querySelectorAll("[data-bento-tile]") : [];
1364
+ if (tiles[index]) tiles[index].focus();
1365
+ };
1366
+ var _onKeyDown = function onKeyDown(event, index) {
1367
+ var step = {
1368
+ ArrowRight: 1,
1369
+ ArrowLeft: -1,
1370
+ ArrowDown: cols,
1371
+ ArrowUp: -cols
1372
+ }[event.key];
1373
+ if (step === undefined) {
1374
+ if (event.key === "Home") focusTile(0);else if (event.key === "End") focusTile(items.length - 1);else return;
1375
+ event.preventDefault();
1376
+ return;
1377
+ }
1378
+ var next = index + step;
1379
+ if (next >= 0 && next < items.length) {
1380
+ event.preventDefault();
1381
+ focusTile(next);
1382
+ }
1383
+ };
1384
+ return jsxRuntime.jsx("ul", cn._objectSpread2(cn._objectSpread2({
1385
+ ref: setRef,
1386
+ "aria-label": label,
1387
+ className: cn.cn("flex snap-x snap-mandatory gap-4 overflow-x-auto pb-2", "md:grid md:h-[min(80vh,720px)] md:snap-none md:overflow-visible md:pb-0", "md:transition-[grid-template-columns,grid-template-rows] md:duration-500 md:ease-emphasized motion-reduce:transition-none", className),
1388
+ style: cn._objectSpread2({
1389
+ gridTemplateColumns: tracks(cols, activeCol, expand),
1390
+ gridTemplateRows: tracks(rows, activeRow, expand)
1391
+ }, style),
1392
+ onMouseLeave: function onMouseLeave(e) {
1393
+ if (trigger === "hover") setActive(null);
1394
+ if (_onMouseLeave) _onMouseLeave(e);
1395
+ }
1396
+ }, rest), {}, {
1397
+ children: items.map(function (item, i) {
1398
+ var _item$id;
1399
+ var Tag = item.href ? "a" : trigger === "click" ? "button" : "div";
1400
+ var expanded = active === i;
1401
+ var interactive = {
1402
+ "data-bento-tile": "",
1403
+ onKeyDown: function onKeyDown(e) {
1404
+ return _onKeyDown(e, i);
1405
+ },
1406
+ onFocus: trigger !== "click" ? function () {
1407
+ return setActive(i);
1408
+ } : undefined,
1409
+ onMouseEnter: trigger === "hover" ? function () {
1410
+ return setActive(i);
1411
+ } : undefined,
1412
+ onClick: trigger === "click" ? function () {
1413
+ return setActive(expanded ? null : i);
1414
+ } : undefined
1415
+ };
1416
+ if (Tag === "a") interactive.href = item.href;
1417
+ if (Tag === "button") {
1418
+ interactive.type = "button";
1419
+ interactive["aria-pressed"] = expanded;
1420
+ }
1421
+ if (Tag === "div") interactive.tabIndex = 0;
1422
+ return jsxRuntime.jsx("li", {
1423
+ className: "w-[80%] shrink-0 snap-start sm:w-[45%] md:w-auto md:min-h-0 md:min-w-0",
1424
+ children: jsxRuntime.jsxs(Tag, cn._objectSpread2(cn._objectSpread2({}, interactive), {}, {
1425
+ "data-active": expanded ? "" : undefined,
1426
+ className: "group relative block h-72 w-full overflow-hidden rounded-card text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 md:h-full",
1427
+ children: [isImage$1(item.media) ? jsxRuntime.jsx("img", {
1428
+ src: item.media.src,
1429
+ alt: item.media.alt,
1430
+ loading: "lazy",
1431
+ className: "absolute inset-0 h-full w-full object-cover transition-transform duration-700 ease-emphasized group-hover:scale-105 group-data-[active]:scale-105 motion-reduce:transition-none motion-reduce:group-hover:scale-100"
1432
+ }) : jsxRuntime.jsx("span", {
1433
+ className: "absolute inset-0",
1434
+ children: item.media
1435
+ }), jsxRuntime.jsx("span", {
1436
+ "aria-hidden": "true",
1437
+ className: "absolute inset-0 bg-gradient-to-t from-gray-950/80 via-gray-950/20 to-transparent"
1438
+ }), jsxRuntime.jsxs("span", {
1439
+ className: "absolute inset-x-0 bottom-0 flex flex-col gap-1 p-4 text-primary-foreground",
1440
+ children: [item.tag ? jsxRuntime.jsx("span", {
1441
+ className: "w-max rounded-pill bg-primary-foreground/20 px-2.5 py-0.5 text-micro uppercase tracking-wider backdrop-blur",
1442
+ children: item.tag
1443
+ }) : null, item.client ? jsxRuntime.jsx("span", {
1444
+ className: "text-caption text-primary-foreground/80",
1445
+ children: item.client
1446
+ }) : null, jsxRuntime.jsx("span", {
1447
+ className: "text-body font-semibold",
1448
+ children: item.title
1449
+ })]
1450
+ })]
1451
+ }))
1452
+ }, (_item$id = item.id) !== null && _item$id !== void 0 ? _item$id : i);
1453
+ })
1454
+ }));
1455
+ });
1456
+ BentoExpandGrid.displayName = "BentoExpandGrid";
1457
+
1458
+ var _excluded$2 = ["items", "autoplay", "grid", "label", "className"];
1459
+ var isImage = function isImage(logo) {
1460
+ return logo && cn._typeof(logo) === "object" && ! /*#__PURE__*/React__default.default.isValidElement(logo) && "src" in logo;
1461
+ };
1462
+ function Logo(_ref) {
1463
+ var logo = _ref.logo,
1464
+ decorative = _ref.decorative;
1465
+ if (!logo) return null;
1466
+ if (isImage(logo)) {
1467
+ return jsxRuntime.jsx("img", {
1468
+ src: logo.src,
1469
+ alt: decorative ? "" : logo.alt,
1470
+ className: "max-h-10 max-w-[70%] object-contain"
1471
+ });
1472
+ }
1473
+ return decorative ? jsxRuntime.jsx("span", {
1474
+ "aria-hidden": "true",
1475
+ children: logo
1476
+ }) : logo;
1477
+ }
1478
+ /**
1479
+ * Client quotes with a 3×3 tile grid of logos: the active client's logo sits in the centre
1480
+ * tile and the quote beside it. Dots are a tab list (arrow keys, Home/End). Autoplay pauses
1481
+ * on hover, while focus is inside, while off screen and under reduced motion, and has a
1482
+ * visible pause button (WCAG 2.2.2).
1483
+ */
1484
+ var TestimonialSpotlight = /*#__PURE__*/React__default.default.forwardRef(function (_ref2, ref) {
1485
+ var _ref2$items = _ref2.items,
1486
+ items = _ref2$items === void 0 ? [] : _ref2$items,
1487
+ _ref2$autoplay = _ref2.autoplay,
1488
+ autoplay = _ref2$autoplay === void 0 ? 6000 : _ref2$autoplay,
1489
+ _ref2$grid = _ref2.grid,
1490
+ grid = _ref2$grid === void 0 ? true : _ref2$grid,
1491
+ _ref2$label = _ref2.label,
1492
+ label = _ref2$label === void 0 ? "Testimonials" : _ref2$label,
1493
+ className = _ref2.className,
1494
+ rest = cn._objectWithoutProperties(_ref2, _excluded$2);
1495
+ var local = React.useRef(null);
1496
+ var setRef = useMergedRef(ref, local);
1497
+ var reduced = useReducedMotion.useReducedMotion();
1498
+ var id = React.useId();
1499
+ var _useState = React.useState(0),
1500
+ _useState2 = cn._slicedToArray(_useState, 2),
1501
+ index = _useState2[0],
1502
+ setIndex = _useState2[1];
1503
+ var _useState3 = React.useState(false),
1504
+ _useState4 = cn._slicedToArray(_useState3, 2),
1505
+ paused = _useState4[0],
1506
+ setPaused = _useState4[1];
1507
+ var _useState5 = React.useState(false),
1508
+ _useState6 = cn._slicedToArray(_useState5, 2),
1509
+ hovered = _useState6[0],
1510
+ setHovered = _useState6[1];
1511
+ var _useState7 = React.useState(false),
1512
+ _useState8 = cn._slicedToArray(_useState7, 2),
1513
+ focused = _useState8[0],
1514
+ setFocused = _useState8[1];
1515
+ var _useState9 = React.useState(false),
1516
+ _useState0 = cn._slicedToArray(_useState9, 2),
1517
+ inView = _useState0[0],
1518
+ setInView = _useState0[1];
1519
+ var count = items.length;
1520
+ var playing = autoplay > 0 && count > 1 && !reduced && !paused && !hovered && !focused && inView;
1521
+ React.useEffect(function () {
1522
+ return observe(local.current, setInView);
1523
+ }, []);
1524
+ React.useEffect(function () {
1525
+ if (!playing) return undefined;
1526
+ var timer = setTimeout(function () {
1527
+ return setIndex(function (i) {
1528
+ return (i + 1) % count;
1529
+ });
1530
+ }, autoplay);
1531
+ return function () {
1532
+ return clearTimeout(timer);
1533
+ };
1534
+ }, [playing, index, autoplay, count]);
1535
+ if (!count) return null;
1536
+ var current = items[Math.min(index, count - 1)];
1537
+ var tabs = items.map(function (_, i) {
1538
+ return "".concat(id, "-tab-").concat(i);
1539
+ });
1540
+ var onTabKey = function onTabKey(event) {
1541
+ var map = {
1542
+ ArrowRight: index + 1,
1543
+ ArrowDown: index + 1,
1544
+ ArrowLeft: index - 1,
1545
+ ArrowUp: index - 1,
1546
+ Home: 0,
1547
+ End: count - 1
1548
+ };
1549
+ if (!(event.key in map)) return;
1550
+ event.preventDefault();
1551
+ var next = (map[event.key] + count) % count;
1552
+ setIndex(next);
1553
+ var el = document.getElementById(tabs[next]);
1554
+ if (el) el.focus();
1555
+ };
1556
+ return jsxRuntime.jsxs("section", cn._objectSpread2(cn._objectSpread2({
1557
+ ref: setRef,
1558
+ "aria-roledescription": "carousel",
1559
+ "aria-label": label,
1560
+ className: cn.cn("grid items-center gap-10 md:grid-cols-2", className),
1561
+ onMouseEnter: function onMouseEnter() {
1562
+ return setHovered(true);
1563
+ },
1564
+ onMouseLeave: function onMouseLeave() {
1565
+ return setHovered(false);
1566
+ },
1567
+ onFocus: function onFocus() {
1568
+ return setFocused(true);
1569
+ },
1570
+ onBlur: function onBlur(e) {
1571
+ if (!e.currentTarget.contains(e.relatedTarget)) setFocused(false);
1572
+ }
1573
+ }, rest), {}, {
1574
+ children: [grid ? jsxRuntime.jsx("div", {
1575
+ className: "grid aspect-square grid-cols-3 gap-3",
1576
+ children: Array.from({
1577
+ length: 9
1578
+ }, function (_, tile) {
1579
+ var centre = tile === 4;
1580
+ var other = items[(index + tile + 1) % count];
1581
+ return jsxRuntime.jsx("div", {
1582
+ className: cn.cn("flex items-center justify-center rounded-card border border-line", centre ? "bg-surface shadow-md dark:bg-background" : "bg-background"),
1583
+ children: centre ? jsxRuntime.jsx("span", {
1584
+ className: "flex w-full justify-center animate-fade-in motion-reduce:animate-none",
1585
+ children: jsxRuntime.jsx(Logo, {
1586
+ logo: current.logo
1587
+ })
1588
+ }, index) : tile % 2 === 0 ? jsxRuntime.jsx("span", {
1589
+ className: "flex w-full justify-center opacity-30 grayscale",
1590
+ "aria-hidden": "true",
1591
+ children: jsxRuntime.jsx(Logo, {
1592
+ logo: other.logo,
1593
+ decorative: true
1594
+ })
1595
+ }) : null
1596
+ }, tile);
1597
+ })
1598
+ }) : null, jsxRuntime.jsxs("div", {
1599
+ className: "flex flex-col gap-6",
1600
+ children: [jsxRuntime.jsxs("figure", {
1601
+ id: "".concat(id, "-panel"),
1602
+ role: "tabpanel",
1603
+ "aria-labelledby": tabs[index],
1604
+ "aria-live": playing ? "off" : "polite",
1605
+ className: "flex flex-col gap-4 animate-fade-in motion-reduce:animate-none",
1606
+ children: [jsxRuntime.jsx("blockquote", {
1607
+ className: "text-section-title font-medium leading-snug text-foreground",
1608
+ children: current.quote
1609
+ }), jsxRuntime.jsxs("figcaption", {
1610
+ className: "text-body text-muted",
1611
+ children: [jsxRuntime.jsx("span", {
1612
+ className: "font-semibold text-foreground",
1613
+ children: current.name
1614
+ }), current.role ? jsxRuntime.jsxs("span", {
1615
+ children: [", ", current.role]
1616
+ }) : null]
1617
+ })]
1618
+ }, index), count > 1 ? jsxRuntime.jsxs("div", {
1619
+ className: "flex items-center gap-4",
1620
+ children: [jsxRuntime.jsx("div", {
1621
+ role: "tablist",
1622
+ "aria-label": "".concat(label, ": choose a quote"),
1623
+ className: "flex gap-2",
1624
+ onKeyDown: onTabKey,
1625
+ children: items.map(function (item, i) {
1626
+ var _item$id;
1627
+ return jsxRuntime.jsx("button", {
1628
+ id: tabs[i],
1629
+ type: "button",
1630
+ role: "tab",
1631
+ "aria-selected": i === index,
1632
+ "aria-controls": "".concat(id, "-panel"),
1633
+ tabIndex: i === index ? 0 : -1,
1634
+ "aria-label": "Quote ".concat(i + 1, " of ").concat(count),
1635
+ onClick: function onClick() {
1636
+ return setIndex(i);
1637
+ },
1638
+ className: cn.cn("h-2.5 rounded-pill transition-[width,background-color] duration-base motion-reduce:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2", i === index ? "w-6 bg-primary-600" : "w-2.5 bg-line hover:bg-muted/60")
1639
+ }, (_item$id = item.id) !== null && _item$id !== void 0 ? _item$id : i);
1640
+ })
1641
+ }), autoplay > 0 && !reduced ? jsxRuntime.jsx("button", {
1642
+ type: "button",
1643
+ onClick: function onClick() {
1644
+ return setPaused(function (p) {
1645
+ return !p;
1646
+ });
1647
+ },
1648
+ className: "rounded-pill px-2 py-1 text-caption text-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500",
1649
+ children: paused ? "Play" : "Pause"
1650
+ }) : null]
1651
+ }) : null]
1652
+ })]
1653
+ }));
1654
+ });
1655
+ TestimonialSpotlight.displayName = "TestimonialSpotlight";
1656
+
1657
+ var _excluded$1 = ["images", "chips", "parallax", "badge", "className", "style"];
1658
+ // Placement and parallax depth (sign = direction) for up to four photos.
1659
+ var SLOTS = [{
1660
+ className: "left-0 top-0 w-[64%] aspect-[3/4]",
1661
+ depth: 1
1662
+ }, {
1663
+ className: "right-0 bottom-0 w-[52%] aspect-square",
1664
+ depth: -0.7
1665
+ }, {
1666
+ className: "right-[4%] top-[6%] w-[32%] aspect-[4/5]",
1667
+ depth: 0.5
1668
+ }, {
1669
+ className: "left-[10%] bottom-[2%] w-[28%] aspect-square",
1670
+ depth: -0.4
1671
+ }];
1672
+ // Paint order (back to front) by slot index: later positioned siblings stack on top, so no
1673
+ // z-index is needed. Chips and the badge render after the photos and sit above them all.
1674
+ var PAINT_ORDER = [2, 0, 1, 3];
1675
+ var CHIP_SLOTS = ["left-[48%] top-[38%]", "left-[2%] bottom-[30%]"];
1676
+ /**
1677
+ * Overlapping, offset photos with floating stat chips and an optional badge. On scroll the
1678
+ * photos drift at different depths (parallax), driven by a CSS variable — no re-renders.
1679
+ * Under reduced motion (or `parallax={0}`) they stay put.
1680
+ */
1681
+ var PhotoCollage = /*#__PURE__*/React__default.default.forwardRef(function (_ref, ref) {
1682
+ var _ref$images = _ref.images,
1683
+ images = _ref$images === void 0 ? [] : _ref$images,
1684
+ _ref$chips = _ref.chips,
1685
+ chips = _ref$chips === void 0 ? [] : _ref$chips,
1686
+ _ref$parallax = _ref.parallax,
1687
+ parallax = _ref$parallax === void 0 ? 0.15 : _ref$parallax,
1688
+ badge = _ref.badge,
1689
+ className = _ref.className,
1690
+ style = _ref.style,
1691
+ rest = cn._objectWithoutProperties(_ref, _excluded$1);
1692
+ var local = React.useRef(null);
1693
+ var setRef = useMergedRef(ref, local);
1694
+ var reduced = useReducedMotion.useReducedMotion();
1695
+ useScrollProgress(local, {
1696
+ enabled: parallax > 0 && !reduced
1697
+ });
1698
+ return jsxRuntime.jsxs("div", cn._objectSpread2(cn._objectSpread2({
1699
+ ref: setRef,
1700
+ className: cn.cn("relative isolate mx-auto aspect-[5/6] w-full max-w-xl", className),
1701
+ style: cn._objectSpread2({
1702
+ "--dt-parallax": parallax * 240
1703
+ }, style)
1704
+ }, rest), {}, {
1705
+ children: [PAINT_ORDER.filter(function (i) {
1706
+ return i < images.length;
1707
+ }).map(function (i) {
1708
+ return jsxRuntime.jsx("figure", {
1709
+ className: cn.cn("absolute overflow-hidden rounded-card shadow-xl ring-4 ring-surface dark:ring-background", "motion-safe:[transform:translate3d(0,calc((var(--dt-p,0.5)_-_0.5)_*_var(--dt-parallax)_*_var(--dt-depth)_*_1px),0)]", SLOTS[i].className),
1710
+ style: {
1711
+ "--dt-depth": SLOTS[i].depth
1712
+ },
1713
+ children: jsxRuntime.jsx("img", {
1714
+ src: images[i].src,
1715
+ alt: images[i].alt,
1716
+ loading: "lazy",
1717
+ className: "h-full w-full object-cover"
1718
+ })
1719
+ }, i);
1720
+ }), chips.slice(0, CHIP_SLOTS.length).map(function (chip, i) {
1721
+ return jsxRuntime.jsxs("div", {
1722
+ className: cn.cn("absolute flex flex-col rounded-card border border-line bg-surface/90 px-4 py-3 shadow-lg backdrop-blur dark:bg-background/90", CHIP_SLOTS[i]),
1723
+ children: [jsxRuntime.jsx("span", {
1724
+ className: "text-section-title font-semibold leading-none text-foreground",
1725
+ children: chip.value
1726
+ }), jsxRuntime.jsx("span", {
1727
+ className: "mt-1 text-micro uppercase tracking-wider text-muted",
1728
+ children: chip.label
1729
+ })]
1730
+ }, i);
1731
+ }), badge ? jsxRuntime.jsx("div", {
1732
+ className: "absolute -right-2 -top-4 md:-right-6",
1733
+ children: badge
1734
+ }) : null]
1735
+ }));
1736
+ });
1737
+ PhotoCollage.displayName = "PhotoCollage";
1738
+
1739
+ var _excluded = ["text", "speed", "size", "icon", "className", "style"];
1740
+ var RADIUS = 38;
1741
+ var CIRCUMFERENCE = 2 * Math.PI * RADIUS;
1742
+ /**
1743
+ * A circular badge with text set around its edge, slowly turning (SVG `textPath`). The
1744
+ * text is spread to fill the circle exactly. Exposed as one image named by `text`
1745
+ * (override with `aria-label`); it stops turning under reduced motion.
1746
+ */
1747
+ var RotatingBadge = /*#__PURE__*/React__default.default.forwardRef(function (_ref, ref) {
1748
+ var text = _ref.text,
1749
+ _ref$speed = _ref.speed,
1750
+ speed = _ref$speed === void 0 ? 14 : _ref$speed,
1751
+ _ref$size = _ref.size,
1752
+ size = _ref$size === void 0 ? 120 : _ref$size,
1753
+ icon = _ref.icon,
1754
+ className = _ref.className,
1755
+ style = _ref.style,
1756
+ rest = cn._objectWithoutProperties(_ref, _excluded);
1757
+ var pathId = "dt-badge-".concat(React.useId().replace(/:/g, ""));
1758
+ return jsxRuntime.jsxs("div", cn._objectSpread2(cn._objectSpread2({
1759
+ ref: ref,
1760
+ role: "img",
1761
+ "aria-label": text,
1762
+ className: cn.cn("relative inline-flex shrink-0 items-center justify-center rounded-full text-foreground", className),
1763
+ style: cn._objectSpread2({
1764
+ width: size,
1765
+ height: size
1766
+ }, style)
1767
+ }, rest), {}, {
1768
+ children: [jsxRuntime.jsxs("svg", {
1769
+ viewBox: "0 0 100 100",
1770
+ "aria-hidden": "true",
1771
+ className: "absolute inset-0 h-full w-full animate-spin motion-reduce:animate-none",
1772
+ style: {
1773
+ animationDuration: "".concat(speed, "s")
1774
+ },
1775
+ children: [jsxRuntime.jsx("defs", {
1776
+ children: jsxRuntime.jsx("path", {
1777
+ id: pathId,
1778
+ d: "M 50,50 m -".concat(RADIUS, ",0 a ").concat(RADIUS, ",").concat(RADIUS, " 0 1,1 ").concat(RADIUS * 2, ",0 a ").concat(RADIUS, ",").concat(RADIUS, " 0 1,1 -").concat(RADIUS * 2, ",0")
1779
+ })
1780
+ }), jsxRuntime.jsx("text", {
1781
+ className: "fill-current text-[8.5px] font-semibold uppercase",
1782
+ children: jsxRuntime.jsx("textPath", {
1783
+ href: "#".concat(pathId),
1784
+ textLength: CIRCUMFERENCE - 2,
1785
+ lengthAdjust: "spacing",
1786
+ children: text
1787
+ })
1788
+ })]
1789
+ }), icon ? jsxRuntime.jsx("span", {
1790
+ "aria-hidden": "true",
1791
+ className: "relative",
1792
+ children: icon
1793
+ }) : null]
1794
+ }));
1795
+ });
1796
+ RotatingBadge.displayName = "RotatingBadge";
1797
+
1798
+ exports.BentoExpandGrid = BentoExpandGrid;
1799
+ exports.CountUp = CountUp;
1800
+ exports.DeviceFrame = DeviceFrame;
1801
+ exports.DeviceMorphHero = DeviceMorphHero;
1802
+ exports.LogoMarquee = LogoMarquee;
1803
+ exports.PhotoCollage = PhotoCollage;
1804
+ exports.Reveal = Reveal;
1805
+ exports.RevealGroup = RevealGroup;
1806
+ exports.RotatingBadge = RotatingBadge;
1807
+ exports.ScrollStage = ScrollStage;
1808
+ exports.SmoothScrollProvider = SmoothScrollProvider;
1809
+ exports.StackedCards = StackedCards;
1810
+ exports.StatStrip = StatStrip;
1811
+ exports.TestimonialSpotlight = TestimonialSpotlight;
1812
+ exports.useScrollProgress = useScrollProgress;
1813
+ exports.useSmoothScroll = useSmoothScroll;