Agent Skills: Explorable Explanations

Build single-file interactive HTML explanations that teach concepts through experimentation — sliders, animations, step-through controls, and multiple visual representations. Use when the user explicitly asks to build an explorable explanation, interactive explanation, or animated walkthrough of a concept.

UncategorizedID: third774/dotfiles/explorable-explanations

Install this agent skill to your local

pnpm dlx add-skill https://github.com/third774/dotfiles/tree/HEAD/opencode/skills/explorable-explanations

Skill Files

Browse the full folder contents for explorable-explanations.

Download Skill

Loading file tree…

opencode/skills/explorable-explanations/SKILL.md

Skill Metadata

Name
explorable-explanations
Description
Build single-file interactive HTML explanations that teach concepts through experimentation — sliders, animations, step-through controls, and multiple visual representations. Use when the user explicitly asks to build an explorable explanation, interactive explanation, or animated walkthrough of a concept.

Explorable Explanations

Build interactive single-file HTML pages that teach concepts through direct manipulation and experimentation, not passive reading. The reader should develop working intuition by playing with the system, not by being told how it works.

Core belief: People learn through experimentation. A slider that lets you feel how frequency affects pitch teaches more than a paragraph explaining it.

Complexity Budget

Match interaction complexity to concept complexity. A concept that needs one slider and one visualization should not ship with a transport bar, scroll-driven progression, and multiple linked representations. Every control, view, and animation frame you add costs the reader attention — spend only what the concept demands.

Before building, ask: what is the minimum interaction that gives the reader the intuition they need? Start there. Add more only when a simpler version falls short.

Pedagogical Framework

Draw from these layers as the concept demands. Not every explorable needs all six — skip steps that don't earn their weight. The order is a sensible default, not a mandate.

1. Ground the Reader

Start with something concrete and familiar. Before explaining how a hash table works, show a phonebook. Before explaining Fourier transforms, play a sound.

  • Open with a visible, interactive element — not a wall of text
  • The initial state SHOULD already demonstrate the concept at its simplest
  • Use meaningful defaults that teach something on their own
  • Signal that the page is interactive — readers often assume static content. A visual cue like an animated down-arrow for scroll-driven explorables, a pulsing "drag me" hint on a draggable element, or a brief intro line ("try moving the slider") helps set the expectation that this is something to play with, not just read

2. One Knob at a Time

Introduce complexity incrementally. Each new interactive control SHOULD teach exactly one thing.

  • Add one parameter, let the reader explore it fully, then introduce the next
  • NEVER present all controls at once — progressive reveal as the reader scrolls or advances
  • Label controls with what they mean, not what they are (e.g., "Pitch" not "Frequency (Hz)" for a first encounter; technical terms come after intuition)

3. Tight Feedback Loops

Every input change MUST produce an immediately visible output change.

  • Continuous controls (sliders) SHOULD update the visualization in real-time, not on release
  • If computation is expensive, show a degraded/simplified preview during interaction and refine on release
  • The visual response should feel directly connected to the control — no indirection, no delay

4. Multiple Representations (When It Helps)

Some concepts click when the reader sees them from two angles at once — a wave as motion and as a graph, a matrix transform as numbers and as geometry. When this applies, linked representations are extremely effective:

  • Pair an animation with a graph
  • Show both the spatial view and the mathematical view
  • Highlight the connection between representations (e.g., a dot on the graph that moves in sync with the animated element)

Not every concept benefits from this. An explorable about easing functions, color mixing, or grid layout doesn't need a second view — the primary visualization is the explanation. Add a second representation only when it reveals a relationship that one view alone cannot.

5. Let Them Break It

The reader SHOULD be able to push parameters to extremes.

  • Edge cases are where intuition solidifies — what happens when frequency is 0? When amplitude is maxed?
  • Don't clamp inputs to "safe" ranges unless things genuinely break
  • When extremes produce interesting behavior, add a brief annotation explaining why

6. Name It Last

