@cosmictraveler002/anim-kit 1.0.0 → 1.2.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.
Files changed (69) hide show
  1. package/README.md +275 -50
  2. package/dist/anim-kit.standalone.js +18 -8
  3. package/dist/anim-kit.standalone.js.map +1 -1
  4. package/dist/core/gsap.d.ts +2 -1
  5. package/dist/core/gsap.d.ts.map +1 -1
  6. package/dist/core/gsap.js +3 -2
  7. package/dist/core/gsap.js.map +1 -1
  8. package/dist/core/split.d.ts.map +1 -1
  9. package/dist/core/split.js +15 -2
  10. package/dist/core/split.js.map +1 -1
  11. package/dist/effects/clip-wipe.d.ts +31 -0
  12. package/dist/effects/clip-wipe.d.ts.map +1 -0
  13. package/dist/effects/clip-wipe.js +82 -0
  14. package/dist/effects/clip-wipe.js.map +1 -0
  15. package/dist/effects/counter.d.ts +9 -0
  16. package/dist/effects/counter.d.ts.map +1 -1
  17. package/dist/effects/counter.js +17 -7
  18. package/dist/effects/counter.js.map +1 -1
  19. package/dist/effects/cursor-follower.d.ts.map +1 -1
  20. package/dist/effects/cursor-follower.js +22 -4
  21. package/dist/effects/cursor-follower.js.map +1 -1
  22. package/dist/effects/flip-words.d.ts +31 -0
  23. package/dist/effects/flip-words.d.ts.map +1 -0
  24. package/dist/effects/flip-words.js +104 -0
  25. package/dist/effects/flip-words.js.map +1 -0
  26. package/dist/effects/line-reveal.d.ts +6 -2
  27. package/dist/effects/line-reveal.d.ts.map +1 -1
  28. package/dist/effects/line-reveal.js +8 -4
  29. package/dist/effects/line-reveal.js.map +1 -1
  30. package/dist/effects/magnetic.d.ts +15 -0
  31. package/dist/effects/magnetic.d.ts.map +1 -0
  32. package/dist/effects/magnetic.js +59 -0
  33. package/dist/effects/magnetic.js.map +1 -0
  34. package/dist/effects/media-settle.d.ts +30 -0
  35. package/dist/effects/media-settle.d.ts.map +1 -0
  36. package/dist/effects/media-settle.js +55 -0
  37. package/dist/effects/media-settle.js.map +1 -0
  38. package/dist/effects/roll-text.d.ts +13 -0
  39. package/dist/effects/roll-text.d.ts.map +1 -0
  40. package/dist/effects/roll-text.js +91 -0
  41. package/dist/effects/roll-text.js.map +1 -0
  42. package/dist/effects/scramble-text.d.ts +17 -0
  43. package/dist/effects/scramble-text.d.ts.map +1 -0
  44. package/dist/effects/scramble-text.js +93 -0
  45. package/dist/effects/scramble-text.js.map +1 -0
  46. package/dist/effects/unfold-reveal.d.ts +23 -0
  47. package/dist/effects/unfold-reveal.d.ts.map +1 -0
  48. package/dist/effects/unfold-reveal.js +53 -0
  49. package/dist/effects/unfold-reveal.js.map +1 -0
  50. package/dist/index.d.ts +25 -2
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +19 -2
  53. package/dist/index.js.map +1 -1
  54. package/dist/styles/anim-kit.css +22 -0
  55. package/package.json +6 -3
  56. package/src/core/gsap.ts +3 -2
  57. package/src/core/split.ts +16 -2
  58. package/src/effects/clip-wipe.ts +141 -0
  59. package/src/effects/counter.ts +26 -6
  60. package/src/effects/cursor-follower.ts +21 -4
  61. package/src/effects/flip-words.ts +148 -0
  62. package/src/effects/line-reveal.ts +12 -5
  63. package/src/effects/magnetic.ts +87 -0
  64. package/src/effects/media-settle.ts +106 -0
  65. package/src/effects/roll-text.ts +118 -0
  66. package/src/effects/scramble-text.ts +122 -0
  67. package/src/effects/unfold-reveal.ts +92 -0
  68. package/src/index.ts +33 -2
  69. package/src/styles/anim-kit.css +22 -0
package/README.md CHANGED
@@ -9,7 +9,7 @@ no components: every effect resolves plain DOM selectors, so it works with
9
9
  **React, Vue, Next, Svelte, Astro or plain HTML**.
10
10
 
11
11
  ```ts
12
- import { lineReveal, marquee, menuOverlay } from "anim-kit";
12
+ import { lineReveal, marquee, menuOverlay } from "@cosmictraveler002/anim-kit";
13
13
 
14
14
  const destroy = lineReveal("[data-lines]", { mode: "scroll" });
15
15
  // …later (route change, HMR, teardown):
@@ -48,12 +48,12 @@ destroy();
48
48
  ## Install
49
49
 
50
50
  ```bash
51
- npm install anim-kit
51
+ npm install @cosmictraveler002/anim-kit
52
52
  ```
53
53
 
54
54
  ```ts
