/* html-ppt :: runtime.js * Keyboard-driven deck runtime. Zero dependencies. * * Features: * ← → / space / PgUp PgDn / Home End navigation * F fullscreen * S presenter mode (opens a NEW WINDOW with current/next slide preview + notes + timer) * The original window stays as audience view, synced via BroadcastChannel. * Slide previews use CSS transform:scale() at design resolution for pixel-perfect layout. * N quick notes overlay (bottom drawer) * O slide overview grid * T cycle themes (reads data-themes on or
) * A cycle demo animation on current slide * URL hash #/N deep-link to slide N (1-based) * Progress bar auto-managed */ (function () { 'use strict'; const ANIMS = ['fade-up','fade-down','fade-left','fade-right','rise-in','drop-in', 'zoom-pop','blur-in','glitch-in','typewriter','neon-glow','shimmer-sweep', 'gradient-flow','stagger-list','counter-up','path-draw','parallax-tilt', 'card-flip-3d','cube-rotate-3d','page-turn-3d','perspective-zoom', 'marquee-scroll','kenburns','confetti-burst','spotlight','morph-shape','ripple-reveal']; function ready(fn){ if(document.readyState!='loading')fn(); else document.addEventListener('DOMContentLoaded',fn);} /* ========== Parse URL for preview-only mode ========== * When loaded as iframe.src = "index.html?preview=3", runtime enters a * locked single-slide mode: only slide N is visible, no chrome, no keys, * no hash updates. This is how the presenter window shows pixel-perfect * previews — by loading the actual deck file in an iframe and telling it * to display only a specific slide. */ function getPreviewIdx() { const m = /[?&]preview=(\d+)/.exec(location.search || ''); return m ? parseInt(m[1], 10) - 1 : -1; } ready(function () { const deck = document.querySelector('.deck'); if (!deck) return; const slides = Array.from(deck.querySelectorAll('.slide')); if (!slides.length) return; const previewOnlyIdx = getPreviewIdx(); const isPreviewMode = previewOnlyIdx >= 0 && previewOnlyIdx < slides.length; /* ===== Preview-only mode: show one slide, hide everything else ===== */ if (isPreviewMode) { function showSlide(i) { slides.forEach((s, j) => { const active = (j === i); s.classList.toggle('is-active', active); s.style.display = active ? '' : 'none'; if (active) { s.style.opacity = '1'; s.style.transform = 'none'; s.style.pointerEvents = 'auto'; } }); } showSlide(previewOnlyIdx); /* Hide chrome that the presenter shouldn't see in preview */ const hideSel = '.progress-bar, .notes-overlay, .overview, .notes, aside.notes, .speaker-notes'; document.querySelectorAll(hideSel).forEach(el => { el.style.display = 'none'; }); document.documentElement.setAttribute('data-preview', '1'); document.body.setAttribute('data-preview', '1'); /* Listen for postMessage from parent presenter window to switch slides * WITHOUT reloading — this eliminates flicker during navigation. */ window.addEventListener('message', function(e) { if (!e.data || e.data.type !== 'preview-goto') return; const n = parseInt(e.data.idx, 10); if (n >= 0 && n < slides.length) showSlide(n); }); /* Signal to parent that preview iframe is ready */ try { window.parent && window.parent.postMessage({ type: 'preview-ready' }, '*'); } catch(e) {} return; } let idx = 0; const total = slides.length; /* ===== BroadcastChannel for presenter sync ===== */ const CHANNEL_NAME = 'html-ppt-presenter-' + location.pathname; let bc; try { bc = new BroadcastChannel(CHANNEL_NAME); } catch(e) { bc = null; } // Are we running inside the presenter popup? (legacy flag, now unused) const isPresenterWindow = false; /* ===== progress bar ===== */ let bar = document.querySelector('.progress-bar'); if (!bar) { bar = document.createElement('div'); bar.className = 'progress-bar'; bar.innerHTML = ''; document.body.appendChild(bar); } const barFill = bar.querySelector('span'); /* ===== notes overlay (N key) ===== */ let notes = document.querySelector('.notes-overlay'); if (!notes) { notes = document.createElement('div'); notes.className = 'notes-overlay'; document.body.appendChild(notes); } /* ===== overview grid (O key) ===== */ let overview = document.querySelector('.overview'); if (!overview) { overview = document.createElement('div'); overview.className = 'overview'; slides.forEach((s, i) => { const t = document.createElement('div'); t.className = 'thumb'; const title = s.getAttribute('data-title') || (s.querySelector('h1,h2,h3')||{}).textContent || ('Slide '+(i+1)); t.innerHTML = '