@astra-kit/ui 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +5 -3
  3. package/THIRD_PARTY_NOTICES.md +1 -1
  4. package/dist/components/empty-state.d.ts +20 -0
  5. package/dist/components/empty-state.js +357 -0
  6. package/dist/components/metric-card.d.ts +22 -0
  7. package/dist/components/metric-card.js +378 -0
  8. package/dist/components/stepper.d.ts +39 -0
  9. package/dist/components/stepper.js +693 -0
  10. package/dist/components/timeline.d.ts +51 -0
  11. package/dist/components/timeline.js +1013 -0
  12. package/dist/components/usage-meter.d.ts +39 -0
  13. package/dist/components/usage-meter.js +1093 -0
  14. package/dist/constants/empty-state.d.ts +12 -0
  15. package/dist/constants/empty-state.js +12 -0
  16. package/dist/constants/metric-card.d.ts +12 -0
  17. package/dist/constants/metric-card.js +12 -0
  18. package/dist/constants/stepper.d.ts +52 -0
  19. package/dist/constants/stepper.js +23 -0
  20. package/dist/constants/timeline.d.ts +62 -0
  21. package/dist/constants/timeline.js +31 -0
  22. package/dist/constants/usage-meter.d.ts +100 -0
  23. package/dist/constants/usage-meter.js +38 -0
  24. package/dist/hooks/use-has-entered-view.d.ts +9 -0
  25. package/dist/hooks/use-has-entered-view.js +46 -0
  26. package/dist/index.d.ts +5 -0
  27. package/dist/index.js +5 -0
  28. package/dist/motion/arc-odometer.d.ts +53 -0
  29. package/dist/motion/arc-odometer.js +424 -0
  30. package/dist/motion/arc-text-swap.d.ts +22 -0
  31. package/dist/motion/arc-text-swap.js +142 -0
  32. package/dist/motion/arc-tokens.d.ts +1 -0
  33. package/dist/motion/arc-tokens.js +2 -1
  34. package/dist/styles.css +1 -1
  35. package/package.json +26 -1
