// Presentation mode script
// Used by both present.html and preview.html
// Requires: window.DECK_CONTENT (JSON string or object)
// Optional: window.DECK_ID, window.ESCAPE_URL (where Escape key navigates)
let currentSlide = 0;
let slides = [];
document.addEventListener('DOMContentLoaded', () => {
if (!window.DECK_CONTENT) return;
const content = typeof window.DECK_CONTENT === 'string'
? JSON.parse(window.DECK_CONTENT)
: window.DECK_CONTENT;
slides = content.slides || [];
if (slides.length === 0) return;
// Apply theme from deck
if (content.theme) {
const root = document.getElementById('html-root');
if (root) root.setAttribute('data-theme', content.theme);
}
renderSlides();
showSlide(0);
setupKeyboardNav();
if (typeof hljs !== 'undefined') {
hljs.highlightAll();
}
});
function renderSlides() {
const container = document.getElementById('slides-container');
container.innerHTML = slides.map((slide, i) => `
<div class="slide bg-base-200" id="slide-${i}">
${slide.blocks.map(block => renderBlock(block)).join('')}
</div>
`).join('');
}
function renderBlock(block) {
switch (block.type) {
case 'heading':
const sizes = { 1: 'text-6xl', 2: 'text-5xl', 3: 'text-4xl' };
return `<h${block.level} class="${sizes[block.level] || sizes[1]} font-bold text-base-content mb-6">${escapeHtml(block.content)}</h${block.level}>`;
case 'text':
return `<p class="text-2xl text-base-content/80 mb-6 leading-relaxed">${escapeHtml(block.content)}</p>`;
case 'code':
return `<pre class="rounded-xl overflow-hidden mb-6 text-lg"><code class="language-${block.language || 'javascript'}">${escapeHtml(block.content)}</code></pre>`;
case 'image':
return block.url ? `
<figure class="mb-6 flex flex-col items-center">
<img src="${escapeHtml(block.url)}" alt="${escapeHtml(block.caption || '')}" class="max-h-[60vh] rounded-xl" />
${block.caption ? `<figcaption class="text-center text-lg text-base-content/50 mt-4">${escapeHtml(block.caption)}</figcaption>` : ''}
</figure>
` : '';
case 'list':
const tag = block.style === 'number' ? 'ol' : 'ul';
return `<${tag} class="list-${block.style === 'number' ? 'decimal' : 'disc'} list-inside text-2xl text-base-content/80 mb-6 space-y-2">
${(block.items || []).map(item => `<li>${escapeHtml(item)}</li>`).join('')}
</${tag}>`;
case 'quote':
return `
<blockquote class="border-l-4 border-primary pl-6 py-4 mb-6">
<p class="text-3xl italic text-base-content/80">${escapeHtml(block.content)}</p>
${block.author ? `<cite class="text-xl text-base-content/50 block mt-4">— ${escapeHtml(block.author)}</cite>` : ''}
</blockquote>
`;
case 'divider':
return `<hr class="border-base-content/10 my-8" />`;
default:
return '';
}
}
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function showSlide(index) {
if (index < 0 || index >= slides.length) return;
// Update slide classes
slides.forEach((_, i) => {
const el = document.getElementById(`slide-${i}`);
el.classList.remove('active', 'prev');
if (i < index) el.classList.add('prev');
if (i === index) el.classList.add('active');
});
currentSlide = index;
// Update counter
document.getElementById('slide-counter').textContent = `${index + 1} / ${slides.length}`;
// Update progress bar
const progress = ((index + 1) / slides.length) * 100;
document.getElementById('progress-bar').style.width = `${progress}%`;
}
function nextSlide() {
if (currentSlide < slides.length - 1) {
showSlide(currentSlide + 1);
}
}
function prevSlide() {
if (currentSlide > 0) {
showSlide(currentSlide - 1);
}
}
function setupKeyboardNav() {
document.addEventListener('keydown', (e) => {
switch (e.key) {
case 'ArrowRight':
case ' ':
case 'Enter':
e.preventDefault();
nextSlide();
break;
case 'ArrowLeft':
case 'Backspace':
e.preventDefault();
prevSlide();
break;
case 'Escape':
// Use custom escape URL if provided, otherwise default to deck view
if (window.ESCAPE_URL) {
window.location.href = window.ESCAPE_URL;
} else if (window.DECK_ID) {
window.location.href = `/deck/${window.DECK_ID}`;
}
break;
case 'Home':
showSlide(0);
break;
case 'End':
showSlide(slides.length - 1);
break;
}
});
// Touch/click navigation
document.addEventListener('click', (e) => {
if (e.target.closest('.exit-btn')) return;
if (e.clientX > window.innerWidth / 2) {
nextSlide();
} else {
prevSlide();
}
});
}
Add rich text formatting and block styling to editor
Pitch decks to help promote Skyscape Apps