@sliiu/present-transition-fx 0.0.0-stage → 0.1.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 (48) hide show
  1. package/COPYRIGHT +5 -0
  2. package/LICENSE +661 -0
  3. package/THIRD-PARTY-NOTICES.md +221 -0
  4. package/dist/cjs/2d.js +487 -0
  5. package/dist/cjs/3d.js +566 -0
  6. package/dist/cjs/geometry.js +141 -0
  7. package/dist/cjs/index.js +98 -0
  8. package/dist/cjs/morph-audit.js +603 -0
  9. package/dist/cjs/morph-match.js +103 -0
  10. package/dist/cjs/morph-path.js +649 -0
  11. package/dist/cjs/morph.js +223 -0
  12. package/dist/cjs/package.json +1 -0
  13. package/dist/cjs/registry.js +130 -0
  14. package/dist/cjs/snapshot.js +420 -0
  15. package/dist/cjs/twin.js +88 -0
  16. package/dist/cjs/types.js +2 -0
  17. package/dist/cjs/util.js +50 -0
  18. package/dist/cjs/webgl.js +271 -0
  19. package/dist/esm/2d.js +454 -0
  20. package/dist/esm/3d.js +538 -0
  21. package/dist/esm/geometry.js +127 -0
  22. package/dist/esm/index.js +6 -0
  23. package/dist/esm/morph-audit.js +558 -0
  24. package/dist/esm/morph-match.js +97 -0
  25. package/dist/esm/morph-path.js +636 -0
  26. package/dist/esm/morph.js +219 -0
  27. package/dist/esm/registry.js +91 -0
  28. package/dist/esm/snapshot.js +414 -0
  29. package/dist/esm/twin.js +84 -0
  30. package/dist/esm/types.js +1 -0
  31. package/dist/esm/util.js +43 -0
  32. package/dist/esm/webgl.js +231 -0
  33. package/dist/types/2d.d.ts +40 -0
  34. package/dist/types/3d.d.ts +38 -0
  35. package/dist/types/geometry.d.ts +38 -0
  36. package/dist/types/index.d.ts +7 -0
  37. package/dist/types/morph-audit.d.ts +127 -0
  38. package/dist/types/morph-match.d.ts +28 -0
  39. package/dist/types/morph-path.d.ts +37 -0
  40. package/dist/types/morph.d.ts +2 -0
  41. package/dist/types/registry.d.ts +6 -0
  42. package/dist/types/snapshot.d.ts +19 -0
  43. package/dist/types/twin.d.ts +2 -0
  44. package/dist/types/types.d.ts +13 -0
  45. package/dist/types/util.d.ts +12 -0
  46. package/dist/types/webgl.d.ts +5 -0
  47. package/package.json +26 -4
  48. package/README.md +0 -3
