Index of every page on this site: llms.txt
All skills
affaan-m avatar

/frontend-slides

@90606c8
by Affaan Mustafaaffaan-m/ecc287k stars
42,569
Run it yourself

Create stunning, animation-rich HTML presentations from scratch or by converting PowerPoint files. Use when the user wants to build a presentation, convert a PPT/PPTX to web, or create slides for a talk/pitch. Helps non-designers discover their aesthetic through visual exploration rather than abstract choices.

Use this Skill: https://skilld.dev/gh/affaan-m/ecc/frontend-slides

Nothing lands on disk. Nothing to clean up.

Fork this Skill

Edit a local copy. It keeps the author and licence.

html-template.md

≈3.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

HTML Presentation Template

Reference architecture for generating slide presentations. Every presentation follows this structure.

Base HTML Structure

<!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:

<div class="edit-hotzone"></div>
<button class="edit-toggle" id="editToggle" title="Edit mode (E)">Edit</button>

CSS (visibility controlled by JS classes only):

/* 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):

// 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:

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

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:

<img src="assets/logo_round.png" alt="Logo" class="slide-image logo" />
<img
  src="assets/screenshot.png"
  alt="Screenshot"
  class="slide-image screenshot"
/>
.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/

Source: SKILL.md on GitHub

skilld matched fixed text patterns in SKILL.md and file names. Patterns miss obfuscated code.

skilld run checks every file with the same patterns. It asks for approval before it loads a Skill with a behavior marked Needs approval.

1 warningtoday3 checks · Risk SAFE
  • Gen Agent Trust Hubtoday

    The skill is designed to generate HTML presentations and includes utilities for converting PowerPoint files and exporting to PDF. It uses standard libraries and implements security controls for its local temporary export server. The primary security consideration is the indirect prompt injection surface when processing user-provided PowerPoint files.

  • Sockettoday

    No alerts

  • Snyktoday

    Risk: MEDIUM · 1 issue

Signed by skilld at 90606c8. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 days ago.

Activeupdated 4 months ago
metadata
{
  "origin": "ECC"
}
  • html
  • presentations
  • slides
  • ppt
  • pptx
  • animation
  • css
  • javascript
  • responsive
  • accessibility

README badge

README badge for affaan-m/ecc/frontend-slides

Creates zero-dependency HTML presentations with animation and responsive design, running entirely in the browser. Supports building decks from scratch, converting PowerPoint files, or enhancing existing HTML slides while enforcing viewport-fit constraints and visual exploration of design direction.

Generated from the current SKILL.md.

Does this skill convert PowerPoint files to HTML?
Yes. The skill uses python-pptx to extract text, images, and notes from .ppt or .pptx files, then builds an HTML presentation from the extracted content. It falls back to manual export if python-pptx is unavailable.
Can I use external libraries or dependencies?
No. The skill defaults to zero-dependency, self-contained HTML files with inline CSS and JavaScript. External fonts may come from Google Fonts or Fontshare, but no build tools or npm packages.
What happens if my content doesn't fit on one slide?
The skill splits content across multiple slides rather than allowing scrolling. Viewport fit is a hard requirement: every slide must display fully within the browser window without internal scrollbars.
Does the skill generate a design automatically or ask me questions first?
It uses visual exploration by default. The skill generates 3 single-slide preview files showing different styles, then asks you which preset to keep or which elements to mix, rather than using abstract questionnaires.
What navigation methods are supported?
Keyboard navigation, touch/swipe navigation, and mouse wheel navigation. The skill also includes a progress indicator and respects reduced-motion preferences.

Generated from the current SKILL.md. These answers refresh after source changes.