@dreamtree-org/twreact-ui 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +48 -4
  2. package/ai-skills/dreamtree-ui.md +20 -3
  3. package/dist/_shared/{cn-DPPQUJu1.js → cn-TMJ-_w1a.js} +14 -1
  4. package/dist/_shared/useLatestRef-CdtlnPUz.js +21 -0
  5. package/dist/_shared/useReducedMotion-B_PMM5ER.js +77 -0
  6. package/dist/ai-schema.json +513 -0
  7. package/dist/charts/index.js +6 -76
  8. package/dist/components/core/Table/Table.d.ts.map +1 -1
  9. package/dist/components/motion/BentoExpandGrid.d.ts +10 -0
  10. package/dist/components/motion/BentoExpandGrid.d.ts.map +1 -0
  11. package/dist/components/motion/CountUp.d.ts +9 -0
  12. package/dist/components/motion/CountUp.d.ts.map +1 -0
  13. package/dist/components/motion/DeviceFrame.d.ts +8 -0
  14. package/dist/components/motion/DeviceFrame.d.ts.map +1 -0
  15. package/dist/components/motion/DeviceMorphHero.d.ts +11 -0
  16. package/dist/components/motion/DeviceMorphHero.d.ts.map +1 -0
  17. package/dist/components/motion/LogoMarquee.d.ts +9 -0
  18. package/dist/components/motion/LogoMarquee.d.ts.map +1 -0
  19. package/dist/components/motion/PhotoCollage.d.ts +9 -0
  20. package/dist/components/motion/PhotoCollage.d.ts.map +1 -0
  21. package/dist/components/motion/Reveal.d.ts +9 -0
  22. package/dist/components/motion/Reveal.d.ts.map +1 -0
  23. package/dist/components/motion/RevealGroup.d.ts +5 -0
  24. package/dist/components/motion/RevealGroup.d.ts.map +1 -0
  25. package/dist/components/motion/RotatingBadge.d.ts +9 -0
  26. package/dist/components/motion/RotatingBadge.d.ts.map +1 -0
  27. package/dist/components/motion/ScrollStage.d.ts +11 -0
  28. package/dist/components/motion/ScrollStage.d.ts.map +1 -0
  29. package/dist/components/motion/SmoothScrollProvider.d.ts +18 -0
  30. package/dist/components/motion/SmoothScrollProvider.d.ts.map +1 -0
  31. package/dist/components/motion/StackedCards.d.ts +10 -0
  32. package/dist/components/motion/StackedCards.d.ts.map +1 -0
  33. package/dist/components/motion/StatStrip.d.ts +8 -0
  34. package/dist/components/motion/StatStrip.d.ts.map +1 -0
  35. package/dist/components/motion/TestimonialSpotlight.d.ts +10 -0
  36. package/dist/components/motion/TestimonialSpotlight.d.ts.map +1 -0
  37. package/dist/components/motion/storyData.d.ts +52 -0
  38. package/dist/components/motion/storyData.d.ts.map +1 -0
  39. package/dist/components/navigation/Navbar.d.ts.map +1 -1
  40. package/dist/esm/components/core/Table/MobileCard.js +2 -2
  41. package/dist/esm/components/core/Table/MobileFilters.js +1 -1
  42. package/dist/esm/components/core/Table/Table.js +28 -26
  43. package/dist/esm/components/motion/BentoExpandGrid.js +144 -0
  44. package/dist/esm/components/motion/CountUp.js +97 -0
  45. package/dist/esm/components/motion/DeviceFrame.js +102 -0
  46. package/dist/esm/components/motion/DeviceMorphHero.js +228 -0
  47. package/dist/esm/components/motion/LogoMarquee.js +144 -0
  48. package/dist/esm/components/motion/PhotoCollage.js +92 -0
  49. package/dist/esm/components/motion/Reveal.js +115 -0
  50. package/dist/esm/components/motion/RevealGroup.js +43 -0
  51. package/dist/esm/components/motion/RotatingBadge.js +66 -0
  52. package/dist/esm/components/motion/ScrollStage.js +62 -0
  53. package/dist/esm/components/motion/SmoothScrollProvider.js +63 -0
  54. package/dist/esm/components/motion/StackedCards.js +166 -0
  55. package/dist/esm/components/motion/StatStrip.js +58 -0
  56. package/dist/esm/components/motion/TestimonialSpotlight.js +209 -0
  57. package/dist/esm/components/navigation/Navbar.js +5 -2
  58. package/dist/esm/hooks/useScrollProgress.js +107 -0
  59. package/dist/esm/hooks/useSmoothScroll.js +33 -0
  60. package/dist/esm/motion/engine/inView.js +54 -0
  61. package/dist/esm/motion/engine/progress.js +45 -0
  62. package/dist/esm/motion/engine/scheduler.js +67 -0
  63. package/dist/esm/motion/engine/useHydrated.js +17 -0
  64. package/dist/esm/motion/engine/useIsomorphicLayoutEffect.js +7 -0
  65. package/dist/esm/motion/engine/useMergedRef.js +12 -0
  66. package/dist/esm/motion/index.js +16 -0
  67. package/dist/esm/motion/tokens.js +10 -0
  68. package/dist/hooks/useScrollProgress.d.ts +17 -0
  69. package/dist/hooks/useScrollProgress.d.ts.map +1 -0
  70. package/dist/hooks/useSmoothScroll.d.ts +11 -0
  71. package/dist/hooks/useSmoothScroll.d.ts.map +1 -0
  72. package/dist/index.css +1 -1
  73. package/dist/index.esm.css +1 -1
  74. package/dist/index.esm.js +36 -31
  75. package/dist/index.js +36 -31
  76. package/dist/motion/engine/inView.d.ts +5 -0
  77. package/dist/motion/engine/inView.d.ts.map +1 -0
  78. package/dist/motion/engine/progress.d.ts +18 -0
  79. package/dist/motion/engine/progress.d.ts.map +1 -0
  80. package/dist/motion/engine/scheduler.d.ts +10 -0
  81. package/dist/motion/engine/scheduler.d.ts.map +1 -0
  82. package/dist/motion/engine/useHydrated.d.ts +3 -0
  83. package/dist/motion/engine/useHydrated.d.ts.map +1 -0
  84. package/dist/motion/engine/useIsomorphicLayoutEffect.d.ts +4 -0
  85. package/dist/motion/engine/useIsomorphicLayoutEffect.d.ts.map +1 -0
  86. package/dist/motion/engine/useMergedRef.d.ts +3 -0
  87. package/dist/motion/engine/useMergedRef.d.ts.map +1 -0
  88. package/dist/motion/index.d.ts +20 -0
  89. package/dist/motion/index.d.ts.map +1 -0
  90. package/dist/motion/index.js +1813 -0
  91. package/dist/motion/tokens.d.ts +6 -0
  92. package/dist/motion/tokens.d.ts.map +1 -0
  93. package/dist/rich-text/index.js +3 -18
  94. package/dist/tokens/tokens.json +21 -0
  95. package/dist/types/motion.d.ts +290 -0
  96. package/dist/types/motion.d.ts.map +1 -0
  97. package/doc/README.md +17 -1
  98. package/mcp/catalog.snapshot.json +959 -12
  99. package/motion/package.json +5 -0
  100. package/package.json +14 -3
  101. package/tokens/tokens.json +6 -0
