@ev-ry/fx 0.1.0-rc.1 → 0.1.0-rc.3

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/build-report.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.0-rc.1",
2
+ "version": "0.1.0-rc.3",
3
3
  "effects": [
4
4
  "dust-wind",
5
5
  "smoke",
@@ -14,8 +14,8 @@
14
14
  },
15
15
  {
16
16
  "file": "src/dom-free.js",
17
- "bytes": 3047,
18
- "gzip": 1159
17
+ "bytes": 5191,
18
+ "gzip": 1910
19
19
  },
20
20
  {
21
21
  "file": "src/dom-attachment.js",
@@ -24,23 +24,23 @@
24
24
  },
25
25
  {
26
26
  "file": "src/dom-reveal.js",
27
- "bytes": 6042,
28
- "gzip": 2310
27
+ "bytes": 6790,
28
+ "gzip": 2528
29
29
  },
30
30
  {
31
31
  "file": "src/dom-svg-surface.js",
32
- "bytes": 4873,
33
- "gzip": 1994
32
+ "bytes": 5096,
33
+ "gzip": 2098
34
34
  },
35
35
  {
36
36
  "file": "src/dom-image-surface.js",
37
- "bytes": 10611,
38
- "gzip": 3666
37
+ "bytes": 14775,
38
+ "gzip": 4711
39
39
  },
40
40
  {
41
41
  "file": "src/image-surface.js",
42
- "bytes": 11834,
43
- "gzip": 4227
42
+ "bytes": 12425,
43
+ "gzip": 4356
44
44
  },
45
45
  {
46
46
  "file": "src/motion-envelope.js",
@@ -94,8 +94,8 @@
94
94
  },
95
95
  {
96
96
  "file": "src/triangle-effect.js",
97
- "bytes": 13694,
98
- "gzip": 4287
97
+ "bytes": 13758,
98
+ "gzip": 4313
99
99
  },
100
100
  {
101
101
  "file": "src/particle-centers.js",
@@ -129,8 +129,8 @@
129
129
  },
130
130
  {
131
131
  "file": "src/text-motion-primitives.js",
132
- "bytes": 4430,
133
- "gzip": 1894
132
+ "bytes": 4764,
133
+ "gzip": 2022
134
134
  },
135
135
  {
136
136
  "file": "src/text-motion-contour.js",
@@ -144,8 +144,8 @@
144
144
  },
145
145
  {
146
146
  "file": "src/dom-image-raster.js",
147
- "bytes": 1916,
148
- "gzip": 870
147
+ "bytes": 3273,
148
+ "gzip": 1287
149
149
  },
150
150
  {
151
151
  "file": "src/image-preparation-queue.js",
@@ -154,23 +154,23 @@
154
154
  },
155
155
  {
156
156
  "file": "src/dom-image-swap.js",
157
- "bytes": 3050,
158
- "gzip": 1225
157
+ "bytes": 3310,
158
+ "gzip": 1316
159
159
  },
160
160
  {
161
161
  "file": "src/dom-once.js",
162
- "bytes": 3023,
163
- "gzip": 1143
162
+ "bytes": 3463,
163
+ "gzip": 1357
164
164
  },
165
165
  {
166
166
  "file": "src/render-owner.js",
167
- "bytes": 3954,
168
- "gzip": 1456
167
+ "bytes": 4268,
168
+ "gzip": 1583
169
169
  },
170
170
  {
171
171
  "file": "src/viewport-render-owner.js",
172
- "bytes": 24414,
173
- "gzip": 7399
172
+ "bytes": 25171,
173
+ "gzip": 7607
174
174
  },
175
175
  {
176
176
  "file": "src/viewport-clip.js",
@@ -179,8 +179,8 @@
179
179
  },
180
180
  {
181
181
  "file": "src/dom-text-surface.js",
182
- "bytes": 18914,
183
- "gzip": 6011
182
+ "bytes": 22552,
183
+ "gzip": 7092
184
184
  },
185
185
  {
186
186
  "file": "src/hybrid-text-flow.js",
@@ -194,8 +194,8 @@
194
194
  },
195
195
  {
196
196
  "file": "src/font-rasterizer.js",
197
- "bytes": 3539,
198
- "gzip": 1322
197
+ "bytes": 4384,
198
+ "gzip": 1544
199
199
  },
200
200
  {
201
201
  "file": "src/native-run-shaping.js",
@@ -204,8 +204,8 @@
204
204
  },
205
205
  {
206
206
  "file": "src/text-scene.js",
207
- "bytes": 13688,
208
- "gzip": 3998
207
+ "bytes": 13840,
208
+ "gzip": 4007
209
209
  },
210
210
  {
211
211
  "file": "src/motion.js",
@@ -234,8 +234,8 @@
234
234
  },
235
235
  {
236
236
  "file": "src/dom-surface-font.js",
237
- "bytes": 3246,
238
- "gzip": 1251
237
+ "bytes": 3475,
238
+ "gzip": 1315
239
239
  },
240
240
  {
241
241
  "file": "src/dom-raster-cache.js",
@@ -244,8 +244,8 @@
244
244
  },
