@windrun-huaiin/third-ui 14.3.0 → 14.4.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 (60) hide show
  1. package/dist/fuma/fuma-page-genarator.js +3 -4
  2. package/dist/fuma/fuma-page-genarator.mjs +3 -4
  3. package/dist/fuma/mdx/index.d.ts +1 -0
  4. package/dist/fuma/mdx/index.js +5 -0
  5. package/dist/fuma/mdx/index.mjs +1 -0
  6. package/dist/fuma/mdx/toc-clerk-portable.d.ts +19 -0
  7. package/dist/fuma/mdx/toc-clerk-portable.js +328 -0
  8. package/dist/fuma/mdx/toc-clerk-portable.mjs +304 -0
  9. package/dist/main/delayed-img.js +1 -1
  10. package/dist/main/delayed-img.mjs +1 -1
  11. package/dist/main/gallery/gallery-mobile-swiper.js +2 -2
  12. package/dist/main/gallery/gallery-mobile-swiper.mjs +2 -2
  13. package/dist/main/index.js +1 -0
  14. package/dist/main/index.mjs +1 -1
  15. package/dist/main/loading.d.ts +1 -0
  16. package/dist/main/loading.js +7 -0
  17. package/dist/main/loading.mjs +7 -1
  18. package/dist/main/snake-loading-frame.js +3 -3
  19. package/dist/main/snake-loading-frame.mjs +3 -3
  20. package/dist/node_modules/.pnpm/{katex@0.16.38 → katex@0.16.43}/node_modules/katex/dist/katex.js +50 -16
  21. package/dist/node_modules/.pnpm/{katex@0.16.38 → katex@0.16.43}/node_modules/katex/dist/katex.mjs +50 -16
  22. package/dist/node_modules/.pnpm/mermaid@11.12.1/node_modules/mermaid/dist/chunks/mermaid.core/chunk-ABZYJK2D.js +1 -1
  23. package/dist/node_modules/.pnpm/mermaid@11.12.1/node_modules/mermaid/dist/chunks/mermaid.core/chunk-ABZYJK2D.mjs +1 -1
  24. package/dist/node_modules/.pnpm/{swiper@12.1.2 → swiper@12.1.3}/node_modules/swiper/shared/swiper-core.js +2 -1
  25. package/dist/node_modules/.pnpm/{swiper@12.1.2 → swiper@12.1.3}/node_modules/swiper/shared/swiper-core.mjs +2 -1
  26. package/dist/node_modules/.pnpm/{swiper@12.1.2 → swiper@12.1.3}/node_modules/swiper/swiper-react.js +2 -2
  27. package/dist/node_modules/.pnpm/{swiper@12.1.2 → swiper@12.1.3}/node_modules/swiper/swiper-react.mjs +2 -2
  28. package/package.json +1 -1
  29. package/src/fuma/fuma-page-genarator.tsx +11 -5
  30. package/src/fuma/mdx/index.ts +1 -0
  31. package/src/fuma/mdx/toc-clerk-portable.tsx +623 -0
  32. package/src/main/delayed-img.tsx +0 -1
  33. package/src/main/loading.tsx +8 -0
  34. package/src/main/snake-loading-frame.tsx +3 -3
  35. package/dist/node_modules/.pnpm/framer-motion@12.38.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/framer-motion/dist/es/utils/use-animation-frame.js +0 -24
  36. package/dist/node_modules/.pnpm/framer-motion@12.38.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/framer-motion/dist/es/utils/use-animation-frame.mjs +0 -22
  37. package/dist/node_modules/.pnpm/framer-motion@12.38.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/framer-motion/dist/es/value/use-combine-values.js +0 -40
  38. package/dist/node_modules/.pnpm/framer-motion@12.38.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/framer-motion/dist/es/value/use-combine-values.mjs +0 -38
  39. package/dist/node_modules/.pnpm/framer-motion@12.38.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/framer-motion/dist/es/value/use-computed.js +0 -22
  40. package/dist/node_modules/.pnpm/framer-motion@12.38.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/framer-motion/dist/es/value/use-computed.mjs +0 -20
  41. package/dist/node_modules/.pnpm/framer-motion@12.38.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/framer-motion/dist/es/value/use-motion-value.js +0 -41
  42. package/dist/node_modules/.pnpm/framer-motion@12.38.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/framer-motion/dist/es/value/use-motion-value.mjs +0 -39
  43. package/dist/node_modules/.pnpm/framer-motion@12.38.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/framer-motion/dist/es/value/use-transform.js +0 -50
  44. package/dist/node_modules/.pnpm/framer-motion@12.38.0_react-dom@19.2.4_react@19.2.4__react@19.2.4/node_modules/framer-motion/dist/es/value/use-transform.mjs +0 -48
  45. package/dist/node_modules/.pnpm/motion-dom@12.38.0/node_modules/motion-dom/dist/es/utils/transform.js +0 -16
  46. package/dist/node_modules/.pnpm/motion-dom@12.38.0/node_modules/motion-dom/dist/es/utils/transform.mjs +0 -14
  47. /package/dist/node_modules/.pnpm/{swiper@12.1.2 → swiper@12.1.3}/node_modules/swiper/modules/pagination.js +0 -0
  48. /package/dist/node_modules/.pnpm/{swiper@12.1.2 → swiper@12.1.3}/node_modules/swiper/modules/pagination.mjs +0 -0
  49. /package/dist/node_modules/.pnpm/{swiper@12.1.2 → swiper@12.1.3}/node_modules/swiper/shared/classes-to-selector.js +0 -0
  50. /package/dist/node_modules/.pnpm/{swiper@12.1.2 → swiper@12.1.3}/node_modules/swiper/shared/classes-to-selector.mjs +0 -0
  51. /package/dist/node_modules/.pnpm/{swiper@12.1.2 → swiper@12.1.3}/node_modules/swiper/shared/create-element-if-not-defined.js +0 -0
  52. /package/dist/node_modules/.pnpm/{swiper@12.1.2 → swiper@12.1.3}/node_modules/swiper/shared/create-element-if-not-defined.mjs +0 -0
  53. /package/dist/node_modules/.pnpm/{swiper@12.1.2 → swiper@12.1.3}/node_modules/swiper/shared/ssr-window.esm.js +0 -0
  54. /package/dist/node_modules/.pnpm/{swiper@12.1.2 → swiper@12.1.3}/node_modules/swiper/shared/ssr-window.esm.mjs +0 -0
  55. /package/dist/node_modules/.pnpm/{swiper@12.1.2 → swiper@12.1.3}/node_modules/swiper/shared/update-on-virtual-data.js +0 -0
  56. /package/dist/node_modules/.pnpm/{swiper@12.1.2 → swiper@12.1.3}/node_modules/swiper/shared/update-on-virtual-data.mjs +0 -0
  57. /package/dist/node_modules/.pnpm/{swiper@12.1.2 → swiper@12.1.3}/node_modules/swiper/shared/update-swiper.js +0 -0
  58. /package/dist/node_modules/.pnpm/{swiper@12.1.2 → swiper@12.1.3}/node_modules/swiper/shared/update-swiper.mjs +0 -0
  59. /package/dist/node_modules/.pnpm/{swiper@12.1.2 → swiper@12.1.3}/node_modules/swiper/shared/utils.js +0 -0
  60. /package/dist/node_modules/.pnpm/{swiper@12.1.2 → swiper@12.1.3}/node_modules/swiper/shared/utils.mjs +0 -0
