# HTML Presentation Template Reference architecture for generating slide presentations. Every presentation follows this structure. ## Base HTML Structure ```html Presentation Title

Presentation Title

Subtitle or author

Slide Title

Content...

``` ## Required JavaScript Features Every presentation must include: 1. **SlidePresentation Class** — Main controller with: - Keyboard navigation (arrows, space, page up/down) - Touch/swipe support - Mouse wheel navigation - Progress bar updates - Navigation dots 2. **Intersection Observer** — For scroll-triggered animations: - Add `.visible` class when slides enter viewport - Trigger CSS transitions efficiently 3. **Optional Enhancements** (match to chosen style): - Custom cursor with trail - Particle system background (canvas) - Parallax effects - 3D tilt on hover - Magnetic buttons - Counter animations 4. **Inline Editing** (only if user opted in during Phase 1 — skip entirely if they said No): - Edit toggle button (hidden by default, revealed via hover hotzone or `E` key) - Auto-save to localStorage - Export/save file functionality - See "Inline Editing Implementation" section below ## Inline Editing Implementation (Opt-In Only) **If the user chose "No" for inline editing in Phase 1, do NOT generate any edit-related HTML, CSS, or JS.** **Do NOT use CSS `~` sibling selector for hover-based show/hide.** The CSS-only approach (`edit-hotzone:hover ~ .edit-toggle`) fails because `pointer-events: none` on the toggle button breaks the hover chain: user hovers hotzone -> button becomes visible -> mouse moves toward button -> leaves hotzone -> button disappears before click. **Required approach: JS-based hover with 400ms delay timeout.** HTML: ```html
``` CSS (visibility controlled by JS classes only): ```css /* Do NOT use CSS ~ sibling selector for this! pointer-events: none breaks the hover chain. Must use JS with delay timeout. */ .edit-hotzone { position: fixed; top: 0; left: 0; width: 80px; height: 80px; z-index: 10000; cursor: pointer; } .edit-toggle { opacity: 0; pointer-events: none; transition: opacity 0.3s ease; z-index: 10001; } .edit-toggle.show, .edit-toggle.active { opacity: 1; pointer-events: auto; } ``` JS (three interaction methods): ```javascript // 1. Click handler on the toggle button document.getElementById("editToggle").addEventListener("click", () => { editor.toggleEditMode(); }); // 2. Hotzone hover with 400ms grace period const hotzone = document.querySelector(".edit-hotzone"); const editToggle = document.getElementById("editToggle"); let hideTimeout = null; hotzone.addEventListener("mouseenter", () => { clearTimeout(hideTimeout); editToggle.classList.add("show"); }); hotzone.addEventListener("mouseleave", () => { hideTimeout = setTimeout(() => { if (!editor.isActive) editToggle.classList.remove("show"); }, 400); }); editToggle.addEventListener("mouseenter", () => { clearTimeout(hideTimeout); }); editToggle.addEventListener("mouseleave", () => { hideTimeout = setTimeout(() => { if (!editor.isActive) editToggle.classList.remove("show"); }, 400); }); // 3. Hotzone direct click hotzone.addEventListener("click", () => { editor.toggleEditMode(); }); // 4. Keyboard shortcut (E key, skip when editing text) document.addEventListener("keydown", (e) => { if ( (e.key === "e" || e.key === "E") && !e.target.getAttribute("contenteditable") ) { editor.toggleEditMode(); } }); ``` **CRITICAL: `exportFile()` must strip edit state before capturing outerHTML.** When the user presses Ctrl+S in edit mode, `document.documentElement.outerHTML` captures the live DOM — including `body.edit-active`, `contenteditable="true"` on every text element, and `.active`/`.show` classes on the toggle button and banner. Anyone opening the saved file sees dashed outlines, a checkmark button, and an edit banner, as if permanently stuck in edit mode. Always implement `exportFile()` like this: ```javascript exportFile() { // Temporarily strip edit state so the saved file opens cleanly const editableEls = Array.from(document.querySelectorAll('[contenteditable]')); editableEls.forEach(el => el.removeAttribute('contenteditable')); document.body.classList.remove('edit-active'); // Also strip UI classes from toggle button and banner const editToggle = document.getElementById('editToggle'); const editBanner = document.querySelector('.edit-banner'); editToggle?.classList.remove('active', 'show'); editBanner?.classList.remove('active', 'show'); const html = '\n' + document.documentElement.outerHTML; // Restore edit state so the user can keep editing document.body.classList.add('edit-active'); editableEls.forEach(el => el.setAttribute('contenteditable', 'true')); editToggle?.classList.add('active'); editBanner?.classList.add('active'); const blob = new Blob([html], { type: 'text/html' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'presentation.html'; a.click(); URL.revokeObjectURL(a.href); } ``` ### Inline Edit — Enter Key & Paste Fix When `contenteditable` is active on elements inside a themed `
` or `
`, pressing Enter inserts a `
` (Chrome default) instead of a `
`, which inherits none of the element's styles and makes the new line appear unstyled. Pasting rich text injects foreign fonts and colors that override the theme. Add these three guards to every inline-editing implementation: ```javascript // 1. Force
on Enter instead of
document.execCommand('defaultParagraphSeparator', false, 'br'); // 2. Intercept Enter to prevent the browser default
insertion document.addEventListener('keydown', e => { if (e.key === 'Enter' && document.querySelector('[contenteditable]:focus')) { e.preventDefault(); document.execCommand('insertLineBreak'); } }); // 3. Strip rich-text formatting on paste — keep plain text only document.addEventListener('paste', e => { const active = document.activeElement; if (!active?.isContentEditable) return; e.preventDefault(); const text = e.clipboardData.getData('text/plain'); document.execCommand('insertText', false, text); }); ``` Without these fixes: after pressing Enter, new lines render with no theme font or color (issue #49); pasting from a browser or document injects foreign styles that break the theme. --- ## Image Pipeline (Skip If No Images) If user chose "No images" in Phase 1, skip this entirely. If images were provided, process them before generating HTML. **Dependency:** `pip install Pillow` ### Image Processing ```python from PIL import Image, ImageDraw # Circular crop (for logos on modern/clean styles) def crop_circle(input_path, output_path): img = Image.open(input_path).convert('RGBA') w, h = img.size size = min(w, h) left, top = (w - size) // 2, (h - size) // 2 img = img.crop((left, top, left + size, top + size)) mask = Image.new('L', (size, size), 0) ImageDraw.Draw(mask).ellipse([0, 0, size, size], fill=255) img.putalpha(mask) img.save(output_path, 'PNG') # Resize (for oversized images that inflate HTML) def resize_max(input_path, output_path, max_dim=1200): img = Image.open(input_path) img.thumbnail((max_dim, max_dim), Image.LANCZOS) img.save(output_path, quality=85) ``` | Situation | Operation | | -------------------------------- | ----------------------------- | | Square logo on rounded aesthetic | `crop_circle()` | | Image > 1MB | `resize_max(max_dim=1200)` | | Wrong aspect ratio | Manual crop with `img.crop()` | Save processed images with `_processed` suffix. Never overwrite originals. ### Image Placement **Use direct file paths** (not base64) — presentations are viewed locally: ```html Screenshot ``` ```css .slide-image { max-width: 100%; max-height: min(50vh, 400px); object-fit: contain; border-radius: 8px; } .slide-image.screenshot { border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 12px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); } .slide-image.logo { max-height: min(30vh, 200px); } ``` **Adapt border/shadow colors to match the chosen style's accent.** Never repeat the same image on multiple slides (except logos on title + closing). **Placement patterns:** Logo centered on title slide. Screenshots in two-column layouts with text. Full-bleed images as slide backgrounds with text overlay (use sparingly). --- ## Code Quality **Comments:** Every section needs clear comments explaining what it does and how to modify it. **Accessibility:** - Semantic HTML (`
`, `