@cosmictraveler002/anim-kit 1.5.0 → 1.6.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.
Files changed (51) hide show
  1. package/README.md +248 -21
  2. package/dist/anim-kit.standalone.js +5 -5
  3. package/dist/anim-kit.standalone.js.map +1 -1
  4. package/dist/effects/horizontal-scroll.d.ts.map +1 -1
  5. package/dist/effects/horizontal-scroll.js +3 -0
  6. package/dist/effects/horizontal-scroll.js.map +1 -1
  7. package/dist/effects/ink-wipe.d.ts +41 -0
  8. package/dist/effects/ink-wipe.d.ts.map +1 -0
  9. package/dist/effects/ink-wipe.js +342 -0
  10. package/dist/effects/ink-wipe.js.map +1 -0
  11. package/dist/effects/scatter-text.d.ts.map +1 -1
  12. package/dist/effects/scatter-text.js +3 -0
  13. package/dist/effects/scatter-text.js.map +1 -1
  14. package/dist/effects/stacked-cards.d.ts.map +1 -1
  15. package/dist/effects/stacked-cards.js +3 -0
  16. package/dist/effects/stacked-cards.js.map +1 -1
  17. package/dist/index.d.ts +3 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +2 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/three/coverflow-wheel.d.ts +30 -0
  22. package/dist/three/coverflow-wheel.d.ts.map +1 -0
  23. package/dist/three/coverflow-wheel.js +537 -0
  24. package/dist/three/coverflow-wheel.js.map +1 -0
  25. package/dist/three/dither-reveal.d.ts +36 -0
  26. package/dist/three/dither-reveal.d.ts.map +1 -0
  27. package/dist/three/dither-reveal.js +292 -0
  28. package/dist/three/dither-reveal.js.map +1 -0
  29. package/dist/three/index.d.ts +8 -0
  30. package/dist/three/index.d.ts.map +1 -1
  31. package/dist/three/index.js +4 -0
  32. package/dist/three/index.js.map +1 -1
  33. package/dist/three/tear-reveal.d.ts +54 -0
  34. package/dist/three/tear-reveal.d.ts.map +1 -0
  35. package/dist/three/tear-reveal.js +269 -0
  36. package/dist/three/tear-reveal.js.map +1 -0
  37. package/dist/three/wordmark-wave.d.ts +29 -0
  38. package/dist/three/wordmark-wave.d.ts.map +1 -0
  39. package/dist/three/wordmark-wave.js +461 -0
  40. package/dist/three/wordmark-wave.js.map +1 -0
  41. package/package.json +1 -1
  42. package/src/effects/horizontal-scroll.ts +3 -0
  43. package/src/effects/ink-wipe.ts +409 -0
  44. package/src/effects/scatter-text.ts +3 -0
  45. package/src/effects/stacked-cards.ts +3 -0
  46. package/src/index.ts +3 -0
  47. package/src/three/coverflow-wheel.ts +618 -0
  48. package/src/three/dither-reveal.ts +365 -0
  49. package/src/three/index.ts +8 -0
  50. package/src/three/tear-reveal.ts +351 -0
  51. package/src/three/wordmark-wave.ts +517 -0