55
- import { smoothScroll, horizontalScroll } from "anim-kit";
56
- import "anim-kit/styles"; // companion stylesheet (plain .css, optional but recommended)
55
+ import { smoothScroll, horizontalScroll } from "@cosmictraveler002/anim-kit";
56
+ import "@cosmictraveler002/anim-kit/styles"; // companion stylesheet (plain .css, optional but recommended)
57
57
  ```
58
58
 
59
59
  `anim-kit` ships **ESM only** with generated `.d.ts` declarations — no CJS
@@ -78,14 +78,14 @@ inside the published tarball):
78
78
 
79
79
  | Specifier | Resolves to | Use for |
80
80
  |---|---|---|
81
- | `anim-kit` | `dist/index.js` + `dist/index.d.ts` | the full barrel — 38 exports |
82
- | `anim-kit/effects/<name>` | `dist/effects/<name>.js` + `.d.ts` | one effect in isolation (`marquee`, `lineReveal`, …) |
83
- | `anim-kit/standalone` | `dist/anim-kit.standalone.js` (types → `index.d.ts`) | the self-contained bundle — same API |
84
- | `anim-kit/styles` | `dist/styles/anim-kit.css` | untouched plain CSS |
81
+ | `@cosmictraveler002/anim-kit` | `dist/index.js` + `dist/index.d.ts` | the full barrel — 45 exports |
82
+ | `@cosmictraveler002/anim-kit/effects/<name>` | `dist/effects/<name>.js` + `.d.ts` | one effect in isolation (`marquee`, `lineReveal`, …) |
83
+ | `@cosmictraveler002/anim-kit/standalone` | `dist/anim-kit.standalone.js` (types → `index.d.ts`) | the self-contained bundle — same API |
84
+ | `@cosmictraveler002/anim-kit/styles` | `dist/styles/anim-kit.css` | untouched plain CSS |
85
85
 
86
86
  ```ts
87
- import { marquee } from "anim-kit/effects/marquee"; // deep import, no barrel
88
- import "anim-kit/styles";
87
+ import { marquee } from "@cosmictraveler002/anim-kit/effects/marquee"; // deep import, no barrel
88
+ import "@cosmictraveler002/anim-kit/styles";
89
89
  ```
90
90
 
91
91
  TypeScript ≥ 4.7 with `moduleResolution: "bundler"` or `"node16"`/`"nodenext"`
@@ -97,7 +97,7 @@ resolves declarations through the same map — no `typesVersions` shim needed.
97
97
 
98
98
  No build step on the consumer's end: `dist/` is served as-is from the npm
99
99
  tarball by any npm CDN. Every URL is **version-pinned** — npm versions are
100
- immutable, so `anim-kit@1.0.0` always resolves to exactly that build, forever
100
+ immutable, so `@cosmictraveler002/anim-kit@1.2.0` always resolves to exactly that build, forever
101
101
  (only a new version creates a new URL; nothing floats unless you ask for a
102
102
  range).
103
103
 
@@ -108,12 +108,12 @@ plugins anim-kit uses) and `lenis` **inlined** — no import map, one URL, works
108
108
  identically on jsDelivr and unpkg:
109
109
 
110
110
  ```html
111
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/anim-kit@1.0.0/dist/styles/anim-kit.css" />
111
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.2.0/dist/styles/anim-kit.css" />
112
112
 
113
113
  <script type="module">
114
114
  import {
115
115
  smoothScroll, lineReveal, marquee,
116
- } from "https://cdn.jsdelivr.net/npm/anim-kit@1.0.0/dist/anim-kit.standalone.js";
116
+ } from "https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.2.0/dist/anim-kit.standalone.js";
117
117
 
118
118
  smoothScroll();
119
119
  lineReveal("[data-lines]", { mode: "scroll" });
@@ -121,7 +121,7 @@ identically on jsDelivr and unpkg:
121
121
  </script>
122
122
  ```
123
123
 
124
- unpkg serves the same file: `https://unpkg.com/anim-kit@1.0.0/dist/anim-kit.standalone.js`
124
+ unpkg serves the same file: `https://unpkg.com/@cosmictraveler002/anim-kit@1.2.0/dist/anim-kit.standalone.js`
125
125
 
126
126
  ### Option 2 — jsDelivr `+esm`
127
127
 
@@ -130,7 +130,7 @@ per version):
130
130
 
131
131
  ```html
132
132
  <script type="module">
133
- import { lineReveal } from "https://cdn.jsdelivr.net/npm/anim-kit@1.0.0/+esm";
133
+ import { lineReveal } from "https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.2.0/+esm";
134
134
  </script>
135
135
  ```
136
136
 
@@ -142,17 +142,18 @@ locally, with CDN URLs — and the way to share one GSAP between anim-kit and
142
142
  the rest of your page:
143
143
 