Technical terminology SHOULD appear after the reader already has intuition for the concept.

  • First: "how far the particle moves" + a slider
  • Then: "this property is called amplitude"
  • The term sticks because it labels something they already understand

Technical Approach

Single HTML File

Output MUST be a single .html file with no build step. Use Tailwind CSS via CDN for styling.

Start with vanilla JS. Event listeners, requestAnimationFrame, and direct DOM updates handle most explorables cleanly. Reach for Vue 3 (via CDN) only when the complexity genuinely warrants it — roughly when you have 5+ reactive bindings that depend on each other, or component-like sections that benefit from Vue's template syntax.

Vanilla baseline:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Explorable: [Topic]</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <style>
    /* Fallback CSS only when Tailwind utilities are insufficient */
  </style>
</head>
<body>
  <div id="app">
    <!-- Template here -->
  </div>
  <script>
    // Direct DOM refs, event listeners, requestAnimationFrame
  </script>
</body>
</html>

When Vue is warranted, add the CDN script and use the Composition API:

<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script>
  const { createApp, ref, computed, onMounted, onUnmounted, watch } = Vue

  createApp({
    setup() {
      // Reactive state, computed properties, animation loops
      return { /* template bindings */ }
    }
  }).mount('#app')
</script>

Styling: Tailwind CDN First

  • Prefer standard Tailwind utility classes for layout, spacing, typography, color, and states.
  • Use custom CSS classes in <style> only as a fallback when a style cannot be expressed cleanly with available utility classes.
  • Keep fallback CSS small and local to the explorable.

Hard rule: Tailwind arbitrary values ([...]) are NOT available in this single-file HTML + no-build-step + CDN setup and MUST NOT be used.

Reference: https://tailwindcss.com/docs/adding-custom-styles#using-arbitrary-values

Visualization: SVG vs Canvas

| Use SVG when | Use Canvas when | |---|---| | < 500 elements | > 500 elements | | Need DOM events on individual elements | Need pixel-level control | | Mostly shapes, lines, text | Particle systems, complex animation | | Interactivity on individual elements | Performance-critical rendering |

Default to SVG. It's declarative, inspectable in devtools, and handles most explorable needs. It also composes well with Vue's template syntax if you're using Vue.

Animation Loop Pattern

For animated explorables, use requestAnimationFrame with elapsed-time-based updates (not frame-counting):

const elapsed = ref(0)
const playing = ref(false)
const speed = ref(1)
let lastTime = null
let frameId = null

function tick(now) {
  if (lastTime != null) {
    elapsed.value += (now - lastTime) * speed.value
  }
  lastTime = now
  frameId = requestAnimationFrame(tick)
}

function togglePlay() {
  if (playing.value) {
    cancelAnimationFrame(frameId)
    lastTime = null
    playing.value = false
  } else {
    playing.value = true
    frameId = requestAnimationFrame(tick)
  }
}

// Step forward one frame (~16ms) while paused
function step() {
  if (!playing.value) {
    elapsed.value += 16 * speed.value
  }
}

onUnmounted(() => cancelAnimationFrame(frameId))

Interpolation for Smooth Transitions

When a discrete parameter change should animate smoothly (e.g., morphing between shapes), use linear interpolation driven by requestAnimationFrame:

function lerp(a, b, t) {
  return a + (b - a) * Math.min(1, Math.max(0, t))
}

For spring-like smoothing on slider values, apply exponential easing each frame:

// In the animation loop:
displayValue.value += (targetValue.value - displayValue.value) * 0.1

Interactive Control Patterns

Playback Controls

Animated explorables need some way for the reader to control time. Pick from this menu based on what the concept requires — don't include controls the reader won't use:

  • Play/Pause toggle — the baseline for any animation
  • Reset button — useful when the reader can get lost in a long-running state
  • Speed slider (0.25x to 4x) — helpful when the concept benefits from slow-motion observation
  • Step button (advance one frame while paused) — valuable for discrete/algorithmic concepts where each tick matters
  • Timeline scrubber — sometimes a single slider replacing play/pause/step is the cleanest option