package/README.md CHANGED
@@ -30,6 +30,7 @@ destroy();
30
30
  - [Core](#core)
31
31
  - [Text animations](#text-animations)
32
32
  - [Scroll & media](#scroll--media)
33
+ - [WebGL](#webgl)
33
34
  - [Loops & marquees](#loops--marquees)
34
35
  - [Buttons & links](#buttons--links)
35
36
  - [Navigation & overlays](#navigation--overlays)
@@ -78,7 +79,7 @@ inside the published tarball):
78
79
 
79
80
  | Specifier | Resolves to | Use for |
80
81
  |---|---|---|
81
- | `@cosmictraveler002/anim-kit` | `dist/index.js` + `dist/index.d.ts` | the full barrel — 47 exports |
82
+ | `@cosmictraveler002/anim-kit` | `dist/index.js` + `dist/index.d.ts` | the full barrel — 48 exports |
82
83
  | `@cosmictraveler002/anim-kit/effects/<name>` | `dist/effects/<name>.js` + `.d.ts` | one effect in isolation (`marquee`, `lineReveal`, …) |
83
84
  | `@cosmictraveler002/anim-kit/three` | `dist/three/index.js` + `.d.ts` | WebGL effects — needs the optional `three` peer (never a core dep) |
84
85
  | `@cosmictraveler002/anim-kit/standalone` | `dist/anim-kit.standalone.js` (types → `index.d.ts`) | the self-contained bundle — same API |
@@ -98,7 +99,7 @@ resolves declarations through the same map — no `typesVersions` shim needed.
98
99
 
99
100
  No build step on the consumer's end: `dist/` is served as-is from the npm
100
101
  tarball by any npm CDN. Every URL is **version-pinned** — npm versions are
101
- immutable, so `@cosmictraveler002/anim-kit@1.5.0` always resolves to exactly that build, forever
102
+ immutable, so `@cosmictraveler002/anim-kit@1.6.1` always resolves to exactly that build, forever
102
103
  (only a new version creates a new URL; nothing floats unless you ask for a
103
104
  range).
104
105
 
@@ -109,12 +110,12 @@ plugins anim-kit uses) and `lenis` **inlined** — no import map, one URL, works
109
110
  identically on jsDelivr and unpkg:
110
111
 
111
112
  ```html
112
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.5.0/dist/styles/anim-kit.css" />
113
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.6.1/dist/styles/anim-kit.css" />
113
114
 
114
115
  <script type="module">
115
116
  import {
116
117
  smoothScroll, lineReveal, marquee,
117
- } from "https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.5.0/dist/anim-kit.standalone.js";
118
+ } from "https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.6.1/dist/anim-kit.standalone.js";
118
119
 
119
120
  smoothScroll();
120
121
  lineReveal("[data-lines]", { mode: "scroll" });
@@ -122,7 +123,7 @@ identically on jsDelivr and unpkg:
122
123
  </script>
123
124
  ```
124
125
 
125
- unpkg serves the same file: `https://unpkg.com/@cosmictraveler002/anim-kit@1.5.0/dist/anim-kit.standalone.js`
126
+ unpkg serves the same file: `https://unpkg.com/@cosmictraveler002/anim-kit@1.6.1/dist/anim-kit.standalone.js`
126
127
 
127
128
  ### Option 2 — jsDelivr `+esm`
128
129
 
@@ -131,7 +132,7 @@ per version):
131
132
 
132
133
  ```html
133
134
  <script type="module">
134
- import { lineReveal } from "https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.5.0/+esm";
135
+ import { lineReveal } from "https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.6.1/+esm";
135
136
  </script>
136
137
  ```
137
138
 
@@ -143,13 +144,13 @@ locally, with CDN URLs — and the way to share one GSAP between anim-kit and
143
144
  the rest of your page:
144
145
 
145
146
  ```html
146
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.5.0/dist/styles/anim-kit.css" />
147
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.6.1/dist/styles/anim-kit.css" />
147
148
 
148
149
  <script type="importmap">
149
150
  {
150
151
  "imports": {
151
- "@cosmictraveler002/anim-kit": "https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.5.0/dist/index.js",
152
- "@cosmictraveler002/anim-kit/three": "https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.5.0/dist/three/index.js",
152
+ "@cosmictraveler002/anim-kit": "https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.6.1/dist/index.js",
153
+ "@cosmictraveler002/anim-kit/three": "https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.6.1/dist/three/index.js",
153
154
  "three": "https://cdn.jsdelivr.net/npm/three@0.186.1/build/three.module.js",
154
155
  "gsap": "https://cdn.jsdelivr.net/npm/gsap@3.15.0/index.js",
155
156
  "gsap/ScrollTrigger": "https://cdn.jsdelivr.net/npm/gsap@3.15.0/ScrollTrigger.js",
@@ -170,7 +171,7 @@ the rest of your page:
170
171
  </script>
171
172
  ```
172
173
 
173
- Swap the host for unpkg (`https://unpkg.com/@cosmictraveler002/anim-kit@1.5.0/dist/index.js`, …) —
174
+ Swap the host for unpkg (`https://unpkg.com/@cosmictraveler002/anim-kit@1.6.1/dist/index.js`, …) —
174
175
  the file layout is identical. GSAP subpaths are listed one by one because
175
176
  import maps match specifiers literally: a trailing-slash prefix map would
176
177
  produce extension-less URLs, which CDNs don't serve. The `gsap`/`lenis` pins
@@ -329,7 +330,9 @@ prompt dock, and backs the `category` / `subcategory` fields on
329
330
  | Scroll & media | Parallax & depth | `parallax` |
330
331
  | Scroll & media | Heroes & media | `heroShrink`, `mediaSettle` |
331
332
  | Scroll & media | Enter reveals | `revealRule`, `unfoldReveal`, `clipWipe` |
332
- | WebGL | Shader media | `webglMedia`, `glRail` |
333
+ | WebGL | Shader media | `webglMedia`, `glRail`, `coverflowWheel` |
334
+ | WebGL | Scenes & overlays | `tearReveal`, `ditherReveal` |
335
+ | WebGL | Kinetic type | `wordmarkWave` |
333
336
  | Loops & marquees | Marquees | `marquee` |
334
337
  | Loops & marquees | Draggables & rails | `dragStrip`, `dragRail` |
335
338
  | Loops & marquees | Equalizers | `audioBars` |
@@ -340,6 +343,7 @@ prompt dock, and backs the `category` / `subcategory` fields on
340
343
  | Navigation & overlays | Cursors | `cursorFollower` |
341
344
  | Intros & transitions | Preloaders | `preloader` |
342
345
  | Intros & transitions | Theme wipes | `themeReveal` |
346
+ | Intros & transitions | Page transitions | `inkWipe` |
343
347
  | Logos & SVG | Path reveals | `logoReveal` |
344
348
 
345
349
  **Growing the library:** a subcategory is the slot sibling effects land in —
@@ -806,7 +810,8 @@ the entrance.
806
810
 
807
811
  ### WebGL
808
812
 
809
- GPU media — three.js re-renders your images as shader cards. This is the only
813
+ GPU media — three.js re-renders your content as shader cards, kinetic type
814
+ and reveal scenes. This is the only
810
815
  category behind the `@cosmictraveler002/anim-kit/three` subpath: `three` is an
811
816
  **optional peer** (see [Subpath exports](#subpath-exports)), so the core
812
817
  barrel and the standalone bundle stay three-free.
@@ -920,6 +925,186 @@ user-select: none`.
920
925
 
921
926
  ---
922
927
 
928
+ #### `coverflowWheel(target, options?) => handle`
929
+
930
+ Media cards on a flattened vertical wheel: each card bends along the arc in
931
+ the vertex shader instead of staying a rigid plane, fronts face you bright
932
+ while the backs show mirrored, one mip blurrier and half-lit, every card
933
+ carries a soft rim glow and a hair of blur at its edge — but photos stay
934
+ fully solid to their border, no translucent frames. Drag to
935
+ scrub — a release within 5 % of a slot commits to the neighbouring card,
936
+ smaller drags spring back — and while idle the wheel creeps a slot's lead-in
937
+ and then advances on its own.
938
+
939
+ ```ts
940
+ import { coverflowWheel } from "@cosmictraveler002/anim-kit/three";
941
+
942
+ const wheel = coverflowWheel("#deck", { idle: 5 });
943
+ wheel.goTo(3); // jump to a card (wraps)
944
+ wheel.next(); // one card on, 0.5 s
945
+ wheel.destroy();
946
+ ```
947
+
948
+ | Option | Default | Notes |
949
+ | --- | --- | --- |
950
+ | `index` | `0` | first card on show (wraps) |
951
+ | `autoplay` / `idle` | `true` / `5` | idle advance — seconds of stillness (with a 1/8-slot lead-in creep) |
952
+ | `duration` | `1.15` | `goTo()` tween, seconds (`next()` / `prev()` use 0.5) |
953
+ | `margin` | `0.1` | gap between cards, as a fraction of one slot |
954
+ | `glow` | `0.32` | rim-glow intensity around every card, 0–1 (0 = hard flat cards) |
955
+ | `edgeBlur` | `1` | soft blur at every card edge, 0–1 (0 = sharp edges); cards stay opaque either way |
956
+ | `dpr` | `2` | device-pixel-ratio cap |
957
+
958
+ Cards are the host's own `<img>`s — cover-cropped into textures at mount (a
959
+ failed decode drops that card; fewer than two survivors → inert) and restored
960
+ from their inline opacity on teardown. Pointer capture keeps the drag over the
961
+ deck wherever the pointer is and native image-dragging is blocked so a card
962
+ never swallows the stream; the host claims `cursor: grab` + `touch-action:
963
+ pan-y` and a `data-ak-coverflow` stamp, all restored by `destroy()`. Card
964
+ backs reuse the front texture (mirrored, half light in-shader) —
965
+ no second texture, no extra pass.
966
+
967
+ **Silent no-op ladder.** Missing target → inert handle; no
968
+ `WebGLRenderingContext` (SSR, jsdom, WebGL disabled) → inert *before* any
969
+ context probe; renderer refused → inert; reduced motion → inert, nothing
970
+ mounts. Every call on an inert handle is a safe no-op — nothing ever logs.
971
+ `destroy()` kills the tween + ticker, disconnects the observers, removes the
972
+ canvas and restores the images, host styles and dataset.
973
+
974
+ ---
975
+
976
+ #### `tearReveal(target, options?) => destroy`
977
+
978
+ A torn-edge sheet that sweeps a section in (or out): a noise-displaced
979
+ boundary travels across the overlay — the fbm joins the field *before*
980
+ thresholding, so patches tear away ahead of the front and ride across as
981
+ separate shards — while a barrel warp bows the edge into an arriving curve
982
+ instead of a straight line wobbling in place.
983
+
984
+ ```ts
985
+ tearReveal("[data-tear]", {
986
+ color: ["#ff8a1e", "#ff5252"], // [foot, top] gradient stops (or one colour)
987
+ blend: "multiply", // tint the section instead of covering it
988
+ direction: "up", // up | down | left | right
989
+ mode: "scroll", // play on enter — or "immediate"
990
+ duration: 1, // seconds for the timed run
991
+ });
992
+ // …or bind it to scroll instead:
993
+ tearReveal("[data-tall]", { scrub: 0.4, start: "top 90%", end: "top 30%" });
994
+ tearReveal(document.body, { fixed: true }); // page-sized sweep over the viewport
995
+ ```
996
+
997
+ | Option | Default | Notes |
998
+ | --- | --- | --- |
999
+ | `color` | `"#111111"` | one colour or `[foot, top]` gradient stops |
1000
+ | `blend` | `"none"` | CSS `mix-blend-mode` — `multiply` tints what is under the sheet |
1001
+ | `direction` | `"up"` | `up` / `down` / `left` / `right` |
1002
+ | `dispAmp` / `dispScale` / `dispDetail` / `dispDrift` | `0.175` / `12.2` / `5` / `0` | tear shape — how far, how big, how gritty, how fast it crawls |
1003
+ | `soft` | `0.001` | threshold half-width — small keeps shards crisp |
1004
+ | `lens` | `-0.275` | barrel warp — negative bows the middle of the boundary up |
1005
+ | `duration` / `ease` | `1` / `"power2.inOut"` | timed run (`mode: "scroll"` or `"immediate"`) |
1006
+ | `scrub` | — | smoothing seconds (or `true`) — progress follows the scroll, both ways |
1007
+ | `start` / `end` | `"top 85%"` / `"top 25%"` | ScrollTrigger positions |
1008
+ | `replay` | `false` | re-run on every viewport entry |
1009
+ | `fixed` / `z` / `dpr` | `false` / `1` / `2` | viewport-sized canvas, stacking, pixel-ratio cap |
1010
+
1011
+ Both ends of the travel are **sealed**: the displaced boundary reaches the
1012
+ far side with its deepest tear at exactly zero, so the last half-covered
1013
+ pixels never read as speckle or leftover haze.
1014
+
1015
+ **Silent no-op ladder.** Missing target → no-op; no `WebGLRenderingContext`
1016
+ (SSR, jsdom, WebGL disabled) → no-op *before* any context probe; renderer
1017
+ refused → no-op; reduced motion → nothing mounts. Nothing ever logs.
1018
+ `destroy()` kills the tween + ScrollTrigger, removes the canvas and restores
1019
+ the host's styles.
1020
+
1021
+ **DOM.** `position: relative; overflow: hidden` on the target — or skip the
1022
+ target box entirely with `fixed: true` for a page-sized sweep.
1023
+
1024
+ ---
1025
+
1026
+ #### `ditherReveal(target, options?) => destroy`
1027
+
1028
+ A photo punches through its placeholder in a hard-edged, fbm-speckled ring
1029
+ expanding from the centre outward — no soft dissolve, no crossfade: every
1030
+ pixel on the boundary flips between hole and not-hole. The noise spread
1031
+ ramps to zero at both ends of the travel, so progress 0 is exactly empty
1032
+ and progress 1 exactly solid.
1033
+
1034
+ ```ts
1035
+ ditherReveal("[data-reveal]", {
1036
+ plate: "#14140f", // flat placeholder where the mask has not opened yet
1037
+ duration: 1.15, // seconds, ease "power4.out"
1038
+ maskScale: 50, // fbm frequency across the element — lump scale of the edge
1039
+ hard: true, // the signature speckle (false = a soft dissolve)
1040
+ play: "visible", // fire on first viewport entry ("load" = when the texture decodes)
1041
+ });
1042
+ ```
1043
+
1044
+ | Option | Default | Notes |
1045
+ | --- | --- | --- |
1046
+ | `src` | first `<img>` | image source override when the wrapper has no image |
1047
+ | `duration` / `ease` | `1.15` / `"power4.out"` | the reveal clock |
1048
+ | `plate` | — | flat placeholder colour (omit → the wrapper's own background shows through) |
1049
+ | `maskScale` / `spread` / `detail` | `50` / `0.35` / `4` | edge shape — frequency, reach, fbm octaves |
1050
+ | `hard` | `true` | hard speckle vs soft dissolve |
1051
+ | `play` | `"visible"` | `"visible"` fires on first viewport entry, `"load"` on texture decode |
1052
+ | `dpr` | `2` | device-pixel-ratio cap |
1053
+
1054
+ The `<img>` keeps layout and alt text: the canvas covers the wrapper and
1055
+ only takes over its pixels once the texture has decoded — with a plate the
1056
+ placeholder covers from load, without one the image only steps aside when
1057
+ the reveal actually starts. The cover-crop runs in-shader (`object-fit:
1058
+ cover` maths), so any source aspect fills any card aspect.
1059
+
1060
+ **Silent no-op ladder.** Missing target → no-op; no `WebGLRenderingContext`
1061
+ (SSR, jsdom, WebGL disabled) → no-op *before* any context probe; renderer
1062
+ refused → no-op; texture 404 → the plain `<img>` stays untouched; reduced
1063
+ motion → the canvas never mounts and the static image is the resting state.
1064
+ Nothing ever logs. `destroy()` kills the tween + observers, disposes
1065
+ geometry/material/texture/renderer, removes the canvas and restores the
1066
+ image opacity and wrapper position.
1067
+
1068
+ ---
1069
+
1070
+ #### `wordmarkWave(target, options?) => destroy`
1071
+
1072
+ The target's own headline rendered to a texture and simulated as a spring
1073
+ lattice: the pointer shears the letters like thick liquid, red and cyan split
1074
+ out of the colour while the surface deforms, and every vertex springs back to
1075
+ exact rest. Text, font and colour come from the element itself — nothing to
1076
+ export, nothing to align.
1077
+
1078
+ ```ts
1079
+ import { wordmarkWave } from "@cosmictraveler002/anim-kit/three";
1080
+
1081
+ const destroy = wordmarkWave("#mark", {
1082
+ drag: 1, // pointer-velocity term — how hard the letters smear
1083
+ push: 0, // radial push — vertices flee the cursor while it sits still
1084
+ chroma: true, // red/cyan fringes while deforming
1085
+ });
1086
+ ```
1087
+
1088
+ | Option | Default | Notes |
1089
+ | --- | --- | --- |
1090
+ | `text` / `font` / `color` | target's own | override what is drawn (the host's colour is hidden while mounted) |
1091
+ | `outline` / `tracking` | `0` / `0` | stroke width, px (0 = filled) and extra letter spacing, px |
1092
+ | `drag` / `push` | `1` / `0` | pointer-velocity and radial-push terms of the simulation |
1093
+ | `chroma` | `true` | red/cyan chromatic split while deforming |
1094
+ | `segments` | `0` | grid columns — auto (≈10 px cells, ~5 k vertices) |
1095
+ | `opacity` / `z` / `dpr` | `1` / `1` / `2` | canvas opacity, stacking order, pixel-ratio cap |
1096
+
1097
+ The simulation steps at a fixed 60 Hz (max 4 steps per frame; a gap over
1098
+ 0.2 s counts as one step) so a backgrounded tab resumes where it left instead
1099
+ of exploding, and the ticker idles while the host is off-screen. **Silent
1100
+ no-op ladder**: missing target → no-op; no `WebGLRenderingContext` (SSR,
1101
+ jsdom, WebGL disabled) → no-op *before* any context probe; renderer refused →
1102
+ no-op; reduced motion → nothing mounts, the real text simply stays. Nothing
1103
+ ever logs. `destroy()` stops the ticker + observers, disposes the renderer,
1104
+ removes the canvas and restores the host colour it hid.
1105
+
1106
+ ---
1107
+
923
1108
  ### Loops & marquees
924
1109
 
925
1110
  Continuous motion — marquees, draggable rails, equaliser bars.
@@ -1245,6 +1430,46 @@ theme.set("dark"); theme.toggle(); theme.current(); theme.destroy();
1245
1430
 
1246
1431
  ---
1247
1432
 
1433
+ #### `inkWipe(options?) => handle`
1434
+
1435
+ A brush-loaded ink sheet sweeps across the whole page: `cover()` paints it
1436
+ over, the swap happens at full cover, `unveil()` lets the new page arrive
1437
+ behind the *same* stroke — one continuous left→right travel whose edge is
1438
+ seeded, so it never boils between frames. Bristle spurs and flecks run
1439
+ ahead of the front.
1440
+
1441
+ ```ts
1442
+ const wipe = inkWipe({ links: "[data-wipe]" }); // intercepts links; next page auto-unveils
1443
+
1444
+ await wipe.cover(); // resolves at full cover — swap your DOM here
1445
+ await wipe.unveil(); // the new page arrives behind the same stroke
1446
+ wipe.destroy();
1447
+
1448
+ // Options-object form or target-first form both work:
1449
+ inkWipe({ host: "#overlay", color: "#111" });
1450
+ inkWipe(document.body, { coverMs: 700 });
1451
+ ```
1452
+
1453
+ | Option | Default | Notes |
1454
+ | --- | --- | --- |
1455
+ | `host` | `document.body` | overlay host the canvas is appended to (target arg also works) |
1456
+ | `color` | `"#0b0b0b"` | ink colour |
1457
+ | `coverMs` / `unveilMs` | `550` / `650` | sweep durations |
1458
+ | `ease` | `"power2.inOut"` | GSAP ease for both halves |
1459
+ | `tilt` / `rough` / `bristle` | `0.35` / `44` / `180` | edge lean (× height), noise amplitude px, longest spur px |
1460
+ | `links` | — | selectors intercepted — a click runs the wipe before navigating |
1461
+ | `sessionKey` / `autoUnveil` | `"ak-ink-wipe"` / `true` | cross-page handoff flag + the page-load unveil |
1462
+ | `z` | `9998` | overlay z-index |
1463
+
1464
+ **The MPA handoff** is a sessionStorage flag: a covered navigation stores
1465
+ `sessionKey`, the next page reads it, covers instantly and unveils — no
1466
+ flash between documents. Reduced motion snaps both halves instantly to a
1467
+ solid sheet, so navigation still never flashes the swap; without a 2D
1468
+ context (SSR, jsdom) both promises resolve immediately and nothing paints.
1469
+ `destroy()` removes the canvas, its listeners and the link interception.
1470
+
1471
+ ---
1472
+
1248
1473
  ### Logos & SVG
1249
1474
 
1250
1475
  Vector reveals for brand marks.
@@ -1417,7 +1642,7 @@ curl http://localhost:4321/api/prompts/marquee # one prompt, text/plain
1417
1642
  ```
1418
1643
 
1419
1644
  On the page, every labelled section carries a **copy prompt** chip, and the
1420
- floating **⧉ prompts (29)** button at the bottom right opens the full
1645
+ floating **⧉ prompts (38)** button at the bottom right opens the full
1421
1646
  catalogue grouped by [effect category](#effect-categories) — one click copies
1422
1647
  an effect's prompt (the prompt states its category), *copy all* puts the
1423
1648
  entire set on the clipboard. The catalogue lives in `scripts/prompts.mjs`:
@@ -1428,17 +1653,19 @@ and slotting the effect into a subcategory.
1428
1653
  **Unit smoke** (`scripts/smoke.mjs`) runs the built bundle in **jsdom** and
1429
1654
  asserts:
1430
1655
 
1431
- 1. all 47 exports are present;
1656
+ 1. all 48 exports are present;
1432
1657
  2. plugins (`ScrollTrigger`, `SplitText`, `Draggable`, `CustomEase`,
1433
1658
  `Flip`, `ScrollSmoother`) and the 4 custom eases are registered;
1434
1659
  3. every effect no-ops safely on missing targets;
1435
- 4. 24 effects mount on real markup and unmount cleanly;
1436
- 5. `preloader` ticks in both the positional and options-object call forms;
1437
- 6. `lineReveal` actually splits into masked lines (and per-character masks
1660
+ 4. 27 effects mount on real markup and unmount cleanly;
1661
+ 5. pinning effects claim `refreshPriority: 1`, so ScrollTrigger refresh
1662
+ measures in document order (root README bug class E);
1663
+ 6. `preloader` ticks in both the positional and options-object call forms;
1664
+ 7. `lineReveal` actually splits into masked lines (and per-character masks
1438
1665
  with `split: "chars"`) and restores markup on destroy;
1439
- 7. `scrambleText` restores its text, `rollText` wraps/unwraps its rows, and
1666
+ 8. `scrambleText` restores its text, `rollText` wraps/unwraps its rows, and
1440
1667
  `counter({ progress: true })` renders a scrubbed readout;
1441
- 8. `utils`, `compose` and `guard` behave per contract.
1668
+ 9. `utils`, `compose` and `guard` behave per contract.
1442
1669
 
1443
1670
  **Demo smoke** (`scripts/demo-smoke.mjs`) loads the real `demo/index.html` and
1444
1671
  executes the real `demo/demo.js` wiring against it, then asserts the effects
@@ -1471,9 +1698,9 @@ anim-kit/
1471
1698
  │ │ ├─ guard.ts reduced-motion gate
1472
1699
  │ │ ├─ util.ts toArray/one/onReady/compose/raf
1473
1700
  │ │ └─ types.ts TargetLike / Destroy / CommonOptions
1474
- │ ├─ effects/ one file per effect (24 files, 28 effect functions)
1701
+ │ ├─ effects/ one file per effect (27 files, 31 effect functions)
1475
1702
  │ ├─ styles/anim-kit.css companion stylesheet
1476
- │ └─ index.ts barrel — 47 exports
1703
+ │ └─ index.ts barrel — 48 exports
1477
1704
  ├─ demo/ visual demo (import map, no bundler)
1478
1705
  ├─ scripts/
1479
1706
  │ ├─ serve.mjs static server + /api/prompts (:4321)