144
144
  ```html
145
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/anim-kit@1.0.0/dist/styles/anim-kit.css" />
145
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.2.0/dist/styles/anim-kit.css" />
146
146
 
147
147
  <script type="importmap">
148
148
  {
149
149
  "imports": {
150
- "anim-kit": "https://cdn.jsdelivr.net/npm/anim-kit@1.0.0/dist/index.js",
150
+ "@cosmictraveler002/anim-kit": "https://cdn.jsdelivr.net/npm/@cosmictraveler002/anim-kit@1.2.0/dist/index.js",
151
151
  "gsap": "https://cdn.jsdelivr.net/npm/gsap@3.15.0/index.js",
152
152
  "gsap/ScrollTrigger": "https://cdn.jsdelivr.net/npm/gsap@3.15.0/ScrollTrigger.js",
153
153
  "gsap/SplitText": "https://cdn.jsdelivr.net/npm/gsap@3.15.0/SplitText.js",
154
154
  "gsap/Draggable": "https://cdn.jsdelivr.net/npm/gsap@3.15.0/Draggable.js",
155
155
  "gsap/CustomEase": "https://cdn.jsdelivr.net/npm/gsap@3.15.0/CustomEase.js",
156
+ "gsap/Flip": "https://cdn.jsdelivr.net/npm/gsap@3.15.0/Flip.js",
156
157
  "gsap/ScrollSmoother": "https://cdn.jsdelivr.net/npm/gsap@3.15.0/ScrollSmoother.js",
157
158
  "lenis": "https://cdn.jsdelivr.net/npm/lenis@1.3.26/dist/lenis.mjs"
158
159
  }
@@ -160,13 +161,13 @@ the rest of your page:
160
161
  </script>
161
162
 
162
163
  <script type="module">
163
- import { smoothScroll, lineReveal } from "anim-kit";
164
+ import { smoothScroll, lineReveal } from "@cosmictraveler002/anim-kit";
164
165
  // per-effect deep imports work here too:
165
- // import { dragStrip } from "anim-kit/effects/dragStrip";
166
+ // import { dragStrip } from "@cosmictraveler002/anim-kit/effects/dragStrip";
166
167
  </script>
167
168
  ```
168
169
 
169
- Swap the host for unpkg (`https://unpkg.com/anim-kit@1.0.0/dist/index.js`, …) —
170
+ Swap the host for unpkg (`https://unpkg.com/@cosmictraveler002/anim-kit@1.2.0/dist/index.js`, …) —
170
171
  the file layout is identical. GSAP subpaths are listed one by one because
171
172
  import maps match specifiers literally: a trailing-slash prefix map would
172
173
  produce extension-less URLs, which CDNs don't serve. The `gsap`/`lenis` pins
@@ -188,7 +189,7 @@ match `package-lock.json`.
188
189
  ```
189
190
 
190
191
  ```js
191
- import { smoothScroll, lineReveal, marquee, compose } from "anim-kit";
192
+ import { smoothScroll, lineReveal, marquee, compose } from "@cosmictraveler002/anim-kit";
192
193
 
193
194
  const scroller = smoothScroll({ lerp: 0.08, smoothWheel: true });
194
195
 
@@ -278,16 +279,20 @@ prompt dock, and backs the `category` / `subcategory` fields on
278
279
  | Core & setup | Smooth scrolling | `smoothScroll` |
279
280
  | Text animations | Line & mask reveals | `lineReveal`, `maskReveal` |
280
281
  | Text animations | Per-character scatter | `scatterText` |
282
+ | Text animations | Decode & scramble | `scrambleText` |
283
+ | Text animations | Rolling text | `rollText` |
281
284
  | Text animations | Counters | `counter` |
285
+ | Text animations | Layout transfers | `flipWords` |
282
286
  | Scroll & media | Pinned galleries | `horizontalScroll`, `stackedCards`, `stackedCardsPinned` |
283
287
  | Scroll & media | Parallax & depth | `parallax` |
284
- | Scroll & media | Heroes & media | `heroShrink` |
285
- | Scroll & media | Enter reveals | `revealRule` |
288
+ | Scroll & media | Heroes & media | `heroShrink`, `mediaSettle` |
289
+ | Scroll & media | Enter reveals | `revealRule`, `unfoldReveal`, `clipWipe` |
286
290
  | Loops & marquees | Marquees | `marquee` |
287
291
  | Loops & marquees | Infinite draggables | `dragStrip` |
288
292
  | Loops & marquees | Equalizers | `audioBars` |
289
293
  | Buttons & links | Liquid fills | `liquidButton` |
290
294
  | Buttons & links | Underlines | `underlineLink` |
295
+ | Buttons & links | Magnetic hover | `magnetic` |
291
296
  | Navigation & overlays | Menus & nav | `navHide`, `menuOverlay` |
292
297
  | Navigation & overlays | Cursors | `cursorFollower` |
293
298
  | Intros & transitions | Preloaders | `preloader` |
@@ -308,7 +313,7 @@ that does not exist.
308
313
  #### `initGSAP()`
309
314
 
310
315
  Registers the plugins anim-kit relies on (`ScrollTrigger`, `SplitText`,
311
- `Draggable`, `CustomEase`, `ScrollSmoother`) and the studio's custom eases.
316
+ `Draggable`, `CustomEase`, `Flip`, `ScrollSmoother`) and the studio's custom eases.
312
317
  Idempotent; called for you by every effect.
313
318
 
314
319
  #### `EASES`
@@ -346,7 +351,8 @@ Central reduced-motion gate. If the user prefers reduced motion and
346
351
 
