@multitrack/core 0.1.0 → 0.1.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.
package/README.md CHANGED
@@ -98,6 +98,38 @@ const timeline = new Timeline({
98
98
  });
99
99
  ```
100
100
 
101
+ ### Scope cleanup
102
+
103
+ Collect subscriptions into a scope and dispose them all at once — similar to GSAP's `gsap.context()`.
104
+
105
+ ```typescript
106
+ const ctx = timeline.scope(() => {
107
+ timeline.on("step:enter", handleEnter);
108
+ timeline.on("scroll", handleScroll);
109
+ timeline.use(loggingMiddleware);
110
+ });
111
+
112
+ // later: clean up everything at once
113
+ ctx.dispose();
114
+ ```
115
+
116
+ ### Conditional steps
117
+
118
+ For runtime conditions beyond media queries, use the `condition` predicate. Steps where `condition` returns `false` are excluded from the resolved timeline.
119
+
120
+ ```typescript
121
+ { name: "mobile-hero", duration: 5, track: "main", condition: () => window.innerWidth < 768 }
122
+ ```
123
+
124
+ ### Dev-mode warnings
125
+
126
+ In development, the engine validates your config and warns about common mistakes:
127
+
128
+ - Zero or negative duration steps
129
+ - Using `snap` easing on long steps (likely unintended)
130
+ - Lone tracks that might be typos
131
+ - `when` references to undefined breakpoints
132
+
101
133
  ## React bindings
102
134
 
103
135
  See [`@multitrack/react`](https://github.com/jakhsu/multitrack/tree/main/packages/react) for React hooks and components.
@@ -0,0 +1,77 @@
1
+ 'use strict';
2
+
3
+ // src/analytics.ts
4
+ function createBeaconTransport(endpoint) {
5
+ return (events) => {
6
+ const blob = new Blob([JSON.stringify(events)], {
7
+ type: "application/json"
8
+ });
9
+ navigator.sendBeacon(endpoint, blob);
10
+ };
11
+ }
12
+ function createAnalyticsMiddleware(options) {
13
+ const {
14
+ endpoint,
15
+ batchSize = 10,
16
+ flushInterval = 5e3,
17
+ sampleRate = 1,
18
+ sessionId = crypto.randomUUID(),
19
+ now = () => (/* @__PURE__ */ new Date()).toISOString(),
20
+ random = Math.random
21
+ } = options;
22
+ const transport = options.transport ?? createBeaconTransport(endpoint);
23
+ if (!options.transport && !endpoint) {
24
+ throw new Error(
25
+ "createAnalyticsMiddleware: provide either `endpoint` or a custom `transport`."
26
+ );
27
+ }
28
+ let buffer = [];
29
+ let destroyed = false;
30
+ function enrich(event) {
31
+ return {
32
+ eventType: event.type,
33
+ stepName: event.payload.name,
34
+ track: event.payload.track,
35
+ timestamp: now(),
36
+ sessionId,
37
+ url: typeof window !== "undefined" ? window.location.href : "unknown",
38
+ stepStart: event.payload.start,
39
+ stepEnd: event.payload.end
40
+ };
41
+ }
42
+ function flush() {
43
+ if (buffer.length === 0) return [];
44
+ const batch = buffer;
45
+ buffer = [];
46
+ transport(batch);
47
+ return batch;
48
+ }
49
+ const timer = setInterval(flush, flushInterval);
50
+ const handlePageHide = () => flush();
51
+ if (typeof window !== "undefined") {
52
+ window.addEventListener("pagehide", handlePageHide);
53
+ }
54
+ const middleware = (event, next) => {
55
+ if (!destroyed && random() < sampleRate) {
56
+ buffer.push(enrich(event));
57
+ if (buffer.length >= batchSize) {
58
+ flush();
59
+ }
60
+ }
61
+ next();
62
+ };
63
+ function destroy() {
64
+ if (destroyed) return;
65
+ destroyed = true;
66
+ flush();
67
+ clearInterval(timer);
68
+ if (typeof window !== "undefined") {
69
+ window.removeEventListener("pagehide", handlePageHide);
70
+ }
71
+ }
72
+ return { middleware, flush, destroy };
73
+ }
74
+
75
+ exports.createAnalyticsMiddleware = createAnalyticsMiddleware;
76
+ //# sourceMappingURL=analytics.cjs.map
77
+ //# sourceMappingURL=analytics.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/analytics.ts"],"names":[],"mappings":"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","file":"analytics.cjs","sourcesContent":["import type { MiddlewareFn, MiddlewareEvent } from \"./middleware.js\";\n\n// --- Public types ---\n\n/**\n * A single analytics event enriched with session metadata, ready for transport.\n */\nexport interface AnalyticsEvent {\n eventType: \"step:enter\" | \"step:exit\";\n stepName: string;\n track: string;\n timestamp: string;\n sessionId: string;\n url: string;\n stepStart: number;\n stepEnd: number;\n}\n\n/**\n * Transport function: receives a batch of events and sends them somewhere.\n * Override this to use fetch, XMLHttpRequest, image pixels, or logging.\n */\nexport type TransportFn = (events: AnalyticsEvent[]) => void;\n\nexport interface AnalyticsMiddlewareOptions {\n /** Endpoint URL for the default sendBeacon transport. Required if `transport` is not provided. */\n endpoint?: string;\n /** Custom transport function. Overrides the default sendBeacon transport. */\n transport?: TransportFn;\n /** Auto-flush when the batch reaches this size. Default: 10 */\n batchSize?: number;\n /** Milliseconds between periodic flushes. Default: 5000 */\n flushInterval?: number;\n /** Sampling rate from 0 to 1. 1 = send everything, 0.1 = send 10%. Default: 1 */\n sampleRate?: number;\n /** Override the auto-generated session ID. */\n sessionId?: string;\n /** Injectable clock for deterministic tests. Default: () => new Date().toISOString() */\n now?: () => string;\n /** Injectable random for deterministic sampling tests. Default: Math.random */\n random?: () => number;\n}\n\nexport interface AnalyticsMiddlewareResult {\n /** The middleware function — pass to `timeline.use()`. */\n middleware: MiddlewareFn;\n /** Force-flush buffered events. Returns the flushed batch. */\n flush: () => AnalyticsEvent[];\n /** Flush remaining events, clear interval timer, remove pagehide listener. */\n destroy: () => void;\n}\n\n// --- Default transport ---\n\nfunction createBeaconTransport(endpoint: string): TransportFn {\n return (events) => {\n const blob = new Blob([JSON.stringify(events)], {\n type: \"application/json\",\n });\n navigator.sendBeacon(endpoint, blob);\n };\n}\n\n// --- Factory ---\n\n/**\n * Creates an analytics middleware that batches step:enter/step:exit events\n * and flushes them via `navigator.sendBeacon` (or a custom transport).\n *\n * Demonstrates production analytics SDK patterns:\n * - Event batching with configurable batch size\n * - Periodic flush on a timer\n * - `navigator.sendBeacon` for reliable delivery on page unload\n * - Probabilistic sampling\n * - Session enrichment (timestamp, sessionId, URL)\n * - Graceful shutdown via `destroy()`\n *\n * @example\n * ```ts\n * const analytics = createAnalyticsMiddleware({\n * endpoint: \"/api/events\",\n * batchSize: 20,\n * flushInterval: 10_000,\n * sampleRate: 0.5,\n * });\n *\n * const unsub = timeline.use(analytics.middleware);\n *\n * // Later: teardown\n * unsub();\n * analytics.destroy();\n * ```\n */\nexport function createAnalyticsMiddleware(\n options: AnalyticsMiddlewareOptions,\n): AnalyticsMiddlewareResult {\n const {\n endpoint,\n batchSize = 10,\n flushInterval = 5000,\n sampleRate = 1,\n sessionId = crypto.randomUUID(),\n now = () => new Date().toISOString(),\n random = Math.random,\n } = options;\n\n const transport =\n options.transport ?? createBeaconTransport(endpoint as string);\n\n if (!options.transport && !endpoint) {\n throw new Error(\n \"createAnalyticsMiddleware: provide either `endpoint` or a custom `transport`.\",\n );\n }\n\n let buffer: AnalyticsEvent[] = [];\n let destroyed = false;\n\n // --- Enrich ---\n\n function enrich(event: MiddlewareEvent): AnalyticsEvent {\n return {\n eventType: event.type,\n stepName: event.payload.name,\n track: event.payload.track,\n timestamp: now(),\n sessionId,\n url: typeof window !== \"undefined\" ? window.location.href : \"unknown\",\n stepStart: event.payload.start,\n stepEnd: event.payload.end,\n };\n }\n\n // --- Flush ---\n\n function flush(): AnalyticsEvent[] {\n if (buffer.length === 0) return [];\n const batch = buffer;\n buffer = [];\n transport(batch);\n return batch;\n }\n\n // --- Periodic flush timer ---\n\n const timer = setInterval(flush, flushInterval);\n\n // --- Page unload handler (SSR-safe) ---\n\n const handlePageHide = () => flush();\n\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"pagehide\", handlePageHide);\n }\n\n // --- Middleware ---\n\n const middleware: MiddlewareFn = (event, next) => {\n if (!destroyed && random() < sampleRate) {\n buffer.push(enrich(event));\n\n if (buffer.length >= batchSize) {\n flush();\n }\n }\n\n // Analytics middleware always passes through — it observes, never gates.\n next();\n };\n\n // --- Destroy ---\n\n function destroy(): void {\n if (destroyed) return;\n destroyed = true;\n flush();\n clearInterval(timer);\n if (typeof window !== \"undefined\") {\n window.removeEventListener(\"pagehide\", handlePageHide);\n }\n }\n\n return { middleware, flush, destroy };\n}\n"]}
@@ -0,0 +1,77 @@
1
+ import { M as MiddlewareFn } from './middleware-DQus4IrE.cjs';
2
+
3
+ /**
4
+ * A single analytics event enriched with session metadata, ready for transport.
5
+ */
6
+ interface AnalyticsEvent {
7
+ eventType: "step:enter" | "step:exit";
8
+ stepName: string;
9
+ track: string;
10
+ timestamp: string;
11
+ sessionId: string;
12
+ url: string;
13
+ stepStart: number;
14
+ stepEnd: number;
15
+ }
16
+ /**
17
+ * Transport function: receives a batch of events and sends them somewhere.
18
+ * Override this to use fetch, XMLHttpRequest, image pixels, or logging.
19
+ */
20
+ type TransportFn = (events: AnalyticsEvent[]) => void;
21
+ interface AnalyticsMiddlewareOptions {
22
+ /** Endpoint URL for the default sendBeacon transport. Required if `transport` is not provided. */
23
+ endpoint?: string;
24
+ /** Custom transport function. Overrides the default sendBeacon transport. */
25
+ transport?: TransportFn;
26
+ /** Auto-flush when the batch reaches this size. Default: 10 */
27
+ batchSize?: number;
28
+ /** Milliseconds between periodic flushes. Default: 5000 */
29
+ flushInterval?: number;
30
+ /** Sampling rate from 0 to 1. 1 = send everything, 0.1 = send 10%. Default: 1 */
31
+ sampleRate?: number;
32
+ /** Override the auto-generated session ID. */
33
+ sessionId?: string;
34
+ /** Injectable clock for deterministic tests. Default: () => new Date().toISOString() */
35
+ now?: () => string;
36
+ /** Injectable random for deterministic sampling tests. Default: Math.random */
37
+ random?: () => number;
38
+ }
39
+ interface AnalyticsMiddlewareResult {
40
+ /** The middleware function — pass to `timeline.use()`. */
41
+ middleware: MiddlewareFn;
42
+ /** Force-flush buffered events. Returns the flushed batch. */
43
+ flush: () => AnalyticsEvent[];
44
+ /** Flush remaining events, clear interval timer, remove pagehide listener. */
45
+ destroy: () => void;
46
+ }
47
+ /**
48
+ * Creates an analytics middleware that batches step:enter/step:exit events
49
+ * and flushes them via `navigator.sendBeacon` (or a custom transport).
50
+ *
51
+ * Demonstrates production analytics SDK patterns:
52
+ * - Event batching with configurable batch size
53
+ * - Periodic flush on a timer
54
+ * - `navigator.sendBeacon` for reliable delivery on page unload
55
+ * - Probabilistic sampling
56
+ * - Session enrichment (timestamp, sessionId, URL)
57
+ * - Graceful shutdown via `destroy()`
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * const analytics = createAnalyticsMiddleware({
62
+ * endpoint: "/api/events",
63
+ * batchSize: 20,
64
+ * flushInterval: 10_000,
65
+ * sampleRate: 0.5,
66
+ * });
67
+ *
68
+ * const unsub = timeline.use(analytics.middleware);
69
+ *
70
+ * // Later: teardown
71
+ * unsub();
72
+ * analytics.destroy();
73
+ * ```
74
+ */
75
+ declare function createAnalyticsMiddleware(options: AnalyticsMiddlewareOptions): AnalyticsMiddlewareResult;
76
+
77
+ export { type AnalyticsEvent, type AnalyticsMiddlewareOptions, type AnalyticsMiddlewareResult, type TransportFn, createAnalyticsMiddleware };
@@ -0,0 +1,77 @@
1
+ import { M as MiddlewareFn } from './middleware-DQus4IrE.js';
2
+
3
+ /**
4
+ * A single analytics event enriched with session metadata, ready for transport.
5
+ */
6
+ interface AnalyticsEvent {
7
+ eventType: "step:enter" | "step:exit";
8
+ stepName: string;
9
+ track: string;
10
+ timestamp: string;
11
+ sessionId: string;
12
+ url: string;
13
+ stepStart: number;
14
+ stepEnd: number;
15
+ }
16
+ /**
17
+ * Transport function: receives a batch of events and sends them somewhere.
18
+ * Override this to use fetch, XMLHttpRequest, image pixels, or logging.
19
+ */
20
+ type TransportFn = (events: AnalyticsEvent[]) => void;
21
+ interface AnalyticsMiddlewareOptions {
22
+ /** Endpoint URL for the default sendBeacon transport. Required if `transport` is not provided. */
23
+ endpoint?: string;
24
+ /** Custom transport function. Overrides the default sendBeacon transport. */
25
+ transport?: TransportFn;
26
+ /** Auto-flush when the batch reaches this size. Default: 10 */
27
+ batchSize?: number;
28
+ /** Milliseconds between periodic flushes. Default: 5000 */
29
+ flushInterval?: number;
30
+ /** Sampling rate from 0 to 1. 1 = send everything, 0.1 = send 10%. Default: 1 */
31
+ sampleRate?: number;
32
+ /** Override the auto-generated session ID. */
33
+ sessionId?: string;
34
+ /** Injectable clock for deterministic tests. Default: () => new Date().toISOString() */
35
+ now?: () => string;
36
+ /** Injectable random for deterministic sampling tests. Default: Math.random */
37
+ random?: () => number;
38
+ }
39
+ interface AnalyticsMiddlewareResult {
40
+ /** The middleware function — pass to `timeline.use()`. */
41
+ middleware: MiddlewareFn;
42
+ /** Force-flush buffered events. Returns the flushed batch. */
43
+ flush: () => AnalyticsEvent[];
44
+ /** Flush remaining events, clear interval timer, remove pagehide listener. */
45
+ destroy: () => void;
46
+ }
47
+ /**
48
+ * Creates an analytics middleware that batches step:enter/step:exit events
49
+ * and flushes them via `navigator.sendBeacon` (or a custom transport).
50
+ *
51
+ * Demonstrates production analytics SDK patterns:
52
+ * - Event batching with configurable batch size
53
+ * - Periodic flush on a timer
54
+ * - `navigator.sendBeacon` for reliable delivery on page unload
55
+ * - Probabilistic sampling
56
+ * - Session enrichment (timestamp, sessionId, URL)
57
+ * - Graceful shutdown via `destroy()`
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * const analytics = createAnalyticsMiddleware({
62
+ * endpoint: "/api/events",
63
+ * batchSize: 20,
64
+ * flushInterval: 10_000,
65
+ * sampleRate: 0.5,
66
+ * });
67
+ *
68
+ * const unsub = timeline.use(analytics.middleware);
69
+ *
70
+ * // Later: teardown
71
+ * unsub();
72
+ * analytics.destroy();
73
+ * ```
74
+ */
75
+ declare function createAnalyticsMiddleware(options: AnalyticsMiddlewareOptions): AnalyticsMiddlewareResult;
76
+
77
+ export { type AnalyticsEvent, type AnalyticsMiddlewareOptions, type AnalyticsMiddlewareResult, type TransportFn, createAnalyticsMiddleware };
@@ -0,0 +1,75 @@
1
+ // src/analytics.ts
2
+ function createBeaconTransport(endpoint) {
3
+ return (events) => {
4
+ const blob = new Blob([JSON.stringify(events)], {
5
+ type: "application/json"
6
+ });
7
+ navigator.sendBeacon(endpoint, blob);
8
+ };
9
+ }
10
+ function createAnalyticsMiddleware(options) {
11
+ const {
12
+ endpoint,
13
+ batchSize = 10,
14
+ flushInterval = 5e3,
15
+ sampleRate = 1,
16
+ sessionId = crypto.randomUUID(),
17
+ now = () => (/* @__PURE__ */ new Date()).toISOString(),
18
+ random = Math.random
19
+ } = options;
20
+ const transport = options.transport ?? createBeaconTransport(endpoint);
21
+ if (!options.transport && !endpoint) {
22
+ throw new Error(
23
+ "createAnalyticsMiddleware: provide either `endpoint` or a custom `transport`."
24
+ );
25
+ }
26
+ let buffer = [];
27
+ let destroyed = false;
28
+ function enrich(event) {
29
+ return {
30
+ eventType: event.type,
31
+ stepName: event.payload.name,
32
+ track: event.payload.track,
33
+ timestamp: now(),
34
+ sessionId,
35
+ url: typeof window !== "undefined" ? window.location.href : "unknown",
36
+ stepStart: event.payload.start,
37
+ stepEnd: event.payload.end
38
+ };
39
+ }
40
+ function flush() {
41
+ if (buffer.length === 0) return [];
42
+ const batch = buffer;
43
+ buffer = [];
44
+ transport(batch);
45
+ return batch;
46
+ }
47
+ const timer = setInterval(flush, flushInterval);
48
+ const handlePageHide = () => flush();
49
+ if (typeof window !== "undefined") {
50
+ window.addEventListener("pagehide", handlePageHide);
51
+ }
52
+ const middleware = (event, next) => {
53
+ if (!destroyed && random() < sampleRate) {
54
+ buffer.push(enrich(event));
55
+ if (buffer.length >= batchSize) {
56
+ flush();
57
+ }
58
+ }
59
+ next();
60
+ };
61
+ function destroy() {
62
+ if (destroyed) return;
63
+ destroyed = true;
64
+ flush();
65
+ clearInterval(timer);
66
+ if (typeof window !== "undefined") {
67
+ window.removeEventListener("pagehide", handlePageHide);
68
+ }
69
+ }
70
+ return { middleware, flush, destroy };
71
+ }
72
+
73
+ export { createAnalyticsMiddleware };
74
+ //# sourceMappingURL=analytics.js.map
75
+ //# sourceMappingURL=analytics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/analytics.ts"],"names":[],"mappings":"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","file":"analytics.js","sourcesContent":["import type { MiddlewareFn, MiddlewareEvent } from \"./middleware.js\";\n\n// --- Public types ---\n\n/**\n * A single analytics event enriched with session metadata, ready for transport.\n */\nexport interface AnalyticsEvent {\n eventType: \"step:enter\" | \"step:exit\";\n stepName: string;\n track: string;\n timestamp: string;\n sessionId: string;\n url: string;\n stepStart: number;\n stepEnd: number;\n}\n\n/**\n * Transport function: receives a batch of events and sends them somewhere.\n * Override this to use fetch, XMLHttpRequest, image pixels, or logging.\n */\nexport type TransportFn = (events: AnalyticsEvent[]) => void;\n\nexport interface AnalyticsMiddlewareOptions {\n /** Endpoint URL for the default sendBeacon transport. Required if `transport` is not provided. */\n endpoint?: string;\n /** Custom transport function. Overrides the default sendBeacon transport. */\n transport?: TransportFn;\n /** Auto-flush when the batch reaches this size. Default: 10 */\n batchSize?: number;\n /** Milliseconds between periodic flushes. Default: 5000 */\n flushInterval?: number;\n /** Sampling rate from 0 to 1. 1 = send everything, 0.1 = send 10%. Default: 1 */\n sampleRate?: number;\n /** Override the auto-generated session ID. */\n sessionId?: string;\n /** Injectable clock for deterministic tests. Default: () => new Date().toISOString() */\n now?: () => string;\n /** Injectable random for deterministic sampling tests. Default: Math.random */\n random?: () => number;\n}\n\nexport interface AnalyticsMiddlewareResult {\n /** The middleware function — pass to `timeline.use()`. */\n middleware: MiddlewareFn;\n /** Force-flush buffered events. Returns the flushed batch. */\n flush: () => AnalyticsEvent[];\n /** Flush remaining events, clear interval timer, remove pagehide listener. */\n destroy: () => void;\n}\n\n// --- Default transport ---\n\nfunction createBeaconTransport(endpoint: string): TransportFn {\n return (events) => {\n const blob = new Blob([JSON.stringify(events)], {\n type: \"application/json\",\n });\n navigator.sendBeacon(endpoint, blob);\n };\n}\n\n// --- Factory ---\n\n/**\n * Creates an analytics middleware that batches step:enter/step:exit events\n * and flushes them via `navigator.sendBeacon` (or a custom transport).\n *\n * Demonstrates production analytics SDK patterns:\n * - Event batching with configurable batch size\n * - Periodic flush on a timer\n * - `navigator.sendBeacon` for reliable delivery on page unload\n * - Probabilistic sampling\n * - Session enrichment (timestamp, sessionId, URL)\n * - Graceful shutdown via `destroy()`\n *\n * @example\n * ```ts\n * const analytics = createAnalyticsMiddleware({\n * endpoint: \"/api/events\",\n * batchSize: 20,\n * flushInterval: 10_000,\n * sampleRate: 0.5,\n * });\n *\n * const unsub = timeline.use(analytics.middleware);\n *\n * // Later: teardown\n * unsub();\n * analytics.destroy();\n * ```\n */\nexport function createAnalyticsMiddleware(\n options: AnalyticsMiddlewareOptions,\n): AnalyticsMiddlewareResult {\n const {\n endpoint,\n batchSize = 10,\n flushInterval = 5000,\n sampleRate = 1,\n sessionId = crypto.randomUUID(),\n now = () => new Date().toISOString(),\n random = Math.random,\n } = options;\n\n const transport =\n options.transport ?? createBeaconTransport(endpoint as string);\n\n if (!options.transport && !endpoint) {\n throw new Error(\n \"createAnalyticsMiddleware: provide either `endpoint` or a custom `transport`.\",\n );\n }\n\n let buffer: AnalyticsEvent[] = [];\n let destroyed = false;\n\n // --- Enrich ---\n\n function enrich(event: MiddlewareEvent): AnalyticsEvent {\n return {\n eventType: event.type,\n stepName: event.payload.name,\n track: event.payload.track,\n timestamp: now(),\n sessionId,\n url: typeof window !== \"undefined\" ? window.location.href : \"unknown\",\n stepStart: event.payload.start,\n stepEnd: event.payload.end,\n };\n }\n\n // --- Flush ---\n\n function flush(): AnalyticsEvent[] {\n if (buffer.length === 0) return [];\n const batch = buffer;\n buffer = [];\n transport(batch);\n return batch;\n }\n\n // --- Periodic flush timer ---\n\n const timer = setInterval(flush, flushInterval);\n\n // --- Page unload handler (SSR-safe) ---\n\n const handlePageHide = () => flush();\n\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"pagehide\", handlePageHide);\n }\n\n // --- Middleware ---\n\n const middleware: MiddlewareFn = (event, next) => {\n if (!destroyed && random() < sampleRate) {\n buffer.push(enrich(event));\n\n if (buffer.length >= batchSize) {\n flush();\n }\n }\n\n // Analytics middleware always passes through — it observes, never gates.\n next();\n };\n\n // --- Destroy ---\n\n function destroy(): void {\n if (destroyed) return;\n destroyed = true;\n flush();\n clearInterval(timer);\n if (typeof window !== \"undefined\") {\n window.removeEventListener(\"pagehide\", handlePageHide);\n }\n }\n\n return { middleware, flush, destroy };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/warnings.ts","../src/resolve-steps.ts","../src/easings.ts","../src/opacity.ts","../src/scroll-driver.ts","../src/emitter.ts","../src/scope.ts","../src/middleware.ts","../src/breakpoints.ts","../src/timeline.ts"],"names":["easing","duration","progress","maxScroll"],"mappings":"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","file":"index.cjs","sourcesContent":["export class MultitrackError extends Error {\n code: string;\n\n constructor(code: string, message: string) {\n super(`[@multitrack] ${code}: ${message}`);\n this.code = code;\n this.name = \"MultitrackError\";\n }\n}\n\nexport function stepNotFound(name: string): MultitrackError {\n return new MultitrackError(\n \"STEP_NOT_FOUND\",\n `Step \"${name}\" not found in resolved steps. Check that the step name matches your config.`,\n );\n}\n\nexport function emptyConfig(): MultitrackError {\n return new MultitrackError(\n \"EMPTY_CONFIG\",\n \"No step configurations provided. Pass at least one StepConfig to create a timeline.\",\n );\n}\n\nexport function duplicateStepName(name: string): MultitrackError {\n return new MultitrackError(\n \"DUPLICATE_STEP_NAME\",\n `Step name \"${name}\" is used more than once. Non-buffer step names must be unique.`,\n );\n}\n","/**\n * Dev-mode warnings for common configuration mistakes.\n * Gated behind process.env.NODE_ENV !== 'production' for tree-shaking.\n */\n\n// Minimal declaration so we don't need @types/node in this browser-first package.\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\n\nexport type WarningCode =\n | \"ZERO_DURATION\"\n | \"LONE_TRACK\"\n | \"SNAP_LONG_STEP\"\n | \"UNKNOWN_BREAKPOINT\";\n\nconst SNAP_LONG_THRESHOLD = 5;\n\n/** Tracks which warnings have already been emitted (deduplicate per session). */\nconst emitted = new Set<string>();\n\n/**\n * Emit a dev-mode warning. Each code+message combo fires at most once.\n */\nfunction warn(code: WarningCode, message: string): void {\n if (typeof process !== \"undefined\" && process.env?.NODE_ENV === \"production\") return;\n\n const key = `${code}:${message}`;\n if (emitted.has(key)) return;\n emitted.add(key);\n\n console.warn(`[@multitrack] ${code}: ${message}`);\n}\n\n/** Clear emitted warnings. Exposed for testing. */\nexport function resetWarnings(): void {\n emitted.clear();\n}\n\n/**\n * Validate step configs and emit warnings for common mistakes.\n * Called from resolveSteps() before resolution.\n */\nexport function validateStepConfigs(\n config: Array<{ name: string; duration: number; track: string; easing?: string | Function }>,\n): void {\n if (typeof process !== \"undefined\" && process.env?.NODE_ENV === \"production\") return;\n\n // Track name frequency (excluding buffers)\n const trackCounts = new Map<string, number>();\n\n for (const step of config) {\n // ZERO_DURATION: step with duration <= 0\n if (step.duration <= 0 && step.name !== \"buffer\") {\n warn(\n \"ZERO_DURATION\",\n `Step \"${step.name}\" has duration ${step.duration}. This is probably unintentional.`,\n );\n }\n\n // SNAP_LONG_STEP: snap easing on a long step\n const easing = step.easing ?? \"snap\";\n if (easing === \"snap\" && step.duration > SNAP_LONG_THRESHOLD && step.name !== \"buffer\") {\n warn(\n \"SNAP_LONG_STEP\",\n `Step \"${step.name}\" uses snap easing with duration ${step.duration}. Snap on long steps looks jarring — consider \"linear\" or \"easeInOut\".`,\n );\n }\n\n // Count track usage (exclude buffers)\n if (step.name !== \"buffer\") {\n trackCounts.set(step.track, (trackCounts.get(step.track) ?? 0) + 1);\n }\n }\n\n // LONE_TRACK: track name appears only once (likely a typo)\n // Only warn if there are multiple tracks total (single-track configs are fine)\n if (trackCounts.size > 1) {\n for (const [track, count] of trackCounts) {\n if (count === 1) {\n warn(\n \"LONE_TRACK\",\n `Track \"${track}\" has only one step. Is this a typo?`,\n );\n }\n }\n }\n}\n\n/**\n * Validate that all `when` values in step configs reference known breakpoint names.\n * Called once from Timeline constructor.\n */\nexport function validateBreakpointRefs(\n config: Array<{ name: string; when?: string }>,\n breakpointNames: string[],\n): void {\n if (typeof process !== \"undefined\" && process.env?.NODE_ENV === \"production\") return;\n\n const known = new Set(breakpointNames);\n for (const step of config) {\n if (step.when && !known.has(step.when)) {\n warn(\n \"UNKNOWN_BREAKPOINT\",\n `Step \"${step.name}\" references breakpoint \"${step.when}\" which is not defined. Known breakpoints: ${breakpointNames.join(\", \") || \"(none)\"}`,\n );\n }\n }\n}\n","import type { Step, StepConfig } from \"./types.js\";\nimport { duplicateStepName, emptyConfig } from \"./errors.js\";\nimport { validateStepConfigs } from \"./warnings.js\";\n\n/**\n * Resolve declarative step configs into absolute timeline positions.\n *\n * Each track maintains its own independent cursor. Steps are placed\n * sequentially within their track, producing parallel timelines that\n * can overlap across tracks.\n *\n * Extracted and generalized from sinking-china App.tsx:transformStepConfig()\n */\nexport function resolveSteps(config: StepConfig[]): Step[] {\n if (config.length === 0) {\n throw emptyConfig();\n }\n\n // Dev-mode warnings for common mistakes (tree-shaken in production)\n validateStepConfigs(config);\n\n // Filter by condition predicates\n const filtered = config.filter((step) => {\n if (step.condition) return step.condition();\n return true;\n });\n\n // Track cursors: each track maintains its own timeline position\n const trackCursors: Record<string, number> = {};\n let bufferIndex = 0;\n\n // Check for duplicate non-buffer names\n const seen = new Set<string>();\n\n const steps: Step[] = filtered.map((stepConfig) => {\n // Auto-rename buffers to avoid collisions\n const name =\n stepConfig.name === \"buffer\"\n ? `buffer_${++bufferIndex}`\n : stepConfig.name;\n\n // Validate uniqueness for non-buffer steps\n if (stepConfig.name !== \"buffer\") {\n if (seen.has(name)) {\n throw duplicateStepName(name);\n }\n seen.add(name);\n }\n\n const track = stepConfig.track;\n const start = trackCursors[track] ?? 0;\n const end = start + stepConfig.duration;\n trackCursors[track] = end;\n\n return {\n name,\n start,\n end,\n track,\n easing: stepConfig.easing ?? \"snap\",\n };\n });\n\n return steps;\n}\n\n/**\n * Get the total number of steps (the maximum end position across all tracks).\n */\nexport function getTotalSteps(steps: Step[]): number {\n return Math.max(...steps.map((s) => s.end));\n}\n","import type { EasingFunction, EasingPreset } from \"./types.js\";\n\n/**\n * Binary snap: opacity is 1 when inside range, 0 outside.\n * This was the default behavior in the original engine (isLinear: false).\n */\nexport const snap: EasingFunction = () => 1;\n\n/**\n * Linear interpolation from 0 to 1 over the step duration.\n * This was `isLinear: true` in the original engine.\n */\nexport const linear: EasingFunction = (t: number) => t;\n\n/** Quadratic ease-in: slow start, fast end. */\nexport const easeIn: EasingFunction = (t: number) => t * t;\n\n/** Quadratic ease-out: fast start, slow end. */\nexport const easeOut: EasingFunction = (t: number) => t * (2 - t);\n\n/** Quadratic ease-in-out: slow start and end, fast middle. */\nexport const easeInOut: EasingFunction = (t: number) =>\n t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;\n\n/** Map of preset names to easing functions. */\nexport const easingPresets: Record<EasingPreset, EasingFunction> = {\n snap,\n linear,\n easeIn,\n easeOut,\n easeInOut,\n};\n\n/**\n * Resolve an easing value (preset name or custom function) to an EasingFunction.\n */\nexport function resolveEasing(\n easing: EasingPreset | EasingFunction | undefined,\n): EasingFunction {\n if (easing === undefined) return snap;\n if (typeof easing === \"function\") return easing;\n return easingPresets[easing];\n}\n","import type { EasingFunction, EasingPreset, Opacities, Step } from \"./types.js\";\nimport { resolveEasing } from \"./easings.js\";\nimport { stepNotFound } from \"./errors.js\";\n\n/**\n * Calculate the opacity for a single step at a given scroll position.\n *\n * The easing function controls how opacity transitions within the step range:\n * - \"snap\": binary 0 or 1 (instant appear/disappear)\n * - \"linear\": smooth 0→1 over the step duration\n * - Custom easing function: any (t: number) => number mapping\n *\n * Extracted and generalized from sinking-china utils.ts:calculateScrollBasedOpacity()\n */\nexport function calculateStepOpacity(\n step: Step,\n totalSteps: number,\n scrollPercentage: number,\n): number {\n const normalizedScroll = scrollPercentage * totalSteps;\n\n // Outside the step range\n if (normalizedScroll < step.start || normalizedScroll > step.end) {\n return 0;\n }\n\n // Last step stays visible once entered\n const isLastStep = step.end === totalSteps;\n if (isLastStep && normalizedScroll >= step.start) {\n const easing = resolveEasing(step.easing);\n // For snap, return 1; for others, calculate progress but cap at 1\n if (step.easing === \"snap\" || step.easing === undefined) return 1;\n const duration = step.end - step.start;\n if (duration === 0) return 1;\n const progress = Math.min((normalizedScroll - step.start) / duration, 1);\n return easing(progress);\n }\n\n // Apply easing\n const easing = resolveEasing(step.easing);\n\n // Snap mode: instant opacity\n if (step.easing === \"snap\" || step.easing === undefined) {\n return 1;\n }\n\n // Calculate progress within the step (0 to 1)\n const duration = step.end - step.start;\n if (duration === 0) return 1;\n const progress = (normalizedScroll - step.start) / duration;\n\n return easing(Math.max(0, Math.min(1, progress)));\n}\n\n/**\n * Calculate opacities for all steps at a given scroll position.\n *\n * Returns a record mapping step names to their current opacity (0-1).\n *\n * Extracted from sinking-china utils.ts:calculateAllOpacities()\n */\nexport function calculateAllOpacities<T extends string = string>(\n scrollPercentage: number,\n steps: Step[],\n): Opacities<T> {\n const totalSteps = Math.max(...steps.map((s) => s.end));\n\n return steps.reduce(\n (acc, step) => {\n (acc as Record<string, number>)[step.name] = calculateStepOpacity(\n step,\n totalSteps,\n scrollPercentage,\n );\n return acc;\n },\n {} as Opacities<T>,\n );\n}\n\n/**\n * Get the start/end range for a named step.\n *\n * Extracted from sinking-china utils.ts:getStepRange()\n */\nexport function getStepRange(\n stepName: string,\n steps: Step[],\n): { start: number; end: number } {\n const step = steps.find((s) => s.name === stepName);\n if (!step) {\n throw stepNotFound(stepName);\n }\n return { start: step.start, end: step.end };\n}\n\n/**\n * Get info about all steps that are active at a given position.\n *\n * Extracted from sinking-china utils.ts:getCurrentStepInfo()\n */\nexport function getCurrentSteps(\n currentStep: number,\n steps: Step[],\n): Array<{ name: string; track: string; start: number; end: number }> {\n const active = steps.filter(\n (s) => currentStep >= s.start && currentStep < s.end,\n );\n\n if (active.length > 0) {\n return active.map((s) => ({\n name: s.name,\n track: s.track,\n start: s.start,\n end: s.end,\n }));\n }\n\n return [];\n}\n","import type { ScrollDriverOptions } from \"./types.js\";\n\nexport type ScrollCallback = (scrollPercentage: number) => void;\n\n/**\n * Manages scroll event listening and converts scroll position to a 0-1 progress value.\n *\n * Extracted and generalized from sinking-china MainMap.tsx scroll handler.\n * The original was tightly coupled to window.scrollY and React state — this\n * version attaches to any scrollable element and uses a subscription API.\n */\nexport class ScrollDriver {\n private target: HTMLElement | Window;\n private callbacks = new Set<ScrollCallback>();\n private bound: (() => void) | null = null;\n private _scrollPercentage = 0;\n\n constructor(options: ScrollDriverOptions = {}) {\n this.target = options.target ?? window;\n }\n\n /** Current scroll progress (0 to 1). */\n get scrollPercentage(): number {\n return this._scrollPercentage;\n }\n\n /**\n * Subscribe to scroll updates. Returns an unsubscribe function.\n */\n onScroll(callback: ScrollCallback): () => void {\n this.callbacks.add(callback);\n return () => {\n this.callbacks.delete(callback);\n };\n }\n\n /**\n * Start listening for scroll events.\n */\n start(): void {\n if (this.bound) return;\n\n this.bound = () => {\n this._scrollPercentage = this.calculateProgress();\n this.callbacks.forEach((cb) => cb(this._scrollPercentage));\n };\n\n this.target.addEventListener(\"scroll\", this.bound, { passive: true });\n\n // Fire initial value\n this.bound();\n }\n\n /**\n * Stop listening and clean up.\n */\n destroy(): void {\n if (this.bound) {\n this.target.removeEventListener(\"scroll\", this.bound);\n this.bound = null;\n }\n this.callbacks.clear();\n }\n\n private calculateProgress(): number {\n if (this.target === window || this.target instanceof Window) {\n const maxScroll =\n document.documentElement.scrollHeight - window.innerHeight;\n if (maxScroll <= 0) return 0;\n return Math.max(0, Math.min(1, window.scrollY / maxScroll));\n }\n\n // HTMLElement target\n const el = this.target as HTMLElement;\n const maxScroll = el.scrollHeight - el.clientHeight;\n if (maxScroll <= 0) return 0;\n return Math.max(0, Math.min(1, el.scrollTop / maxScroll));\n }\n}\n","import type {\n TimelineEventMap,\n TimelineEventType,\n TimelineEventHandler,\n} from \"./types.js\";\n\n/**\n * Minimal typed event emitter for timeline events.\n */\nexport class Emitter {\n private listeners = new Map<\n TimelineEventType,\n Set<TimelineEventHandler<any>>\n >();\n\n on<T extends TimelineEventType>(\n event: T,\n handler: TimelineEventHandler<T>,\n ): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(handler);\n\n // Return unsubscribe function\n return () => {\n this.listeners.get(event)?.delete(handler);\n };\n }\n\n emit<T extends TimelineEventType>(\n event: T,\n payload: TimelineEventMap[T],\n ): void {\n this.listeners.get(event)?.forEach((handler) => handler(payload));\n }\n\n removeAllListeners(): void {\n this.listeners.clear();\n }\n}\n","/**\n * Collects disposer functions (unsubscribes) so they can be\n * cleaned up in a single `dispose()` call.\n *\n * Inspired by GSAP's gsap.context() — moves cleanup from a\n * discipline problem to an API guarantee.\n */\nexport class Scope {\n private disposers: Array<() => void> = [];\n\n /** Track a disposer function for later cleanup. */\n add(disposer: () => void): void {\n this.disposers.push(disposer);\n }\n\n /** Call all tracked disposers and clear the list. Idempotent. */\n dispose(): void {\n // Dispose in reverse order (LIFO)\n for (let i = this.disposers.length - 1; i >= 0; i--) {\n this.disposers[i]();\n }\n this.disposers.length = 0;\n }\n\n /** Number of tracked disposers. */\n get size(): number {\n return this.disposers.length;\n }\n}\n","import type { StepEventPayload } from \"./types.js\";\n\n/**\n * Event passed to middleware functions during step transitions.\n */\nexport interface MiddlewareEvent {\n type: \"step:enter\" | \"step:exit\";\n payload: StepEventPayload;\n}\n\n/**\n * Middleware function signature.\n * Call `next()` to continue the chain; skip it to swallow the event.\n */\nexport type MiddlewareFn = (event: MiddlewareEvent, next: () => void) => void;\n\n/**\n * Manages an ordered chain of middleware functions.\n * Each middleware can inspect/modify events and decide whether to pass through.\n */\nexport class MiddlewareChain {\n private fns: MiddlewareFn[] = [];\n\n /** Add middleware. Returns a function to remove it. */\n add(fn: MiddlewareFn): () => void {\n this.fns.push(fn);\n return () => {\n const idx = this.fns.indexOf(fn);\n if (idx !== -1) this.fns.splice(idx, 1);\n };\n }\n\n /**\n * Run the middleware chain. If all middleware call `next()`,\n * `finalAction` executes (emitting the event to listeners).\n * If any middleware skips `next()`, the event is swallowed.\n */\n run(event: MiddlewareEvent, finalAction: () => void): void {\n let index = 0;\n const fns = this.fns;\n\n const next = () => {\n if (index < fns.length) {\n fns[index++](event, next);\n } else {\n finalAction();\n }\n };\n\n next();\n }\n\n /** Remove all middleware. */\n clear(): void {\n this.fns.length = 0;\n }\n}\n","/**\n * Manages named breakpoints backed by CSS media queries.\n * Listens for changes and notifies the Timeline to re-resolve steps.\n *\n * SSR-safe: no-op when `window` is not available.\n */\nexport class BreakpointManager {\n private queries: Map<string, MediaQueryList> = new Map();\n private listeners: Array<() => void> = [];\n\n constructor(breakpoints: Record<string, string>) {\n if (typeof globalThis.matchMedia !== \"function\") return;\n\n for (const [name, query] of Object.entries(breakpoints)) {\n this.queries.set(name, globalThis.matchMedia(query));\n }\n }\n\n /** Whether a named breakpoint currently matches. */\n isActive(name: string): boolean {\n return this.queries.get(name)?.matches ?? false;\n }\n\n /** All registered breakpoint names. */\n get names(): string[] {\n return [...this.queries.keys()];\n }\n\n /**\n * Subscribe to breakpoint changes. Returns unsubscribe function.\n * The callback fires whenever any breakpoint's match state changes.\n */\n onChange(callback: () => void): () => void {\n const handler = () => callback();\n this.listeners.push(handler);\n\n for (const mql of this.queries.values()) {\n mql.addEventListener(\"change\", handler);\n }\n\n return () => {\n for (const mql of this.queries.values()) {\n mql.removeEventListener(\"change\", handler);\n }\n const idx = this.listeners.indexOf(handler);\n if (idx !== -1) this.listeners.splice(idx, 1);\n };\n }\n\n /** Remove all change listeners. */\n destroy(): void {\n for (const handler of this.listeners) {\n for (const mql of this.queries.values()) {\n mql.removeEventListener(\"change\", handler);\n }\n }\n this.listeners.length = 0;\n }\n}\n","import type {\n DevtoolsState,\n Opacities,\n Step,\n StepConfig,\n TimelineEventHandler,\n TimelineEventType,\n TimelineOptions,\n} from \"./types.js\";\nimport { resolveSteps, getTotalSteps } from \"./resolve-steps.js\";\nimport {\n calculateAllOpacities,\n calculateStepOpacity,\n getStepRange,\n getCurrentSteps,\n} from \"./opacity.js\";\nimport { ScrollDriver } from \"./scroll-driver.js\";\nimport { Emitter } from \"./emitter.js\";\nimport { Scope } from \"./scope.js\";\nimport { MiddlewareChain, type MiddlewareFn } from \"./middleware.js\";\nimport { BreakpointManager } from \"./breakpoints.js\";\nimport { validateBreakpointRefs } from \"./warnings.js\";\n\n/**\n * Timeline: the main facade that ties everything together.\n *\n * Usage:\n * ```ts\n * const timeline = new Timeline({\n * config: [\n * { name: \"intro\", duration: 3, track: \"main\", easing: \"linear\" },\n * { name: \"content\", duration: 5, track: \"main\" },\n * { name: \"caption\", duration: 2, track: \"text\" },\n * ],\n * });\n *\n * timeline.start(); // begins listening to scroll\n * timeline.on(\"scroll\", ({ scrollPercentage }) => {\n * const opacities = timeline.getOpacities(scrollPercentage);\n * });\n * ```\n */\nexport class Timeline {\n private _steps: Step[];\n private _totalSteps: number;\n private config: StepConfig[];\n\n private scrollDriver: ScrollDriver;\n private emitter = new Emitter();\n private activeSteps = new Set<string>();\n private devtoolsEnabled: boolean;\n private scrollUnsubscribe: (() => void) | null = null;\n private _activeScope: Scope | null = null;\n private middleware = new MiddlewareChain();\n private breakpointManager: BreakpointManager | null = null;\n private breakpointUnsub: (() => void) | null = null;\n\n constructor(options: TimelineOptions) {\n this.config = options.config;\n this.devtoolsEnabled = options.devtools ?? false;\n\n // Set up breakpoint manager if breakpoints are provided\n if (options.breakpoints && Object.keys(options.breakpoints).length > 0) {\n this.breakpointManager = new BreakpointManager(options.breakpoints);\n validateBreakpointRefs(options.config, this.breakpointManager.names);\n }\n\n // Initial resolution (filtered by current breakpoint state)\n this._steps = this.resolveWithBreakpoints();\n this._totalSteps = getTotalSteps(this._steps);\n this.scrollDriver = new ScrollDriver();\n }\n\n /** Resolved steps (re-computed on breakpoint changes). */\n get steps(): Step[] {\n return this._steps;\n }\n\n /** Total steps across all tracks (re-computed on breakpoint changes). */\n get totalSteps(): number {\n return this._totalSteps;\n }\n\n /** Start listening to scroll events. */\n start(): void {\n // Clean up any prior subscription (safe for repeated start/destroy cycles)\n this.scrollUnsubscribe?.();\n\n // Subscribe to breakpoint changes for reactive re-resolution\n if (this.breakpointManager) {\n this.breakpointUnsub?.();\n this.breakpointUnsub = this.breakpointManager.onChange(() => {\n this.reconfigure();\n });\n }\n\n // Wire scroll driver to event emission\n this.scrollUnsubscribe = this.scrollDriver.onScroll((scrollPercentage) => {\n const currentStep = scrollPercentage * this._totalSteps;\n\n // Emit scroll event\n this.emitter.emit(\"scroll\", { scrollPercentage, currentStep });\n\n // Track step enter/exit events\n const nowActive = new Set<string>();\n for (const step of this._steps) {\n if (currentStep >= step.start && currentStep < step.end) {\n nowActive.add(step.name);\n\n if (!this.activeSteps.has(step.name)) {\n const payload = {\n name: step.name,\n track: step.track,\n start: step.start,\n end: step.end,\n };\n this.middleware.run(\n { type: \"step:enter\", payload },\n () => this.emitter.emit(\"step:enter\", payload),\n );\n }\n }\n }\n\n // Emit exit for steps that were active but no longer are\n for (const name of this.activeSteps) {\n if (!nowActive.has(name)) {\n const step = this._steps.find((s) => s.name === name);\n if (step) {\n const payload = {\n name: step.name,\n track: step.track,\n start: step.start,\n end: step.end,\n };\n this.middleware.run(\n { type: \"step:exit\", payload },\n () => this.emitter.emit(\"step:exit\", payload),\n );\n }\n }\n }\n\n this.activeSteps = nowActive;\n\n // Update devtools state\n if (this.devtoolsEnabled && typeof window !== \"undefined\") {\n this.updateDevtools(scrollPercentage, currentStep);\n }\n });\n\n // Set initial devtools state before any scroll\n if (this.devtoolsEnabled && typeof window !== \"undefined\") {\n this.updateDevtools(0, 0);\n }\n this.scrollDriver.start();\n }\n\n /** Stop listening and clean up all resources. */\n destroy(): void {\n this.scrollUnsubscribe?.();\n this.scrollUnsubscribe = null;\n this.breakpointUnsub?.();\n this.breakpointUnsub = null;\n this.breakpointManager?.destroy();\n this.scrollDriver.destroy();\n this.emitter.removeAllListeners();\n this.middleware.clear();\n this.activeSteps.clear();\n if (\n this.devtoolsEnabled &&\n typeof window !== \"undefined\" &&\n (window as any).__MULTITRACK_DEVTOOLS__\n ) {\n delete (window as any).__MULTITRACK_DEVTOOLS__;\n }\n }\n\n /**\n * Register middleware that intercepts step:enter/step:exit events.\n * Call `next()` to pass through, or skip it to swallow the event.\n *\n * ```ts\n * timeline.use((event, next) => {\n * analytics.track(event.type, event.payload.name);\n * next();\n * });\n * ```\n */\n use(fn: MiddlewareFn): () => void {\n const unsub = this.middleware.add(fn);\n this._activeScope?.add(unsub);\n return unsub;\n }\n\n /** Subscribe to timeline events. Returns an unsubscribe function. */\n on<T extends TimelineEventType>(\n event: T,\n handler: TimelineEventHandler<T>,\n ): () => void {\n const unsub = this.emitter.on(event, handler);\n this._activeScope?.add(unsub);\n return unsub;\n }\n\n /**\n * Collect all subscriptions created inside `fn` into a Scope.\n * Call `scope.dispose()` to clean them all up at once.\n *\n * ```ts\n * const ctx = timeline.scope(() => {\n * timeline.on('step:enter', handleEnter);\n * timeline.on('scroll', handleScroll);\n * });\n * // later: ctx.dispose() cleans up both listeners\n * ```\n */\n scope(fn: () => void): Scope {\n const scope = new Scope();\n const previousScope = this._activeScope;\n this._activeScope = scope;\n try {\n fn();\n } finally {\n this._activeScope = previousScope;\n }\n return scope;\n }\n\n /** Current scroll progress (0 to 1). */\n get scrollPercentage(): number {\n return this.scrollDriver.scrollPercentage;\n }\n\n /** Current step position (0 to totalSteps). */\n get currentStep(): number {\n return this.scrollDriver.scrollPercentage * this._totalSteps;\n }\n\n /** Calculate opacities for all steps at a given scroll position. */\n getOpacities<T extends string = string>(\n scrollPercentage?: number,\n ): Opacities<T> {\n return calculateAllOpacities<T>(\n scrollPercentage ?? this.scrollDriver.scrollPercentage,\n this._steps,\n );\n }\n\n /** Get the start/end range for a named step. */\n getStepRange(name: string): { start: number; end: number } {\n return getStepRange(name, this._steps);\n }\n\n /** Get all steps that are active at a given position. */\n getCurrentSteps(position?: number) {\n return getCurrentSteps(position ?? this.currentStep, this._steps);\n }\n\n /** Enable devtools integration (exposes state on window.__MULTITRACK_DEVTOOLS__). */\n enableDevtools(): void {\n this.devtoolsEnabled = true;\n }\n\n /**\n * Filter config by active breakpoints and resolve steps.\n * Steps without `when` are always included.\n * Steps with `when` are included only if that breakpoint currently matches.\n */\n private resolveWithBreakpoints(): Step[] {\n const filtered = this.breakpointManager\n ? this.config.filter(\n (step) => !step.when || this.breakpointManager!.isActive(step.when),\n )\n : this.config;\n\n return resolveSteps(filtered);\n }\n\n /**\n * Re-resolve steps after a breakpoint change.\n * Clears active steps and emits timeline:reconfigure.\n */\n private reconfigure(): void {\n this._steps = this.resolveWithBreakpoints();\n this._totalSteps = getTotalSteps(this._steps);\n this.activeSteps.clear();\n\n this.emitter.emit(\"timeline:reconfigure\", {\n steps: this._steps,\n totalSteps: this._totalSteps,\n });\n\n // Update devtools with new step layout\n if (this.devtoolsEnabled && typeof window !== \"undefined\") {\n this.updateDevtools(this.scrollPercentage, this.currentStep);\n }\n }\n\n private updateDevtools(\n scrollPercentage: number,\n currentStep: number,\n ): void {\n // Serialize steps without function references so JSON.stringify works in devtools eval\n const serializableSteps: Step[] = this._steps.map((s) => ({\n name: s.name,\n start: s.start,\n end: s.end,\n track: s.track,\n easing: (typeof s.easing === \"function\" ? \"custom\" : s.easing) as Step[\"easing\"],\n }));\n const state: DevtoolsState = {\n steps: serializableSteps,\n currentStep,\n totalSteps: this._totalSteps,\n opacities: this.getOpacities(scrollPercentage),\n scrollPercentage,\n };\n (window as any).__MULTITRACK_DEVTOOLS__ = state;\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/warnings.ts","../src/resolve-steps.ts","../src/easings.ts","../src/opacity.ts","../src/scroll-driver.ts","../src/emitter.ts","../src/scope.ts","../src/middleware.ts","../src/breakpoints.ts","../src/timeline.ts"],"names":["easing","duration","progress","maxScroll"],"mappings":"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","file":"index.cjs","sourcesContent":["export class MultitrackError extends Error {\n code: string;\n\n constructor(code: string, message: string) {\n super(`[@multitrack] ${code}: ${message}`);\n this.code = code;\n this.name = \"MultitrackError\";\n }\n}\n\nexport function stepNotFound(name: string): MultitrackError {\n return new MultitrackError(\n \"STEP_NOT_FOUND\",\n `Step \"${name}\" not found in resolved steps. Check that the step name matches your config.`,\n );\n}\n\nexport function emptyConfig(): MultitrackError {\n return new MultitrackError(\n \"EMPTY_CONFIG\",\n \"No step configurations provided. Pass at least one StepConfig to create a timeline.\",\n );\n}\n\nexport function duplicateStepName(name: string): MultitrackError {\n return new MultitrackError(\n \"DUPLICATE_STEP_NAME\",\n `Step name \"${name}\" is used more than once. Non-buffer step names must be unique.`,\n );\n}\n","/**\n * Dev-mode warnings for common configuration mistakes.\n * Gated behind process.env.NODE_ENV !== 'production' for tree-shaking.\n */\n\n// Minimal declaration so we don't need @types/node in this browser-first package.\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\n\nexport type WarningCode =\n | \"ZERO_DURATION\"\n | \"LONE_TRACK\"\n | \"SNAP_LONG_STEP\"\n | \"UNKNOWN_BREAKPOINT\";\n\nconst SNAP_LONG_THRESHOLD = 5;\n\n/** Tracks which warnings have already been emitted (deduplicate per session). */\nconst emitted = new Set<string>();\n\n/**\n * Emit a dev-mode warning. Each code+message combo fires at most once.\n */\nfunction warn(code: WarningCode, message: string): void {\n if (typeof process !== \"undefined\" && process.env?.NODE_ENV === \"production\") return;\n\n const key = `${code}:${message}`;\n if (emitted.has(key)) return;\n emitted.add(key);\n\n console.warn(`[@multitrack] ${code}: ${message}`);\n}\n\n/** Clear emitted warnings. Exposed for testing. */\nexport function resetWarnings(): void {\n emitted.clear();\n}\n\n/**\n * Validate step configs and emit warnings for common mistakes.\n * Called from resolveSteps() before resolution.\n */\nexport function validateStepConfigs(\n config: Array<{ name: string; duration: number; track: string; easing?: string | Function }>,\n): void {\n if (typeof process !== \"undefined\" && process.env?.NODE_ENV === \"production\") return;\n\n // Track name frequency (excluding buffers)\n const trackCounts = new Map<string, number>();\n\n for (const step of config) {\n // ZERO_DURATION: step with duration <= 0\n if (step.duration <= 0 && step.name !== \"buffer\") {\n warn(\n \"ZERO_DURATION\",\n `Step \"${step.name}\" has duration ${step.duration}. This is probably unintentional.`,\n );\n }\n\n // SNAP_LONG_STEP: snap easing on a long step\n const easing = step.easing ?? \"snap\";\n if (easing === \"snap\" && step.duration > SNAP_LONG_THRESHOLD && step.name !== \"buffer\") {\n warn(\n \"SNAP_LONG_STEP\",\n `Step \"${step.name}\" uses snap easing with duration ${step.duration}. Snap on long steps looks jarring — consider \"linear\" or \"easeInOut\".`,\n );\n }\n\n // Count track usage (exclude buffers)\n if (step.name !== \"buffer\") {\n trackCounts.set(step.track, (trackCounts.get(step.track) ?? 0) + 1);\n }\n }\n\n // LONE_TRACK: track name appears only once (likely a typo)\n // Only warn if there are multiple tracks total (single-track configs are fine)\n if (trackCounts.size > 1) {\n for (const [track, count] of trackCounts) {\n if (count === 1) {\n warn(\n \"LONE_TRACK\",\n `Track \"${track}\" has only one step. Is this a typo?`,\n );\n }\n }\n }\n}\n\n/**\n * Validate that all `when` values in step configs reference known breakpoint names.\n * Called once from Timeline constructor.\n */\nexport function validateBreakpointRefs(\n config: Array<{ name: string; when?: string }>,\n breakpointNames: string[],\n): void {\n if (typeof process !== \"undefined\" && process.env?.NODE_ENV === \"production\") return;\n\n const known = new Set(breakpointNames);\n for (const step of config) {\n if (step.when && !known.has(step.when)) {\n warn(\n \"UNKNOWN_BREAKPOINT\",\n `Step \"${step.name}\" references breakpoint \"${step.when}\" which is not defined. Known breakpoints: ${breakpointNames.join(\", \") || \"(none)\"}`,\n );\n }\n }\n}\n","import type { Step, StepConfig } from \"./types.js\";\nimport { duplicateStepName, emptyConfig } from \"./errors.js\";\nimport { validateStepConfigs } from \"./warnings.js\";\n\n/**\n * Resolve declarative step configs into absolute timeline positions.\n *\n * Each track maintains its own independent cursor. Steps are placed\n * sequentially within their track, producing parallel timelines that\n * can overlap across tracks.\n *\n * Extracted and generalized from sinking-china App.tsx:transformStepConfig()\n */\nexport function resolveSteps(config: StepConfig[]): Step[] {\n if (config.length === 0) {\n throw emptyConfig();\n }\n\n // Dev-mode warnings for common mistakes (tree-shaken in production)\n validateStepConfigs(config);\n\n // Filter by condition predicates\n const filtered = config.filter((step) => {\n if (step.condition) return step.condition();\n return true;\n });\n\n // Track cursors: each track maintains its own timeline position\n const trackCursors: Record<string, number> = {};\n let bufferIndex = 0;\n\n // Check for duplicate non-buffer names\n const seen = new Set<string>();\n\n const steps: Step[] = filtered.map((stepConfig) => {\n // Auto-rename buffers to avoid collisions\n const name =\n stepConfig.name === \"buffer\"\n ? `buffer_${++bufferIndex}`\n : stepConfig.name;\n\n // Validate uniqueness for non-buffer steps\n if (stepConfig.name !== \"buffer\") {\n if (seen.has(name)) {\n throw duplicateStepName(name);\n }\n seen.add(name);\n }\n\n const track = stepConfig.track;\n const start = trackCursors[track] ?? 0;\n const end = start + stepConfig.duration;\n trackCursors[track] = end;\n\n return {\n name,\n start,\n end,\n track,\n easing: stepConfig.easing ?? \"snap\",\n };\n });\n\n return steps;\n}\n\n/**\n * Get the total number of steps (the maximum end position across all tracks).\n */\nexport function getTotalSteps(steps: Step[]): number {\n return Math.max(...steps.map((s) => s.end));\n}\n","import type { EasingFunction, EasingPreset } from \"./types.js\";\n\n/**\n * Binary snap: opacity is 1 when inside range, 0 outside.\n * This was the default behavior in the original engine (isLinear: false).\n */\nexport const snap: EasingFunction = () => 1;\n\n/**\n * Linear interpolation from 0 to 1 over the step duration.\n * This was `isLinear: true` in the original engine.\n */\nexport const linear: EasingFunction = (t: number) => t;\n\n/** Quadratic ease-in: slow start, fast end. */\nexport const easeIn: EasingFunction = (t: number) => t * t;\n\n/** Quadratic ease-out: fast start, slow end. */\nexport const easeOut: EasingFunction = (t: number) => t * (2 - t);\n\n/** Quadratic ease-in-out: slow start and end, fast middle. */\nexport const easeInOut: EasingFunction = (t: number) =>\n t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;\n\n/** Map of preset names to easing functions. */\nexport const easingPresets: Record<EasingPreset, EasingFunction> = {\n snap,\n linear,\n easeIn,\n easeOut,\n easeInOut,\n};\n\n/**\n * Resolve an easing value (preset name or custom function) to an EasingFunction.\n */\nexport function resolveEasing(\n easing: EasingPreset | EasingFunction | undefined,\n): EasingFunction {\n if (easing === undefined) return snap;\n if (typeof easing === \"function\") return easing;\n return easingPresets[easing];\n}\n","import type { Opacities, Step } from \"./types.js\";\nimport { resolveEasing } from \"./easings.js\";\nimport { stepNotFound } from \"./errors.js\";\n\n/**\n * Calculate the opacity for a single step at a given scroll position.\n *\n * The easing function controls how opacity transitions within the step range:\n * - \"snap\": binary 0 or 1 (instant appear/disappear)\n * - \"linear\": smooth 0→1 over the step duration\n * - Custom easing function: any (t: number) => number mapping\n *\n * Extracted and generalized from sinking-china utils.ts:calculateScrollBasedOpacity()\n */\nexport function calculateStepOpacity(\n step: Step,\n totalSteps: number,\n scrollPercentage: number,\n): number {\n const normalizedScroll = scrollPercentage * totalSteps;\n\n // Outside the step range\n if (normalizedScroll < step.start || normalizedScroll > step.end) {\n return 0;\n }\n\n // Last step stays visible once entered\n const isLastStep = step.end === totalSteps;\n if (isLastStep && normalizedScroll >= step.start) {\n const easing = resolveEasing(step.easing);\n // For snap, return 1; for others, calculate progress but cap at 1\n if (step.easing === \"snap\" || step.easing === undefined) return 1;\n const duration = step.end - step.start;\n if (duration === 0) return 1;\n const progress = Math.min((normalizedScroll - step.start) / duration, 1);\n return easing(progress);\n }\n\n // Apply easing\n const easing = resolveEasing(step.easing);\n\n // Snap mode: instant opacity\n if (step.easing === \"snap\" || step.easing === undefined) {\n return 1;\n }\n\n // Calculate progress within the step (0 to 1)\n const duration = step.end - step.start;\n if (duration === 0) return 1;\n const progress = (normalizedScroll - step.start) / duration;\n\n return easing(Math.max(0, Math.min(1, progress)));\n}\n\n/**\n * Calculate opacities for all steps at a given scroll position.\n *\n * Returns a record mapping step names to their current opacity (0-1).\n *\n * Extracted from sinking-china utils.ts:calculateAllOpacities()\n */\nexport function calculateAllOpacities<T extends string = string>(\n scrollPercentage: number,\n steps: Step[],\n): Opacities<T> {\n const totalSteps = Math.max(...steps.map((s) => s.end));\n\n return steps.reduce(\n (acc, step) => {\n (acc as Record<string, number>)[step.name] = calculateStepOpacity(\n step,\n totalSteps,\n scrollPercentage,\n );\n return acc;\n },\n {} as Opacities<T>,\n );\n}\n\n/**\n * Get the start/end range for a named step.\n *\n * Extracted from sinking-china utils.ts:getStepRange()\n */\nexport function getStepRange(\n stepName: string,\n steps: Step[],\n): { start: number; end: number } {\n const step = steps.find((s) => s.name === stepName);\n if (!step) {\n throw stepNotFound(stepName);\n }\n return { start: step.start, end: step.end };\n}\n\n/**\n * Get info about all steps that are active at a given position.\n *\n * Extracted from sinking-china utils.ts:getCurrentStepInfo()\n */\nexport function getCurrentSteps(\n currentStep: number,\n steps: Step[],\n): Array<{ name: string; track: string; start: number; end: number }> {\n const active = steps.filter(\n (s) => currentStep >= s.start && currentStep < s.end,\n );\n\n if (active.length > 0) {\n return active.map((s) => ({\n name: s.name,\n track: s.track,\n start: s.start,\n end: s.end,\n }));\n }\n\n return [];\n}\n","import type { ScrollDriverOptions } from \"./types.js\";\n\nexport type ScrollCallback = (scrollPercentage: number) => void;\n\n/**\n * Manages scroll event listening and converts scroll position to a 0-1 progress value.\n *\n * Extracted and generalized from sinking-china MainMap.tsx scroll handler.\n * The original was tightly coupled to window.scrollY and React state — this\n * version attaches to any scrollable element and uses a subscription API.\n */\nexport class ScrollDriver {\n private target: HTMLElement | Window;\n private callbacks = new Set<ScrollCallback>();\n private bound: (() => void) | null = null;\n private _scrollPercentage = 0;\n\n constructor(options: ScrollDriverOptions = {}) {\n this.target = options.target ?? window;\n }\n\n /** Current scroll progress (0 to 1). */\n get scrollPercentage(): number {\n return this._scrollPercentage;\n }\n\n /**\n * Subscribe to scroll updates. Returns an unsubscribe function.\n */\n onScroll(callback: ScrollCallback): () => void {\n this.callbacks.add(callback);\n return () => {\n this.callbacks.delete(callback);\n };\n }\n\n /**\n * Start listening for scroll events.\n */\n start(): void {\n if (this.bound) return;\n\n this.bound = () => {\n this._scrollPercentage = this.calculateProgress();\n this.callbacks.forEach((cb) => cb(this._scrollPercentage));\n };\n\n this.target.addEventListener(\"scroll\", this.bound, { passive: true });\n\n // Fire initial value\n this.bound();\n }\n\n /**\n * Stop listening and clean up.\n */\n destroy(): void {\n if (this.bound) {\n this.target.removeEventListener(\"scroll\", this.bound);\n this.bound = null;\n }\n this.callbacks.clear();\n }\n\n private calculateProgress(): number {\n if (this.target === window || this.target instanceof Window) {\n const maxScroll =\n document.documentElement.scrollHeight - window.innerHeight;\n if (maxScroll <= 0) return 0;\n return Math.max(0, Math.min(1, window.scrollY / maxScroll));\n }\n\n // HTMLElement target\n const el = this.target as HTMLElement;\n const maxScroll = el.scrollHeight - el.clientHeight;\n if (maxScroll <= 0) return 0;\n return Math.max(0, Math.min(1, el.scrollTop / maxScroll));\n }\n}\n","import type {\n TimelineEventMap,\n TimelineEventType,\n TimelineEventHandler,\n} from \"./types.js\";\n\n/**\n * Minimal typed event emitter for timeline events.\n */\nexport class Emitter {\n private listeners = new Map<\n TimelineEventType,\n Set<TimelineEventHandler<any>>\n >();\n\n on<T extends TimelineEventType>(\n event: T,\n handler: TimelineEventHandler<T>,\n ): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(handler);\n\n // Return unsubscribe function\n return () => {\n this.listeners.get(event)?.delete(handler);\n };\n }\n\n emit<T extends TimelineEventType>(\n event: T,\n payload: TimelineEventMap[T],\n ): void {\n this.listeners.get(event)?.forEach((handler) => handler(payload));\n }\n\n removeAllListeners(): void {\n this.listeners.clear();\n }\n}\n","/**\n * Collects disposer functions (unsubscribes) so they can be\n * cleaned up in a single `dispose()` call.\n *\n * Inspired by GSAP's gsap.context() — moves cleanup from a\n * discipline problem to an API guarantee.\n */\nexport class Scope {\n private disposers: Array<() => void> = [];\n\n /** Track a disposer function for later cleanup. */\n add(disposer: () => void): void {\n this.disposers.push(disposer);\n }\n\n /** Call all tracked disposers and clear the list. Idempotent. */\n dispose(): void {\n // Dispose in reverse order (LIFO)\n for (let i = this.disposers.length - 1; i >= 0; i--) {\n this.disposers[i]();\n }\n this.disposers.length = 0;\n }\n\n /** Number of tracked disposers. */\n get size(): number {\n return this.disposers.length;\n }\n}\n","import type { StepEventPayload } from \"./types.js\";\n\n/**\n * Event passed to middleware functions during step transitions.\n */\nexport interface MiddlewareEvent {\n type: \"step:enter\" | \"step:exit\";\n payload: StepEventPayload;\n}\n\n/**\n * Middleware function signature.\n * Call `next()` to continue the chain; skip it to swallow the event.\n */\nexport type MiddlewareFn = (event: MiddlewareEvent, next: () => void) => void;\n\n/**\n * Manages an ordered chain of middleware functions.\n * Each middleware can inspect/modify events and decide whether to pass through.\n */\nexport class MiddlewareChain {\n private fns: MiddlewareFn[] = [];\n\n /** Add middleware. Returns a function to remove it. */\n add(fn: MiddlewareFn): () => void {\n this.fns.push(fn);\n return () => {\n const idx = this.fns.indexOf(fn);\n if (idx !== -1) this.fns.splice(idx, 1);\n };\n }\n\n /**\n * Run the middleware chain. If all middleware call `next()`,\n * `finalAction` executes (emitting the event to listeners).\n * If any middleware skips `next()`, the event is swallowed.\n */\n run(event: MiddlewareEvent, finalAction: () => void): void {\n let index = 0;\n const fns = this.fns;\n\n const next = () => {\n if (index < fns.length) {\n fns[index++](event, next);\n } else {\n finalAction();\n }\n };\n\n next();\n }\n\n /** Remove all middleware. */\n clear(): void {\n this.fns.length = 0;\n }\n}\n","/**\n * Manages named breakpoints backed by CSS media queries.\n * Listens for changes and notifies the Timeline to re-resolve steps.\n *\n * SSR-safe: no-op when `window` is not available.\n */\nexport class BreakpointManager {\n private queries: Map<string, MediaQueryList> = new Map();\n private listeners: Array<() => void> = [];\n\n constructor(breakpoints: Record<string, string>) {\n if (typeof globalThis.matchMedia !== \"function\") return;\n\n for (const [name, query] of Object.entries(breakpoints)) {\n this.queries.set(name, globalThis.matchMedia(query));\n }\n }\n\n /** Whether a named breakpoint currently matches. */\n isActive(name: string): boolean {\n return this.queries.get(name)?.matches ?? false;\n }\n\n /** All registered breakpoint names. */\n get names(): string[] {\n return [...this.queries.keys()];\n }\n\n /**\n * Subscribe to breakpoint changes. Returns unsubscribe function.\n * The callback fires whenever any breakpoint's match state changes.\n */\n onChange(callback: () => void): () => void {\n const handler = () => callback();\n this.listeners.push(handler);\n\n for (const mql of this.queries.values()) {\n mql.addEventListener(\"change\", handler);\n }\n\n return () => {\n for (const mql of this.queries.values()) {\n mql.removeEventListener(\"change\", handler);\n }\n const idx = this.listeners.indexOf(handler);\n if (idx !== -1) this.listeners.splice(idx, 1);\n };\n }\n\n /** Remove all change listeners. */\n destroy(): void {\n for (const handler of this.listeners) {\n for (const mql of this.queries.values()) {\n mql.removeEventListener(\"change\", handler);\n }\n }\n this.listeners.length = 0;\n }\n}\n","import type {\n DevtoolsState,\n Opacities,\n Step,\n StepConfig,\n TimelineEventHandler,\n TimelineEventType,\n TimelineOptions,\n} from \"./types.js\";\nimport { resolveSteps, getTotalSteps } from \"./resolve-steps.js\";\nimport {\n calculateAllOpacities,\n getStepRange,\n getCurrentSteps,\n} from \"./opacity.js\";\nimport { ScrollDriver } from \"./scroll-driver.js\";\nimport { Emitter } from \"./emitter.js\";\nimport { Scope } from \"./scope.js\";\nimport { MiddlewareChain, type MiddlewareFn } from \"./middleware.js\";\nimport { BreakpointManager } from \"./breakpoints.js\";\nimport { validateBreakpointRefs } from \"./warnings.js\";\n\n/**\n * Timeline: the main facade that ties everything together.\n *\n * Usage:\n * ```ts\n * const timeline = new Timeline({\n * config: [\n * { name: \"intro\", duration: 3, track: \"main\", easing: \"linear\" },\n * { name: \"content\", duration: 5, track: \"main\" },\n * { name: \"caption\", duration: 2, track: \"text\" },\n * ],\n * });\n *\n * timeline.start(); // begins listening to scroll\n * timeline.on(\"scroll\", ({ scrollPercentage }) => {\n * const opacities = timeline.getOpacities(scrollPercentage);\n * });\n * ```\n */\nexport class Timeline {\n private _steps: Step[];\n private _totalSteps: number;\n private config: StepConfig[];\n\n private scrollDriver: ScrollDriver;\n private emitter = new Emitter();\n private activeSteps = new Set<string>();\n private devtoolsEnabled: boolean;\n private scrollUnsubscribe: (() => void) | null = null;\n private _activeScope: Scope | null = null;\n private middleware = new MiddlewareChain();\n private breakpointManager: BreakpointManager | null = null;\n private breakpointUnsub: (() => void) | null = null;\n\n constructor(options: TimelineOptions) {\n this.config = options.config;\n this.devtoolsEnabled = options.devtools ?? false;\n\n // Set up breakpoint manager if breakpoints are provided\n if (options.breakpoints && Object.keys(options.breakpoints).length > 0) {\n this.breakpointManager = new BreakpointManager(options.breakpoints);\n validateBreakpointRefs(options.config, this.breakpointManager.names);\n }\n\n // Initial resolution (filtered by current breakpoint state)\n this._steps = this.resolveWithBreakpoints();\n this._totalSteps = getTotalSteps(this._steps);\n this.scrollDriver = new ScrollDriver();\n }\n\n /** Resolved steps (re-computed on breakpoint changes). */\n get steps(): Step[] {\n return this._steps;\n }\n\n /** Total steps across all tracks (re-computed on breakpoint changes). */\n get totalSteps(): number {\n return this._totalSteps;\n }\n\n /** Start listening to scroll events. */\n start(): void {\n // Clean up any prior subscription (safe for repeated start/destroy cycles)\n this.scrollUnsubscribe?.();\n\n // Subscribe to breakpoint changes for reactive re-resolution\n if (this.breakpointManager) {\n this.breakpointUnsub?.();\n this.breakpointUnsub = this.breakpointManager.onChange(() => {\n this.reconfigure();\n });\n }\n\n // Wire scroll driver to event emission\n this.scrollUnsubscribe = this.scrollDriver.onScroll((scrollPercentage) => {\n const currentStep = scrollPercentage * this._totalSteps;\n\n // Emit scroll event\n this.emitter.emit(\"scroll\", { scrollPercentage, currentStep });\n\n // Track step enter/exit events\n const nowActive = new Set<string>();\n for (const step of this._steps) {\n if (currentStep >= step.start && currentStep < step.end) {\n nowActive.add(step.name);\n\n if (!this.activeSteps.has(step.name)) {\n const payload = {\n name: step.name,\n track: step.track,\n start: step.start,\n end: step.end,\n };\n this.middleware.run(\n { type: \"step:enter\", payload },\n () => this.emitter.emit(\"step:enter\", payload),\n );\n }\n }\n }\n\n // Emit exit for steps that were active but no longer are\n for (const name of this.activeSteps) {\n if (!nowActive.has(name)) {\n const step = this._steps.find((s) => s.name === name);\n if (step) {\n const payload = {\n name: step.name,\n track: step.track,\n start: step.start,\n end: step.end,\n };\n this.middleware.run(\n { type: \"step:exit\", payload },\n () => this.emitter.emit(\"step:exit\", payload),\n );\n }\n }\n }\n\n this.activeSteps = nowActive;\n\n // Update devtools state\n if (this.devtoolsEnabled && typeof window !== \"undefined\") {\n this.updateDevtools(scrollPercentage, currentStep);\n }\n });\n\n // Set initial devtools state before any scroll\n if (this.devtoolsEnabled && typeof window !== \"undefined\") {\n this.updateDevtools(0, 0);\n }\n this.scrollDriver.start();\n }\n\n /** Stop listening and clean up all resources. */\n destroy(): void {\n this.scrollUnsubscribe?.();\n this.scrollUnsubscribe = null;\n this.breakpointUnsub?.();\n this.breakpointUnsub = null;\n this.breakpointManager?.destroy();\n this.scrollDriver.destroy();\n this.emitter.removeAllListeners();\n this.middleware.clear();\n this.activeSteps.clear();\n if (\n this.devtoolsEnabled &&\n typeof window !== \"undefined\" &&\n (window as any).__MULTITRACK_DEVTOOLS__\n ) {\n delete (window as any).__MULTITRACK_DEVTOOLS__;\n }\n }\n\n /**\n * Register middleware that intercepts step:enter/step:exit events.\n * Call `next()` to pass through, or skip it to swallow the event.\n *\n * ```ts\n * timeline.use((event, next) => {\n * analytics.track(event.type, event.payload.name);\n * next();\n * });\n * ```\n */\n use(fn: MiddlewareFn): () => void {\n const unsub = this.middleware.add(fn);\n this._activeScope?.add(unsub);\n return unsub;\n }\n\n /** Subscribe to timeline events. Returns an unsubscribe function. */\n on<T extends TimelineEventType>(\n event: T,\n handler: TimelineEventHandler<T>,\n ): () => void {\n const unsub = this.emitter.on(event, handler);\n this._activeScope?.add(unsub);\n return unsub;\n }\n\n /**\n * Collect all subscriptions created inside `fn` into a Scope.\n * Call `scope.dispose()` to clean them all up at once.\n *\n * ```ts\n * const ctx = timeline.scope(() => {\n * timeline.on('step:enter', handleEnter);\n * timeline.on('scroll', handleScroll);\n * });\n * // later: ctx.dispose() cleans up both listeners\n * ```\n */\n scope(fn: () => void): Scope {\n const scope = new Scope();\n const previousScope = this._activeScope;\n this._activeScope = scope;\n try {\n fn();\n } finally {\n this._activeScope = previousScope;\n }\n return scope;\n }\n\n /** Current scroll progress (0 to 1). */\n get scrollPercentage(): number {\n return this.scrollDriver.scrollPercentage;\n }\n\n /** Current step position (0 to totalSteps). */\n get currentStep(): number {\n return this.scrollDriver.scrollPercentage * this._totalSteps;\n }\n\n /** Calculate opacities for all steps at a given scroll position. */\n getOpacities<T extends string = string>(\n scrollPercentage?: number,\n ): Opacities<T> {\n return calculateAllOpacities<T>(\n scrollPercentage ?? this.scrollDriver.scrollPercentage,\n this._steps,\n );\n }\n\n /** Get the start/end range for a named step. */\n getStepRange(name: string): { start: number; end: number } {\n return getStepRange(name, this._steps);\n }\n\n /** Get all steps that are active at a given position. */\n getCurrentSteps(position?: number) {\n return getCurrentSteps(position ?? this.currentStep, this._steps);\n }\n\n /** Enable devtools integration (exposes state on window.__MULTITRACK_DEVTOOLS__). */\n enableDevtools(): void {\n this.devtoolsEnabled = true;\n }\n\n /**\n * Filter config by active breakpoints and resolve steps.\n * Steps without `when` are always included.\n * Steps with `when` are included only if that breakpoint currently matches.\n */\n private resolveWithBreakpoints(): Step[] {\n const filtered = this.breakpointManager\n ? this.config.filter(\n (step) => !step.when || this.breakpointManager!.isActive(step.when),\n )\n : this.config;\n\n return resolveSteps(filtered);\n }\n\n /**\n * Re-resolve steps after a breakpoint change.\n * Clears active steps and emits timeline:reconfigure.\n */\n private reconfigure(): void {\n this._steps = this.resolveWithBreakpoints();\n this._totalSteps = getTotalSteps(this._steps);\n this.activeSteps.clear();\n\n this.emitter.emit(\"timeline:reconfigure\", {\n steps: this._steps,\n totalSteps: this._totalSteps,\n });\n\n // Update devtools with new step layout\n if (this.devtoolsEnabled && typeof window !== \"undefined\") {\n this.updateDevtools(this.scrollPercentage, this.currentStep);\n }\n }\n\n private updateDevtools(\n scrollPercentage: number,\n currentStep: number,\n ): void {\n // Serialize steps without function references so JSON.stringify works in devtools eval\n const serializableSteps: Step[] = this._steps.map((s) => ({\n name: s.name,\n start: s.start,\n end: s.end,\n track: s.track,\n easing: (typeof s.easing === \"function\" ? \"custom\" : s.easing) as Step[\"easing\"],\n }));\n const state: DevtoolsState = {\n steps: serializableSteps,\n currentStep,\n totalSteps: this._totalSteps,\n opacities: this.getOpacities(scrollPercentage),\n scrollPercentage,\n };\n (window as any).__MULTITRACK_DEVTOOLS__ = state;\n }\n}\n"]}