/**
* Blog Presentation Mode
* Renders blog posts as full-viewport slides with presenter notes
* Zero dependencies — pure JS
*/
/* eslint-disable no-unused-vars */
/**
* Check if presentation mode is requested and initialize
* Called from blog-post.js after post data is loaded
*/
function initPresentationMode(post) {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('mode') !== 'presentation') return false;
const slides = buildSlides(post);
if (slides.length === 0) return false;
renderPresentation(slides);
return true;
}
/**
* Build slides from post data
* Option A: curated slides from post.presentation
* Option B: auto-extract from HTML content using
breaks
*/
function buildSlides(post) {
const slides = [];
// Title slide is always first
const authorName = post.author_name || (post.author && post.author.name) || 'Tractatus Team';
const publishedDate = post.published_at
? new Date(post.published_at).toLocaleDateString('en-US', {
year: 'numeric', month: 'long', day: 'numeric'
})
: '';
slides.push({
type: 'title',
heading: post.title,
subtitle: post.excerpt || '',
meta: [authorName, publishedDate].filter(Boolean).join(' — '),
notes: ''
});
// Option A: curated presentation data
if (post.presentation && post.presentation.enabled &&
post.presentation.slides && post.presentation.slides.length > 0) {
for (const slide of post.presentation.slides) {
slides.push({
type: 'content',
heading: slide.heading,
bullets: slide.bullets || [],
notes: slide.notes || ''
});
}
return slides;
}
// Option B: auto-extract from content HTML
const contentHtml = post.content_html || post.content || '';
if (!contentHtml) return slides;
// Parse the HTML to extract sections by
const parser = new DOMParser();
const doc = parser.parseFromString(`
${contentHtml}
`, 'text/html');
const wrapper = doc.body.firstChild;
let currentHeading = null;
let currentElements = [];
const flushSection = () => {
if (!currentHeading) return;
// Extract bullet points from paragraphs
const bullets = [];
for (const el of currentElements) {
if (el.tagName === 'UL' || el.tagName === 'OL') {
const items = el.querySelectorAll('li');
for (const item of items) {
const text = item.textContent.trim();
if (text) bullets.push(text);
}
} else if (el.tagName === 'P') {
const text = el.textContent.trim();
if (text) {
const firstSentence = text.match(/^[^.!?]+[.!?]/);
bullets.push(firstSentence ? firstSentence[0] : text.substring(0, 120));
}
} else if (el.tagName === 'BLOCKQUOTE') {
const text = el.textContent.trim();
if (text) bullets.push(text);
}
}
// Limit to 6 bullets per slide for readability
const slideBullets = bullets.slice(0, 6);
// Use full section text as notes
const allText = currentElements
.map(el => el.textContent.trim())
.filter(Boolean)
.join(' ');
const notes = allText.length > 200
? `${allText.substring(0, 500)}...`
: allText;
slides.push({
type: 'content',
heading: currentHeading,
bullets: slideBullets,
notes
});
};
for (const node of wrapper.childNodes) {
if (node.nodeType !== 1) continue; // skip text nodes
if (node.tagName === 'H2') {
flushSection();
currentHeading = node.textContent.trim();
currentElements = [];
} else if (currentHeading) {
currentElements.push(node);
}
}
flushSection();
return slides;
}
/**
* Escape HTML for safe insertion
*/
function escapeForPresentation(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
/**
* Render the presentation UI
*/
function renderPresentation(slides) {
// Hide the normal article view
document.body.classList.add('presentation-active');
// Create presentation container
const container = document.createElement('div');
container.className = 'presentation-mode';
container.id = 'presentation-container';
// Progress bar
const progress = document.createElement('div');
progress.className = 'slide-progress';
progress.id = 'slide-progress';
container.appendChild(progress);
// Slides container
const slidesEl = document.createElement('div');
slidesEl.className = 'presentation-slides';
for (let i = 0; i < slides.length; i++) {
const slide = slides[i];
const slideEl = document.createElement('div');
slideEl.className = `presentation-slide${slide.type === 'title' ? ' slide-title' : ''}`;
slideEl.dataset.index = i;
if (i === 0) slideEl.classList.add('active');
if (slide.type === 'title') {
const subtitleHtml = slide.subtitle
? `