返回 oh-my-ppt
index-runtime.js
根目录 / resources / index-runtime.js
1 (function () {
2 'use strict';
3 // @ohmyppt-index-runtim:arcsin1:v2.0.19
4
5 var pages = JSON.parse(document.getElementById('pages-data')?.textContent || '[]');
6 var deckMetadata = (function () {
7 try {
8 return JSON.parse(document.getElementById('deck-metadata')?.textContent || '{}');
9 } catch (_) {
10 return {};
11 }
12 })();
13 var slideWidth = Number(deckMetadata.width) > 0 ? Number(deckMetadata.width) : 1600;
14 var slideHeight = Number(deckMetadata.height) > 0 ? Number(deckMetadata.height) : 900;
15 var frameViewport = document.getElementById('frameViewport');
16 var thumbs = document.getElementById('thumbs');
17 var deckSwitcher = document.getElementById('deckSwitcher');
18 var indicator = document.getElementById('indicator');
19 var prevBtn = document.getElementById('prevBtn');
20 var nextBtn = document.getElementById('nextBtn');
21 var tabsBtn = document.getElementById('tabsBtn');
22 var presentBtn = document.getElementById('presentBtn');
23 var fullscreenBtn = document.getElementById('fullscreenBtn');
24 var search = new URLSearchParams(window.location.search);
25 var embedMode = search.get('embed') === '1';
26 var presentMode = search.get('present') === '1';
27 var playbackMode = presentMode && !embedMode;
28 var currentPageId = '';
29 var fitRaf = 0;
30 var indexTransitionType = 'fade'; // default, overridden by container build
31 var indexTransitionDuration = 600; // ms
32 var playbackRequestSeq = 0;
33 var pendingPlaybackRequests = {};
34 var pageSwitchSeq = 0;
35 var isPageSwitching = false;
36 var prefetchedPageUrls = new Set();
37 var wheelDeltaBuffer = 0;
38 var wheelGestureLocked = false;
39 var wheelGestureLockDirection = 0;
40 var lastWheelNavigateAt = 0;
41 var wheelGestureUnlockTimer = 0;
42 var queuedNavigationOffset = 0;
43 var WHEEL_NAV_THRESHOLD = 80;
44 var WHEEL_NAV_COOLDOWN = 520;
45 var WHEEL_GESTURE_IDLE = 260;
46 var FRAME_LOAD_TIMEOUT = 3000;
47 var INDEX_TRANSITION_TYPES = {
48 none: true,
49 fade: true,
50 'slide-left': true,
51 'slide-up': true,
52 push: true,
53 wipe: true,
54 zoom: true,
55 flip: true,
56 stack: true,
57 rotate: true,
58 cube: true,
59 'cover-flow': true,
60 blur: true,
61 iris: true,
62 swing: true,
63 'center-reveal': true
64 };
65
66 function normalizeIndexTransitionType(type) {
67 var value = String(type || '').trim();
68 return INDEX_TRANSITION_TYPES[value] ? value : 'fade';
69 }
70
71 function clampTransitionDuration(type, durationMs) {
72 if (type === 'none') return 0;
73 var duration = Number(durationMs);
74 if (!Number.isFinite(duration)) duration = 600;
75 return Math.max(120, Math.min(1200, Math.round(duration)));
76 }
77
78 function prefersReducedMotion() {
79 try {
80 return Boolean(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
81 } catch (_) {
82 return false;
83 }
84 }
85
86 function ensureIndexTransitionBaseStyles() {
87 if (document.getElementById('ppt-index-transition-base-styles')) return;
88 var style = document.createElement('style');
89 style.id = 'ppt-index-transition-base-styles';
90 style.textContent =
91 '.ppt-preview-viewport { perspective: 2400px; }' +
92 '.ppt-preview-frame {' +
93 ' --ppt-fit-transform: translate(0px, 0px) scale(1);' +
94 ' --ppt-transition-x: 0px;' +
95 ' --ppt-transition-y: 0px;' +
96 ' --ppt-transition-scale: 1;' +
97 ' --ppt-transition-rotate-y: 0deg;' +
98 ' --ppt-transition-rotate-z: 0deg;' +
99 ' transform: var(--ppt-fit-transform) translate3d(var(--ppt-transition-x), var(--ppt-transition-y), 0) scale(var(--ppt-transition-scale)) rotateY(var(--ppt-transition-rotate-y)) rotate(var(--ppt-transition-rotate-z));' +
100 ' transform-style: preserve-3d;' +
101 ' backface-visibility: hidden;' +
102 '}' +
103 '.ppt-preview-frame.ppt-transitioning {' +
104 ' display: block !important;' +
105 ' pointer-events: none !important;' +
106 ' will-change: transform, opacity, clip-path, filter;' +
107 '}';
108 document.head.appendChild(style);
109 }
110
111 function ensurePresentBackgroundStyles() {
112 if (document.getElementById('ppt-present-background-style')) return;
113 var style = document.createElement('style');
114 style.id = 'ppt-present-background-style';
115 style.textContent =
116 'body.present { background: #000000 !important; }' +
117 'body.present .ppt-layout,' +
118 'body.present .ppt-stage,' +
119 'body.present .ppt-preview-viewport {' +
120 ' background: #000000 !important;' +
121 '}';
122 document.head.appendChild(style);
123 }
124
125 function clearPendingPlaybackRequests() {
126 Object.keys(pendingPlaybackRequests).forEach(function (requestId) {
127 window.clearTimeout(pendingPlaybackRequests[requestId]);
128 delete pendingPlaybackRequests[requestId];
129 });
130 if (wheelGestureUnlockTimer) {
131 window.clearTimeout(wheelGestureUnlockTimer);
132 wheelGestureUnlockTimer = 0;
133 }
134 }
135
136 function resetWheelGestureState() {
137 wheelDeltaBuffer = 0;
138 wheelGestureLocked = false;
139 wheelGestureLockDirection = 0;
140 lastWheelNavigateAt = 0;
141 }
142
143 function getPageKey(page) {
144 return String((page && (page.id || page.pageId)) || '');
145 }
146
147 function getLegacyPageId(page) {
148 return String((page && page.pageId) || getPageKey(page));
149 }
150
151 var pageByKey = new Map();
152 var pageIndexByKey = new Map();
153 var legacyPageKeyById = new Map();
154 pages.forEach(function (page, index) {
155 var pageKey = getPageKey(page);
156 if (!pageKey) return;
157 pageByKey.set(pageKey, page);
158 pageIndexByKey.set(pageKey, index);
159 legacyPageKeyById.set(getLegacyPageId(page), pageKey);
160 });
161
162 // ── iframe pool: one per page, lazy-loaded on first visit ──
163 var framePool = new Map();
164 var loadedPages = new Set();
165 var loadingFrames = new Map();
166 var allFrames = frameViewport
167 ? Array.from(frameViewport.querySelectorAll('.ppt-preview-frame'))
168 : [];
169 allFrames.forEach(function (el) {
170 var pid = el.getAttribute('data-page-id');
171 if (pid) framePool.set(pid, el);
172 });
173
174 // Build URL for a page iframe
175 function buildPageUrl(page) {
176 var url = new URL(page.htmlPath, window.location.href);
177 url.searchParams.set('fit', 'off');
178 if (embedMode) url.searchParams.set('embed', '1');
179 url.searchParams.set('pptPlayback', playbackMode ? '1' : '0');
180 if (playbackMode) url.searchParams.delete('print');
181 else url.searchParams.set('print', '1');
182 return url.toString();
183 }
184
185 function postPlaybackAdvanceToFrame(offset) {
186 var frame = getActiveFrame();
187 if (!frame) return false;
188 try {
189 var frameWindow = frame.contentWindow;
190 if (!frameWindow || typeof frameWindow.postMessage !== 'function') return false;
191 var requestId = 'playback-' + (++playbackRequestSeq);
192 pendingPlaybackRequests[requestId] = window.setTimeout(function () {
193 delete pendingPlaybackRequests[requestId];
194 gotoOffset(offset || 1);
195 }, 160);
196 frameWindow.postMessage({
197 type: 'ohmyppt:playback:advance',
198 offset: offset || 1,
199 requestId: requestId
200 }, '*');
201 return true;
202 } catch (_) {}
203 return false;
204 }
205
206 function requestNavigation(offset, options) {
207 var navOffset = Number.isFinite(offset) && offset !== 0 ? offset : 1;
208 var opts = options || {};
209 if (isPageSwitching) {
210 if (opts.queueWhileSwitching) queuedNavigationOffset = navOffset;
211 return false;
212 }
213 if (navOffset > 0 && opts.allowPlaybackAdvance !== false) {
214 if (playbackMode && postPlaybackAdvanceToFrame(1)) return true;
215 }
216 return gotoOffset(navOffset);
217 }
218
219 function resetWheelGestureSoon() {
220 if (wheelGestureUnlockTimer) window.clearTimeout(wheelGestureUnlockTimer);
221 wheelGestureUnlockTimer = window.setTimeout(function () {
222 wheelGestureUnlockTimer = 0;
223 wheelDeltaBuffer = 0;
224 wheelGestureLocked = false;
225 wheelGestureLockDirection = 0;
226 }, WHEEL_GESTURE_IDLE);
227 }
228
229 function handlePresentationKey(event) {
230 // Forward click advance to iframe first (for click-triggered animations)
231 var clickForwardKeys = ['ArrowRight', 'ArrowDown', 'PageDown', ' '];
232 if (clickForwardKeys.indexOf(event.key) >= 0) {
233 if (isPageSwitching) {
234 event.preventDefault();
235 return;
236 }
237 if (playbackMode && postPlaybackAdvanceToFrame(1)) {
238 event.preventDefault();
239 return;
240 }
241 }
242
243 if (event.key === 'ArrowRight' || event.key === 'ArrowDown' || event.key === 'PageDown' || event.key === ' ') {
244 event.preventDefault();
245 requestNavigation(1, { allowPlaybackAdvance: false });
246 return;
247 }
248 if (event.key === 'ArrowLeft' || event.key === 'ArrowUp' || event.key === 'PageUp') {
249 event.preventDefault();
250 if (isPageSwitching) return;
251 requestNavigation(-1, { allowPlaybackAdvance: false });
252 return;
253 }
254 if (event.key === 'Escape') {
255 if (deckSwitcher) deckSwitcher.classList.remove('open');
256 }
257 if (event.key === 'Escape' && presentMode) {
258 event.preventDefault();
259 exitPresentMode();
260 }
261 }
262
263 function isEditableTarget(target) {
264 if (!target || target.nodeType !== 1) return false;
265 var tagName = String(target.tagName || '').toLowerCase();
266 var closest = typeof target.closest === 'function'
267 ? target.closest.bind(target)
268 : function () { return null; };
269 return Boolean(
270 target.isContentEditable ||
271 tagName === 'input' ||
272 tagName === 'textarea' ||
273 tagName === 'select' ||
274 closest('[contenteditable="true"]')
275 );
276 }
277
278 function isDeckSwitcherWheelTarget(target) {
279 if (!target || typeof target.nodeType !== 'number') return false;
280 try {
281 return Boolean(deckSwitcher && deckSwitcher.contains(target));
282 } catch (_) {
283 return false;
284 }
285 }
286
287 function normalizeWheelDelta(event) {
288 var delta = Math.abs(event.deltaY) >= Math.abs(event.deltaX) ? event.deltaY : event.deltaX;
289 if (event.deltaMode === 1) delta *= 16;
290 else if (event.deltaMode === 2) delta *= 900;
291 return delta;
292 }
293
294 function handlePresentationWheel(event) {
295 if (event.ctrlKey || event.metaKey) return;
296 if (isEditableTarget(event.target)) return;
297 if (isDeckSwitcherWheelTarget(event.target)) return;
298
299 var delta = normalizeWheelDelta(event);
300 if (!Number.isFinite(delta) || Math.abs(delta) < 1) return;
301 var direction = delta > 0 ? 1 : -1;
302 var isReverseGesture =
303 wheelGestureLocked && wheelGestureLockDirection && direction !== wheelGestureLockDirection;
304
305 if (Math.sign(delta) !== Math.sign(wheelDeltaBuffer)) wheelDeltaBuffer = 0;
306 wheelDeltaBuffer += delta;
307 resetWheelGestureSoon();
308
309 if (Math.abs(wheelDeltaBuffer) < WHEEL_NAV_THRESHOLD) return;
310
311 if (isReverseGesture) {
312 wheelGestureLocked = false;
313 wheelGestureLockDirection = 0;
314 lastWheelNavigateAt = 0;
315 }
316
317 if (isPageSwitching) {
318 wheelDeltaBuffer = 0;
319 wheelGestureLocked = true;
320 wheelGestureLockDirection = direction;
321 if (isReverseGesture) queuedNavigationOffset = direction;
322 event.preventDefault();
323 return;
324 }
325
326 if (wheelGestureLocked) {
327 wheelDeltaBuffer = 0;
328 event.preventDefault();
329 return;
330 }
331
332 var now = Date.now();
333 if (now - lastWheelNavigateAt < WHEEL_NAV_COOLDOWN) {
334 wheelDeltaBuffer = 0;
335 wheelGestureLocked = true;
336 wheelGestureLockDirection = direction;
337 event.preventDefault();
338 return;
339 }
340
341 var offset = wheelDeltaBuffer > 0 ? 1 : -1;
342 wheelDeltaBuffer = 0;
343 event.preventDefault();
344 if (requestNavigation(offset)) {
345 wheelGestureLocked = true;
346 wheelGestureLockDirection = offset;
347 lastWheelNavigateAt = now;
348 } else {
349 resetWheelGestureState();
350 }
351 }
352
353 function bindFrameKeyboard(frame) {
354 try {
355 var frameWindow = frame.contentWindow;
356 var frameDocument = frameWindow && frameWindow.document;
357 if (!frameWindow || !frameDocument || frame.__ohmypptKeyboardDocument === frameDocument) return;
358 frameWindow.addEventListener('keydown', handlePresentationKey);
359 frame.__ohmypptKeyboardDocument = frameDocument;
360 } catch (_) {}
361 }
362
363 function handleFramePlaybackMessage(event) {
364 var frame = getActiveFrame();
365 if (!frame) return;
366 if (event.source && event.source !== frame.contentWindow) return;
367 var data = event.data;
368 if (!data) return;
369 if (data.requestId && pendingPlaybackRequests[data.requestId]) {
370 window.clearTimeout(pendingPlaybackRequests[data.requestId]);
371 delete pendingPlaybackRequests[data.requestId];
372 }
373 if (data.type === 'ohmyppt:playback:handled') return;
374 if (data.type !== 'ohmyppt:playback:goto') return;
375 var offset = Number(data.offset);
376 var navigated = requestNavigation(
377 Number.isFinite(offset) && offset !== 0 ? offset : 1,
378 { allowPlaybackAdvance: false, queueWhileSwitching: true }
379 );
380 if (data.requestId) {
381 try {
382 frame.contentWindow.postMessage({
383 type: 'ohmyppt:playback:navigation-result',
384 requestId: data.requestId,
385 navigated: navigated
386 }, '*');
387 } catch (_) {}
388 }
389 }
390
391 function prefetchPage(page) {
392 if (!page || embedMode) return;
393 try {
394 var url = buildPageUrl(page);
395 if (prefetchedPageUrls.has(url)) return;
396 prefetchedPageUrls.add(url);
397 var link = document.createElement('link');
398 link.rel = 'prefetch';
399 link.as = 'document';
400 link.href = url;
401 document.head.appendChild(link);
402 } catch (_) {}
403 }
404
405 function prefetchAdjacentPages(pageId) {
406 if (!Array.isArray(pages) || pages.length <= 1) return;
407 var index = pageIndexByKey.has(pageId) ? pageIndexByKey.get(pageId) : -1;
408 if (index < 0) return;
409 [-1, 1].forEach(function (offset) {
410 var page = pages[index + offset];
411 if (page) prefetchPage(page);
412 });
413 }
414
415 // Load or reload a page iframe so slide-level animations replay on revisit.
416 function ensureFrameLoaded(pageId, forceReload, onReady) {
417 var page = pageByKey.get(pageId);
418 var frame = framePool.get(pageId);
419 if (!page || !frame) return;
420 if (loadedPages.has(pageId) && !forceReload) {
421 bindFrameKeyboard(frame);
422 if (typeof onReady === 'function') onReady(frame);
423 return;
424 }
425 if (!forceReload && loadingFrames.has(pageId)) {
426 var waitingCallbacks = loadingFrames.get(pageId);
427 if (typeof onReady === 'function') waitingCallbacks.push(onReady);
428 return;
429 }
430 var callbacks = typeof onReady === 'function' ? [onReady] : [];
431 loadingFrames.set(pageId, callbacks);
432 var pageUrl = new URL(buildPageUrl(page));
433 if (forceReload) pageUrl.searchParams.set('_pptReplay', String(Date.now()));
434 var loadToken = String(Date.now()) + '-' + Math.random();
435 var loadTimer = 0;
436 var ready = false;
437 frame.__ohmypptLoadToken = loadToken;
438 function finishLoad() {
439 if (ready || frame.__ohmypptLoadToken !== loadToken) return;
440 ready = true;
441 if (loadTimer) window.clearTimeout(loadTimer);
442 loadedPages.add(pageId);
443 var readyCallbacks = loadingFrames.get(pageId) || [];
444 loadingFrames.delete(pageId);
445 bindFrameKeyboard(frame);
446 if (pageId === currentPageId) scheduleFitFrame();
447 readyCallbacks.forEach(function (callback) {
448 if (typeof callback === 'function') callback(frame);
449 });
450 }
451 frame.addEventListener('load', finishLoad, { once: true });
452 loadTimer = window.setTimeout(finishLoad, FRAME_LOAD_TIMEOUT);
453 frame.src = pageUrl.toString();
454 }
455
456 if (embedMode) document.body.classList.add('embed');
457
458 function applyPresentMode(nextPresentMode, syncQuery) {
459 var nextPresentModeValue = Boolean(nextPresentMode);
460 var playbackModeChanged = playbackMode !== (nextPresentModeValue && !embedMode);
461 presentMode = nextPresentModeValue;
462 playbackMode = presentMode && !embedMode;
463 document.body.classList.toggle('present', presentMode);
464 if (presentBtn) {
465 presentBtn.textContent = presentMode ? '退出演示' : '演示模式(ESC退出)';
466 }
467 if (fullscreenBtn) {
468 fullscreenBtn.style.display = 'none';
469 }
470 if (syncQuery) {
471 try {
472 var next = new URLSearchParams(window.location.search);
473 if (presentMode) next.set('present', '1');
474 else next.delete('present');
475 var query = next.toString();
476 window.history.replaceState(
477 null,
478 '',
479 window.location.pathname + (query ? '?' + query : '') + (window.location.hash || '')
480 );
481 } catch (_) {}
482 }
483 scheduleFitFrame();
484 if (playbackModeChanged && currentPageId) {
485 applyPage(currentPageId, false, true);
486 }
487 }
488
489 function normalizePageId(hashValue) {
490 var raw = (hashValue || '').replace(/^#/, '').trim();
491 if (!raw && pages.length > 0) return getPageKey(pages[0]);
492 var decoded = decodeURIComponent(raw || '');
493 if (pageByKey.has(decoded)) return decoded;
494 var legacyMatch = legacyPageKeyById.get(decoded);
495 if (legacyMatch) return legacyMatch;
496 return (pages[0] ? getPageKey(pages[0]) : '');
497 }
498
499 function getActiveFrame() {
500 return currentPageId ? framePool.get(currentPageId) : null;
501 }
502
503 function applyFrameFit(frame) {
504 if (!frame || !frameViewport) return;
505 var rect = frameViewport.getBoundingClientRect();
506 var rawScale = Math.min(rect.width / slideWidth, rect.height / slideHeight);
507 var scale = Number.isFinite(rawScale) && rawScale > 0 ? rawScale : 1;
508 var offsetX = Math.max(0, (rect.width - slideWidth * scale) / 2);
509 var offsetY = Math.max(0, (rect.height - slideHeight * scale) / 2);
510 frame.style.setProperty(
511 '--ppt-fit-transform',
512 'translate(' + offsetX + 'px, ' + offsetY + 'px) scale(' + scale + ')'
513 );
514 }
515
516 function fitFrame() {
517 applyFrameFit(getActiveFrame());
518 }
519
520 function scheduleFitFrame() {
521 if (fitRaf) cancelAnimationFrame(fitRaf);
522 fitRaf = requestAnimationFrame(function () {
523 fitRaf = 0;
524 fitFrame();
525 });
526 }
527
528 function renderThumbs(activePageId) {
529 if (!thumbs || embedMode) return;
530 Array.from(thumbs.querySelectorAll('.ppt-thumb-item')).forEach(function (item) {
531 item.classList.toggle('active', item.getAttribute('data-page-id') === activePageId);
532 });
533 }
534
535 function bindThumbEvents() {
536 if (!thumbs) return;
537 Array.from(thumbs.querySelectorAll('.ppt-thumb-item')).forEach(function (item) {
538 item.addEventListener('click', function () {
539 var pageId = item.getAttribute('data-page-id');
540 if (!pageId) return;
541 if (deckSwitcher) deckSwitcher.classList.remove('open');
542 window.location.hash = '#' + encodeURIComponent(pageId);
543 });
544 });
545 }
546
547 function currentIndex() {
548 return pageIndexByKey.has(currentPageId) ? pageIndexByKey.get(currentPageId) : -1;
549 }
550
551 function updateIndicator() {
552 if (!indicator) return;
553 var index = currentIndex();
554 indicator.textContent = index >= 0 ? (index + 1) + ' / ' + pages.length : '--';
555 }
556
557 function resetTransitionFrame(frame) {
558 if (!frame) return;
559 frame.classList.remove('ppt-transitioning');
560 frame.style.opacity = '';
561 frame.style.zIndex = '';
562 frame.style.clipPath = '';
563 frame.style.filter = '';
564 frame.style.transformOrigin = '';
565 frame.style.setProperty('--ppt-transition-x', '0px');
566 frame.style.setProperty('--ppt-transition-y', '0px');
567 frame.style.setProperty('--ppt-transition-scale', '1');
568 frame.style.setProperty('--ppt-transition-rotate-y', '0deg');
569 frame.style.setProperty('--ppt-transition-rotate-z', '0deg');
570 }
571
572 function runAnime(targets, params) {
573 var api = window.anime;
574 try {
575 if (api && typeof api.animate === 'function') return api.animate(targets, params);
576 if (typeof api === 'function') return api(Object.assign({ targets: targets }, params));
577 } catch (_) {}
578 return null;
579 }
580
581 function waitForAnimeAnimations(animations, duration, callback) {
582 var settled = false;
583 var pending = 0;
584 function done() {
585 if (settled) return;
586 settled = true;
587 callback();
588 }
589 animations.forEach(function (animation) {
590 var promise = animation && animation.finished && typeof animation.finished.then === 'function'
591 ? animation.finished
592 : (animation && typeof animation.then === 'function' ? animation : null);
593 if (!promise) return;
594 pending += 1;
595 promise.then(function () {
596 pending -= 1;
597 if (pending <= 0) done();
598 }, done);
599 });
600 if (pending <= 0) {
601 window.setTimeout(done, Math.max(0, duration));
602 return;
603 }
604 window.setTimeout(done, Math.max(80, duration + 120));
605 }
606
607 function transitionValue(percent) {
608 return (Math.round(percent * 1000) / 1000) + '%';
609 }
610
611 function buildTransitionParams(type, direction, duration) {
612 var d = direction < 0 ? -1 : 1;
613 var ease = 'out(3)';
614 var base = { duration: duration, ease: ease };
615 if (type === 'slide-left') {
616 return {
617 oldParams: Object.assign({}, base, {
618 '--ppt-transition-x': ['0%', transitionValue(-18 * d)],
619 opacity: [1, 0.65]
620 }),
621 newParams: Object.assign({}, base, {
622 '--ppt-transition-x': [transitionValue(100 * d), '0%'],
623 opacity: [1, 1]
624 })
625 };
626 }
627 if (type === 'slide-up') {
628 return {
629 oldParams: Object.assign({}, base, {
630 '--ppt-transition-y': ['0%', transitionValue(-18 * d)],
631 opacity: [1, 0.65]
632 }),
633 newParams: Object.assign({}, base, {
634 '--ppt-transition-y': [transitionValue(100 * d), '0%'],
635 opacity: [1, 1]
636 })
637 };
638 }
639 if (type === 'push') {
640 return {
641 oldParams: Object.assign({}, base, {
642 '--ppt-transition-x': ['0%', transitionValue(-100 * d)]
643 }),
644 newParams: Object.assign({}, base, {
645 '--ppt-transition-x': [transitionValue(100 * d), '0%']
646 })
647 };
648 }
649 if (type === 'wipe') {
650 return {
651 oldParams: Object.assign({}, base, { opacity: [1, 0.88] }),
652 newParams: Object.assign({}, base, {
653 clipPath: d > 0
654 ? ['inset(0 0 0 100%)', 'inset(0 0 0 0%)']
655 : ['inset(0 100% 0 0)', 'inset(0 0% 0 0)'],
656 opacity: [1, 1]
657 })
658 };
659 }
660 if (type === 'zoom') {
661 return {
662 oldParams: Object.assign({}, base, {
663 '--ppt-transition-scale': [1, 0.88],
664 opacity: [1, 0]
665 }),
666 newParams: Object.assign({}, base, {
667 '--ppt-transition-scale': [1.08, 1],
668 opacity: [0, 1]
669 })
670 };
671 }
672 if (type === 'flip') {
673 return {
674 oldParams: Object.assign({}, base, {
675 '--ppt-transition-rotate-y': ['0deg', (-72 * d) + 'deg'],
676 opacity: [1, 0]
677 }),
678 newParams: Object.assign({}, base, {
679 '--ppt-transition-rotate-y': [(72 * d) + 'deg', '0deg'],
680 opacity: [0, 1]
681 })
682 };
683 }
684 if (type === 'stack') {
685 return {
686 oldParams: Object.assign({}, base, {
687 '--ppt-transition-y': ['0px', (18 * d) + 'px'],
688 '--ppt-transition-scale': [1, 0.96],
689 opacity: [1, 0.55]
690 }),
691 newParams: Object.assign({}, base, {
692 '--ppt-transition-y': [(-28 * d) + 'px', '0px'],
693 '--ppt-transition-scale': [0.96, 1],
694 opacity: [0, 1]
695 })
696 };
697 }
698 if (type === 'rotate') {
699 return {
700 oldParams: Object.assign({}, base, {
701 '--ppt-transition-rotate-z': ['0deg', (-8 * d) + 'deg'],
702 '--ppt-transition-scale': [1, 0.9],
703 opacity: [1, 0]
704 }),
705 newParams: Object.assign({}, base, {
706 '--ppt-transition-rotate-z': [(10 * d) + 'deg', '0deg'],
707 '--ppt-transition-scale': [1.08, 1],
708 opacity: [0, 1]
709 })
710 };
711 }
712 if (type === 'cube') {
713 return {
714 oldParams: Object.assign({}, base, {
715 '--ppt-transition-x': ['0%', transitionValue(-34 * d)],
716 '--ppt-transition-rotate-y': ['0deg', (-88 * d) + 'deg'],
717 opacity: [1, 0.12]
718 }),
719 newParams: Object.assign({}, base, {
720 '--ppt-transition-x': [transitionValue(34 * d), '0%'],
721 '--ppt-transition-rotate-y': [(88 * d) + 'deg', '0deg'],
722 opacity: [0.12, 1]
723 })
724 };
725 }
726 if (type === 'cover-flow') {
727 return {
728 oldParams: Object.assign({}, base, {
729 '--ppt-transition-x': ['0%', transitionValue(-42 * d)],
730 '--ppt-transition-scale': [1, 0.82],
731 '--ppt-transition-rotate-y': ['0deg', (42 * d) + 'deg'],
732 opacity: [1, 0.32]
733 }),
734 newParams: Object.assign({}, base, {
735 '--ppt-transition-x': [transitionValue(42 * d), '0%'],
736 '--ppt-transition-scale': [0.82, 1],
737 '--ppt-transition-rotate-y': [(-42 * d) + 'deg', '0deg'],
738 opacity: [0.32, 1]
739 })
740 };
741 }
742 if (type === 'blur') {
743 return {
744 oldParams: Object.assign({}, base, {
745 '--ppt-transition-scale': [1, 1.035],
746 filter: ['blur(0px)', 'blur(16px)'],
747 opacity: [1, 0]
748 }),
749 newParams: Object.assign({}, base, {
750 '--ppt-transition-scale': [0.965, 1],
751 filter: ['blur(16px)', 'blur(0px)'],
752 opacity: [0, 1]
753 })
754 };
755 }
756 if (type === 'iris') {
757 return {
758 oldParams: Object.assign({}, base, {
759 opacity: [1, 0.75]
760 }),
761 newParams: Object.assign({}, base, {
762 clipPath: ['circle(0% at 50% 50%)', 'circle(76% at 50% 50%)'],
763 opacity: [1, 1]
764 })
765 };
766 }
767 if (type === 'swing') {
768 return {
769 oldParams: Object.assign({}, base, {
770 '--ppt-transition-x': ['0%', transitionValue(-16 * d)],
771 '--ppt-transition-rotate-z': ['0deg', (-12 * d) + 'deg'],
772 '--ppt-transition-scale': [1, 0.94],
773 opacity: [1, 0]
774 }),
775 newParams: Object.assign({}, base, {
776 '--ppt-transition-x': [transitionValue(18 * d), '0%'],
777 '--ppt-transition-rotate-z': [(12 * d) + 'deg', '0deg'],
778 '--ppt-transition-scale': [0.96, 1],
779 opacity: [0, 1]
780 })
781 };
782 }
783 if (type === 'center-reveal') {
784 return {
785 oldParams: Object.assign({}, base, {
786 '--ppt-transition-scale': [1, 0.98],
787 opacity: [1, 0.82]
788 }),
789 newParams: Object.assign({}, base, {
790 clipPath: ['inset(0 50% 0 50%)', 'inset(0 0% 0 0%)'],
791 '--ppt-transition-scale': [1.02, 1],
792 opacity: [1, 1]
793 })
794 };
795 }
796 return {
797 oldParams: Object.assign({}, base, { opacity: [1, 0] }),
798 newParams: Object.assign({}, base, { opacity: [0, 1] })
799 };
800 }
801
802 function applyPage(pageId, syncHash, forceReload) {
803 if (!Array.isArray(pages) || pages.length === 0) {
804 document.body.classList.add('empty');
805 if (indicator) indicator.textContent = '0 / 0';
806 return;
807 }
808 document.body.classList.remove('empty');
809 var page = pageByKey.get(pageId) || pages[0];
810 if (!page) return;
811
812 var previousPageId = currentPageId;
813 var prevFrame = previousPageId ? framePool.get(previousPageId) : null;
814 var nextPageId = getPageKey(page);
815 var samePage = previousPageId === nextPageId;
816 var switchSeq = ++pageSwitchSeq;
817 if (previousPageId && !samePage) clearPendingPlaybackRequests();
818
819 function finishSwitch() {
820 if (switchSeq !== pageSwitchSeq) return;
821 isPageSwitching = false;
822 if (!queuedNavigationOffset) return;
823 var nextOffset = queuedNavigationOffset;
824 queuedNavigationOffset = 0;
825 window.setTimeout(function () {
826 requestNavigation(nextOffset, { allowPlaybackAdvance: false });
827 }, 0);
828 }
829
830 function updatePageState() {
831 if (switchSeq !== pageSwitchSeq) return;
832 currentPageId = nextPageId;
833 if (syncHash && window.location.hash !== '#' + encodeURIComponent(currentPageId)) {
834 window.history.replaceState(null, '', '#' + encodeURIComponent(currentPageId));
835 }
836 renderThumbs(currentPageId);
837 updateIndicator();
838 prefetchAdjacentPages(currentPageId);
839 }
840
841 function switchFrame() {
842 if (switchSeq !== pageSwitchSeq) return;
843 if (prevFrame) {
844 prevFrame.classList.remove('active');
845 resetTransitionFrame(prevFrame);
846 }
847 var nextFrame = framePool.get(nextPageId);
848 if (nextFrame) {
849 nextFrame.classList.add('active');
850 resetTransitionFrame(nextFrame);
851 }
852 updatePageState();
853 scheduleFitFrame();
854 }
855
856 function animateFrameSwitch(done) {
857 if (switchSeq !== pageSwitchSeq) return;
858 var nextFrame = framePool.get(nextPageId);
859 var canAnimate =
860 presentMode &&
861 indexTransitionType !== 'none' &&
862 !prefersReducedMotion() &&
863 previousPageId &&
864 !samePage &&
865 prevFrame &&
866 nextFrame;
867 if (!canAnimate) {
868 switchFrame();
869 done();
870 return;
871 }
872 var previousIndex = pageIndexByKey.has(previousPageId) ? pageIndexByKey.get(previousPageId) : -1;
873 var nextIndex = pageIndexByKey.has(nextPageId) ? pageIndexByKey.get(nextPageId) : previousIndex + 1;
874 var direction = previousIndex >= 0 && nextIndex < previousIndex ? -1 : 1;
875 var duration = clampTransitionDuration(indexTransitionType, indexTransitionDuration);
876
877 resetTransitionFrame(prevFrame);
878 resetTransitionFrame(nextFrame);
879 applyFrameFit(prevFrame);
880 applyFrameFit(nextFrame);
881 prevFrame.classList.add('active', 'ppt-transitioning');
882 nextFrame.classList.add('active', 'ppt-transitioning');
883 prevFrame.style.zIndex = '1';
884 nextFrame.style.zIndex = '2';
885 updatePageState();
886
887 var params = buildTransitionParams(indexTransitionType, direction, duration);
888 var oldAnimation = runAnime(prevFrame, params.oldParams);
889 var newAnimation = runAnime(nextFrame, params.newParams);
890 var animations = [oldAnimation, newAnimation];
891 if (!animations.some(Boolean)) {
892 if (switchSeq !== pageSwitchSeq) return;
893 prevFrame.classList.remove('active');
894 resetTransitionFrame(prevFrame);
895 resetTransitionFrame(nextFrame);
896 scheduleFitFrame();
897 done();
898 return;
899 }
900 waitForAnimeAnimations(animations, duration, function () {
901 if (switchSeq !== pageSwitchSeq) return;
902 prevFrame.classList.remove('active');
903 resetTransitionFrame(prevFrame);
904 resetTransitionFrame(nextFrame);
905 scheduleFitFrame();
906 done();
907 });
908 }
909
910 function commitWhenReady() {
911 if (switchSeq !== pageSwitchSeq) return;
912 animateFrameSwitch(finishSwitch);
913 }
914
915 isPageSwitching = !samePage;
916 ensureFrameLoaded(
917 nextPageId,
918 Boolean(forceReload) || (loadedPages.has(nextPageId) && !samePage),
919 commitWhenReady
920 );
921 if (samePage) finishSwitch();
922 }
923
924 function gotoOffset(offset) {
925 if (!Array.isArray(pages) || pages.length === 0) return false;
926 if (isPageSwitching) return false;
927 var index = currentIndex();
928 if (index < 0) return false;
929 var target = Math.max(0, Math.min(pages.length - 1, index + offset));
930 if (target === index) return false;
931 var targetPage = pages[target];
932 if (!targetPage) return false;
933 window.location.hash = '#' + encodeURIComponent(getPageKey(targetPage));
934 return true;
935 }
936
937 function onHashChange() {
938 var pageId = normalizePageId(window.location.hash);
939 applyPage(pageId, false);
940 }
941
942 function togglePresentMode() {
943 applyPresentMode(!presentMode, true);
944 if (presentMode && !document.fullscreenElement) {
945 try { document.documentElement.requestFullscreen(); } catch (_) {}
946 } else if (!presentMode && document.fullscreenElement) {
947 try { document.exitFullscreen(); } catch (_) {}
948 }
949 }
950
951 function exitPresentMode() {
952 if (!presentMode) return;
953 applyPresentMode(false, true);
954 if (document.fullscreenElement) {
955 try { document.exitFullscreen(); } catch (_) {}
956 }
957 }
958
959 function toggleFullscreen() {
960 togglePresentMode();
961 }
962
963 // Read transition config from container data attribute
964 try {
965 var transitionConfig = document.getElementById('ppt-index-transition-config');
966 if (transitionConfig) {
967 var config = JSON.parse(transitionConfig.textContent || '{}');
968 indexTransitionType = normalizeIndexTransitionType(config.type);
969 indexTransitionDuration = clampTransitionDuration(indexTransitionType, config.durationMs);
970 }
971 } catch (_) {}
972
973 ensureIndexTransitionBaseStyles();
974 ensurePresentBackgroundStyles();
975
976 bindThumbEvents();
977 if (prevBtn) prevBtn.addEventListener('click', function () { gotoOffset(-1); });
978 if (nextBtn) nextBtn.addEventListener('click', function () { gotoOffset(1); });
979 if (tabsBtn) tabsBtn.addEventListener('click', function () { if (deckSwitcher) deckSwitcher.classList.toggle('open'); });
980 if (presentBtn) presentBtn.addEventListener('click', function () { togglePresentMode(); });
981 if (fullscreenBtn) fullscreenBtn.addEventListener('click', function () { toggleFullscreen(); });
982 window.addEventListener('resize', function () { scheduleFitFrame(); });
983 window.addEventListener('hashchange', onHashChange);
984 window.addEventListener('keydown', handlePresentationKey);
985 window.addEventListener('wheel', handlePresentationWheel, { passive: false });
986 window.addEventListener('message', handleFramePlaybackMessage);
987 window.addEventListener('pagehide', clearPendingPlaybackRequests);
988 window.addEventListener('beforeunload', clearPendingPlaybackRequests);
989 document.addEventListener('fullscreenchange', function () {
990 if (!document.fullscreenElement && presentMode) {
991 exitPresentMode();
992 }
993 });
994 document.addEventListener('click', function (event) {
995 var target = event.target;
996 if (!(target instanceof Node)) return;
997 if (!deckSwitcher || !deckSwitcher.classList.contains('open')) return;
998 var inSwitcher = deckSwitcher.contains(target);
999 var inTabsButton = tabsBtn && tabsBtn.contains(target);
1000 if (!inSwitcher && !inTabsButton) {
1001 deckSwitcher.classList.remove('open');
1002 }
1003 });
1004
1005 applyPresentMode(presentMode, false);
1006 applyPage(normalizePageId(window.location.hash), true);
1007 scheduleFitFrame();
1008 })();
1009
1009 lines JAVASCRIPT