A simple looping animation might only need play/pause. A sorting algorithm walkthrough might need step + reset. Let the concept decide.

Parameter Controls

  • Sliders for continuous values — always show the current numeric value alongside
  • Segmented toggles for discrete choices (e.g., waveform shape: sine / triangle / square)
  • Checkboxes for toggling layers or annotations on/off

Scroll-Driven Progression

For long-form explorables that tell a story:

  • Pin the visualization and let explanatory text scroll beside it
  • Each scroll section introduces one new concept or control
  • Use IntersectionObserver to trigger state changes as sections enter the viewport

This structure works well for long-form explorables that build up a complex concept over many steps. For simpler concepts, a single viewport with everything visible is often better — the reader can cross-reference controls and output without losing context to scrolling.

Layout and Styling

Structure

A typical explorable layout:

┌──────────────────────────────────┐
│  Title                           │
├──────────────────────────────────┤
│                                  │
│  ┌────────────────────────────┐  │
│  │                            │  │
│  │     Visualization          │  │
│  │                            │  │
│  └────────────────────────────┘  │
│                                  │
│  ┌────────────────────────────┐  │
│  │ ▶ ⏸  ──●────── 1.0x  ⟳   │  │
│  └────────────────────────────┘  │
│                                  │
│  ┌─────────┐  ┌──────────────┐  │
│  │ Control │  │ Explanatory  │  │
│  │ Panel   │  │ text that    │  │
│  │         │  │ updates with │  │
│  │         │  │ context      │  │
│  └─────────┘  └──────────────┘  │
└──────────────────────────────────┘

For scroll-driven explorables, use a two-column layout with sticky visualization:

┌──────────────┬───────────────────┐
│              │                   │
│  Scrolling   │  Sticky           │
│  text with   │  visualization    │
│  controls    │  that reacts to   │
│  appearing   │  scroll position  │
│  per section │                   │
│              │                   │
└──────────────┴───────────────────┘

Styling Defaults

  • Tailwind utility classes SHOULD be the default styling mechanism
  • Use a clear high-contrast palette (dark or light) so the visualization remains legible
  • Generous padding around the visualization — it's the centerpiece, give it room
  • Controls SHOULD look touchable — rounded corners, visible affordances
  • Max-width the content area (~900px) — don't let it sprawl on wide screens
  • If Tailwind utilities are insufficient, add a small fallback custom class in <style>

Workflow

When building an explorable explanation:

Build Progress:
- [ ] Step 1: Identify the core concept and what intuition the reader should leave with
- [ ] Step 2: Find the simplest possible interactive demonstration of that concept
- [ ] Step 3: Plan the progression — what controls appear and in what order
- [ ] Step 4: Build the base visualization with one interactive parameter
- [ ] Step 5: Layer in additional parameters one at a time
- [ ] Step 6: Add playback controls if animated
- [ ] Step 7: Write bridging text — short, contextual, appears alongside the relevant control
- [ ] Step 8: Test the pedagogical flow — does each step build on the last?
- [ ] Step 9: Polish — smooth transitions, meaningful defaults, edge case behavior

Anti-Patterns

| Pattern | Why it fails | |---|---| | Long text intro before any interactivity | Reader disengages before reaching the interesting part | | All controls visible from the start | Overwhelming; no guided learning path | | Controls that don't visibly affect anything | Breaks the feedback loop; reader loses trust | | Technical jargon before intuition | Terms without grounding don't stick | | Clamping everything to "safe" ranges | Robs the reader of discovering edge cases | | Tailwind arbitrary value classes (e.g., w-[37rem], text-[#4a9eff]) | Not available in single-file CDN/no-build setup; use standard utilities or fallback custom CSS | | Static diagram where interaction would help | If the reader can't manipulate it, it's not an explorable |

<IMPORTANT> The goal is intuition, not information transfer. If the reader can recite facts but can't predict what happens when they move a slider, the explorable failed. Every design decision should serve the question: "Will this help the reader build a mental model they can reason with?" </IMPORTANT>