@docmentis/udoc-viewer 0.6.11 → 0.6.13

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 (45) hide show
  1. package/dist/package.json +1 -1
  2. package/dist/src/UDocClient.d.ts +7 -0
  3. package/dist/src/UDocClient.d.ts.map +1 -1
  4. package/dist/src/UDocClient.js +1 -1
  5. package/dist/src/UDocClient.js.map +1 -1
  6. package/dist/src/UDocViewer.d.ts.map +1 -1
  7. package/dist/src/UDocViewer.js +3 -1
  8. package/dist/src/UDocViewer.js.map +1 -1
  9. package/dist/src/ui/viewer/components/Spread.d.ts.map +1 -1
  10. package/dist/src/ui/viewer/components/Spread.js +15 -1
  11. package/dist/src/ui/viewer/components/Spread.js.map +1 -1
  12. package/dist/src/ui/viewer/components/Viewport.d.ts.map +1 -1
  13. package/dist/src/ui/viewer/components/Viewport.js +153 -22
  14. package/dist/src/ui/viewer/components/Viewport.js.map +1 -1
  15. package/dist/src/ui/viewer/effects.d.ts.map +1 -1
  16. package/dist/src/ui/viewer/effects.js +54 -82
  17. package/dist/src/ui/viewer/effects.js.map +1 -1
  18. package/dist/src/ui/viewer/state.d.ts +2 -0
  19. package/dist/src/ui/viewer/state.d.ts.map +1 -1
  20. package/dist/src/ui/viewer/state.js +1 -0
  21. package/dist/src/ui/viewer/state.js.map +1 -1
  22. package/dist/src/ui/viewer/styles-inline.d.ts +1 -1
  23. package/dist/src/ui/viewer/styles-inline.d.ts.map +1 -1
  24. package/dist/src/ui/viewer/styles-inline.js +37 -47
  25. package/dist/src/ui/viewer/styles-inline.js.map +1 -1
  26. package/dist/src/ui/viewer/transition.d.ts +26 -0
  27. package/dist/src/ui/viewer/transition.d.ts.map +1 -0
  28. package/dist/src/ui/viewer/transition.js +898 -0
  29. package/dist/src/ui/viewer/transition.js.map +1 -0
  30. package/dist/src/wasm/udoc.d.ts +83 -6
  31. package/dist/src/wasm/udoc.js +9 -9
  32. package/dist/src/wasm/udoc_bg.wasm +0 -0
  33. package/dist/src/wasm/udoc_bg.wasm.d.ts +3 -3
  34. package/dist/src/worker/WorkerClient.d.ts +24 -5
  35. package/dist/src/worker/WorkerClient.d.ts.map +1 -1
  36. package/dist/src/worker/WorkerClient.js +243 -98
  37. package/dist/src/worker/WorkerClient.js.map +1 -1
  38. package/dist/src/worker/index.d.ts +1 -1
  39. package/dist/src/worker/index.d.ts.map +1 -1
  40. package/dist/src/worker/worker-inline.js +1 -1
  41. package/dist/src/worker/worker.d.ts +2 -0
  42. package/dist/src/worker/worker.d.ts.map +1 -1
  43. package/dist/src/worker/worker.js +11 -10
  44. package/dist/src/worker/worker.js.map +1 -1
  45. package/package.json +1 -1