@@ -0,0 +1,102 @@
1
+ 'use client';
2
+ import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
+ import React from 'react';
5
+ import { cn } from '../../utils/cn.js';
6
+
7
+ var _excluded = ["device", "url", "screenClassName", "screenRef", "className", "children"];
8
+ // Bezel parts read `--dt-chrome` (opacity, default 1) so DeviceMorphHero can fade them in.
9
+ var CHROME = "opacity-[var(--dt-chrome,1)]";
10
+ // Bezels are borders (not filled boxes) so the screen area stays see-through: DeviceMorphHero
11
+ // slides its full-bleed media layer under a transparent screen.
12
+ var FRAMES = {
13
+ phone: {
14
+ root: "w-[220px] md:w-[262px] aspect-[262/562]",
15
+ bezel: "inset-0 rounded-[2.75rem] md:rounded-[3.25rem] border-[10px] border-shell shadow-2xl",
16
+ screen: "inset-[10px] rounded-[2.1rem] md:rounded-[2.6rem]",
17
+ extras: jsxs(Fragment, {
18
+ children: [jsx("span", {
19
+ "aria-hidden": "true",
20
+ className: cn("absolute left-1/2 top-[18px] h-[22px] w-[30%] -translate-x-1/2 rounded-pill bg-shell", CHROME)
21
+ }), jsx("span", {
22
+ "aria-hidden": "true",
23
+ className: cn("absolute bottom-[16px] left-1/2 h-[4px] w-[34%] -translate-x-1/2 rounded-pill bg-shell-foreground/70", CHROME)
24
+ }), jsx("span", {
25
+ "aria-hidden": "true",
26
+ className: cn("absolute -left-[3px] top-[22%] h-[9%] w-[3px] rounded-l bg-shell-active", CHROME)
27
+ }), jsx("span", {
28
+ "aria-hidden": "true",
29
+ className: cn("absolute -right-[3px] top-[28%] h-[14%] w-[3px] rounded-r bg-shell-active", CHROME)
30
+ })]
31
+ })
32
+ },
33
+ tablet: {
34
+ root: "w-[320px] md:w-[440px] aspect-[3/4]",
35
+ bezel: "inset-0 rounded-[2rem] border-[14px] border-shell shadow-2xl",
36
+ screen: "inset-[14px] rounded-[1rem]",
37
+ extras: jsx("span", {
38
+ "aria-hidden": "true",
39
+ className: cn("absolute left-1/2 top-[5px] h-[4px] w-[4px] -translate-x-1/2 rounded-full bg-shell-active", CHROME)
40
+ })
41
+ },
42
+ laptop: {
43
+ root: "w-[340px] md:w-[640px] aspect-[16/10.6]",
44
+ bezel: "inset-x-0 top-0 bottom-[4%] rounded-t-[1.25rem] border-[10px] border-b-[22px] border-shell shadow-2xl",
45
+ screen: "left-[10px] right-[10px] top-[10px] bottom-[calc(4%+22px)] rounded-[0.25rem]",
46
+ extras: jsx("span", {
47
+ "aria-hidden": "true",
48
+ className: cn("absolute -inset-x-[6%] bottom-0 h-[4%] rounded-b-[1rem] bg-line shadow-lg", CHROME)
49
+ })
50
+ },
51
+ browser: {
52
+ root: "w-[340px] md:w-[720px] aspect-[16/10.5]",
53
+ bezel: "inset-0 rounded-card border border-t-[36px] border-line border-t-gray-100 shadow-2xl dark:border-t-gray-800",
54
+ screen: "inset-x-px bottom-px top-[36px] rounded-b-card",
55
+ extras: null
56
+ }
57
+ };
58
+ /**
59
+ * A CSS-only device mock-up (phone, tablet, laptop or browser window) around any
60
+ * screen content. Purely presentational: no motion, works anywhere.
61
+ */
62
+ var DeviceFrame = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
63
+ var _ref$device = _ref.device,
64
+ device = _ref$device === void 0 ? "phone" : _ref$device,
65
+ url = _ref.url,
66
+ screenClassName = _ref.screenClassName,
67
+ screenRef = _ref.screenRef,
68
+ className = _ref.className,
69
+ children = _ref.children,
70
+ rest = _objectWithoutProperties(_ref, _excluded);
71
+ var f = FRAMES[device] || FRAMES.phone;
72
+ return jsxs("div", _objectSpread2(_objectSpread2({
73
+ ref: ref,
74
+ "data-device": device,
75
+ className: cn("relative shrink-0", f.root, className)
76
+ }, rest), {}, {
77
+ children: [jsx("div", {
78
+ "aria-hidden": "true",
79
+ className: cn("pointer-events-none absolute", f.bezel, CHROME)
80
+ }), device === "browser" ? jsxs("div", {
81
+ "aria-hidden": "true",
82
+ className: cn("absolute inset-x-0 top-0 flex h-[36px] items-center gap-1.5 px-3", CHROME),
83
+ children: [jsx("span", {
84
+ className: "h-2.5 w-2.5 rounded-full bg-error-400"
85
+ }), jsx("span", {
86
+ className: "h-2.5 w-2.5 rounded-full bg-warning-400"
87
+ }), jsx("span", {
88
+ className: "h-2.5 w-2.5 rounded-full bg-success-400"
89
+ }), url ? jsx("span", {
90
+ className: "ml-3 flex-1 truncate rounded-pill bg-surface px-3 py-0.5 text-micro text-muted dark:bg-background",
91
+ children: url
92
+ }) : null]
93
+ }) : null, jsx("div", {
94
+ ref: screenRef,
95
+ className: cn("absolute overflow-hidden bg-surface dark:bg-background", f.screen, screenClassName),
96
+ children: children
97
+ }), f.extras]
98
+ }));
99
+ });
100
+ DeviceFrame.displayName = "DeviceFrame";
101
+
102
+ export { DeviceFrame as default };
@@ -0,0 +1,228 @@
1
+ 'use client';
2
+ import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+ import React, { useRef, useEffect, useCallback } from 'react';
5
+ import { cn } from '../../utils/cn.js';
6
+ import { useReducedMotion } from '../../hooks/useReducedMotion.js';
7
+ import { useMediaQuery } from '../../hooks/useMediaQuery.js';
8
+ import { observe } from '../../motion/engine/inView.js';
9
+ import { lerp, segment, easeInOut, easeOut } from '../../motion/engine/progress.js';
10
+ import { useMergedRef } from '../../motion/engine/useMergedRef.js';
11
+ import { useHydrated } from '../../motion/engine/useHydrated.js';
12
+ import { MOBILE_QUERY, SCENE_LENGTH_MOBILE, SCENE_LENGTH } from '../../motion/tokens.js';
13
+ import ScrollStage from './ScrollStage.js';
14
+ import DeviceFrame from './DeviceFrame.js';
15
+ import { useSmoothScroll } from '../../hooks/useSmoothScroll.js';
16
+
17
+ var _excluded = ["media", "device", "length", "mobileLength", "side", "content", "scrollHint", "className"];
18
+ var MAX_LENGTH = 400;
19
+ var COVER = "h-full w-full object-cover";
20
+ function Media(_ref) {
21
+ var media = _ref.media,
22
+ videoRef = _ref.videoRef;
23
+ var decorative = !media.alt;
24
+ if (media.video) {
25
+ return jsx("video", {
26
+ ref: videoRef,
27
+ className: COVER,
28
+ src: media.video,
29
+ poster: media.poster,
30
+ muted: true,
31
+ loop: true,
32
+ autoPlay: true,
33
+ playsInline: true,
34
+ preload: "metadata",
35
+ "aria-label": decorative ? undefined : media.alt,
36
+ "aria-hidden": decorative ? "true" : undefined
37
+ });
38
+ }
39
+ return jsx("img", {
40
+ className: COVER,
41
+ src: media.image || media.poster,
42
+ alt: media.alt || ""
43
+ });
44
+ }
45
+ /**
46
+ * The signature scroll hero: full-bleed media shrinks into a device frame, the bezel fades
47
+ * in, then the device slides aside while the copy (`content`) fades in beside it. The
48
+ * timeline maps the scene's scroll progress: 0–45 % morph, 40–60 % bezel, 60–90 % slide.
49
+ * The server, no-JS readers and reduced-motion users get the final layout (copy beside the
50
+ * device) with no pinning. Below `md` the device rises and shrinks and the copy appears under it.
51
+ */
52
+ var DeviceMorphHero = /*#__PURE__*/React.forwardRef(function (_ref2, ref) {
53
+ var media = _ref2.media,
54
+ _ref2$device = _ref2.device,
55
+ device = _ref2$device === void 0 ? "phone" : _ref2$device,
56
+ _ref2$length = _ref2.length,
57
+ length = _ref2$length === void 0 ? SCENE_LENGTH : _ref2$length,
58
+ _ref2$mobileLength = _ref2.mobileLength,
59
+ mobileLength = _ref2$mobileLength === void 0 ? SCENE_LENGTH_MOBILE : _ref2$mobileLength,
60
+ _ref2$side = _ref2.side,
61
+ side = _ref2$side === void 0 ? "right" : _ref2$side,
62
+ content = _ref2.content,
63
+ _ref2$scrollHint = _ref2.scrollHint,
64
+ scrollHint = _ref2$scrollHint === void 0 ? "Scroll to explore" : _ref2$scrollHint,
65
+ className = _ref2.className,
66
+ rest = _objectWithoutProperties(_ref2, _excluded);
67
+ var reduced = useReducedMotion();
68
+ var mobile = useMediaQuery(MOBILE_QUERY);
69
+ var armed = useHydrated() && !reduced;
70
+ var stageRef = useRef(null);
71
+ var setRef = useMergedRef(ref, stageRef);
72
+ var layerRef = useRef(null);
73
+ var frameRef = useRef(null);
74
+ var screenRef = useRef(null);
75
+ var contentRef = useRef(null);
76
+ var hintRef = useRef(null);
77
+ var videoRef = useRef(null);
78
+ var geo = useRef(null);
79
+ var _useSmoothScroll = useSmoothScroll(),
80
+ scrollTo = _useSmoothScroll.scrollTo;
81
+ // Re-measure the frame geometry after a resize or a layout change.
82
+ useEffect(function () {
83
+ var reset = function reset() {
84
+ geo.current = null;
85
+ };
86
+ reset();
87
+ window.addEventListener("resize", reset);
88
+ return function () {
89
+ return window.removeEventListener("resize", reset);
90
+ };
91
+ }, [mobile, device, armed]);
92
+ // Play the video only while the hero is on screen.
93
+ useEffect(function () {
94
+ return observe(stageRef.current, function (inView) {
95
+ var v = videoRef.current;
96
+ if (!v) return;
97
+ if (inView && !reduced) {
98
+ var played = v.play && v.play();
99
+ if (played && played["catch"]) played["catch"](function () {});
100
+ } else if (v.pause) v.pause();
101
+ });
102
+ }, [armed, reduced]);
103
+ var onProgress = useCallback(function (p) {
104
+ var layer = layerRef.current;
105
+ var frame = frameRef.current;
106
+ var screen = screenRef.current;
107
+ var stage = stageRef.current && stageRef.current.firstElementChild;
108
+ if (!layer || !frame || !screen || !stage) return;
109
+ if (!geo.current) {
110
+ geo.current = {
111
+ vw: stage.clientWidth,
112
+ vh: stage.clientHeight,
113
+ sx: frame.offsetLeft + screen.offsetLeft,
114
+ sy: frame.offsetTop + screen.offsetTop,
115
+ sw: screen.offsetWidth,
116
+ sh: screen.offsetHeight,
117
+ r: parseFloat(getComputedStyle(screen).borderTopLeftRadius) || 0
118
+ };
119
+ }
120
+ var g = geo.current;
121
+ if (!g.sw || !g.sh || !g.vw || !g.vh) return;
122
+ var q = easeInOut(segment(p, 0, 0.45)); // full-bleed → screen
123
+ var chrome = segment(p, 0.4, 0.6); // bezel fades in
124
+ var c = easeOut(segment(p, 0.6, 0.9)); // slide aside + copy in
125
+ var fit = Math.max(g.sw / g.vw, g.sh / g.vh);
126
+ var insetX = (g.vw - g.sw / fit) / 2 * q;
127
+ var insetY = (g.vh - g.sh / fit) / 2 * q;
128
+ var dx = (g.sx + g.sw / 2 - g.vw / 2) * q;
129
+ var dy = (g.sy + g.sh / 2 - g.vh / 2) * q;
130
+ var tx = mobile ? 0 : (side === "left" ? -1 : 1) * g.vw * 0.22 * c;
131
+ var ty = mobile ? -g.vh * 0.2 * c : 0;
132
+ var k = mobile ? lerp(1, 0.62, c) : 1;
133
+ layer.style.transform = "translate3d(".concat(dx + tx, "px,").concat(dy + ty, "px,0) scale(").concat(lerp(1, fit, q) * k, ")");
134
+ layer.style.clipPath = "inset(".concat(insetY, "px ").concat(insetX, "px round ").concat(g.r / fit * q, "px)");
135
+ frame.style.transform = "translate3d(".concat(tx, "px,").concat(ty, "px,0) scale(").concat(k, ")");
136
+ frame.style.setProperty("--dt-chrome", chrome.toFixed(3));
137
+ if (contentRef.current) {
138
+ contentRef.current.style.opacity = c.toFixed(3);
139
+ contentRef.current.style.transform = "translateY(".concat((1 - c) * 24, "px)");
140
+ contentRef.current.style.pointerEvents = c > 0.5 ? "auto" : "none";
141
+ }
142
+ if (hintRef.current) hintRef.current.style.opacity = (1 - segment(p, 0, 0.12)).toFixed(3);
143
+ }, [mobile, side]);
144
+ var skip = function skip() {
145
+ var stage = stageRef.current;
146
+ if (!stage) return;
147
+ var end = window.scrollY + stage.getBoundingClientRect().bottom - innerHeight;
148
+ scrollTo(end, {
149
+ immediate: reduced
150
+ });
151
+ };
152
+ var deviceLeft = side === "left";
153
+ if (!armed) {
154
+ return jsx("section", _objectSpread2(_objectSpread2({
155
+ ref: setRef,
156
+ "data-dt-hero": "static",
157
+ className: cn("relative", className)
158
+ }, rest), {}, {
159
+ children: jsxs("div", {
160
+ className: "mx-auto grid max-w-7xl items-center gap-12 px-4 py-16 md:grid-cols-2 md:px-8",
161
+ children: [jsx("div", {
162
+ className: cn(deviceLeft && "md:order-2"),
163
+ children: content
164
+ }), jsx("div", {
165
+ className: cn("flex justify-center", deviceLeft && "md:order-1"),
166
+ children: jsx(DeviceFrame, {
167
+ device: device,
168
+ children: jsx(Media, {
169
+ media: media,
170
+ videoRef: videoRef
171
+ })
172
+ })
173
+ })]
174
+ })
175
+ }));
176
+ }
177
+ return jsxs(ScrollStage, _objectSpread2(_objectSpread2({
178
+ ref: setRef,
179
+ "data-dt-hero": "morph",
180
+ length: Math.min(length, MAX_LENGTH),
181
+ mobileLength: Math.min(mobileLength, MAX_LENGTH),
182
+ onProgress: onProgress,
183
+ className: className
184
+ }, rest), {}, {
185
+ children: [jsx("div", {
186
+ ref: layerRef,
187
+ className: "absolute inset-0 origin-center will-change-transform",
188
+ children: jsx(Media, {
189
+ media: media,
190
+ videoRef: videoRef
191
+ })
192
+ }), jsx("div", {
193
+ className: "pointer-events-none absolute inset-0 flex items-center justify-center",
194
+ children: jsx(DeviceFrame, {
195
+ ref: frameRef,
196
+ screenRef: screenRef,
197
+ device: device,
198
+ screenClassName: "bg-transparent dark:bg-transparent",
199
+ className: "will-change-transform [--dt-chrome:0]"
200
+ })
201
+ }), content ? jsx("div", {
202
+ ref: contentRef,
203
+ // The copy stays in the accessibility tree while faded out. Tabbing into it jumps the
204
+ // scene to its end, so a focused link is never invisible.
205
+ onFocus: function onFocus() {
206
+ if (contentRef.current && parseFloat(contentRef.current.style.opacity || "0") < 1) skip();
207
+ },
208
+ className: 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"),
209
+ children: jsx("div", {
210
+ className: "w-full",
211
+ children: content
212
+ })
213
+ }) : null, scrollHint !== false ? jsxs("button", {
214
+ ref: hintRef,
215
+ type: "button",
216
+ onClick: skip,
217
+ 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",
218
+ children: [scrollHint, jsx("span", {
219
+ "aria-hidden": "true",
220
+ className: "ml-2 inline-block motion-safe:animate-bounce",
221
+ children: "\u2193"
222
+ })]
223
+ }) : null]
224
+ }));
225
+ });
226
+ DeviceMorphHero.displayName = "DeviceMorphHero";
227
+
228
+ export { DeviceMorphHero as default };
@@ -0,0 +1,144 @@
1
+ 'use client';
2
+ import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2, typeof as _typeof } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+ import React, { useRef, useState, useEffect } from 'react';
5
+ import { cn } from '../../utils/cn.js';
6
+ import { observe } from '../../motion/engine/inView.js';
7
+ import { useMergedRef } from '../../motion/engine/useMergedRef.js';
8
+
9
+ var _excluded = ["items", "speed", "direction", "pauseOnHover", "fadeEdges", "rows", "label", "className"];
10
+ var isLogo = function isLogo(item) {
11
+ return item && _typeof(item) === "object" && ! /*#__PURE__*/React.isValidElement(item) && "src" in item;
12
+ };
13
+ function MarqueeItem(_ref) {
14
+ var item = _ref.item;
15
+ if (isLogo(item)) {
16
+ var img = jsx("img", {
17
+ src: item.src,
18
+ alt: item.alt,
19
+ loading: "lazy",
20
+ className: "h-8 w-auto max-w-[10rem] object-contain"
21
+ });
22
+ return item.href ? jsx("a", {
23
+ href: item.href,
24
+ className: "rounded-control focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500",
25
+ children: img
26
+ }) : img;
27
+ }
28
+ if (typeof item === "string") {
29
+ return jsx("span", {
30
+ className: "whitespace-nowrap text-lg font-semibold tracking-tight text-muted",
31
+ children: item
32
+ });
33
+ }
34
+ return item;
35
+ }
36
+ function List(_ref2) {
37
+ var items = _ref2.items,
38
+ clone = _ref2.clone;
39
+ return jsx("ul", {
40
+ role: clone ? undefined : "list",
41
+ "aria-hidden": clone ? "true" : undefined,
42
+ className: 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"),
43
+ children: items.map(function (item, i) {
44
+ return jsx("li", {
45
+ className: "flex shrink-0 items-center",
46
+ children: clone && isLogo(item) && item.href ? jsx(MarqueeItem, {
47
+ item: {
48
+ src: item.src,
49
+ alt: item.alt
50
+ }
51
+ }) : jsx(MarqueeItem, {
52
+ item: item
53
+ })
54
+ }, i);
55
+ })
56
+ });
57
+ }
58
+ /**
59
+ * An endless strip of logos or names. The second copy of the list (needed for a seamless
60
+ * loop) is `aria-hidden`, so screen readers read each item once. Pauses on hover, while
61
+ * focus is inside and while off screen; under reduced motion it is a static, wrapped row.
62
+ */
63
+ var LogoMarquee = /*#__PURE__*/React.forwardRef(function (_ref3, ref) {
64
+ var _ref3$items = _ref3.items,
65
+ items = _ref3$items === void 0 ? [] : _ref3$items,
66
+ _ref3$speed = _ref3.speed,
67
+ speed = _ref3$speed === void 0 ? 40 : _ref3$speed,
68
+ _ref3$direction = _ref3.direction,
69
+ direction = _ref3$direction === void 0 ? "left" : _ref3$direction,
70
+ _ref3$pauseOnHover = _ref3.pauseOnHover,
71
+ pauseOnHover = _ref3$pauseOnHover === void 0 ? true : _ref3$pauseOnHover,
72
+ _ref3$fadeEdges = _ref3.fadeEdges,
73
+ fadeEdges = _ref3$fadeEdges === void 0 ? true : _ref3$fadeEdges,
74
+ _ref3$rows = _ref3.rows,
75
+ rows = _ref3$rows === void 0 ? 1 : _ref3$rows,
76
+ _ref3$label = _ref3.label,
77
+ label = _ref3$label === void 0 ? "Clients" : _ref3$label,
78
+ className = _ref3.className,
79
+ rest = _objectWithoutProperties(_ref3, _excluded);
80
+ var local = useRef(null);
81
+ var setRef = useMergedRef(ref, local);
82
+ var _useState = useState(true),
83
+ _useState2 = _slicedToArray(_useState, 2),
84
+ inView = _useState2[0],
85
+ setInView = _useState2[1];
86
+ // Seconds per loop. A rough guess for the server render; measured on the client.
87
+ var _useState3 = useState(Math.max(items.length, 4) * 3),
88
+ _useState4 = _slicedToArray(_useState3, 2),
89
+ seconds = _useState4[0],
90
+ setSeconds = _useState4[1];
91
+ useEffect(function () {
92
+ return observe(local.current, function (v) {
93
+ return setInView(v);
94
+ });
95
+ }, []);
96
+ useEffect(function () {
97
+ var list = local.current && local.current.querySelector("ul");
98
+ if (!list) return undefined;
99
+ var measure = function measure() {
100
+ var width = list.getBoundingClientRect().width;
101
+ if (width > 0 && speed > 0) setSeconds(width / speed);
102
+ };
103
+ measure();
104
+ if (typeof ResizeObserver === "undefined") return undefined;
105
+ var ro = new ResizeObserver(measure);
106
+ ro.observe(list);
107
+ return function () {
108
+ return ro.disconnect();
109
+ };
110
+ }, [speed, items]);
111
+ var rowCount = rows === 2 ? 2 : 1;
112
+ return jsx("div", _objectSpread2(_objectSpread2({
113
+ ref: setRef,
114
+ role: "region",
115
+ "aria-label": label,
116
+ className: 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)
117
+ }, rest), {}, {
118
+ children: jsx("div", {
119
+ className: "flex flex-col gap-y-6",
120
+ children: Array.from({
121
+ length: rowCount
122
+ }, function (_, row) {
123
+ var reverse = direction === "right" !== (row === 1);
124
+ return jsxs("div", {
125
+ "data-marquee-track": "",
126
+ className: 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]"),
127
+ style: {
128
+ animationDuration: "".concat(seconds, "s"),
129
+ animationDirection: reverse ? "reverse" : "normal"
130
+ },
131
+ children: [jsx(List, {
132
+ items: items
133
+ }), jsx(List, {
134
+ items: items,
135
+ clone: true
136
+ })]
137
+ }, row);
138
+ })
139
+ })
140
+ }));
141
+ });
142
+ LogoMarquee.displayName = "LogoMarquee";
143
+
144
+ export { LogoMarquee as default };
@@ -0,0 +1,92 @@
1
+ 'use client';
2
+ import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+ import React, { useRef } from 'react';
5
+ import { cn } from '../../utils/cn.js';
6
+ import { useReducedMotion } from '../../hooks/useReducedMotion.js';
7
+ import { useScrollProgress } from '../../hooks/useScrollProgress.js';
8
+ import { useMergedRef } from '../../motion/engine/useMergedRef.js';
9
+
10
+ var _excluded = ["images", "chips", "parallax", "badge", "className", "style"];
11
+ // Placement and parallax depth (sign = direction) for up to four photos.
12
+ var SLOTS = [{
13
+ className: "left-0 top-0 w-[64%] aspect-[3/4]",
14
+ depth: 1
15
+ }, {
16
+ className: "right-0 bottom-0 w-[52%] aspect-square",
17
+ depth: -0.7
18
+ }, {
19
+ className: "right-[4%] top-[6%] w-[32%] aspect-[4/5]",
20
+ depth: 0.5
21
+ }, {
22
+ className: "left-[10%] bottom-[2%] w-[28%] aspect-square",
23
+ depth: -0.4
24
+ }];
25
+ // Paint order (back to front) by slot index: later positioned siblings stack on top, so no
26
+ // z-index is needed. Chips and the badge render after the photos and sit above them all.
27
+ var PAINT_ORDER = [2, 0, 1, 3];
28
+ var CHIP_SLOTS = ["left-[48%] top-[38%]", "left-[2%] bottom-[30%]"];
29
+ /**
30
+ * Overlapping, offset photos with floating stat chips and an optional badge. On scroll the
31
+ * photos drift at different depths (parallax), driven by a CSS variable — no re-renders.
32
+ * Under reduced motion (or `parallax={0}`) they stay put.
33
+ */
34
+ var PhotoCollage = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
35
+ var _ref$images = _ref.images,
36
+ images = _ref$images === void 0 ? [] : _ref$images,
37
+ _ref$chips = _ref.chips,
38
+ chips = _ref$chips === void 0 ? [] : _ref$chips,
39
+ _ref$parallax = _ref.parallax,
40
+ parallax = _ref$parallax === void 0 ? 0.15 : _ref$parallax,
41
+ badge = _ref.badge,
42
+ className = _ref.className,
43
+ style = _ref.style,
44
+ rest = _objectWithoutProperties(_ref, _excluded);
45
+ var local = useRef(null);
46
+ var setRef = useMergedRef(ref, local);
47
+ var reduced = useReducedMotion();
48
+ useScrollProgress(local, {
49
+ enabled: parallax > 0 && !reduced
50
+ });
51
+ return jsxs("div", _objectSpread2(_objectSpread2({
52
+ ref: setRef,
53
+ className: cn("relative isolate mx-auto aspect-[5/6] w-full max-w-xl", className),
54
+ style: _objectSpread2({
55
+ "--dt-parallax": parallax * 240
56
+ }, style)
57
+ }, rest), {}, {
58
+ children: [PAINT_ORDER.filter(function (i) {
59
+ return i < images.length;
60
+ }).map(function (i) {
61
+ return jsx("figure", {
62
+ className: 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),
63
+ style: {
64
+ "--dt-depth": SLOTS[i].depth
65
+ },
66
+ children: jsx("img", {
67
+ src: images[i].src,
68
+ alt: images[i].alt,
69
+ loading: "lazy",
70
+ className: "h-full w-full object-cover"
71
+ })
72
+ }, i);
73
+ }), chips.slice(0, CHIP_SLOTS.length).map(function (chip, i) {
74
+ return jsxs("div", {
75
+ className: 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]),
76
+ children: [jsx("span", {
77
+ className: "text-section-title font-semibold leading-none text-foreground",
78
+ children: chip.value
79
+ }), jsx("span", {
80
+ className: "mt-1 text-micro uppercase tracking-wider text-muted",
81
+ children: chip.label
82
+ })]
83
+ }, i);
84
+ }), badge ? jsx("div", {
85
+ className: "absolute -right-2 -top-4 md:-right-6",
86
+ children: badge
87
+ }) : null]
88
+ }));
89
+ });
90
+ PhotoCollage.displayName = "PhotoCollage";
91
+
92
+ export { PhotoCollage as default };
@@ -0,0 +1,115 @@
1
+ 'use client';
2
+ import { objectWithoutProperties as _objectWithoutProperties, slicedToArray as _slicedToArray, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+ import React, { useRef, useState } from 'react';
5
+ import { cn } from '../../utils/cn.js';
6
+ import { useReducedMotion } from '../../hooks/useReducedMotion.js';
7
+ import { observe } from '../../motion/engine/inView.js';
8
+ import { useIsomorphicLayoutEffect } from '../../motion/engine/useIsomorphicLayoutEffect.js';
9
+ import { useMergedRef } from '../../motion/engine/useMergedRef.js';
10
+
11
+ var _excluded = ["effect", "delay", "stagger", "once", "as", "className", "style", "children"];
12
+ var BASE = "transition-[opacity,transform,clip-path] duration-700 ease-emphasized motion-reduce:transition-none";
13
+ // Classes for the hidden state of each effect (the shown state is the element's natural style).
14
+ var HIDDEN = {
15
+ "fade-up": "opacity-0 translate-y-6",
16
+ fade: "opacity-0",
17
+ scale: "opacity-0 scale-95",
18
+ clip: "[clip-path:inset(0_0_100%_0)]",
19
+ "split-words": ""
20
+ };
21
+ var WORD_HIDDEN = "opacity-0 translate-y-[0.6em]";
22
+ /**
23
+ * Plays an entrance when the element scrolls into view. The server, no-JS readers and
24
+ * reduced-motion users get the content as-is; elements already on screen at load are not
25
+ * hidden (no flash, no LCP delay). Only content below the fold animates.
26
+ */
27
+ var Reveal = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
28
+ var _ref$effect = _ref.effect,
29
+ effect = _ref$effect === void 0 ? "fade-up" : _ref$effect,
30
+ _ref$delay = _ref.delay,
31
+ delay = _ref$delay === void 0 ? 0 : _ref$delay,
32
+ _ref$stagger = _ref.stagger,
33
+ stagger = _ref$stagger === void 0 ? 40 : _ref$stagger,
34
+ _ref$once = _ref.once,
35
+ once = _ref$once === void 0 ? true : _ref$once,
36
+ _ref$as = _ref.as,
37
+ Tag = _ref$as === void 0 ? "div" : _ref$as,
38
+ className = _ref.className,
39
+ style = _ref.style,
40
+ children = _ref.children,
41
+ rest = _objectWithoutProperties(_ref, _excluded);
42
+ var local = useRef(null);
43
+ var setRef = useMergedRef(ref, local);
44
+ var reduced = useReducedMotion();
45
+ // "static": final state (server, reduced motion, above the fold) · "hidden" · "shown"
46
+ var _useState = useState("static"),
47
+ _useState2 = _slicedToArray(_useState, 2),
48
+ state = _useState2[0],
49
+ setState = _useState2[1];
50
+ useIsomorphicLayoutEffect(function () {
51
+ var el = local.current;
52
+ if (reduced || !el) {
53
+ setState("static");
54
+ return undefined;
55
+ }
56
+ var belowFold = el.getBoundingClientRect().top > innerHeight;
57
+ if (!belowFold && once) return undefined;
58
+ if (belowFold) setState("hidden");
59
+ var stop = function stop() {};
60
+ stop = observe(el, function (inView) {
61
+ if (inView) {
62
+ setState("shown");
63
+ if (once) stop();
64
+ } else if (!once) {
65
+ setState("hidden");
66
+ }
67
+ }, {
68
+ threshold: 0.15
69
+ });
70
+ return function () {
71
+ return stop();
72
+ };
73
+ }, [reduced, once]);
74
+ var hidden = state === "hidden";
75
+ var animated = state !== "static";
76
+ if (effect === "split-words" && typeof children === "string") {
77
+ var words = children.split(/\s+/).filter(Boolean);
78
+ return jsxs(Tag, _objectSpread2(_objectSpread2({
79
+ ref: setRef,
80
+ className: className,
81
+ style: style
82
+ }, rest), {}, {
83
+ children: [jsx("span", {
84
+ className: "sr-only",
85
+ children: children
86
+ }), jsx("span", {
87
+ "aria-hidden": "true",
88
+ children: words.map(function (word, i) {
89
+ return jsxs(React.Fragment, {
90
+ children: [jsx("span", {
91
+ className: cn("inline-block", animated && BASE, hidden && WORD_HIDDEN),
92
+ style: animated ? {
93
+ transitionDelay: "".concat(delay + i * stagger, "ms")
94
+ } : undefined,
95
+ children: word
96
+ }), i < words.length - 1 ? " " : null]
97
+ }, i);
98
+ })
99
+ })]
100
+ }));
101
+ }
102
+ return jsx(Tag, _objectSpread2(_objectSpread2({
103
+ ref: setRef,
104
+ "data-reveal": state,
105
+ className: cn(animated && BASE, hidden && (HIDDEN[effect] || HIDDEN["fade-up"]), className),
106
+ style: animated && delay ? _objectSpread2({
107
+ transitionDelay: "".concat(delay, "ms")
108
+ }, style) : style
109
+ }, rest), {}, {
110
+ children: children
111
+ }));
112
+ });
113
+ Reveal.displayName = "Reveal";
114
+
115
+ export { Reveal as default };