@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.
Files changed (63) hide show
  1. package/README.md +293 -36
  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 +3 -2
  5. package/dist/core/gsap.d.ts.map +1 -1
  6. package/dist/core/gsap.js +4 -3
  7. package/dist/core/gsap.js.map +1 -1
  8. package/dist/core/smooth-scroll.d.ts +1 -1
  9. package/dist/core/smooth-scroll.js +1 -1
  10. package/dist/effects/clip-wipe.d.ts +10 -2
  11. package/dist/effects/clip-wipe.d.ts.map +1 -1
  12. package/dist/effects/clip-wipe.js +30 -9
  13. package/dist/effects/clip-wipe.js.map +1 -1
  14. package/dist/effects/cursor-follower.d.ts.map +1 -1
  15. package/dist/effects/cursor-follower.js +22 -4
  16. package/dist/effects/cursor-follower.js.map +1 -1
  17. package/dist/effects/drag-rail.d.ts +23 -0
  18. package/dist/effects/drag-rail.d.ts.map +1 -0
  19. package/dist/effects/drag-rail.js +196 -0
  20. package/dist/effects/drag-rail.js.map +1 -0
  21. package/dist/effects/flip-words.d.ts +31 -0
  22. package/dist/effects/flip-words.d.ts.map +1 -0
  23. package/dist/effects/flip-words.js +104 -0
  24. package/dist/effects/flip-words.js.map +1 -0
  25. package/dist/effects/magnetic.d.ts +15 -0
  26. package/dist/effects/magnetic.d.ts.map +1 -0
  27. package/dist/effects/magnetic.js +59 -0
  28. package/dist/effects/magnetic.js.map +1 -0
  29. package/dist/effects/media-settle.d.ts +2 -0
  30. package/dist/effects/media-settle.d.ts.map +1 -1
  31. package/dist/effects/media-settle.js +5 -2
  32. package/dist/effects/media-settle.js.map +1 -1
  33. package/dist/effects/preloader.js +1 -1
  34. package/dist/effects/reel-text.d.ts +19 -0
  35. package/dist/effects/reel-text.d.ts.map +1 -0
  36. package/dist/effects/reel-text.js +163 -0
  37. package/dist/effects/reel-text.js.map +1 -0
  38. package/dist/index.d.ts +14 -2
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +9 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/three/index.d.ts +13 -0
  43. package/dist/three/index.d.ts.map +1 -0
  44. package/dist/three/index.js +12 -0
  45. package/dist/three/index.js.map +1 -0
  46. package/dist/three/webgl-media.d.ts +21 -0
  47. package/dist/three/webgl-media.d.ts.map +1 -0
  48. package/dist/three/webgl-media.js +268 -0
  49. package/dist/three/webgl-media.js.map +1 -0
  50. package/package.json +17 -1
  51. package/src/core/gsap.ts +4 -3
  52. package/src/core/smooth-scroll.ts +1 -1
  53. package/src/effects/clip-wipe.ts +50 -10
  54. package/src/effects/cursor-follower.ts +21 -4
  55. package/src/effects/drag-rail.ts +236 -0
  56. package/src/effects/flip-words.ts +148 -0
  57. package/src/effects/magnetic.ts +87 -0
  58. package/src/effects/media-settle.ts +7 -1
  59. package/src/effects/preloader.ts +1 -1
  60. package/src/effects/reel-text.ts +212 -0
  61. package/src/index.ts +18 -2
  62. package/src/three/index.ts +12 -0
  63. package/src/three/webgl-media.ts +325 -0