@@ -0,0 +1,898 @@
1
+ /**
2
+ * Slide transition animations for PPTX spread-mode navigation.
3
+ *
4
+ * IMPORTANT: Effects must NEVER set `transform` on the incoming element.
5
+ * The incoming element is the real spread — transforms on it would change
6
+ * its layout position, causing scrollbar flicker, resize-observer loops,
7
+ * and page-jump bugs. Only use `opacity` and `clip-path` on incoming.
8
+ * The outgoing element is a disposable snapshot overlay, so `transform`
9
+ * and `opacity` are safe on it.
10
+ */
11
+ /**
12
+ * Run a slide transition animation between two spread elements.
13
+ *
14
+ * @param outgoing The snapshot overlay (will be removed on complete)
15
+ * @param incoming The real spread element (already mounted and positioned)
16
+ * @param transition Transition descriptor from PageInfo
17
+ * @param forward Navigation direction (true = forward, false = backward)
18
+ * @param onComplete Called when the transition finishes (or is cancelled)
19
+ */
20
+ export function runTransition(outgoing, incoming, transition, forward, onComplete) {
21
+ const durationMs = transition.durationMs ?? 500;
22
+ const apply = resolveEffect(transition.effect, forward);
23
+ // Instant transitions
24
+ if (durationMs <= 0 || !apply) {
25
+ onComplete();
26
+ return { cancel() { } };
27
+ }
28
+ // Prepare elements for animation.
29
+ // No will-change on either element — it creates a new compositing layer
30
+ // with different sub-pixel alignment, causing a visible 1px shift.
31
+ // Modern browsers handle short transform/opacity animations efficiently
32
+ // without the hint.
33
+ outgoing.style.pointerEvents = "none";
34
+ // The snapshot is inserted before the spread in the DOM, so the spread
35
+ // (incoming) naturally paints on top. Effects that need the snapshot on
36
+ // top set outgoing.style.zIndex = "1". We never set zIndex on the
37
+ // incoming element to avoid creating a stacking context (causes 1px shift).
38
+ // Set initial frame
39
+ apply(0, outgoing, incoming);
40
+ let rafId = 0;
41
+ let done = false;
42
+ const startTime = performance.now();
43
+ function tick(now) {
44
+ if (done)
45
+ return;
46
+ const elapsed = now - startTime;
47
+ const raw = Math.min(elapsed / durationMs, 1);
48
+ const t = easeInOut(raw);
49
+ apply(t, outgoing, incoming);
50
+ if (raw < 1) {
51
+ rafId = requestAnimationFrame(tick);
52
+ }
53
+ else {
54
+ finish();
55
+ }
56
+ }
57
+ function finish() {
58
+ if (done)
59
+ return;
60
+ done = true;
61
+ cancelAnimationFrame(rafId);
62
+ resetOutgoing(outgoing);
63
+ resetIncoming(incoming);
64
+ onComplete();
65
+ }
66
+ rafId = requestAnimationFrame(tick);
67
+ return {
68
+ cancel() {
69
+ finish();
70
+ },
71
+ };
72
+ }
73
+ // ---------------------------------------------------------------------------
74
+ // Easing
75
+ // ---------------------------------------------------------------------------
76
+ function easeInOut(t) {
77
+ return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
78
+ }
79
+ // ---------------------------------------------------------------------------
80
+ // Style helpers
81
+ // ---------------------------------------------------------------------------
82
+ /** Reset the outgoing snapshot — about to be removed from DOM anyway. */
83
+ function resetOutgoing(el) {
84
+ el.style.pointerEvents = "";
85
+ el.style.zIndex = "";
86
+ el.style.opacity = "";
87
+ el.style.clipPath = "";
88
+ el.style.transform = "none";
89
+ el.style.filter = "";
90
+ }
91
+ /** Reset the incoming spread — only clear opacity/clip-path. NEVER touch transform or zIndex. */
92
+ function resetIncoming(el) {
93
+ el.style.opacity = "";
94
+ el.style.clipPath = "";
95
+ el.style.maskImage = "";
96
+ el.style.setProperty("-webkit-mask-image", "");
97
+ }
98
+ // ---------------------------------------------------------------------------
99
+ // Direction helpers
100
+ // ---------------------------------------------------------------------------
101
+ function sideToTranslate(dir, progress) {
102
+ const p = progress * 100;
103
+ switch (dir) {
104
+ case "left":
105
+ return `translateX(${-p}%)`;
106
+ case "right":
107
+ return `translateX(${p}%)`;
108
+ case "up":
109
+ return `translateY(${-p}%)`;
110
+ case "down":
111
+ return `translateY(${p}%)`;
112
+ }
113
+ }
114
+ function oppositeSide(dir) {
115
+ switch (dir) {
116
+ case "left":
117
+ return "right";
118
+ case "right":
119
+ return "left";
120
+ case "up":
121
+ return "down";
122
+ case "down":
123
+ return "up";
124
+ }
125
+ }
126
+ function eightDirToTranslate(dir, progress) {
127
+ const p = progress * 100;
128
+ const x = dir === "left" || dir === "leftUp" || dir === "leftDown"
129
+ ? -p
130
+ : dir === "right" || dir === "rightUp" || dir === "rightDown"
131
+ ? p
132
+ : 0;
133
+ const y = dir === "up" || dir === "leftUp" || dir === "rightUp"
134
+ ? -p
135
+ : dir === "down" || dir === "leftDown" || dir === "rightDown"
136
+ ? p
137
+ : 0;
138
+ return `translate(${x}%, ${y}%)`;
139
+ }
140
+ function oppositeEightDir(dir) {
141
+ const map = {
142
+ left: "right",
143
+ right: "left",
144
+ up: "down",
145
+ down: "up",
146
+ leftUp: "rightDown",
147
+ rightUp: "leftDown",
148
+ leftDown: "rightUp",
149
+ rightDown: "leftUp",
150
+ };
151
+ return map[dir];
152
+ }
153
+ /**
154
+ * Build a clip-path: inset() that reveals the incoming element
155
+ * progressively from the given direction.
156
+ */
157
+ function revealInset(dir, t) {
158
+ const p = (1 - t) * 100;
159
+ switch (dir) {
160
+ case "right":
161
+ return `inset(0 0 0 ${p}%)`;
162
+ case "left":
163
+ return `inset(0 ${p}% 0 0)`;
164
+ case "down":
165
+ return `inset(${p}% 0 0 0)`;
166
+ case "up":
167
+ return `inset(0 0 ${p}% 0)`;
168
+ }
169
+ }
170
+ function eightDirToRevealInset(dir, t) {
171
+ const p = (1 - t) * 100;
172
+ let top = 0, right = 0, bottom = 0, left = 0;
173
+ if (dir === "left" || dir === "leftUp" || dir === "leftDown")
174
+ right = p;
175
+ if (dir === "right" || dir === "rightUp" || dir === "rightDown")
176
+ left = p;
177
+ if (dir === "up" || dir === "leftUp" || dir === "rightUp")
178
+ bottom = p;
179
+ if (dir === "down" || dir === "leftDown" || dir === "rightDown")
180
+ top = p;
181
+ return `inset(${top}% ${right}% ${bottom}% ${left}%)`;
182
+ }
183
+ function resolveEffect(effect, forward) {
184
+ switch (effect.type) {
185
+ case "replace":
186
+ return null;
187
+ case "fade":
188
+ return effect.throughBlack ? fadeThroughBlack : crossfade;
189
+ case "cut":
190
+ return effect.throughBlack ? cutThroughBlack : null;
191
+ case "dissolve":
192
+ return dissolveEffect();
193
+ case "push": {
194
+ const dir = forward ? effect.direction : oppositeSide(effect.direction);
195
+ return pushEffect(dir);
196
+ }
197
+ case "wipe": {
198
+ const dir = forward ? effect.direction : oppositeSide(effect.direction);
199
+ return wipeEffect(dir);
200
+ }
201
+ case "cover":
202
+ return coverEffect(oppositeEightDir(effect.direction));
203
+ case "uncover":
204
+ return uncoverEffect(effect.direction);
205
+ case "pull":
206
+ return pullEffect(effect.direction);
207
+ case "split":
208
+ return splitEffect(effect.orientation, effect.inOut);
209
+ case "zoom":
210
+ case "box":
211
+ return effect.inOut === "in" ? revealFromCenter : shrinkOutgoing;
212
+ case "fly": {
213
+ const dir = forward ? effect.direction : oppositeSide(effect.direction);
214
+ return flyEffect(dir);
215
+ }
216
+ case "glitter": {
217
+ const dir = forward ? effect.direction : oppositeSide(effect.direction);
218
+ return wipeEffect(dir);
219
+ }
220
+ case "circle":
221
+ return circleEffect;
222
+ case "diamond":
223
+ return diamondEffect;
224
+ case "plus":
225
+ return plusEffect;
226
+ case "wedge":
227
+ return wedgeEffect;
228
+ case "wheel":
229
+ return wheelSweepEffect(effect.spokes, true);
230
+ case "newsflash":
231
+ return circleEffect;
232
+ case "blinds":
233
+ return blindsEffect(effect.orientation);
234
+ case "checker":
235
+ return checkerEffect(effect.orientation);
236
+ case "comb":
237
+ return combEffect(effect.orientation);
238
+ case "strips":
239
+ return stripsEffect(effect.direction);
240
+ case "random": {
241
+ const options = [
242
+ crossfade,
243
+ fadeThroughBlack,
244
+ wipeEffect("left"),
245
+ wipeEffect("right"),
246
+ wipeEffect("up"),
247
+ wipeEffect("down"),
248
+ pushEffect("left"),
249
+ pushEffect("right"),
250
+ blindsEffect("horizontal"),
251
+ blindsEffect("vertical"),
252
+ circleEffect,
253
+ diamondEffect,
254
+ plusEffect,
255
+ splitEffect("horizontal", "out"),
256
+ splitEffect("vertical", "out"),
257
+ ];
258
+ return options[Math.floor(Math.random() * options.length)];
259
+ }
260
+ case "randomBar":
261
+ return randomBarEffect(effect.orientation);
262
+ // PPTX 2010+ effects (p14)
263
+ case "vortex":
264
+ return shrinkOutgoing;
265
+ case "switch":
266
+ case "flip":
267
+ case "gallery":
268
+ case "conveyor":
269
+ return flyEffect(effect.direction);
270
+ case "ripple":
271
+ return circleEffect;
272
+ case "honeycomb":
273
+ return crossfade;
274
+ case "prism":
275
+ return wipeEffect(effect.direction);
276
+ case "doors":
277
+ return splitEffect(effect.orientation, "out");
278
+ case "window":
279
+ return splitEffect(effect.orientation, "in");
280
+ case "ferris":
281
+ return flyEffect(effect.direction);
282
+ case "pan": {
283
+ const dir = forward ? effect.direction : oppositeSide(effect.direction);
284
+ return pushEffect(dir);
285
+ }
286
+ case "warp":
287
+ case "flythrough":
288
+ return effect.inOut === "in" ? revealFromCenter : shrinkOutgoing;
289
+ case "flash":
290
+ return fadeThroughBlack;
291
+ case "shred":
292
+ return effect.inOut === "in" ? revealFromCenter : shrinkOutgoing;
293
+ case "reveal":
294
+ return effect.throughBlack ? fadeThroughBlack : wipeEffect(effect.direction);
295
+ case "wheelReverse":
296
+ return wheelSweepEffect(effect.spokes, false);
297
+ case "morph":
298
+ return crossfade;
299
+ default:
300
+ return crossfade;
301
+ }
302
+ }
303
+ // ---------------------------------------------------------------------------
304
+ // Effect implementations
305
+ //
306
+ // Rules:
307
+ // outgoing (snapshot): transform + opacity OK
308
+ // incoming (real spread): opacity + clip-path ONLY, NO transform
309
+ // ---------------------------------------------------------------------------
310
+ // All effects below follow the z-order convention:
311
+ // - Snapshot (outgoing) is inserted BEFORE the spread in the DOM,
312
+ // so the spread (incoming) naturally paints on top.
313
+ // - Effects where the snapshot must be on top set outgoing.style.zIndex = "1".
314
+ // - We NEVER set zIndex on the incoming element.
315
+ /** Simple crossfade — outgoing on top, faded out to reveal incoming below. */
316
+ function crossfade(t, outgoing, _incoming) {
317
+ outgoing.style.zIndex = "1";
318
+ outgoing.style.opacity = `${1 - t}`;
319
+ }
320
+ /**
321
+ * Fade through black: outgoing dims to solid black, then black fades out
322
+ * to reveal incoming. Uses CSS brightness filter so the "black" is real,
323
+ * not dependent on the viewer background color.
324
+ */
325
+ function fadeThroughBlack(t, outgoing, _incoming) {
326
+ outgoing.style.zIndex = "1";
327
+ if (t < 0.5) {
328
+ // Outgoing content dims to black
329
+ outgoing.style.filter = `brightness(${1 - t * 2})`;
330
+ outgoing.style.opacity = "1";
331
+ }
332
+ else {
333
+ // Black overlay fades out, revealing incoming below
334
+ outgoing.style.filter = "brightness(0)";
335
+ outgoing.style.opacity = `${1 - (t - 0.5) * 2}`;
336
+ }
337
+ }
338
+ /**
339
+ * Hard cut through black: instant switch to black, then instant switch
340
+ * to new slide. Uses brightness(0) for actual black.
341
+ */
342
+ function cutThroughBlack(t, outgoing, _incoming) {
343
+ outgoing.style.zIndex = "1";
344
+ if (t < 0.5) {
345
+ // Show solid black (outgoing rendered as black)
346
+ outgoing.style.filter = "brightness(0)";
347
+ outgoing.style.opacity = "1";
348
+ }
349
+ else {
350
+ // Remove outgoing entirely, incoming visible below
351
+ outgoing.style.opacity = "0";
352
+ }
353
+ }
354
+ /**
355
+ * Push: snapshot slides away on top, incoming revealed via clip-path underneath.
356
+ */
357
+ function pushEffect(dir) {
358
+ return (t, outgoing, incoming) => {
359
+ outgoing.style.zIndex = "1";
360
+ outgoing.style.transform = sideToTranslate(dir, t);
361
+ incoming.style.clipPath = revealInset(oppositeSide(dir), t);
362
+ };
363
+ }
364
+ /**
365
+ * Wipe: incoming revealed via clip-path on top of static snapshot.
366
+ */
367
+ function wipeEffect(dir) {
368
+ return (t, _outgoing, incoming) => {
369
+ incoming.style.clipPath = revealInset(dir, t);
370
+ };
371
+ }
372
+ /**
373
+ * Cover: incoming revealed via clip-path on top of static snapshot.
374
+ */
375
+ function coverEffect(dir) {
376
+ return (t, _outgoing, incoming) => {
377
+ incoming.style.clipPath = eightDirToRevealInset(dir, t);
378
+ };
379
+ }
380
+ /**
381
+ * Uncover: snapshot slides away on top, revealing incoming underneath.
382
+ */
383
+ function uncoverEffect(dir) {
384
+ return (t, outgoing, _incoming) => {
385
+ outgoing.style.zIndex = "1";
386
+ outgoing.style.transform = eightDirToTranslate(dir, t);
387
+ };
388
+ }
389
+ /**
390
+ * Pull: snapshot slides away on top while incoming is revealed via clip-path.
391
+ */
392
+ function pullEffect(dir) {
393
+ return (t, outgoing, incoming) => {
394
+ outgoing.style.zIndex = "1";
395
+ outgoing.style.transform = eightDirToTranslate(dir, t);
396
+ incoming.style.clipPath = eightDirToRevealInset(oppositeEightDir(dir), t);
397
+ };
398
+ }
399
+ /**
400
+ * Split "out": incoming revealed from center outward — clip-path on incoming.
401
+ * Split "in": incoming revealed from edges inward — clip-path on snapshot (outgoing).
402
+ */
403
+ function splitEffect(orientation, inOut) {
404
+ if (inOut === "out") {
405
+ // Center outward: incoming starts fully clipped, opens from center
406
+ return (t, _outgoing, incoming) => {
407
+ const p = (1 - t) * 50;
408
+ incoming.style.clipPath = orientation === "horizontal" ? `inset(${p}% 0)` : `inset(0 ${p}%)`;
409
+ };
410
+ }
411
+ else {
412
+ // Edges inward: snapshot collapses toward center, revealing incoming at edges
413
+ return (t, outgoing, _incoming) => {
414
+ outgoing.style.zIndex = "1";
415
+ const p = t * 50;
416
+ outgoing.style.clipPath = orientation === "horizontal" ? `inset(${p}% 0)` : `inset(0 ${p}%)`;
417
+ };
418
+ }
419
+ }
420
+ /**
421
+ * Zoom/box in: incoming revealed from center via inset clip-path.
422
+ */
423
+ function revealFromCenter(t, _outgoing, incoming) {
424
+ const p = (1 - t) * 50;
425
+ incoming.style.clipPath = `inset(${p}% ${p}% ${p}% ${p}%)`;
426
+ }
427
+ /**
428
+ * Zoom/box out: snapshot shrinks away on top, incoming visible underneath.
429
+ */
430
+ function shrinkOutgoing(t, outgoing, _incoming) {
431
+ outgoing.style.zIndex = "1";
432
+ outgoing.style.transform = `scale(${1 - t})`;
433
+ outgoing.style.opacity = `${1 - t}`;
434
+ }
435
+ /**
436
+ * Fly: snapshot slides and fades out on top, incoming fades in underneath.
437
+ */
438
+ function flyEffect(dir) {
439
+ return (t, outgoing, incoming) => {
440
+ outgoing.style.zIndex = "1";
441
+ outgoing.style.transform = `${sideToTranslate(dir, t)} scale(${1 - t * 0.5})`;
442
+ outgoing.style.opacity = `${1 - t}`;
443
+ incoming.style.opacity = `${t}`;
444
+ };
445
+ }
446
+ /**
447
+ * Circle reveal from center with soft/feathered edge.
448
+ * Uses mask-image radial gradient for a blurred boundary like PowerPoint.
449
+ * Radius 71% ≈ sqrt(50² + 50²) to fully cover rectangular corners.
450
+ */
451
+ function circleEffect(t, _outgoing, incoming) {
452
+ if (t >= 1) {
453
+ incoming.style.maskImage = "";
454
+ incoming.style.setProperty("-webkit-mask-image", "");
455
+ return;
456
+ }
457
+ if (t <= 0) {
458
+ const mask = "radial-gradient(circle 0% at 50% 50%, #000 0%, transparent 0%)";
459
+ incoming.style.maskImage = mask;
460
+ incoming.style.setProperty("-webkit-mask-image", mask);
461
+ return;
462
+ }
463
+ const r = t * 71;
464
+ const edge = Math.max(1, r * 0.12); // ~12% of radius for soft edge
465
+ const inner = Math.max(0, r - edge);
466
+ const mask = `radial-gradient(circle ${r}% at 50% 50%, #000 ${inner}%, transparent ${r + 0.5}%)`;
467
+ incoming.style.maskImage = mask;
468
+ incoming.style.setProperty("-webkit-mask-image", mask);
469
+ }
470
+ /** Diamond reveal from center. */
471
+ function diamondEffect(t, _outgoing, incoming) {
472
+ const p = t * 50;
473
+ incoming.style.clipPath = `polygon(50% ${50 - p}%, ${50 + p}% 50%, 50% ${50 + p}%, ${50 - p}% 50%)`;
474
+ }
475
+ /** Plus/cross reveal from center. */
476
+ function plusEffect(t, _outgoing, incoming) {
477
+ const h = t * 50;
478
+ const v = t * 50;
479
+ incoming.style.clipPath =
480
+ `polygon(${50 - v}% 0%, ${50 + v}% 0%, ${50 + v}% ${50 - h}%, 100% ${50 - h}%, ` +
481
+ `100% ${50 + h}%, ${50 + v}% ${50 + h}%, ${50 + v}% 100%, ${50 - v}% 100%, ` +
482
+ `${50 - v}% ${50 + h}%, 0% ${50 + h}%, 0% ${50 - h}%, ${50 - v}% ${50 - h}%)`;
483
+ }
484
+ /**
485
+ * Strips: diagonal wipe that reveals the incoming slide from a corner.
486
+ * The reveal starts at the corner and sweeps diagonally across.
487
+ */
488
+ function stripsEffect(dir) {
489
+ return (t, _outgoing, incoming) => {
490
+ if (t >= 1) {
491
+ incoming.style.clipPath = "none";
492
+ return;
493
+ }
494
+ if (t <= 0) {
495
+ incoming.style.clipPath = "polygon(0% 0%, 0% 0%, 0% 0%)";
496
+ return;
497
+ }
498
+ // d goes from 0 to 200 — the diagonal sweep distance across the rectangle
499
+ const d = t * 200;
500
+ let points;
501
+ if (d <= 100) {
502
+ // Triangle phase: expanding triangle from the corner
503
+ switch (dir) {
504
+ case "rightDown":
505
+ points = `0% 0%, ${d}% 0%, 0% ${d}%`;
506
+ break;
507
+ case "leftDown":
508
+ points = `100% 0%, ${100 - d}% 0%, 100% ${d}%`;
509
+ break;
510
+ case "rightUp":
511
+ points = `0% 100%, ${d}% 100%, 0% ${100 - d}%`;
512
+ break;
513
+ case "leftUp":
514
+ points = `100% 100%, ${100 - d}% 100%, 100% ${100 - d}%`;
515
+ break;
516
+ }
517
+ }
518
+ else {
519
+ // Quadrilateral phase: the diagonal has passed the opposite edges
520
+ const e = d - 100;
521
+ switch (dir) {
522
+ case "rightDown":
523
+ points = `0% 0%, 100% 0%, 100% ${e}%, ${e}% 100%, 0% 100%`;
524
+ break;
525
+ case "leftDown":
526
+ points = `100% 0%, 0% 0%, 0% ${e}%, ${100 - e}% 100%, 100% 100%`;
527
+ break;
528
+ case "rightUp":
529
+ points = `0% 100%, 100% 100%, 100% ${100 - e}%, ${e}% 0%, 0% 0%`;
530
+ break;
531
+ case "leftUp":
532
+ points = `100% 100%, 0% 100%, 0% ${100 - e}%, ${100 - e}% 0%, 100% 0%`;
533
+ break;
534
+ }
535
+ }
536
+ incoming.style.clipPath = `polygon(${points})`;
537
+ };
538
+ }
539
+ // ---------------------------------------------------------------------------
540
+ // Blinds / Comb
541
+ // ---------------------------------------------------------------------------
542
+ const BLIND_STRIPS = 6;
543
+ /**
544
+ * Blinds: 3D flip effect per strip.
545
+ * First half: outgoing strips narrow toward their top/left edge (rotating away).
546
+ * Second half: incoming strips widen from their top/left edge (rotating toward viewer).
547
+ * Simulates each strip flipping over to reveal the new slide on its back.
548
+ */
549
+ function blindsEffect(orientation) {
550
+ const N = BLIND_STRIPS;
551
+ return (t, outgoing, incoming) => {
552
+ if (t >= 1) {
553
+ incoming.style.clipPath = "none";
554
+ outgoing.style.clipPath = "polygon(0% 0%, 0% 0%, 0% 0%)";
555
+ return;
556
+ }
557
+ if (t <= 0) {
558
+ outgoing.style.zIndex = "1";
559
+ incoming.style.clipPath = "polygon(0% 0%, 0% 0%, 0% 0%)";
560
+ return;
561
+ }
562
+ if (t < 0.5) {
563
+ // First half: outgoing strips narrow (flip away from viewer)
564
+ outgoing.style.zIndex = "1";
565
+ const frac = 1 - t * 2; // 1 → 0
566
+ const outPoints = [];
567
+ if (orientation === "horizontal") {
568
+ const stripH = 100 / N;
569
+ for (let i = 0; i < N; i++) {
570
+ const top = i * stripH;
571
+ const bot = top + frac * stripH;
572
+ outPoints.push("0% 0%", `0% ${top}%`, `100% ${top}%`, `100% ${bot}%`, `0% ${bot}%`, `0% ${top}%`, "0% 0%");
573
+ }
574
+ }
575
+ else {
576
+ const stripW = 100 / N;
577
+ for (let i = 0; i < N; i++) {
578
+ const left = i * stripW;
579
+ const right = left + frac * stripW;
580
+ outPoints.push("0% 0%", `${left}% 0%`, `${right}% 0%`, `${right}% 100%`, `${left}% 100%`, `${left}% 0%`, "0% 0%");
581
+ }
582
+ }
583
+ outgoing.style.clipPath = `polygon(${outPoints.join(", ")})`;
584
+ incoming.style.clipPath = "polygon(0% 0%, 0% 0%, 0% 0%)";
585
+ }
586
+ else {
587
+ // Second half: incoming strips widen (flip toward viewer)
588
+ outgoing.style.opacity = "0";
589
+ const frac = (t - 0.5) * 2; // 0 → 1
590
+ const inPoints = [];
591
+ if (orientation === "horizontal") {
592
+ const stripH = 100 / N;
593
+ for (let i = 0; i < N; i++) {
594
+ const top = i * stripH;
595
+ const bot = top + frac * stripH;
596
+ inPoints.push("0% 0%", `0% ${top}%`, `100% ${top}%`, `100% ${bot}%`, `0% ${bot}%`, `0% ${top}%`, "0% 0%");
597
+ }
598
+ }
599
+ else {
600
+ const stripW = 100 / N;
601
+ for (let i = 0; i < N; i++) {
602
+ const left = i * stripW;
603
+ const right = left + frac * stripW;
604
+ inPoints.push("0% 0%", `${left}% 0%`, `${right}% 0%`, `${right}% 100%`, `${left}% 100%`, `${left}% 0%`, "0% 0%");
605
+ }
606
+ }
607
+ incoming.style.clipPath = `polygon(${inPoints.join(", ")})`;
608
+ }
609
+ };
610
+ }
611
+ /**
612
+ * Comb: N strips revealed from alternating directions.
613
+ * Horizontal = even strips from left, odd from right.
614
+ * Vertical = even strips from top, odd from bottom.
615
+ */
616
+ function combEffect(orientation) {
617
+ const N = BLIND_STRIPS;
618
+ return (t, _outgoing, incoming) => {
619
+ if (t >= 1) {
620
+ incoming.style.clipPath = "none";
621
+ return;
622
+ }
623
+ // Each rectangle is anchored through the origin (0% 0%) so that
624
+ // bridges between non-adjacent strips retrace and produce zero
625
+ // net winding — no visible triangles between strips.
626
+ const points = [];
627
+ if (orientation === "horizontal") {
628
+ const stripH = 100 / N;
629
+ for (let i = 0; i < N; i++) {
630
+ const top = i * stripH;
631
+ const bot = (i + 1) * stripH;
632
+ points.push("0% 0%");
633
+ if (i % 2 === 0) {
634
+ const r = t * 100;
635
+ points.push(`0% ${top}%`, `${r}% ${top}%`, `${r}% ${bot}%`, `0% ${bot}%`, `0% ${top}%`);
636
+ }
637
+ else {
638
+ const l = (1 - t) * 100;
639
+ points.push(`${l}% ${top}%`, `100% ${top}%`, `100% ${bot}%`, `${l}% ${bot}%`, `${l}% ${top}%`);
640
+ }
641
+ points.push("0% 0%");
642
+ }
643
+ }
644
+ else {
645
+ const stripW = 100 / N;
646
+ for (let i = 0; i < N; i++) {
647
+ const left = i * stripW;
648
+ const right = (i + 1) * stripW;
649
+ points.push("0% 0%");
650
+ if (i % 2 === 0) {
651
+ const b = t * 100;
652
+ points.push(`${left}% 0%`, `${right}% 0%`, `${right}% ${b}%`, `${left}% ${b}%`, `${left}% 0%`);
653
+ }
654
+ else {
655
+ const tp = (1 - t) * 100;
656
+ points.push(`${left}% ${tp}%`, `${right}% ${tp}%`, `${right}% 100%`, `${left}% 100%`, `${left}% ${tp}%`);
657
+ }
658
+ points.push("0% 0%");
659
+ }
660
+ }
661
+ incoming.style.clipPath = `polygon(${points.join(", ")})`;
662
+ };
663
+ }
664
+ // ---------------------------------------------------------------------------
665
+ // Wheel / Wedge
666
+ // ---------------------------------------------------------------------------
667
+ /** Arc resolution for wheel/wedge polygon approximation. */
668
+ const ARC_STEPS = 12;
669
+ /** Radius large enough to fully cover a rectangular element from center. */
670
+ const SWEEP_RADIUS = 75;
671
+ /**
672
+ * Wheel: N spokes sweep from 12 o'clock, revealing N sectors simultaneously.
673
+ * @param clockwise true = clockwise (wheel), false = counter-clockwise (wheelReverse)
674
+ */
675
+ function wheelSweepEffect(spokes, clockwise) {
676
+ const n = Math.max(1, spokes);
677
+ const dir = clockwise ? 1 : -1;
678
+ return (t, _outgoing, incoming) => {
679
+ if (t >= 1) {
680
+ incoming.style.clipPath = "none";
681
+ return;
682
+ }
683
+ if (t <= 0) {
684
+ incoming.style.clipPath = "polygon(50% 50%, 50% 50%, 50% 50%)";
685
+ return;
686
+ }
687
+ const sectorAngle = 360 / n;
688
+ const sweep = t * sectorAngle;
689
+ const points = [];
690
+ for (let s = 0; s < n; s++) {
691
+ const startDeg = -90 + s * sectorAngle;
692
+ points.push("50% 50%");
693
+ for (let j = 0; j <= ARC_STEPS; j++) {
694
+ const deg = startDeg + dir * (j / ARC_STEPS) * sweep;
695
+ const rad = (deg * Math.PI) / 180;
696
+ points.push(`${50 + SWEEP_RADIUS * Math.cos(rad)}% ${50 + SWEEP_RADIUS * Math.sin(rad)}%`);
697
+ }
698
+ points.push("50% 50%");
699
+ }
700
+ incoming.style.clipPath = `polygon(${points.join(", ")})`;
701
+ };
702
+ }
703
+ /**
704
+ * Wedge: two sectors expand symmetrically from 12 o'clock (one CW, one CCW).
705
+ */
706
+ function wedgeEffect(t, _outgoing, incoming) {
707
+ if (t >= 1) {
708
+ incoming.style.clipPath = "none";
709
+ return;
710
+ }
711
+ if (t <= 0) {
712
+ incoming.style.clipPath = "polygon(50% 50%, 50% 50%, 50% 50%)";
713
+ return;
714
+ }
715
+ const sweep = t * 180;
716
+ const points = ["50% 50%"];
717
+ // Counter-clockwise half
718
+ for (let j = ARC_STEPS; j >= 0; j--) {
719
+ const deg = -90 - (j / ARC_STEPS) * sweep;
720
+ const rad = (deg * Math.PI) / 180;
721
+ points.push(`${50 + SWEEP_RADIUS * Math.cos(rad)}% ${50 + SWEEP_RADIUS * Math.sin(rad)}%`);
722
+ }
723
+ // Clockwise half
724
+ for (let j = 0; j <= ARC_STEPS; j++) {
725
+ const deg = -90 + (j / ARC_STEPS) * sweep;
726
+ const rad = (deg * Math.PI) / 180;
727
+ points.push(`${50 + SWEEP_RADIUS * Math.cos(rad)}% ${50 + SWEEP_RADIUS * Math.sin(rad)}%`);
728
+ }
729
+ incoming.style.clipPath = `polygon(${points.join(", ")})`;
730
+ }
731
+ // ---------------------------------------------------------------------------
732
+ // Checker / Random Bar
733
+ // ---------------------------------------------------------------------------
734
+ const CHECKER_COLS = 8;
735
+ const CHECKER_ROWS = 6;
736
+ /**
737
+ * Checker: 3D flip effect per cell in a checkerboard pattern.
738
+ * Each cell flips individually: outgoing cell narrows (first half),
739
+ * then incoming cell widens (second half). The flip is staggered
740
+ * by position, sweeping "across" or "down" with checkerboard offset.
741
+ */
742
+ function checkerEffect(orientation) {
743
+ const cols = orientation === "horizontal" ? CHECKER_COLS : CHECKER_ROWS;
744
+ const rows = orientation === "horizontal" ? CHECKER_ROWS : CHECKER_COLS;
745
+ const sweepLen = (orientation === "horizontal" ? cols : rows) + 1;
746
+ // Each cell's flip takes 1 sweep-unit. Total time = sweepLen + 1 (for flip duration)
747
+ const totalLen = sweepLen + 1;
748
+ return (t, outgoing, incoming) => {
749
+ if (t >= 1) {
750
+ incoming.style.clipPath = "none";
751
+ outgoing.style.clipPath = "polygon(0% 0%, 0% 0%, 0% 0%)";
752
+ return;
753
+ }
754
+ const cellW = 100 / cols;
755
+ const cellH = 100 / rows;
756
+ const sweep = t * totalLen;
757
+ const outPoints = [];
758
+ const inPoints = [];
759
+ for (let r = 0; r < rows; r++) {
760
+ for (let c = 0; c < cols; c++) {
761
+ const pos = orientation === "horizontal" ? c : r;
762
+ const phase = (r + c) % 2;
763
+ const cellStart = pos + phase;
764
+ // cellT: 0 = not started, 0→0.5 = outgoing shrinks, 0.5→1 = incoming grows
765
+ const cellT = Math.max(0, Math.min(1, sweep - cellStart));
766
+ const left = c * cellW;
767
+ const top = r * cellH;
768
+ if (cellT <= 0) {
769
+ // Cell hasn't started flipping — outgoing fully visible
770
+ outPoints.push("0% 0%", `${left}% ${top}%`, `${left + cellW}% ${top}%`, `${left + cellW}% ${top + cellH}%`, `${left}% ${top + cellH}%`, `${left}% ${top}%`, "0% 0%");
771
+ }
772
+ else if (cellT < 0.5) {
773
+ // First half: outgoing cell narrows (flip away)
774
+ const frac = 1 - cellT * 2;
775
+ if (orientation === "horizontal") {
776
+ const bot = top + frac * cellH;
777
+ outPoints.push("0% 0%", `${left}% ${top}%`, `${left + cellW}% ${top}%`, `${left + cellW}% ${bot}%`, `${left}% ${bot}%`, `${left}% ${top}%`, "0% 0%");
778
+ }
779
+ else {
780
+ const right = left + frac * cellW;
781
+ outPoints.push("0% 0%", `${left}% ${top}%`, `${right}% ${top}%`, `${right}% ${top + cellH}%`, `${left}% ${top + cellH}%`, `${left}% ${top}%`, "0% 0%");
782
+ }
783
+ }
784
+ else {
785
+ // Second half: incoming cell widens (flip toward viewer)
786
+ const frac = (cellT - 0.5) * 2;
787
+ if (orientation === "horizontal") {
788
+ const bot = top + frac * cellH;
789
+ inPoints.push("0% 0%", `${left}% ${top}%`, `${left + cellW}% ${top}%`, `${left + cellW}% ${bot}%`, `${left}% ${bot}%`, `${left}% ${top}%`, "0% 0%");
790
+ }
791
+ else {
792
+ const right = left + frac * cellW;
793
+ inPoints.push("0% 0%", `${left}% ${top}%`, `${right}% ${top}%`, `${right}% ${top + cellH}%`, `${left}% ${top + cellH}%`, `${left}% ${top}%`, "0% 0%");
794
+ }
795
+ }
796
+ }
797
+ }
798
+ outgoing.style.zIndex = "1";
799
+ outgoing.style.clipPath = outPoints.length
800
+ ? `polygon(${outPoints.join(", ")})`
801
+ : "polygon(0% 0%, 0% 0%, 0% 0%)";
802
+ incoming.style.clipPath = inPoints.length ? `polygon(${inPoints.join(", ")})` : "polygon(0% 0%, 0% 0%, 0% 0%)";
803
+ };
804
+ }
805
+ // ---------------------------------------------------------------------------
806
+ // Dissolve
807
+ // ---------------------------------------------------------------------------
808
+ const DISSOLVE_COLS = 12;
809
+ const DISSOLVE_ROWS = 8;
810
+ /**
811
+ * Dissolve: random grid cells appear progressively, approximating PowerPoint's
812
+ * pixelated dissolve pattern. Cells snap fully visible in shuffled order.
813
+ */
814
+ function dissolveEffect() {
815
+ const total = DISSOLVE_COLS * DISSOLVE_ROWS;
816
+ // Fisher-Yates shuffle — computed once per transition
817
+ const order = Array.from({ length: total }, (_, i) => i);
818
+ for (let i = total - 1; i > 0; i--) {
819
+ const j = Math.floor(Math.random() * (i + 1));
820
+ [order[i], order[j]] = [order[j], order[i]];
821
+ }
822
+ return (t, _outgoing, incoming) => {
823
+ if (t >= 1) {
824
+ incoming.style.clipPath = "none";
825
+ return;
826
+ }
827
+ const revealed = Math.ceil(t * total);
828
+ if (revealed <= 0) {
829
+ incoming.style.clipPath = "polygon(0% 0%, 0% 0%, 0% 0%)";
830
+ return;
831
+ }
832
+ const cellW = 100 / DISSOLVE_COLS;
833
+ const cellH = 100 / DISSOLVE_ROWS;
834
+ const points = [];
835
+ for (let k = 0; k < revealed; k++) {
836
+ const idx = order[k];
837
+ const c = idx % DISSOLVE_COLS;
838
+ const r = Math.floor(idx / DISSOLVE_COLS);
839
+ const left = c * cellW;
840
+ const top = r * cellH;
841
+ points.push("0% 0%", `${left}% ${top}%`, `${left + cellW}% ${top}%`, `${left + cellW}% ${top + cellH}%`, `${left}% ${top + cellH}%`, `${left}% ${top}%`, "0% 0%");
842
+ }
843
+ incoming.style.clipPath = `polygon(${points.join(", ")})`;
844
+ };
845
+ }
846
+ // ---------------------------------------------------------------------------
847
+ // Random Bar
848
+ // ---------------------------------------------------------------------------
849
+ const RANDOM_BAR_COUNT = 10;
850
+ /**
851
+ * Random Bar: bars revealed in shuffled order.
852
+ * Each bar fully appears before the next starts, creating a staggered wipe.
853
+ */
854
+ function randomBarEffect(orientation) {
855
+ const N = RANDOM_BAR_COUNT;
856
+ // Fisher-Yates shuffle — computed once per transition
857
+ const order = Array.from({ length: N }, (_, i) => i);
858
+ for (let i = N - 1; i > 0; i--) {
859
+ const j = Math.floor(Math.random() * (i + 1));
860
+ [order[i], order[j]] = [order[j], order[i]];
861
+ }
862
+ return (t, _outgoing, incoming) => {
863
+ if (t >= 1) {
864
+ incoming.style.clipPath = "none";
865
+ return;
866
+ }
867
+ // How many bars are fully or partially revealed
868
+ const progress = t * N;
869
+ const revealed = Math.floor(progress);
870
+ const partial = progress - revealed;
871
+ const points = [];
872
+ for (let k = 0; k <= revealed && k < N; k++) {
873
+ const barIndex = order[k];
874
+ const barT = k < revealed ? 1 : partial;
875
+ if (barT <= 0)
876
+ continue;
877
+ if (orientation === "horizontal") {
878
+ const stripH = 100 / N;
879
+ const top = barIndex * stripH;
880
+ const right = barT * 100;
881
+ points.push(`0% ${top}%`, `${right}% ${top}%`, `${right}% ${top + stripH}%`, `0% ${top + stripH}%`, `0% ${top}%`);
882
+ }
883
+ else {
884
+ const stripW = 100 / N;
885
+ const left = barIndex * stripW;
886
+ const bot = barT * 100;
887
+ points.push(`${left}% 0%`, `${left + stripW}% 0%`, `${left + stripW}% ${bot}%`, `${left}% ${bot}%`, `${left}% 0%`);
888
+ }
889
+ }
890
+ if (points.length === 0) {
891
+ incoming.style.clipPath = "polygon(0% 0%, 0% 0%, 0% 0%)";
892
+ }
893
+ else {
894
+ incoming.style.clipPath = `polygon(${points.join(", ")})`;
895
+ }
896
+ };
897
+ }
898
+ //# sourceMappingURL=transition.js.map