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;