@@ -0,0 +1,21 @@
1
+ import type { CommonOptions, Destroy, TargetLike } from "../core/types.js";
2
+ export interface WebglMediaOptions extends CommonOptions {
3
+ /** Image source — defaults to the wrapper's first `<img>`. */
4
+ src?: string;
5
+ /** Corner radius, px. @default 16 */
6
+ corner?: number;
7
+ /** Dent depth pressed into the card on hover, px. @default 70 */
8
+ dent?: number;
9
+ /** Chromatic split at full hover, px. @default 2 */
10
+ chroma?: number;
11
+ /** Wipe the card in when its texture loads. @default true */
12
+ reveal?: boolean;
13
+ /** Reveal duration, seconds. @default 1.1 */
14
+ revealDuration?: number;
15
+ /** Hover response duration, seconds. @default 0.6 */
16
+ hoverDuration?: number;
17
+ /** Device-pixel-ratio cap. @default 2 */
18
+ dpr?: number;
19
+ }
20
+ export declare function webglMedia(target: TargetLike, options?: WebglMediaOptions): Destroy;
21
+ //# sourceMappingURL=webgl-media.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webgl-media.d.ts","sourceRoot":"","sources":["../../src/three/webgl-media.ts"],"names":[],"mappings":"AAuCA,OAAO,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE3E,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,8DAA8D;IAC9D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qDAAqD;IACrD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAyED,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAiMvF"}
@@ -0,0 +1,268 @@
1
+ /**
2
+ * WebGL media — the shader card: a DOM `<img>` re-rendered as a rounded,
3
+ * hover-reactive GL plane.
4
+ *
5
+ * The image keeps owning layout and alt text; the effect paints it onto a
6
+ * canvas that covers the wrapper, so the card can do what CSS can't: press a
7
+ * *dent* into the picture on hover (sample squeeze + fake dome lighting),
8
+ * split its channels (chroma), and wipe in on reveal — all inside one
9
+ * fragment shader. This is the media-card pattern from three.js portfolios,
10
+ * kept dependency-honest:
11
+ *
12
+ * - **Optional peer.** Lives behind the `@cosmictraveler002/anim-kit/three`
13
+ * subpath; the core barrel stays gsap+lenis only. `three` is an optional
14
+ * peer — install it to use this entry, the core never imports it.
15
+ * - **Silent no-op ladder.** Missing target → no-op. No
16
+ * `WebGLRenderingContext` (jsdom, SSR, WebGL-disabled browsers) → no-op
17
+ * *before* a context is ever requested (a probe call would itself log).
18
+ * Renderer construction throws → no-op. Texture 404s → the plain `<img>`
19
+ * stays visible. Under `prefers-reduced-motion` the canvas never mounts.
20
+ * - **Cover crop in-shader.** The texture is sampled with `object-fit: cover`
21
+ * maths (`scale = uRes / uTexSize`, windowed around the centre), so any
22
+ * source aspect fills the card without letterboxing.
23
+ * - **Rounded corners are shader-true.** A rounded-box SDF with a 1px smooth
24
+ * edge — not `border-radius` on the canvas — so the dent/chroma never bleed
25
+ * into the corner.
26
+ * - **Fidelity.** `destroy()` kills ticker + tweens + observers/listeners,
27
+ * disposes geometry/material/texture/renderer, removes the canvas and
28
+ * restores the `<img>` opacity and wrapper position it changed.
29
+ *
30
+ * webglMedia("[data-gl]", { corner: 18, dent: 70, chroma: 2 })
31
+ *
32
+ * Markup: a wrapper element containing `<img>` (or pass `src` explicitly):
33
+ *
34
+ * <figure class="gl-card" data-gl><img src="photo.jpg" alt="" /></figure>
35
+ */
36
+ import * as THREE from "three";
37
+ import { gsap, initGSAP } from "../core/gsap.js";
38
+ import { guard } from "../core/guard.js";
39
+ import { one } from "../core/util.js";
40
+ const VERT = /* glsl */ `
41
+ varying vec2 vUv;
42
+ uniform float uHover;
43
+ uniform float uDent;
44
+ uniform float uTime;
45
+
46
+ void main() {
47
+ vUv = uv;
48
+ vec3 p = position;
49
+ float dome = max(cos(uv.x * 3.14159265) * cos(uv.y * 3.14159265), 0.0);
50
+ dome *= 0.94 + 0.06 * sin(uTime * 1.6);
51
+ // Pressed-in curvature while hovered — the fragment does the visible work.
52
+ p.z -= uHover * uDent * dome;
53
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);
54
+ }
55
+ `;
56
+ const FRAG = /* glsl */ `
57
+ precision highp float;
58
+ varying vec2 vUv;
59
+ uniform sampler2D uTex;
60
+ uniform vec2 uRes;
61
+ uniform vec2 uTexSize;
62
+ uniform float uCorner;
63
+ uniform float uHover;
64
+ uniform float uDent;
65
+ uniform float uChroma;
66
+ uniform float uProgress;
67
+ uniform float uTime;
68
+
69
+ float sdRoundBox(vec2 p, vec2 b, float r) {
70
+ vec2 q = abs(p) - b + r;
71
+ return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
72
+ }
73
+
74
+ void main() {
75
+ vec2 px = (vUv - 0.5) * uRes;
76
+ float d = sdRoundBox(px, uRes * 0.5, uCorner);
77
+ float alpha = 1.0 - smoothstep(-1.0, 1.0, d); // 1px-ish AA on the corner
78
+
79
+ // Entrance wipe: grows from the bottom with a soft leading edge.
80
+ if (uProgress < 1.0) {
81
+ alpha *= 1.0 - smoothstep(uProgress - 0.05, uProgress, vUv.y);
82
+ }
83
+ if (alpha < 0.003) discard;
84
+
85
+ // object-fit: cover — window the texture around its centre.
86
+ vec2 scale = uRes / max(uTexSize, vec2(1.0));
87
+ vec2 cover = (vUv - 0.5) * (scale / max(scale.x, scale.y)) + 0.5;
88
+
89
+ // Hover dent: squeeze the sample toward the centre (breathing slightly).
90
+ vec2 c = cover - 0.5;
91
+ float dome = max(cos(vUv.x * 3.14159265) * cos(vUv.y * 3.14159265), 0.0);
92
+ dome *= 0.94 + 0.06 * sin(uTime * 1.6);
93
+ vec2 suv = 0.5 + c * (1.0 - uHover * uDent * 0.0016 * dome);
94
+
95
+ float chroma = uChroma * uHover * dome / max(uRes.x, 1.0);
96
+ vec3 col;
97
+ col.r = texture2D(uTex, suv + vec2(chroma, 0.0)).r;
98
+ col.g = texture2D(uTex, suv).g;
99
+ col.b = texture2D(uTex, suv - vec2(chroma, 0.0)).b;
100
+
101
+ // Fake lighting off the dome slope: sheen at the crest, shade at the rim.
102
+ float light = dome * uHover;
103
+ col += light * 0.12;
104
+ col *= 1.0 - 0.12 * uHover * (1.0 - dome);
105
+
106
+ gl_FragColor = vec4(col, alpha);
107
+ }
108
+ `;
109
+ export function webglMedia(target, options = {}) {
110
+ initGSAP();
111
+ const host = one(target);
112
+ if (!host)
113
+ return () => { };
114
+ const { src, corner = 16, dent = 70, chroma = 2, reveal = true, revealDuration = 1.1, hoverDuration = 0.6, dpr = 2, } = options;
115
+ return guard(options, () => {
116
+ /* ---------------- availability ladder (silent, no probes) ---------------- */
117
+ if (typeof window === "undefined" || !("WebGLRenderingContext" in window))
118
+ return () => { };
119
+ let renderer;
120
+ try {
121
+ renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
122
+ }
123
+ catch {
124
+ return () => { }; // context refused (disabled/blacklisted) — plain img stays
125
+ }
126
+ const img = host.tagName === "IMG" ? null : host.querySelector("img");
127
+ const textureSrc = src || img?.currentSrc || img?.src;
128
+ if (!textureSrc) {
129
+ renderer.dispose();
130
+ return () => { };
131
+ }
132
+ /* ---------------- layout + styles we must restore ---------------- */
133
+ const prevPosition = host.style.position;
134
+ if (!prevPosition && getComputedStyle(host).position === "static") {
135
+ host.style.position = "relative";
136
+ }
137
+ const prevImgOpacity = img?.style.opacity ?? "";
138
+ host.dataset.akGl = "true";
139
+ const w = Math.max(1, host.clientWidth);
140
+ const h = Math.max(1, host.clientHeight);
141
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, dpr));
142
+ renderer.setSize(w, h, false);
143
+ const canvas = renderer.domElement;
144
+ canvas.style.cssText =
145
+ "position:absolute;inset:0;width:100%;height:100%;display:block;pointer-events:none;";
146
+ const scene = new THREE.Scene();
147
+ const camera = new THREE.OrthographicCamera(-w / 2, w / 2, h / 2, -h / 2, -100, 100);
148
+ camera.position.z = 10;
149
+ const geometry = new THREE.PlaneGeometry(1, 1, 1, 1);
150
+ const uniforms = {
151
+ uTex: { value: null },
152
+ uRes: { value: new THREE.Vector2(w, h) },
153
+ uTexSize: { value: new THREE.Vector2(1, 1) },
154
+ uCorner: { value: corner },
155
+ uHover: { value: 0 },
156
+ uDent: { value: dent },
157
+ uChroma: { value: chroma },
158
+ uProgress: { value: reveal ? 0 : 1 },
159
+ uTime: { value: 0 },
160
+ };
161
+ const material = new THREE.ShaderMaterial({
162
+ vertexShader: VERT,
163
+ fragmentShader: FRAG,
164
+ uniforms,
165
+ transparent: true,
166
+ });
167
+ const mesh = new THREE.Mesh(geometry, material);
168
+ mesh.scale.set(w, h, 1);
169
+ mesh.visible = false;
170
+ scene.add(mesh);
171
+ host.appendChild(canvas);
172
+ /* ---------------- texture ---------------- */
173
+ let disposed = false;
174
+ const loader = new THREE.TextureLoader();
175
+ let revealTween = null;
176
+ loader.load(textureSrc, (texture) => {
177
+ if (disposed) {
178
+ texture.dispose();
179
+ return;
180
+ }
181
+ texture.minFilter = THREE.LinearFilter;
182
+ texture.magFilter = THREE.LinearFilter;
183
+ // No sRGB decode: the raw shader passes sampled values through, so the
184
+ // texture must stay an ordinary RGBA8 upload (built-in colorSpace
185
+ // conversion only happens inside three's own material chunks).
186
+ uniforms.uTex.value = texture;
187
+ uniforms.uTexSize.value.set(texture.image.width || 1, texture.image.height || 1);
188
+ mesh.visible = true;
189
+ if (img)
190
+ img.style.opacity = "0"; // canvas owns the pixels from here
191
+ if (reveal) {
192
+ revealTween = gsap.fromTo(uniforms.uProgress, { value: 0 }, { value: 1, duration: revealDuration, ease: "power2.inOut" });
193
+ }
194
+ }, undefined, () => {
195
+ /* 404 / decode failure — the plain <img> was never hidden. */
196
+ });
197
+ /* ---------------- hover ---------------- */
198
+ let hoverTween = null;
199
+ const hoverTo = (value) => {
200
+ hoverTween = gsap.to(uniforms.uHover, {
201
+ value,
202
+ duration: hoverDuration,
203
+ ease: "power3.out",
204
+ overwrite: "auto",
205
+ });
206
+ };
207
+ const onEnter = () => hoverTo(1);
208
+ const onLeave = () => hoverTo(0);
209
+ host.addEventListener("pointerenter", onEnter);
210
+ host.addEventListener("pointerleave", onLeave);
211
+ /* ---------------- loop / observers ---------------- */
212
+ let visible = true;
213
+ let io = null;
214
+ if (typeof IntersectionObserver === "function") {
215
+ io = new IntersectionObserver(([entry]) => (visible = entry.isIntersecting), {
216
+ rootMargin: "120px",
217
+ });
218
+ io.observe(host);
219
+ }
220
+ let ro = null;
221
+ const resize = () => {
222
+ const rw = Math.max(1, host.clientWidth);
223
+ const rh = Math.max(1, host.clientHeight);
224
+ renderer.setSize(rw, rh, false);
225
+ camera.left = -rw / 2;
226
+ camera.right = rw / 2;
227
+ camera.top = rh / 2;
228
+ camera.bottom = -rh / 2;
229
+ camera.updateProjectionMatrix();
230
+ mesh.scale.set(rw, rh, 1);
231
+ uniforms.uRes.value.set(rw, rh);
232
+ };
233
+ if (typeof ResizeObserver === "function") {
234
+ ro = new ResizeObserver(resize);
235
+ ro.observe(host);
236
+ }
237
+ const render = () => {
238
+ if (!visible || !mesh.visible)
239
+ return;
240
+ uniforms.uTime.value = gsap.ticker.time;
241
+ renderer.render(scene, camera);
242
+ };
243
+ gsap.ticker.add(render);
244
+ /* ---------------- teardown ---------------- */
245
+ return () => {
246
+ disposed = true;
247
+ gsap.ticker.remove(render);
248
+ hoverTween?.kill();
249
+ revealTween?.kill();
250
+ gsap.killTweensOf(uniforms.uHover);
251
+ gsap.killTweensOf(uniforms.uProgress);
252
+ host.removeEventListener("pointerenter", onEnter);
253
+ host.removeEventListener("pointerleave", onLeave);
254
+ io?.disconnect();
255
+ ro?.disconnect();
256
+ uniforms.uTex.value?.dispose();
257
+ geometry.dispose();
258
+ material.dispose();
259
+ renderer.dispose();
260
+ canvas.remove();
261
+ delete host.dataset.akGl;
262
+ host.style.position = prevPosition;
263
+ if (img)
264
+ img.style.opacity = prevImgOpacity;
265
+ };
266
+ });
267
+ }
268
+ //# sourceMappingURL=webgl-media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webgl-media.js","sourceRoot":"","sources":["../../src/three/webgl-media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAsBtC,MAAM,IAAI,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;CAevB,CAAC;AAEF,MAAM,IAAI,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoDvB,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,MAAkB,EAAE,UAA6B,EAAE;IAC5E,QAAQ,EAAE,CAAC;IAEX,MAAM,IAAI,GAAG,GAAG,CAAc,MAAM,CAAC,CAAC;IACtC,IAAI,CAAC,IAAI;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;IAE3B,MAAM,EACJ,GAAG,EACH,MAAM,GAAG,EAAE,EACX,IAAI,GAAG,EAAE,EACT,MAAM,GAAG,CAAC,EACV,MAAM,GAAG,IAAI,EACb,cAAc,GAAG,GAAG,EACpB,aAAa,GAAG,GAAG,EACnB,GAAG,GAAG,CAAC,GACR,GAAG,OAAO,CAAC;IAEZ,OAAO,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE;QACzB,+EAA+E;QAC/E,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,CAAC,CAAC,uBAAuB,IAAI,MAAM,CAAC;YAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;QAE3F,IAAI,QAA6B,CAAC;QAClC,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,KAAK,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACvE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,2DAA2D;QAC9E,CAAC;QAED,MAAM,GAAG,GACP,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAE,IAAI,CAAC,aAAa,CAAC,KAAK,CAA6B,CAAC;QACzF,MAAM,UAAU,GAAG,GAAG,IAAI,GAAG,EAAE,UAAU,IAAI,GAAG,EAAE,GAAG,CAAC;QACtD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,QAAQ,CAAC,OAAO,EAAE,CAAC;YACnB,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;QAClB,CAAC;QAED,uEAAuE;QAEvE,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;QACzC,IAAI,CAAC,YAAY,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAClE,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,UAAU,CAAC;QACnC,CAAC;QACD,MAAM,cAAc,GAAG,GAAG,EAAE,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC;QAChD,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC;QAE3B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QACxC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAEzC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,gBAAgB,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QACpE,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;QAC9B,MAAM,MAAM,GAAG,QAAQ,CAAC,UAAU,CAAC;QACnC,MAAM,CAAC,KAAK,CAAC,OAAO;YAClB,qFAAqF,CAAC;QAExF,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACrF,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,EAAE,CAAC;QAEvB,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACrD,MAAM,QAAQ,GAAG;YACf,IAAI,EAAE,EAAE,KAAK,EAAE,IAA4B,EAAE;YAC7C,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE;YACxC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE;YAC5C,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;YAC1B,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE;YACpB,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE;YACtB,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;YAC1B,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;YACpC,KAAK,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE;SACpB,CAAC;QACF,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAC,cAAc,CAAC;YACxC,YAAY,EAAE,IAAI;YAClB,cAAc,EAAE,IAAI;YACpB,QAAQ;YACR,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAChD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACxB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAChB,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAEzB,+CAA+C;QAE/C,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,MAAM,MAAM,GAAG,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;QACzC,IAAI,WAAW,GAA2B,IAAI,CAAC;QAE/C,MAAM,CAAC,IAAI,CACT,UAAU,EACV,CAAC,OAAO,EAAE,EAAE;YACV,IAAI,QAAQ,EAAE,CAAC;gBACb,OAAO,CAAC,OAAO,EAAE,CAAC;gBAClB,OAAO;YACT,CAAC;YACD,OAAO,CAAC,SAAS,GAAG,KAAK,CAAC,YAAY,CAAC;YACvC,OAAO,CAAC,SAAS,GAAG,KAAK,CAAC,YAAY,CAAC;YACvC,uEAAuE;YACvE,kEAAkE;YAClE,+DAA+D;YAC/D,QAAQ,CAAC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC;YAC9B,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;YACjF,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,IAAI,GAAG;gBAAE,GAAG,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC,CAAC,mCAAmC;YACrE,IAAI,MAAM,EAAE,CAAC;gBACX,WAAW,GAAG,IAAI,CAAC,MAAM,CACvB,QAAQ,CAAC,SAAS,EAClB,EAAE,KAAK,EAAE,CAAC,EAAE,EACZ,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,cAAc,EAAE,CAC7D,CAAC;YACJ,CAAC;QACH,CAAC,EACD,SAAS,EACT,GAAG,EAAE;YACH,8DAA8D;QAChE,CAAC,CACF,CAAC;QAEF,6CAA6C;QAE7C,IAAI,UAAU,GAA2B,IAAI,CAAC;QAC9C,MAAM,OAAO,GAAG,CAAC,KAAa,EAAE,EAAE;YAChC,UAAU,GAAG,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE;gBACpC,KAAK;gBACL,QAAQ,EAAE,aAAa;gBACvB,IAAI,EAAE,YAAY;gBAClB,SAAS,EAAE,MAAM;aAClB,CAAC,CAAC;QACL,CAAC,CAAC;QACF,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACjC,IAAI,CAAC,gBAAgB,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;QAC/C,IAAI,CAAC,gBAAgB,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;QAE/C,wDAAwD;QAExD,IAAI,OAAO,GAAG,IAAI,CAAC;QACnB,IAAI,EAAE,GAAgC,IAAI,CAAC;QAC3C,IAAI,OAAO,oBAAoB,KAAK,UAAU,EAAE,CAAC;YAC/C,EAAE,GAAG,IAAI,oBAAoB,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,GAAG,KAAK,CAAC,cAAc,CAAC,EAAE;gBAC3E,UAAU,EAAE,OAAO;aACpB,CAAC,CAAC;YACH,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;QAED,IAAI,EAAE,GAA0B,IAAI,CAAC;QACrC,MAAM,MAAM,GAAG,GAAG,EAAE;YAClB,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;YACzC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;YAC1C,QAAQ,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;YAChC,MAAM,CAAC,IAAI,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;YACtB,MAAM,CAAC,KAAK,GAAG,EAAE,GAAG,CAAC,CAAC;YACtB,MAAM,CAAC,GAAG,GAAG,EAAE,GAAG,CAAC,CAAC;YACpB,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;YACxB,MAAM,CAAC,sBAAsB,EAAE,CAAC;YAChC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;YAC1B,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QAClC,CAAC,CAAC;QACF,IAAI,OAAO,cAAc,KAAK,UAAU,EAAE,CAAC;YACzC,EAAE,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC;YAChC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;QAED,MAAM,MAAM,GAAG,GAAG,EAAE;YAClB,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO;gBAAE,OAAO;YACtC,QAAQ,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YACxC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACjC,CAAC,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAExB,gDAAgD;QAEhD,OAAO,GAAG,EAAE;YACV,QAAQ,GAAG,IAAI,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC3B,UAAU,EAAE,IAAI,EAAE,CAAC;YACnB,WAAW,EAAE,IAAI,EAAE,CAAC;YACpB,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YACnC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;YACtC,IAAI,CAAC,mBAAmB,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;YAClD,IAAI,CAAC,mBAAmB,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;YAClD,EAAE,EAAE,UAAU,EAAE,CAAC;YACjB,EAAE,EAAE,UAAU,EAAE,CAAC;YACjB,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC;YAC/B,QAAQ,CAAC,OAAO,EAAE,CAAC;YACnB,QAAQ,CAAC,OAAO,EAAE,CAAC;YACnB,QAAQ,CAAC,OAAO,EAAE,CAAC;YACnB,MAAM,CAAC,MAAM,EAAE,CAAC;YAChB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;YACzB,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,YAAY,CAAC;YACnC,IAAI,GAAG;gBAAE,GAAG,CAAC,KAAK,CAAC,OAAO,GAAG,cAAc,CAAC;QAC9C,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmictraveler002/anim-kit",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Modular, framework-agnostic animation library — GSAP + ScrollTrigger + Lenis effects packaged as composable ES modules.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,6 +17,11 @@
17
17
  "import": "./dist/anim-kit.standalone.js",