package/dist/esm/2d.js ADDED
@@ -0,0 +1,454 @@
1
+ import { cellMask, glitterSweep, splitMotion, stripsClips, wheelPolygon, wheelSpokes } from "./geometry.js";
2
+ import { dropTwins, mountTwin } from "./twin.js";
3
+ import { dirVec, easing, ms, runAll, STD_EASE } from "./util.js";
4
+ export const fade = async (ctx) => {
5
+ const a = ctx.ghost.animate([{ opacity: 1 }, { opacity: 0 }], { duration: ms(ctx), easing: easing(ctx), fill: "forwards" });
6
+ await runAll([a]);
7
+ };
8
+ export const cut = async () => {
9
+ // instant
10
+ };
11
+ export const push = async (ctx) => {
12
+ const { dx, dy } = dirVec(String(ctx.params.dir ?? "left"), ctx.stageSize.w, ctx.stageSize.h);
13
+ const sBase = ctx.stage.style.transform || "";
14
+ const gBase = ctx.ghost.style.transform || "";
15
+ const a = ctx.stage.animate([{ transform: `translate(${dx}px, ${dy}px) ${sBase}` }, { transform: sBase }], { duration: ms(ctx), easing: easing(ctx), fill: "forwards" });
16
+ const b = ctx.ghost.animate([{ transform: gBase }, { transform: `translate(${-dx}px, ${-dy}px) ${gBase}` }], { duration: ms(ctx), easing: easing(ctx), fill: "forwards" });
17
+ await runAll([a, b]);
18
+ };
19
+ export const cover = async (ctx) => {
20
+ const { dx, dy } = dirVec(String(ctx.params.dir ?? "left"), ctx.stageSize.w, ctx.stageSize.h);
21
+ const sBase = ctx.stage.style.transform || "";
22
+ const a = ctx.stage.animate([{ transform: `translate(${dx}px, ${dy}px) ${sBase}` }, { transform: sBase }], { duration: ms(ctx), easing: easing(ctx), fill: "forwards" });
23
+ await runAll([a]);
24
+ };
25
+ export const uncover = async (ctx) => {
26
+ const { dx, dy } = dirVec(String(ctx.params.dir ?? "left"), ctx.stageSize.w, ctx.stageSize.h);
27
+ const gBase = ctx.ghost.style.transform || "";
28
+ const a = ctx.ghost.animate([{ transform: gBase }, { transform: `translate(${-dx}px, ${-dy}px) ${gBase}` }], { duration: ms(ctx), easing: easing(ctx), fill: "forwards" });
29
+ await runAll([a]);
30
+ };
31
+ export const wipe = async (ctx) => {
32
+ const dir = String(ctx.params.dir ?? "left");
33
+ const clipStart = wipeClip(dir, true);
34
+ const clipEnd = wipeClip(dir, false);
35
+ const a = ctx.stage.animate([{ clipPath: clipStart }, { clipPath: clipEnd }], { duration: ms(ctx), easing: easing(ctx), fill: "forwards" });
36
+ await runAll([a], () => {
37
+ ctx.stage.style.clipPath = "";
38
+ });
39
+ };
40
+ export const split = async (ctx) => {
41
+ const motion = splitMotion(ctx.params.orient, ctx.params.dir);
42
+ if (motion.kind === "meet") {
43
+ await splitMeet(ctx, motion.axis);
44
+ return;
45
+ }
46
+ const a = ctx.stage.animate([{ clipPath: motion.clip }, { clipPath: "inset(0 0 0 0)" }], { duration: ms(ctx), easing: easing(ctx), fill: "forwards" });
47
+ await runAll([a], () => {
48
+ ctx.stage.style.clipPath = "";
49
+ });
50
+ };
51
+ export const zoom = async (ctx) => {
52
+ const out = String(ctx.params.dir ?? "in") === "out";
53
+ const sBase = ctx.stage.style.transform || "";
54
+ const startScale = out ? 1.7 : 0.3;
55
+ const a = ctx.stage.animate([
56
+ { transform: `${sBase} scale(${startScale})`, opacity: 0 },
57
+ { transform: sBase, opacity: 1 },
58
+ ], { duration: ms(ctx), easing: easing(ctx), fill: "forwards" });
59
+ const b = ctx.ghost.animate([{ opacity: 1 }, { opacity: 0 }], { duration: ms(ctx), easing: easing(ctx), fill: "forwards" });
60
+ await runAll([a, b]);
61
+ };
62
+ export const blinds = async (ctx) => {
63
+ const horiz = String(ctx.params.orient ?? "horz") !== "vert";
64
+ return runContentBands(ctx, 6, horiz, "together");
65
+ };
66
+ export const checker = async (ctx) => {
67
+ const horiz = String(ctx.params.orient ?? "horz") !== "vert";
68
+ return runChecker(ctx, horiz);
69
+ };
70
+ export const randomBar = async (ctx) => {
71
+ const horiz = String(ctx.params.orient ?? "horz") !== "vert";
72
+ return runContentBands(ctx, 10, horiz, "random");
73
+ };
74
+ /** 旧页切成小格,按固定乱序一块块消失。着色器快照失败时用这条。 */
75
+ export const dissolve = async (ctx) => {
76
+ const cols = 10;
77
+ const rows = 6;
78
+ const cells = [];
79
+ for (let r = 0; r < rows; r++) {
80
+ for (let c = 0; c < cols; c++) {
81
+ const top = (r / rows) * 100;
82
+ const bottom = ((rows - r - 1) / rows) * 100;
83
+ const left = (c / cols) * 100;
84
+ const right = ((cols - c - 1) / cols) * 100;
85
+ cells.push(mountTwin(ctx.ghost, `inset(${top}% ${right}% ${bottom}% ${left}%)`));
86
+ }
87
+ }
88
+ const prev = ctx.ghost.style.visibility;
89
+ ctx.ghost.style.visibility = "hidden";
90
+ const dur = ms(ctx);
91
+ const n = cells.length;
92
+ const anims = cells.map((el, i) => {
93
+ const rank = (i * 17 + 5) % n;
94
+ return el.animate([{ opacity: 1 }, { opacity: 0 }], { duration: Math.max(80, dur * 0.22), delay: (rank / n) * dur * 0.78, easing: "linear", fill: "forwards" });
95
+ });
96
+ await runAll(anims, () => dropTwins(cells, ctx.ghost, prev));
97
+ };
98
+ export const newsflash = async (ctx) => {
99
+ const sBase = ctx.stage.style.transform || "";
100
+ const a = ctx.stage.animate([
101
+ { transform: `${sBase} translate(-100%, -100%) scale(0.1) rotate(-540deg)`, opacity: 0 },
102
+ { transform: sBase, opacity: 1 },
103
+ ], { duration: ms(ctx), easing: STD_EASE, fill: "forwards" });
104
+ await runAll([a]);
105
+ };
106
+ async function runWheel(ctx, reverse) {
107
+ const n = wheelSpokes(ctx.params.pattern);
108
+ const a = ctx.stage.animate([{ clipPath: wheelPolygon(n, 0, reverse) }, { clipPath: wheelPolygon(n, 1, reverse) }], { duration: ms(ctx), easing: easing(ctx), fill: "forwards" });
109
+ await runAll([a], () => {
110
+ ctx.stage.style.clipPath = "";
111
+ });
112
+ }
113
+ export const wheel = (ctx) => runWheel(ctx, false);
114
+ export const wheelReverse = (ctx) => runWheel(ctx, true);
115
+ export const circle = async (ctx) => {
116
+ const a = ctx.stage.animate([
117
+ { clipPath: "circle(0% at 50% 50%)" },
118
+ { clipPath: "circle(75% at 50% 50%)" },
119
+ ], { duration: ms(ctx), easing: easing(ctx), fill: "forwards" });
120
+ await runAll([a], () => {
121
+ ctx.stage.style.clipPath = "";
122
+ });
123
+ };
124
+ export const diamond = async (ctx) => {
125
+ const a = ctx.stage.animate([
126
+ { clipPath: "polygon(50% 50%, 50% 50%, 50% 50%, 50% 50%)" },
127
+ { clipPath: "polygon(50% -10%, 110% 50%, 50% 110%, -10% 50%)" },
128
+ ], { duration: ms(ctx), easing: easing(ctx), fill: "forwards" });
129
+ await runAll([a], () => {
130
+ ctx.stage.style.clipPath = "";
131
+ });
132
+ };
133
+ export const plus = async (ctx) => {
134
+ const a = ctx.stage.animate([
135
+ { clipPath: "polygon(50% 50%, 50% 50%, 50% 50%, 50% 50%, 50% 50%, 50% 50%, 50% 50%, 50% 50%, 50% 50%, 50% 50%, 50% 50%, 50% 50%)" },
136
+ { clipPath: "polygon(33% 0%, 67% 0%, 67% 33%, 100% 33%, 100% 67%, 67% 67%, 67% 100%, 33% 100%, 33% 67%, 0% 67%, 0% 33%, 33% 33%)" },
137
+ ], { duration: ms(ctx), easing: easing(ctx), fill: "forwards" });
138
+ await runAll([a], () => {
139
+ ctx.stage.style.clipPath = "";
140
+ });
141
+ };
142
+ export const random = async (ctx) => {
143
+ const opts = [fade, push, wipe, split, dissolve];
144
+ const pick = opts[Math.floor(Math.random() * opts.length)] ?? fade;
145
+ await pick(ctx);
146
+ };
147
+ export const fall = async (ctx) => {
148
+ const sBase = ctx.stage.style.transform || "";
149
+ const gBase = ctx.ghost.style.transform || "";
150
+ const a = ctx.stage.animate([
151
+ { transform: `${sBase} translateY(-100%)`, opacity: 0 },
152
+ { transform: sBase, opacity: 1 },
153
+ ], { duration: ms(ctx), easing: "cubic-bezier(0.22, 1, 0.36, 1)", fill: "forwards" });
154
+ const b = ctx.ghost.animate([
155
+ { transform: gBase, opacity: 1 },
156
+ { transform: `${gBase} translateY(100%) rotate(8deg)`, opacity: 0 },
157
+ ], { duration: ms(ctx), easing: "cubic-bezier(0.55, 0, 0.55, 0.2)", fill: "forwards" });
158
+ await runAll([a, b]);
159
+ };
160
+ export const reveal = async (ctx) => {
161
+ const toRight = String(ctx.params.dir ?? "left") === "right";
162
+ const gBase = ctx.ghost.style.transform || "";
163
+ const slide = toRight ? "translateX(22%)" : "translateX(-22%)";
164
+ const clipEnd = toRight ? "inset(0 0 0 100%)" : "inset(0 100% 0 0)";
165
+ const a = ctx.ghost.animate([
166
+ { transform: gBase, clipPath: "inset(0 0 0 0)", opacity: 1, filter: "brightness(1)" },
167
+ { transform: `${slide} ${gBase}`, clipPath: clipEnd, opacity: 0, filter: "brightness(0.2)" },
168
+ ], { duration: ms(ctx), easing: STD_EASE, fill: "forwards" });
169
+ await runAll([a], () => {
170
+ ctx.ghost.style.clipPath = "";
171
+ });
172
+ };
173
+ export const flash = async (ctx) => {
174
+ const flashEl = ctx.ghost.ownerDocument.createElement("div");
175
+ flashEl.style.cssText = "position:absolute;inset:0;background:#fff;opacity:0;pointer-events:none;z-index:10";
176
+ ctx.ghost.parentElement?.appendChild(flashEl);
177
+ const dur = ms(ctx);
178
+ const a = flashEl.animate([{ opacity: 0 }, { opacity: 1, offset: 0.4 }, { opacity: 0 }], { duration: dur, easing: LINEAR_INOUT, fill: "forwards" });
179
+ const b = ctx.ghost.animate([{ opacity: 1 }, { opacity: 0, offset: 0.5 }, { opacity: 0 }], { duration: dur, easing: LINEAR_INOUT, fill: "forwards" });
180
+ await runAll([a, b], () => {
181
+ flashEl.remove();
182
+ });
183
+ };
184
+ function applyTileMask(el, mask) {
185
+ el.style.webkitMaskImage = mask;
186
+ el.style.maskImage = mask;
187
+ el.style.webkitMaskRepeat = "repeat";
188
+ el.style.maskRepeat = "repeat";
189
+ el.style.webkitMaskPosition = "center";
190
+ el.style.maskPosition = "center";
191
+ }
192
+ function clearTileMask(el) {
193
+ el.style.maskImage = "";
194
+ el.style.webkitMaskImage = "";
195
+ el.style.maskSize = "";
196
+ el.style.webkitMaskSize = "";
197
+ el.style.maskPosition = "";
198
+ el.style.webkitMaskPosition = "";
199
+ el.style.maskRepeat = "";
200
+ el.style.webkitMaskRepeat = "";
201
+ }
202
+ /** 菱形或六边形沿方向扫过,同时旧页闪几下。pattern 来自 OOXML hexagon / diamond。 */
203
+ export const glitter = async (ctx) => {
204
+ const pattern = ctx.params.pattern === "hexagon" ? "hexagon" : "diamond";
205
+ const sweep = glitterSweep(String(ctx.params.dir ?? "left"));
206
+ applyTileMask(ctx.stage, cellMask(pattern));
207
+ const dur = ms(ctx);
208
+ const a = ctx.stage.animate([
209
+ { maskSize: "0% 0%", maskPosition: sweep.from, webkitMaskSize: "0% 0%", webkitMaskPosition: sweep.from },
210
+ { maskSize: "16% 16%", maskPosition: sweep.to, webkitMaskSize: "16% 16%", webkitMaskPosition: sweep.to },
211
+ ], { duration: dur, easing: easing(ctx), fill: "forwards" });
212
+ const b = ctx.ghost.animate([
213
+ { opacity: 1, filter: "brightness(1)" },
214
+ { opacity: 0.85, filter: "brightness(2.6)", offset: 0.25 },
215
+ { opacity: 0.45, filter: "brightness(1.15)", offset: 0.55 },
216
+ { opacity: 0, filter: "brightness(2.2)" },
217
+ ], { duration: dur, easing: easing(ctx), fill: "forwards" });
218
+ await runAll([a, b], () => clearTileMask(ctx.stage));
219
+ };
220
+ /** 疏六边形从中心铺开,格子之间留缝,不闪。 */
221
+ export const honeycomb = async (ctx) => {
222
+ applyTileMask(ctx.stage, cellMask("honeycomb"));
223
+ const a = ctx.stage.animate([
224
+ { maskSize: "0% 0%", webkitMaskSize: "0% 0%" },
225
+ { maskSize: "34% 34%", webkitMaskSize: "34% 34%" },
226
+ ], { duration: ms(ctx), easing: easing(ctx), fill: "forwards" });
227
+ await runAll([a], () => clearTileMask(ctx.stage));
228
+ };
229
+ /** 斜向细条撕开。向外从中心铺开新页,向内让旧页的细条收到中心。 */
230
+ export const shred = async (ctx) => {
231
+ const inward = ctx.params.dir === "in";
232
+ const mask = "repeating-linear-gradient(108deg, #000 0 7px, transparent 7px 12px)";
233
+ const host = inward ? ctx.ghost : ctx.stage;
234
+ host.style.webkitMaskImage = mask;
235
+ host.style.maskImage = mask;
236
+ host.style.webkitMaskRepeat = "repeat";
237
+ host.style.maskRepeat = "repeat";
238
+ host.style.webkitMaskPosition = "center";
239
+ host.style.maskPosition = "center";
240
+ const from = inward ? "160% 160%" : "0% 0%";
241
+ const to = inward ? "0% 0%" : "160% 160%";
242
+ const a = host.animate([
243
+ { maskSize: from, webkitMaskSize: from },
244
+ { maskSize: to, webkitMaskSize: to },
245
+ ], { duration: ms(ctx), easing: easing(ctx), fill: "forwards" });
246
+ await runAll([a], () => {
247
+ host.style.maskImage = "";
248
+ host.style.webkitMaskImage = "";
249
+ host.style.maskSize = "";
250
+ host.style.webkitMaskSize = "";
251
+ host.style.maskPosition = "";
252
+ host.style.webkitMaskPosition = "";
253
+ });
254
+ };
255
+ /** 旧页切成小块飞散,新页从稍大缩回。 */
256
+ export const fracture = async (ctx) => {
257
+ const cols = 4;
258
+ const rows = 3;
259
+ const shards = [];
260
+ for (let r = 0; r < rows; r++) {
261
+ for (let c = 0; c < cols; c++) {
262
+ const top = (r / rows) * 100;
263
+ const bottom = ((rows - r - 1) / rows) * 100;
264
+ const left = (c / cols) * 100;
265
+ const right = ((cols - c - 1) / cols) * 100;
266
+ shards.push(mountTwin(ctx.ghost, `inset(${top}% ${right}% ${bottom}% ${left}%)`));
267
+ }
268
+ }
269
+ const prev = ctx.ghost.style.visibility;
270
+ ctx.ghost.style.visibility = "hidden";
271
+ const sBase = ctx.stage.style.transform || "";
272
+ const dur = ms(ctx);
273
+ const anims = [
274
+ ctx.stage.animate([
275
+ { transform: `${sBase} scale(1.08)`, opacity: 0 },
276
+ { transform: sBase, opacity: 1 },
277
+ ], { duration: dur, easing: easing(ctx), fill: "forwards" }),
278
+ ];
279
+ shards.forEach((el, i) => {
280
+ const c = i % cols;
281
+ const r = Math.floor(i / cols);
282
+ const dx = (c - (cols - 1) / 2) * 36;
283
+ const dy = (r - (rows - 1) / 2) * 28;
284
+ const rot = (i % 2 === 0 ? 1 : -1) * (14 + (i % 5) * 8);
285
+ const base = el.style.transform || "";
286
+ anims.push(el.animate([
287
+ { transform: base, opacity: 1 },
288
+ { transform: `translate(${dx}%, ${dy}%) rotate(${rot}deg) ${base}`, opacity: 0 },
289
+ ], { duration: dur, easing: easing(ctx), fill: "forwards" }));
290
+ });
291
+ await runAll(anims, () => dropTwins(shards, ctx.ghost, prev));
292
+ };
293
+ /** 旧页向左掀起,一道高光扫过新页。 */
294
+ export const prestige = async (ctx) => {
295
+ const parent = ctx.stage.parentElement;
296
+ const prevPerspective = parent?.style.perspective ?? "";
297
+ const prevStyle = parent?.style.transformStyle ?? "";
298
+ if (parent) {
299
+ parent.style.perspective = "1400px";
300
+ parent.style.transformStyle = "preserve-3d";
301
+ }
302
+ const doc = ctx.stage.ownerDocument;
303
+ const sheen = doc.createElement("div");
304
+ sheen.style.cssText = "position:absolute;inset:0;overflow:hidden;pointer-events:none;z-index:8";
305
+ const bar = doc.createElement("div");
306
+ bar.style.cssText = "position:absolute;top:-8%;bottom:-8%;width:18%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.78),transparent)";
307
+ sheen.appendChild(bar);
308
+ parent?.appendChild(sheen);
309
+ ctx.ghost.style.transformOrigin = "left center";
310
+ ctx.ghost.style.transformStyle = "preserve-3d";
311
+ const gBase = ctx.ghost.style.transform || "";
312
+ const sBase = ctx.stage.style.transform || "";
313
+ const dur = ms(ctx);
314
+ const a = ctx.ghost.animate([
315
+ { transform: gBase, opacity: 1, filter: "brightness(1)" },
316
+ { transform: `rotateY(-22deg) scale(0.94) ${gBase}`, opacity: 0, filter: "brightness(1.45)" },
317
+ ], { duration: dur, easing: easing(ctx), fill: "forwards" });
318
+ const b = ctx.stage.animate([
319
+ { opacity: 0, transform: `${sBase} scale(1.02)`, filter: "brightness(1.2)" },
320
+ { opacity: 1, transform: sBase, filter: "brightness(1)" },
321
+ ], { duration: dur, easing: easing(ctx), fill: "forwards" });
322
+ const c = bar.animate([{ transform: "translateX(-140%)" }, { transform: "translateX(640%)" }], { duration: dur, easing: "linear", fill: "forwards" });
323
+ await runAll([a, b, c], () => {
324
+ sheen.remove();
325
+ ctx.ghost.style.transformOrigin = "";
326
+ ctx.ghost.style.transformStyle = "";
327
+ if (parent) {
328
+ parent.style.perspective = prevPerspective;
329
+ parent.style.transformStyle = prevStyle;
330
+ }
331
+ });
332
+ };
333
+ /** 梳齿交错抽走旧页。水平是上下两排齿,垂直是左右两排齿。 */
334
+ export const comb = async (ctx) => {
335
+ const vert = String(ctx.params.orient ?? "horz") === "vert";
336
+ return runContentBands(ctx, 8, !vert, "comb");
337
+ };
338
+ /** 斜向宽带从指定角扫过。 */
339
+ export const strips = async (ctx) => {
340
+ const [start, mid, end] = stripsClips(String(ctx.params.dir ?? "bottomRight"));
341
+ const a = ctx.stage.animate([{ clipPath: start }, { clipPath: mid, offset: 0.55 }, { clipPath: end }], { duration: ms(ctx), easing: easing(ctx), fill: "forwards" });
342
+ await runAll([a], () => {
343
+ ctx.stage.style.clipPath = "";
344
+ });
345
+ };
346
+ function pieClip(portion) {
347
+ const steps = 24;
348
+ const pts = ["50% 50%"];
349
+ const p = Math.min(1, Math.max(0, portion));
350
+ for (let i = 0; i <= steps; i++) {
351
+ const ang = -Math.PI / 2 + p * (i / steps) * Math.PI * 2;
352
+ const x = 50 + Math.cos(ang) * 80;
353
+ const y = 50 + Math.sin(ang) * 80;
354
+ pts.push(`${x.toFixed(2)}% ${y.toFixed(2)}%`);
355
+ }
356
+ return `polygon(${pts.join(",")})`;
357
+ }
358
+ /** 从中心张开一块扇形,和整圆轮子分开。 */
359
+ export const wedge = async (ctx) => {
360
+ const a = ctx.stage.animate([{ clipPath: pieClip(0) }, { clipPath: pieClip(1) }], { duration: ms(ctx), easing: easing(ctx), fill: "forwards" });
361
+ await runAll([a], () => {
362
+ ctx.stage.style.clipPath = "";
363
+ });
364
+ };
365
+ const LINEAR_INOUT = "cubic-bezier(0.4, 0, 0.6, 1)";
366
+ function wipeClip(dir, start) {
367
+ switch (dir) {
368
+ case "left":
369
+ return start ? "inset(0 0 0 100%)" : "inset(0 0 0 0)";
370
+ case "right":
371
+ return start ? "inset(0 100% 0 0)" : "inset(0 0 0 0)";
372
+ case "up":
373
+ return start ? "inset(100% 0 0 0)" : "inset(0 0 0 0)";
374
+ case "down":
375
+ return start ? "inset(0 0 100% 0)" : "inset(0 0 0 0)";
376
+ default:
377
+ return start ? "inset(0 0 0 100%)" : "inset(0 0 0 0)";
378
+ }
379
+ }
380
+ async function splitMeet(ctx, axis) {
381
+ const left = mountTwin(ctx.stage, axis === "x" ? "inset(0 50% 0 0)" : "inset(0 0 50% 0)");
382
+ const right = mountTwin(ctx.stage, axis === "x" ? "inset(0 0 0 50%)" : "inset(50% 0 0 0)");
383
+ const prev = ctx.stage.style.visibility;
384
+ ctx.stage.style.visibility = "hidden";
385
+ const base = ctx.stage.style.transform || "";
386
+ const fromA = axis === "x" ? "translateX(-50%)" : "translateY(-50%)";
387
+ const fromB = axis === "x" ? "translateX(50%)" : "translateY(50%)";
388
+ const dur = ms(ctx);
389
+ const ease = easing(ctx);
390
+ await runAll([
391
+ left.animate([{ transform: `${fromA} ${base}` }, { transform: base }], { duration: dur, easing: ease, fill: "forwards" }),
392
+ right.animate([{ transform: `${fromB} ${base}` }, { transform: base }], { duration: dur, easing: ease, fill: "forwards" }),
393
+ ], () => dropTwins([left, right], ctx.stage, prev));
394
+ }
395
+ async function runContentBands(ctx, count, horizontal, order) {
396
+ const bands = [];
397
+ for (let i = 0; i < count; i++) {
398
+ const clip = horizontal
399
+ ? `inset(${(i / count) * 100}% 0 ${((count - i - 1) / count) * 100}% 0)`
400
+ : `inset(0 ${((count - i - 1) / count) * 100}% 0 ${(i / count) * 100}%)`;
401
+ bands.push(mountTwin(ctx.ghost, clip));
402
+ }
403
+ const prev = ctx.ghost.style.visibility;
404
+ ctx.ghost.style.visibility = "hidden";
405
+ const dur = ms(ctx);
406
+ const ease = easing(ctx);
407
+ const seq = order === "random" ? shuffle(count) : bands.map((_, i) => i);
408
+ const anims = seq.map((idx, k) => {
409
+ const el = bands[idx];
410
+ const base = el.style.transform || "";
411
+ if (order === "comb") {
412
+ const out = horizontal
413
+ ? idx % 2 === 0 ? "translateY(-100%)" : "translateY(100%)"
414
+ : idx % 2 === 0 ? "translateX(-100%)" : "translateX(100%)";
415
+ return el.animate([{ transform: base }, { transform: `${out} ${base}` }], { duration: dur, easing: ease, fill: "forwards", delay: (idx % 2) * dur * 0.08 });
416
+ }
417
+ const squash = horizontal ? "scaleY(0)" : "scaleX(0)";
418
+ const delay = order === "together" ? 0 : (k / count) * dur * 0.55;
419
+ const duration = order === "together" ? dur : dur * 0.45;
420
+ return el.animate([{ transform: base }, { transform: `${squash} ${base}` }], { duration, easing: ease, fill: "forwards", delay });
421
+ });
422
+ await runAll(anims, () => dropTwins(bands, ctx.ghost, prev));
423
+ }
424
+ function shuffle(n) {
425
+ const a = Array.from({ length: n }, (_, i) => i);
426
+ for (let i = a.length - 1; i > 0; i--) {
427
+ const j = Math.floor(Math.random() * (i + 1));
428
+ [a[i], a[j]] = [a[j], a[i]];
429
+ }
430
+ return a;
431
+ }
432
+ async function runChecker(ctx, horizontal) {
433
+ const cols = horizontal ? 8 : 5;
434
+ const rows = horizontal ? 5 : 8;
435
+ const cells = [];
436
+ for (let r = 0; r < rows; r++) {
437
+ for (let c = 0; c < cols; c++) {
438
+ const top = (r / rows) * 100;
439
+ const bottom = ((rows - r - 1) / rows) * 100;
440
+ const left = (c / cols) * 100;
441
+ const right = ((cols - c - 1) / cols) * 100;
442
+ cells.push(mountTwin(ctx.ghost, `inset(${top}% ${right}% ${bottom}% ${left}%)`));
443
+ }
444
+ }
445
+ const prev = ctx.ghost.style.visibility;
446
+ ctx.ghost.style.visibility = "hidden";
447
+ const dur = ms(ctx);
448
+ const anims = [];
449
+ cells.forEach((el, i) => {
450
+ const even = (Math.floor(i / cols) + (i % cols)) % 2 === 0;
451
+ anims.push(el.animate([{ opacity: 1 }, { opacity: 0 }], { duration: dur * 0.55, fill: "forwards", easing: STD_EASE, delay: even ? 0 : dur * 0.4 }));
452
+ });
453
+ await runAll(anims, () => dropTwins(cells, ctx.ghost, prev));
454
+ }