245
245
  {
246
246
  "file": "src/dom-rich-text.js",
247
- "bytes": 11269,
248
- "gzip": 4185
247
+ "bytes": 12881,
248
+ "gzip": 4613
249
249
  },
250
250
  {
251
251
  "file": "src/dom-text-runs.js",
@@ -254,8 +254,13 @@
254
254
  },
255
255
  {
256
256
  "file": "src/dom-text-fingerprint.js",
257
- "bytes": 2060,
258
- "gzip": 1005
257
+ "bytes": 2103,
258
+ "gzip": 1021
259
+ },
260
+ {
261
+ "file": "src/dom-text-paint-mask.js",
262
+ "bytes": 1469,
263
+ "gzip": 644
259
264
  },
260
265
  {
261
266
  "file": "src/dom-auto-route.js",
@@ -294,8 +299,8 @@
294
299
  },
295
300
  {
296
301
  "file": "src/dom-free.d.ts",
297
- "bytes": 1259,
298
- "gzip": 587
302
+ "bytes": 1387,
303
+ "gzip": 639
299
304
  },
300
305
  {
301
306
  "file": "src/dom-auto-reveal.d.ts",
@@ -304,8 +309,8 @@
304
309
  },
305
310
  {
306
311
  "file": "src/dom-attachment.d.ts",
307
- "bytes": 4471,
308
- "gzip": 1373
312
+ "bytes": 4537,
313
+ "gzip": 1404
309
314
  },
310
315
  {
311
316
  "file": "assets/vendor/three-LICENSE.txt",
@@ -323,6 +328,6 @@
323
328
  "gzip": 1971
324
329
  }
325
330
  ],
326
- "bytes": 954714,
327
- "gzipSum": 274468
331
+ "bytes": 974292,
332
+ "gzipSum": 280473
328
333
  }
package/docs/GUIDE.md CHANGED
@@ -1,124 +1,134 @@
1
1
  <!-- Generated by scripts/prepare-free-release.cjs from FREE-PACKAGE.md. -->
2
- # EV-RY FX Free — integration guide
3
-
4
- Four fixed behaviors: text enters with wind and exits as smoke; images enter as snow and exit with melt. Triangle particles, 2000ms, native resting. Existing native semantics remain with the host. SVG/icon attachment and simple image swaps are included with the image presets. Editable controls and other effect recipes remain outside this build. Shared text shaping and rendering utilities remain where required by display text.
5
-
6
- ## Script
7
-
8
- Serve this directory intact at `/thd/`:
9
-
10
- ```html
11
- <script src="/thd/src/dom-free-script.js" data-thd-auto defer></script>
12
- ```
13
-
14
- The loader uses existing window.THREE or loads the accompanying r158 asset. After the script executes, await `THDFree.ready`; its `instance` is the automatic installation. Without data-thd-auto, call `(await THDFree.ready).create(document, options)` explicitly. This is a script entry with accompanying modules/assets, not a single-file bundle.
15
-
16
- ## Modules
17
-
18
- ```js
19
- import {createFree} from '@ev-ry/fx';
20
- const free = createFree(THREE, document, {auto: false});
21
- const text = free.attachText(document.querySelector('.title'));
22
- await text.ready;
23
- text.play('exit');
24
- // free.destroy() on page/component unmount.
25
- ```
26
-
27
- Provide a compatible THREE namespace; r158 is the version tested here. The package import itself has no page side effects. Copy the assets directory with its relative paths if your bundler does not preserve new URL assets.
28
-
29
- For explicit initialization from an ES module, use the separate loader entry:
30
-
31
- ```js
32
- import * as THREE from 'three'; // r158 is the tested version.
33
- import {loadFree} from '@ev-ry/fx/script';
34
- const api = await loadFree({THREE, auto: true});
35
- // api.instance is the automatic installation.
36
- ```
37
-
38
- Importing this entry is inert, including during server-side rendering. Call `loadFree()` only in the browser after mounting the document. Without an injected THREE namespace it reuses `window.THREE` or loads the accompanying asset; keep that asset reachable when bundling. Repeated calls reuse the first `THDFree.ready`, so the first initialization chooses `auto`. For manual ownership, use `auto:false` then `api.create(root, options)`. Loaders do not silently start a second automatic installation.
39
-
40
- The classic `<script src="…/src/dom-free-script.js">` URL remains unchanged. `@ev-ry/fx/classic-script` resolves that classic asset; it is not an ES-module import entry. Do not use `type="module"` with the classic asset: use `loadFree` instead.
41
-
2
+ # EV-RY FX Free — integration guide
3
+
4
+ Four fixed behaviors: text enters with wind and exits as smoke; images enter as snow and exit with melt. Triangle particles, 2000ms, native resting. Existing native semantics remain with the host. SVG/icon attachment and simple image swaps are included with the image presets. Editable controls and other effect recipes remain outside this build. Shared text shaping and rendering utilities remain where required by display text.
5
+
6
+ ## Script
7
+
8
+ Serve this directory intact at `/thd/`:
9
+
10
+ ```html
11
+ <script src="/thd/src/dom-free-script.js" data-thd-auto defer></script>
12
+ ```
13
+
14
+ The loader uses existing window.THREE or loads the accompanying r158 asset. After the script executes, await `THDFree.ready`; its `instance` is the automatic installation. Without data-thd-auto, call `(await THDFree.ready).create(document, options)` explicitly. This is a script entry with accompanying modules/assets, not a single-file bundle.
15
+
16
+ ## Modules
17
+
18
+ ```js
19
+ import {createFree} from '@ev-ry/fx';
20
+ const free = createFree(THREE, document, {auto: false});
21
+ const text = free.attachText(document.querySelector('.title'));
22
+ await text.ready;
23
+ text.play('exit');
24
+ // free.destroy() on page/component unmount.
25
+ ```
26
+
27
+ Provide a compatible THREE namespace; r158 is the version tested here. The package import itself has no page side effects. Copy the assets directory with its relative paths if your bundler does not preserve new URL assets.
28
+
29
+ For explicit initialization from an ES module, use the separate loader entry:
30
+
31
+ ```js
32
+ import * as THREE from 'three'; // r158 is the tested version.
33
+ import {loadFree} from '@ev-ry/fx/script';
34
+ const api = await loadFree({THREE, auto: true});
35
+ // api.instance is the automatic installation.
36
+ ```
37
+
38
+ Importing this entry is inert, including during server-side rendering. Call `loadFree()` only in the browser after mounting the document. Without an injected THREE namespace it reuses `window.THREE` or loads the accompanying asset; keep that asset reachable when bundling. Repeated calls reuse the first `THDFree.ready`, so the first initialization chooses `auto`. For manual ownership, use `auto:false` then `api.create(root, options)`. Loaders do not silently start a second automatic installation.
39
+
40
+ The classic `<script src="…/src/dom-free-script.js">` URL remains unchanged. `@ev-ry/fx/classic-script` resolves that classic asset; it is not an ES-module import entry. Do not use `type="module"` with the classic asset: use `loadFree` instead.
41
+
42
42
  Auto selects h1/h2/[data-thd-text] and img[data-thd-image]; data-thd-ignore and interactive/navigation/dialog regions are excluded. Options: auto, textSelector, imageSelector, presentation, threshold, once, intersectionRoot. Call refresh after route/DOM changes. Manual attachment accepts presentation and revealOnView. play/cancel/refresh/stats/destroy are exposed; arbitrary effects are not. Scroll exit hides repeated targets rather than running an exit animation.
