Generate interactive HTML presentations with sidebar navigation, scrollable sections, and 40+ typography-driven components. Supports Metis branding with auto-embedded logo and client brand extraction from PPTX templates. Output is a self-contained HTML file viewable in any browser.
68
85%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Passed
No known issues
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{TITLE}}</title>
<style>
{{THEME_CSS}}
{{COMPONENTS_CSS}}
</style>
</head>
<body>
<div class="presentation">
<!-- ── Sidebar ── -->
<nav class="sidebar" id="sidebar">
<div class="sidebar-header">
{{LOGO}}
<div class="sidebar-deck-title">{{SUBTITLE}}</div>
</div>
<div class="sidebar-nav" id="sidebarNav">
{{SIDEBAR_NAV}}
</div>
<div class="sidebar-footer">
<div class="progress-bar"><div class="progress-fill" id="progressFill"></div></div>
<div class="progress-label" id="progressLabel">PROGRESS 1 / 1</div>
</div>
</nav>
<!-- ── Content Area ── -->
<main class="content-area">
<header class="section-header">
<div class="section-marker" id="sectionMarker">
<span class="section-symbol">§</span> <span id="sectionText"></span>
</div>
<div class="slide-pagination">
<span class="key-hint">← → keys</span>
<button class="nav-arrow" id="navPrev" aria-label="Previous slide">‹</button>
<span class="page-count" id="pageCount">1 / 1</span>
<button class="nav-arrow" id="navNext" aria-label="Next slide">›</button>
</div>
</header>
<div class="deck" id="deck">
{{SLIDES}}
</div>
</main>
</div>
<script>
(function() {
var deck = document.getElementById('deck');
var slides = deck.querySelectorAll('.slide');
var pageCount = document.getElementById('pageCount');
var progressFill = document.getElementById('progressFill');
var progressLabel = document.getElementById('progressLabel');
var sectionText = document.getElementById('sectionText');
var sidebarNav = document.getElementById('sidebarNav');
var current = 0;
// ── Build section map from slide data attributes ──
var sections = [];
var sectionMap = {}; // sectionNum -> { title, slides: [indices] }
slides.forEach(function(slide, i) {
var secNum = slide.dataset.sectionNum || '';
var secTitle = slide.dataset.sectionTitle || '';
var subLabel = slide.dataset.subLabel || '';
if (secNum && !sectionMap[secNum]) {
sectionMap[secNum] = { title: secTitle, num: secNum, slides: [], subLabels: [] };
sections.push(sectionMap[secNum]);
}
if (secNum) {
sectionMap[secNum].slides.push(i);
sectionMap[secNum].subLabels.push(subLabel);
}
});
// ── Show slide ──
function showSlide(n) {
if (n < 0 || n >= slides.length) return;
slides[current].classList.remove('active');
current = n;
slides[current].classList.add('active');
// Reset scroll position
slides[current].scrollTop = 0;
// Update page counter
pageCount.textContent = (current + 1) + ' / ' + slides.length;
// Update progress
var pct = slides.length > 1 ? ((current) / (slides.length - 1)) * 100 : 100;
progressFill.style.width = pct + '%';
progressLabel.textContent = 'PROGRESS ' + (current + 1) + ' / ' + slides.length;
// Update section marker
var secNum = slides[current].dataset.sectionNum || '';
var secTitle = slides[current].dataset.sectionTitle || '';
if (secNum) {
sectionText.textContent = secNum + ' \u00B7 ' + secTitle;
} else {
sectionText.textContent = '';
}
// Update sidebar active states
updateSidebar();
// Trigger count-up animations
animateCountUps();
}
function next() { showSlide(current + 1); }
function prev() { showSlide(current - 1); }
// ── Update sidebar highlighting ──
function updateSidebar() {
var curSecNum = slides[current].dataset.sectionNum || '';
var curSubLabel = slides[current].dataset.subLabel || '';
// Update section active states
var navSections = sidebarNav.querySelectorAll('.nav-section');
navSections.forEach(function(sec) {
var secNum = sec.dataset.section;
sec.classList.remove('active');
if (secNum === curSecNum) {
sec.classList.add('active');
}
// Mark visited
if (sectionMap[secNum]) {
var firstSlide = sectionMap[secNum].slides[0];
if (firstSlide < current) {
sec.classList.add('visited');
}
}
});
// Update sub-item active states
var subItems = sidebarNav.querySelectorAll('.nav-sub-item');
subItems.forEach(function(sub) {
sub.classList.remove('active');
if (sub.dataset.slideIndex == current) {
sub.classList.add('active');
}
});
// Scroll active item into view
var activeSec = sidebarNav.querySelector('.nav-section.active');
if (activeSec) {
activeSec.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
}
}
// ── Sidebar click handlers ──
sidebarNav.addEventListener('click', function(e) {
// Section header click
var header = e.target.closest('.nav-section-header');
if (header) {
var slideIdx = parseInt(header.dataset.slide, 10);
if (!isNaN(slideIdx)) showSlide(slideIdx);
return;
}
// Sub-item click
var subItem = e.target.closest('.nav-sub-item');
if (subItem) {
var slideIdx = parseInt(subItem.dataset.slideIndex, 10);
if (!isNaN(slideIdx)) showSlide(slideIdx);
return;
}
});
// ── Pagination button clicks ──
document.getElementById('navPrev').addEventListener('click', function(e) {
e.stopPropagation();
prev();
});
document.getElementById('navNext').addEventListener('click', function(e) {
e.stopPropagation();
next();
});
// ── Keyboard navigation ──
document.addEventListener('keydown', function(e) {
switch(e.key) {
case 'ArrowRight': case 'ArrowDown': case ' ': case 'PageDown':
e.preventDefault(); next(); break;
case 'ArrowLeft': case 'ArrowUp': case 'PageUp':
e.preventDefault(); prev(); break;
case 'Home': e.preventDefault(); showSlide(0); break;
case 'End': e.preventDefault(); showSlide(slides.length - 1); break;
case 'f': case 'F':
if (!e.ctrlKey && !e.metaKey) {
e.preventDefault();
if (document.fullscreenElement) document.exitFullscreen();
else document.documentElement.requestFullscreen();
}
break;
case 'n': case 'N':
if (!e.ctrlKey && !e.metaKey) {
document.querySelectorAll('.notes').forEach(function(el) {
el.style.display = el.style.display === 'block' ? 'none' : 'block';
});
}
break;
}
});
// ── Touch/swipe support on deck ──
var touchStartX = 0;
deck.addEventListener('touchstart', function(e) { touchStartX = e.touches[0].clientX; });
deck.addEventListener('touchend', function(e) {
var dx = e.changedTouches[0].clientX - touchStartX;
if (Math.abs(dx) > 50) { dx < 0 ? next() : prev(); }
});
// ── Click navigation on deck (right 70% = next, left 30% = prev) ──
deck.addEventListener('click', function(e) {
if (e.target.closest('a, button, input, .tab-btn, .clickable-reveal, .accordion-trigger, .nav-arrow')) return;
var deckRect = deck.getBoundingClientRect();
var relX = e.clientX - deckRect.left;
(relX < deckRect.width * 0.3) ? prev() : next();
});
// ── Clickable reveal ──
document.querySelectorAll('.clickable-reveal').forEach(function(el) {
el.addEventListener('click', function(e) {
if (e.target.closest('a, button, input, .tab-btn')) return;
el.classList.toggle('open');
e.stopPropagation();
});
});
// ── Tabs ──
document.querySelectorAll('.tab-group').forEach(function(group) {
var btns = group.querySelectorAll('.tab-btn');
var panels = group.querySelectorAll('.tab-panel');
btns.forEach(function(btn, i) {
btn.addEventListener('click', function(e) {
e.stopPropagation();
btns.forEach(function(b) { b.classList.remove('active'); });
panels.forEach(function(p) { p.classList.remove('active'); });
btn.classList.add('active');
if (panels[i]) panels[i].classList.add('active');
});
});
});
// ── Accordion ──
document.querySelectorAll('.accordion-trigger').forEach(function(trigger) {
trigger.addEventListener('click', function(e) {
e.stopPropagation();
var item = trigger.closest('.accordion-item');
item.classList.toggle('open');
});
});
// ── Count-up animation ──
function animateCountUps() {
document.querySelectorAll('.slide.active .count-up').forEach(function(el) {
if (el.dataset.counted) return;
el.dataset.counted = 'true';
var target = parseFloat(el.textContent.replace(/[^0-9.]/g, ''));
var prefix = el.textContent.match(/^[^0-9]*/)[0];
var suffix = el.textContent.match(/[^0-9]*$/)[0];
var duration = 1200;
var start = performance.now();
function step(now) {
var progress = Math.min((now - start) / duration, 1);
var eased = 1 - Math.pow(1 - progress, 3);
var val = Math.round(target * eased);
el.textContent = prefix + val.toLocaleString() + suffix;
if (progress < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
});
}
// Reset count-up on slide change
var _showSlide = showSlide;
showSlide = function(n) {
slides[current].querySelectorAll('.count-up').forEach(function(el) {
delete el.dataset.counted;
});
_showSlide(n);
};
// ── Initialize ──
if (slides.length > 0) {
slides[0].classList.add('active');
pageCount.textContent = '1 / ' + slides.length;
var pct0 = slides.length > 1 ? 0 : 100;
progressFill.style.width = pct0 + '%';
progressLabel.textContent = 'PROGRESS 1 / ' + slides.length;
var sec0Num = slides[0].dataset.sectionNum || '';
var sec0Title = slides[0].dataset.sectionTitle || '';
if (sec0Num) {
sectionText.textContent = sec0Num + ' \u00B7 ' + sec0Title;
}
updateSidebar();
animateCountUps();
}
})();
</script>
</body>
</html>