347
352
  ### Text animations
348
353
 
349
- Typography in motion — masked lines, rising masks, per-character scatter, tickers.
354
+ Typography in motion — masked lines, rising masks, per-character scatter,
355
+ decode reveals, rolling words, tickers, layout transfers.
350
356
 
351
357
  #### `lineReveal(target, options?) => destroy`
352
358
 
@@ -356,12 +362,14 @@ source site.
356
362
  ```ts
357
363
  lineReveal("[data-hero-text]", { mode: "immediate", delay: 0.35 }); // above the fold
358
364
  lineReveal("[data-lines]", { mode: "scroll" }); // reverses on leave
365
+ lineReveal("[data-headline]", { split: "chars", stagger: 0.03 }); // per-character rise
359
366
  ```
360
367
 
361
368
  | Option | Default | Notes |
362
369
  | --------- | --------------- | --------------------------------------- |
363
370
  | `mode` | `"scroll"` | `"scroll"` or `"immediate"` |
364
- | `stagger` | `0.1` | seconds between lines |
371
+ | `split` | `"lines"` | `"lines"` or `"chars"` (per-character masked rise) |
372
+ | `stagger` | `0.1` | seconds between lines (`0.03` for chars) |
365
373
  | `duration`| `1` | seconds |
366
374
  | `ease` | `"power4.out"` | |
367
375
  | `delay` | `0` | seconds |
@@ -369,7 +377,8 @@ lineReveal("[data-lines]", { mode: "scroll" }); // reverses
369
377
  | `end` | `"bottom 10%"` | ScrollTrigger end |
370
378
 
371
379
  **DOM:** any block of text — headings with `<br>` hard breaks work. Produces
372
- `.ak-line-mask > .ak-line` per line; `destroy()` restores the original HTML.
380
+ `.ak-line-mask > .ak-line` per line (or `.ak-char-mask > .ak-char` with
381
+ `split: "chars"`); `destroy()` restores the original HTML.
373
382
 
374
383
  #### `maskReveal(target, options?) => destroy`
375
384
 