@@ -0,0 +1,623 @@
1
+ 'use client';
2
+
3
+ import * as Primitive from 'fumadocs-core/toc';
4
+ import {
5
+ PageTOC,
6
+ PageTOCPopover,
7
+ PageTOCPopoverContent,
8
+ PageTOCPopoverTrigger,
9
+ PageTOCTitle,
10
+ } from 'fumadocs-ui/layouts/docs/page';
11
+ import {
12
+ type ComponentProps,
13
+ type ReactNode,
14
+ useLayoutEffect,
15
+ useMemo,
16
+ useRef,
17
+ useState,
18
+ } from 'react';
19
+ import {
20
+ themeIconColor,
21
+ themeSvgIconColor,
22
+ } from '@windrun-huaiin/base-ui/lib';
23
+
24
+ type TOCItemType = Primitive.TOCItemType;
25
+
26
+ type PortableClerkTOCProps = {
27
+ toc: TOCItemType[];
28
+ header?: ReactNode;
29
+ footer?: ReactNode;
30
+ title?: ReactNode;
31
+ emptyLabel?: ReactNode;
32
+ className?: string;
33
+ };
34
+
35
+ type ClerkItemMeta = {
36
+ item: TOCItemType;
37
+ resolvedContent: ReactNode;
38
+ stepNumber: string | null;
39
+ itemPadding: number;
40
+ lineOffset: number;
41
+ };
42
+
43
+ type ClerkItemMeasure = {
44
+ url: string;
45
+ y: number;
46
+ x: number;
47
+ stepNumber: string | null;
48
+ };
49
+
50
+ // Base stroke width for both the inactive rail and the active highlight path.
51
+ const CLERK_PATH_STROKE_WIDTH = 1;
52
+ // Radius of the moving endpoint dot that marks the latest active heading.
53
+ const CLERK_ACTIVE_DOT_RADIUS = 2;
54
+ // Max vertical space reserved for a turn inside the gap between two headings.
55
+ const CLERK_TURN_CURVE_HEIGHT = 12;
56
+ // Multiplier for bezier control points; higher values make the turn rounder.
57
+ const CLERK_TURN_CONTROL_FACTOR = 0.68;
58
+ // Safety margin that keeps turns away from the heading rows themselves.
59
+ const CLERK_TURN_GAP_MARGIN = 7;
60
+
61
+ export function PortableClerkTOC({
62
+ toc,
63
+ header,
64
+ footer,
65
+ title,
66
+ emptyLabel = 'No headings',
67
+ className,
68
+ }: PortableClerkTOCProps) {
69
+ return (
70
+ <PageTOC className={className}>
71
+ {header}
72
+ {title ?? <PageTOCTitle />}
73
+ <PortableClerkTOCScrollArea>
74
+ <PortableClerkTOCItems toc={toc} emptyLabel={emptyLabel} />
75
+ </PortableClerkTOCScrollArea>
76
+ {footer}
77
+ </PageTOC>
78
+ );
79
+ }
80
+
81
+ export function PortableClerkTOCPopover({
82
+ toc,
83
+ header,
84
+ footer,
85
+ emptyLabel = 'No headings',
86
+ }: Omit<PortableClerkTOCProps, 'title' | 'className'>) {
87
+ return (
88
+ <PageTOCPopover>
89
+ <PageTOCPopoverTrigger />
90
+ <PageTOCPopoverContent>
91
+ {header}
92
+ <PortableClerkTOCScrollArea>
93
+ <PortableClerkTOCItems toc={toc} emptyLabel={emptyLabel} />
94
+ </PortableClerkTOCScrollArea>
95
+ {footer}
96
+ </PageTOCPopoverContent>
97
+ </PageTOCPopover>
98
+ );
99
+ }
100
+
101
+ export function PortableClerkTOCScrollArea({
102
+ ref,
103
+ className,
104
+ ...props
105
+ }: ComponentProps<'div'>) {
106
+ const viewRef = useRef<HTMLDivElement>(null);
107
+
108
+ return (
109
+ <div
110
+ ref={mergeRefs(viewRef, ref)}
111
+ className={cn(
112
+ 'relative min-h-0 text-sm ms-px overflow-auto [scrollbar-width:none] mask-[linear-gradient(to_bottom,transparent,white_16px,white_calc(100%-16px),transparent)] py-3',
113
+ className,
114
+ )}
115
+ {...props}
116
+ >
117
+ <Primitive.ScrollProvider containerRef={viewRef}>
118
+ {props.children}
119
+ </Primitive.ScrollProvider>
120
+ </div>
121
+ );
122
+ }
123
+
124
+ export function PortableClerkTOCItems({
125
+ toc,
126
+ emptyLabel = 'No headings',
127
+ ref,
128
+ className,
129
+ ...props
130
+ }: ComponentProps<'div'> & { toc: TOCItemType[]; emptyLabel?: ReactNode }) {
131
+ const containerRef = useRef<HTMLDivElement>(null);
132
+ const activeAnchors = Primitive.useActiveAnchors();
133
+ const itemRefs = useRef<(HTMLAnchorElement | null)[]>([]);
134
+ const contentRefs = useRef<(HTMLSpanElement | null)[]>([]);
135
+ const [layout, setLayout] = useState<{
136
+ height: number;
137
+ items: ClerkItemMeasure[];
138
+ }>({
139
+ height: 0,
140
+ items: [],
141
+ });
142
+
143
+ const metas = useMemo(() => toc.map(resolveClerkItem), [toc]);
144
+ const outlinePath = useMemo(
145
+ () => buildOutlinePath(layout.items),
146
+ [layout.items],
147
+ );
148
+ const activeItems = useMemo(
149
+ () => getActiveItems(layout.items, activeAnchors),
150
+ [activeAnchors, layout.items],
151
+ );
152
+ const activePath = useMemo(() => buildOutlinePath(activeItems), [activeItems]);
153
+ const activeEndpoint = useMemo(
154
+ () => getActiveEndpoint(activeItems),
155
+ [activeItems],
156
+ );
157
+
158
+ useLayoutEffect(() => {
159
+ const container = containerRef.current;
160
+ if (!container) return;
161
+
162
+ let frame = 0;
163
+ const updateLayout = () => {
164
+ frame = 0;
165
+ const nextItems = metas.flatMap((meta, index) => {
166
+ const element = itemRefs.current[index];
167
+ const content = contentRefs.current[index];
168
+ if (!element || !content) return [];
169
+
170
+ const y = measureItemLineY(element, content);
171
+
172
+ return [
173
+ {
174
+ url: meta.item.url,
175
+ y,
176
+ x: meta.lineOffset,
177
+ stepNumber: meta.stepNumber,
178
+ },
179
+ ];
180
+ });
181
+
182
+ setLayout((prev) => {
183
+ const next = {
184
+ height: container.clientHeight,
185
+ items: nextItems,
186
+ };
187
+
188
+ if (isSameLayout(prev, next)) return prev;
189
+ return next;
190
+ });
191
+ };
192
+
193
+ const queueUpdate = () => {
194
+ if (frame !== 0) cancelAnimationFrame(frame);
195
+ frame = requestAnimationFrame(updateLayout);
196
+ };
197
+
198
+ queueUpdate();
199
+
200
+ const observer = new ResizeObserver(queueUpdate);
201
+ observer.observe(container);
202
+
203
+ for (const element of itemRefs.current) {
204
+ if (element) observer.observe(element);
205
+ }
206
+
207
+ return () => {
208
+ if (frame !== 0) cancelAnimationFrame(frame);
209
+ observer.disconnect();
210
+ };
211
+ }, [metas]);
212
+
213
+ if (toc.length === 0) {
214
+ return (
215
+ <div className="rounded-lg border bg-fd-card p-3 text-xs text-fd-muted-foreground">
216
+ {emptyLabel}
217
+ </div>
218
+ );
219
+ }
220
+
221
+ return (
222
+ <div
223
+ ref={mergeRefs(containerRef, ref)}
224
+ className={cn('relative flex flex-col', className)}
225
+ {...props}
226
+ >
227
+ <ClerkOutline
228
+ path={outlinePath}
229
+ items={layout.items}
230
+ activePath={activePath}
231
+ activeAnchors={activeAnchors}
232
+ activeEndpoint={activeEndpoint}
233
+ />
234
+ {metas.map((meta, i) => (
235
+ <PortableClerkTOCItem
236
+ key={meta.item.url}
237
+ item={meta.item}
238
+ isActive={activeAnchors.includes(meta.item.url.slice(1))}
239
+ resolvedContent={meta.resolvedContent}
240
+ itemPadding={meta.itemPadding}
241
+ contentRef={(node: HTMLSpanElement | null) => {
242
+ contentRefs.current[i] = node;
243
+ }}
244
+ ref={(node: HTMLAnchorElement | null) => {
245
+ itemRefs.current[i] = node;
246
+ }}
247
+ />
248
+ ))}
249
+ </div>
250
+ );
251
+ }
252
+
253
+ function PortableClerkTOCItem({
254
+ item,
255
+ isActive,
256
+ resolvedContent,
257
+ itemPadding,
258
+ contentRef,
259
+ ref,
260
+ }: {
261
+ item: TOCItemType;
262
+ isActive: boolean;
263
+ resolvedContent: ReactNode;
264
+ itemPadding: number;
265
+ contentRef?: ((node: HTMLSpanElement | null) => void) | null;
266
+ ref?: ((node: HTMLAnchorElement | null) => void) | null;
267
+ }) {
268
+ return (
269
+ <Primitive.TOCItem
270
+ ref={ref}
271
+ href={item.url}
272
+ data-clerk-item=""
273
+ style={{
274
+ paddingInlineStart: itemPadding,
275
+ }}
276
+ className={cn(
277
+ 'prose group relative py-1.5 text-sm transition-colors wrap-anywhere first:pt-0 last:pb-0 hover:text-fd-accent-foreground',
278
+ isActive ? themeIconColor : 'text-fd-muted-foreground',
279
+ )}
280
+ >
281
+ <span ref={contentRef} className="relative z-10">
282
+ {resolvedContent}
283
+ </span>
284
+ </Primitive.TOCItem>
285
+ );
286
+ }
287
+
288
+ function ClerkOutline({
289
+ path,
290
+ items,
291
+ activePath,
292
+ activeAnchors,
293
+ activeEndpoint,
294
+ }: {
295
+ path: string;
296
+ items: ClerkItemMeasure[];
297
+ activePath: string;
298
+ activeAnchors: string[];
299
+ activeEndpoint: { x: number; y: number } | null;
300
+ }) {
301
+ if (!path) return null;
302
+
303
+ const activeSet = new Set(activeAnchors);
304
+
305
+ return (
306
+ <>
307
+ <svg
308
+ aria-hidden="true"
309
+ className="pointer-events-none absolute inset-0 z-0 overflow-visible"
310
+ width="100%"
311
+ height="100%"
312
+ >
313
+ <path
314
+ d={path}
315
+ className="stroke-fd-foreground/15"
316
+ fill="none"
317
+ strokeWidth={CLERK_PATH_STROKE_WIDTH}
318
+ strokeLinecap="round"
319
+ strokeLinejoin="round"
320
+ />
321
+ </svg>
322
+ <svg
323
+ aria-hidden="true"
324
+ className="pointer-events-none absolute inset-0 z-0 overflow-visible"
325
+ width="100%"
326
+ height="100%"
327
+ >
328
+ {activePath ? (
329
+ <path
330
+ d={activePath}
331
+ fill="none"
332
+ strokeWidth={CLERK_PATH_STROKE_WIDTH}
333
+ strokeLinecap="round"
334
+ strokeLinejoin="round"
335
+ stroke={themeSvgIconColor}
336
+ />
337
+ ) : null}
338
+ </svg>
339
+ <svg
340
+ aria-hidden="true"
341
+ className="pointer-events-none absolute inset-0 z-0 overflow-visible"
342
+ width="100%"
343
+ height="100%"
344
+ >
345
+ {activeEndpoint ? (
346
+ <circle
347
+ cx={activeEndpoint.x}
348
+ cy={activeEndpoint.y}
349
+ r={CLERK_ACTIVE_DOT_RADIUS}
350
+ fill={themeSvgIconColor}
351
+ />
352
+ ) : null}
353
+ </svg>
354
+ <svg
355
+ aria-hidden="true"
356
+ className="pointer-events-none absolute inset-0 z-1 overflow-visible"
357
+ width="100%"
358
+ height="100%"
359
+ >
360
+ {items.map((item) => {
361
+ if (!item.stepNumber) return null;
362
+
363
+ const isActive = activeSet.has(item.url.slice(1));
364
+
365
+ return (
366
+ <g key={item.url} transform={`translate(${item.x}, ${item.y})`}>
367
+ <circle
368
+ r="7"
369
+ fill={isActive ? themeSvgIconColor : undefined}
370
+ className={cn(!isActive && 'fill-black dark:fill-white')}
371
+ />
372
+ <text
373
+ y="0.5"
374
+ textAnchor="middle"
375
+ dominantBaseline="middle"
376
+ className="fill-white text-[9px] font-medium dark:fill-black"
377
+ >
378
+ {item.stepNumber}
379
+ </text>
380
+ </g>
381
+ );
382
+ })}
383
+ </svg>
384
+ </>
385
+ );
386
+ }
387
+
388
+ function getItemOffset(depth: number): number {
389
+ if (depth <= 2) return 14;
390
+ if (depth === 3) return 26;
391
+ return 36;
392
+ }
393
+
394
+ function getLineOffset(depth: number): number {
395
+ return depth >= 3 ? 18 : 6;
396
+ }
397
+
398
+ function getVisualLinePosition(depth: number): number {
399
+ return getLineOffset(depth);
400
+ }
401
+
402
+ function resolveClerkItem(item: TOCItemType): ClerkItemMeta {
403
+ const isH3 = item.depth === 3;
404
+ const rawTitle = typeof item.title === 'string' ? item.title : '';
405
+ const { isStep, displayStep, content } = getStepInfoFromTitle(rawTitle);
406
+ let stepNumber: string | null = isH3 && isStep ? String(displayStep) : null;
407
+ let resolvedContent: ReactNode = item.title;
408
+
409
+ if (isH3 && isStep) {
410
+ resolvedContent = content ?? item.title;
411
+ }
412
+
413
+ if (isH3 && !stepNumber) {
414
+ const urlNum = getDigitsFromUrl(item.url);
415
+ if (urlNum != null) {
416
+ const clamped = Math.max(0, Math.min(19, urlNum));
417
+ stepNumber = String(clamped);
418
+ if (typeof rawTitle === 'string') {
419
+ const match = rawTitle.match(/^(\d+(?:\.\d+)*\.?)\s+(.+)$/);
420
+ if (match?.[2]) {
421
+ resolvedContent = match[2];
422
+ }
423
+ }
424
+ }
425
+ }
426
+
427
+ return {
428
+ item,
429
+ resolvedContent,
430
+ stepNumber,
431
+ itemPadding: getItemOffset(item.depth),
432
+ lineOffset: getVisualLinePosition(item.depth),
433
+ };
434
+ }
435
+
436
+ function buildOutlinePath(items: ClerkItemMeasure[]): string {
437
+ if (items.length === 0) return '';
438
+
439
+ const [first] = items;
440
+ const last = items.at(-1);
441
+ if (!last) return '';
442
+
443
+ let path = `M ${round(first.x)} ${round(first.y)}`;
444
+
445
+ for (let i = 1; i < items.length; i++) {
446
+ path += ` ${buildTurnSegment(items[i - 1], items[i])}`;
447
+ }
448
+
449
+ return path;
450
+ }
451
+
452
+ function buildTurnSegment(
453
+ previous: ClerkItemMeasure,
454
+ current: ClerkItemMeasure,
455
+ ): string {
456
+ if (Math.abs(previous.x - current.x) <= 0.5) {
457
+ return `L ${round(current.x)} ${round(current.y)}`;
458
+ }
459
+
460
+ const distanceY = current.y - previous.y;
461
+ if (distanceY <= 0) {
462
+ return `L ${round(current.x)} ${round(current.y)}`;
463
+ }
464
+
465
+ const gapMidY = previous.y + distanceY / 2;
466
+ const maxCurveHeight = Math.max(distanceY - CLERK_TURN_GAP_MARGIN * 2, 0);
467
+ const curveHeight = Math.min(
468
+ CLERK_TURN_CURVE_HEIGHT,
469
+ Math.max(maxCurveHeight, 0),
470
+ );
471
+ if (curveHeight <= 0.5) {
472
+ return `L ${round(current.x)} ${round(current.y)}`;
473
+ }
474
+
475
+ const turnStartY = gapMidY - curveHeight / 2;
476
+ const turnEndY = gapMidY + curveHeight / 2;
477
+ const controlDelta = curveHeight * CLERK_TURN_CONTROL_FACTOR;
478
+
479
+ return [
480
+ `L ${round(previous.x)} ${round(turnStartY)}`,
481
+ `C ${round(previous.x)} ${round(turnStartY + controlDelta)} ${round(
482
+ current.x,
483
+ )} ${round(turnEndY - controlDelta)} ${round(current.x)} ${round(
484
+ turnEndY,
485
+ )}`,
486
+ `L ${round(current.x)} ${round(current.y)}`,
487
+ ].join(' ');
488
+ }
489
+
490
+ function getActiveItems(
491
+ items: ClerkItemMeasure[],
492
+ activeAnchors: string[],
493
+ ): ClerkItemMeasure[] {
494
+ if (items.length === 0 || activeAnchors.length === 0) return [];
495
+
496
+ return items.filter((item) => activeAnchors.includes(item.url.slice(1)));
497
+ }
498
+
499
+ function getActiveEndpoint(
500
+ items: ClerkItemMeasure[],
501
+ ): { x: number; y: number } | null {
502
+ if (items.length === 0) return null;
503
+
504
+ const last = items.at(-1);
505
+ if (!last) return null;
506
+
507
+ return {
508
+ x: last.x,
509
+ y: last.y,
510
+ };
511
+ }
512
+
513
+ function isSameLayout(
514
+ previous: { height: number; items: ClerkItemMeasure[] },
515
+ next: { height: number; items: ClerkItemMeasure[] },
516
+ ): boolean {
517
+ if (Math.abs(previous.height - next.height) > 0.5) return false;
518
+ if (previous.items.length !== next.items.length) return false;
519
+
520
+ for (let i = 0; i < previous.items.length; i++) {
521
+ const prev = previous.items[i];
522
+ const curr = next.items[i];
523
+ if (!prev || !curr) return false;
524
+ if (prev.url !== curr.url || prev.stepNumber !== curr.stepNumber) {
525
+ return false;
526
+ }
527
+ if (Math.abs(prev.y - curr.y) > 0.5) return false;
528
+ if (Math.abs(prev.x - curr.x) > 0.5) return false;
529
+ }
530
+
531
+ return true;
532
+ }
533
+
534
+ function measureItemLineY(
535
+ element: HTMLAnchorElement,
536
+ content: HTMLSpanElement,
537
+ ): number {
538
+ const anchorRect = element.getBoundingClientRect();
539
+ const lineRects = Array.from(content.getClientRects()).filter(
540
+ (rect) => rect.height > 0,
541
+ );
542
+
543
+ if (lineRects.length > 0) {
544
+ const lastRect = lineRects.at(-1);
545
+ if (lastRect) {
546
+ return (
547
+ element.offsetTop +
548
+ (lastRect.top - anchorRect.top) +
549
+ lastRect.height / 2
550
+ );
551
+ }
552
+ }
553
+
554
+ const styles = getComputedStyle(element);
555
+ const top = element.offsetTop + parseFloat(styles.paddingTop);
556
+ const bottom =
557
+ element.offsetTop +
558
+ element.clientHeight -
559
+ parseFloat(styles.paddingBottom);
560
+ return (top + bottom) / 2;
561
+ }
562
+
563
+ function round(value: number): number {
564
+ return Number(value.toFixed(2));
565
+ }
566
+
567
+ function getDigitsFromUrl(url: string): number | null {
568
+ const match = /^#(\d+)-/.exec(url);
569
+ if (!match) return null;
570
+ const value = Number.parseInt(match[1], 10);
571
+ return Number.isNaN(value) ? null : value;
572
+ }
573
+
574
+ function getStepInfoFromTitle(title: string): {
575
+ isStep: boolean;
576
+ displayStep: number | null;
577
+ content: string | null;
578
+ } {
579
+ const trimmed = title.trim();
580
+ const match = trimmed.match(/^(\d+(?:\.\d+)*\.?)\s+(.+)$/);
581
+ if (!match) return { isStep: false, displayStep: null, content: null };
582
+
583
+ const content = (match[2] ?? '').trim();
584
+ if (content.length === 0) {
585
+ return { isStep: false, displayStep: null, content: null };
586
+ }
587
+
588
+ const numericPart = match[1].replace(/\.$/, '');
589
+ const parts = numericPart.split('.').map((part) => Number.parseInt(part, 10));
590
+ const lastPart = parts.at(-1);
591
+ if (lastPart == null || Number.isNaN(lastPart)) {
592
+ return { isStep: false, displayStep: null, content: null };
593
+ }
594
+
595
+ const clamped = Math.max(0, Math.min(19, lastPart));
596
+ return { isStep: true, displayStep: clamped, content };
597
+ }
598
+
599
+ function cn(...inputs: Array<string | false | null | undefined>): string {
600
+ return inputs.filter(Boolean).join(' ');
601
+ }
602
+
603
+ function mergeRefs<T>(
604
+ ...refs: Array<
605
+ React.Ref<T> | ((instance: T | null) => void) | null | undefined
606
+ >
607
+ ) {
608
+ return (node: T | null) => {
609
+ for (const ref of refs) {
610
+ if (!ref) continue;
611
+ if (typeof ref === 'function') {
612
+ ref(node);
613
+ continue;
614
+ }
615
+
616
+ try {
617
+ (ref as React.MutableRefObject<T | null>).current = node;
618
+ } catch {
619
+ // ignore readonly refs
620
+ }
621
+ }
622
+ };
623
+ }
@@ -51,7 +51,6 @@ export function DelayedImg({
51
51
  shape="rounded-rect"
52
52
  loading
53
53
  themeColor={themeSvgIconColor}
54
- strokeWidth={3}
55
54
  className={cn(
56
55
  "absolute inset-0 rounded-[inherit] border shadow-sm bg-white/70 dark:bg-white/5",
57
56
  themeBgColor,
@@ -10,6 +10,14 @@ const SPACING = 12; // px, space between dot centers
10
10
  const ANIMATION_DURATION = 1.8; // seconds
11
11
  const STAGGER_DELAY_FACTOR = 0.08; // seconds, delay per unit of distance from center
12
12
 
13
+ export function getLoadingCycleDurationMs() {
14
+ const centerX = (NUM_COLS - 1) / 2;
15
+ const centerY = (NUM_ROWS - 1) / 2;
16
+ const furthestDistance = Math.sqrt(Math.pow(centerY, 2) + Math.pow(centerX, 2));
17
+
18
+ return (ANIMATION_DURATION + furthestDistance * STAGGER_DELAY_FACTOR) * 1000;
19
+ }
20
+
13
21
  function clampChannel(value: number) {
14
22
  return Math.max(0, Math.min(255, Math.round(value)));
15
23
  }
@@ -32,12 +32,12 @@ const TRACK_COLOR = 'rgba(148, 163, 184, 0.22)';
32
32
  const BODY_LENGTH_RATIO = 0.26;
33
33
  const EXIT_DURATION_MS = 260;
34
34
  const LOOP_DURATION_SECONDS = 1.85;
35
- const DEFAULT_CIRCLE_STROKE = 2;
36
- const DEFAULT_RECT_STROKE = 3;
35
+ const DEFAULT_CIRCLE_STROKE = 0.5;
36
+ const DEFAULT_RECT_STROKE = 1;
37
37
  const MIN_FRAME_SIZE = 2;
38
38
  const MIN_BODY_LENGTH = 24;
39
39
  const MAX_BODY_LENGTH_RATIO = 0.36;
40
- const RECT_MIN_STROKE_WIDTH = 3;
40
+ const RECT_MIN_STROKE_WIDTH = 1;
41
41
 
42
42
  function clampProgress(progress: number): number {
43
43
  if (!Number.isFinite(progress)) {
@@ -1,24 +0,0 @@
1
- "use client";
2
- 'use strict';
3
-
4
- var React = require('react');
5
- var MotionConfigContext = require('../context/MotionConfigContext.js');
6
- var frame = require('../../../../../../motion-dom@12.38.0/node_modules/motion-dom/dist/es/frameloop/frame.js');
7
-
8
- function useAnimationFrame(callback) {
9
- const initialTimestamp = React.useRef(0);
10
- const { isStatic } = React.useContext(MotionConfigContext.MotionConfigContext);
11
- React.useEffect(() => {
12
- if (isStatic)
13
- return;
14
- const provideTimeSinceStart = ({ timestamp, delta }) => {
15
- if (!initialTimestamp.current)
16
- initialTimestamp.current = timestamp;
17
- callback(timestamp - initialTimestamp.current, delta);
18
- };
19
- frame.frame.update(provideTimeSinceStart, true);
20
- return () => frame.cancelFrame(provideTimeSinceStart);
21
- }, [callback]);
22
- }
23
-
24
- exports.useAnimationFrame = useAnimationFrame;
@@ -1,22 +0,0 @@
1
- "use client";
2
- import { useRef, useContext, useEffect } from 'react';
3
- import { MotionConfigContext } from '../context/MotionConfigContext.mjs';
4
- import { frame, cancelFrame } from '../../../../../../motion-dom@12.38.0/node_modules/motion-dom/dist/es/frameloop/frame.mjs';
5
-
6
- function useAnimationFrame(callback) {
7
- const initialTimestamp = useRef(0);
8
- const { isStatic } = useContext(MotionConfigContext);
9
- useEffect(() => {
10
- if (isStatic)
11
- return;
12
- const provideTimeSinceStart = ({ timestamp, delta }) => {
13
- if (!initialTimestamp.current)
14
- initialTimestamp.current = timestamp;
15
- callback(timestamp - initialTimestamp.current, delta);
16
- };
17
- frame.update(provideTimeSinceStart, true);
18
- return () => cancelFrame(provideTimeSinceStart);
19
- }, [callback]);
20
- }
21
-
22
- export { useAnimationFrame };