transform-marquee 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Muhsinjon Bokijonov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,143 @@
1
- # Temporary Holding Version
1
+ # transform-marquee
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A React marquee where every chip **wipes from a "before" state into an "after" state** as it crosses a line. Rows of chips drift across the screen; at the split line, *Rough idea* becomes *Production-ready product*.
4
+
5
+ - **One component, a few props:** rows, chips per row, speed, direction and split position
6
+ - **Restyle with CSS variables:** border radius, colours, icon size, padding
7
+ - **Fast:** layout is measured once with `ResizeObserver`, each frame only writes `transform` and `clip-path`, and the animation stops when the marquee is off-screen
8
+ - **Accessible:** respects `prefers-reduced-motion`, has an optional pause button, and hides the animation from screen readers while giving them a plain list of the pairs
9
+ - **No dependencies** besides React 18+. Works with Next.js App Router (ships as a client component).
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm i transform-marquee
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```tsx
20
+ import { TransformMarquee } from "transform-marquee";
21
+ import "transform-marquee/styles.css";
22
+
23
+ const items = [
24
+ { from: "Rough idea", to: "Production-ready product" },
25
+ { from: "Slow experience", to: "Lightning-fast UX" },
26
+ { from: "Buggy code", to: "Reliable software" },
27
+ ];
28
+
29
+ export function Hero() {
30
+ return (
31
+ <div style={{ height: 420 }}>
32
+ <TransformMarquee items={items} />
33
+ </div>
34
+ );
35
+ }
36
+ ```
37
+
38
+ By default `rows="auto"` fills the container's height with as many whole rows as fit, **so the container needs a height**. To size it from the content instead, pass a number: `rows={4}`.
39
+
40
+ ### Icons
41
+
42
+ Pass any React node as an icon, such as an SVG or a component from your icon library:
43
+
44
+ ```tsx
45
+ import { LuLightbulb, LuRocket } from "react-icons/lu";
46
+
47
+ const items = [
48
+ { from: "Rough idea", to: "Production-ready product", fromIcon: <LuLightbulb />, toIcon: <LuRocket /> },
49
+ ];
50
+ ```
51
+
52
+ ### Responsive values
53
+
54
+ `rows`, `perRow` and `edgeFade` accept a value per breakpoint, mobile-first:
55
+
56
+ ```tsx
57
+ <TransformMarquee items={items} rows={{ base: 4, md: 6 }} perRow={{ base: 2, lg: 4 }} />
58
+ ```
59
+
60
+ Breakpoints are measured on the **container's width**, not the viewport's, so the marquee adapts the same way in a sidebar or a full-width section. Defaults: `sm: 640`, `md: 768`, `lg: 1024`, `xl: 1280`. Override them with the `breakpoints` prop.
61
+
62
+ ## Props
63
+
64
+ | Prop | Type | Default | Description |
65
+ |---|---|---|---|
66
+ | `items` | `TransformMarqueeItem[]` | required | `{ from, to, fromIcon?, toIcon?, id? }`. Items are dealt out across rows in order and repeat if there are fewer items than chips. |
67
+ | `rows` | `Responsive<number \| "auto">` | `"auto"` | Number of rows, or fill the container's height. |
68
+ | `perRow` | `Responsive<number>` | `{ base: 2, lg: 4 }` | Chips travelling in each row at once. |
69
+ | `speed` | `number \| [min, max]` | `[120, 155]` | Pixels per second. A range gives each row its own speed. |
70
+ | `direction` | `"right" \| "left"` | `"right"` | Travel direction. |
71
+ | `splitAt` | `number` | `0.5` | Position of the wipe line, from 0 to 1 across the row. |
72
+ | `rowHeight` | `number` | `68` | Row height in px. |
73
+ | `rowGap` | `number` | `4` | Gap between rows in px. |
74
+ | `chipGap` | `number` | `48` | Minimum gap between chips in the same row, in px. |
75
+ | `pauseOnHover` | `boolean` | `true` | Pause a row while the pointer is over it. |
76
+ | `paused` | `boolean` | | Controlled pause state. |
77
+ | `onPausedChange` | `(paused) => void` | | Called when the pause button is pressed. |
78
+ | `pauseButton` | `boolean` | `false` | Show a pause/play button. Recommended for [WCAG 2.2.2](https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide). |
79
+ | `divider` | `boolean` | `true` | Draw the vertical line at the split. |
80
+ | `edgeFade` | `Responsive<string \| number> \| false` | `"clamp(3rem, 15%, 16rem)"` | Width of the left and right fade masks. |
81
+ | `breakpoints` | `Partial<Breakpoints>` | see above | Container widths used by responsive props. |
82
+ | `renderChip` | `(item, side) => ReactNode` | | Replace a chip's content (the icon and label) entirely. |
83
+ | `classNames` | `{ root, lane, chip, from, to, icon, divider, fade, pauseButton }` | | Extra classes for each part. |
84
+ | `srList` | `boolean` | `true` | Render a visually hidden list of the pairs for screen readers. |
85
+ | `labels` | `{ pause?, play? }` | | Accessible labels for the pause button. |
86
+
87
+ Other `div` attributes (`className`, `style`, `id`, `aria-*`…) are passed to the root element.
88
+
89
+ ## Styling
90
+
91
+ Every visual choice is a CSS custom property on `.tm-root`. Set them with `style`, a class, or on any ancestor:
92
+
93
+ ```tsx
94
+ <TransformMarquee
95
+ items={items}
96
+ style={{ "--tm-radius": "999px", "--tm-accent": "#16a34a" } as React.CSSProperties}
97
+ />
98
+ ```
99
+
100
+ | Variable | Default |
101
+ |---|---|
102
+ | `--tm-radius` | `0.5rem` |
103
+ | `--tm-icon-radius` | `0.5rem` |
104
+ | `--tm-icon-size` / `--tm-icon-glyph-size` | `1.75rem` / `1rem` |
105
+ | `--tm-font-size` | `1rem` |
106
+ | `--tm-font-weight` / `--tm-font-weight-to` | `500` / `600` |
107
+ | `--tm-padding-x` / `--tm-padding-y` | `1rem` / `0.75rem` |
108
+ | `--tm-chip-gap` | `0.625rem` (space between icon and label) |
109
+ | `--tm-border-width` | `1px` |
110
+ | `--tm-bg` | `Canvas` |
111
+ | `--tm-fg` | `CanvasText` |
112
+ | `--tm-muted` | 60% of `--tm-fg` |
113
+ | `--tm-border` | 15% of `--tm-fg` |
114
+ | `--tm-accent` / `--tm-accent-fg` | `#6366f1` / `#fff` |
115
+ | `--tm-fade-color` | `--tm-bg` |
116
+ | `--tm-divider-color` | `--tm-accent` |
117
+
118
+ The colour defaults use the CSS system colours `Canvas` and `CanvasText`, so they follow your page's `color-scheme`. If your site sets its own background colours, set `--tm-bg` (and `--tm-fade-color`, if different) to match the section behind the marquee, so the edge fades blend in.
119
+
120
+ ### Tailwind
121
+
122
+ Use `classNames` for utility classes, or set variables with arbitrary properties:
123
+
124
+ ```tsx
125
+ <TransformMarquee
126
+ items={items}
127
+ className="[--tm-radius:9999px] [--tm-accent:var(--color-primary)]"
128
+ classNames={{ chip: "shadow-sm" }}
129
+ />
130
+ ```
131
+
132
+ ## Development
133
+
134
+ ```bash
135
+ npm install
136
+ npm run dev # demo playground at http://localhost:5173
137
+ npm test # unit tests for the layout/timing engine
138
+ npm run build # builds dist/ (ESM + CJS + types + styles.css)
139
+ ```
140
+
141
+ ## License
142
+
143
+ MIT © Muhsinjon Bokijonov
package/dist/index.cjs ADDED
@@ -0,0 +1,405 @@
1
+ "use client";
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ DEFAULT_BREAKPOINTS: () => DEFAULT_BREAKPOINTS,
25
+ TransformMarquee: () => TransformMarquee
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/transform-marquee.tsx
30
+ var import_react = require("react");
31
+
32
+ // src/engine.ts
33
+ var DEFAULT_BREAKPOINTS = { sm: 640, md: 768, lg: 1024, xl: 1280 };
34
+ var ORDER = ["sm", "md", "lg", "xl"];
35
+ function isPerBreakpoint(value) {
36
+ return typeof value === "object" && value !== null && !Array.isArray(value);
37
+ }
38
+ function resolveResponsive(value, width, breakpoints, fallback) {
39
+ if (value === void 0) return fallback;
40
+ if (!isPerBreakpoint(value)) return value;
41
+ let result = value.base ?? fallback;
42
+ for (const bp of ORDER) {
43
+ const v = value[bp];
44
+ if (v !== void 0 && width >= breakpoints[bp]) result = v;
45
+ }
46
+ return result;
47
+ }
48
+ function vanDerCorput(n, base = 2) {
49
+ let result = 0;
50
+ let denominator = 1 / base;
51
+ let k = Math.floor(n);
52
+ while (k > 0) {
53
+ result += k % base * denominator;
54
+ k = Math.floor(k / base);
55
+ denominator /= base;
56
+ }
57
+ return result;
58
+ }
59
+ function fitRows(height, rowHeight, gap) {
60
+ if (rowHeight <= 0 || height < rowHeight) return 0;
61
+ return Math.floor((height + gap) / (rowHeight + gap));
62
+ }
63
+ function rowsHeight(rows, rowHeight, gap) {
64
+ return rows <= 0 ? 0 : rows * rowHeight + (rows - 1) * gap;
65
+ }
66
+ function laneSpeed(lane, speed) {
67
+ if (typeof speed === "number") return speed;
68
+ const [min, max] = speed;
69
+ return min + (max - min) * vanDerCorput(lane + 1, 3);
70
+ }
71
+ function travelerPhase(lane, slot, perRow) {
72
+ return (vanDerCorput(lane + 1) + slot / perRow) % 1;
73
+ }
74
+ function itemIndexFor(lane, slot, perRow, count) {
75
+ return count <= 0 ? 0 : (lane * perRow + slot) % count;
76
+ }
77
+ function travelGeometry(rowWidth, widestChip, perRow, minGap) {
78
+ const range = Math.max(rowWidth + widestChip, perRow * (widestChip + minGap));
79
+ return { start: -widestChip, range };
80
+ }
81
+ function positionAt(elapsedS, speed, phase, start, range) {
82
+ if (range <= 0) return start;
83
+ const traveled = elapsedS * speed + phase * range;
84
+ return start + (traveled % range + range) % range;
85
+ }
86
+ function wipeFraction(x, width, split) {
87
+ if (width <= 0) return 0;
88
+ return Math.min(1, Math.max(0, (split - x) / width));
89
+ }
90
+ function clipPaths(fraction, direction) {
91
+ const left = `inset(0 ${(1 - fraction) * 100}% 0 0)`;
92
+ const right = `inset(0 0 0 ${fraction * 100}%)`;
93
+ return direction === "right" ? { from: left, to: right } : { from: right, to: left };
94
+ }
95
+
96
+ // src/transform-marquee.tsx
97
+ var import_jsx_runtime = require("react/jsx-runtime");
98
+ var newClock = () => ({
99
+ holds: 0,
100
+ pausedMs: 0,
101
+ pauseStartedAt: null,
102
+ globalHeld: false,
103
+ hoverHeld: false
104
+ });
105
+ function hold(clock, now) {
106
+ if (clock.holds++ === 0) clock.pauseStartedAt = now;
107
+ }
108
+ function release(clock, now) {
109
+ if (clock.holds === 0) return;
110
+ if (--clock.holds === 0 && clock.pauseStartedAt !== null) {
111
+ clock.pausedMs += now - clock.pauseStartedAt;
112
+ clock.pauseStartedAt = null;
113
+ }
114
+ }
115
+ function pausedFor(clock, now) {
116
+ return clock.pausedMs + (clock.pauseStartedAt !== null ? now - clock.pauseStartedAt : 0);
117
+ }
118
+ function syncGlobal(clock, paused, now) {
119
+ if (paused && !clock.globalHeld) {
120
+ hold(clock, now);
121
+ clock.globalHeld = true;
122
+ } else if (!paused && clock.globalHeld) {
123
+ release(clock, now);
124
+ clock.globalHeld = false;
125
+ }
126
+ }
127
+ var cx = (...names) => names.filter(Boolean).join(" ");
128
+ var toLength = (value) => typeof value === "number" ? `${value}px` : value;
129
+ var isDev = () => typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
130
+ function TransformMarquee({
131
+ items,
132
+ rows = "auto",
133
+ perRow = { base: 2, lg: 4 },
134
+ speed = [120, 155],
135
+ direction = "right",
136
+ splitAt = 0.5,
137
+ rowHeight = 68,
138
+ rowGap = 4,
139
+ chipGap = 48,
140
+ pauseOnHover = true,
141
+ paused,
142
+ onPausedChange,
143
+ pauseButton = false,
144
+ divider = true,
145
+ edgeFade = "clamp(3rem, 15%, 16rem)",
146
+ breakpoints,
147
+ classNames = {},
148
+ renderChip,
149
+ srList = true,
150
+ labels,
151
+ className,
152
+ style,
153
+ ...rest
154
+ }) {
155
+ const rootRef = (0, import_react.useRef)(null);
156
+ const gridRef = (0, import_react.useRef)(null);
157
+ const [size, setSize] = (0, import_react.useState)(null);
158
+ const [internalPaused, setInternalPaused] = (0, import_react.useState)(false);
159
+ const isPaused = paused ?? internalPaused;
160
+ const startRef = (0, import_react.useRef)(null);
161
+ const clocksRef = (0, import_react.useRef)(/* @__PURE__ */ new Map());
162
+ const pausedRef = (0, import_react.useRef)(isPaused);
163
+ const bps = { ...DEFAULT_BREAKPOINTS, ...breakpoints };
164
+ const width = size?.w ?? 0;
165
+ const split = Math.min(1, Math.max(0, splitAt));
166
+ const rowsSetting = resolveResponsive(rows, width, bps, "auto");
167
+ const perRowCount = Math.max(1, Math.round(resolveResponsive(perRow, width, bps, 2)));
168
+ const fixedRows = rowsSetting === "auto" ? null : Math.max(0, Math.floor(rowsSetting));
169
+ const laneCount = !size || items.length === 0 ? 0 : fixedRows ?? fitRows(size.h, rowHeight, rowGap);
170
+ const fade = edgeFade === false ? null : resolveResponsive(edgeFade, width, bps, "0px");
171
+ const [speedMin, speedMax] = typeof speed === "number" ? [speed, speed] : speed;
172
+ (0, import_react.useEffect)(() => {
173
+ const root = rootRef.current;
174
+ if (!root) return;
175
+ const observer = new ResizeObserver(([entry]) => {
176
+ if (!entry) return;
177
+ const w = Math.round(entry.contentRect.width);
178
+ const h = Math.round(entry.contentRect.height);
179
+ setSize((prev) => prev && prev.w === w && prev.h === h ? prev : { w, h });
180
+ });
181
+ observer.observe(root);
182
+ return () => observer.disconnect();
183
+ }, []);
184
+ const warnedRef = (0, import_react.useRef)(false);
185
+ (0, import_react.useEffect)(() => {
186
+ if (!size || fixedRows !== null || warnedRef.current || !isDev()) return;
187
+ if (size.h < rowHeight) {
188
+ warnedRef.current = true;
189
+ console.warn(
190
+ `[transform-marquee] rows="auto" needs a container with a height of at least rowHeight (${rowHeight}px); this one is ${size.h}px tall. Give it a height, or pass a number to \`rows\`.`
191
+ );
192
+ }
193
+ }, [size, fixedRows, rowHeight]);
194
+ (0, import_react.useEffect)(() => {
195
+ pausedRef.current = isPaused;
196
+ const now = performance.now();
197
+ for (const clock of clocksRef.current.values()) syncGlobal(clock, isPaused, now);
198
+ }, [isPaused]);
199
+ (0, import_react.useEffect)(() => {
200
+ const grid = gridRef.current;
201
+ if (!grid || laneCount === 0) return;
202
+ const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
203
+ const start = startRef.current ?? (startRef.current = performance.now());
204
+ const builtAt = performance.now();
205
+ const speedSetting = [speedMin, speedMax];
206
+ const lanes = Array.from(grid.querySelectorAll("[data-tm-lane]"), (el, index) => {
207
+ let clock = clocksRef.current.get(index);
208
+ if (!clock) {
209
+ clock = newClock();
210
+ clocksRef.current.set(index, clock);
211
+ }
212
+ syncGlobal(clock, pausedRef.current, builtAt);
213
+ return {
214
+ el,
215
+ speed: laneSpeed(index, speedSetting),
216
+ width: 0,
217
+ widest: 0,
218
+ clock,
219
+ chips: Array.from(el.querySelectorAll("[data-tm-traveler]"), (traveler) => ({
220
+ el: traveler,
221
+ from: traveler.querySelector("[data-tm-from]"),
222
+ to: traveler.querySelector("[data-tm-to]"),
223
+ phase: Number(traveler.dataset.phase),
224
+ width: 0
225
+ }))
226
+ };
227
+ });
228
+ const laneByEl = new Map(lanes.map((lane) => [lane.el, lane]));
229
+ const chipByEl = /* @__PURE__ */ new Map();
230
+ for (const lane of lanes) for (const chip of lane.chips) chipByEl.set(chip.el, chip);
231
+ const render = (now) => {
232
+ for (const lane of lanes) {
233
+ if (!lane.width || !lane.widest) continue;
234
+ const { start: from, range } = travelGeometry(lane.width, lane.widest, lane.chips.length, chipGap);
235
+ const elapsedS = reduceMotion ? 0 : (now - start - pausedFor(lane.clock, now)) / 1e3;
236
+ const splitPx = lane.width * split;
237
+ for (const chip of lane.chips) {
238
+ if (!chip.width) continue;
239
+ const pos = positionAt(elapsedS, lane.speed, chip.phase, from, range);
240
+ const x = direction === "right" ? pos : lane.width - pos - chip.width;
241
+ const clips = clipPaths(wipeFraction(x, chip.width, splitPx), direction);
242
+ chip.el.style.transform = `translate3d(${x}px, 0, 0)`;
243
+ chip.from.style.clipPath = clips.from;
244
+ chip.to.style.clipPath = clips.to;
245
+ }
246
+ }
247
+ };
248
+ const sizeObserver = new ResizeObserver((entries) => {
249
+ for (const entry of entries) {
250
+ const w = entry.borderBoxSize?.[0]?.inlineSize ?? entry.contentRect.width;
251
+ const lane = laneByEl.get(entry.target);
252
+ if (lane) lane.width = w;
253
+ else {
254
+ const chip = chipByEl.get(entry.target);
255
+ if (chip) chip.width = w;
256
+ }
257
+ }
258
+ for (const lane of lanes) lane.widest = Math.max(0, ...lane.chips.map((chip) => chip.width));
259
+ render(performance.now());
260
+ grid.dataset.ready = "";
261
+ });
262
+ for (const lane of lanes) {
263
+ sizeObserver.observe(lane.el);
264
+ for (const chip of lane.chips) sizeObserver.observe(chip.el);
265
+ }
266
+ if (reduceMotion) return () => sizeObserver.disconnect();
267
+ const enter = (event) => {
268
+ const lane = laneByEl.get(event.currentTarget);
269
+ if (lane && !lane.clock.hoverHeld) {
270
+ hold(lane.clock, performance.now());
271
+ lane.clock.hoverHeld = true;
272
+ }
273
+ };
274
+ const leave = (event) => {
275
+ const lane = laneByEl.get(event.currentTarget);
276
+ if (lane?.clock.hoverHeld) {
277
+ release(lane.clock, performance.now());
278
+ lane.clock.hoverHeld = false;
279
+ }
280
+ };
281
+ if (pauseOnHover) {
282
+ for (const lane of lanes) {
283
+ lane.el.addEventListener("mouseenter", enter);
284
+ lane.el.addEventListener("mouseleave", leave);
285
+ }
286
+ }
287
+ let raf = 0;
288
+ const frame = (now) => {
289
+ raf = requestAnimationFrame(frame);
290
+ render(now);
291
+ };
292
+ const visibilityObserver = new IntersectionObserver(([entry]) => {
293
+ cancelAnimationFrame(raf);
294
+ raf = entry?.isIntersecting ? requestAnimationFrame(frame) : 0;
295
+ });
296
+ visibilityObserver.observe(grid);
297
+ return () => {
298
+ cancelAnimationFrame(raf);
299
+ visibilityObserver.disconnect();
300
+ sizeObserver.disconnect();
301
+ const now = performance.now();
302
+ for (const lane of lanes) {
303
+ lane.el.removeEventListener("mouseenter", enter);
304
+ lane.el.removeEventListener("mouseleave", leave);
305
+ if (lane.clock.hoverHeld) {
306
+ release(lane.clock, now);
307
+ lane.clock.hoverHeld = false;
308
+ }
309
+ }
310
+ };
311
+ }, [laneCount, perRowCount, speedMin, speedMax, chipGap, split, direction, pauseOnHover]);
312
+ const togglePaused = () => {
313
+ const next = !isPaused;
314
+ if (paused === void 0) setInternalPaused(next);
315
+ onPausedChange?.(next);
316
+ };
317
+ const rootStyle = {
318
+ "--tm-row-height": `${rowHeight}px`,
319
+ "--tm-row-gap": `${rowGap}px`,
320
+ "--tm-split": `${split * 100}%`,
321
+ ...fade !== null && { "--tm-fade-width": toLength(fade) },
322
+ ...fixedRows !== null && { height: rowsHeight(fixedRows, rowHeight, rowGap) },
323
+ ...style
324
+ };
325
+ const initialClips = clipPaths(0, direction);
326
+ const chipContent = (item, side) => {
327
+ if (renderChip) return renderChip(item, side);
328
+ const icon = side === "from" ? item.fromIcon : item.toIcon;
329
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
330
+ icon != null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: cx("tm-icon", classNames.icon), children: icon }),
331
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tm-label", children: side === "from" ? item.from : item.to })
332
+ ] });
333
+ };
334
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
335
+ "div",
336
+ {
337
+ ref: rootRef,
338
+ className: cx("tm-root", fixedRows === null && "tm-root--auto", className, classNames.root),
339
+ "data-direction": direction,
340
+ style: rootStyle,
341
+ ...rest,
342
+ children: [
343
+ srList && items.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "tm-sr-only", children: items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
344
+ item.from,
345
+ " \u2192 ",
346
+ item.to
347
+ ] }, item.id ?? i)) }),
348
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: gridRef, className: "tm-grid", "aria-hidden": "true", children: Array.from({ length: laneCount }, (_, lane) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-tm-lane": "", className: cx("tm-lane", classNames.lane), children: Array.from({ length: perRowCount }, (_2, slot) => {
349
+ const item = items[itemIndexFor(lane, slot, perRowCount, items.length)];
350
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
351
+ "div",
352
+ {
353
+ "data-tm-traveler": "",
354
+ "data-phase": travelerPhase(lane, slot, perRowCount),
355
+ className: "tm-traveler",
356
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tm-stack", children: [
357
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
358
+ "div",
359
+ {
360
+ "data-tm-from": "",
361
+ className: cx("tm-chip", "tm-chip-from", classNames.chip, classNames.from),
362
+ style: { clipPath: initialClips.from },
363
+ children: chipContent(item, "from")
364
+ }
365
+ ),
366
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
367
+ "div",
368
+ {
369
+ "data-tm-to": "",
370
+ className: cx("tm-chip", "tm-chip-to", classNames.chip, classNames.to),
371
+ style: { clipPath: initialClips.to },
372
+ children: chipContent(item, "to")
373
+ }
374
+ )
375
+ ] })
376
+ },
377
+ slot
378
+ );
379
+ }) }, lane)) }),
380
+ divider && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cx("tm-divider", classNames.divider), "aria-hidden": "true" }),
381
+ fade !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
382
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cx("tm-fade", "tm-fade-left", classNames.fade), "aria-hidden": "true" }),
383
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cx("tm-fade", "tm-fade-right", classNames.fade), "aria-hidden": "true" })
384
+ ] }),
385
+ pauseButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
386
+ "button",
387
+ {
388
+ type: "button",
389
+ className: cx("tm-pause", classNames.pauseButton),
390
+ "aria-pressed": isPaused,
391
+ "aria-label": isPaused ? labels?.play ?? "Play animation" : labels?.pause ?? "Pause animation",
392
+ onClick: togglePaused,
393
+ children: isPaused ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4 2.5v11l9-5.5z", fill: "currentColor" }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4 2.5h3v11H4zM9 2.5h3v11H9z", fill: "currentColor" }) })
394
+ }
395
+ )
396
+ ]
397
+ }
398
+ );
399
+ }
400
+ // Annotate the CommonJS export names for ESM import in node:
401
+ 0 && (module.exports = {
402
+ DEFAULT_BREAKPOINTS,
403
+ TransformMarquee
404
+ });
405
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/transform-marquee.tsx","../src/engine.ts"],"sourcesContent":["export { TransformMarquee } from \"./transform-marquee\";\nexport type {\n TransformMarqueeClassNames,\n TransformMarqueeItem,\n TransformMarqueeProps,\n} from \"./transform-marquee\";\nexport { DEFAULT_BREAKPOINTS } from \"./engine\";\nexport type { Breakpoint, Breakpoints, Direction, Responsive } from \"./engine\";\n","import {\n useEffect,\n useRef,\n useState,\n type CSSProperties,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport {\n DEFAULT_BREAKPOINTS,\n clipPaths,\n fitRows,\n itemIndexFor,\n laneSpeed,\n positionAt,\n resolveResponsive,\n rowsHeight,\n travelGeometry,\n travelerPhase,\n wipeFraction,\n type Breakpoints,\n type Direction,\n type Responsive,\n} from \"./engine\";\n\nexport type TransformMarqueeItem = {\n /** Stable key. Falls back to the item's index. */\n id?: string | number;\n /** The \"before\" state, shown until the chip crosses the split line. */\n from: ReactNode;\n /** The \"after\" state, revealed as the chip crosses the split line. */\n to: ReactNode;\n fromIcon?: ReactNode;\n toIcon?: ReactNode;\n};\n\nexport type TransformMarqueeClassNames = Partial<\n Record<\"root\" | \"lane\" | \"chip\" | \"from\" | \"to\" | \"icon\" | \"divider\" | \"fade\" | \"pauseButton\", string>\n>;\n\nexport interface TransformMarqueeProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n items: TransformMarqueeItem[];\n /**\n * Number of rows, or `\"auto\"` to fit as many whole rows as the container's\n * height allows (the container then needs a height). Default `\"auto\"`.\n */\n rows?: Responsive<number | \"auto\">;\n /** Chips travelling in each row at once. Default `{ base: 2, lg: 4 }`. */\n perRow?: Responsive<number>;\n /** Speed in px/s, or a `[min, max]` range spread across rows. Default `[120, 155]`. */\n speed?: number | readonly [number, number];\n /** Travel direction. Default `\"right\"`. */\n direction?: Direction;\n /** Where the wipe line sits, 0–1 of the row width. Default `0.5`. */\n splitAt?: number;\n /** Row height in px. Default `68`. */\n rowHeight?: number;\n /** Gap between rows in px. Default `4`. */\n rowGap?: number;\n /** Minimum horizontal gap between chips in the same row, px. Default `48`. */\n chipGap?: number;\n /** Pause a row while the pointer is over it. Default `true`. */\n pauseOnHover?: boolean;\n /** Controlled pause state. Omit to let the component manage it. */\n paused?: boolean;\n /** Called when the built-in pause button is pressed. */\n onPausedChange?: (paused: boolean) => void;\n /** Show a pause/play button (helps meet WCAG 2.2.2). Default `false`. */\n pauseButton?: boolean;\n /** Draw the vertical line at the split. Default `true`. */\n divider?: boolean;\n /** Width of the left/right fade masks (CSS length or px), or `false`. */\n edgeFade?: Responsive<string | number> | false;\n /** Min-widths (px) for responsive props. Measured on the container, not the viewport. */\n breakpoints?: Partial<Breakpoints>;\n classNames?: TransformMarqueeClassNames;\n /** Replace the default chip content (icon + label) for a side. */\n renderChip?: (item: TransformMarqueeItem, side: \"from\" | \"to\") => ReactNode;\n /**\n * Render a visually hidden list of all `from → to` pairs for screen\n * readers (the animation itself is hidden from them). Default `true`.\n */\n srList?: boolean;\n /** Labels for the pause button. */\n labels?: { pause?: string; play?: string };\n}\n\ntype Clock = {\n holds: number;\n pausedMs: number;\n pauseStartedAt: number | null;\n globalHeld: boolean;\n hoverHeld: boolean;\n};\n\nconst newClock = (): Clock => ({\n holds: 0,\n pausedMs: 0,\n pauseStartedAt: null,\n globalHeld: false,\n hoverHeld: false,\n});\n\nfunction hold(clock: Clock, now: number) {\n if (clock.holds++ === 0) clock.pauseStartedAt = now;\n}\n\nfunction release(clock: Clock, now: number) {\n if (clock.holds === 0) return;\n if (--clock.holds === 0 && clock.pauseStartedAt !== null) {\n clock.pausedMs += now - clock.pauseStartedAt;\n clock.pauseStartedAt = null;\n }\n}\n\nfunction pausedFor(clock: Clock, now: number) {\n return clock.pausedMs + (clock.pauseStartedAt !== null ? now - clock.pauseStartedAt : 0);\n}\n\nfunction syncGlobal(clock: Clock, paused: boolean, now: number) {\n if (paused && !clock.globalHeld) {\n hold(clock, now);\n clock.globalHeld = true;\n } else if (!paused && clock.globalHeld) {\n release(clock, now);\n clock.globalHeld = false;\n }\n}\n\nconst cx = (...names: (string | false | undefined)[]) => names.filter(Boolean).join(\" \");\nconst toLength = (value: string | number) => (typeof value === \"number\" ? `${value}px` : value);\n\nconst isDev = () =>\n typeof process !== \"undefined\" && process.env?.NODE_ENV !== \"production\";\n\nexport function TransformMarquee({\n items,\n rows = \"auto\",\n perRow = { base: 2, lg: 4 },\n speed = [120, 155],\n direction = \"right\",\n splitAt = 0.5,\n rowHeight = 68,\n rowGap = 4,\n chipGap = 48,\n pauseOnHover = true,\n paused,\n onPausedChange,\n pauseButton = false,\n divider = true,\n edgeFade = \"clamp(3rem, 15%, 16rem)\",\n breakpoints,\n classNames = {},\n renderChip,\n srList = true,\n labels,\n className,\n style,\n ...rest\n}: TransformMarqueeProps) {\n const rootRef = useRef<HTMLDivElement>(null);\n const gridRef = useRef<HTMLDivElement>(null);\n const [size, setSize] = useState<{ w: number; h: number } | null>(null);\n const [internalPaused, setInternalPaused] = useState(false);\n const isPaused = paused ?? internalPaused;\n\n // Persist across engine rebuilds (resize, prop changes) so motion and\n // accumulated pauses continue instead of jumping back to the start.\n const startRef = useRef<number | null>(null);\n const clocksRef = useRef(new Map<number, Clock>());\n const pausedRef = useRef(isPaused);\n\n const bps: Breakpoints = { ...DEFAULT_BREAKPOINTS, ...breakpoints };\n const width = size?.w ?? 0;\n const split = Math.min(1, Math.max(0, splitAt));\n const rowsSetting = resolveResponsive(rows, width, bps, \"auto\");\n const perRowCount = Math.max(1, Math.round(resolveResponsive(perRow, width, bps, 2)));\n const fixedRows = rowsSetting === \"auto\" ? null : Math.max(0, Math.floor(rowsSetting));\n const laneCount =\n !size || items.length === 0\n ? 0\n : fixedRows ?? fitRows(size.h, rowHeight, rowGap);\n const fade = edgeFade === false ? null : resolveResponsive(edgeFade, width, bps, \"0px\");\n const [speedMin, speedMax] = typeof speed === \"number\" ? [speed, speed] : speed;\n\n // Container size drives breakpoints and, in \"auto\" mode, the row count.\n useEffect(() => {\n const root = rootRef.current;\n if (!root) return;\n const observer = new ResizeObserver(([entry]) => {\n if (!entry) return;\n const w = Math.round(entry.contentRect.width);\n const h = Math.round(entry.contentRect.height);\n setSize((prev) => (prev && prev.w === w && prev.h === h ? prev : { w, h }));\n });\n observer.observe(root);\n return () => observer.disconnect();\n }, []);\n\n const warnedRef = useRef(false);\n useEffect(() => {\n if (!size || fixedRows !== null || warnedRef.current || !isDev()) return;\n if (size.h < rowHeight) {\n warnedRef.current = true;\n console.warn(\n `[transform-marquee] rows=\"auto\" needs a container with a height of at least rowHeight (${rowHeight}px); ` +\n `this one is ${size.h}px tall. Give it a height, or pass a number to \\`rows\\`.`,\n );\n }\n }, [size, fixedRows, rowHeight]);\n\n // Apply the controlled/uncontrolled pause to every lane clock. Declared\n // before the engine effect so pausedRef is current when lanes are built.\n useEffect(() => {\n pausedRef.current = isPaused;\n const now = performance.now();\n for (const clock of clocksRef.current.values()) syncGlobal(clock, isPaused, now);\n }, [isPaused]);\n\n // The animation engine. Sizes come from ResizeObserver and are cached, so\n // each frame only does arithmetic and style writes — no layout reads.\n useEffect(() => {\n const grid = gridRef.current;\n if (!grid || laneCount === 0) return;\n\n const reduceMotion = window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n const start = (startRef.current ??= performance.now());\n const builtAt = performance.now();\n const speedSetting: [number, number] = [speedMin, speedMax];\n\n type Chip = { el: HTMLElement; from: HTMLElement; to: HTMLElement; phase: number; width: number };\n type Lane = { el: HTMLElement; speed: number; width: number; widest: number; chips: Chip[]; clock: Clock };\n\n const lanes: Lane[] = Array.from(grid.querySelectorAll<HTMLElement>(\"[data-tm-lane]\"), (el, index) => {\n let clock = clocksRef.current.get(index);\n if (!clock) {\n clock = newClock();\n clocksRef.current.set(index, clock);\n }\n syncGlobal(clock, pausedRef.current, builtAt);\n return {\n el,\n speed: laneSpeed(index, speedSetting),\n width: 0,\n widest: 0,\n clock,\n chips: Array.from(el.querySelectorAll<HTMLElement>(\"[data-tm-traveler]\"), (traveler) => ({\n el: traveler,\n from: traveler.querySelector<HTMLElement>(\"[data-tm-from]\")!,\n to: traveler.querySelector<HTMLElement>(\"[data-tm-to]\")!,\n phase: Number(traveler.dataset.phase),\n width: 0,\n })),\n };\n });\n\n const laneByEl = new Map<Element, Lane>(lanes.map((lane) => [lane.el, lane]));\n const chipByEl = new Map<Element, Chip>();\n for (const lane of lanes) for (const chip of lane.chips) chipByEl.set(chip.el, chip);\n\n const render = (now: number) => {\n for (const lane of lanes) {\n if (!lane.width || !lane.widest) continue;\n const { start: from, range } = travelGeometry(lane.width, lane.widest, lane.chips.length, chipGap);\n const elapsedS = reduceMotion ? 0 : (now - start - pausedFor(lane.clock, now)) / 1000;\n const splitPx = lane.width * split;\n for (const chip of lane.chips) {\n if (!chip.width) continue;\n const pos = positionAt(elapsedS, lane.speed, chip.phase, from, range);\n const x = direction === \"right\" ? pos : lane.width - pos - chip.width;\n const clips = clipPaths(wipeFraction(x, chip.width, splitPx), direction);\n chip.el.style.transform = `translate3d(${x}px, 0, 0)`;\n chip.from.style.clipPath = clips.from;\n chip.to.style.clipPath = clips.to;\n }\n }\n };\n\n // Callbacks run after layout, so reading sizes here is free. The first\n // callback also places every chip before the (hidden) grid is revealed.\n const sizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const w = entry.borderBoxSize?.[0]?.inlineSize ?? entry.contentRect.width;\n const lane = laneByEl.get(entry.target);\n if (lane) lane.width = w;\n else {\n const chip = chipByEl.get(entry.target);\n if (chip) chip.width = w;\n }\n }\n for (const lane of lanes) lane.widest = Math.max(0, ...lane.chips.map((chip) => chip.width));\n render(performance.now());\n grid.dataset.ready = \"\";\n });\n for (const lane of lanes) {\n sizeObserver.observe(lane.el);\n for (const chip of lane.chips) sizeObserver.observe(chip.el);\n }\n\n if (reduceMotion) return () => sizeObserver.disconnect();\n\n // Pause a whole lane, not a single chip: its chips share one speed, so\n // pausing only one would let the next chip run into it.\n const enter = (event: Event) => {\n const lane = laneByEl.get(event.currentTarget as Element);\n if (lane && !lane.clock.hoverHeld) {\n hold(lane.clock, performance.now());\n lane.clock.hoverHeld = true;\n }\n };\n const leave = (event: Event) => {\n const lane = laneByEl.get(event.currentTarget as Element);\n if (lane?.clock.hoverHeld) {\n release(lane.clock, performance.now());\n lane.clock.hoverHeld = false;\n }\n };\n if (pauseOnHover) {\n for (const lane of lanes) {\n lane.el.addEventListener(\"mouseenter\", enter);\n lane.el.addEventListener(\"mouseleave\", leave);\n }\n }\n\n // Animate only while on screen.\n let raf = 0;\n const frame = (now: number) => {\n raf = requestAnimationFrame(frame);\n render(now);\n };\n const visibilityObserver = new IntersectionObserver(([entry]) => {\n cancelAnimationFrame(raf);\n raf = entry?.isIntersecting ? requestAnimationFrame(frame) : 0;\n });\n visibilityObserver.observe(grid);\n\n return () => {\n cancelAnimationFrame(raf);\n visibilityObserver.disconnect();\n sizeObserver.disconnect();\n const now = performance.now();\n for (const lane of lanes) {\n lane.el.removeEventListener(\"mouseenter\", enter);\n lane.el.removeEventListener(\"mouseleave\", leave);\n if (lane.clock.hoverHeld) {\n release(lane.clock, now);\n lane.clock.hoverHeld = false;\n }\n }\n };\n }, [laneCount, perRowCount, speedMin, speedMax, chipGap, split, direction, pauseOnHover]);\n\n const togglePaused = () => {\n const next = !isPaused;\n if (paused === undefined) setInternalPaused(next);\n onPausedChange?.(next);\n };\n\n const rootStyle = {\n \"--tm-row-height\": `${rowHeight}px`,\n \"--tm-row-gap\": `${rowGap}px`,\n \"--tm-split\": `${split * 100}%`,\n ...(fade !== null && { \"--tm-fade-width\": toLength(fade) }),\n ...(fixedRows !== null && { height: rowsHeight(fixedRows, rowHeight, rowGap) }),\n ...style,\n } as CSSProperties;\n\n const initialClips = clipPaths(0, direction);\n\n const chipContent = (item: TransformMarqueeItem, side: \"from\" | \"to\") => {\n if (renderChip) return renderChip(item, side);\n const icon = side === \"from\" ? item.fromIcon : item.toIcon;\n return (\n <>\n {icon != null && <span className={cx(\"tm-icon\", classNames.icon)}>{icon}</span>}\n <span className=\"tm-label\">{side === \"from\" ? item.from : item.to}</span>\n </>\n );\n };\n\n return (\n <div\n ref={rootRef}\n className={cx(\"tm-root\", fixedRows === null && \"tm-root--auto\", className, classNames.root)}\n data-direction={direction}\n style={rootStyle}\n {...rest}\n >\n {srList && items.length > 0 && (\n <ul className=\"tm-sr-only\">\n {items.map((item, i) => (\n <li key={item.id ?? i}>\n {item.from} → {item.to}\n </li>\n ))}\n </ul>\n )}\n\n <div ref={gridRef} className=\"tm-grid\" aria-hidden=\"true\">\n {Array.from({ length: laneCount }, (_, lane) => (\n <div key={lane} data-tm-lane=\"\" className={cx(\"tm-lane\", classNames.lane)}>\n {Array.from({ length: perRowCount }, (_, slot) => {\n const item = items[itemIndexFor(lane, slot, perRowCount, items.length)]!;\n return (\n <div\n key={slot}\n data-tm-traveler=\"\"\n data-phase={travelerPhase(lane, slot, perRowCount)}\n className=\"tm-traveler\"\n >\n <div className=\"tm-stack\">\n <div\n data-tm-from=\"\"\n className={cx(\"tm-chip\", \"tm-chip-from\", classNames.chip, classNames.from)}\n style={{ clipPath: initialClips.from }}\n >\n {chipContent(item, \"from\")}\n </div>\n <div\n data-tm-to=\"\"\n className={cx(\"tm-chip\", \"tm-chip-to\", classNames.chip, classNames.to)}\n style={{ clipPath: initialClips.to }}\n >\n {chipContent(item, \"to\")}\n </div>\n </div>\n </div>\n );\n })}\n </div>\n ))}\n </div>\n\n {divider && <div className={cx(\"tm-divider\", classNames.divider)} aria-hidden=\"true\" />}\n {fade !== null && (\n <>\n <div className={cx(\"tm-fade\", \"tm-fade-left\", classNames.fade)} aria-hidden=\"true\" />\n <div className={cx(\"tm-fade\", \"tm-fade-right\", classNames.fade)} aria-hidden=\"true\" />\n </>\n )}\n\n {pauseButton && (\n <button\n type=\"button\"\n className={cx(\"tm-pause\", classNames.pauseButton)}\n aria-pressed={isPaused}\n aria-label={isPaused ? labels?.play ?? \"Play animation\" : labels?.pause ?? \"Pause animation\"}\n onClick={togglePaused}\n >\n {isPaused ? (\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path d=\"M4 2.5v11l9-5.5z\" fill=\"currentColor\" />\n </svg>\n ) : (\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path d=\"M4 2.5h3v11H4zM9 2.5h3v11H9z\" fill=\"currentColor\" />\n </svg>\n )}\n </button>\n )}\n </div>\n );\n}\n","// Pure layout and timing maths for TransformMarquee. No DOM access here, so\n// every function is deterministic and unit-testable.\n\nexport type Breakpoint = \"base\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\nexport type Breakpoints = Record<Exclude<Breakpoint, \"base\">, number>;\n\n/** A single value, or a value per breakpoint (mobile-first, like Tailwind). */\nexport type Responsive<T> = T | Partial<Record<Breakpoint, T>>;\n\nexport const DEFAULT_BREAKPOINTS: Breakpoints = { sm: 640, md: 768, lg: 1024, xl: 1280 };\n\nconst ORDER = [\"sm\", \"md\", \"lg\", \"xl\"] as const;\n\nfunction isPerBreakpoint<T>(value: Responsive<T>): value is Partial<Record<Breakpoint, T>> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Resolves a responsive value for a container width. Each breakpoint applies\n * from its min-width upwards; below the first defined one, `base` (or\n * `fallback`) applies.\n */\nexport function resolveResponsive<T>(\n value: Responsive<T> | undefined,\n width: number,\n breakpoints: Breakpoints,\n fallback: T,\n): T {\n if (value === undefined) return fallback;\n if (!isPerBreakpoint(value)) return value;\n let result = value.base ?? fallback;\n for (const bp of ORDER) {\n const v = value[bp];\n if (v !== undefined && width >= breakpoints[bp]) result = v;\n }\n return result;\n}\n\n/**\n * Low-discrepancy sequence in [0, 1). Any prefix of it is evenly spread, so\n * however many lanes are visible their starting points never clump.\n */\nexport function vanDerCorput(n: number, base = 2): number {\n let result = 0;\n let denominator = 1 / base;\n let k = Math.floor(n);\n while (k > 0) {\n result += (k % base) * denominator;\n k = Math.floor(k / base);\n denominator /= base;\n }\n return result;\n}\n\n/** How many whole rows of `rowHeight` fit in `height` with `gap` between them. */\nexport function fitRows(height: number, rowHeight: number, gap: number): number {\n if (rowHeight <= 0 || height < rowHeight) return 0;\n return Math.floor((height + gap) / (rowHeight + gap));\n}\n\n/** Height of `rows` rows of `rowHeight` with `gap` between them. */\nexport function rowsHeight(rows: number, rowHeight: number, gap: number): number {\n return rows <= 0 ? 0 : rows * rowHeight + (rows - 1) * gap;\n}\n\n/**\n * Speed for a lane in px/s. A range is spread deterministically across lanes\n * (base-3 sequence, so it doesn't line up with the base-2 phase sequence).\n * Every chip in a lane shares its lane's speed, so they can never collide.\n */\nexport function laneSpeed(lane: number, speed: number | readonly [number, number]): number {\n if (typeof speed === \"number\") return speed;\n const [min, max] = speed;\n return min + (max - min) * vanDerCorput(lane + 1, 3);\n}\n\n/** Starting phase (0–1 of the lane's travel cycle) for one chip. */\nexport function travelerPhase(lane: number, slot: number, perRow: number): number {\n return (vanDerCorput(lane + 1) + slot / perRow) % 1;\n}\n\n/** Which item a lane/slot shows: items are dealt out in order, wrapping. */\nexport function itemIndexFor(lane: number, slot: number, perRow: number, count: number): number {\n return count <= 0 ? 0 : (lane * perRow + slot) % count;\n}\n\n/**\n * The travel cycle of a lane. Chips enter fully off one edge and leave fully\n * off the other (`range >= rowWidth + widest chip`), and the cycle is long\n * enough that `perRow` evenly spaced chips keep at least `minGap` between\n * them however long their labels are.\n */\nexport function travelGeometry(\n rowWidth: number,\n widestChip: number,\n perRow: number,\n minGap: number,\n): { start: number; range: number } {\n const range = Math.max(rowWidth + widestChip, perRow * (widestChip + minGap));\n return { start: -widestChip, range };\n}\n\n/** Left edge (px, for rightward travel) of a chip after `elapsedS` seconds. */\nexport function positionAt(\n elapsedS: number,\n speed: number,\n phase: number,\n start: number,\n range: number,\n): number {\n if (range <= 0) return start;\n const traveled = elapsedS * speed + phase * range;\n return start + (((traveled % range) + range) % range);\n}\n\n/** Fraction (0–1) of a chip at `x` with `width` that lies left of `split`. */\nexport function wipeFraction(x: number, width: number, split: number): number {\n if (width <= 0) return 0;\n return Math.min(1, Math.max(0, (split - x) / width));\n}\n\nexport type Direction = \"right\" | \"left\";\n\n/**\n * clip-path values for both chip layers. Moving right, the part left of the\n * split hasn't crossed yet (shows \"from\"); moving left, it's the reverse.\n */\nexport function clipPaths(fraction: number, direction: Direction): { from: string; to: string } {\n const left = `inset(0 ${(1 - fraction) * 100}% 0 0)`;\n const right = `inset(0 0 0 ${fraction * 100}%)`;\n return direction === \"right\" ? { from: left, to: right } : { from: right, to: left };\n}\n"],"mappings":"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","names":["_"]}