@@ -420,6 +429,91 @@ scatterText("[data-scatter-pin]", {
420
429
  `destroy()`). Line measurement waits for `document.fonts.ready` so travel
421
430
  distance is correct with webfonts.
422
431
 
432
+ #### `scrambleText(target, options?) => destroy`
433
+
434
+ The decode / cipher reveal: each character churns through the charset and
435
+ settles on its final glyph, left to right. Letters scramble; digits,
436
+ punctuation and spaces stay put; case is preserved.
437
+
438
+ ```ts
439
+ scrambleText("[data-scramble]"); // once, on enter
440
+ scrambleText("[data-headline]", { mode: "immediate", delay: 0.2 }); // right away
441
+ scrambleText(".nav-link", { mode: "hover", durationPerChar: 0.12 }); // on hover
442
+ ```
443
+
444
+ | Option | Default | Notes |
445
+ | --- | --- | --- |
446
+ | `mode` | `"scroll"` | `"scroll"` (once on enter), `"immediate"`, or `"hover"` (re-scrambles on pointerenter) |
447
+ | `charset` | `"abcdefghijklmnopqrstuvwxyz"` | glyphs letters churn through |
448
+ | `durationPerChar` | `0.18` | seconds each character scrambles |
449
+ | `stagger` | `0.04` | seconds between character starts |
450
+ | `delay` | `0` | seconds before the timeline starts |
451
+ | `start` | `"top 80%"` | ScrollTrigger start (`mode: "scroll"`) |
452
+
453
+ **DOM:** plain-text elements only — the effect rewrites `textContent` while
454
+ scrambling and restores the original exactly on `destroy()`.
455
+
456
+ #### `rollText(target, options?) => destroy`
457
+
458
+ The rolling word rotator: two or more rows stacked into a hidden overflow box
459
+ one row tall, rolling to the next on an interval. The first row is cloned at
460
+ the end so the wrap is seamless (same trick as `marquee()`).
461
+
462
+ ```html
463
+ <span class="ak-roll" data-roll>
464
+ <span>Design</span><span>Build</span><span>Motion</span>
465
+ </span>
466
+ ```
467
+
468
+ ```ts
469
+ rollText("[data-roll]", { interval: 2.2, duration: 0.6 });
470
+ rollText("[data-roll-rev]", { direction: "down" }); // walk rows in reverse
471
+ ```
472
+
473
+ | Option | Default | Notes |
474
+ | --- | --- | --- |
475
+ | `interval` | `2.2` | seconds each row is shown (including the roll) |
476
+ | `duration` | `0.6` | roll duration, seconds |
477
+ | `ease` | `"power4.inOut"` | GSAP ease for the roll |
478
+ | `direction` | `"up"` | `"up"` or `"down"` |
479
+
480
+ **DOM:** rows are direct children of the target; the effect stacks them as
481
+ blocks itself. `destroy()` unwraps the rows, removes the clone and restores
482
+ every inline style — markup comes back byte-identical.
483
+
484
+ #### `flipWords(target, { to }, options?) => destroy`
485
+
486
+ Words measured in one layout, moved into another and animated from where
487
+ they stood — the FLIP technique: a column of words fans out into a row,
488
+ driven by scroll progress or played once.
489
+
490
+ ```ts
491
+ // Both blocks share one grid cell so the stage never reflows mid-flight:
492
+ flipWords("[data-flip-from]", { to: "[data-flip-to]", scrub: 0.6 });
493
+
494
+ // One-shot on enter (reverses on leave-back), or immediately:
495
+ flipWords("[data-flip-from]", { to: "[data-flip-to]", mode: "scroll" });
496
+ ```
497
+
498
+ | Option | Default | Notes |
499
+ | --- | --- | --- |
500
+ | `to` | — (required) | destination block; every word is moved into it |
501
+ | `words` | source children | `[data-flip-word]` matches, else the source's element children |
502
+ | `duration` | `1.4` | seconds for one word's travel |
503
+ | `ease` | `"power4.inOut"` | GSAP ease |
504
+ | `stagger` | `0.2` | seconds between word starts |
505
+ | `scale` | `0.2` | mid-flight squash each word pops through (`0` disables) |
506
+ | `mode` | `"scroll"` | or `"immediate"` to play at once |
507
+ | `scrub` | unset | number = scrub smoothing seconds, `true` = immediate |
508
+ | `start / end` | `"top 75%"` / `"bottom 45%"` | ScrollTrigger positions |
509
+
510
+ **DOM:** put the source and destination blocks in the same grid cell
511
+ (`grid-area: 1 / 1`, a `min-height` on both keeps the stage stable). The words
512
+ live in the destination from init but render at their source positions until
513
+ the timeline runs, so scrubbing reverses perfectly. `destroy()` kills the
514
+ timeline, puts every word back in its original parent and restores the
515
+ inline transform.
516
+
423
517
  #### `counter(target, options?) => destroy`
424
518
 
425
519
  Tabular number ticker.
@@ -428,6 +522,7 @@ Tabular number ticker.
428
522
  counter("[data-count]", { to: 240, duration: 3, suffix: "+" });
429
523
  counter("[data-count-scroll]", { to: 98, onScroll: true }); // waits for view
430
524
  counter("[data-count-pad]", { to: 42, pad: 3 }); // 000 → 042
525
+ counter("[data-progress]", { progress: true, pad: 2, suffix: "%" }); // scrubs 00% → 100%
431
526
  ```
432
527
 
433
528
  | Option | Default |
@@ -438,7 +533,8 @@ counter("[data-count-pad]", { to: 42, pad: 3 }); // 000 →
438
533
  | `pad` | `0` (none) |
439
534
  | `suffix` | `""` |
440
535
  | `onScroll` | `false` |
441
- | `start` | ScrollTrigger start when `onScroll` |
536
+ | `progress` | `false` — scrub the value from scroll progress instead of a timed tween |
537
+ | `start` / `end` | `"top 90%"` / `"bottom top"` — ScrollTrigger positions (`end` with `progress`) |
442
538
  | `onComplete` | `(value) => {}` |
443
539
 
444
540
  ---
@@ -457,6 +553,61 @@ revealRule("[data-rule]", { duration: 1, delay: 0.2 }); // default ease: EASES.r
457
553
 
458
554
  **DOM:** any element that should animate `width: 0 → 100%` when it enters.
459
555
 
556
+ #### `unfoldReveal(target, options?) => destroy`
557
+
558
+ Blocks that grow open from an edge: `scaleY: 0 → 1` from the top (or bottom)
559
+ for a vertical unfold, `scaleX: 0 → 1` from the left for a horizontal one —
560
+ targets stagger together off the first match's trigger.
561
+
562
+ ```ts
563
+ unfoldReveal("[data-unfold]", { axis: "y", origin: "top" });
564
+ unfoldReveal("[data-unfold-x]", { axis: "x", origin: "left", duration: 1.2 });
565
+ ```
566
+
567
+ | Option | Default | Notes |
568
+ | --- | --- | --- |
569
+ | `axis` | `"y"` | `"y"` → `scaleY`, `"x"` → `scaleX` |
570
+ | `origin` | `"top"` / `"left"` | `transformOrigin`, defaults per axis |
571
+ | `duration` | `0.7` | seconds |
572
+ | `ease` | `"power3.out"` | GSAP ease |
573
+ | `stagger` | `0.08` | seconds between targets |
574
+ | `delay` | `0` | seconds |
575
+ | `mode` | `"scroll"` | or `"immediate"` to play at once |
576
+ | `start` | `"top 85%"` | ScrollTrigger start |
577
+ | `replay` | `false` | re-unfold when leaving / re-entering |
578
+
579
+ `destroy()` clears `transform` + `transform-origin`, so elements rest exactly
580
+ as authored.
581
+
582
+ #### `clipWipe(target, options?) => destroy`
583
+
584
+ A `clip-path: inset()` reveal: the element is collapsed behind one edge,
585
+ corner (or inside a frame margin) and the inset animates to zero so it wipes
586
+ into view.
587
+
588
+ ```ts
589
+ clipWipe("[data-clip]", { from: "left" }); // inset(0 100% 0 0) → 0
590
+ clipWipe("[data-frame]", { from: "frame", inset: 12 }); // opens out of a frame
591
+ clipWipe("[data-corner]", { from: "bottom-right" }); // opens toward the opposite corner
592
+ clipWipe("[data-scrub]", { from: "frame", scrub: 0.5 }); // bound to scroll progress
593
+ ```
594
+
595
+ | Option | Default | Notes |
596
+ | --- | --- | --- |
597
+ | `from` | `"left"` | `"left"` / `"right"` / `"top"` / `"bottom"`, a corner (`"top-left"` / `"top-right"` / `"bottom-left"` / `"bottom-right"`), or `"frame"` |
598
+ | `inset` | `15` | frame margin in % (`from: "frame"`) |
599
+ | `duration` | `1` | seconds |
600
+ | `ease` | `"power3.out"` | GSAP ease |
601
+ | `stagger` | `0.08` | seconds between targets |
602
+ | `mode` | `"scroll"` | or `"immediate"` to play at once |
603
+ | `start` | `"top 85%"` | ScrollTrigger start |
604
+ | `end` | `"top 20%"` | ScrollTrigger end (scrub mode) |
605
+ | `scrub` | unset | number = scrub smoothing seconds, `true` = immediate: tie the wipe to scroll progress |
606
+ | `replay` | `false` | re-wipe when leaving / re-entering |
607
+
608
+ Works on images, video, blocks and text. `destroy()` removes the inline
609
+ `clip-path`, restoring the authored (visible) state.
610
+
460
611
  #### `parallax(target, options?) => destroy`
461
612
 
462
613
  `data-speed` parallax over everything inside `target`.
@@ -546,6 +697,34 @@ heroShrink("[data-hero-media]", { offsetY: "49vh", scale: 0.23, scrub: 1 });
546
697
  // options: offsetX "0px", start "top top", end "bottom top"
547
698
  ```
548
699
 
700
+ #### `mediaSettle(target, options?) => destroy`
701
+
702
+ Images and video that arrive slightly oversized and ease down to size as the
703
+ section enters — content lands instead of popping in. Set `scrub` to bind the
704
+ settle to scroll progress, or `replay: true` to reverse back to `from` on
705
+ leave-back and replay on every re-enter.
706
+
707
+ ```ts
708
+ mediaSettle("[data-settle]", { from: 1.15, duration: 1.5 }); // on enter
709
+ mediaSettle("[data-settle-scrub]", { scrub: 0.5, from: 1.2 }); // scroll-bound
710
+ ```
711
+
712
+ | Option | Default | Notes |
713
+ | --- | --- | --- |
714
+ | `from` | `1.15` | starting scale — settles down to 1 |
715
+ | `duration` | `1.5` | seconds (enter mode) |
716
+ | `ease` | `"power2.out"` | GSAP ease (enter mode) |
717
+ | `origin` | `"center"` | `transformOrigin` |
718
+ | `stagger` | `0.06` | seconds between targets |
719
+ | `mode` | `"scroll"` | or `"immediate"` to play at once |
720
+ | `start` / `end` | `"top 75%"` / `"bottom top"` | ScrollTrigger positions |
721
+ | `scrub` | unset | number = scrub smoothing seconds, `true` = immediate |
722
+ | `replay` | `false` | re-settle when leaving / re-entering (enter mode) |
723
+
724
+ `destroy()` clears `transform` — media returns to its authored scale. Unlike
725
+ `heroShrink()` (which scrubs media down as it *leaves*), `mediaSettle()` plays
726
+ the entrance.
727
+
549
728
  ---
550
729
 
551
730
  ### Loops & marquees
@@ -638,7 +817,7 @@ would rewrite `height` *after* teardown) and clears the inline height.
638
817
 