18
18
  "default": "./dist/anim-kit.standalone.js"
19
19
  },
20
+ "./three": {
21
+ "types": "./dist/three/index.d.ts",
22
+ "import": "./dist/three/index.js",
23
+ "default": "./dist/three/index.js"
24
+ },
20
25
  "./effects/*": {
21
26
  "types": "./dist/effects/*.d.ts",
22
27
  "import": "./dist/effects/*.js",
@@ -42,6 +47,7 @@
42
47
  "typecheck": "tsc --noEmit -p tsconfig.json",
43
48
  "dev": "tsc -p tsconfig.json --watch",
44
49
  "demo": "node scripts/serve.mjs",
50
+ "sync:live": "node scripts/sync-demo-live.mjs",
45
51
  "test": "npm run build && node scripts/smoke.mjs && node scripts/demo-smoke.mjs",
46
52
  "smoke": "node scripts/smoke.mjs && node scripts/demo-smoke.mjs",
47
53
  "prepublishOnly": "npm run build && npm run smoke"
@@ -75,8 +81,18 @@
75
81
  "gsap": "^3.13.0",
76
82
  "lenis": "^1.3.11"
77
83
  },
84
+ "peerDependencies": {
85
+ "three": ">=0.160.0"
86
+ },
87
+ "peerDependenciesMeta": {
88
+ "three": {
89
+ "optional": true
90
+ }
91
+ },
78
92
  "devDependencies": {
93
+ "@types/three": "^0.186.0",
79
94
  "jsdom": "^25.0.1",
95
+ "three": "^0.186.1",
80
96
  "tsup": "^8.5.1",
81
97
  "typescript": "^5.7.3"
82
98
  }
