use-scroll-animate 9.4.0 → 9.6.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/CHANGELOG.md CHANGED
@@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [9.6.0] - 2026-10-09
11
+
12
+ ### Added
13
+ - **Performance 3.0 — `motionary/fx/perf`** (= `motionary/components/fx-perf`, `registerPerf3Pack()`, also in `registerEffectPacks()` and the marketplace): `offscreenRender(canvas, program, { worker, paused })` (canvas animation in a Web Worker via OffscreenCanvas, main-thread fallback on the shared frame loop; `{ backend, stop, resize }`), `runInWorker(fn, ...args)` (one-off pure computation in a worker, inline fallback), `fpsMeter()`. Effects `idle-reveal` (enter — waits for `requestIdleCallback`) and `gpu-lift` (hover — compositor-only).
14
+ - **2 new components (9.6)** in `motionary/components/widgets`:
15
+ - `<usa-perf-monitor>` — live overlay: FPS + sparkline, active animations, frame-loop callbacks, long tasks, clock state; `corner` (or `inline`), `collapsed`, `warn`; `stats`; `usa:jank` { fps }.
16
+ - `<usa-worker-canvas>` — canvas animation rendered off the main thread (`scene` particles · orbits · starfield, or your `program`); `data-usa-backend` worker / main; `backend`; `usa:backend`; `WORKER_SCENES`.
17
+ - Showcase: 3 new gallery cards with copyable code, live demos and live Store thumbnails.
18
+
19
+ ### Accessibility
20
+ - The perf monitor is a labelled `status` region with a real expand / collapse button (`aria-expanded`). The worker canvas is an `img` with `label`. Reduced motion: the worker canvas paints one still frame, `idle-reveal` shows at once, `gpu-lift` does nothing.
21
+
22
+ ## [9.5.0] - 2026-10-09
23
+
24
+ ### Added
25
+ - **Accessible motion 2.0 — `motionary/fx/safe`** (= `motionary/components/fx-safe`, `registerSafePack()`, also in `registerEffectPacks()` and the marketplace): `vestibularSafe(keyframes)` (strip movement, keep opacity / colour), `flashCount()` / `isFlashSafe(keyframes, duration, iterations)` (WCAG 2.3.1 three-flashes check), `applyMotionPreferences(prefs)` / `loadMotionPreferences()` (sensitivity level, shared clock speed, `data-usa-pause-autoplay` / `data-usa-no-parallax` on `<html>`, persisted). Effects that never move: `safe-fade` (enter), `focus-glow` (attention), `color-pulse` (attention), `underline-sweep` (hover) — they run under reduced motion too.
26
+ - **2 new components (9.5)** in `motionary/components/widgets`:
27
+ - `<usa-motion-prefs>` — motion preference panel: level (Full · Gentle · Minimal · None), speed 0.25–2×, pause autoplaying video, no parallax, live sample, Reset; applies instantly and persists; `prefs`, `reset()`; `usa:change` { prefs }.
28
+ - `<usa-pause-all>` — one button that pauses / resumes all motion (shared clock, CSS / Web animations, autoplaying media; WCAG 2.2.2), `aria-pressed`, synced with `motionClock`; `scope` limits it to a subtree; `paused`, `toggle()`; `usa:pause-all` { paused }.
29
+ - Showcase: 3 new gallery cards with copyable code, live demos and live Store thumbnails.
30
+
31
+ ### Accessibility
32
+ - The preference panel is a labelled `form` with a `fieldset` / `legend` of radio buttons, a labelled range with an `<output>`, and checkboxes; its sample is `aria-hidden`. The pause button is a real `button` with `aria-pressed` and a text label.
33
+
34
+ ### Fixed
35
+
36
+ - Build: Rollup ran out of heap at the 4 GB cap (`npm run build` aborted with "JavaScript heap out of memory") once the 9.5 entries were added; the build script's `--max-old-space-size` is raised to 6144.
37
+
10
38
  ## [9.4.0] - 2026-10-09
11
39
 
12
40
  ### Added