639
818
  ### Buttons & links
640
819
 
641
- Hover affordances for CTAs and inline links.
820
+ Hover affordances for CTAs and inline links — fills, underlines, magnetic pulls.
642
821
 
643
822
  #### `liquidButton(target, options?) => destroy`
644
823
 
@@ -682,6 +861,29 @@ Pure CSS under the hood — it just adds/removes the `.ak-underline` class whose
682
861
  `::after` sweep is styled by the companion stylesheet, and `destroy()` removes
683
862
  the class again.
684
863
 
864
+ #### `magnetic(target, options?) => destroy`
865
+
866
+ Buttons and links that lean toward the pointer while hovered — following a
867
+ fraction of the pull with a tilt — then spring back to rest with an elastic
868
+ snap on leave.
869
+
870
+ ```ts
871
+ magnetic("[data-magnet]", { strength: 0.5, rotation: 10, scale: 1.04 });
872
+ ```
873
+
874
+ | Option | Default | Notes |
875
+ | --- | --- | --- |
876
+ | `strength` | `0.4` | how far the element follows the pointer — fraction of its own box |
877
+ | `rotation` | `8` | max tilt in degrees at full pull (`0` disables) |
878
+ | `scale` | `1` | scale held while the pointer is over the element (`1` = none) |
879
+ | `duration` | `1.2` | spring-back duration, seconds |
880
+ | `ease` | `"elastic.out(1, 0.35)"` | spring-back ease |
881
+
882
+ **DOM:** pass a list (selector, array, NodeList) — each element gets its own
883
+ listeners and its own pull. Keep CSS transitions off `transform` for magnetic
884
+ elements (GSAP animates transform directly). `destroy()` removes the
885
+ listeners, kills in-flight tweens and restores the inline transform.
886
+
685
887
  ---