package/src/core/gsap.ts CHANGED
@@ -10,12 +10,13 @@ import { SplitText } from "gsap/SplitText";
10
10
  import { Draggable } from "gsap/Draggable";
11
11
  import { CustomEase } from "gsap/CustomEase";
12
12
  import { ScrollSmoother } from "gsap/ScrollSmoother";
13
+ import { Flip } from "gsap/Flip";
13
14
  import { toArray } from "./util.js";
14
15
  import type { TargetLike } from "./types.js";
15
16
 
16
- export { gsap, ScrollTrigger, SplitText, Draggable, CustomEase, ScrollSmoother };
17
+ export { gsap, ScrollTrigger, SplitText, Draggable, CustomEase, ScrollSmoother, Flip };
17
18
 
18
- /** Named eases recovered from dzinrstudio.com's bundles. */
19
+ /** Named eases from kalakritico.in's bundles. */
19
20
  export const EASES = {
20
21
  /** `.76,0,.2,1` — the studio's signature menu / nav curtain ease. */
21
22
  curtain: "ak-curtain",
@@ -34,7 +35,7 @@ export function initGSAP(): void {
34
35
  if (initted || typeof window === "undefined") return;
35
36
  initted = true;
36
37
 
37
- gsap.registerPlugin(ScrollTrigger, SplitText, Draggable, CustomEase);
38
+ gsap.registerPlugin(ScrollTrigger, SplitText, Draggable, CustomEase, Flip);
38
39
 
39
40
  // ScrollSmoother is a bonus for people who prefer it over Lenis; it is only
40
41
  // registered so `ScrollSmoother.create()` works if you call it yourself.
@@ -10,7 +10,7 @@
10
10
  * clocks on the same frame, which is what stops ScrollTrigger from stuttering.
11
11
  *
12
12
  * `useScrollerProxy: true` additionally proxies `document.documentElement`,
13
- * matching dzinrstudio.com's setup — only needed if you scroll a nested
13
+ * matching kalakritico.in's setup — only needed if you scroll a nested
14
14
  * element instead of the window.
15
15
  */
16
16
  import Lenis from "lenis";
@@ -1,14 +1,22 @@
1
1
  /**
2
2
  * Clip wipe — a `clip-path: inset()` reveal.
3
3
  *
4
- * The element is collapsed behind one edge (or inside a frame margin) and
5
- * the inset animates to zero, wiping it into view:
4
+ * The element is collapsed behind one edge, corner (or inside a frame margin)
5
+ * and the inset animates to zero, wiping it into view:
6
6
  *
7
- * from: "left" inset(0 100% 0 0) → grows rightward from the left edge
8
- * from: "right" inset(0 0 0 100%) → grows leftward from the right edge
9
- * from: "top" inset(0 0 100% 0) → grows downward from the top edge
10
- * from: "bottom" inset(100% 0 0 0) → grows upward from the bottom edge
11
- * from: "frame" inset(15% 15% 15% 15%) → opens out of a centered frame
7
+ * from: "left" inset(0 100% 0 0) → grows rightward from the left edge
8
+ * from: "right" inset(0 0 0 100%) → grows leftward from the right edge
9
+ * from: "top" inset(0 0 100% 0) → grows downward from the top edge
10
+ * from: "bottom" inset(100% 0 0 0) → grows upward from the bottom edge
11
+ * from: "top-left" inset(0 100% 100% 0) → opens toward the bottom-right corner
12
+ * from: "top-right" inset(0 0 100% 100%) → opens toward the bottom-left corner
13
+ * from: "bottom-left" inset(100% 100% 0 0) → opens toward the top-right corner
14
+ * from: "bottom-right" inset(100% 0 0 100%) → opens toward the top-left corner
15
+ * from: "frame" inset(15% 15% 15% 15%) → opens out of a centered frame
16
+ *
17
+ * The wipe can also be bound to scroll progress (`scrub`), so the inset
18
+ * opens as the element travels through the viewport — the reveal then
19
+ * reverses with the scroll instead of playing once.
12
20
  *
13
21
  * Works on images, videos, blocks and text. destroy() kills the tween and
14
22
  * removes the inline clip-path.
@@ -18,9 +26,20 @@ import { guard } from "../core/guard.js";
18
26
  import { toArray } from "../core/util.js";
19
27
  import type { CommonOptions, Destroy, TargetLike } from "../core/types.js";
20
28
 
29
+ export type ClipWipeFrom =
30
+ | "left"
31
+ | "right"
32
+ | "top"
33
+ | "bottom"
34
+ | "top-left"
35
+ | "top-right"
36
+ | "bottom-left"
37
+ | "bottom-right"
38
+ | "frame";
39
+
21
40
  export interface ClipWipeOptions extends CommonOptions {
22
- /** Which edge the wipe starts from. @default "left" */
23
- from?: "left" | "right" | "top" | "bottom" | "frame";
41
+ /** Which edge or corner the wipe starts from. @default "left" */
42
+ from?: ClipWipeFrom;
24
43
  /** Frame margin in % (only used when `from: "frame"`). @default 15 */
25
44
  inset?: number;
26
45
  /** Animation duration, seconds. @default 1 */
@@ -35,8 +54,15 @@ export interface ClipWipeOptions extends CommonOptions {
35
54
  mode?: "scroll" | "immediate";
36
55
  /** ScrollTrigger start position. @default "top 85%" */
37
56
  start?: string;
57
+ /** ScrollTrigger end position (scrub mode). @default "top 20%" */
58
+ end?: string;
38
59
  /** Re-wipe when leaving / re-entering the viewport. @default false */
39
60
  replay?: boolean;
61
+ /**
62
+ * Bind the wipe to scroll progress instead of playing it on enter —
63
+ * number = scrub smoothing seconds, `true` = immediate. unset = one-shot.
64
+ */
65
+ scrub?: number | boolean;
40
66
  }
41
67
 
42
68
  const FROM: Record<string, string> = {
@@ -44,6 +70,10 @@ const FROM: Record<string, string> = {
44
70
  right: "inset(0% 0% 0% 100%)",
45
71
  top: "inset(0% 0% 100% 0%)",
46
72
  bottom: "inset(100% 0% 0% 0%)",
73
+ "top-left": "inset(0% 100% 100% 0%)",
74
+ "top-right": "inset(0% 0% 100% 100%)",
75
+ "bottom-left": "inset(100% 100% 0% 0%)",
76
+ "bottom-right": "inset(100% 0% 0% 100%)",
47
77
  };
48
78
  const TO = "inset(0% 0% 0% 0%)";
49
79
 
@@ -59,7 +89,9 @@ export function clipWipe(target: TargetLike, options: ClipWipeOptions = {}): Des
59
89
  delay = 0,
60
90
  mode = "scroll",
61
91
  start = "top 85%",
92
+ end = "top 20%",
62
93
  replay = false,
94
+ scrub,
63
95
  } = options;
64
96
 
65
97
  const els = toArray<HTMLElement>(target);
@@ -80,7 +112,15 @@ export function clipWipe(target: TargetLike, options: ClipWipeOptions = {}): Des
80
112
  delay,
81
113
  overwrite: "auto",
82
114
  };
83
- if (mode === "scroll") {
115
+ if (scrub !== undefined) {
116
+ vars.ease = "none";
117
+ vars.scrollTrigger = {
118
+ trigger: els[0],
119
+ start,
120
+ end,
121
+ scrub: scrub === true ? true : scrub,
122
+ };
123
+ } else if (mode === "scroll") {
84
124
  vars.scrollTrigger = {
85
125
  trigger: els[0],
86
126
  start,
@@ -5,6 +5,10 @@
5
5
  * whole thing fades/blurs in when the pointer enters the hot zone. The label
6
6
  * uses `mix-blend-mode: exclusion` so it inverts over any background.
7
7
  *
8
+ * The tag is pinned `position: fixed` and driven in VIEWPORT space
9
+ * (clientX/clientY + offset), so it can live anywhere in the DOM — it only
10
+ * reacts while the pointer is over the zone.
11
+ *
8
12
  * cursorFollower(zone, { follower: ".ak-cursor" })
9
13
  */
10
14
  import { gsap, initGSAP, killTweens } from "../core/gsap.js";
@@ -51,7 +55,11 @@ export function cursorFollower(
51
55
  };
52
56
 
53
57
  el.style.mixBlendMode = blendMode as never;
54
- el.style.position = "absolute";
58
+ // Viewport space: position:fixed + clientX/clientY keeps the tag at the
59
+ // pointer wherever it sits in the DOM. (The old position:absolute +
60
+ // zone-relative math only lined up when the tag was a child of the zone —
61
+ // a body-level tag landed near the document origin instead, off-screen.)
62
+ el.style.position = "fixed";
55
63
  el.style.left = "0";
56
64
  el.style.top = "0";
57
65
  el.style.pointerEvents = "none";
@@ -61,10 +69,19 @@ export function cursorFollower(
61
69
  const xTo = gsap.quickTo(el, "x", { ...spring, duration: 0.4, ease: "power3" });
62
70
  const yTo = gsap.quickTo(el, "y", { ...spring, duration: 0.4, ease: "power3" });
63
71
 
72
+ // First move lands on the pointer; later moves spring after it — starting
73
+ // from (0,0), the spring would otherwise carry the tag in from the corner.
74
+ let snapped = false;
64
75
  const onMove = (e: PointerEvent) => {
65
- const r = area.getBoundingClientRect();
66
- xTo(e.clientX - r.left + offset);
67
- yTo(e.clientY - r.top + offset);
76
+ const x = e.clientX + offset;
77
+ const y = e.clientY + offset;
78
+ if (snapped) {
79
+ xTo(x);
80
+ yTo(y);
81
+ } else {
82
+ gsap.set(el, { x, y });
83
+ snapped = true;
84
+ }
68
85
  if (fade) gsap.to(el, { opacity: 1, duration: 0.25, overwrite: "auto" });
69
86
  };
70
87