@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.
- package/README.md +47 -4
- package/ai-skills/dreamtree-ui.md +20 -3
- package/dist/_shared/{cn-DPPQUJu1.js → cn-TMJ-_w1a.js} +14 -1
- package/dist/_shared/useLatestRef-CdtlnPUz.js +21 -0
- package/dist/_shared/useReducedMotion-B_PMM5ER.js +77 -0
- package/dist/ai-schema.json +513 -0
- package/dist/charts/index.js +6 -76
- package/dist/components/motion/BentoExpandGrid.d.ts +10 -0
- package/dist/components/motion/BentoExpandGrid.d.ts.map +1 -0
- package/dist/components/motion/CountUp.d.ts +9 -0
- package/dist/components/motion/CountUp.d.ts.map +1 -0
- package/dist/components/motion/DeviceFrame.d.ts +8 -0
- package/dist/components/motion/DeviceFrame.d.ts.map +1 -0
- package/dist/components/motion/DeviceMorphHero.d.ts +11 -0
- package/dist/components/motion/DeviceMorphHero.d.ts.map +1 -0
- package/dist/components/motion/LogoMarquee.d.ts +9 -0
- package/dist/components/motion/LogoMarquee.d.ts.map +1 -0
- package/dist/components/motion/PhotoCollage.d.ts +9 -0
- package/dist/components/motion/PhotoCollage.d.ts.map +1 -0
- package/dist/components/motion/Reveal.d.ts +9 -0
- package/dist/components/motion/Reveal.d.ts.map +1 -0
- package/dist/components/motion/RevealGroup.d.ts +5 -0
- package/dist/components/motion/RevealGroup.d.ts.map +1 -0
- package/dist/components/motion/RotatingBadge.d.ts +9 -0
- package/dist/components/motion/RotatingBadge.d.ts.map +1 -0
- package/dist/components/motion/ScrollStage.d.ts +11 -0
- package/dist/components/motion/ScrollStage.d.ts.map +1 -0
- package/dist/components/motion/SmoothScrollProvider.d.ts +18 -0
- package/dist/components/motion/SmoothScrollProvider.d.ts.map +1 -0
- package/dist/components/motion/StackedCards.d.ts +10 -0
- package/dist/components/motion/StackedCards.d.ts.map +1 -0
- package/dist/components/motion/StatStrip.d.ts +8 -0
- package/dist/components/motion/StatStrip.d.ts.map +1 -0
- package/dist/components/motion/TestimonialSpotlight.d.ts +10 -0
- package/dist/components/motion/TestimonialSpotlight.d.ts.map +1 -0
- package/dist/components/motion/storyData.d.ts +52 -0
- package/dist/components/motion/storyData.d.ts.map +1 -0
- package/dist/esm/components/motion/BentoExpandGrid.js +144 -0
- package/dist/esm/components/motion/CountUp.js +97 -0
- package/dist/esm/components/motion/DeviceFrame.js +102 -0
- package/dist/esm/components/motion/DeviceMorphHero.js +228 -0
- package/dist/esm/components/motion/LogoMarquee.js +144 -0
- package/dist/esm/components/motion/PhotoCollage.js +92 -0
- package/dist/esm/components/motion/Reveal.js +115 -0
- package/dist/esm/components/motion/RevealGroup.js +43 -0
- package/dist/esm/components/motion/RotatingBadge.js +66 -0
- package/dist/esm/components/motion/ScrollStage.js +62 -0
- package/dist/esm/components/motion/SmoothScrollProvider.js +63 -0
- package/dist/esm/components/motion/StackedCards.js +166 -0
- package/dist/esm/components/motion/StatStrip.js +58 -0
- package/dist/esm/components/motion/TestimonialSpotlight.js +209 -0
- package/dist/esm/hooks/useScrollProgress.js +107 -0
- package/dist/esm/hooks/useSmoothScroll.js +33 -0
- package/dist/esm/motion/engine/inView.js +54 -0
- package/dist/esm/motion/engine/progress.js +45 -0
- package/dist/esm/motion/engine/scheduler.js +67 -0
- package/dist/esm/motion/engine/useHydrated.js +17 -0
- package/dist/esm/motion/engine/useIsomorphicLayoutEffect.js +7 -0
- package/dist/esm/motion/engine/useMergedRef.js +12 -0
- package/dist/esm/motion/index.js +16 -0
- package/dist/esm/motion/tokens.js +10 -0
- package/dist/hooks/useScrollProgress.d.ts +17 -0
- package/dist/hooks/useScrollProgress.d.ts.map +1 -0
- package/dist/hooks/useSmoothScroll.d.ts +11 -0
- package/dist/hooks/useSmoothScroll.d.ts.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.esm.css +1 -1
- package/dist/motion/engine/inView.d.ts +5 -0
- package/dist/motion/engine/inView.d.ts.map +1 -0
- package/dist/motion/engine/progress.d.ts +18 -0
- package/dist/motion/engine/progress.d.ts.map +1 -0
- package/dist/motion/engine/scheduler.d.ts +10 -0
- package/dist/motion/engine/scheduler.d.ts.map +1 -0
- package/dist/motion/engine/useHydrated.d.ts +3 -0
- package/dist/motion/engine/useHydrated.d.ts.map +1 -0
- package/dist/motion/engine/useIsomorphicLayoutEffect.d.ts +4 -0
- package/dist/motion/engine/useIsomorphicLayoutEffect.d.ts.map +1 -0
- package/dist/motion/engine/useMergedRef.d.ts +3 -0
- package/dist/motion/engine/useMergedRef.d.ts.map +1 -0
- package/dist/motion/index.d.ts +20 -0
- package/dist/motion/index.d.ts.map +1 -0
- package/dist/motion/index.js +1813 -0
- package/dist/motion/tokens.d.ts +6 -0
- package/dist/motion/tokens.d.ts.map +1 -0
- package/dist/rich-text/index.js +3 -18
- package/dist/tokens/tokens.json +21 -0
- package/dist/types/motion.d.ts +290 -0
- package/dist/types/motion.d.ts.map +1 -0
- package/doc/README.md +17 -1
- package/mcp/catalog.snapshot.json +952 -5
- package/motion/package.json +5 -0
- package/package.json +14 -3
- 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;
|