@@ -0,0 +1,424 @@
1
+ "use client";
2
+ import { c as _c } from "react/compiler-runtime";
3
+ /**
4
+ * Rolling digits adapted from Arc's AnimatedCounter and UsageMeter (https://uiarc.dev), Copyright (c) 2026 Elia Kuratli,
5
+ * MIT License. The full license text ships in THIRD_PARTY_NOTICES.md. Internal: the odometer behind `MetricCard` and
6
+ * `UsageMeter`. It is decoration over text that screen readers get elsewhere, so it is always `aria-hidden`.
7
+ */
8
+ import { useEffect, useRef, useState } from "react";
9
+ import { AnimatePresence, animate, cancelFrame, frame, motion, useMotionValue, useTransform } from "motion/react";
10
+ import { cn } from "../lib/utils.js";
11
+ import { ARC_BLUR, ARC_DURATION, ARC_SPRING_MORPH, ARC_SPRING_SMOOTH, ARC_STAGGER } from "./arc-tokens.js";
12
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
+ /**
14
+ * Motion drops inherited velocity on time-defined springs, so values that retarget mid-flight run the same springs
15
+ * written as stiffness and damping.
16
+ * @param spring - Visual duration and bounce of the spring.
17
+ * @param restDelta - Distance at which the spring counts as settled.
18
+ * @returns The equivalent physical spring.
19
+ */
20
+ export function toPhysicalSpring({ visualDuration, bounce }, restDelta = .001) {
21
+ const angularFrequency = 2 * Math.PI / (visualDuration * 1.2);
22
+ return {
23
+ type: "spring",
24
+ stiffness: angularFrequency * angularFrequency,
25
+ damping: 2 * (1 - bounce) * angularFrequency,
26
+ restDelta,
27
+ restSpeed: restDelta * 2
28
+ };
29
+ }
30
+ /**
31
+ * Starts an animation on the next frame, so the render that caused it never swallows a spring's first frames.
32
+ * @param start - Starts the animation and returns its controls.
33
+ * @returns A cleanup that cancels the pending start or stops the running animation.
34
+ */
35
+ export function startNextFrame(start) {
36
+ let controls = null;
37
+ const run = () => {
38
+ controls = start();
39
+ };
40
+ frame.update(run);
41
+ return () => {
42
+ cancelFrame(run);
43
+ controls?.stop();
44
+ };
45
+ }
46
+ /** Wheels turn on a critically damped spring. */
47
+ const TURN_SPRING = toPhysicalSpring(ARC_SPRING_SMOOTH);
48
+ /** The first reveal from zero takes a little longer, so the digits read as counting up. */
49
+ const REVEAL_SPRING = toPhysicalSpring({
50
+ visualDuration: ARC_DURATION.considered,
51
+ bounce: 0
52
+ });
53
+ /** New columns and separators open from zero width on the morph spring. */
54
+ const FIT_SPRING = toPhysicalSpring(ARC_SPRING_MORPH, .01);
55
+ /** Longest stagger, in seconds, between columns revealed together. */
56
+ const REVEAL_STAGGER_LIMIT_S = .25;
57
+ /** Number of glyphs on a wheel. */
58
+ const WHEEL_SIZE = 10;
59
+ const DIGITS = Array.from({ length: WHEEL_SIZE }, (_, digit) => digit);
60
+ /** Distance, in wheel steps, at which a glyph is fully sharp. */
61
+ const SHARP_OFFSET = .02;
62
+ const columnPresence = {
63
+ initial: {
64
+ width: 0,
65
+ opacity: 0
66
+ },
67
+ animate: {
68
+ width: "auto",
69
+ opacity: 1
70
+ },
71
+ exit: {
72
+ width: 0,
73
+ opacity: 0
74
+ }
75
+ };
76
+ /** Each wheel is clipped and feathers out at its edges, so a turning digit fades instead of being cut. */
77
+ const columnClassName = "relative inline-block overflow-hidden [--feather:.16em] -my-(--feather) py-(--feather) [mask-image:linear-gradient(to_bottom,transparent,#000_calc(var(--feather)*1.5),#000_calc(100%_-_var(--feather)*1.5),transparent)]";
78
+ const numberFormats = new Map();
79
+ /**
80
+ * Splits a formatted number into columns keyed by place value, so 999 → 1.000 keeps the ones column the ones column and
81
+ * a new thousands column slides in.
82
+ * @param value - Number to split.
83
+ * @param decimals - Digits after the decimal separator.
84
+ * @param locale - Formatting locale.
85
+ * @returns Digit columns and separators in display order.
86
+ */
87
+ function partsFor(value, decimals, locale) {
88
+ const formatKey = `${locale}|${decimals}`;
89
+ let format = numberFormats.get(formatKey);
90
+ if (!format) {
91
+ format = new Intl.NumberFormat(locale, {
92
+ minimumFractionDigits: decimals,
93
+ maximumFractionDigits: decimals,
94
+ numberingSystem: "latn"
95
+ });
96
+ numberFormats.set(formatKey, format);
97
+ }
98
+ const parts = format.formatToParts(value);
99
+ let place = parts.reduce((count, part) => count + (part.type === "integer" ? part.value.length : 0), 0);
100
+ let fraction = 0;
101
+ let order = 0;
102
+ return parts.flatMap((part, index) => {
103
+ if (part.type === "integer") return [...part.value].map((character) => ({
104
+ key: `i${--place}`,
105
+ digit: Number(character),
106
+ order: order++
107
+ }));
108
+ if (part.type === "fraction") return [...part.value].map((character) => ({
109
+ key: `f${fraction++}`,
110
+ digit: Number(character),
111
+ order: order++
112
+ }));
113
+ const key = part.type === "group" ? `g${place}` : part.type === "decimal" ? "d" : `${part.type}${index}`;
114
+ return [{
115
+ key,
116
+ text: part.value
117
+ }];
118
+ });
119
+ }
120
+ /**
121
+ * Formats a number the way the odometer draws it, for the screen-reader copy beside it.
122
+ * @param value - Number to format.
123
+ * @param decimals - Digits after the decimal separator.
124
+ * @param locale - Formatting locale.
125
+ * @returns The formatted number.
126
+ */
127
+ export function formatOdometerValue(value, decimals, locale) {
128
+ return partsFor(value, decimals, locale).map((part) => "text" in part ? part.text : part.digit).join("");
129
+ }
130
+ /** One digit on a wheel; its distance from the wheel position sets where it sits and how clearly it shows. */
131
+ function Glyph(t0) {
132
+ const $ = _c(10);
133
+ const { position, digit } = t0;
134
+ let t1;
135
+ if ($[0] !== digit) {
136
+ t1 = (current) => ((digit - current) % WHEEL_SIZE + WHEEL_SIZE * 1.5) % WHEEL_SIZE - WHEEL_SIZE / 2;
137
+ $[0] = digit;
138
+ $[1] = t1;
139
+ } else {
140
+ t1 = $[1];
141
+ }
142
+ const offset = useTransform(position, t1);
143
+ const y = useTransform(offset, _temp);
144
+ const opacity = useTransform(offset, _temp2);
145
+ const visibility = useTransform(offset, _temp3);
146
+ const filter = useTransform(offset, _temp4);
147
+ let t2;
148
+ if ($[2] !== filter || $[3] !== opacity || $[4] !== visibility || $[5] !== y) {
149
+ t2 = {
150
+ y,
151
+ opacity,
152
+ filter,
153
+ visibility
154
+ };
155
+ $[2] = filter;
156
+ $[3] = opacity;
157
+ $[4] = visibility;
158
+ $[5] = y;
159
+ $[6] = t2;
160
+ } else {
161
+ t2 = $[6];
162
+ }
163
+ let t3;
164
+ if ($[7] !== digit || $[8] !== t2) {
165
+ t3 = /* @__PURE__ */ _jsx(motion.span, {
166
+ className: "absolute inset-x-0 inset-y-(--feather) text-center",
167
+ style: t2,
168
+ children: digit
169
+ });
170
+ $[7] = digit;
171
+ $[8] = t2;
172
+ $[9] = t3;
173
+ } else {
174
+ t3 = $[9];
175
+ }
176
+ return t3;
177
+ }
178
+ function _temp4(current_3) {
179
+ return Math.abs(current_3) < SHARP_OFFSET || Math.abs(current_3) >= 1 ? "none" : `blur(${(Math.abs(current_3) * ARC_BLUR.subtle).toFixed(2)}px)`;
180
+ }
181
+ function _temp3(current_2) {
182
+ return Math.abs(current_2) >= 1 ? "hidden" : "visible";
183
+ }
184
+ function _temp2(current_1) {
185
+ return Math.max(0, 1 - Math.abs(current_1));
186
+ }
187
+ function _temp(current_0) {
188
+ return `${current_0}em`;
189
+ }
190
+ /** A digit wheel that always turns the way the whole number moved, wrapping 9 → 0 like an odometer. */
191
+ function Column(t0) {
192
+ const $ = _c(22);
193
+ const { digit, direction, armed, delay, reduceMotion } = t0;
194
+ const position = useMotionValue(armed ? 0 : digit);
195
+ const t1 = armed ? 0 : digit;
196
+ const t2 = armed ? 0 : digit;
197
+ const t3 = !armed;
198
+ let t4;
199
+ if ($[0] !== t1 || $[1] !== t2 || $[2] !== t3) {
200
+ t4 = {
201
+ digit: t1,
202
+ target: t2,
203
+ revealed: t3
204
+ };
205
+ $[0] = t1;
206
+ $[1] = t2;
207
+ $[2] = t3;
208
+ $[3] = t4;
209
+ } else {
210
+ t4 = $[3];
211
+ }
212
+ const wheel = useRef(t4);
213
+ const running = useRef(null);
214
+ let t5;
215
+ let t6;
216
+ if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
217
+ t5 = () => () => running.current?.();
218
+ t6 = [];
219
+ $[4] = t5;
220
+ $[5] = t6;
221
+ } else {
222
+ t5 = $[4];
223
+ t6 = $[5];
224
+ }
225
+ useEffect(t5, t6);
226
+ let t7;
227
+ let t8;
228
+ if ($[6] !== armed || $[7] !== delay || $[8] !== digit || $[9] !== direction || $[10] !== position || $[11] !== reduceMotion) {
229
+ t7 = () => {
230
+ const state = wheel.current;
231
+ const hasNewDigit = !armed && state.digit !== digit;
232
+ if (hasNewDigit) {
233
+ state.target = state.target + (direction < 0 && state.revealed ? -((state.digit - digit + WHEEL_SIZE) % WHEEL_SIZE) : (digit - state.digit + WHEEL_SIZE) % WHEEL_SIZE);
234
+ state.digit = digit;
235
+ }
236
+ if (reduceMotion) {
237
+ running.current?.();
238
+ running.current = null;
239
+ if (!armed) {
240
+ state.revealed = true;
241
+ }
242
+ if (position.get() !== state.target) {
243
+ position.jump(state.target);
244
+ }
245
+ return;
246
+ }
247
+ if (!hasNewDigit) {
248
+ if (!armed) {
249
+ state.revealed = true;
250
+ }
251
+ return;
252
+ }
253
+ running.current?.();
254
+ const target = state.target;
255
+ const transition = state.revealed ? TURN_SPRING : {
256
+ ...REVEAL_SPRING,
257
+ delay
258
+ };
259
+ state.revealed = true;
260
+ running.current = startNextFrame(() => animate(position, target, transition));
261
+ };
262
+ t8 = [
263
+ armed,
264
+ delay,
265
+ digit,
266
+ direction,
267
+ position,
268
+ reduceMotion
269
+ ];
270
+ $[6] = armed;
271
+ $[7] = delay;
272
+ $[8] = digit;
273
+ $[9] = direction;
274
+ $[10] = position;
275
+ $[11] = reduceMotion;
276
+ $[12] = t7;
277
+ $[13] = t8;
278
+ } else {
279
+ t7 = $[12];
280
+ t8 = $[13];
281
+ }
282
+ useEffect(t7, t8);
283
+ let t9;
284
+ if ($[14] !== reduceMotion) {
285
+ t9 = reduceMotion ? { duration: 0 } : FIT_SPRING;
286
+ $[14] = reduceMotion;
287
+ $[15] = t9;
288
+ } else {
289
+ t9 = $[15];
290
+ }
291
+ let t10;
292
+ if ($[16] === Symbol.for("react.memo_cache_sentinel")) {
293
+ t10 = /* @__PURE__ */ _jsx("span", {
294
+ className: "invisible",
295
+ children: "0"
296
+ });
297
+ $[16] = t10;
298
+ } else {
299
+ t10 = $[16];
300
+ }
301
+ let t11;
302
+ if ($[17] !== position) {
303
+ t11 = DIGITS.map((item) => /* @__PURE__ */ _jsx(Glyph, {
304
+ position,
305
+ digit: item
306
+ }, item));
307
+ $[17] = position;
308
+ $[18] = t11;
309
+ } else {
310
+ t11 = $[18];
311
+ }
312
+ let t12;
313
+ if ($[19] !== t11 || $[20] !== t9) {
314
+ t12 = /* @__PURE__ */ _jsxs(motion.span, {
315
+ className: columnClassName,
316
+ ...columnPresence,
317
+ transition: t9,
318
+ children: [t10, t11]
319
+ });
320
+ $[19] = t11;
321
+ $[20] = t9;
322
+ $[21] = t12;
323
+ } else {
324
+ t12 = $[21];
325
+ }
326
+ return t12;
327
+ }
328
+ /**
329
+ * A number that rolls to each new value, digit by digit, in the direction it moved.
330
+ * @param props - Value, decimals, locale, reveal state and motion preference.
331
+ * @returns The odometer, hidden from assistive technology.
332
+ */
333
+ export function ArcOdometer(t0) {
334
+ const $ = _c(20);
335
+ const { value, decimals: t1, locale, armed: t2, reduceMotion, className } = t0;
336
+ const decimals = t1 === undefined ? 0 : t1;
337
+ const armed = t2 === undefined ? false : t2;
338
+ let t3;
339
+ if ($[0] !== value) {
340
+ t3 = {
341
+ value,
342
+ direction: 1
343
+ };
344
+ $[0] = value;
345
+ $[1] = t3;
346
+ } else {
347
+ t3 = $[1];
348
+ }
349
+ const [trend, setTrend] = useState(t3);
350
+ if (trend.value !== value) {
351
+ setTrend({
352
+ value,
353
+ direction: value > trend.value ? 1 : -1
354
+ });
355
+ }
356
+ let t4;
357
+ if ($[2] !== className) {
358
+ t4 = cn("relative inline-flex items-start whitespace-nowrap tabular-nums select-none", className);
359
+ $[2] = className;
360
+ $[3] = t4;
361
+ } else {
362
+ t4 = $[3];
363
+ }
364
+ let t5;
365
+ if ($[4] !== armed || $[5] !== decimals || $[6] !== locale || $[7] !== reduceMotion || $[8] !== trend.direction || $[9] !== value) {
366
+ let t6;
367
+ if ($[11] !== armed || $[12] !== reduceMotion || $[13] !== trend.direction) {
368
+ t6 = (part) => "digit" in part ? /* @__PURE__ */ _jsx(Column, {
369
+ digit: part.digit,
370
+ direction: trend.direction,
371
+ armed,
372
+ delay: Math.min(part.order * ARC_STAGGER.item, REVEAL_STAGGER_LIMIT_S),
373
+ reduceMotion
374
+ }, part.key) : /* @__PURE__ */ _jsx(motion.span, {
375
+ className: "inline-block overflow-x-clip",
376
+ ...columnPresence,
377
+ transition: reduceMotion ? { duration: 0 } : FIT_SPRING,
378
+ children: part.text
379
+ }, part.key);
380
+ $[11] = armed;
381
+ $[12] = reduceMotion;
382
+ $[13] = trend.direction;
383
+ $[14] = t6;
384
+ } else {
385
+ t6 = $[14];
386
+ }
387
+ t5 = partsFor(value, decimals, locale).map(t6);
388
+ $[4] = armed;
389
+ $[5] = decimals;
390
+ $[6] = locale;
391
+ $[7] = reduceMotion;
392
+ $[8] = trend.direction;
393
+ $[9] = value;
394
+ $[10] = t5;
395
+ } else {
396
+ t5 = $[10];
397
+ }
398
+ let t6;
399
+ if ($[15] !== t5) {
400
+ t6 = /* @__PURE__ */ _jsx(AnimatePresence, {
401
+ initial: false,
402
+ children: t5
403
+ });
404
+ $[15] = t5;
405
+ $[16] = t6;
406
+ } else {
407
+ t6 = $[16];
408
+ }
409
+ let t7;
410
+ if ($[17] !== t4 || $[18] !== t6) {
411
+ t7 = /* @__PURE__ */ _jsx("span", {
412
+ "data-slot": "odometer",
413
+ "aria-hidden": "true",
414
+ className: t4,
415
+ children: t6
416
+ });
417
+ $[17] = t4;
418
+ $[18] = t6;
419
+ $[19] = t7;
420
+ } else {
421
+ t7 = $[19];
422
+ }
423
+ return t7;
424
+ }
@@ -0,0 +1,22 @@
1
+ /** Vertical travel, in em, of new copy rising in and old copy lifting out. */
2
+ export declare const ARC_TEXT_SWAP_OFFSET_EM = 0.3;
3
+ /** Direction new copy enters from: `1` rises from below (a larger value), `-1` drops from above (a smaller one). */
4
+ export type ArcTextSwapDirection = 1 | -1;
5
+ export type ArcTextSwapProps = {
6
+ /** Copy to show. A new value rolls in over the previous one; without copy the last one leaves. */
7
+ text: string | undefined;
8
+ /** Whether the reduced-motion crossfade replaces the rise. */
9
+ reduceMotion: boolean;
10
+ /** Side the new copy enters from. */
11
+ direction?: ArcTextSwapDirection;
12
+ /** Classes of the positioned slot the outgoing copy pops out against. */
13
+ className?: string;
14
+ /** Classes of each line of copy. */
15
+ lineClassName?: string;
16
+ };
17
+ /**
18
+ * Rolls changed copy in place: the outgoing line pops out of flow so the slot never holds both widths.
19
+ * @param props - Copy, motion preference, direction and classes.
20
+ * @returns The swapping copy.
21
+ */
22
+ export declare function ArcTextSwap({ text, reduceMotion, direction, className, lineClassName }: ArcTextSwapProps): import("react").JSX.Element;
@@ -0,0 +1,142 @@
1
+ "use client";
2
+ import { c as _c } from "react/compiler-runtime";
3
+ /**
4
+ * Text swap adapted from Arc (https://uiarc.dev), Copyright (c) 2026 Elia Kuratli, MIT License.
5
+ * The full license text ships in THIRD_PARTY_NOTICES.md. Internal: shared by the Arc-derived data displays (metric
6
+ * card, usage meter, timeline, stepper and empty state) to roll changed copy in place.
7
+ */
8
+ import { AnimatePresence, motion, useIsPresent } from "motion/react";
9
+ import { cn } from "../lib/utils.js";
10
+ import { ARC_BLUR, ARC_DURATION, ARC_EASE } from "./arc-tokens.js";
11
+ import { jsx as _jsx } from "react/jsx-runtime";
12
+ /** Vertical travel, in em, of new copy rising in and old copy lifting out. */
13
+ export const ARC_TEXT_SWAP_OFFSET_EM = .3;
14
+ /** New copy rises out of a soft blur from the side it moved toward; the old copy leaves a little faster. */
15
+ const riseVariants = {
16
+ hidden: (direction) => ({
17
+ opacity: 0,
18
+ y: `${ARC_TEXT_SWAP_OFFSET_EM * direction}em`,
19
+ filter: `blur(${ARC_BLUR.soft}px)`
20
+ }),
21
+ shown: {
22
+ opacity: 1,
23
+ y: 0,
24
+ filter: "blur(0px)",
25
+ transition: {
26
+ duration: ARC_DURATION.standard,
27
+ ease: [...ARC_EASE.enter]
28
+ }
29
+ },
30
+ gone: (direction) => ({
31
+ opacity: 0,
32
+ y: `${-ARC_TEXT_SWAP_OFFSET_EM * direction}em`,
33
+ filter: `blur(${ARC_BLUR.subtle}px)`,
34
+ transition: {
35
+ duration: ARC_DURATION.fast,
36
+ ease: [...ARC_EASE.standard]
37
+ }
38
+ })
39
+ };
40
+ /** With reduced motion, copy only crossfades in place. */
41
+ const fadeVariants = {
42
+ hidden: {
43
+ opacity: 0,
44
+ y: 0,
45
+ filter: "blur(0px)"
46
+ },
47
+ shown: {
48
+ opacity: 1,
49
+ y: 0,
50
+ filter: "blur(0px)",
51
+ transition: { duration: ARC_DURATION.instant }
52
+ },
53
+ gone: {
54
+ opacity: 0,
55
+ y: 0,
56
+ filter: "blur(0px)",
57
+ transition: { duration: ARC_DURATION.instant }
58
+ }
59
+ };
60
+ /** Outgoing copies are hidden from assistive technology while they fade, so only the current copy is read. */
61
+ function SwapLine(props) {
62
+ const $ = _c(3);
63
+ const isPresent = useIsPresent();
64
+ const t0 = isPresent ? props["aria-hidden"] : true;
65
+ let t1;
66
+ if ($[0] !== props || $[1] !== t0) {
67
+ t1 = /* @__PURE__ */ _jsx(motion.span, {
68
+ ...props,
69
+ "aria-hidden": t0
70
+ });
71
+ $[0] = props;
72
+ $[1] = t0;
73
+ $[2] = t1;
74
+ } else {
75
+ t1 = $[2];
76
+ }
77
+ return t1;
78
+ }
79
+ /**
80
+ * Rolls changed copy in place: the outgoing line pops out of flow so the slot never holds both widths.
81
+ * @param props - Copy, motion preference, direction and classes.
82
+ * @returns The swapping copy.
83
+ */
84
+ export function ArcTextSwap(t0) {
85
+ const $ = _c(13);
86
+ const { text, reduceMotion, direction: t1, className, lineClassName } = t0;
87
+ const direction = t1 === undefined ? 1 : t1;
88
+ let t2;
89
+ if ($[0] !== className) {
90
+ t2 = cn("relative block max-w-full", className);
91
+ $[0] = className;
92
+ $[1] = t2;
93
+ } else {
94
+ t2 = $[1];
95
+ }
96
+ let t3;
97
+ if ($[2] !== direction || $[3] !== lineClassName || $[4] !== reduceMotion || $[5] !== text) {
98
+ t3 = text ? /* @__PURE__ */ _jsx(SwapLine, {
99
+ className: cn("block", lineClassName),
100
+ custom: direction,
101
+ variants: reduceMotion ? fadeVariants : riseVariants,
102
+ initial: "hidden",
103
+ animate: "shown",
104
+ exit: "gone",
105
+ children: text
106
+ }, text) : null;
107
+ $[2] = direction;
108
+ $[3] = lineClassName;
109
+ $[4] = reduceMotion;
110
+ $[5] = text;
111
+ $[6] = t3;
112
+ } else {
113
+ t3 = $[6];
114
+ }
115
+ let t4;
116
+ if ($[7] !== direction || $[8] !== t3) {
117
+ t4 = /* @__PURE__ */ _jsx(AnimatePresence, {
118
+ mode: "popLayout",
119
+ initial: false,
120
+ custom: direction,
121
+ children: t3
122
+ });
123
+ $[7] = direction;
124
+ $[8] = t3;
125
+ $[9] = t4;
126
+ } else {
127
+ t4 = $[9];
128
+ }
129
+ let t5;
130
+ if ($[10] !== t2 || $[11] !== t4) {
131
+ t5 = /* @__PURE__ */ _jsx("span", {
132
+ className: t2,
133
+ children: t4
134
+ });
135
+ $[10] = t2;
136
+ $[11] = t4;
137
+ $[12] = t5;
138
+ } else {
139
+ t5 = $[12];
140
+ }
141
+ return t5;
142
+ }
@@ -45,6 +45,7 @@ export declare const ARC_SPRING_CSS: {
45
45
  export declare const ARC_STAGGER: {
46
46
  readonly char: 0.016;
47
47
  readonly item: 0.035;
48
+ readonly line: 0.08;
48
49
  };
49
50
  /** Blur radii in pixels for brief content crossfades. */
50
51
  export declare const ARC_BLUR: {
@@ -64,7 +64,8 @@ export const ARC_SPRING_CSS = {
64
64
  /** Stagger steps in seconds; keep a whole staggered entrance under roughly 0.4s. */
65
65
  export const ARC_STAGGER = {
66
66
  char: .016,
67
- item: .035
67
+ item: .035,
68
+ line: .08
68
69
  };
69
70
  /** Blur radii in pixels for brief content crossfades. */
70
71
  export const ARC_BLUR = {