---
name: spiral-gallery
description: >-
  Drop a scroll-scrubbable, draggable 3D spiral/helix media gallery (images and
  videos wrapped around a rotating cylinder, WebGL/Three.js) into any web page.
  As the visitor scrolls the section the helix rotates; they can also drag to
  spin it, hover a tile to zoom it, and click a tile to open its file. Use this
  whenever the user wants a "spiral gallery", "helix gallery", "rotating
  carousel of clips", "3D showcase of photos/videos", a coverflow-style reel, a
  DJ/portfolio/product media wall, or references the spiral thing from
  ogtraplord.com — even if they don't say the word "spiral". Framework-agnostic:
  works in plain HTML, React, Vue, Next, Astro, or any static site.
---

# Spiral Gallery

A self-contained WebGL component that arranges images and videos as planes
coiled around a vertical cylinder. Three motion sources add together: a gentle
idle drift, page-scroll scrub, and pointer drag. Hovering a tile darkens and
zooms it; clicking a tile (a real click, not a drag) opens that file. A custom
shader gives each plane rounded corners, a cover-fit crop, a blurred back face,
and an optional color wash.

This is the engine behind the DJTOKBET spiral on ogtraplord.com, generalized so
it drops into any project without hardcoded IDs or media.

## What's bundled

```
spiral-gallery/
├── SKILL.md
├── assets/
│   ├── spiral-gallery.js      ← the engine (ES module). import { initSpiralGallery }
│   ├── three.module.min.js    ← Three.js (required by the engine)
│   ├── three.core.min.js      ← Three.js core (three.module imports it; keep them together)
│   └── demo.html              ← complete working example, zero external assets
└── references/
    └── customization.md       ← every option, media formats, framework integration, troubleshooting
```

## How to add it to a project

The fastest path is to copy the three JS files in and wire a container. Do this:

1. **Copy the engine + Three.js** into the project (keep the two `three.*.min.js`
   files next to `spiral-gallery.js` — the module imports `./three.core.min.js`
   by a relative path). A common home is `js/` or `assets/`.

2. **Add a container with real height.** The canvas fills the container, so the
   container needs a height (e.g. `min-height:100vh`) and should clip overflow:

   ```html
   <section id="gallery" style="position:relative; min-height:100vh; overflow:hidden"></section>
   ```

3. **Initialize with your media.** Point `src` at real image/video files:

   ```html
   <script type="module">
     import { initSpiralGallery } from './js/spiral-gallery.js';
     initSpiralGallery({
       container: '#gallery',
       media: [
         { type: 'img',   src: 'shots/01.jpg' },
         { type: 'video', src: 'clips/02.mp4', poster: 'shots/02.jpg' },
         { type: 'video', src: 'clips/03.mp4', link: 'https://youtu.be/xxxx' },
       ],
     });
   </script>
   ```

That's the whole integration. Open the page and the spiral appears, drifts,
drags, and opens tiles on click.

**Always show the user `assets/demo.html` first** — it renders with zero external
assets (inline SVG tiles), so it's the quickest way to confirm the component
works in their environment before they wire in real media. They can open it
directly (`open assets/demo.html`) or serve the folder.

## Media items

Each entry in `media` is an object:

- `type` — `'img'` or `'video'` (required)
- `src` — path/URL to the file (required). Relative paths resolve against the
  page URL; if the page uses `<base href>`, they resolve against that.
- `poster` — (video only, optional) a still image shown on touch devices so the
  mp4 is never downloaded on mobile until the visitor opens it. Strongly
  recommended for video-heavy galleries on mobile data.
- `link` — (optional) open this URL on click instead of the raw file. Use it to
  send a clip to its YouTube/Instagram page while still showing the mp4 in the
  spiral.

Portrait media (9:16-ish) looks best with the default `planeAspect`. For
landscape media, set `planeAspect: 0.66` (see customization).

## Common tweaks (the knobs people reach for first)

- **Shape of the coil** — `radius` (distance from camera), `angleGap` (spacing
  between tiles), `vGap` (vertical pitch of the helix).
- **Speed** — `drift` (idle rotation; `0` = still until dragged), `scrubTurns`
  (how far scroll spins it).
- **Color** — `tint` (default cool grey-blue `[0.78,0.86,0.95]`; set `[1,1,1]`
  for true color) and `tintAmount`.
- **Click behavior** — `openOnClick`, `openInNewTab` (set `false` to navigate
  the current tab), or a per-tile `link`.

Full option table, framework notes (React/Next/Vue), the optional GSAP
scroll-scrub + reveal, and troubleshooting live in
`references/customization.md`. Read it when the user wants anything beyond the
defaults or hits a rendering/integration issue.

## Dependencies

- **Three.js** — required, bundled. No install needed.
- **GSAP + ScrollTrigger** — optional. When loaded on the page (globals `gsap` /
  `ScrollTrigger`) before the module, you additionally get scroll-scrub and a
  staggered reveal. Without them the gallery still drifts, drags, hovers, and
  clicks — it just appears instantly and ignores scroll. The engine feature-
  detects them; nothing to configure.

## Notes that save time

- The container must have a non-zero height or the canvas renders 0px tall and
  you see nothing. This is the #1 integration gotcha.
- WebGL needs a GPU context. Headless/sandboxed browsers (some CI, some
  screenshot tools) can't create one and will log "WebGLRenderer: could not
  create context" — that's the environment, not the component. It renders on any
  real browser.
- One spiral per container. To run several on a page, call `initSpiralGallery`
  once per container element.
