UNPKG

opencode-agent-kit

Version:

Multi-stack OpenCode agent toolkit — 33+ specialized AI agents, 200+ skills, 46 commands, 8 MCP servers (Nuxt, React, Node.js, Laravel, CI3, Android, Flutter, DevOps, SEO, SonarQube, and more)

420 lines (341 loc) 12.5 kB
# HTML Presentation Template Reference architecture for generating slide presentations. Every presentation follows this structure. ## Base HTML Structure ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Presentation Title</title> <!-- Fonts: use Fontshare or Google Fonts — never system fonts --> <link rel="stylesheet" href="https://api.fontshare.com/v2/css?f[]=..." /> <style> /* =========================================== CSS CUSTOM PROPERTIES (THEME) Change these to change the whole look =========================================== */ :root { /* Colors — from chosen style preset */ --bg-primary: #0a0f1c; --bg-secondary: #111827; --text-primary: #ffffff; --text-secondary: #9ca3af; --accent: #00ffcc; --accent-glow: rgba(0, 255, 204, 0.3); /* Typography — MUST use clamp() */ --font-display: "Clash Display", sans-serif; --font-body: "Satoshi", sans-serif; --title-size: clamp(2rem, 6vw, 5rem); --subtitle-size: clamp(0.875rem, 2vw, 1.25rem); --body-size: clamp(0.75rem, 1.2vw, 1rem); /* Spacing — MUST use clamp() */ --slide-padding: clamp(1.5rem, 4vw, 4rem); --content-gap: clamp(1rem, 2vw, 2rem); /* Animation */ --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); --duration-normal: 0.6s; } /* =========================================== BASE STYLES =========================================== */ * { margin: 0; padding: 0; box-sizing: border-box; } /* --- PASTE viewport-base.css CONTENTS HERE --- */ /* =========================================== ANIMATIONS Trigger via .visible class (added by JS on scroll) =========================================== */ .reveal { opacity: 0; transform: translateY(30px); transition: opacity var(--duration-normal) var(--ease-out-expo), transform var(--duration-normal) var(--ease-out-expo); } .slide.visible .reveal { opacity: 1; transform: translateY(0); } /* Stagger children for sequential reveal */ .reveal:nth-child(1) { transition-delay: 0.1s; } .reveal:nth-child(2) { transition-delay: 0.2s; } .reveal:nth-child(3) { transition-delay: 0.3s; } .reveal:nth-child(4) { transition-delay: 0.4s; } /* ... preset-specific styles ... */ </style> </head> <body> <!-- Optional: Progress bar --> <div class="progress-bar"></div> <!-- Optional: Navigation dots --> <nav class="nav-dots"><!-- Generated by JS --></nav> <!-- Slides --> <section class="slide title-slide"> <h1 class="reveal">Presentation Title</h1> <p class="reveal">Subtitle or author</p> </section> <section class="slide"> <div class="slide-content"> <h2 class="reveal">Slide Title</h2> <p class="reveal">Content...</p> </div> </section> <!-- More slides... --> <script> /* =========================================== SLIDE PRESENTATION CONTROLLER =========================================== */ class SlidePresentation { constructor() { this.slides = document.querySelectorAll(".slide"); this.currentSlide = 0; this.setupIntersectionObserver(); this.setupKeyboardNav(); this.setupTouchNav(); this.setupProgressBar(); this.setupNavDots(); } setupIntersectionObserver() { // Add .visible class when slides enter viewport // Triggers CSS animations efficiently } setupKeyboardNav() { // Arrow keys, Space, Page Up/Down } setupTouchNav() { // Touch/swipe support for mobile } setupProgressBar() { // Update progress bar on scroll } setupNavDots() { // IMPORTANT: Always clear before building — if outerHTML was // captured while dots were rendered, re-opening the file would // append a duplicate set on top of the existing ones. this.navDotsContainer.innerHTML = ""; // Generate and manage navigation dots } } new SlidePresentation(); </script> </body> </html> ``` ## 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 <div class="edit-hotzone"></div> <button class="edit-toggle" id="editToggle" title="Edit mode (E)">Edit</button> ``` 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 = '<!DOCTYPE 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); } ``` ## 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 <img src="assets/logo_round.png" alt="Logo" class="slide-image logo" /> <img src="assets/screenshot.png" alt="Screenshot" class="slide-image 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 (`<section>`, `<nav>`, `<main>`) - Keyboard navigation works fully - ARIA labels where needed - `prefers-reduced-motion` support (included in viewport-base.css) ## File Structure Single presentations: ``` presentation.html # Self-contained, all CSS/JS inline assets/ # Images only, if any ``` Multiple presentations in one project: ``` [name].html [name]-assets/ ```