43
43
 
44
- Preparation is lazy for offscreen targets. Native content may paint before a late script initializes; use the optional early mask below for initial-page entry effects. Unsupported content falls back to native presentation. Cross-origin image canvas restrictions and unsupported rich CSS still apply. This does not claim general CSS reproduction or new physical-device validation.
45
-
46
- For an offscreen or zero-area image, `ready` can resolve with temporary native reason `Image not visible`; it does not promise a prepared GPU mesh outside the viewport. Image preparation begins near the viewport (roughly half a viewport height ahead). The native image's network loading policy is unchanged. Resize work is coalesced: the current raster follows the new box briefly, then the final crop and rounded corners are rebuilt after about 50ms of quiet, with a roughly 150ms bound during continuous resizing. Existing motion keeps its timeline.
47
-
48
- Unchanged text reuses validated layout and document-scoped raster pixels. The retained shared pixel cache is bounded at 16MiB; active geometry, textures and referenced pixels are separate, so this is not a total memory limit. Font loading invalidates cached pixels. Unsupported text is shown natively without repeated preparation retries; correct its layout and call `refresh()` to retry. Always destroy attachments on unmount.
49
-
50
- ## First-paint setup: avoid an initial flash
51
-
52
- Load the small boot script synchronously in the head, before body content (no async/defer). Keep the main loader deferred:
53
-
54
- ```html
55
- <head>
56
- <script src="/thd/src/dom-reveal-boot.js"></script>
57
- <script src="/thd/src/dom-free-script.js" data-thd-auto defer></script>
58
- </head>
59
- <body>
60
- <h1 data-thd-pending>Hello EV-RY FX</h1>
61
- <img data-thd-image data-thd-pending src="photo.jpg"
62
- width="640" height="400" alt="A descriptive caption">
63
- </body>
64
- ```
65
-
66
- `data-thd-pending` only requests initial masking; it does not select or attach an element. Use it on targets that will actually receive entry effects. The mask preserves layout and transfers to the reveal controller during attachment. Never edit the controller-owned `data-thd-reveal` attribute. Specify image dimensions or aspect-ratio to prevent loading-related layout shifts.
67
-
68
- Without JavaScript the boot mask is never installed. If the engine fails to load, `thd:error` releases the boot mask; an eight-second timer from boot execution also releases unclaimed masks. This timer is a failure fallback, not an animation delay. Late initialization after that timeout cannot guarantee flash-free entry. Once attached, the reveal controller owns its own preparation/error handling. A late-loaded boot script cannot undo an earlier paint. Sites with a restrictive CSP must allow the boot script and its injected style; otherwise omit pre-masking or integrate an equivalent permitted policy.
69
-
70
- ## Reveal, replay and component lifecycle
71
-
72
- Automatic reveal defaults to `threshold:0` and `once:true`. Threshold is visible area ratio: `0` means a positive intersection, `.5` half, `1` full visibility; an element larger than the viewport may never reach `1`.
73
-
74
- ```js
75
- const free = (await THDFree.ready).create(document, {
76
- once: false, threshold: 0.5
77
- }); // Load the script WITHOUT data-thd-auto for this manual setup.
78
- ```
79
-
80
- With `once:false`, a full exit rearms the target and hides it immediately; it does not play the exit effect. Small scroll changes while still intersecting do not rearm. Reentry during unfinished entry resumes the timeline; reentry after completion starts a new entry. Explicit `surface.play('enter')` is a replay request. Use `surface.play('exit')` for animated disappearance.
81
-
82
- Do not attach manually to an automatically selected element. Use `auto:false` for fully manual ownership, or exclude manual targets from the automatic selectors. Call `free.refresh()` after adding/removing targets; discovery is not a blanket DOM mutation watcher. Destroy the surface or installation on component unmount. In React, initialize after the host exists and return cleanup from the effect; see `examples/AnimatedTitle.jsx`. Its children are a string, not an arbitrary React subtree adapter.
83
-
84
- Automatic loader installations survive a persisted `pagehide`/`pageshow` round trip (the browser's back/forward cache). Return refreshes the same owner and preserves completed once-only entries; a non-persisted page exit releases it. A manually destroyed installation is never revived. For installations created with `api.create()` or `createFree()`, lifecycle belongs to the host: avoid destroying solely for `pagehide` when `event.persisted` is true, refresh on persisted return, and still destroy on actual component unmount.
85
-
86
- ## Troubleshooting
87
-
88
- Plain headings that wrap across lines use the existing DOM-measured multi-line text path automatically. A line break caused by available width alone does not require nested spans or a smaller font to animate. Unsupported typography can still retain native fallback.
44
+ An explicit `surface.play()` takes ownership from automatic `revealOnView` for that attachment, retaining the initial mask until rendering starts. Offscreen text, images and SVG keep their start timestamp without particle updates or drawing, and resume at the elapsed position instead of restarting. Use `await surface.whenFinished()` for cleanup, including native handoff and offscreen completion; it returns `{status: 'completed' | 'cancelled' | 'unsupported'}`. See `examples/evry-website.md` for captions driven by a slider clock.
89
45
 
90
- Normal/nowrap HTML whitespace is collapsed for raster text while DOM source offsets are preserved. NBSP and preformatted spacing are not globally trimmed. An initially empty manually attached target can reveal after content arrives; a recoverable unsupported layout can reveal after correction and refresh. Automatic scanning still requires `free.refresh()` to discover a previously empty, unattached target. Recovery does not replay an already completed once-only entry.
91
-
92
- | Symptom | Check |
93
- | --- | --- |
94
- | Initial flash | Boot script precedes body paint; target has `data-thd-pending`; no async/defer on boot. |
95
- | Target stays hidden until fallback | Target matches an attachment selector and is not inside an excluded region. |
96
- | No entry at full visibility threshold | Target fits inside the intersection root; try a smaller threshold. |
97
- | Image stays native | Inspect CORS restrictions, image loading and supported CSS; native fallback is intentional. |
98
- | Effect appears above an unrelated layer | Review zIndex and presentation routing; try local presentation for that target. |
99
- | Duplicate work after navigation | Destroy old owners, avoid simultaneous auto/manual attachment, then refresh new targets. |
100
-
101
- See [the Persian quickstart](../QUICKSTART.fa.md), [script example](../examples/script.html) and [phone/history check](../examples/navigation.html). Serve examples over HTTP. The boot helper applies to manual reveal attachment as well; the main loader is not required when using the module API.
102
-
103
- Build specialization strips unused attachment entry points and effect graphs, with assertions that fail on incompatible source changes. It is not source protection: browser code is inspectable. This Free distribution is MIT licensed; see ../LICENSE and ../NOTICE.md. Third-party notices retain their original terms. The private full-product source and unpublished Pro code are outside this distribution.
46
+ Concurrent `whenFinished()` calls share one pending completion and polling loop. A new successful `play()` cancels the previous pending wait; an invalid phase does not interrupt it. Cancellation, destruction of the attachment or its installation, and detaching the target resolve outstanding waits as `cancelled`. An untouched automatic reveal can wait for its first intersection without a preparation timeout. The 30-second watchdog only bounds an actual play/preparation attempt. Suspended effects are checked infrequently; waiting does not require a per-frame render loop.
104
47
 
48
+ Preparation is lazy for offscreen targets. Native content may paint before a late script initializes; use the optional early mask below for initial-page entry effects. Unsupported content falls back to native presentation. Cross-origin image canvas restrictions and unsupported rich CSS still apply. This does not claim general CSS reproduction or new physical-device validation.
105
49
 
106
- ## Media additions
107
-
108
- attachSVG(svg, options) uses snow/melt and native resting, supporting the existing static path/shape SVG subset. Unsupported filters, masks, use, embedded text/images and external paint references retain native fallback. This is not arbitrary SVG support.
109
-
110
- swapImage(previous, next, {exit:true, waitForExit:false, presentation:'global'}) returns {finished,cancel}. Both images must be unattached. Entry uses snow; optional exit uses melt. waitForExit sequences them. Arbitrary effect selection stays unavailable. The next image takes the previous image's placement on success; previous is hidden. These APIs reuse the main engine.
50
+ `surface.ready` describes the first preparation result, not installation readiness. A text/SVG target below the viewport can remain unprepared until it approaches view. Enable ordinary controls after attachment creation rather than waiting for every page target's `ready`. `play()` can queue a request before preparation; use `whenFinished()` for the end of that requested effect. Targets without an entry request keep their native presentation.
51
+
52
+ For an offscreen or zero-area image, `ready` can resolve with temporary native reason `Image not visible`; it does not promise a prepared GPU mesh outside the viewport. Image preparation begins near the viewport (roughly half a viewport height ahead). The native image's network loading policy is unchanged. Resize work is coalesced: the current raster follows the new box briefly, then the final crop and rounded corners are rebuilt after about 50ms of quiet, with a roughly 150ms bound during continuous resizing. Existing motion keeps its timeline.
53
+
54
+ Unchanged text reuses validated layout and document-scoped raster pixels. The retained shared pixel cache is bounded at 16MiB; active geometry, textures and referenced pixels are separate, so this is not a total memory limit. Font loading invalidates cached pixels. Unsupported text is shown natively without repeated preparation retries; correct its layout and call `refresh()` to retry. Always destroy attachments on unmount.
55
+
56
+ ## First-paint setup: avoid an initial flash
57
+
58
+ Load the small boot script synchronously in the head, before body content (no async/defer). Keep the main loader deferred:
59
+
60
+ ```html
61
+ <head>
62
+ <script src="/thd/src/dom-reveal-boot.js"></script>
63
+ <script src="/thd/src/dom-free-script.js" data-thd-auto defer></script>
64
+ </head>
65
+ <body>
66
+ <h1 data-thd-pending>Hello EV-RY FX</h1>
67
+ <img data-thd-image data-thd-pending src="photo.jpg"
68
+ width="640" height="400" alt="A descriptive caption">
69
+ </body>
70
+ ```
71
+
72
+ `data-thd-pending` only requests initial masking; it does not select or attach an element. Use it on targets that will actually receive entry effects. The mask preserves layout and transfers to the reveal controller during attachment. Never edit the controller-owned `data-thd-reveal` attribute. Specify image dimensions or aspect-ratio to prevent loading-related layout shifts.
73
+
74
+ Without JavaScript the boot mask is never installed. If the engine fails to load, `thd:error` releases the boot mask; an eight-second timer from boot execution also releases unclaimed masks. This timer is a failure fallback, not an animation delay. Late initialization after that timeout cannot guarantee flash-free entry. Once attached, the reveal controller owns its own preparation/error handling. A late-loaded boot script cannot undo an earlier paint. Sites with a restrictive CSP must allow the boot script and its injected style; otherwise omit pre-masking or integrate an equivalent permitted policy.
75
+
76
+ ## Reveal, replay and component lifecycle
77
+
78
+ Automatic reveal defaults to `threshold:0` and `once:true`. Threshold is visible area ratio: `0` means a positive intersection, `.5` half, `1` full visibility; an element larger than the viewport may never reach `1`.
79
+
80
+ ```js
81
+ const free = (await THDFree.ready).create(document, {
82
+ once: false, threshold: 0.5
83
+ }); // Load the script WITHOUT data-thd-auto for this manual setup.
84
+ ```
85
+
86
+ With `once:false`, a full exit rearms the target and hides it immediately; it does not play the exit effect. Small scroll changes while still intersecting do not rearm. Reentry during unfinished entry resumes the timeline; reentry after completion starts a new entry. Explicit `surface.play('enter')` is a replay request. Use `surface.play('exit')` for animated disappearance.
87
+
88
+ Do not attach manually to an automatically selected element. Use `auto:false` for fully manual ownership, or exclude manual targets from the automatic selectors. Call `free.refresh()` after adding/removing targets; discovery is not a blanket DOM mutation watcher. Destroy the surface or installation on component unmount. In React, initialize after the host exists and return cleanup from the effect; see `examples/AnimatedTitle.jsx`. Its children are a string, not an arbitrary React subtree adapter.
89
+
90
+ Automatic loader installations survive a persisted `pagehide`/`pageshow` round trip (the browser's back/forward cache). Return refreshes the same owner and preserves completed once-only entries; a non-persisted page exit releases it. A manually destroyed installation is never revived. For installations created with `api.create()` or `createFree()`, lifecycle belongs to the host: avoid destroying solely for `pagehide` when `event.persisted` is true, refresh on persisted return, and still destroy on actual component unmount.
91
+
92
+ ## Troubleshooting
93
+
94
+ Plain headings that wrap across lines use the existing DOM-measured multi-line text path automatically. A line break caused by available width alone does not require nested spans or a smaller font to animate. Unsupported typography can still retain native fallback.
95
+
96
+ Normal/nowrap HTML whitespace is collapsed for raster text while DOM source offsets are preserved. NBSP and preformatted spacing are not globally trimmed. An initially empty manually attached target can reveal after content arrives; a recoverable unsupported layout can reveal after correction and refresh. Automatic scanning still requires `free.refresh()` to discover a previously empty, unattached target. Recovery does not replay an already completed once-only entry.
97
+
98
+ | Symptom | Check |
99
+ | --- | --- |
100
+ | Initial flash | Boot script precedes body paint; target has `data-thd-pending`; no async/defer on boot. |
101
+ | Target stays hidden until fallback | Target matches an attachment selector and is not inside an excluded region. |
102
+ | No entry at full visibility threshold | Target fits inside the intersection root; try a smaller threshold. |
103
+ | Image stays native | Inspect CORS restrictions, image loading and supported CSS; native fallback is intentional. |
104
+ | Effect appears above an unrelated layer | Review zIndex and presentation routing; try local presentation for that target. |
105
+ | Duplicate work after navigation | Destroy old owners, avoid simultaneous auto/manual attachment, then refresh new targets. |
106
+
107
+ See [the Persian quickstart](../QUICKSTART.fa.md), [script example](../examples/script.html) and [phone/history check](../examples/navigation.html). Serve examples over HTTP. The boot helper applies to manual reveal attachment as well; the main loader is not required when using the module API.
108
+
109
+ Build specialization strips unused attachment entry points and effect graphs, with assertions that fail on incompatible source changes. It is not source protection: browser code is inspectable. This Free distribution is MIT licensed; see ../LICENSE and ../NOTICE.md. Third-party notices retain their original terms. The private full-product source and unpublished Pro code are outside this distribution.
110
+
111
+
112
+ ## Media additions
113
+
114
+ attachSVG(svg, options) uses snow/melt and native resting, supporting the existing static path/shape SVG subset. Unsupported filters, masks, use, embedded text/images and external paint references retain native fallback. This is not arbitrary SVG support.
115
+
116
+ swapImage(previous, next, {exit:true, waitForExit:false, presentation:'global'}) returns {finished,cancel}. Both images must be unattached. Entry uses snow; optional exit uses melt. waitForExit sequences them. Arbitrary effect selection stays unavailable. The next image takes the previous image's placement on success; previous is hidden. These APIs reuse the main engine.
117
+
118
+
119
+ ### Native image shape and layering
120
+
121
+ Free global canvas defaults to zIndex:1; createFree accepts zIndex to fit the host's layer convention. A header at z-index 20 now stays above the effect. This is an explicit shared-layer policy, not automatic reconstruction of arbitrary stacking contexts.
111
122
 
112
-
113
- ### Native image shape and layering
114
-
115
- Free global canvas defaults to zIndex:1; createFree accepts zIndex to fit the host's layer convention. A header at z-index 20 now stays above the effect. This is an explicit shared-layer policy, not automatic reconstruction of arbitrary stacking contexts.
116
-
117
123
  DOM images are rasterized from their displayed box before meshing, applying supported centered object-fit fill/contain/cover and elliptical/percentage corner radii. The alpha mask travels with particles instead of appearing only on native handoff. Changes in size/fit/radii rebuild that raster. This adds preparation work. Tainted canvas/unsupported layouts retain native fallback. Borders, shadows, arbitrary CSS clipping and complete screenshot equivalence are not claimed.
118
-
119
- ### Default shared canvas (2026-09-14)
120
-
124
+
125
+ ### Default shared canvas (2026-09-14)
126
+
121
127
  createFree defaults to auto routing with documentCanvas:true. Ordinary page effects use the bounded document-connected shared canvas; nested scroll containers, fixed/sticky content and explicit positioned stacking contexts route conservatively to local presentation. Force local with presentation:'local'; select legacy fixed shared rendering with presentation:'global',documentCanvas:false. experimentalDocumentCanvas is retained as a compatibility alias. Dialog-root rendering is unchanged. This is not general CSS stacking equivalence.
128
+
129
+ ## Native text handoff
130
+
131
+ Display text returns to native paint with a 350ms transition. Native text begins fading in 175ms before the entry timeline ends; mesh fade-out starts at the end. Allow preparation plus the 2000ms effect plus 350ms when observing completion; do not destroy a surface on a fixed 2000ms timer. Reduced-motion users bypass the handoff. Unsupported typography still falls back to native immediately.
122
132
 
123
133
  ## Package identity
124
134
 
@@ -1,40 +1,75 @@
1
- # EV-RY FX Free 0.1.0-rc.1
1
+ # EV-RY FX Free 0.1.0-rc.3
2
2
 
3
- Public preview release candidate from EV-RY. The Free edition uses the [MIT license](../LICENSE). Package: `@ev-ry/fx`. Try the [live demo](https://kbaghini.github.io/evry-fx/docs/) or browse the [source and examples](https://github.com/kbaghini/evry-fx).
4
-
5
- The product is now EV-RY FX. Existing `THDFree`, `data-thd-*`, `createFree`, `loadFree` and `src/dom-*` names remain compatible. No behavior or API rename is implied by the branding change.
6
-
7
- ## Included
8
-
9
- - Automatic intersection reveal for headings, marked display text and marked images.
10
- - Manual text, image and supported static SVG attachment, plus simple image swaps.
11
- - Four fixed presets: dust wind / smoke for text, drifting snow / melt for media. Textured triangular particles, a 2000ms timeline and native resting presentation.
12
- - Classic script loading, explicit `loadFree()` ES-module initialization, TypeScript declarations and integration examples.
13
- - Document-connected shared canvas with conservative local routing for special layouts.
14
-
15
- ## Reliability work
16
-
17
- - Early opt-in masking prevents native content flashing before initial reveal, while preserving layout and providing failure recovery.
18
- - Wrapped headings and ordinary HTML whitespace use browser-measured placement. Initially empty or recoverable unsupported text can resume after valid content/layout and refresh.
19
- - Reentry during an unfinished reveal preserves its clock. Completed once-only entries are not restarted by minor scrolling or presentation updates.
20
- - Unsupported content returns to native rendering instead of repeatedly retrying known failures. Repeated local refresh preserves valid attachments.
21
- - Font loading invalidates shared pixels and refreshes rich-text meshes even when the CSS font name and line metrics stay unchanged.
22
- - Automatic loader ownership handles browser back/forward-cache returns; manual owners retain explicit lifecycle responsibility.
23
- - `FreeSurface.cancel()` is required in the declarations, matching the runtime API. The module loader has its own typed export.
24
-
25
- ## Performance work
26
-
27
- - Validated, unchanged text reuses its layout before the expensive per-character placement pass. Character-frame data is collected only when the selected entry/exit effect needs it.
28
- - Display-text raster pixels use a document-scoped cache capped at 16MiB. This is a cache limit, not total page or GPU memory.
29
- - Offscreen images prepare near the viewport. Internal image preparation avoids an intermediate PNG encode/decode, and rapid resize requests are coalesced.
30
- - Geometry settings, visual quality, preset choices and effect duration are preserved. These changes remove redundant work; they do not promise a universal FPS improvement or eliminate all cold WebGL startup cost.
31
-
32
- ## Validation and limits
33
-
34
- Chrome desktop regression checks cover actual entry/exit motion, rapid reentry, plain/rich text rebuilding, whitespace and Persian mixed-text cases, fallback recovery, lifecycle cleanup and cache invalidation. Strict TypeScript consumer checks cover source and packaged declarations, cancellation, module-loader exports and rejected Free-only configuration.
35
-
36
- Earlier document-canvas scrolling tests on physical iPhone and Android devices were reported successful by the user. That is not fresh physical-device acceptance of every optimization in this candidate, nor a guarantee for all browsers, GPUs or websites.
37
-
38
- The Free build retains native fallback for unsupported content. Complete CSS stacking/clipping reproduction, arbitrary SVG, editable controls, custom effect recipes and dedicated framework component packs are outside this release. Cross-origin media must satisfy browser canvas restrictions. Review the [guide](GUIDE.md) before integration.
39
-
40
- [Back to the overview](../README.md)
3
+ - Cancel/update no longer starts a fresh text handoff; invalid play phases leave automatic reveal intact, and offscreen no-effect calls complete immediately.
4
+ - Image/SVG source refresh preserves the active effect clock, seed and settled exit mask. SVG snapshot errors retain native fallback diagnostics.
5
+ - SVG native pixels now appear below the settled mesh during the same 250ms handoff as images.
6
+ - Cancelling an image swap restores the incoming image's original location, including removal when it was initially detached.
7
+ - Completion waits share one pending poller, cancel promptly on superseding play or owner disposal, and do not time out untouched intersection reveals.
8
+ - Public whenFinished() for completion, cancellation and unsupported rendering.
9
+ - Offscreen text preserves its effect clock without particle updates or drawing; plain/rich text resumes at elapsed time and completion no longer waits for viewport entry.
10
+ - Explicit play takes ownership from automatic reveal without dropping the initial mask or restarting on intersection. Existing automatic reveal behavior remains in effect until explicit play.
11
+ - Offscreen exits suppress native text/image/SVG paint immediately and retain it through reentry and completion; cancellation and disposal restore original styles. Regression covers actual departure shader clocks, seeds, coordinates and rendered smoke pixels.
12
+ - Transient effects and image swaps complete on their original offscreen timeline without waiting for visual readiness; hidden transient polling is infrequent and cancellation still restores native content.
13
+ - Images reveal native pixels at assembly completion, then fade the mesh for 250ms, including swaps.
14
+
15
+ - Reuse local rendering buffers across differently sized surfaces, avoiding repeated GPU buffer allocation.
16
+ - Clear retained scratch pixels before each local surface copy.
17
+ - Image motion preserves source colors by default (recipe glow defaults to zero).
18
+ - Gradual particle appearance on the existing effect clock.
19
+ - Free image swaps support `enter` and `topImage`, including outgoing-only melt over the next native image.
20
+ - Include the EV-RY website integration recipe and initial-reveal lifecycle guidance.
21
+
22
+ Website artwork and SPA changes are not package runtime exports. Physical-device performance and the reported first-image brightness difference are not certified by this release.
23
+
24
+ # EV-RY FX Free 0.1.0-rc.2
25
+
26
+ - Fix collapsed whitespace at wrapped line ends triggering native fallback, including Android headings with negative letter spacing.
27
+ - Respect DOM font kerning and text-rendering settings; include them in cache identity and invalidation.
28
+ - Normalize high-resolution raster advances using the actual display font size. Physical iOS width parity remains to be confirmed.
29
+ - Smooth native-rest handoff: native text fades in over 350ms starting 175ms before entry ends; the mesh fades out over the following 350ms. Reduced motion skips this transition.
30
+ - Tie the handoff to the original effect clock to avoid restarting on delayed frames.
31
+
32
+ No Free API removal or effect expansion. The marketing site's SPA router, loader and staged headlines are site behavior, not package exports.
33
+
34
+ ## Previous release
35
+
36
+ # EV-RY FX Free 0.1.0-rc.1
37
+
38
+ Public preview release candidate from EV-RY. The Free edition uses the [MIT license](../LICENSE). Package: `@ev-ry/fx`. Try the [live demo](https://kbaghini.github.io/evry-fx/docs/) or browse the [source and examples](https://github.com/kbaghini/evry-fx).
39
+
40
+ The product is now EV-RY FX. Existing `THDFree`, `data-thd-*`, `createFree`, `loadFree` and `src/dom-*` names remain compatible. No behavior or API rename is implied by the branding change.
41
+
42
+ ## Included
43
+
44
+ - Automatic intersection reveal for headings, marked display text and marked images.
45
+ - Manual text, image and supported static SVG attachment, plus simple image swaps.
46
+ - Four fixed presets: dust wind / smoke for text, drifting snow / melt for media. Textured triangular particles, a 2000ms timeline and native resting presentation.
47
+ - Classic script loading, explicit `loadFree()` ES-module initialization, TypeScript declarations and integration examples.
48
+ - Document-connected shared canvas with conservative local routing for special layouts.
49
+
50
+ ## Reliability work
51
+
52
+ - Early opt-in masking prevents native content flashing before initial reveal, while preserving layout and providing failure recovery.
53
+ - Wrapped headings and ordinary HTML whitespace use browser-measured placement. Initially empty or recoverable unsupported text can resume after valid content/layout and refresh.
54
+ - Reentry during an unfinished reveal preserves its clock. Completed once-only entries are not restarted by minor scrolling or presentation updates.
55
+ - Unsupported content returns to native rendering instead of repeatedly retrying known failures. Repeated local refresh preserves valid attachments.
56
+ - Font loading invalidates shared pixels and refreshes rich-text meshes even when the CSS font name and line metrics stay unchanged.
57
+ - Automatic loader ownership handles browser back/forward-cache returns; manual owners retain explicit lifecycle responsibility.
58
+ - `FreeSurface.cancel()` is required in the declarations, matching the runtime API. The module loader has its own typed export.
59
+
60
+ ## Performance work
61
+
62
+ - Validated, unchanged text reuses its layout before the expensive per-character placement pass. Character-frame data is collected only when the selected entry/exit effect needs it.
63
+ - Display-text raster pixels use a document-scoped cache capped at 16MiB. This is a cache limit, not total page or GPU memory.
64
+ - Offscreen images prepare near the viewport. Internal image preparation avoids an intermediate PNG encode/decode, and rapid resize requests are coalesced.
65
+ - Geometry settings, visual quality, preset choices and effect duration are preserved. These changes remove redundant work; they do not promise a universal FPS improvement or eliminate all cold WebGL startup cost.
66
+
67
+ ## Validation and limits
68
+
69
+ Chrome desktop regression checks cover actual entry/exit motion, rapid reentry, plain/rich text rebuilding, whitespace and Persian mixed-text cases, fallback recovery, lifecycle cleanup and cache invalidation. Strict TypeScript consumer checks cover source and packaged declarations, cancellation, module-loader exports and rejected Free-only configuration.
70
+
71
+ Earlier document-canvas scrolling tests on physical iPhone and Android devices were reported successful by the user. That is not fresh physical-device acceptance of every optimization in this candidate, nor a guarantee for all browsers, GPUs or websites.
72
+
73
+ The Free build retains native fallback for unsupported content. Complete CSS stacking/clipping reproduction, arbitrary SVG, editable controls, custom effect recipes and dedicated framework component packs are outside this release. Cross-origin media must satisfy browser canvas restrictions. Review the [guide](GUIDE.md) before integration.
74
+
75
+ [Back to the overview](../README.md)