# 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
```
```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 (``, `