Skip to content
Content
Skill

/360-panorama-viewer

Build a fully self-contained 360° equirectangular panorama viewer as a single HTML file. The viewer uses Three.js to render immersive spherical panoramas with drag-to-look, zoom, auto-rotate, and a scene-switcher sidebar. All panorama images are embedded as base64 JPEG — no

BOOST
From plugin
happycapy-skills
13757 skills
Install
$ npx -y skills add happycapy-ai/happycapy-skills --skill 360-panorama-viewer --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/360-panorama-viewer

Context preview

The summary Claude sees to decide when to auto-load this skill.

Build a fully self-contained 360° equirectangular panorama viewer as a single HTML file. The viewer uses Three.js to render immersive spherical panoramas with drag-to-look, zoom, auto-rotate, and a scene-switcher sidebar. All panorama images are embedded as base64 JPEG — no

SKILL.md

360-panorama-viewer.SKILL.md
name: 360-panorama-viewer
description: >
  Build a fully self-contained 360° equirectangular panorama viewer as a single HTML file.
  The viewer uses Three.js to render immersive spherical panoramas with drag-to-look,
  zoom, auto-rotate, and a scene-switcher sidebar. All panorama images are embedded as
  base64 JPEG — no server needed. Use this skill whenever the user asks to create a 360
  viewer, VR panorama app, immersive scene gallery, equirectangular image viewer, or
  wants to combine multiple AI-generated panoramas into an interactive webpage.
  Also trigger when the user says things like "make a 360 viewer", "VR world gallery",
  "360度全景", "全景查看器", "make scenes I can look around in", etc.

360° Panorama Viewer Skill

This skill creates a polished, self-contained 360° panorama viewer HTML file.

What it produces

A single `.html` file (~3–6 MB depending on scene count) that:

  • Renders equirectangular panoramas as spherical 360° environments using Three.js
  • Supports dragging to look around, scroll to zoom, auto-rotate toggle, fullscreen
  • Shows a thumbnail sidebar to switch between multiple scenes
  • Works offline — no CDN dependencies, all assets embedded

Skill assets

| Asset | Purpose | |---|---| | `assets/viewer_template.html` | Complete viewer HTML with Three.js inlined; panorama data injected at build time | | `scripts/build_viewer.py` | Loads images, applies seam fix, base64-encodes, injects into template |

---

Workflow

Step 1 — Gather scene specs from the user

Ask (or infer from context) for each scene:

  • **Description** of what the panorama should show
  • **Title** for the HUD (emoji + name, e.g. `🍄 Mario World`)
  • **Thumbnail label** (≤12 chars shown on the sidebar chip)

Typical count: 3–6 scenes. You can also accept user-provided image files directly (skip generation).

Step 2 — Generate panorama images

For **each scene**, generate a 360° equirectangular panorama image.

**Model choice:**

  • **Preferred:** `google/gemini-3.1-flash-image-preview` via AI Gateway — reliable 2:1 output, no safety rejections for fictional themes
  • **Alternative:** `gpt-image-2` via AI Gateway at size `1536x1024` — higher quality but may reject branded IP (Mario, Zelda, etc.)

**Generation code (Gemini route):**

import os, requests, base64
from PIL import Image
import io

api_key = os.environ['AI_GATEWAY_API_KEY']

def generate_panorama(prompt: str, save_path: str):
    payload = {
        "model": "google/gemini-3.1-flash-image-preview",
        "prompt": prompt,
        "response_format": "b64_json",
        "n": 1
    }
    resp = requests.post(
        'https://ai-gateway.happycapy.ai/api/v1/images/generations',
        headers={
            'Authorization': f'Bearer {api_key}',
            'Content-Type': 'application/json',
            'Origin': 'https://trickle.so'
        },
        json=payload,
        timeout=180
    )
    resp.raise_for_status()
    img_bytes = base64.b64decode(resp.json()['data'][0]['b64_json'])
    img = Image.open(io.BytesIO(img_bytes)).convert('RGB')
    img.save(save_path)
    return save_path

**Prompt formula for good equirectangular panoramas:**

360 degree equirectangular panorama of [SCENE DESCRIPTION].
[Key visual elements]. [Style description].
Wide seamless landscape, 2:1 aspect ratio,
left and right edges must tile perfectly for 360 VR viewing.

**Example prompts:**

  • Mario: `360 degree equirectangular panorama of a colorful cartoon mushroom kingdom platformer world. Bright blue sky, rolling green hills, red mushroom houses, floating brick blocks, gold coins, stone castle. Vibrant cartoon illustration, 2:1 aspect ratio, seamless tiling.`
  • Underwater city: `360 degree equirectangular panorama of a futuristic underwater city. Bioluminescent buildings, schools of fish, coral reefs, deep ocean light shafts. Cinematic, 2:1 aspect ratio, seamless tiling.`

Save each generated image to a temp path (e.g. `tmp/scene_N_raw.png`).

Step 3 — Build the viewer

Run the build script, passing all scenes as a JSON array:

SKILL_DIR=/home/node/.claude/skills/360-panorama-viewer

python3 "$SKILL_DIR/scripts/build_viewer.py" \
  --template "$SKILL_DIR/assets/viewer_template.html" \
  --output "outputs/360_viewer.html" \
  --scenes '[
    {"title":"🍄 Mario World","thumb_label":"Mario","image_path":"tmp/mario.png","initial_lon":90},
    {"title":"🐚 Underwater City","thumb_label":"Deep City","image_path":"tmp/underwater.png","initial_lon":0}
  ]'

The script will: 1. Resize each image to 1774×887 (standard 2:1) 2. Apply a **pure-roll seam fix** — finds the horizontal offset that minimises left/right edge difference, then `np.roll`s the image. No blending. This ensures the 360° seam is as clean as possible without distorting colors. 3. Base64-encode as JPEG quality 90 4. Inject into the template and write the output HTML

Step 4 — Deliver

Output the file to `outputs/360_viewer.html` (or user-specified path) and attach it as a static HTML deliverable.

---

Replacing a single scene in an existing viewer

If the user already has a viewer and wants to swap out one scene (e.g. regenerate Mario), do NOT rerun all scenes — just re-encode the new image and replace its `b64` field in the HTML:

import re, base64, io, numpy as np
from PIL import Image

SKILL_DIR = '/home/node/.claude/skills/360-panorama-viewer'

# Load and fix the new image
img = Image.open('new_mario.png').convert('RGB')
img = img.resize((1774, 887), Image.LANCZOS)
arr = np.array(img)
# pure roll seam fix
f = arr.astype(np.float32)
h, w = arr.shape[:2]
best_roll, best_score = 0, float('inf')
for roll in range(w):
    r = np.roll(f, roll, axis=1)
    score = float(np.abs(r[:, :5] - r[:, w-5:]).mean())
    if score < best_score:
        best_score = score
        best_roll = roll
fixed = np.roll(arr, best_roll, axis=1)

buf = io.BytesIO()
Image.fromarray(fixed).save(buf, format='JPEG', quality=90)
new_b64 = base64.b64encode(buf
Read more
Ships withhappycapy-skills

A curated collection of high-quality Claude Code skills to enhance your development workflow

Get the whole plugin

Other skills on happycapy-skills.