/* html-ppt :: runtime.js * Keyboard-driven deck runtime. Zero dependencies. * * Features: * ← → / space / PgUp PgDn / Home End navigation * F fullscreen * S speaker notes overlay * 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);} ready(function () { const deck = document.querySelector('.deck'); if (!deck) return; const slides = Array.from(deck.querySelectorAll('.slide')); if (!slides.length) return; let idx = 0; const total = slides.length; // 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 let notes = document.querySelector('.notes-overlay'); if (!notes) { notes = document.createElement('div'); notes.className = 'notes-overlay'; document.body.appendChild(notes); } // overview 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 = '
'+(i+1)+'
'+title.trim().slice(0,80)+'
'; t.addEventListener('click', () => { go(i); toggleOverview(false); }); overview.appendChild(t); }); document.body.appendChild(overview); } function go(n){ n = Math.max(0, Math.min(total-1, n)); slides.forEach((s,i) => { s.classList.toggle('is-active', i===n); s.classList.toggle('is-prev', i { const a = el.getAttribute('data-anim'); el.classList.remove('anim-'+a); void el.offsetWidth; el.classList.add('anim-'+a); }); // counter-up slides[n].querySelectorAll('.counter').forEach(el => { const target = parseFloat(el.getAttribute('data-to')||el.textContent); const dur = parseInt(el.getAttribute('data-dur')||'1200',10); const start = performance.now(); const from = 0; function tick(now){ const t = Math.min(1,(now-start)/dur); const v = from + (target-from)*(1-Math.pow(1-t,3)); el.textContent = (target % 1 === 0) ? Math.round(v) : v.toFixed(1); if (t<1) requestAnimationFrame(tick); } requestAnimationFrame(tick); }); } function toggleNotes(force){ notes.classList.toggle('open', force!==undefined?force:!notes.classList.contains('open')); } function toggleOverview(force){ overview.classList.toggle('open', force!==undefined?force:!overview.classList.contains('open')); } function fullscreen(){ const el=document.documentElement; if (!document.fullscreenElement) el.requestFullscreen&&el.requestFullscreen(); else document.exitFullscreen&&document.exitFullscreen(); } // theme cycling const root = document.documentElement; const themesAttr = root.getAttribute('data-themes') || document.body.getAttribute('data-themes'); const themes = themesAttr ? themesAttr.split(',').map(s=>s.trim()).filter(Boolean) : []; let themeIdx = 0; function cycleTheme(){ if (!themes.length) return; themeIdx = (themeIdx+1) % themes.length; const name = themes[themeIdx]; let link = document.getElementById('theme-link'); if (!link) { link = document.createElement('link'); link.rel = 'stylesheet'; link.id = 'theme-link'; document.head.appendChild(link); } // resolve relative to runtime's location const themePath = (root.getAttribute('data-theme-base') || 'assets/themes/') + name + '.css'; link.href = themePath; root.setAttribute('data-theme', name); const ind = document.querySelector('.theme-indicator'); if (ind) ind.textContent = name; } // animation cycling on current slide let animIdx = 0; function cycleAnim(){ animIdx = (animIdx+1) % ANIMS.length; const a = ANIMS[animIdx]; const target = slides[idx].querySelector('[data-anim-target]') || slides[idx]; ANIMS.forEach(x => target.classList.remove('anim-'+x)); void target.offsetWidth; target.classList.add('anim-'+a); target.setAttribute('data-anim', a); const ind = document.querySelector('.anim-indicator'); if (ind) ind.textContent = a; } document.addEventListener('keydown', function (e) { if (e.metaKey||e.ctrlKey||e.altKey) return; switch (e.key) { case 'ArrowRight': case ' ': case 'PageDown': case 'Enter': go(idx+1); e.preventDefault(); break; case 'ArrowLeft': case 'PageUp': case 'Backspace': go(idx-1); e.preventDefault(); break; case 'Home': go(0); break; case 'End': go(total-1); break; case 'f': case 'F': fullscreen(); break; case 's': case 'S': toggleNotes(); break; case 'o': case 'O': toggleOverview(); break; case 't': case 'T': cycleTheme(); break; case 'a': case 'A': cycleAnim(); break; case 'Escape': toggleOverview(false); toggleNotes(false); break; } }); // hash deep-link function fromHash(){ const m = /^#\/(\d+)/.exec(location.hash||''); if (m) go(Math.max(0, parseInt(m[1],10)-1)); } window.addEventListener('hashchange', fromHash); fromHash(); go(idx); }); })();