class PXSlideViewer { constructor(container, options = {}) { this.container = container; this.slides = []; this.currentIndex = 0; this.isPlaying = false; this.speakerNotesOpen = false; this.thumbnailOpen = false; this.transitionType = options.transition || 'fade'; this.autoplayDelay = options.autoplayDelay || 8000; this._options = options; this._init(); } _init() { var opts = this._options || {}; this.slides = Array.from(this.container.querySelectorAll('.viewer-slide')); if (this.slides.length === 0 && opts.slides && opts.slides.length) { this.slides = opts.slides; } if (this.slides.length === 0) return; this._setupHTML(); this._setupStyles(); this._setupEvents(); this.goTo(0); } _setupHTML() { this.container.classList.add('px-viewer'); this.container.innerHTML = ''; const stage = document.createElement('div'); stage.className = 'px-viewer__stage'; this.stageEl = stage; this.slides.forEach((slide, idx) => { const wrapper = document.createElement('div'); wrapper.className = 'px-viewer__slide'; wrapper.dataset.index = idx; wrapper.innerHTML = slide.innerHTML || slide.html || ''; stage.appendChild(wrapper); }); this.slideEls = stage.querySelectorAll('.px-viewer__slide'); const controls = document.createElement('div'); controls.className = 'px-viewer__controls'; controls.innerHTML = `
1 / ${this.slides.length}
`; this.progressFill = controls.querySelector('.px-viewer__progress-fill'); this.counterEl = controls.querySelector('.px-viewer__counter'); const notesPanel = document.createElement('div'); notesPanel.className = 'px-viewer__notes'; notesPanel.innerHTML = '
'; this.notesEl = notesPanel.querySelector('.px-viewer__notes-content'); const thumbPanel = document.createElement('div'); thumbPanel.className = 'px-viewer__thumbs'; this.thumbsEl = thumbPanel; this.slides.forEach((slide, idx) => { const thumb = document.createElement('div'); thumb.className = 'px-viewer__thumb'; thumb.dataset.index = idx; const title = slide.title || `Slide ${idx + 1}`; const layout = slide.layout || ''; thumb.innerHTML = `
${idx + 1}
${title}
${layout.replace(/_/g, ' ')}
`; thumb.addEventListener('click', () => this.goTo(idx)); thumbPanel.appendChild(thumb); }); this.container.appendChild(stage); this.container.appendChild(notesPanel); this.container.appendChild(thumbPanel); this.container.appendChild(controls); } _setupStyles() { if (document.getElementById('px-viewer-styles')) return; const style = document.createElement('style'); style.id = 'px-viewer-styles'; style.textContent = ` .px-viewer { position: fixed; inset: 0; background: #0a0a0a; z-index: 9999; display: flex; flex-direction: column; font-family: 'Inter', sans-serif; } .px-viewer__stage { flex: 1; position: relative; display: flex; align-items: center; justify-content: center; overflow: hidden; } .px-viewer__slide { position: absolute; top: 0; left: 0; width: 1920px; height: 1080px; transform-origin: top left; opacity: 0; pointer-events: none; transition: opacity 0.5s ease, transform 0.5s ease; } .px-viewer__slide.active { opacity: 1; pointer-events: auto; z-index: 1; } .px-viewer__slide.slide-enter { opacity: 0; transform: scale(0.97); } .px-viewer__slide.active.slide-enter { opacity: 1; transform: scale(1); } .px-viewer__progress { height: 3px; background: rgba(255,255,255,0.1); position: relative; } .px-viewer__progress-fill { height: 100%; background: linear-gradient(to right, #005696, #0EA5E9); transition: width 0.4s ease; border-radius: 0 2px 2px 0; } .px-viewer__bar { display: flex; align-items: center; justify-content: space-between; padding: 10px 20px; background: rgba(15,15,15,0.95); backdrop-filter: blur(10px); } .px-viewer__bar-left, .px-viewer__bar-right { display: flex; gap: 4px; } .px-viewer__nav { display: flex; align-items: center; gap: 12px; } .px-viewer__btn { background: none; border: none; color: rgba(255,255,255,0.6); cursor: pointer; padding: 8px; border-radius: 8px; transition: all 0.2s; display: flex; align-items: center; justify-content: center; } .px-viewer__btn:hover { color: #fff; background: rgba(255,255,255,0.1); } .px-viewer__counter { color: rgba(255,255,255,0.5); font-size: 13px; font-variant-numeric: tabular-nums; min-width: 60px; text-align: center; user-select: none; } .px-viewer__controls { position: relative; z-index: 10; } .px-viewer__notes { position: fixed; bottom: 60px; right: 0; width: 400px; max-height: 50vh; background: rgba(15,15,15,0.95); backdrop-filter: blur(10px); border-left: 1px solid rgba(255,255,255,0.1); border-top: 1px solid rgba(255,255,255,0.1); border-radius: 16px 0 0 0; padding: 24px; color: rgba(255,255,255,0.8); font-size: 14px; line-height: 1.7; overflow-y: auto; transform: translateX(100%); transition: transform 0.3s ease; z-index: 20; } .px-viewer__notes.open { transform: translateX(0); } .px-viewer__notes-content { white-space: pre-wrap; } .px-viewer__thumbs { position: fixed; left: 0; top: 0; bottom: 60px; width: 280px; background: rgba(15,15,15,0.95); backdrop-filter: blur(10px); border-right: 1px solid rgba(255,255,255,0.1); padding: 20px 16px; overflow-y: auto; transform: translateX(-100%); transition: transform 0.3s ease; z-index: 20; display: flex; flex-direction: column; gap: 8px; } .px-viewer__thumbs.open { transform: translateX(0); } .px-viewer__thumb { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border-radius: 10px; cursor: pointer; transition: background 0.2s; border: 2px solid transparent; } .px-viewer__thumb:hover { background: rgba(255,255,255,0.05); } .px-viewer__thumb.active { background: rgba(14,165,233,0.15); border-color: rgba(14,165,233,0.4); } .px-viewer__thumb-number { width: 32px; height: 32px; border-radius: 8px; background: rgba(255,255,255,0.1); color: rgba(255,255,255,0.5); display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 600; flex-shrink: 0; } .px-viewer__thumb.active .px-viewer__thumb-number { background: rgba(14,165,233,0.3); color: #0EA5E9; } .px-viewer__thumb-title { font-size: 13px; font-weight: 500; color: rgba(255,255,255,0.7); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .px-viewer__thumb-layout { font-size: 11px; color: rgba(255,255,255,0.35); text-transform: capitalize; } `; document.head.appendChild(style); } _setupEvents() { document.addEventListener('keydown', (e) => { if (e.target.matches('input, textarea, [contenteditable]')) return; switch (e.key) { case 'ArrowRight': case ' ': case 'PageDown': e.preventDefault(); this.next(); break; case 'ArrowLeft': case 'PageUp': e.preventDefault(); this.prev(); break; case 'Home': e.preventDefault(); this.goTo(0); break; case 'End': e.preventDefault(); this.goTo(this.slides.length - 1); break; case 'Escape': e.preventDefault(); this.destroy(); break; case 'f': case 'F': e.preventDefault(); this.toggleFullscreen(); break; case 'n': case 'N': e.preventDefault(); this.toggleNotes(); break; case 't': case 'T': e.preventDefault(); this.toggleThumbnails(); break; } }); this.container.addEventListener('click', (e) => { const btn = e.target.closest('[data-action]'); if (!btn) return; const action = btn.dataset.action; switch (action) { case 'prev': this.prev(); break; case 'next': this.next(); break; case 'fullscreen': this.toggleFullscreen(); break; case 'exit': this.destroy(); break; case 'notes': this.toggleNotes(); break; case 'thumbnails': this.toggleThumbnails(); break; } }); window.addEventListener('resize', () => this._scale()); } _scale() { const slide = this.stageEl.querySelector('.px-viewer__slide.active'); if (!slide) return; const vw = this.stageEl.clientWidth; const vh = this.stageEl.clientHeight; const scale = Math.min(vw / 1920, vh / 1080); const offsetX = (vw - 1920 * scale) / 2; const offsetY = (vh - 1080 * scale) / 2; this.slideEls.forEach(el => { el.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`; }); } goTo(index) { if (index < 0 || index >= this.slides.length) return; this.slideEls.forEach((el, i) => { el.classList.remove('active', 'slide-enter'); if (i === index) { el.classList.add('active', 'slide-enter'); } }); this.currentIndex = index; this.counterEl.textContent = `${index + 1} / ${this.slides.length}`; this.progressFill.style.width = `${((index + 1) / this.slides.length) * 100}%`; this.notesEl.textContent = this.slides[index]?.notes || 'No speaker notes for this slide.'; this.thumbsEl.querySelectorAll('.px-viewer__thumb').forEach((t, i) => { t.classList.toggle('active', i === index); }); this._scale(); } next() { if (this.currentIndex < this.slides.length - 1) { this.goTo(this.currentIndex + 1); } } prev() { if (this.currentIndex > 0) { this.goTo(this.currentIndex - 1); } } toggleFullscreen() { if (!document.fullscreenElement) { this.container.requestFullscreen(); } else { document.exitFullscreen(); } } toggleNotes() { this.speakerNotesOpen = !this.speakerNotesOpen; this.container.querySelector('.px-viewer__notes').classList.toggle('open', this.speakerNotesOpen); } toggleThumbnails() { this.thumbnailOpen = !this.thumbnailOpen; this.container.querySelector('.px-viewer__thumbs').classList.toggle('open', this.thumbnailOpen); if (this.thumbnailOpen) { setTimeout(() => this._scale(), 350); } } destroy() { if (document.fullscreenElement) { document.exitFullscreen(); } this.container.remove(); if (window._pxViewerCleanup) window._pxViewerCleanup(); } } window.PXSlideViewer = PXSlideViewer;