@sphynxoid/stepwise 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -59,8 +59,17 @@ function useSteps(items, opts) {
59
59
 
60
60
  // src/use-step-player.ts
61
61
  import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
62
- function useStepPlayer({ total, baseDelayMs = 900, autoPlay = false, engine, syncHash, onEnd }) {
62
+ function useStepPlayer({
63
+ total,
64
+ baseDelayMs = 900,
65
+ autoPlay = false,
66
+ engine,
67
+ syncHash,
68
+ onEnd,
69
+ respectReducedMotion = true
70
+ }) {
63
71
  const core = useSteps(total);
72
+ const { go, next: coreNext, prev: corePrev, first } = core;
64
73
  const [started, setStarted] = useState2(autoPlay && total > 0);
65
74
  const [playing, setPlaying] = useState2(autoPlay && total > 0);
66
75
  const [speed, setSpeed] = useState2(1);
@@ -81,10 +90,16 @@ function useStepPlayer({ total, baseDelayMs = 900, autoPlay = false, engine, syn
81
90
  }, []);
82
91
  const index = started && core.index >= 0 ? core.index : -1;
83
92
  const atEnd = total > 0 && index >= 0 && index >= total - 1;
84
- const engineRef = useRef2(engine);
85
- engineRef.current = engine;
86
93
  const totalRef = useRef2(total);
87
94
  totalRef.current = total;
95
+ const cursorRef = useRef2(core.index);
96
+ cursorRef.current = core.index;
97
+ const playingRef = useRef2(playing);
98
+ playingRef.current = playing;
99
+ const atEndRef = useRef2(atEnd);
100
+ atEndRef.current = atEnd;
101
+ const engineRef = useRef2(engine);
102
+ engineRef.current = engine;
88
103
  const engineApiRef = useRef2({});
89
104
  useEffect2(() => {
90
105
  if (!engine) return;
@@ -92,7 +107,7 @@ function useStepPlayer({ total, baseDelayMs = 900, autoPlay = false, engine, syn
92
107
  get total() {
93
108
  return totalRef.current;
94
109
  },
95
- onFrame: (f) => core.go(f),
110
+ onFrame: (f) => go(f),
96
111
  onEnd: () => {
97
112
  markEnded();
98
113
  setPlaying(false);
@@ -108,17 +123,24 @@ function useStepPlayer({ total, baseDelayMs = 900, autoPlay = false, engine, syn
108
123
  engineApiRef.current = {};
109
124
  handle.disconnect();
110
125
  };
111
- }, [engine]);
126
+ }, [engine, go, markEnded]);
127
+ useEffect2(() => {
128
+ if (!respectReducedMotion || !autoPlay) return;
129
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
130
+ if (!window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
131
+ setPlaying(false);
132
+ engineApiRef.current.pause?.();
133
+ }, []);
112
134
  const start = useCallback2(() => {
113
135
  setStarted(true);
114
136
  setPlaying(true);
115
137
  clearEnded();
116
- core.first();
138
+ first();
117
139
  if (engineRef.current) {
118
140
  engineApiRef.current.seek?.(0);
119
141
  engineApiRef.current.play?.();
120
142
  }
121
- }, [core]);
143
+ }, [first, clearEnded]);
122
144
  const reset = useCallback2(() => {
123
145
  setStarted(false);
124
146
  setPlaying(false);
@@ -127,62 +149,59 @@ function useStepPlayer({ total, baseDelayMs = 900, autoPlay = false, engine, syn
127
149
  }, [clearEnded]);
128
150
  const seek = useCallback2(
129
151
  (f) => {
130
- if (total === 0) return;
152
+ const t = totalRef.current;
153
+ if (t === 0) return;
131
154
  setStarted(true);
132
155
  setPlaying(false);
133
156
  clearEnded();
134
- core.go(Math.max(0, Math.min(f, total - 1)));
157
+ go(Math.max(0, Math.min(f, t - 1)));
135
158
  if (engineRef.current) {
136
159
  engineApiRef.current.pause?.();
137
160
  engineApiRef.current.seek?.(f);
138
161
  }
139
162
  },
140
- [total, core, clearEnded]
163
+ [go, clearEnded]
141
164
  );
142
165
  const next = useCallback2(
143
166
  (onEmpty) => {
144
167
  setPlaying(false);
145
- if (total === 0) {
168
+ const t = totalRef.current;
169
+ if (t === 0) {
146
170
  onEmpty?.();
147
171
  return;
148
172
  }
149
173
  setStarted(true);
150
174
  clearEnded();
151
- core.next();
175
+ const target = Math.min(cursorRef.current + 1, t - 1);
176
+ coreNext();
152
177
  if (engineRef.current) {
153
178
  engineApiRef.current.pause?.();
154
- engineApiRef.current.seek?.(Math.min(core.index + 1, total - 1));
179
+ engineApiRef.current.seek?.(target);
155
180
  }
156
181
  },
157
- [total, core, clearEnded]
182
+ [coreNext, clearEnded]
158
183
  );
159
184
  const prev = useCallback2(() => {
160
185
  setPlaying(false);
161
186
  clearEnded();
162
- core.prev();
187
+ const target = Math.max(0, cursorRef.current - 1);
188
+ corePrev();
163
189
  if (engineRef.current) {
164
190
  engineApiRef.current.pause?.();
165
- engineApiRef.current.seek?.(Math.max(0, core.index - 1));
191
+ engineApiRef.current.seek?.(target);
166
192
  }
167
- }, [core, clearEnded]);
193
+ }, [corePrev, clearEnded]);
168
194
  const toggle = useCallback2(
169
195
  (onEmpty) => {
170
- if (total === 0) {
196
+ if (totalRef.current === 0) {
171
197
  onEmpty?.();
172
198
  return;
173
199
  }
174
- if (!playing && atEnd) {
175
- setStarted(true);
176
- setPlaying(true);
177
- clearEnded();
178
- core.first();
179
- if (engineRef.current) {
180
- engineApiRef.current.seek?.(0);
181
- engineApiRef.current.play?.();
182
- }
200
+ if (!playingRef.current && atEndRef.current) {
201
+ start();
183
202
  return;
184
203
  }
185
- const nextPlaying = !playing;
204
+ const nextPlaying = !playingRef.current;
186
205
  setPlaying(nextPlaying);
187
206
  setStarted(true);
188
207
  if (engineRef.current) {
@@ -190,7 +209,7 @@ function useStepPlayer({ total, baseDelayMs = 900, autoPlay = false, engine, syn
190
209
  else engineApiRef.current.pause?.();
191
210
  }
192
211
  },
193
- [total, playing, atEnd, core, clearEnded]
212
+ [start]
194
213
  );
195
214
  const changeSpeed = useCallback2((s) => {
196
215
  setSpeed(s);
@@ -207,25 +226,22 @@ function useStepPlayer({ total, baseDelayMs = 900, autoPlay = false, engine, syn
207
226
  setPlaying(false);
208
227
  return;
209
228
  }
210
- const id = setTimeout(() => core.next(), baseDelayMs / speed);
229
+ const id = setTimeout(() => coreNext(), baseDelayMs / speed);
211
230
  return () => clearTimeout(id);
212
- }, [playing, started, core, total, speed, baseDelayMs, markEnded]);
213
- const hashKey = syncHash;
214
- const coreGoRef = useRef2(core.go);
215
- coreGoRef.current = core.go;
231
+ }, [playing, started, core.index, coreNext, total, speed, baseDelayMs, markEnded]);
216
232
  const lastWrittenHash = useRef2("");
217
233
  const readHash = useCallback2(() => {
218
- if (!hashKey || typeof window === "undefined" || total === 0) return;
234
+ if (!syncHash || typeof window === "undefined" || total === 0) return;
219
235
  const hash = window.location.hash;
220
236
  if (hash === lastWrittenHash.current) return;
221
- const match = hash.match(new RegExp(`[#&]${hashKey}=(\\d+)`));
222
- if (!match) return;
223
- const f = Number(match[1]);
224
- if (Number.isFinite(f) && f >= 0 && f < total) {
237
+ const value = readHashParam(hash, syncHash);
238
+ if (value === null || !/^\d+$/.test(value)) return;
239
+ const f = Number(value);
240
+ if (f < total) {
225
241
  setStarted(true);
226
- coreGoRef.current(f);
242
+ go(f);
227
243
  }
228
- }, [hashKey, total]);
244
+ }, [syncHash, total, go]);
229
245
  useEffect2(() => {
230
246
  readHash();
231
247
  const onHashChange = () => readHash();
@@ -234,7 +250,7 @@ function useStepPlayer({ total, baseDelayMs = 900, autoPlay = false, engine, syn
234
250
  }, [readHash]);
235
251
  useEffect2(() => {
236
252
  if (!syncHash || index < 0 || typeof window === "undefined") return;
237
- const next2 = `#${syncHash}=${index}`;
253
+ const next2 = writeHashParam(window.location.hash, syncHash, String(index));
238
254
  if (window.location.hash !== next2) {
239
255
  lastWrittenHash.current = next2;
240
256
  window.history.replaceState(null, "", next2);
@@ -261,25 +277,395 @@ function useStepPlayer({ total, baseDelayMs = 900, autoPlay = false, engine, syn
261
277
  [total, index, playing, speed, atEnd, ended, start, reset, seek, next, prev, toggle, changeSpeed]
262
278
  );
263
279
  }
280
+ function hashParts(hash) {
281
+ const body = hash.startsWith("#") ? hash.slice(1) : hash;
282
+ return body ? body.split("&") : [];
283
+ }
284
+ function readHashParam(hash, key) {
285
+ for (const part of hashParts(hash)) {
286
+ const eq = part.indexOf("=");
287
+ if (eq > 0 && part.slice(0, eq) === key) return part.slice(eq + 1);
288
+ }
289
+ return null;
290
+ }
291
+ function writeHashParam(hash, key, value) {
292
+ const parts = hashParts(hash);
293
+ const pair = `${key}=${value}`;
294
+ const i = parts.findIndex((p) => p.startsWith(`${key}=`));
295
+ if (i >= 0) parts[i] = pair;
296
+ else parts.push(pair);
297
+ return `#${parts.join("&")}`;
298
+ }
299
+
300
+ // src/use-trace.ts
301
+ import { useCallback as useCallback3, useMemo as useMemo3, useRef as useRef3 } from "react";
302
+ function useTrace(steps, opts) {
303
+ const player = useStepPlayer({ ...opts, total: steps.length });
304
+ const stepsRef = useRef3(steps);
305
+ stepsRef.current = steps;
306
+ const frame = steps.length === 0 ? -1 : Math.max(0, Math.min(player.index, steps.length - 1));
307
+ const step = frame >= 0 ? steps[frame] : void 0;
308
+ const toneAt = useCallback3((i) => stepsRef.current[i]?.tone ?? "neutral", []);
309
+ const titleAt = useCallback3((i) => stepsRef.current[i]?.label ?? `step ${i + 1}`, []);
310
+ return useMemo3(() => ({ player, step, frame, toneAt, titleAt }), [player, step, frame, toneAt, titleAt]);
311
+ }
312
+
313
+ // src/create-trace.ts
314
+ function createTrace() {
315
+ const frames = [];
316
+ const builder = {
317
+ step(step) {
318
+ frames.push(step);
319
+ return builder;
320
+ },
321
+ get length() {
322
+ return frames.length;
323
+ },
324
+ last() {
325
+ return frames[frames.length - 1];
326
+ },
327
+ build() {
328
+ return frames.slice();
329
+ }
330
+ };
331
+ return builder;
332
+ }
333
+
334
+ // src/use-synced-tracks.ts
335
+ import { useCallback as useCallback4, useMemo as useMemo4, useRef as useRef4 } from "react";
336
+ function alignTracks(tracks, key) {
337
+ const names = Object.keys(tracks);
338
+ const lens = names.map((n) => tracks[n].length);
339
+ const rows = [];
340
+ const holdCell = (len, at) => ({ index: len === 0 ? -1 : Math.min(at, len - 1), held: true });
341
+ if (!key) {
342
+ const length = Math.max(0, ...lens);
343
+ for (let i = 0; i < length; i++) {
344
+ const row = {};
345
+ names.forEach((n, t) => {
346
+ row[n] = i < lens[t] ? { index: i, held: false } : holdCell(lens[t], i);
347
+ });
348
+ rows.push(row);
349
+ }
350
+ return rows;
351
+ }
352
+ const keyed = names.map(
353
+ (n) => tracks[n].map((step, i) => {
354
+ const k = key(step, i, n);
355
+ return k == null ? null : k;
356
+ })
357
+ );
358
+ const qualified = keyed.map((ks) => {
359
+ const seen = /* @__PURE__ */ new Map();
360
+ return ks.map((k) => {
361
+ if (k === null) return null;
362
+ const n = seen.get(k) ?? 0;
363
+ seen.set(k, n + 1);
364
+ return `${k}\0${n}`;
365
+ });
366
+ });
367
+ const where = /* @__PURE__ */ new Map();
368
+ qualified.forEach(
369
+ (ks, t) => ks.forEach((k, i) => {
370
+ if (k === null) return;
371
+ let m = where.get(k);
372
+ if (!m) where.set(k, m = /* @__PURE__ */ new Map());
373
+ m.set(t, i);
374
+ })
375
+ );
376
+ for (const [k, m] of where) if (m.size < 2) where.delete(k);
377
+ const pos = names.map(() => 0);
378
+ while (pos.some((p, t) => p < lens[t])) {
379
+ const live = names.map((_, t) => pos[t] < lens[t]);
380
+ const blocked = names.map((_, t) => {
381
+ if (!live[t]) return false;
382
+ const k = qualified[t][pos[t]];
383
+ const m = k === null ? void 0 : where.get(k);
384
+ if (!m) return false;
385
+ for (const [u, at] of m) if (u !== t && at > pos[u]) return true;
386
+ return false;
387
+ });
388
+ const anyFree = live.some((l, t) => l && !blocked[t]);
389
+ const row = {};
390
+ names.forEach((n, t) => {
391
+ if (live[t] && (!blocked[t] || !anyFree)) {
392
+ row[n] = { index: pos[t], held: false };
393
+ pos[t]++;
394
+ } else {
395
+ row[n] = holdCell(lens[t], Math.max(0, pos[t] - 1));
396
+ }
397
+ });
398
+ rows.push(row);
399
+ }
400
+ return rows;
401
+ }
402
+ var TONE_RANK = ["bad", "good", "write", "accent", "neutral", "idle"];
403
+ function useSyncedTracks(tracks, opts) {
404
+ const { key, ...playerOpts } = opts ?? {};
405
+ const keyRef = useRef4(key);
406
+ keyRef.current = key;
407
+ const cache = useRef4(null);
408
+ const names = Object.keys(tracks);
409
+ const arrays = names.map((n) => tracks[n]);
410
+ const c = cache.current;
411
+ if (!c || c.keyed !== !!key || c.names.length !== names.length || names.some((n, i) => c.names[i] !== n || c.arrays[i] !== arrays[i])) {
412
+ cache.current = { names, arrays, keyed: !!key, rows: alignTracks(tracks, keyRef.current) };
413
+ }
414
+ const rows = cache.current.rows;
415
+ const player = useStepPlayer({ ...playerOpts, total: rows.length });
416
+ const tracksRef = useRef4(tracks);
417
+ tracksRef.current = tracks;
418
+ const rowsRef = useRef4(rows);
419
+ rowsRef.current = rows;
420
+ const frame = rows.length === 0 ? -1 : Math.max(0, Math.min(player.index, rows.length - 1));
421
+ const row = frame >= 0 ? rows[frame] : void 0;
422
+ const aligned = cache.current;
423
+ const view = useMemo4(() => {
424
+ const out = {};
425
+ aligned.names.forEach((n, t) => {
426
+ const list = aligned.arrays[t];
427
+ const cell = row?.[n];
428
+ const index = cell ? cell.index : list.length === 0 ? -1 : 0;
429
+ out[n] = {
430
+ step: index >= 0 ? list[index] : void 0,
431
+ index,
432
+ held: cell?.held ?? false,
433
+ done: list.length > 0 && index === list.length - 1,
434
+ length: list.length
435
+ };
436
+ });
437
+ return out;
438
+ }, [aligned, row]);
439
+ const moved = useCallback4((i) => {
440
+ const r = rowsRef.current[i];
441
+ if (!r) return [];
442
+ return Object.keys(r).filter((n) => !r[n].held).map((n) => ({ name: n, step: tracksRef.current[n]?.[r[n].index] })).filter((m) => !!m.step);
443
+ }, []);
444
+ const toneAt = useCallback4(
445
+ (i) => {
446
+ const tones = moved(i).map((m) => m.step.tone);
447
+ return TONE_RANK.find((t) => tones.includes(t)) ?? "neutral";
448
+ },
449
+ [moved]
450
+ );
451
+ const titleAt = useCallback4(
452
+ (i) => {
453
+ const parts = moved(i).map((m) => `${m.name}: ${m.step.label}`);
454
+ return parts.length ? parts.join(" \xB7 ") : `frame ${i + 1}`;
455
+ },
456
+ [moved]
457
+ );
458
+ return useMemo4(
459
+ () => ({ player, frame, total: rows.length, tracks: view, rows, toneAt, titleAt }),
460
+ [player, frame, rows, view, toneAt, titleAt]
461
+ );
462
+ }
463
+
464
+ // src/use-lesson-stream.ts
465
+ import { useCallback as useCallback5, useEffect as useEffect4, useLayoutEffect, useMemo as useMemo5, useRef as useRef5, useState as useState4 } from "react";
466
+
467
+ // src/use-prefers-reduced-motion.ts
468
+ import { useEffect as useEffect3, useState as useState3 } from "react";
469
+ function usePrefersReducedMotion() {
470
+ const [reduced, setReduced] = useState3(false);
471
+ useEffect3(() => {
472
+ const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
473
+ setReduced(mq.matches);
474
+ const onChange = (e) => setReduced(e.matches);
475
+ mq.addEventListener("change", onChange);
476
+ return () => mq.removeEventListener("change", onChange);
477
+ }, []);
478
+ return reduced;
479
+ }
480
+
481
+ // src/use-lesson-stream.ts
482
+ var useIsoLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect4;
483
+ function useLessonStream(count, opts = {}) {
484
+ const { radius = 1, approachBelow = 600, approachAbove = 300, scrollToSeed = true } = opts;
485
+ const last = count - 1;
486
+ const seed = count === 0 ? -1 : Math.max(0, Math.min(opts.seed ?? 0, last));
487
+ const reducedMotion = usePrefersReducedMotion();
488
+ const core = useSteps(count, { initialIndex: Math.max(0, seed) });
489
+ const coreGoRef = useRef5(core.go);
490
+ coreGoRef.current = core.go;
491
+ const activeRef = useRef5(core.index);
492
+ activeRef.current = core.index;
493
+ const [range, setRange] = useState4(() => ({
494
+ first: Math.max(0, seed - radius),
495
+ last: Math.min(last, seed + radius)
496
+ }));
497
+ const first = Math.max(0, Math.min(range.first, last));
498
+ const lastMounted = Math.min(range.last, last);
499
+ const rangeRef = useRef5({ first, last: lastMounted });
500
+ rangeRef.current = { first, last: lastMounted };
501
+ const countRef = useRef5(count);
502
+ countRef.current = count;
503
+ const reducedRef = useRef5(reducedMotion);
504
+ reducedRef.current = reducedMotion;
505
+ const onActiveRef = useRef5(opts.onActiveChange);
506
+ onActiveRef.current = opts.onActiveChange;
507
+ const optsRef = useRef5({ approachBelow, approachAbove });
508
+ optsRef.current = { approachBelow, approachAbove };
509
+ const blocks = useRef5(/* @__PURE__ */ new Map());
510
+ const refCache = useRef5(/* @__PURE__ */ new Map());
511
+ const refFor = useCallback5((i) => {
512
+ let fn = refCache.current.get(i);
513
+ if (!fn) {
514
+ fn = (el) => {
515
+ if (el) blocks.current.set(i, el);
516
+ else blocks.current.delete(i);
517
+ };
518
+ refCache.current.set(i, fn);
519
+ }
520
+ return fn;
521
+ }, []);
522
+ const causeRef = useRef5("scroll");
523
+ const expandTo = useCallback5((i, cause) => {
524
+ const r = rangeRef.current;
525
+ if (i >= r.first && i <= r.last) return;
526
+ causeRef.current = cause;
527
+ const grown = { first: Math.min(r.first, i), last: Math.max(r.last, i) };
528
+ rangeRef.current = grown;
529
+ setRange(grown);
530
+ }, []);
531
+ const scheduleRef = useRef5(null);
532
+ useEffect4(() => {
533
+ if (count === 0) return;
534
+ let raf = 0;
535
+ const update = () => {
536
+ raf = 0;
537
+ if (blocks.current.size === 0) return;
538
+ const mid = window.innerHeight / 2;
539
+ let best = -1;
540
+ for (const [i, el] of blocks.current) {
541
+ const rect = el.getBoundingClientRect();
542
+ if (rect.top <= mid && rect.bottom >= mid) best = Math.max(best, i);
543
+ }
544
+ if (best === -1) best = Math.min(...blocks.current.keys());
545
+ if (best !== activeRef.current) {
546
+ activeRef.current = best;
547
+ coreGoRef.current(best);
548
+ onActiveRef.current?.(best);
549
+ }
550
+ const { first: f, last: l } = rangeRef.current;
551
+ const { approachBelow: below, approachAbove: above } = optsRef.current;
552
+ const lastEl = blocks.current.get(l);
553
+ if (lastEl && l < countRef.current - 1 && lastEl.getBoundingClientRect().bottom - window.innerHeight < below) {
554
+ expandTo(l + 1, "scroll");
555
+ }
556
+ const firstEl = blocks.current.get(f);
557
+ if (firstEl && f > 0 && firstEl.getBoundingClientRect().top > -above) {
558
+ expandTo(f - 1, "scroll");
559
+ }
560
+ };
561
+ const onScroll = () => {
562
+ if (!raf) raf = requestAnimationFrame(update);
563
+ };
564
+ scheduleRef.current = onScroll;
565
+ update();
566
+ window.addEventListener("scroll", onScroll, { passive: true });
567
+ window.addEventListener("resize", onScroll);
568
+ return () => {
569
+ scheduleRef.current = null;
570
+ window.removeEventListener("scroll", onScroll);
571
+ window.removeEventListener("resize", onScroll);
572
+ if (raf) cancelAnimationFrame(raf);
573
+ };
574
+ }, [count, expandTo]);
575
+ useEffect4(() => {
576
+ scheduleRef.current?.();
577
+ }, [first, lastMounted]);
578
+ const firstRef = useRef5(first);
579
+ useIsoLayoutEffect(() => {
580
+ if (first < firstRef.current && causeRef.current === "scroll") {
581
+ let height = 0;
582
+ for (let i = first; i < firstRef.current; i++) {
583
+ height += blocks.current.get(i)?.getBoundingClientRect().height ?? 0;
584
+ }
585
+ if (height) window.scrollBy({ top: height, behavior: "auto" });
586
+ }
587
+ causeRef.current = "scroll";
588
+ firstRef.current = first;
589
+ }, [first]);
590
+ useIsoLayoutEffect(() => {
591
+ if (!scrollToSeed || seed <= 0) return;
592
+ blocks.current.get(seed)?.scrollIntoView({ block: "start", behavior: "auto" });
593
+ }, []);
594
+ const [nav, setNav] = useState4(null);
595
+ useEffect4(() => {
596
+ if (!nav) return;
597
+ const raf = requestAnimationFrame(
598
+ () => blocks.current.get(nav.target)?.scrollIntoView({ behavior: reducedRef.current ? "auto" : "smooth", block: "start" })
599
+ );
600
+ return () => cancelAnimationFrame(raf);
601
+ }, [nav]);
602
+ const goTo = useCallback5(
603
+ (i) => {
604
+ const c = countRef.current;
605
+ if (c === 0) return;
606
+ const target = Math.max(0, Math.min(i, c - 1));
607
+ expandTo(target, "nav");
608
+ setNav({ target });
609
+ },
610
+ [expandTo]
611
+ );
612
+ const next = useCallback5(() => goTo(activeRef.current + 1), [goTo]);
613
+ const prev = useCallback5(() => goTo(activeRef.current - 1), [goTo]);
614
+ const mounted = useMemo5(() => {
615
+ const out = [];
616
+ for (let i = first; i <= lastMounted; i++) out.push(i);
617
+ return out;
618
+ }, [first, lastMounted]);
619
+ return useMemo5(
620
+ () => ({
621
+ active: core.index,
622
+ seed,
623
+ mounted,
624
+ complete: count > 0 && lastMounted === last,
625
+ refFor,
626
+ goTo,
627
+ next,
628
+ prev,
629
+ reducedMotion
630
+ }),
631
+ [core.index, seed, mounted, count, lastMounted, last, refFor, goTo, next, prev, reducedMotion]
632
+ );
633
+ }
634
+ function useFadeIn(initiallyVisible = false) {
635
+ const reduced = usePrefersReducedMotion();
636
+ const [shown, setShown] = useState4(initiallyVisible);
637
+ useEffect4(() => {
638
+ if (shown) return;
639
+ if (reduced) {
640
+ setShown(true);
641
+ return;
642
+ }
643
+ const raf = requestAnimationFrame(() => setShown(true));
644
+ return () => cancelAnimationFrame(raf);
645
+ }, [shown, reduced]);
646
+ return shown;
647
+ }
264
648
 
265
649
  // src/use-guided-tour.ts
266
- import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
650
+ import { useCallback as useCallback6, useEffect as useEffect5, useMemo as useMemo6, useRef as useRef6, useState as useState5 } from "react";
267
651
  function useGuidedTour(steps, opts) {
268
- const [running, setRunning] = useState3(false);
269
- const runningRef = useRef3(running);
652
+ const [running, setRunning] = useState5(false);
653
+ const runningRef = useRef6(running);
270
654
  runningRef.current = running;
271
655
  const core = useSteps(steps.length);
272
- const stepsRef = useRef3(steps);
656
+ const stepsRef = useRef6(steps);
273
657
  stepsRef.current = steps;
274
- const driverRef = useRef3(opts?.driver);
658
+ const driverRef = useRef6(opts?.driver);
275
659
  driverRef.current = opts?.driver;
276
- const coreGoRef = useRef3(core.go);
660
+ const coreGoRef = useRef6(core.go);
277
661
  coreGoRef.current = core.go;
278
- const isSkipped = useCallback3(
662
+ const cursorRef = useRef6(core.index);
663
+ cursorRef.current = core.index;
664
+ const isSkipped = useCallback6(
279
665
  (i) => stepsRef.current[i]?.when?.() === false,
280
666
  []
281
667
  );
282
- const settle = useCallback3(
668
+ const settle = useCallback6(
283
669
  (start2, dir) => {
284
670
  const len = stepsRef.current.length;
285
671
  let i = start2;
@@ -291,7 +677,7 @@ function useGuidedTour(steps, opts) {
291
677
  },
292
678
  [isSkipped]
293
679
  );
294
- const arrive = useCallback3(
680
+ const arrive = useCallback6(
295
681
  (i, dir) => {
296
682
  const target = settle(i, dir);
297
683
  if (target === null) {
@@ -303,14 +689,14 @@ function useGuidedTour(steps, opts) {
303
689
  },
304
690
  [settle]
305
691
  );
306
- const goTo = useCallback3(
692
+ const goTo = useCallback6(
307
693
  (i) => {
308
694
  if (stepsRef.current.length === 0) return;
309
695
  arrive(Math.max(0, Math.min(i, stepsRef.current.length - 1)), 1);
310
696
  },
311
697
  [arrive]
312
698
  );
313
- const goToId = useCallback3(
699
+ const goToId = useCallback6(
314
700
  (id) => {
315
701
  const i = stepsRef.current.findIndex((s) => s.id === id);
316
702
  if (i >= 0) goTo(i);
@@ -318,11 +704,11 @@ function useGuidedTour(steps, opts) {
318
704
  [goTo]
319
705
  );
320
706
  const index = running ? core.index : null;
321
- useEffect3(() => {
707
+ useEffect5(() => {
322
708
  if (!running) return;
323
709
  stepsRef.current[core.index]?.onEnter?.();
324
710
  }, [running, core.index]);
325
- useEffect3(() => {
711
+ useEffect5(() => {
326
712
  const driver = driverRef.current;
327
713
  if (!driver) return;
328
714
  if (!running) {
@@ -341,14 +727,14 @@ function useGuidedTour(steps, opts) {
341
727
  back: () => goTo(core.index - 1),
342
728
  end: () => setRunning(false)
343
729
  });
344
- }, [running, core.index, goTo, core]);
345
- useEffect3(() => () => driverRef.current?.hide(), []);
346
- const start = useCallback3(() => {
730
+ }, [running, core.index, goTo]);
731
+ useEffect5(() => () => driverRef.current?.hide(), []);
732
+ const start = useCallback6(() => {
347
733
  if (stepsRef.current.length === 0) return;
348
734
  arrive(0, 1);
349
735
  }, [arrive]);
350
- const end = useCallback3(() => setRunning(false), []);
351
- const resolveNext = useCallback3((i) => {
736
+ const end = useCallback6(() => setRunning(false), []);
737
+ const resolveNext = useCallback6((i) => {
352
738
  const list = stepsRef.current;
353
739
  const edge = list[i]?.next?.();
354
740
  if (edge != null) {
@@ -357,9 +743,9 @@ function useGuidedTour(steps, opts) {
357
743
  }
358
744
  return i + 1;
359
745
  }, []);
360
- const next = useCallback3(() => {
746
+ const next = useCallback6(() => {
361
747
  if (!runningRef.current) return;
362
- const i = core.index;
748
+ const i = cursorRef.current;
363
749
  if (stepsRef.current[i]?.gate?.() === false) return;
364
750
  const target = resolveNext(i);
365
751
  if (target > stepsRef.current.length - 1) {
@@ -367,20 +753,23 @@ function useGuidedTour(steps, opts) {
367
753
  return;
368
754
  }
369
755
  arrive(target, 1);
370
- }, [core, arrive, resolveNext]);
371
- const back = useCallback3(() => {
756
+ }, [arrive, resolveNext]);
757
+ const back = useCallback6(() => {
372
758
  if (!runningRef.current) return;
373
- const i = core.index;
759
+ const i = cursorRef.current;
374
760
  if (i <= 0) return;
375
761
  arrive(i - 1, -1);
376
- }, [core, arrive]);
762
+ }, [arrive]);
377
763
  const current = running ? stepsRef.current[core.index] ?? null : null;
378
764
  const blocked = current?.gate?.() === false;
379
- return { index, current, active: running, blocked, start, end, next, back, goto: goTo, gotoId: goToId };
765
+ return useMemo6(
766
+ () => ({ index, current, active: running, blocked, start, end, next, back, goto: goTo, gotoId: goToId }),
767
+ [index, current, running, blocked, start, end, next, back, goTo, goToId]
768
+ );
380
769
  }
381
770
 
382
771
  // src/use-chapters.ts
383
- import { useCallback as useCallback4, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef4, useState as useState4 } from "react";
772
+ import { useCallback as useCallback7, useEffect as useEffect6, useMemo as useMemo7, useRef as useRef7, useState as useState6 } from "react";
384
773
 
385
774
  // src/flatten.ts
386
775
  function flattenChapters(chapters, depth = 0) {
@@ -399,17 +788,17 @@ function flattenChapters(chapters, depth = 0) {
399
788
 
400
789
  // src/use-chapters.ts
401
790
  function useChapters(chapters) {
402
- const [mounted, setMounted] = useState4(false);
403
- const flat = useMemo3(() => flattenChapters(chapters), [chapters]);
404
- const flatRef = useRef4(flat);
791
+ const [mounted, setMounted] = useState6(false);
792
+ const flat = useMemo7(() => flattenChapters(chapters), [chapters]);
793
+ const flatRef = useRef7(flat);
405
794
  flatRef.current = flat;
406
- const [active, setActive] = useState4(0);
407
- const activeRef = useRef4(0);
795
+ const [active, setActive] = useState6(0);
796
+ const activeRef = useRef7(0);
408
797
  activeRef.current = active;
409
798
  const core = useSteps(flat.length, { initialIndex: 0 });
410
- const coreGoRef = useRef4(core.go);
799
+ const coreGoRef = useRef7(core.go);
411
800
  coreGoRef.current = core.go;
412
- useEffect4(() => {
801
+ useEffect6(() => {
413
802
  if (!mounted) return;
414
803
  let raf = 0;
415
804
  const update = () => {
@@ -447,8 +836,8 @@ function useChapters(chapters) {
447
836
  if (raf) cancelAnimationFrame(raf);
448
837
  };
449
838
  }, [mounted, flat.length]);
450
- useEffect4(() => setMounted(true), []);
451
- const goTo = useCallback4((i) => {
839
+ useEffect6(() => setMounted(true), []);
840
+ const goTo = useCallback7((i) => {
452
841
  const entry = flatRef.current[i];
453
842
  if (!entry) return;
454
843
  setActive(i);
@@ -457,25 +846,25 @@ function useChapters(chapters) {
457
846
  if (!el) return;
458
847
  el.scrollIntoView({ behavior: "smooth", block: "start" });
459
848
  }, []);
460
- const next = useCallback4(() => {
849
+ const next = useCallback7(() => {
461
850
  if (activeRef.current < flatRef.current.length - 1) goTo(activeRef.current + 1);
462
851
  }, [goTo]);
463
- const prev = useCallback4(() => {
852
+ const prev = useCallback7(() => {
464
853
  if (activeRef.current > 0) goTo(activeRef.current - 1);
465
854
  }, [goTo]);
466
- return { active: core.index, goTo, next, prev };
855
+ return useMemo7(() => ({ active: core.index, goTo, next, prev }), [core.index, goTo, next, prev]);
467
856
  }
468
857
 
469
858
  // src/use-global-keys.ts
470
- import { useEffect as useEffect5, useRef as useRef5 } from "react";
859
+ import { useEffect as useEffect7, useRef as useRef8 } from "react";
471
860
  var DEFAULT_IGNORED_TAGS = ["INPUT", "TEXTAREA", "SELECT", "BUTTON"];
472
861
  function useGlobalKeys(map, opts) {
473
- const mapRef = useRef5(map);
862
+ const mapRef = useRef8(map);
474
863
  mapRef.current = map;
475
- const ignoreRef = useRef5(new Set(opts?.ignoreTags ?? DEFAULT_IGNORED_TAGS));
476
- const enabledRef = useRef5(opts?.enabled ?? true);
864
+ const ignoreRef = useRef8(new Set(opts?.ignoreTags ?? DEFAULT_IGNORED_TAGS));
865
+ const enabledRef = useRef8(opts?.enabled ?? true);
477
866
  enabledRef.current = opts?.enabled ?? true;
478
- useEffect5(() => {
867
+ useEffect7(() => {
479
868
  const onKey = (e) => {
480
869
  const gate = enabledRef.current;
481
870
  const enabled = typeof gate === "function" ? gate() : gate;
@@ -493,7 +882,7 @@ function useGlobalKeys(map, opts) {
493
882
  }
494
883
 
495
884
  // src/use-discoveries.ts
496
- import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo4, useRef as useRef6, useState as useState5 } from "react";
885
+ import { useCallback as useCallback8, useEffect as useEffect8, useMemo as useMemo8, useRef as useRef9, useState as useState7 } from "react";
497
886
  function localStorageStore(key = "stepwise.discoveries") {
498
887
  return {
499
888
  read() {
@@ -544,23 +933,23 @@ function memoryStore(seed = []) {
544
933
  };
545
934
  }
546
935
  function useDiscoveries(opts) {
547
- const defaultStoreRef = useRef6(null);
548
- const storeRef = useRef6(null);
936
+ const defaultStoreRef = useRef9(null);
937
+ const storeRef = useRef9(null);
549
938
  if (opts?.store) {
550
939
  storeRef.current = opts.store;
551
940
  } else {
552
941
  if (!defaultStoreRef.current) defaultStoreRef.current = localStorageStore(opts?.key);
553
942
  storeRef.current = defaultStoreRef.current;
554
943
  }
555
- const [ids, setIds] = useState5(() => /* @__PURE__ */ new Set());
556
- const [ready, setReady] = useState5(false);
557
- const idsRef = useRef6(ids);
944
+ const [ids, setIds] = useState7(() => /* @__PURE__ */ new Set());
945
+ const [ready, setReady] = useState7(false);
946
+ const idsRef = useRef9(ids);
558
947
  idsRef.current = ids;
559
- useEffect6(() => {
948
+ useEffect8(() => {
560
949
  setIds(new Set(storeRef.current.read()));
561
950
  setReady(true);
562
951
  }, []);
563
- const markDiscovered = useCallback5((id) => {
952
+ const markDiscovered = useCallback8((id) => {
564
953
  const additions = Array.isArray(id) ? id : [id];
565
954
  const merged = new Set(idsRef.current);
566
955
  let changed = false;
@@ -575,24 +964,24 @@ function useDiscoveries(opts) {
575
964
  setIds(merged);
576
965
  storeRef.current.write([...merged]);
577
966
  }, []);
578
- const reset = useCallback5(() => {
967
+ const reset = useCallback8(() => {
579
968
  const empty = /* @__PURE__ */ new Set();
580
969
  idsRef.current = empty;
581
970
  setIds(empty);
582
971
  storeRef.current.write([]);
583
972
  }, []);
584
- const isDiscovered = useCallback5((id) => idsRef.current.has(id), []);
585
- return useMemo4(
973
+ const isDiscovered = useCallback8((id) => idsRef.current.has(id), []);
974
+ return useMemo8(
586
975
  () => ({ discovered: ids, ready, isDiscovered, markDiscovered, reset }),
587
976
  [ids, ready, isDiscovered, markDiscovered, reset]
588
977
  );
589
978
  }
590
979
 
591
980
  // src/use-focus-trap.ts
592
- import { useEffect as useEffect7, useRef as useRef7 } from "react";
981
+ import { useEffect as useEffect9, useRef as useRef10 } from "react";
593
982
  function useFocusTrap(active) {
594
- const ref = useRef7(null);
595
- useEffect7(() => {
983
+ const ref = useRef10(null);
984
+ useEffect9(() => {
596
985
  if (!active) return;
597
986
  const container = ref.current;
598
987
  if (!container) return;
@@ -626,31 +1015,17 @@ function useFocusTrap(active) {
626
1015
  return ref;
627
1016
  }
628
1017
 
629
- // src/use-prefers-reduced-motion.ts
630
- import { useEffect as useEffect8, useState as useState6 } from "react";
631
- function usePrefersReducedMotion() {
632
- const [reduced, setReduced] = useState6(false);
633
- useEffect8(() => {
634
- const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
635
- setReduced(mq.matches);
636
- const onChange = (e) => setReduced(e.matches);
637
- mq.addEventListener("change", onChange);
638
- return () => mq.removeEventListener("change", onChange);
639
- }, []);
640
- return reduced;
641
- }
642
-
643
1018
  // src/glossary.tsx
644
1019
  import {
645
1020
  createContext,
646
- useCallback as useCallback6,
1021
+ useCallback as useCallback9,
647
1022
  useContext,
648
- useEffect as useEffect9,
1023
+ useEffect as useEffect10,
649
1024
  useId,
650
- useLayoutEffect,
651
- useMemo as useMemo5,
652
- useRef as useRef8,
653
- useState as useState7
1025
+ useLayoutEffect as useLayoutEffect2,
1026
+ useMemo as useMemo9,
1027
+ useRef as useRef11,
1028
+ useState as useState8
654
1029
  } from "react";
655
1030
  import { createPortal } from "react-dom";
656
1031
 
@@ -708,7 +1083,7 @@ function GlossaryProvider({
708
1083
  theme = defaultTheme,
709
1084
  children
710
1085
  }) {
711
- const value = useMemo5(
1086
+ const value = useMemo9(
712
1087
  () => ({ byId: new Map(entries.map((e) => [e.id, e])), entries, theme }),
713
1088
  [entries, theme]
714
1089
  );
@@ -718,23 +1093,23 @@ function Term({ id, children, className }) {
718
1093
  const ctx = useContext(GlossaryContext);
719
1094
  const depth = useContext(DepthContext);
720
1095
  const entry = ctx?.byId.get(id);
721
- const [open, setOpen] = useState7(false);
722
- const [pos, setPos] = useState7(null);
723
- const openRef = useRef8(false);
724
- const wrapperRef = useRef8(null);
725
- const triggerRef = useRef8(null);
726
- const cardRef = useRef8(null);
727
- const openTimer = useRef8(0);
728
- const closeTimer = useRef8(0);
1096
+ const [open, setOpen] = useState8(false);
1097
+ const [pos, setPos] = useState8(null);
1098
+ const openRef = useRef11(false);
1099
+ const wrapperRef = useRef11(null);
1100
+ const triggerRef = useRef11(null);
1101
+ const cardRef = useRef11(null);
1102
+ const openTimer = useRef11(0);
1103
+ const closeTimer = useRef11(0);
729
1104
  const tipId = useId();
730
1105
  const display = children ?? entry?.term ?? id;
731
- const closeNow = useCallback6(() => {
1106
+ const closeNow = useCallback9(() => {
732
1107
  window.clearTimeout(openTimer.current);
733
1108
  window.clearTimeout(closeTimer.current);
734
1109
  openRef.current = false;
735
1110
  setOpen(false);
736
1111
  }, []);
737
- const openNow = useCallback6((focusCard = false) => {
1112
+ const openNow = useCallback9((focusCard = false) => {
738
1113
  window.clearTimeout(closeTimer.current);
739
1114
  window.clearTimeout(openTimer.current);
740
1115
  openRef.current = true;
@@ -743,18 +1118,18 @@ function Term({ id, children, className }) {
743
1118
  requestAnimationFrame(() => cardRef.current?.focus({ preventScroll: true }));
744
1119
  }
745
1120
  }, []);
746
- const scheduleOpen = useCallback6(() => {
1121
+ const scheduleOpen = useCallback9(() => {
747
1122
  window.clearTimeout(closeTimer.current);
748
1123
  if (openRef.current) return;
749
1124
  window.clearTimeout(openTimer.current);
750
1125
  openTimer.current = window.setTimeout(() => openNow(), OPEN_DELAY_MS);
751
1126
  }, [openNow]);
752
- const scheduleClose = useCallback6(() => {
1127
+ const scheduleClose = useCallback9(() => {
753
1128
  window.clearTimeout(openTimer.current);
754
1129
  window.clearTimeout(closeTimer.current);
755
1130
  closeTimer.current = window.setTimeout(closeNow, CLOSE_GRACE_MS);
756
1131
  }, [closeNow]);
757
- useLayoutEffect(() => {
1132
+ useLayoutEffect2(() => {
758
1133
  if (!open) {
759
1134
  setPos(null);
760
1135
  return;
@@ -791,7 +1166,7 @@ function Term({ id, children, className }) {
791
1166
  if (raf) cancelAnimationFrame(raf);
792
1167
  };
793
1168
  }, [open]);
794
- useEffect9(() => {
1169
+ useEffect10(() => {
795
1170
  if (!open) return;
796
1171
  const onPointerDown = (e) => {
797
1172
  const target = e.target;
@@ -802,7 +1177,7 @@ function Term({ id, children, className }) {
802
1177
  document.addEventListener("pointerdown", onPointerDown);
803
1178
  return () => document.removeEventListener("pointerdown", onPointerDown);
804
1179
  }, [open, closeNow]);
805
- useEffect9(() => () => {
1180
+ useEffect10(() => () => {
806
1181
  window.clearTimeout(openTimer.current);
807
1182
  window.clearTimeout(closeTimer.current);
808
1183
  }, []);
@@ -854,6 +1229,7 @@ function Term({ id, children, className }) {
854
1229
  "span",
855
1230
  {
856
1231
  ref: wrapperRef,
1232
+ "data-stepwise": "",
857
1233
  className: "relative inline",
858
1234
  onMouseEnter: interactive ? scheduleOpen : void 0,
859
1235
  onMouseLeave: interactive ? scheduleClose : void 0,
@@ -876,6 +1252,7 @@ function Term({ id, children, className }) {
876
1252
  "span",
877
1253
  {
878
1254
  ref: cardRef,
1255
+ "data-stepwise": "",
879
1256
  role: "tooltip",
880
1257
  id: tipId,
881
1258
  tabIndex: -1,
@@ -946,14 +1323,14 @@ function GlossaryAppendix({
946
1323
  if (bare) {
947
1324
  return /* @__PURE__ */ jsx(AppendixContext.Provider, { value: true, children: body });
948
1325
  }
949
- return /* @__PURE__ */ jsx(AppendixContext.Provider, { value: true, children: /* @__PURE__ */ jsxs("section", { "aria-label": title, className: cn("mt-16 border-t border-white/10 pt-10", className), children: [
1326
+ return /* @__PURE__ */ jsx(AppendixContext.Provider, { value: true, children: /* @__PURE__ */ jsxs("section", { "data-stepwise": "", "aria-label": title, className: cn("mt-16 border-t border-white/10 pt-10", className), children: [
950
1327
  /* @__PURE__ */ jsx("p", { className: cn("text-[11px] font-mono uppercase tracking-widest mb-6", theme.muted), children: title }),
951
1328
  body
952
1329
  ] }) });
953
1330
  }
954
1331
 
955
1332
  // src/components.tsx
956
- import { useEffect as useEffect10, useState as useState8 } from "react";
1333
+ import { useEffect as useEffect11, useState as useState9 } from "react";
957
1334
  import { createPortal as createPortal2 } from "react-dom";
958
1335
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
959
1336
  var TONE_SEGMENT_CLASS = defaultTheme.toneSegment;
@@ -975,7 +1352,7 @@ function CodeBlock({
975
1352
  theme = defaultTheme,
976
1353
  className
977
1354
  }) {
978
- return /* @__PURE__ */ jsx2("div", { className: cn(theme.codeBlock, className), children: lines.map((line, i) => {
1355
+ return /* @__PURE__ */ jsx2("div", { "data-stepwise": "", className: cn(theme.codeBlock, className), children: lines.map((line, i) => {
979
1356
  const commentStart = line.indexOf("//");
980
1357
  if (commentStart === 0) {
981
1358
  return /* @__PURE__ */ jsx2("div", { className: cn("whitespace-pre", theme.codeMuted), children: line }, i);
@@ -1022,7 +1399,7 @@ function StepScrubber({
1022
1399
  }
1023
1400
  };
1024
1401
  if (count === 0) {
1025
- return /* @__PURE__ */ jsx2("div", { className: cn("flex-1 flex items-center min-w-0", className), children: emptyHint && /* @__PURE__ */ jsx2("span", { className: cn("text-xs font-mono px-2", theme.muted), children: emptyHint }) });
1402
+ return /* @__PURE__ */ jsx2("div", { "data-stepwise": "", className: cn("flex-1 flex items-center min-w-0", className), children: emptyHint && /* @__PURE__ */ jsx2("span", { className: cn("text-xs font-mono px-2", theme.muted), children: emptyHint }) });
1026
1403
  }
1027
1404
  const valueText = labels?.frame ? labels.frame(index) : titleAt?.(index) ?? `frame ${index + 1} of ${count}`;
1028
1405
  const segment = (i) => /* @__PURE__ */ jsx2(
@@ -1064,6 +1441,7 @@ function StepScrubber({
1064
1441
  return /* @__PURE__ */ jsx2(
1065
1442
  "div",
1066
1443
  {
1444
+ "data-stepwise": "",
1067
1445
  role: "slider",
1068
1446
  tabIndex: 0,
1069
1447
  "aria-label": labels?.scrubber ?? "Frames",
@@ -1101,7 +1479,7 @@ function PlayerBar({
1101
1479
  next: labels?.next ?? "Next step"
1102
1480
  };
1103
1481
  const playLabel = player.playing ? L.pause : player.total > 0 && player.atEnd ? L.replay : L.play;
1104
- return /* @__PURE__ */ jsxs2("div", { className: cn("flex items-center gap-2 p-2", className), children: [
1482
+ return /* @__PURE__ */ jsxs2("div", { "data-stepwise": "", className: cn("flex items-center gap-2 p-2", className), children: [
1105
1483
  leading,
1106
1484
  /* @__PURE__ */ jsx2(StepButton, { variant: "ghost", onClick: () => player.prev(), className: "h-7 px-2 text-xs", "aria-label": L.previous, children: "\u2190" }),
1107
1485
  /* @__PURE__ */ jsx2(StepButton, { variant: "outline", onClick: () => player.toggle(onEmpty), className: "h-7 px-3.5 text-[11px]", children: playLabel }),
@@ -1163,6 +1541,7 @@ function ChapterNav({
1163
1541
  return /* @__PURE__ */ jsx2(
1164
1542
  "nav",
1165
1543
  {
1544
+ "data-stepwise": "",
1166
1545
  "aria-label": "Chapters",
1167
1546
  className: cn(
1168
1547
  "sticky top-20 md:top-24 z-30 rounded-sm shadow-[0_8px_24px_rgba(0,0,0,0.35)]",
@@ -1186,7 +1565,7 @@ function LessonPager({
1186
1565
  className,
1187
1566
  labels
1188
1567
  }) {
1189
- return /* @__PURE__ */ jsxs2("div", { className: cn("flex items-center gap-2", className), children: [
1568
+ return /* @__PURE__ */ jsxs2("div", { "data-stepwise": "", className: cn("flex items-center gap-2", className), children: [
1190
1569
  /* @__PURE__ */ jsxs2(
1191
1570
  StepButton,
1192
1571
  {
@@ -1223,8 +1602,8 @@ function TourSpotlight({
1223
1602
  target,
1224
1603
  theme = defaultTheme
1225
1604
  }) {
1226
- const [rect, setRect] = useState8(null);
1227
- useEffect10(() => {
1605
+ const [rect, setRect] = useState9(null);
1606
+ useEffect11(() => {
1228
1607
  if (!target) {
1229
1608
  setRect(null);
1230
1609
  return;
@@ -1256,7 +1635,7 @@ function TourSpotlight({
1256
1635
  }, [target]);
1257
1636
  if (!rect) return null;
1258
1637
  const pad = 6;
1259
- return /* @__PURE__ */ jsx2("div", { className: "fixed inset-0 z-40 pointer-events-none", children: /* @__PURE__ */ jsx2(
1638
+ return /* @__PURE__ */ jsx2("div", { "data-stepwise": "", className: "fixed inset-0 z-40 pointer-events-none", children: /* @__PURE__ */ jsx2(
1260
1639
  "div",
1261
1640
  {
1262
1641
  className: cn("absolute", theme.spotlight),
@@ -1294,6 +1673,7 @@ function TourCard({
1294
1673
  "div",
1295
1674
  {
1296
1675
  ref: trapRef,
1676
+ "data-stepwise": "",
1297
1677
  role: "dialog",
1298
1678
  "aria-modal": "true",
1299
1679
  "aria-label": `${L.title} \xB7 ${index + 1}/${total}`,
@@ -1352,10 +1732,10 @@ function ShortcutsDialog({
1352
1732
  className,
1353
1733
  theme = defaultTheme
1354
1734
  }) {
1355
- const [mounted, setMounted] = useState8(false);
1735
+ const [mounted, setMounted] = useState9(false);
1356
1736
  const trapRef = useFocusTrap(open);
1357
- useEffect10(() => setMounted(true), []);
1358
- useEffect10(() => {
1737
+ useEffect11(() => setMounted(true), []);
1738
+ useEffect11(() => {
1359
1739
  if (!open) return;
1360
1740
  const onKey = (e) => {
1361
1741
  if (e.key === "Escape") onClose();
@@ -1368,6 +1748,7 @@ function ShortcutsDialog({
1368
1748
  /* @__PURE__ */ jsx2(
1369
1749
  "div",
1370
1750
  {
1751
+ "data-stepwise": "",
1371
1752
  className: cn("fixed inset-0 z-50 flex items-center justify-center p-4", theme.overlay, className),
1372
1753
  onClick: onClose,
1373
1754
  children: /* @__PURE__ */ jsxs2(
@@ -1424,18 +1805,24 @@ export {
1424
1805
  Term,
1425
1806
  TourCard,
1426
1807
  TourSpotlight,
1808
+ alignTracks,
1427
1809
  cookieStore,
1810
+ createTrace,
1428
1811
  defaultTheme,
1429
1812
  flattenChapters,
1430
1813
  localStorageStore,
1431
1814
  memoryStore,
1432
1815
  useChapters,
1433
1816
  useDiscoveries,
1817
+ useFadeIn,
1434
1818
  useFocusTrap,
1435
1819
  useGlobalKeys,
1436
1820
  useGuidedTour,
1821
+ useLessonStream,
1437
1822
  usePrefersReducedMotion,
1438
1823
  useStepPlayer,
1439
- useSteps
1824
+ useSteps,
1825
+ useSyncedTracks,
1826
+ useTrace
1440
1827
  };
1441
1828
  //# sourceMappingURL=index.js.map