@@ -0,0 +1,161 @@
1
+ 'use strict';
2
+
3
+ var registry = require('../chunks/registry-BVklOepd.cjs');
4
+ var base = require('../chunks/base-DoRUZBy-.cjs');
5
+
6
+ /** Run a pure function in a Web Worker; resolves with its (structured-cloneable) result (9.6). */
7
+ function runInWorker(fn, ...args) {
8
+ if (typeof Worker === 'undefined' || typeof Blob === 'undefined' || typeof URL === 'undefined' || !URL.createObjectURL)
9
+ return Promise.resolve().then(() => fn(...args));
10
+ const src = `self.onmessage=async(e)=>{try{const r=await (${fn.toString()})(...e.data);self.postMessage({ok:true,r})}catch(err){self.postMessage({ok:false,e:String(err&&err.message||err)})}}`;
11
+ const url = URL.createObjectURL(new Blob([src], { type: 'text/javascript' }));
12
+ return new Promise((resolve, reject) => {
13
+ let w;
14
+ try {
15
+ w = new Worker(url);
16
+ }
17
+ catch (e) {
18
+ URL.revokeObjectURL(url);
19
+ return resolve(Promise.resolve().then(() => fn(...args)));
20
+ }
21
+ const done = () => {
22
+ w.terminate();
23
+ URL.revokeObjectURL(url);
24
+ };
25
+ w.onmessage = (e) => {
26
+ done();
27
+ if (e.data?.ok)
28
+ resolve(e.data.r);
29
+ else
30
+ reject(new Error(e.data?.e || 'worker failed'));
31
+ };
32
+ w.onerror = (e) => {
33
+ done();
34
+ reject(new Error(e.message || 'worker error'));
35
+ };
36
+ w.postMessage(args);
37
+ });
38
+ }
39
+ /** Animate `canvas` with `program` in a worker (OffscreenCanvas) when possible, else on the main thread (9.6). */
40
+ function offscreenRender(canvas, program, opts = {}) {
41
+ const code = typeof program === 'string' ? program : program.toString();
42
+ const canWorker = opts.worker !== false && typeof Worker !== 'undefined' && typeof canvas.transferControlToOffscreen === 'function' && typeof Blob !== 'undefined' && !!URL.createObjectURL;
43
+ if (canWorker) {
44
+ try {
45
+ const off = canvas.transferControlToOffscreen();
46
+ const src = `let c,ctx,w,h,st={},run=true,t0=0;const draw=(${code});self.onmessage=(e)=>{const d=e.data;if(d.canvas){c=d.canvas;ctx=c.getContext('2d');w=c.width;h=c.height;const loop=(t)=>{if(run){if(!t0)t0=t;draw(ctx,t-t0,w,h,st)}requestAnimationFrame(loop)};requestAnimationFrame(loop)}if(d.size){w=c.width=d.size[0];h=c.height=d.size[1]}if('run' in d)run=d.run;if(d.stop)close()}`;
47
+ const url = URL.createObjectURL(new Blob([src], { type: 'text/javascript' }));
48
+ const w = new Worker(url);
49
+ w.postMessage({ canvas: off }, [off]);
50
+ if (opts.paused)
51
+ w.postMessage({ run: false });
52
+ return {
53
+ backend: 'worker',
54
+ stop: () => {
55
+ w.postMessage({ stop: true });
56
+ w.terminate();
57
+ URL.revokeObjectURL(url);
58
+ },
59
+ resize: (a, b) => w.postMessage({ size: [a, b] }),
60
+ };
61
+ }
62
+ catch {
63
+ /* fall back to the main thread */
64
+ }
65
+ }
66
+ const ctx = canvas.getContext?.('2d');
67
+ if (!ctx)
68
+ return { backend: 'none', stop: () => undefined, resize: () => undefined };
69
+ // eslint-disable-next-line no-new-func
70
+ const draw = typeof program === 'function' ? program : new Function(`return (${code})`)();
71
+ const st = {};
72
+ let t = 0;
73
+ const stop = opts.paused
74
+ ? () => undefined
75
+ : base.onFrame((_now, dt) => {
76
+ t += dt;
77
+ draw(ctx, t, canvas.width, canvas.height, st);
78
+ });
79
+ if (opts.paused)
80
+ draw(ctx, 0, canvas.width, canvas.height, st);
81
+ return {
82
+ backend: 'main',
83
+ stop,
84
+ resize: (a, b) => {
85
+ canvas.width = a;
86
+ canvas.height = b;
87
+ },
88
+ };
89
+ }
90
+ /** A rolling FPS meter on the shared frame loop: { fps, stop } (fps updates every frame) (9.6). */
91
+ function fpsMeter(window = 30) {
92
+ const samples = [];
93
+ let fps = 0;
94
+ const stop = base.onFrame((_t, dt) => {
95
+ if (dt <= 0)
96
+ return;
97
+ samples.push(1000 / dt);
98
+ if (samples.length > window)
99
+ samples.shift();
100
+ fps = Math.round(samples.reduce((a, b) => a + b, 0) / samples.length);
101
+ });
102
+ return {
103
+ get fps() {
104
+ return fps;
105
+ },
106
+ samples,
107
+ stop,
108
+ };
109
+ }
110
+ const PERF3_FX = [
111
+ {
112
+ name: 'idle-reveal',
113
+ kind: 'enter',
114
+ description: 'Waits for an idle moment (requestIdleCallback, ≤ `timeout` ms) before fading in, keeping first paint and input snappy.',
115
+ defaults: { duration: 400, timeout: 600 },
116
+ run: (el, o, ctx) => {
117
+ if (ctx.reduced)
118
+ return;
119
+ const prev = el.style.opacity;
120
+ el.style.opacity = '0';
121
+ ctx.onCleanup(() => (el.style.opacity = prev));
122
+ return new Promise((resolve) => {
123
+ const go = () => {
124
+ el.style.opacity = prev;
125
+ const a = ctx.animate(el, [{ opacity: 0 }, { opacity: 1 }], { duration: o.duration, easing: 'ease-out' });
126
+ if (a)
127
+ a.finished.then(() => resolve(), () => resolve());
128
+ else
129
+ resolve();
130
+ };
131
+ const ric = globalThis.requestIdleCallback;
132
+ if (ric)
133
+ ric(go, { timeout: Number(o.timeout) || 600 });
134
+ else
135
+ setTimeout(go, 1);
136
+ });
137
+ },
138
+ },
139
+ {
140
+ name: 'gpu-lift',
141
+ kind: 'hover',
142
+ description: 'A compositor-only hover lift — only transform and opacity change, so it never triggers layout or paint.',
143
+ defaults: { duration: 250, lift: 6 },
144
+ run: (el, o, ctx) => {
145
+ if (ctx.reduced)
146
+ return;
147
+ return ctx.animate(el, [{ transform: 'translateY(0) scale(1)' }, { transform: `translateY(${-Math.abs(Number(o.lift) || 6)}px) scale(1.02)` }], { duration: o.duration, easing: 'ease-out', fill: 'forwards' })?.finished.catch(() => undefined);
148
+ },
149
+ },
150
+ ];
151
+ /** Register idle-reveal and gpu-lift (9.6). */
152
+ function registerPerf3Pack() {
153
+ registry.registerEffects(PERF3_FX);
154
+ }
155
+
156
+ exports.PERF3_FX = PERF3_FX;
157
+ exports.fpsMeter = fpsMeter;
158
+ exports.offscreenRender = offscreenRender;
159
+ exports.registerPerf3Pack = registerPerf3Pack;
160
+ exports.runInWorker = runInWorker;
161
+ //# sourceMappingURL=fx-perf.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fx-perf.cjs","sources":["../../src/components/fx2/perf3.ts"],"sourcesContent":["/**\n * 9.6 — Performance 3.0 (`motionary/fx/perf`, also `motionary/components/fx-perf`):\n *\n * - `runInWorker(fn, ...args)` — run a pure function in a throw-away Web\n * Worker (built from its source) and get a promise of its result; runs\n * inline when Workers are unavailable.\n * - `offscreenRender(canvas, program, options)` — move a canvas animation\n * off the main thread: `program` is the source of\n * `function (ctx, t, w, h, state) {…}`; with OffscreenCanvas + Worker it\n * runs in a worker (`backend: 'worker'`), otherwise on the main thread via\n * the shared frame loop (`'main'`). Returns { backend, stop, resize }.\n * - `fpsMeter()` — a rolling frames-per-second meter on the shared loop.\n *\n * Effects: `idle-reveal` (enter) waits for an idle moment before fading in\n * (keeps first paint / input snappy); `gpu-lift` (hover) a compositor-only\n * lift (transform + opacity only). Reduced motion: idle-reveal just shows,\n * gpu-lift does nothing.\n */\nimport type { EffectContext, EffectDefinition } from '../fx/registry';\nimport { registerEffects } from '../fx/registry';\nimport { onFrame } from '../base';\n\n/** Run a pure function in a Web Worker; resolves with its (structured-cloneable) result (9.6). */\nexport function runInWorker<A extends unknown[], R>(fn: (...args: A) => R | Promise<R>, ...args: A): Promise<R> {\n if (typeof Worker === 'undefined' || typeof Blob === 'undefined' || typeof URL === 'undefined' || !URL.createObjectURL) return Promise.resolve().then(() => fn(...args));\n const src = `self.onmessage=async(e)=>{try{const r=await (${fn.toString()})(...e.data);self.postMessage({ok:true,r})}catch(err){self.postMessage({ok:false,e:String(err&&err.message||err)})}}`;\n const url = URL.createObjectURL(new Blob([src], { type: 'text/javascript' }));\n return new Promise<R>((resolve, reject) => {\n let w: Worker;\n try {\n w = new Worker(url);\n } catch (e) {\n URL.revokeObjectURL(url);\n return resolve(Promise.resolve().then(() => fn(...args)));\n }\n const done = () => {\n w.terminate();\n URL.revokeObjectURL(url);\n };\n w.onmessage = (e) => {\n done();\n if (e.data?.ok) resolve(e.data.r as R);\n else reject(new Error(e.data?.e || 'worker failed'));\n };\n w.onerror = (e) => {\n done();\n reject(new Error(e.message || 'worker error'));\n };\n w.postMessage(args);\n });\n}\n\nexport type DrawProgram = string | ((ctx: CanvasRenderingContext2D, t: number, w: number, h: number, state: Record<string, unknown>) => void);\n/** Animate `canvas` with `program` in a worker (OffscreenCanvas) when possible, else on the main thread (9.6). */\nexport function offscreenRender(canvas: HTMLCanvasElement, program: DrawProgram, opts: { worker?: boolean; paused?: boolean } = {}): { backend: 'worker' | 'main' | 'none'; stop(): void; resize(w: number, h: number): void } {\n const code = typeof program === 'string' ? program : program.toString();\n const canWorker = opts.worker !== false && typeof Worker !== 'undefined' && typeof (canvas as any).transferControlToOffscreen === 'function' && typeof Blob !== 'undefined' && !!URL.createObjectURL;\n if (canWorker) {\n try {\n const off = (canvas as any).transferControlToOffscreen();\n const src = `let c,ctx,w,h,st={},run=true,t0=0;const draw=(${code});self.onmessage=(e)=>{const d=e.data;if(d.canvas){c=d.canvas;ctx=c.getContext('2d');w=c.width;h=c.height;const loop=(t)=>{if(run){if(!t0)t0=t;draw(ctx,t-t0,w,h,st)}requestAnimationFrame(loop)};requestAnimationFrame(loop)}if(d.size){w=c.width=d.size[0];h=c.height=d.size[1]}if('run' in d)run=d.run;if(d.stop)close()}`;\n const url = URL.createObjectURL(new Blob([src], { type: 'text/javascript' }));\n const w = new Worker(url);\n w.postMessage({ canvas: off }, [off]);\n if (opts.paused) w.postMessage({ run: false });\n return {\n backend: 'worker',\n stop: () => {\n w.postMessage({ stop: true });\n w.terminate();\n URL.revokeObjectURL(url);\n },\n resize: (a, b) => w.postMessage({ size: [a, b] }),\n };\n } catch {\n /* fall back to the main thread */\n }\n }\n const ctx = canvas.getContext?.('2d');\n if (!ctx) return { backend: 'none', stop: () => undefined, resize: () => undefined };\n // eslint-disable-next-line no-new-func\n const draw = typeof program === 'function' ? program : (new Function(`return (${code})`)() as Exclude<DrawProgram, string>);\n const st: Record<string, unknown> = {};\n let t = 0;\n const stop = opts.paused\n ? () => undefined\n : onFrame((_now, dt) => {\n t += dt;\n draw(ctx, t, canvas.width, canvas.height, st);\n });\n if (opts.paused) draw(ctx, 0, canvas.width, canvas.height, st);\n return {\n backend: 'main',\n stop,\n resize: (a, b) => {\n canvas.width = a;\n canvas.height = b;\n },\n };\n}\n\n/** A rolling FPS meter on the shared frame loop: { fps, stop } (fps updates every frame) (9.6). */\nexport function fpsMeter(window = 30): { readonly fps: number; readonly samples: number[]; stop(): void } {\n const samples: number[] = [];\n let fps = 0;\n const stop = onFrame((_t, dt) => {\n if (dt <= 0) return;\n samples.push(1000 / dt);\n if (samples.length > window) samples.shift();\n fps = Math.round(samples.reduce((a, b) => a + b, 0) / samples.length);\n });\n return {\n get fps() {\n return fps;\n },\n samples,\n stop,\n };\n}\n\nexport const PERF3_FX: EffectDefinition[] = [\n {\n name: 'idle-reveal',\n kind: 'enter',\n description: 'Waits for an idle moment (requestIdleCallback, ≤ `timeout` ms) before fading in, keeping first paint and input snappy.',\n defaults: { duration: 400, timeout: 600 },\n run: (el: HTMLElement, o: any, ctx: EffectContext) => {\n if (ctx.reduced) return;\n const prev = el.style.opacity;\n el.style.opacity = '0';\n ctx.onCleanup(() => (el.style.opacity = prev));\n return new Promise<void>((resolve) => {\n const go = () => {\n el.style.opacity = prev;\n const a = ctx.animate(el, [{ opacity: 0 }, { opacity: 1 }], { duration: o.duration, easing: 'ease-out' });\n if (a) a.finished.then(() => resolve(), () => resolve());\n else resolve();\n };\n const ric = (globalThis as any).requestIdleCallback as ((cb: () => void, o?: { timeout: number }) => number) | undefined;\n if (ric) ric(go, { timeout: Number(o.timeout) || 600 });\n else setTimeout(go, 1);\n });\n },\n },\n {\n name: 'gpu-lift',\n kind: 'hover',\n description: 'A compositor-only hover lift — only transform and opacity change, so it never triggers layout or paint.',\n defaults: { duration: 250, lift: 6 },\n run: (el: HTMLElement, o: any, ctx: EffectContext) => {\n if (ctx.reduced) return;\n return ctx.animate(el, [{ transform: 'translateY(0) scale(1)' }, { transform: `translateY(${-Math.abs(Number(o.lift) || 6)}px) scale(1.02)` }], { duration: o.duration, easing: 'ease-out', fill: 'forwards' })?.finished.catch(() => undefined);\n },\n },\n];\n\n/** Register idle-reveal and gpu-lift (9.6). */\nexport function registerPerf3Pack(): void {\n registerEffects(PERF3_FX);\n}\n"],"names":["onFrame","registerEffects"],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ type MotionSensitivity = 'full' | 'gentle' | 'minimal' | 'static';
2
+ type Cleanup = () => void;
3
+
4
+ /**
5
+ * 5.0 — unified plugin-style effect registration. Every effect (built-in or
6
+ * yours) is a plain object registered once and played the same way:
7
+ * `playEffect(el, name)`, `bindEffect(el, name, { trigger })` or
8
+ * `<usa-fx effect="name" trigger="click">`. Effects get a context that
9
+ * already applies reduced motion, motion sensitivity, intensity and the
10
+ * animation budget.
11
+ */
12
+
13
+ declare const EFFECT_KINDS: readonly ["enter", "exit", "attention", "click", "hover", "card", "loop", "page", "background", "text", "cursor", "scroll"];
14
+ type EffectKind = (typeof EFFECT_KINDS)[number];
15
+ interface EffectContext {
16
+ /** Reduced motion applies (OS setting, `minimal` / `static` sensitivity). */
17
+ readonly reduced: boolean;
18
+ readonly sensitivity: MotionSensitivity;
19
+ /** The triggering event (pointer position for click effects), if any. */
20
+ readonly event?: Event;
21
+ /** `el.animate()` with the library's motion rules (may return `null`). */
22
+ animate(el: Element, keyframes: Keyframe[], options: KeyframeAnimationOptions): Animation | null;
23
+ /** Register teardown for long-running effects (loops, listeners). */
24
+ onCleanup(fn: Cleanup): void;
25
+ }
26
+ interface EffectDefinition<O extends Record<string, unknown> = Record<string, any>> {
27
+ /** Unique, kebab-case. */
28
+ name: string;
29
+ kind: EffectKind;
30
+ /** One line for docs and the gallery. */
31
+ description?: string;
32
+ /** Option defaults (merged under the caller's options). */
33
+ defaults?: Partial<O>;
34
+ /**
35
+ * Under reduced motion: `'skip'` (do nothing — default for loop, background
36
+ * and cursor effects) or `'run'` (run with `ctx.reduced === true`, the
37
+ * effect degrades itself — default for everything else).
38
+ */
39
+ reduced?: 'skip' | 'run';
40
+ /** Play the effect. Return an Animation / Promise to be awaited, or a cleanup. */
41
+ run(el: HTMLElement, options: O, ctx: EffectContext): void | Cleanup | Animation | null | Promise<unknown>;
42
+ }
43
+
44
+ /**
45
+ * 9.6 — Performance 3.0 (`motionary/fx/perf`, also `motionary/components/fx-perf`):
46
+ *
47
+ * - `runInWorker(fn, ...args)` — run a pure function in a throw-away Web
48
+ * Worker (built from its source) and get a promise of its result; runs
49
+ * inline when Workers are unavailable.
50
+ * - `offscreenRender(canvas, program, options)` — move a canvas animation
51
+ * off the main thread: `program` is the source of
52
+ * `function (ctx, t, w, h, state) {…}`; with OffscreenCanvas + Worker it
53
+ * runs in a worker (`backend: 'worker'`), otherwise on the main thread via
54
+ * the shared frame loop (`'main'`). Returns { backend, stop, resize }.
55
+ * - `fpsMeter()` — a rolling frames-per-second meter on the shared loop.
56
+ *
57
+ * Effects: `idle-reveal` (enter) waits for an idle moment before fading in
58
+ * (keeps first paint / input snappy); `gpu-lift` (hover) a compositor-only
59
+ * lift (transform + opacity only). Reduced motion: idle-reveal just shows,
60
+ * gpu-lift does nothing.
61
+ */
62
+
63
+ /** Run a pure function in a Web Worker; resolves with its (structured-cloneable) result (9.6). */
64
+ declare function runInWorker<A extends unknown[], R>(fn: (...args: A) => R | Promise<R>, ...args: A): Promise<R>;
65
+ type DrawProgram = string | ((ctx: CanvasRenderingContext2D, t: number, w: number, h: number, state: Record<string, unknown>) => void);
66
+ /** Animate `canvas` with `program` in a worker (OffscreenCanvas) when possible, else on the main thread (9.6). */
67
+ declare function offscreenRender(canvas: HTMLCanvasElement, program: DrawProgram, opts?: {
68
+ worker?: boolean;
69
+ paused?: boolean;
70
+ }): {
71
+ backend: 'worker' | 'main' | 'none';
72
+ stop(): void;
73
+ resize(w: number, h: number): void;
74
+ };
75
+ /** A rolling FPS meter on the shared frame loop: { fps, stop } (fps updates every frame) (9.6). */
76
+ declare function fpsMeter(window?: number): {
77
+ readonly fps: number;
78
+ readonly samples: number[];
79
+ stop(): void;
80
+ };
81
+ declare const PERF3_FX: EffectDefinition[];
82
+ /** Register idle-reveal and gpu-lift (9.6). */
83
+ declare function registerPerf3Pack(): void;
84
+
85
+ export { PERF3_FX, fpsMeter, offscreenRender, registerPerf3Pack, runInWorker };
86
+ export type { DrawProgram };
@@ -0,0 +1,86 @@
1
+ type MotionSensitivity = 'full' | 'gentle' | 'minimal' | 'static';
2
+ type Cleanup = () => void;
3
+
4
+ /**
5
+ * 5.0 — unified plugin-style effect registration. Every effect (built-in or
6
+ * yours) is a plain object registered once and played the same way:
7
+ * `playEffect(el, name)`, `bindEffect(el, name, { trigger })` or
8
+ * `<usa-fx effect="name" trigger="click">`. Effects get a context that
9
+ * already applies reduced motion, motion sensitivity, intensity and the
10
+ * animation budget.
11
+ */
12
+
13
+ declare const EFFECT_KINDS: readonly ["enter", "exit", "attention", "click", "hover", "card", "loop", "page", "background", "text", "cursor", "scroll"];
14
+ type EffectKind = (typeof EFFECT_KINDS)[number];
15
+ interface EffectContext {
16
+ /** Reduced motion applies (OS setting, `minimal` / `static` sensitivity). */
17
+ readonly reduced: boolean;
18
+ readonly sensitivity: MotionSensitivity;
19
+ /** The triggering event (pointer position for click effects), if any. */
20
+ readonly event?: Event;
21
+ /** `el.animate()` with the library's motion rules (may return `null`). */
22
+ animate(el: Element, keyframes: Keyframe[], options: KeyframeAnimationOptions): Animation | null;
23
+ /** Register teardown for long-running effects (loops, listeners). */
24
+ onCleanup(fn: Cleanup): void;
25
+ }
26
+ interface EffectDefinition<O extends Record<string, unknown> = Record<string, any>> {
27
+ /** Unique, kebab-case. */
28
+ name: string;
29
+ kind: EffectKind;
30
+ /** One line for docs and the gallery. */
31
+ description?: string;
32
+ /** Option defaults (merged under the caller's options). */
33
+ defaults?: Partial<O>;
34
+ /**
35
+ * Under reduced motion: `'skip'` (do nothing — default for loop, background
36
+ * and cursor effects) or `'run'` (run with `ctx.reduced === true`, the
37
+ * effect degrades itself — default for everything else).
38
+ */
39
+ reduced?: 'skip' | 'run';
40
+ /** Play the effect. Return an Animation / Promise to be awaited, or a cleanup. */
41
+ run(el: HTMLElement, options: O, ctx: EffectContext): void | Cleanup | Animation | null | Promise<unknown>;
42
+ }
43
+
44
+ /**
45
+ * 9.6 — Performance 3.0 (`motionary/fx/perf`, also `motionary/components/fx-perf`):
46
+ *
47
+ * - `runInWorker(fn, ...args)` — run a pure function in a throw-away Web
48
+ * Worker (built from its source) and get a promise of its result; runs
49
+ * inline when Workers are unavailable.
50
+ * - `offscreenRender(canvas, program, options)` — move a canvas animation
51
+ * off the main thread: `program` is the source of
52
+ * `function (ctx, t, w, h, state) {…}`; with OffscreenCanvas + Worker it
53
+ * runs in a worker (`backend: 'worker'`), otherwise on the main thread via
54
+ * the shared frame loop (`'main'`). Returns { backend, stop, resize }.
55
+ * - `fpsMeter()` — a rolling frames-per-second meter on the shared loop.
56
+ *
57
+ * Effects: `idle-reveal` (enter) waits for an idle moment before fading in
58
+ * (keeps first paint / input snappy); `gpu-lift` (hover) a compositor-only
59
+ * lift (transform + opacity only). Reduced motion: idle-reveal just shows,
60
+ * gpu-lift does nothing.
61
+ */
62
+
63
+ /** Run a pure function in a Web Worker; resolves with its (structured-cloneable) result (9.6). */
64
+ declare function runInWorker<A extends unknown[], R>(fn: (...args: A) => R | Promise<R>, ...args: A): Promise<R>;
65
+ type DrawProgram = string | ((ctx: CanvasRenderingContext2D, t: number, w: number, h: number, state: Record<string, unknown>) => void);
66
+ /** Animate `canvas` with `program` in a worker (OffscreenCanvas) when possible, else on the main thread (9.6). */
67
+ declare function offscreenRender(canvas: HTMLCanvasElement, program: DrawProgram, opts?: {
68
+ worker?: boolean;
69
+ paused?: boolean;
70
+ }): {
71
+ backend: 'worker' | 'main' | 'none';
72
+ stop(): void;
73
+ resize(w: number, h: number): void;
74
+ };
75
+ /** A rolling FPS meter on the shared frame loop: { fps, stop } (fps updates every frame) (9.6). */
76
+ declare function fpsMeter(window?: number): {
77
+ readonly fps: number;
78
+ readonly samples: number[];
79
+ stop(): void;
80
+ };
81
+ declare const PERF3_FX: EffectDefinition[];
82
+ /** Register idle-reveal and gpu-lift (9.6). */
83
+ declare function registerPerf3Pack(): void;
84
+
85
+ export { PERF3_FX, fpsMeter, offscreenRender, registerPerf3Pack, runInWorker };
86
+ export type { DrawProgram };
@@ -0,0 +1,155 @@
1
+ import { registerEffects } from '../chunks/registry-CnO7ZVPK.js';
2
+ import { o as onFrame } from '../chunks/base-CLuqlLfG.js';
3
+
4
+ /** Run a pure function in a Web Worker; resolves with its (structured-cloneable) result (9.6). */
5
+ function runInWorker(fn, ...args) {
6
+ if (typeof Worker === 'undefined' || typeof Blob === 'undefined' || typeof URL === 'undefined' || !URL.createObjectURL)
7
+ return Promise.resolve().then(() => fn(...args));
8
+ const src = `self.onmessage=async(e)=>{try{const r=await (${fn.toString()})(...e.data);self.postMessage({ok:true,r})}catch(err){self.postMessage({ok:false,e:String(err&&err.message||err)})}}`;
9
+ const url = URL.createObjectURL(new Blob([src], { type: 'text/javascript' }));
10
+ return new Promise((resolve, reject) => {
11
+ let w;
12
+ try {
13
+ w = new Worker(url);
14
+ }
15
+ catch (e) {
16
+ URL.revokeObjectURL(url);
17
+ return resolve(Promise.resolve().then(() => fn(...args)));
18
+ }
19
+ const done = () => {
20
+ w.terminate();
21
+ URL.revokeObjectURL(url);
22
+ };
23
+ w.onmessage = (e) => {
24
+ done();
25
+ if (e.data?.ok)
26
+ resolve(e.data.r);
27
+ else
28
+ reject(new Error(e.data?.e || 'worker failed'));
29
+ };
30
+ w.onerror = (e) => {
31
+ done();
32
+ reject(new Error(e.message || 'worker error'));
33
+ };
34
+ w.postMessage(args);
35
+ });
36
+ }
37
+ /** Animate `canvas` with `program` in a worker (OffscreenCanvas) when possible, else on the main thread (9.6). */
38
+ function offscreenRender(canvas, program, opts = {}) {
39
+ const code = typeof program === 'string' ? program : program.toString();
40
+ const canWorker = opts.worker !== false && typeof Worker !== 'undefined' && typeof canvas.transferControlToOffscreen === 'function' && typeof Blob !== 'undefined' && !!URL.createObjectURL;
41
+ if (canWorker) {
42
+ try {
43
+ const off = canvas.transferControlToOffscreen();
44
+ const src = `let c,ctx,w,h,st={},run=true,t0=0;const draw=(${code});self.onmessage=(e)=>{const d=e.data;if(d.canvas){c=d.canvas;ctx=c.getContext('2d');w=c.width;h=c.height;const loop=(t)=>{if(run){if(!t0)t0=t;draw(ctx,t-t0,w,h,st)}requestAnimationFrame(loop)};requestAnimationFrame(loop)}if(d.size){w=c.width=d.size[0];h=c.height=d.size[1]}if('run' in d)run=d.run;if(d.stop)close()}`;
45
+ const url = URL.createObjectURL(new Blob([src], { type: 'text/javascript' }));
46
+ const w = new Worker(url);
47
+ w.postMessage({ canvas: off }, [off]);
48
+ if (opts.paused)
49
+ w.postMessage({ run: false });
50
+ return {
51
+ backend: 'worker',
52
+ stop: () => {
53
+ w.postMessage({ stop: true });
54
+ w.terminate();
55
+ URL.revokeObjectURL(url);
56
+ },
57
+ resize: (a, b) => w.postMessage({ size: [a, b] }),
58
+ };
59
+ }
60
+ catch {
61
+ /* fall back to the main thread */
62
+ }
63
+ }
64
+ const ctx = canvas.getContext?.('2d');
65
+ if (!ctx)
66
+ return { backend: 'none', stop: () => undefined, resize: () => undefined };
67
+ // eslint-disable-next-line no-new-func
68
+ const draw = typeof program === 'function' ? program : new Function(`return (${code})`)();
69
+ const st = {};
70
+ let t = 0;
71
+ const stop = opts.paused
72
+ ? () => undefined
73
+ : onFrame((_now, dt) => {
74
+ t += dt;
75
+ draw(ctx, t, canvas.width, canvas.height, st);
76
+ });
77
+ if (opts.paused)
78
+ draw(ctx, 0, canvas.width, canvas.height, st);
79
+ return {
80
+ backend: 'main',
81
+ stop,
82
+ resize: (a, b) => {
83
+ canvas.width = a;
84
+ canvas.height = b;
85
+ },
86
+ };
87
+ }
88
+ /** A rolling FPS meter on the shared frame loop: { fps, stop } (fps updates every frame) (9.6). */
89
+ function fpsMeter(window = 30) {
90
+ const samples = [];
91
+ let fps = 0;
92
+ const stop = onFrame((_t, dt) => {
93
+ if (dt <= 0)
94
+ return;
95
+ samples.push(1000 / dt);
96
+ if (samples.length > window)
97
+ samples.shift();
98
+ fps = Math.round(samples.reduce((a, b) => a + b, 0) / samples.length);
99
+ });
100
+ return {
101
+ get fps() {
102
+ return fps;
103
+ },
104
+ samples,
105
+ stop,
106
+ };
107
+ }
108
+ const PERF3_FX = [
109
+ {
110
+ name: 'idle-reveal',
111
+ kind: 'enter',
112
+ description: 'Waits for an idle moment (requestIdleCallback, ≤ `timeout` ms) before fading in, keeping first paint and input snappy.',
113
+ defaults: { duration: 400, timeout: 600 },
114
+ run: (el, o, ctx) => {
115
+ if (ctx.reduced)
116
+ return;
117
+ const prev = el.style.opacity;
118
+ el.style.opacity = '0';
119
+ ctx.onCleanup(() => (el.style.opacity = prev));
120
+ return new Promise((resolve) => {
121
+ const go = () => {
122
+ el.style.opacity = prev;
123
+ const a = ctx.animate(el, [{ opacity: 0 }, { opacity: 1 }], { duration: o.duration, easing: 'ease-out' });
124
+ if (a)
125
+ a.finished.then(() => resolve(), () => resolve());
126
+ else
127
+ resolve();
128
+ };
129
+ const ric = globalThis.requestIdleCallback;
130
+ if (ric)
131
+ ric(go, { timeout: Number(o.timeout) || 600 });
132
+ else
133
+ setTimeout(go, 1);
134
+ });
135
+ },
136
+ },
137
+ {
138
+ name: 'gpu-lift',
139
+ kind: 'hover',
140
+ description: 'A compositor-only hover lift — only transform and opacity change, so it never triggers layout or paint.',
141
+ defaults: { duration: 250, lift: 6 },
142
+ run: (el, o, ctx) => {
143
+ if (ctx.reduced)
144
+ return;
145
+ return ctx.animate(el, [{ transform: 'translateY(0) scale(1)' }, { transform: `translateY(${-Math.abs(Number(o.lift) || 6)}px) scale(1.02)` }], { duration: o.duration, easing: 'ease-out', fill: 'forwards' })?.finished.catch(() => undefined);
146
+ },
147
+ },
148
+ ];
149
+ /** Register idle-reveal and gpu-lift (9.6). */
150
+ function registerPerf3Pack() {
151
+ registerEffects(PERF3_FX);
152
+ }
153
+
154
+ export { PERF3_FX, fpsMeter, offscreenRender, registerPerf3Pack, runInWorker };
155
+ //# sourceMappingURL=fx-perf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fx-perf.js","sources":["../../src/components/fx2/perf3.ts"],"sourcesContent":["/**\n * 9.6 — Performance 3.0 (`motionary/fx/perf`, also `motionary/components/fx-perf`):\n *\n * - `runInWorker(fn, ...args)` — run a pure function in a throw-away Web\n * Worker (built from its source) and get a promise of its result; runs\n * inline when Workers are unavailable.\n * - `offscreenRender(canvas, program, options)` — move a canvas animation\n * off the main thread: `program` is the source of\n * `function (ctx, t, w, h, state) {…}`; with OffscreenCanvas + Worker it\n * runs in a worker (`backend: 'worker'`), otherwise on the main thread via\n * the shared frame loop (`'main'`). Returns { backend, stop, resize }.\n * - `fpsMeter()` — a rolling frames-per-second meter on the shared loop.\n *\n * Effects: `idle-reveal` (enter) waits for an idle moment before fading in\n * (keeps first paint / input snappy); `gpu-lift` (hover) a compositor-only\n * lift (transform + opacity only). Reduced motion: idle-reveal just shows,\n * gpu-lift does nothing.\n */\nimport type { EffectContext, EffectDefinition } from '../fx/registry';\nimport { registerEffects } from '../fx/registry';\nimport { onFrame } from '../base';\n\n/** Run a pure function in a Web Worker; resolves with its (structured-cloneable) result (9.6). */\nexport function runInWorker<A extends unknown[], R>(fn: (...args: A) => R | Promise<R>, ...args: A): Promise<R> {\n if (typeof Worker === 'undefined' || typeof Blob === 'undefined' || typeof URL === 'undefined' || !URL.createObjectURL) return Promise.resolve().then(() => fn(...args));\n const src = `self.onmessage=async(e)=>{try{const r=await (${fn.toString()})(...e.data);self.postMessage({ok:true,r})}catch(err){self.postMessage({ok:false,e:String(err&&err.message||err)})}}`;\n const url = URL.createObjectURL(new Blob([src], { type: 'text/javascript' }));\n return new Promise<R>((resolve, reject) => {\n let w: Worker;\n try {\n w = new Worker(url);\n } catch (e) {\n URL.revokeObjectURL(url);\n return resolve(Promise.resolve().then(() => fn(...args)));\n }\n const done = () => {\n w.terminate();\n URL.revokeObjectURL(url);\n };\n w.onmessage = (e) => {\n done();\n if (e.data?.ok) resolve(e.data.r as R);\n else reject(new Error(e.data?.e || 'worker failed'));\n };\n w.onerror = (e) => {\n done();\n reject(new Error(e.message || 'worker error'));\n };\n w.postMessage(args);\n });\n}\n\nexport type DrawProgram = string | ((ctx: CanvasRenderingContext2D, t: number, w: number, h: number, state: Record<string, unknown>) => void);\n/** Animate `canvas` with `program` in a worker (OffscreenCanvas) when possible, else on the main thread (9.6). */\nexport function offscreenRender(canvas: HTMLCanvasElement, program: DrawProgram, opts: { worker?: boolean; paused?: boolean } = {}): { backend: 'worker' | 'main' | 'none'; stop(): void; resize(w: number, h: number): void } {\n const code = typeof program === 'string' ? program : program.toString();\n const canWorker = opts.worker !== false && typeof Worker !== 'undefined' && typeof (canvas as any).transferControlToOffscreen === 'function' && typeof Blob !== 'undefined' && !!URL.createObjectURL;\n if (canWorker) {\n try {\n const off = (canvas as any).transferControlToOffscreen();\n const src = `let c,ctx,w,h,st={},run=true,t0=0;const draw=(${code});self.onmessage=(e)=>{const d=e.data;if(d.canvas){c=d.canvas;ctx=c.getContext('2d');w=c.width;h=c.height;const loop=(t)=>{if(run){if(!t0)t0=t;draw(ctx,t-t0,w,h,st)}requestAnimationFrame(loop)};requestAnimationFrame(loop)}if(d.size){w=c.width=d.size[0];h=c.height=d.size[1]}if('run' in d)run=d.run;if(d.stop)close()}`;\n const url = URL.createObjectURL(new Blob([src], { type: 'text/javascript' }));\n const w = new Worker(url);\n w.postMessage({ canvas: off }, [off]);\n if (opts.paused) w.postMessage({ run: false });\n return {\n backend: 'worker',\n stop: () => {\n w.postMessage({ stop: true });\n w.terminate();\n URL.revokeObjectURL(url);\n },\n resize: (a, b) => w.postMessage({ size: [a, b] }),\n };\n } catch {\n /* fall back to the main thread */\n }\n }\n const ctx = canvas.getContext?.('2d');\n if (!ctx) return { backend: 'none', stop: () => undefined, resize: () => undefined };\n // eslint-disable-next-line no-new-func\n const draw = typeof program === 'function' ? program : (new Function(`return (${code})`)() as Exclude<DrawProgram, string>);\n const st: Record<string, unknown> = {};\n let t = 0;\n const stop = opts.paused\n ? () => undefined\n : onFrame((_now, dt) => {\n t += dt;\n draw(ctx, t, canvas.width, canvas.height, st);\n });\n if (opts.paused) draw(ctx, 0, canvas.width, canvas.height, st);\n return {\n backend: 'main',\n stop,\n resize: (a, b) => {\n canvas.width = a;\n canvas.height = b;\n },\n };\n}\n\n/** A rolling FPS meter on the shared frame loop: { fps, stop } (fps updates every frame) (9.6). */\nexport function fpsMeter(window = 30): { readonly fps: number; readonly samples: number[]; stop(): void } {\n const samples: number[] = [];\n let fps = 0;\n const stop = onFrame((_t, dt) => {\n if (dt <= 0) return;\n samples.push(1000 / dt);\n if (samples.length > window) samples.shift();\n fps = Math.round(samples.reduce((a, b) => a + b, 0) / samples.length);\n });\n return {\n get fps() {\n return fps;\n },\n samples,\n stop,\n };\n}\n\nexport const PERF3_FX: EffectDefinition[] = [\n {\n name: 'idle-reveal',\n kind: 'enter',\n description: 'Waits for an idle moment (requestIdleCallback, ≤ `timeout` ms) before fading in, keeping first paint and input snappy.',\n defaults: { duration: 400, timeout: 600 },\n run: (el: HTMLElement, o: any, ctx: EffectContext) => {\n if (ctx.reduced) return;\n const prev = el.style.opacity;\n el.style.opacity = '0';\n ctx.onCleanup(() => (el.style.opacity = prev));\n return new Promise<void>((resolve) => {\n const go = () => {\n el.style.opacity = prev;\n const a = ctx.animate(el, [{ opacity: 0 }, { opacity: 1 }], { duration: o.duration, easing: 'ease-out' });\n if (a) a.finished.then(() => resolve(), () => resolve());\n else resolve();\n };\n const ric = (globalThis as any).requestIdleCallback as ((cb: () => void, o?: { timeout: number }) => number) | undefined;\n if (ric) ric(go, { timeout: Number(o.timeout) || 600 });\n else setTimeout(go, 1);\n });\n },\n },\n {\n name: 'gpu-lift',\n kind: 'hover',\n description: 'A compositor-only hover lift — only transform and opacity change, so it never triggers layout or paint.',\n defaults: { duration: 250, lift: 6 },\n run: (el: HTMLElement, o: any, ctx: EffectContext) => {\n if (ctx.reduced) return;\n return ctx.animate(el, [{ transform: 'translateY(0) scale(1)' }, { transform: `translateY(${-Math.abs(Number(o.lift) || 6)}px) scale(1.02)` }], { duration: o.duration, easing: 'ease-out', fill: 'forwards' })?.finished.catch(() => undefined);\n },\n },\n];\n\n/** Register idle-reveal and gpu-lift (9.6). */\nexport function registerPerf3Pack(): void {\n registerEffects(PERF3_FX);\n}\n"],"names":[],"mappings":"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"}