686
888
 
687
889
  ### Navigation & overlays
@@ -725,7 +927,12 @@ Curtain uses `EASES.curtain` (`.76,0,.24,1`).
725
927
 
726
928
  #### `cursorFollower(zone, options?) => destroy`
727
929
 
728
- Spring-followed cursor tag, e.g. "▶ Play Showreel" over a video.
930
+ Spring-followed cursor tag, e.g. "▶ Play Showreel" over a video. The tag is
931
+ pinned `position: fixed` and driven in viewport space (`clientX/Y + offset`),
932
+ so it can live anywhere in the DOM — it only reacts while the pointer is over
933
+ the zone. Style it with `opacity: 0` and light text (`color: #fff`): the
934
+ `exclusion` blend inverts the tag against whatever is behind it, which only
935
+ reads with a light source colour.
729
936
 
730
937
  ```ts
731
938
  cursorFollower("[data-showreel]", {
@@ -827,14 +1034,15 @@ Types: `TargetLike`, `Destroy`, `CommonOptions`.
827
1034
  ## Styling
828
1035
 
829
1036
  ```ts
830
- import "anim-kit/styles"; // → dist/styles/anim-kit.css
1037
+ import "@cosmictraveler002/anim-kit/styles"; // → dist/styles/anim-kit.css
831
1038
  ```
832
1039
 
833
1040
  The companion stylesheet supplies:
834
1041
 
835
1042
  - **Design tokens:** `--ak-primary`, `--ak-curtain`, `--ak-reveal`, `--ak-out`
836
- - **Text masks:** `.ak-line-mask`, `.ak-line`, `.ak-word`, `.ak-space`
1043
+ - **Text masks:** `.ak-line-mask`, `.ak-line`, `.ak-char-mask`, `.ak-char`, `.ak-word`, `.ak-space`
837
1044
  - **Heading masks:** `.ak-mask`, `.ak-mask__inner`
1045
+ - **Rolling text:** `.ak-roll`, `.ak-roll__inner`
838
1046
  - **Liquid button:** `.ak-liquid`, `.ak-liquid__wave`, `.ak-liquid__label`
839
1047
  - **Underline:** `.ak-underline`
840
1048
  - **Marquee:** `.ak-marquee`, `.ak-marquee__viewport`, `.ak-marquee__track`
@@ -924,15 +1132,30 @@ npm run typecheck # tsc --noEmit (what CI runs)
924
1132
  ```
925
1133
 
926
1134
  The demo page wires the whole effect set against one document —
927
- `demo/index.html` + `demo/demo.js`. (`stackedCardsPinned`, the
928
- sibling-viewport variant, plus `split()` are exercised by the unit smoke
929
- instead.)
1135
+ `demo/index.html` + `demo/demo.js` — and every effect in the catalogue has
1136
+ an on-page *copy prompt* chip (the demo smoke enforces the two-way mapping).
1137
+ (`split()` is exercised by the unit smoke instead.)
1138
+
1139
+ Two pages share that wiring:
1140
+
1141
+ - **`demo/index.html`** (default — `npm run demo` → `/demo/`) — import map on
1142
+ local files (`/node_modules/...`, `/dist/index.js`): fast iteration, works
1143
+ offline, and the page `demo-smoke` drives.
1144
+ - **`demo/anim_kit_live.html`** (`/demo/anim_kit_live.html`) — byte-for-byte
1145
+ the same page wired to the **version-pinned CDN** (jsdelivr `@<version>` for
1146
+ the package, `gsap@3.15.0`, `lenis@1.3.26`): the exact path visitors copy
1147
+ from the prompts and docs, so CDN wiring gets the same manual QA as the
1148
+ local build. `demo-smoke` fails if the two files drift apart or the pin
1149
+ stops matching the release version, and the page only loads once that
1150
+ version is published to npm.
930
1151
 
931
1152
  ### Copy-prompt API
932
1153
 
933
1154
  The demo server doubles as a **prompt server**: every effect has a ready-to-
934
- paste *"how to implement this with anim-kit"* prompt — markup, import,
935
- initialisation call, options table, teardown and gotchas:
1155
+ paste *"how to implement this with anim-kit"* prompt — markup, a numbered
1156
+ copy-paste procedure that runs straight from the version-pinned CDN (no
1157
+ build step, plus the npm import for bundlers), initialisation call, options
1158
+ table, teardown and gotchas:
936
1159
 
937
1160
  ```bash
938
1161
  curl http://localhost:4321/api/prompts # { count, categories, prompts: [{ id, title, summary, category, subcategory, text }] }
@@ -940,7 +1163,7 @@ curl http://localhost:4321/api/prompts/marquee # one prompt, text/plain
940
1163
  ```
941
1164
 
942
1165
  On the page, every labelled section carries a **copy prompt** chip, and the
