@cosmictraveler002/anim-kit 1.1.0 → 1.3.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/README.md +293 -36
- package/dist/anim-kit.standalone.js +18 -8
- package/dist/anim-kit.standalone.js.map +1 -1
- package/dist/core/gsap.d.ts +3 -2
- package/dist/core/gsap.d.ts.map +1 -1
- package/dist/core/gsap.js +4 -3
- package/dist/core/gsap.js.map +1 -1
- package/dist/core/smooth-scroll.d.ts +1 -1
- package/dist/core/smooth-scroll.js +1 -1
- package/dist/effects/clip-wipe.d.ts +10 -2
- package/dist/effects/clip-wipe.d.ts.map +1 -1
- package/dist/effects/clip-wipe.js +30 -9
- package/dist/effects/clip-wipe.js.map +1 -1
- package/dist/effects/cursor-follower.d.ts.map +1 -1
- package/dist/effects/cursor-follower.js +22 -4
- package/dist/effects/cursor-follower.js.map +1 -1
- package/dist/effects/drag-rail.d.ts +23 -0
- package/dist/effects/drag-rail.d.ts.map +1 -0
- package/dist/effects/drag-rail.js +196 -0
- package/dist/effects/drag-rail.js.map +1 -0
- package/dist/effects/flip-words.d.ts +31 -0
- package/dist/effects/flip-words.d.ts.map +1 -0
- package/dist/effects/flip-words.js +104 -0
- package/dist/effects/flip-words.js.map +1 -0
- package/dist/effects/magnetic.d.ts +15 -0
- package/dist/effects/magnetic.d.ts.map +1 -0
- package/dist/effects/magnetic.js +59 -0
- package/dist/effects/magnetic.js.map +1 -0
- package/dist/effects/media-settle.d.ts +2 -0
- package/dist/effects/media-settle.d.ts.map +1 -1
- package/dist/effects/media-settle.js +5 -2
- package/dist/effects/media-settle.js.map +1 -1
- package/dist/effects/preloader.js +1 -1
- package/dist/effects/reel-text.d.ts +19 -0
- package/dist/effects/reel-text.d.ts.map +1 -0
- package/dist/effects/reel-text.js +163 -0
- package/dist/effects/reel-text.js.map +1 -0
- package/dist/index.d.ts +14 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/three/index.d.ts +13 -0
- package/dist/three/index.d.ts.map +1 -0
- package/dist/three/index.js +12 -0
- package/dist/three/index.js.map +1 -0
- package/dist/three/webgl-media.d.ts +21 -0
- package/dist/three/webgl-media.d.ts.map +1 -0
- package/dist/three/webgl-media.js +268 -0
- package/dist/three/webgl-media.js.map +1 -0
- package/package.json +17 -1
- package/src/core/gsap.ts +4 -3
- package/src/core/smooth-scroll.ts +1 -1
- package/src/effects/clip-wipe.ts +50 -10
- package/src/effects/cursor-follower.ts +21 -4
- package/src/effects/drag-rail.ts +236 -0
- package/src/effects/flip-words.ts +148 -0
- package/src/effects/magnetic.ts +87 -0
- package/src/effects/media-settle.ts +7 -1
- package/src/effects/preloader.ts +1 -1
- package/src/effects/reel-text.ts +212 -0
- package/src/index.ts +18 -2
- package/src/three/index.ts +12 -0
- package/src/three/webgl-media.ts +325 -0
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# anim-kit
|
|
2
2
|
|
|
3
|
-
A modular, framework-agnostic animation library
|
|
4
|
-
[
|
|
3
|
+
A modular, framework-agnostic animation library by
|
|
4
|
+
[kalakritico.in](https://kalakritico.in/) — GSAP + ScrollTrigger + Lenis
|
|
5
5
|
effects packaged as independent ES modules.
|
|
6
6
|
|
|
7
7
|
TypeScript source → compiled ESM + `.d.ts` output. No framework, no virtual DOM,
|
|
@@ -78,8 +78,9 @@ inside the published tarball):
|
|
|
78
78
|
|
|
79
79
|
| Specifier | Resolves to | Use for |
|
|
80
80
|
|---|---|---|
|
|
81
|
-
| `@cosmictraveler002/anim-kit` | `dist/index.js` + `dist/index.d.ts` | the full barrel —
|
|
81
|
+
| `@cosmictraveler002/anim-kit` | `dist/index.js` + `dist/index.d.ts` | the full barrel — 47 exports |
|
|
82
82
|
| `@cosmictraveler002/anim-kit/effects/<name>` | `dist/effects/<name>.js` + `.d.ts` | one effect in isolation (`marquee`, `lineReveal`, …) |
|
|
83
|
+
| `@cosmictraveler002/anim-kit/three` | `dist/three/index.js` + `.d.ts` | WebGL effects — needs the optional `three` peer (never a core dep) |
|
|
83
84
|
| `@cosmictraveler002/anim-kit/standalone` | `dist/anim-kit.standalone.js` (types → `index.d.ts`) | the self-contained bundle — same API |
|
|
84
85
|
| `@cosmictraveler002/anim-kit/styles` | `dist/styles/anim-kit.css` | untouched plain CSS |
|
|
85
86
|
|
|
@@ -97,7 +98,7 @@ resolves declarations through the same map — no `typesVersions` shim needed.
|
|
|
97
98
|
|
|
98
99
|
No build step on the consumer's end: `dist/` is served as-is from the npm
|
|
99
100
|
tarball by any npm CDN. Every URL is **version-pinned** — npm versions are
|
|
100
|
-
immutable, so `@cosmictraveler002/anim-kit@1.
|
|
101
|
+
immutable, so `@cosmictraveler002/anim-kit@1.3.0` always resolves to exactly that build, forever
|
|
101
102
|
(only a new version creates a new URL; nothing floats unless you ask for a
|
|
102
103
|
range).
|
|
103
104
|
|
|
@@ -108,12 +109,12 @@ plugins anim-kit uses) and `lenis` **inlined** — no import map, one URL, works
|
|
|
108
109
|
identically on jsDelivr and unpkg:
|
|
109
110
|
|
|
110
111
|
```html
|
|
111
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.
|
|
112
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.3.0/dist/styles/anim-kit.css" />
|
|
112
113
|
|
|
113
114
|
<script type="module">
|
|
114
115
|
import {
|
|
115
116
|
smoothScroll, lineReveal, marquee,
|
|
116
|
-
} from "https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.
|
|
117
|
+
} from "https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.3.0/dist/anim-kit.standalone.js";
|
|
117
118
|
|
|
118
119
|
smoothScroll();
|
|
119
120
|
lineReveal("[data-lines]", { mode: "scroll" });
|
|
@@ -121,7 +122,7 @@ identically on jsDelivr and unpkg:
|
|
|
121
122
|
</script>
|
|
122
123
|
```
|
|
123
124
|
|
|
124
|
-
unpkg serves the same file: `https://unpkg.com/@cosmictraveler002/anim-kit@1.
|
|
125
|
+
unpkg serves the same file: `https://unpkg.com/@cosmictraveler002/anim-kit@1.3.0/dist/anim-kit.standalone.js`
|
|
125
126
|
|
|
126
127
|
### Option 2 — jsDelivr `+esm`
|
|
127
128
|
|
|
@@ -130,7 +131,7 @@ per version):
|
|
|
130
131
|
|
|
131
132
|
```html
|
|
132
133
|
<script type="module">
|
|
133
|
-
import { lineReveal } from "https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.
|
|
134
|
+
import { lineReveal } from "https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.3.0/+esm";
|
|
134
135
|
</script>
|
|
135
136
|
```
|
|
136
137
|
|
|
@@ -142,17 +143,20 @@ locally, with CDN URLs — and the way to share one GSAP between anim-kit and
|
|
|
142
143
|
the rest of your page:
|
|
143
144
|
|
|
144
145
|
```html
|
|
145
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.
|
|
146
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.3.0/dist/styles/anim-kit.css" />
|
|
146
147
|
|
|
147
148
|
<script type="importmap">
|
|
148
149
|
{
|
|
149
150
|
"imports": {
|
|
150
|
-
"@cosmictraveler002/anim-kit": "https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.
|
|
151
|
+
"@cosmictraveler002/anim-kit": "https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.3.0/dist/index.js",
|
|
152
|
+
"@cosmictraveler002/anim-kit/three": "https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.3.0/dist/three/index.js",
|
|
153
|
+
"three": "https://cdn.jsdelivr.net/npm/three@0.186.1/build/three.module.js",
|
|
151
154
|
"gsap": "https://cdn.jsdelivr.net/npm/gsap@3.15.0/index.js",
|
|
152
155
|
"gsap/ScrollTrigger": "https://cdn.jsdelivr.net/npm/gsap@3.15.0/ScrollTrigger.js",
|
|
153
156
|
"gsap/SplitText": "https://cdn.jsdelivr.net/npm/gsap@3.15.0/SplitText.js",
|
|
154
157
|
"gsap/Draggable": "https://cdn.jsdelivr.net/npm/gsap@3.15.0/Draggable.js",
|
|
155
158
|
"gsap/CustomEase": "https://cdn.jsdelivr.net/npm/gsap@3.15.0/CustomEase.js",
|
|
159
|
+
"gsap/Flip": "https://cdn.jsdelivr.net/npm/gsap@3.15.0/Flip.js",
|
|
156
160
|
"gsap/ScrollSmoother": "https://cdn.jsdelivr.net/npm/gsap@3.15.0/ScrollSmoother.js",
|
|
157
161
|
"lenis": "https://cdn.jsdelivr.net/npm/lenis@1.3.26/dist/lenis.mjs"
|
|
158
162
|
}
|
|
@@ -166,12 +170,42 @@ the rest of your page:
|
|
|
166
170
|
</script>
|
|
167
171
|
```
|
|
168
172
|
|
|
169
|
-
Swap the host for unpkg (`https://unpkg.com/@cosmictraveler002/anim-kit@1.
|
|
173
|
+
Swap the host for unpkg (`https://unpkg.com/@cosmictraveler002/anim-kit@1.3.0/dist/index.js`, …) —
|
|
170
174
|
the file layout is identical. GSAP subpaths are listed one by one because
|
|
171
175
|
import maps match specifiers literally: a trailing-slash prefix map would
|
|
172
176
|
produce extension-less URLs, which CDNs don't serve. The `gsap`/`lenis` pins
|
|
173
177
|
match `package-lock.json`.
|
|
174
178
|
|
|
179
|
+
### Version discipline — the pin must match the code
|
|
180
|
+
|
|
181
|
+
The pin is a contract between the URL and the importing code: a bundle older
|
|
182
|
+
than the names your code imports fails at **module instantiation**, before a
|
|
183
|
+
single line runs —
|
|
184
|
+
|
|
185
|
+
```text
|
|
186
|
+
SyntaxError: The requested module '…/anim-kit@<old-version>/dist/anim-kit.standalone.js'
|
|
187
|
+
does not provide an export named 'clipWipe'
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
On a page with a preloader that reads as **frozen on the loader**: the script
|
|
191
|
+
that would dismiss it never executed. Whenever a loader won't clear, open the
|
|
192
|
+
console first — an `… does not provide an export named 'X'` error is version
|
|
193
|
+
skew, not an effect bug.
|
|
194
|
+
|
|
195
|
+
Rules that keep it from happening:
|
|
196
|
+
|
|
197
|
+
- **Never hand-copy the demo or hand-edit CDN URLs.** Regenerate the
|
|
198
|
+
deployable copy with `npm run sync:live` — it rewrites every pin from
|
|
199
|
+
`CDN_VERSION` in `scripts/prompts.mjs`, the single source, and
|
|
200
|
+
`demo-smoke` fails CI if `demo/`, `demo_live/` or this README drift from
|
|
201
|
+
the release version.
|
|
202
|
+
- **On a release bump**, change `version` and `CDN_VERSION` together (the
|
|
203
|
+
smoke enforces it); every copy prompt, the docs page and `demo_live/`
|
|
204
|
+
follow automatically.
|
|
205
|
+
- **In your own project**, every CDN URL you ship must carry the *same*
|
|
206
|
+
version — mixing a new JS pin with an old CSS pin (or vice versa) is the
|
|
207
|
+
same bug in slow motion.
|
|
208
|
+
|
|
175
209
|
---
|
|
176
210
|
|
|
177
211
|
## Quick start
|
|
@@ -279,17 +313,20 @@ prompt dock, and backs the `category` / `subcategory` fields on
|
|
|
279
313
|
| Text animations | Line & mask reveals | `lineReveal`, `maskReveal` |
|
|
280
314
|
| Text animations | Per-character scatter | `scatterText` |
|
|
281
315
|
| Text animations | Decode & scramble | `scrambleText` |
|
|
282
|
-
| Text animations | Rolling text | `rollText` |
|
|
316
|
+
| Text animations | Rolling text | `rollText`, `reelText` |
|
|
283
317
|
| Text animations | Counters | `counter` |
|
|
318
|
+
| Text animations | Layout transfers | `flipWords` |
|
|
284
319
|
| Scroll & media | Pinned galleries | `horizontalScroll`, `stackedCards`, `stackedCardsPinned` |
|
|
285
320
|
| Scroll & media | Parallax & depth | `parallax` |
|
|
286
321
|
| Scroll & media | Heroes & media | `heroShrink`, `mediaSettle` |
|
|
287
322
|
| Scroll & media | Enter reveals | `revealRule`, `unfoldReveal`, `clipWipe` |
|
|
323
|
+
| WebGL | Shader media | `webglMedia` |
|
|
288
324
|
| Loops & marquees | Marquees | `marquee` |
|
|
289
|
-
| Loops & marquees |
|
|
325
|
+
| Loops & marquees | Draggables & rails | `dragStrip`, `dragRail` |
|
|
290
326
|
| Loops & marquees | Equalizers | `audioBars` |
|
|
291
327
|
| Buttons & links | Liquid fills | `liquidButton` |
|
|
292
328
|
| Buttons & links | Underlines | `underlineLink` |
|
|
329
|
+
| Buttons & links | Magnetic hover | `magnetic` |
|
|
293
330
|
| Navigation & overlays | Menus & nav | `navHide`, `menuOverlay` |
|
|
294
331
|
| Navigation & overlays | Cursors | `cursorFollower` |
|
|
295
332
|
| Intros & transitions | Preloaders | `preloader` |
|
|
@@ -310,7 +347,7 @@ that does not exist.
|
|
|
310
347
|
#### `initGSAP()`
|
|
311
348
|
|
|
312
349
|
Registers the plugins anim-kit relies on (`ScrollTrigger`, `SplitText`,
|
|
313
|
-
`Draggable`, `CustomEase`, `ScrollSmoother`) and the studio's custom eases.
|
|
350
|
+
`Draggable`, `CustomEase`, `Flip`, `ScrollSmoother`) and the studio's custom eases.
|
|
314
351
|
Idempotent; called for you by every effect.
|
|
315
352
|
|
|
316
353
|
#### `EASES`
|
|
@@ -349,7 +386,7 @@ Central reduced-motion gate. If the user prefers reduced motion and
|
|
|
349
386
|
### Text animations
|
|
350
387
|
|
|
351
388
|
Typography in motion — masked lines, rising masks, per-character scatter,
|
|
352
|
-
decode reveals, rolling words, tickers.
|
|
389
|
+
decode reveals, rolling words and reels, tickers, layout transfers.
|
|
353
390
|
|
|
354
391
|
#### `lineReveal(target, options?) => destroy`
|
|
355
392
|
|
|
@@ -478,6 +515,73 @@ rollText("[data-roll-rev]", { direction: "down" }); // walk rows in reverse
|
|
|
478
515
|
blocks itself. `destroy()` unwraps the rows, removes the clone and restores
|
|
479
516
|
every inline style — markup comes back byte-identical.
|
|
480
517
|
|
|
518
|
+
#### `reelText(target, options?) => destroy`
|
|
519
|
+
|
|
520
|
+
Per-character odometer roll — the DOM slot-machine decode. Every alphanumeric
|
|
521
|
+
character gets a masked cell holding a vertical strip of same-case ghost
|
|
522
|
+
glyphs; the strip rolls upward and lands on the real text, staggered left to
|
|
523
|
+
right. Spaces and punctuation pass through untouched.
|
|
524
|
+
|
|
525
|
+
```html
|
|
526
|
+
<p data-reel>Every character rolls into place.</p>
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
```ts
|
|
530
|
+
reelText("[data-reel]", { mode: "scroll", frames: 4, stagger: 0.05 }); // roll on enter
|
|
531
|
+
reelText("[data-immediate]"); // roll at init
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
| Option | Default | Notes |
|
|
535
|
+
| --- | --- | --- |
|
|
536
|
+
| `mode` | `"immediate"` | `"scroll"` rolls on viewport enter instead |
|
|
537
|
+
| `start` | `"top 85%"` | ScrollTrigger start (scroll mode) |
|
|
538
|
+
| `frames` | `4` | ghost glyphs per character before the final one |
|
|
539
|
+
| `duration` | `0.8` | roll time per character, seconds |
|
|
540
|
+
| `stagger` | `0.05` | delay between characters, seconds |
|
|
541
|
+
| `ease` | `"power4.out"` | GSAP ease for the roll |
|
|
542
|
+
| `replay` | `false` | re-arm on every scroll re-entry instead of once |
|
|
543
|
+
| `force` | `false` | run even under `prefers-reduced-motion` |
|
|
544
|
+
|
|
545
|
+
**DOM:** targets must be plain text — the effect snapshots `innerHTML`, wraps
|
|
546
|
+
each character in a measured mask (width taken from the *final* glyph, so the
|
|
547
|
+
line never jitters while spinning) and restores the original markup byte for
|
|
548
|
+
byte when the roll completes and on `destroy()`. Scroll-mode registration
|
|
549
|
+
happens once and is killed on teardown; reduced motion leaves the target
|
|
550
|
+
completely untouched. No stylesheet required — masks are inline.
|
|
551
|
+
|
|
552
|
+
#### `flipWords(target, { to }, options?) => destroy`
|
|
553
|
+
|
|
554
|
+
Words measured in one layout, moved into another and animated from where
|
|
555
|
+
they stood — the FLIP technique: a column of words fans out into a row,
|
|
556
|
+
driven by scroll progress or played once.
|
|
557
|
+
|
|
558
|
+
```ts
|
|
559
|
+
// Both blocks share one grid cell so the stage never reflows mid-flight:
|
|
560
|
+
flipWords("[data-flip-from]", { to: "[data-flip-to]", scrub: 0.6 });
|
|
561
|
+
|
|
562
|
+
// One-shot on enter (reverses on leave-back), or immediately:
|
|
563
|
+
flipWords("[data-flip-from]", { to: "[data-flip-to]", mode: "scroll" });
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
| Option | Default | Notes |
|
|
567
|
+
| --- | --- | --- |
|
|
568
|
+
| `to` | — (required) | destination block; every word is moved into it |
|
|
569
|
+
| `words` | source children | `[data-flip-word]` matches, else the source's element children |
|
|
570
|
+
| `duration` | `1.4` | seconds for one word's travel |
|
|
571
|
+
| `ease` | `"power4.inOut"` | GSAP ease |
|
|
572
|
+
| `stagger` | `0.2` | seconds between word starts |
|
|
573
|
+
| `scale` | `0.2` | mid-flight squash each word pops through (`0` disables) |
|
|
574
|
+
| `mode` | `"scroll"` | or `"immediate"` to play at once |
|
|
575
|
+
| `scrub` | unset | number = scrub smoothing seconds, `true` = immediate |
|
|
576
|
+
| `start / end` | `"top 75%"` / `"bottom 45%"` | ScrollTrigger positions |
|
|
577
|
+
|
|
578
|
+
**DOM:** put the source and destination blocks in the same grid cell
|
|
579
|
+
(`grid-area: 1 / 1`, a `min-height` on both keeps the stage stable). The words
|
|
580
|
+
live in the destination from init but render at their source positions until
|
|
581
|
+
the timeline runs, so scrubbing reverses perfectly. `destroy()` kills the
|
|
582
|
+
timeline, puts every word back in its original parent and restores the
|
|
583
|
+
inline transform.
|
|
584
|
+
|
|
481
585
|
#### `counter(target, options?) => destroy`
|
|
482
586
|
|
|
483
587
|
Tabular number ticker.
|
|
@@ -545,23 +649,28 @@ as authored.
|
|
|
545
649
|
|
|
546
650
|
#### `clipWipe(target, options?) => destroy`
|
|
547
651
|
|
|
548
|
-
A `clip-path: inset()` reveal: the element is collapsed behind one edge
|
|
549
|
-
inside a frame margin) and the inset animates to zero so it wipes
|
|
652
|
+
A `clip-path: inset()` reveal: the element is collapsed behind one edge,
|
|
653
|
+
corner (or inside a frame margin) and the inset animates to zero so it wipes
|
|
654
|
+
into view.
|
|
550
655
|
|
|
551
656
|
```ts
|
|
552
657
|
clipWipe("[data-clip]", { from: "left" }); // inset(0 100% 0 0) → 0
|
|
553
658
|
clipWipe("[data-frame]", { from: "frame", inset: 12 }); // opens out of a frame
|
|
659
|
+
clipWipe("[data-corner]", { from: "bottom-right" }); // opens toward the opposite corner
|
|
660
|
+
clipWipe("[data-scrub]", { from: "frame", scrub: 0.5 }); // bound to scroll progress
|
|
554
661
|
```
|
|
555
662
|
|
|
556
663
|
| Option | Default | Notes |
|
|
557
664
|
| --- | --- | --- |
|
|
558
|
-
| `from` | `"left"` | `"left"` / `"right"` / `"top"` / `"bottom"` / `"frame"` |
|
|
665
|
+
| `from` | `"left"` | `"left"` / `"right"` / `"top"` / `"bottom"`, a corner (`"top-left"` / `"top-right"` / `"bottom-left"` / `"bottom-right"`), or `"frame"` |
|
|
559
666
|
| `inset` | `15` | frame margin in % (`from: "frame"`) |
|
|
560
667
|
| `duration` | `1` | seconds |
|
|
561
668
|
| `ease` | `"power3.out"` | GSAP ease |
|
|
562
669
|
| `stagger` | `0.08` | seconds between targets |
|
|
563
670
|
| `mode` | `"scroll"` | or `"immediate"` to play at once |
|
|
564
671
|
| `start` | `"top 85%"` | ScrollTrigger start |
|
|
672
|
+
| `end` | `"top 20%"` | ScrollTrigger end (scrub mode) |
|
|
673
|
+
| `scrub` | unset | number = scrub smoothing seconds, `true` = immediate: tie the wipe to scroll progress |
|
|
565
674
|
| `replay` | `false` | re-wipe when leaving / re-entering |
|
|
566
675
|
|
|
567
676
|
Works on images, video, blocks and text. `destroy()` removes the inline
|
|
@@ -660,7 +769,8 @@ heroShrink("[data-hero-media]", { offsetY: "49vh", scale: 0.23, scrub: 1 });
|
|
|
660
769
|
|
|
661
770
|
Images and video that arrive slightly oversized and ease down to size as the
|
|
662
771
|
section enters — content lands instead of popping in. Set `scrub` to bind the
|
|
663
|
-
settle to scroll progress
|
|
772
|
+
settle to scroll progress, or `replay: true` to reverse back to `from` on
|
|
773
|
+
leave-back and replay on every re-enter.
|
|
664
774
|
|
|
665
775
|
```ts
|
|
666
776
|
mediaSettle("[data-settle]", { from: 1.15, duration: 1.5 }); // on enter
|
|
@@ -677,6 +787,7 @@ mediaSettle("[data-settle-scrub]", { scrub: 0.5, from: 1.2 }); // scroll-bound
|
|
|
677
787
|
| `mode` | `"scroll"` | or `"immediate"` to play at once |
|
|
678
788
|
| `start` / `end` | `"top 75%"` / `"bottom top"` | ScrollTrigger positions |
|
|
679
789
|
| `scrub` | unset | number = scrub smoothing seconds, `true` = immediate |
|
|
790
|
+
| `replay` | `false` | re-settle when leaving / re-entering (enter mode) |
|
|
680
791
|
|
|
681
792
|
`destroy()` clears `transform` — media returns to its authored scale. Unlike
|
|
682
793
|
`heroShrink()` (which scrubs media down as it *leaves*), `mediaSettle()` plays
|
|
@@ -684,9 +795,63 @@ the entrance.
|
|
|
684
795
|
|
|
685
796
|
---
|
|
686
797
|
|
|
798
|
+
### WebGL
|
|
799
|
+
|
|
800
|
+
GPU media — three.js re-renders your images as shader cards. This is the only
|
|
801
|
+
category behind the `@cosmictraveler002/anim-kit/three` subpath: `three` is an
|
|
802
|
+
**optional peer** (see [Subpath exports](#subpath-exports)), so the core
|
|
803
|
+
barrel and the standalone bundle stay three-free.
|
|
804
|
+
|
|
805
|
+
#### `webglMedia(target, options?) => destroy`
|
|
806
|
+
|
|
807
|
+
> From `@cosmictraveler002/anim-kit/three`, not the core barrel.
|
|
808
|
+
|
|
809
|
+
Re-renders a plain `<img>` as a rounded GL plane: hover presses a dent into
|
|
810
|
+
the picture (the sample UV is squeezed toward the cursor, with a fake dome
|
|
811
|
+
lighting term and a chroma split), and the card wipes in bottom-up on reveal.
|
|
812
|
+
Corners are shader-true (rounded-box SDF with a 1px AA edge) and the cover
|
|
813
|
+
crop runs in-shader (`object-fit: cover` maths), so any source aspect fills
|
|
814
|
+
any card aspect without letterboxing.
|
|
815
|
+
|
|
816
|
+
```html
|
|
817
|
+
<figure data-gl><img src="photo.jpg" alt="" /></figure>
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
```ts
|
|
821
|
+
import { webglMedia } from "@cosmictraveler002/anim-kit/three";
|
|
822
|
+
|
|
823
|
+
webglMedia("[data-gl]", { corner: 18, dent: 70, chroma: 2 });
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
| Option | Default | Notes |
|
|
827
|
+
| --- | --- | --- |
|
|
828
|
+
| `src` | first `<img>` | image source override when the wrapper has none |
|
|
829
|
+
| `corner` | `16` | corner radius, px (SDF, not `border-radius`) |
|
|
830
|
+
| `dent` | `70` | dent depth pressed into the card on hover, px |
|
|
831
|
+
| `chroma` | `2` | rgb split at full hover, px |
|
|
832
|
+
| `reveal` | `true` | wipe the card in bottom-up when its texture loads |
|
|
833
|
+
| `revealDuration` | `1.1` | reveal duration, seconds |
|
|
834
|
+
| `hoverDuration` | `0.6` | hover response duration, seconds |
|
|
835
|
+
| `dpr` | `2` | device-pixel-ratio cap |
|
|
836
|
+
| `force` | `false` | run even under `prefers-reduced-motion` |
|
|
837
|
+
|
|
838
|
+
**Silent no-op ladder.** Missing target → no-op; no `WebGLRenderingContext`
|
|
839
|
+
(SSR, jsdom, WebGL disabled) → no-op *before* any context probe (a probe
|
|
840
|
+
itself logs an error in jsdom); renderer refused → no-op; texture 404 → the
|
|
841
|
+
plain `<img>` stays visible. Nothing ever logs. Reduced motion → the canvas
|
|
842
|
+
never mounts; the static image is the resting state.
|
|
843
|
+
|
|
844
|
+
**DOM.** The wrapper gets inline `position: relative` if it was static (so
|
|
845
|
+
the canvas can cover it — restored on destroy) and keeps layout, sizing and
|
|
846
|
+
alt text in its `<img>`; the canvas takes over only after the texture has
|
|
847
|
+
loaded. `destroy()` kills ticker + tweens + resize observer and disposes
|
|
848
|
+
geometry, material, texture and renderer.
|
|
849
|
+
|
|
850
|
+
---
|
|
851
|
+
|
|
687
852
|
### Loops & marquees
|
|
688
853
|
|
|
689
|
-
Continuous motion — marquees,
|
|
854
|
+
Continuous motion — marquees, draggable rails, equaliser bars.
|
|
690
855
|
|
|
691
856
|
#### `marquee(track, options?) => destroy`
|
|
692
857
|
|
|
@@ -755,6 +920,50 @@ cursor/user-select/touch-action.
|
|
|
755
920
|
`touch-action: pan-y` inline and restores them on destroy); items keep
|
|
756
921
|
`transform-origin: 50% 100%` so they pivot from their base.
|
|
757
922
|
|
|
923
|
+
#### `dragRail(track, options?) => destroy`
|
|
924
|
+
|
|
925
|
+
Bounded drag rail with real physics — the finite counterpart to
|
|
926
|
+
`dragStrip()`'s infinite loop. Pointer, wheel and trackpad all feed one
|
|
927
|
+
*intent* value; a single ticker lerps the track toward it (`lerp` scaled by
|
|
928
|
+
GSAP's `deltaRatio`, so the settle rate is framerate-independent), past
|
|
929
|
+
either end the intent squashes through a tanh rubber-band
|
|
930
|
+
(`edge * tanh(overshoot / edge)` — pull further, gain less, never a hard
|
|
931
|
+
stop), and a release coasts on velocity sampled over the last few frames.
|
|
932
|
+
|
|
933
|
+
```ts
|
|
934
|
+
dragRail("[data-rail]", { tilt: 0.05, throwScale: 14, wheel: true });
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
| Option | Default | Notes |
|
|
938
|
+
| --- | --- | --- |
|
|
939
|
+
| `viewport` | track's parent | scroll viewport around the track |
|
|
940
|
+
| `item` | `":scope > *"` | children that tilt with velocity |
|
|
941
|
+
| `lerp` | `0.1` | follow speed toward intent, per 60fps frame (0..1) |
|
|
942
|
+
| `edge` | `140` | rubber-band resistance distance past the ends, px |
|
|
943
|
+
| `throwScale` | `14` | momentum multiplier on release |
|
|
944
|
+
| `wheel` | `true` | wheel/trackpad drives the rail too |
|
|
945
|
+
| `tilt` | `0` | degrees of tilt per px/frame of velocity (`0` = off) |
|
|
946
|
+
| `tiltMax` | `8` | tilt clamp, degrees |
|
|
947
|
+
| `onTick` | — | `(pos, velocity) => {}` every rendered frame |
|
|
948
|
+
| `force` | `false` | run even under `prefers-reduced-motion` |
|
|
949
|
+
|
|
950
|
+
**Single writer.** Pointer, wheel and release handlers only ever touch
|
|
951
|
+
*intent*; the ticker is the sole writer of the track transform — that
|
|
952
|
+
separation is what gives a hard throw its buttery settle (the v1 stutter
|
|
953
|
+
came from letting every input path write `x` directly).
|
|
954
|
+
|
|
955
|
+
**Wheel is Lenis-safe.** While the rail can still move in that direction the
|
|
956
|
+
event gets `preventDefault` **and** `stopPropagation` (Lenis listens above us
|
|
957
|
+
and would scroll the page in parallel). Once over-extended a full `edge`, the
|
|
958
|
+
wheel passes through to the page — the section never traps the reader. When
|
|
959
|
+
input stops, a restore force springs any overscrolled intent home while the
|
|
960
|
+
lerp chases it.
|
|
961
|
+
|
|
962
|
+
**DOM:** same shape as `dragStrip` — `overflow: hidden` viewport, flex track
|
|
963
|
+
of `width: max-content`; the effect sets inline `cursor` / `user-select` /
|
|
964
|
+
`touch-action` and clears them plus the transform/tilt on `destroy()`. Feed
|
|
965
|
+
`onTick(pos, velocity)` a shader or skew effect to drive along with the rail.
|
|
966
|
+
|
|
758
967
|
#### `audioBars(target, options?) => handle`
|
|
759
968
|
|
|
760
969
|
Equaliser visualiser — returns `{ start, stop, destroy }`.
|
|
@@ -774,7 +983,7 @@ would rewrite `height` *after* teardown) and clears the inline height.
|
|
|
774
983
|
|
|
775
984
|
### Buttons & links
|
|
776
985
|
|
|
777
|
-
Hover affordances for CTAs and inline links.
|
|
986
|
+
Hover affordances for CTAs and inline links — fills, underlines, magnetic pulls.
|
|
778
987
|
|
|
779
988
|
#### `liquidButton(target, options?) => destroy`
|
|
780
989
|
|
|
@@ -818,6 +1027,29 @@ Pure CSS under the hood — it just adds/removes the `.ak-underline` class whose
|
|
|
818
1027
|
`::after` sweep is styled by the companion stylesheet, and `destroy()` removes
|
|
819
1028
|
the class again.
|
|
820
1029
|
|
|
1030
|
+
#### `magnetic(target, options?) => destroy`
|
|
1031
|
+
|
|
1032
|
+
Buttons and links that lean toward the pointer while hovered — following a
|
|
1033
|
+
fraction of the pull with a tilt — then spring back to rest with an elastic
|
|
1034
|
+
snap on leave.
|
|
1035
|
+
|
|
1036
|
+
```ts
|
|
1037
|
+
magnetic("[data-magnet]", { strength: 0.5, rotation: 10, scale: 1.04 });
|
|
1038
|
+
```
|
|
1039
|
+
|
|
1040
|
+
| Option | Default | Notes |
|
|
1041
|
+
| --- | --- | --- |
|
|
1042
|
+
| `strength` | `0.4` | how far the element follows the pointer — fraction of its own box |
|
|
1043
|
+
| `rotation` | `8` | max tilt in degrees at full pull (`0` disables) |
|
|
1044
|
+
| `scale` | `1` | scale held while the pointer is over the element (`1` = none) |
|
|
1045
|
+
| `duration` | `1.2` | spring-back duration, seconds |
|
|
1046
|
+
| `ease` | `"elastic.out(1, 0.35)"` | spring-back ease |
|
|
1047
|
+
|
|
1048
|
+
**DOM:** pass a list (selector, array, NodeList) — each element gets its own
|
|
1049
|
+
listeners and its own pull. Keep CSS transitions off `transform` for magnetic
|
|
1050
|
+
elements (GSAP animates transform directly). `destroy()` removes the
|
|
1051
|
+
listeners, kills in-flight tweens and restores the inline transform.
|
|
1052
|
+
|
|
821
1053
|
---
|
|
822
1054
|
|
|
823
1055
|
### Navigation & overlays
|
|
@@ -861,7 +1093,12 @@ Curtain uses `EASES.curtain` (`.76,0,.24,1`).
|
|
|
861
1093
|
|
|
862
1094
|
#### `cursorFollower(zone, options?) => destroy`
|
|
863
1095
|
|
|
864
|
-
Spring-followed cursor tag, e.g. "▶ Play Showreel" over a video.
|
|
1096
|
+
Spring-followed cursor tag, e.g. "▶ Play Showreel" over a video. The tag is
|
|
1097
|
+
pinned `position: fixed` and driven in viewport space (`clientX/Y + offset`),
|
|
1098
|
+
so it can live anywhere in the DOM — it only reacts while the pointer is over
|
|
1099
|
+
the zone. Style it with `opacity: 0` and light text (`color: #fff`): the
|
|
1100
|
+
`exclusion` blend inverts the tag against whatever is behind it, which only
|
|
1101
|
+
reads with a light source colour.
|
|
865
1102
|
|
|
866
1103
|
```ts
|
|
867
1104
|
cursorFollower("[data-showreel]", {
|
|
@@ -1055,22 +1292,43 @@ destroy on teardown. Route changes and HMR are why `destroy()` exists.
|
|
|
1055
1292
|
```bash
|
|
1056
1293
|
npm run build # tsc → dist/ (ESM + .d.ts) + CSS copy + tsup standalone bundle
|
|
1057
1294
|
npm run demo # static server on http://localhost:4321/demo/
|
|
1295
|
+
npm run sync:live # regenerate demo_live/ (static deploy) from demo/
|
|
1058
1296
|
npm test # build + unit smoke + demo integration smoke
|
|
1059
1297
|
npm run smoke # both smokes (expects dist/ to exist)
|
|
1060
1298
|
npm run typecheck # tsc --noEmit (what CI runs)
|
|
1061
1299
|
```
|
|
1062
1300
|
|
|
1063
1301
|
The demo page wires the whole effect set against one document —
|
|
1064
|
-
`demo/index.html` + `demo/demo.js
|
|
1065
|
-
|
|
1066
|
-
instead.)
|
|
1302
|
+
`demo/index.html` + `demo/demo.js` — and every effect in the catalogue has
|
|
1303
|
+
an on-page *copy prompt* chip (the demo smoke enforces the two-way mapping).
|
|
1304
|
+
(`split()` is exercised by the unit smoke instead.)
|
|
1305
|
+
|
|
1306
|
+
Two trees share that wiring:
|
|
1307
|
+
|
|
1308
|
+
- **`demo/`** (default — `npm run demo` → `/demo/`) — import map on local
|
|
1309
|
+
files (`/node_modules/...`, `/dist/index.js`): fast iteration, works
|
|
1310
|
+
offline, and the pages `demo-smoke` drives.
|
|
1311
|
+
- **`demo_live/`** (`npm run sync:live` regenerates it from `demo/`) — a
|
|
1312
|
+
deploy-anywhere copy: the **version-pinned CDN** import map and stylesheet
|
|
1313
|
+
(jsdelivr `@<version>` for the package — the `/three` subpath included —
|
|
1314
|
+
plus `three@0.186.1`, `gsap@3.15.0`, `lenis@1.3.26`: the exact URLs the
|
|
1315
|
+
prompts teach), a byte-copied `assets/` folder, relative internal links, a
|
|
1316
|
+
generated
|
|
1317
|
+
`prompts.json`, and agent crawler files (`robots.txt`, `sitemap.xml`,
|
|
1318
|
+
`llms.txt` with absolute URLs from `SITE_URL`), so the dock and docs run on
|
|
1319
|
+
any static host with no Node server (`/api/prompts` first, `prompts.json`
|
|
1320
|
+
fallback) and agents reach the full catalogue in one fetch instead of
|
|
1321
|
+
scraping HTML. Upload the folder as-is (Vercel, GitHub Pages, Netlify, S3).
|
|
1322
|
+
`demo-smoke` re-runs the generator and fails if the folder goes stale or a
|
|
1323
|
+
pin stops matching the release version.
|
|
1067
1324
|
|
|
1068
1325
|
### Copy-prompt API
|
|
1069
1326
|
|
|
1070
1327
|
The demo server doubles as a **prompt server**: every effect has a ready-to-
|
|
1071
|
-
paste *"how to implement this with anim-kit"* prompt — markup,
|
|
1072
|
-
|
|
1073
|
-
|
|
1328
|
+
paste *"how to implement this with anim-kit"* prompt — markup, a numbered
|
|
1329
|
+
copy-paste procedure that runs straight from the version-pinned CDN (no
|
|
1330
|
+
build step, plus the npm import for bundlers), initialisation call, options
|
|
1331
|
+
table, teardown and gotchas:
|
|
1074
1332
|
|
|
1075
1333
|
```bash
|
|
1076
1334
|
curl http://localhost:4321/api/prompts # { count, categories, prompts: [{ id, title, summary, category, subcategory, text }] }
|
|
@@ -1078,7 +1336,7 @@ curl http://localhost:4321/api/prompts/marquee # one prompt, text/plain
|
|
|
1078
1336
|
```
|
|
1079
1337
|
|
|
1080
1338
|
On the page, every labelled section carries a **copy prompt** chip, and the
|
|
1081
|
-
floating **⧉ prompts (
|
|
1339
|
+
floating **⧉ prompts (29)** button at the bottom right opens the full
|
|
1082
1340
|
catalogue grouped by [effect category](#effect-categories) — one click copies
|
|
1083
1341
|
an effect's prompt (the prompt states its category), *copy all* puts the
|
|
1084
1342
|
entire set on the clipboard. The catalogue lives in `scripts/prompts.mjs`:
|
|
@@ -1089,11 +1347,11 @@ and slotting the effect into a subcategory.
|
|
|
1089
1347
|
**Unit smoke** (`scripts/smoke.mjs`) runs the built bundle in **jsdom** and
|
|
1090
1348
|
asserts:
|
|
1091
1349
|
|
|
1092
|
-
1. all
|
|
1350
|
+
1. all 47 exports are present;
|
|
1093
1351
|
2. plugins (`ScrollTrigger`, `SplitText`, `Draggable`, `CustomEase`,
|
|
1094
|
-
`ScrollSmoother`) and the 4 custom eases are registered;
|
|
1352
|
+
`Flip`, `ScrollSmoother`) and the 4 custom eases are registered;
|
|
1095
1353
|
3. every effect no-ops safely on missing targets;
|
|
1096
|
-
4.
|
|
1354
|
+
4. 24 effects mount on real markup and unmount cleanly;
|
|
1097
1355
|
5. `preloader` ticks in both the positional and options-object call forms;
|
|
1098
1356
|
6. `lineReveal` actually splits into masked lines (and per-character masks
|
|
1099
1357
|
with `split: "chars"`) and restores markup on destroy;
|
|
@@ -1132,9 +1390,9 @@ anim-kit/
|
|
|
1132
1390
|
│ │ ├─ guard.ts reduced-motion gate
|
|
1133
1391
|
│ │ ├─ util.ts toArray/one/onReady/compose/raf
|
|
1134
1392
|
│ │ └─ types.ts TargetLike / Destroy / CommonOptions
|
|
1135
|
-
│ ├─ effects/ one file per effect (
|
|
1393
|
+
│ ├─ effects/ one file per effect (24 files, 28 effect functions)
|
|
1136
1394
|
│ ├─ styles/anim-kit.css companion stylesheet
|
|
1137
|
-
│ └─ index.ts barrel —
|
|
1395
|
+
│ └─ index.ts barrel — 47 exports
|
|
1138
1396
|
├─ demo/ visual demo (import map, no bundler)
|
|
1139
1397
|
├─ scripts/
|
|
1140
1398
|
│ ├─ serve.mjs static server + /api/prompts (:4321)
|
|
@@ -1165,8 +1423,7 @@ Each effect is an independent module — if you only need the marquee, import
|
|
|
1165
1423
|
|
|
1166
1424
|
## Credits
|
|
1167
1425
|
|
|
1168
|
-
Effects
|
|
1169
|
-
[dzinrstudio.com](https://dzinrstudio.com/). Built on
|
|
1426
|
+
Effects by [kalakritico.in](https://kalakritico.in/). Built on
|
|
1170
1427
|
[GSAP](https://gsap.com/) (free plugins only) and
|
|
1171
1428
|
[Lenis](https://lenis.darkroom.engineering/).
|
|
1172
1429
|
|