943
- floating **⧉ prompts (22)** button at the bottom right opens the full
1166
+ floating **⧉ prompts (29)** button at the bottom right opens the full
944
1167
  catalogue grouped by [effect category](#effect-categories) — one click copies
945
1168
  an effect's prompt (the prompt states its category), *copy all* puts the
946
1169
  entire set on the clipboard. The catalogue lives in `scripts/prompts.mjs`:
@@ -951,28 +1174,30 @@ and slotting the effect into a subcategory.
951
1174
  **Unit smoke** (`scripts/smoke.mjs`) runs the built bundle in **jsdom** and
952
1175
  asserts:
953
1176
 
954
- 1. all 38 exports are present;
1177
+ 1. all 45 exports are present;
955
1178
  2. plugins (`ScrollTrigger`, `SplitText`, `Draggable`, `CustomEase`,
956
- `ScrollSmoother`) and the 4 custom eases are registered;
1179
+ `Flip`, `ScrollSmoother`) and the 4 custom eases are registered;
957
1180
  3. every effect no-ops safely on missing targets;
958
- 4. 16 effects mount on real markup and unmount cleanly;
1181
+ 4. 24 effects mount on real markup and unmount cleanly;
959
1182
  5. `preloader` ticks in both the positional and options-object call forms;
960
- 6. `lineReveal` actually splits into masked lines and restores markup on
961
- destroy;
962
- 7. `utils`, `compose` and `guard` behave per contract.
1183
+ 6. `lineReveal` actually splits into masked lines (and per-character masks
1184
+ with `split: "chars"`) and restores markup on destroy;
1185
+ 7. `scrambleText` restores its text, `rollText` wraps/unwraps its rows, and
1186
+ `counter({ progress: true })` renders a scrubbed readout;
1187
+ 8. `utils`, `compose` and `guard` behave per contract.
963
1188
 
964
1189
  **Demo smoke** (`scripts/demo-smoke.mjs`) loads the real `demo/index.html` and
965
1190
  executes the real `demo/demo.js` wiring against it, then asserts the effects
966
1191
  actually *did* something (hero split, preloader counter ticking, marquee track
967
1192
  duplicated, per-call liquid directions, menu/theme/smooth-scroll handles in
968
- their initial state), that ~40 ScrollTriggers + a Draggable were created, that
1193
+ their initial state), that ~50 ScrollTriggers + a Draggable were created, that
969
1194
  no console errors were logged, that `dragStrip` tiled its content for the
970
1195
  seamless loop, that every effect referenced on the page has a `/api/prompts`
971
1196
  entry, that the taxonomy classifies every effect exactly once, that the
972
1197
  prompt dock renders one group per category with every effect listed once,
973
1198
  and that teardown leaves **zero** live ScrollTriggers, Draggables or
974
1199
  page-element tweens behind while restoring the original markup (marquee and
975
- drag-strip clones removed).
1200
+ drag-strip clones removed, rolling rows unwrapped, scrambled text restored).
976
1201
 
977
1202
  > jsdom is used deliberately: GSAP's CSSPlugin/Draggable probe element
978
1203
  > style/computed values during registration, which a hand-rolled DOM stub
@@ -992,9 +1217,9 @@ anim-kit/
992
1217
  │ │ ├─ guard.ts reduced-motion gate
993
1218
  │ │ ├─ util.ts toArray/one/onReady/compose/raf
994
1219
  │ │ └─ types.ts TargetLike / Destroy / CommonOptions
995
- │ ├─ effects/ one file per effect (17 files, 21 effect functions)
1220
+ │ ├─ effects/ one file per effect (24 files, 28 effect functions)
996
1221
  │ ├─ styles/anim-kit.css companion stylesheet
997
- │ └─ index.ts barrel — 38 exports
1222
+ │ └─ index.ts barrel — 45 exports
998
1223
  ├─ demo/ visual demo (import map, no bundler)
999
1224
  ├─ scripts/
1000
1225
  │ ├─ serve.mjs static server + /api/prompts (:4321)
@@ -1007,7 +1232,7 @@ anim-kit/
1007
1232
  ├─ LICENSE MIT
1008
1233
  └─ dist/ build output
1009
1234
  ├─ index.js / *.d.ts per-file ESM + declarations (tsc)
1010
- ├─ effects/*.js one module per effect → anim-kit/effects/* subpaths
1235
+ ├─ effects/*.js one module per effect → @cosmictraveler002/anim-kit/effects/* subpaths
1011
1236
  ├─ anim-kit.standalone.js self-contained CDN bundle (gsap+lenis inlined)
1012
1237
  └─ styles/anim-kit.css plain CSS, copied verbatim
1013
1238
  ```
@@ -1019,7 +1244,7 @@ push/PR) and `.github/workflows/release.yml` (tag `v*` → `npm publish
1019
1244
 
1020
1245
  Each effect is an independent module — if you only need the marquee, import
1021
1246
  `marquee` and the bundler drops the rest, or deep-import
1022
- `anim-kit/effects/marquee` to skip the barrel entirely.
1247
+ `@cosmictraveler002/anim-kit/effects/marquee` to skip the barrel entirely.
1023
1248
 
1024
1249
  ---
1025
1250