From lll-animation
This skill should be used when a participant asks "what do I do next?", "I'm stuck", "what should I work on?", asks about a specific task number (e.g. "help with 1.3"), wants to check if their work is done, or needs to understand the workshop structure, or mentions Phase 1, Phase 2, or any task by number. Provides task-by-task guidance, acceptance criteria, gotcha warnings, and phase transition help for the 3-hour rolling cube workshop.
npx claudepluginhub simon-tanna/lll-animation-plugin --plugin lll-animationThis skill uses the workspace's default tool permissions.
A 3-hour workshop building a rolling 3D isometric cube in two phases.
Creates 3D scenes, interactive experiences, and visual effects using Three.js best practices for WebGL rendering, geometries, lighting, animations, and OrbitControls.
Creates simple Three.js web apps with scene setup, lighting, geometries, materials, animations, and responsive rendering for 3D web content.
Builds interactive 3D web scenes with Three.js using WebGL/WebGPU. Guides on scenes, cameras, renderers, geometries, materials, meshes, lights, animations, and OrbitControls.
Share bugs, ideas, or general feedback.
A 3-hour workshop building a rolling 3D isometric cube in two phases.
Tech stack: Vite + React TypeScript, GSAP, Three.js
Participants receive a starter repo with a working Vite/React TypeScript app, an isometric diamond grid background, and all dependencies pre-installed (GSAP, Three.js).
End result: A mint/teal 3D cube in isometric view that continuously rolls across the grid — tumbling face-to-face in random directions, bouncing off window boundaries.
Pre-configured: Vite + React TypeScript, isometric diamond grid background, OrthographicCamera and RAF render loop (Phase 2 boilerplate), all dependencies installed (GSAP, Three.js).
Do not modify: the camera, the grid, or the boilerplate render loop.
Component files: Cube.tsx (Phase 1 — write your CSS cube here), Cube3D.tsx (Phase 2 — write your Three.js cube here).
| Phase | Duration | Stack | Focus |
|---|---|---|---|
| Phase 1 | ~1.5 hours | CSS + GSAP | Build cube with CSS 3D transforms, animate rolling with GSAP |
| Phase 2 | ~1.5 hours | Three.js + GSAP | Rebuild cube with Three.js geometry, animate with GSAP |
Both phases share the same end behaviour — the difference is the rendering approach.
Phase 1 — CSS + GSAP
1.1 Construct the 3D Cube (HTML + CSS)
1.2 Apply Isometric Projection
1.3 Implement Rolling Animation with GSAP
1.4 Random Direction Selection
1.5 Window Boundary Enforcement
1.6 OPTIONAL: Cursor Following
Phase 2 — Three.js + GSAP
2.1 Create the Cube (Geometry + Material)
2.2 Understand the Isometric Camera (read-only, no code)
2.3 Implement Rolling Animation with GSAP
2.4 Window Boundary Enforcement
2.5 OPTIONAL: Cursor Following
Goal: Create a static 3D cube using CSS transforms with a 3-container hierarchy (position container, perspective container, cube, 6 face divs).
Acceptance criteria:
Key concepts: Use three nested containers — position container (GSAP animates x/y only), perspective container (static isometric rotation, never animated), cube (GSAP animates rolling rotation only). GSAP overwrites the full transform property — mixing position and rotation on one element breaks both. transform-style: preserve-3d (not inherited — set on every element whose children participate in 3D space), rotate + translateZ(halfSize) face positioning pattern, color-mix() for face shading from a single base colour.
Skill: phase1-css-cube — full Desandro 6-div technique, code examples, and preserve-3d deep dive
Reference: Intro to CSS 3D Transforms — Cube (David DeSandro)
Goal: Rotate the cube into true isometric orientation matching the starter grid.
Acceptance criteria:
Key concepts: Rotation angles derived from atan(1/sqrt(2)) = 35.264 degrees. Applied to a container element, not individual faces. No CSS perspective property — the Desandro tutorial uses perspective for a vanishing-point look, but isometric projection is orthographic (parallel lines stay parallel). The .scene container needs preserve-3d so the cube's 3D structure passes through.
Skill: phase1-css-cube — isometric projection section with full CSS
Gotcha: Watch out for — Rotation axis confusion, using perspective when isometric requires none
Goal: Animate the cube tipping over its bottom edge, translating one grid cell per roll.
Acceptance criteria:
Key concepts: Each roll is a basic 90-degree rotation. There are 4 possible roll directions in isometric space: top-right, bottom-right, bottom-left, top-left — each corresponds to a different rotation axis and translation vector. After each roll, reset — zero the rotation and update the element's position to the new grid cell. This reset-per-roll pattern is the core technique. Chain rolls with onComplete callbacks — don't pre-build a long timeline since directions are random. Use gsap.delayedCall(pauseDuration, rollNext) for pauses between rolls. Easing: "power1.out" (GSAP default) gives natural deceleration; "power2.inOut" gives a more physical roll-and-settle feel. Optional landing jump: gsap.to(positionContainer, { y: -jumpHeight, duration: rollDuration / 2, yoyo: true, repeat: 1, ease: "power2.out" }) — yoyo: true with repeat: 1 plays the tween forward then automatically reverses, producing a natural up-then-down bounce on each landing.
Skills: isometric-rolling-cube for rolling technique, gsap-expert for GSAP API
Gotchas: Watch out for — Rotation axis confusion, Cumulative rotation state, Grid snapping after rolls
Goal: Randomly choose from 4 isometric directions after each roll.
Acceptance criteria:
Key concepts: Define directions as data (rotation axis + translation offset per direction). Separate animation concerns from direction/movement logic.
Skill: isometric-rolling-cube — direction data tables for both CSS and Three.js
Goal: Prevent the cube from rolling off-screen.
Acceptance criteria:
Key concepts: Track logical grid position (not just pixel position). Filter available directions before each roll — in corners, multiple directions may be invalid. Don't just re-roll once; filter the list. The isometric grid means "out of bounds" checks need to consider diagonal screen-space movement.
Skill: isometric-rolling-cube — grid snapping and boundary logic
Goal: Bias direction selection toward the cursor position.
Acceptance criteria:
Key concepts: Calculate vector from cube to cursor, determine which of the 4 isometric directions best aligns with that vector, use weighted random selection (higher probability for cursor-aligned directions feels more natural than always picking the closest).
Skill: isometric-rolling-cube — direction selection logic
Goal: Create a 3D cube in the pre-initialised Three.js scene.
Acceptance criteria:
Key concepts: BoxGeometry + MeshBasicMaterial. Use an array of 6 MeshBasicMaterial instances for per-face colour (optional — can reuse the same material) — this matches the CSS approach and keeps things simple (no scene lighting needed). The boilerplate scene already includes a camera — just add the mesh.
Skills: threejs-fundamentals for scene setup and BoxGeometry, threejs-materials for MeshBasicMaterial
Goal: Understand the boilerplate's OrthographicCamera setup. No code required.
Acceptance criteria:
Key concepts: Unlike CSS (where the scene is rotated), Three.js positions the camera for isometric view. OrthographicCamera has no perspective distortion — parallel lines stay parallel, matching the CSS phase. World X/Z axes appear diagonal on screen. Understanding the camera's orientation helps reason about which Three.js axes correspond to which screen-space directions.
Skill: threejs-fundamentals for camera and coordinate system concepts
Goal: Animate rolling using Three.js objects and GSAP.
Acceptance criteria:
Key concepts: The Object3D pivot technique — create a temporary pivot at the rolling edge, pivot.attach(cube) to reparent while preserving world transform, rotate the pivot with GSAP, then scene.attach(cube) to detach. Target sub-objects directly: gsap.to(mesh.rotation, { x: ... }) and gsap.to(mesh.position, { x: ..., z: ... }) — not dot-path syntax on the mesh. Need a render loop (rAF, onUpdate, or gsap.ticker.add(() => renderer.render(scene, camera))). Note: when using the pivot technique, GSAP targets pivot.rotation, not mesh.rotation directly. The spec's gsap.to(mesh.rotation, ...) example illustrates GSAP's sub-object targeting syntax — in the pivot implementation, pivot is the actual animated target.
Skills: isometric-rolling-cube for pivot technique and reparenting cycle, gsap-expert for GSAP API
Gotchas: Watch out for — Pivot point in Three.js, GSAP + render loop, Euler rotation order, Grid snapping, attach() vs add()
Goal: Keep the cube within the camera's visible area.
Acceptance criteria:
Key concepts: The camera's left/right/top/bottom are camera-space, not world-space — for a rotated isometric camera, these don't directly correspond to ground-plane boundaries. Simplest approach: track logical grid position and define bounds in grid coordinates, avoiding camera-to-world projection math entirely. Alternative: unproject frustum corners onto the ground plane for precise world-space bounds.
Skill: isometric-rolling-cube — boundary logic and grid position tracking
Goal: Bias direction toward mouse position, same as Phase 1.
Acceptance criteria:
Key concepts: Use Three.js Raycaster or Vector3.unproject() to convert screen coordinates to world coordinates — with an orthographic camera, the math is simpler than with a perspective camera. Direction-biasing logic from Phase 1 carries over almost directly once cursor position is in world-space.
Skills: isometric-rolling-cube for direction selection, threejs-fundamentals for coordinate conversion
When starting Phase 2, here's what carries over and what changes:
| Concept | Phase 1 (CSS) | Phase 2 (Three.js) |
|---|---|---|
| Cube construction | HTML elements + CSS transforms | BoxGeometry + Material |
| Isometric view | 3 containers: position (x/y only), perspective (rotateX(-35.264deg) rotateY(45deg)), cube (roll only). The position and perspective containers collapse into one THREE.Group (with static isometric rotation); the cube element maps to the mesh. | OrthographicCamera + THREE.Group with static isometric rotation |
| Pivot point | Basic rotation + reset in onComplete | Temporary Object3D at edge + attach() reparenting |
| Roll animation | GSAP tweens CSS properties | GSAP tweens mesh.rotation / mesh.position sub-objects |
| Render trigger | Browser handles CSS rendering | Need explicit render loop (rAF or gsap.ticker) |
| Direction data | Same 4 directions | Same 4 directions (but mapped to Three.js XZ axes) |
| Boundary logic | Viewport pixel bounds | Grid coordinate bounds or frustum unprojection |
| Cursor following | Screen-space vector math | Needs screen-to-world coordinate conversion |
Reusable directly:
onComplete chaining pattern for random roll sequencingChanges fundamentally:
| Gotcha | Relevant Tasks | What Goes Wrong |
|---|---|---|
| Rotation axis confusion | 1.2, 1.3, 2.3 | Visual "right" on screen is diagonal in world-space. Each roll direction maps to a different axis. |
transform-origin misconfiguration | 1.3, 1.4 | CSS only. A 2D transform-origin (two values) places the pivot at Z=0, which is wrong for the Bottom-Right and Top-Left directions (those need a non-zero Z component). Must be a 3D value (three components) targeting the specific bottom edge in 3D space. Distinct from rotation axis confusion — axis can be correct while transform-origin is still wrong. |
| Cumulative rotation state | 1.3, 2.3 | After several rolls, local axes have shifted. Must track or reset orientation. |
| Euler rotation order | 2.3 | Three.js defaults to 'XYZ'. Multiple 90-degree rolls across axes → drift. Reset rotation to clean multiples of PI/2 after each roll. |
| Pivot point in Three.js | 2.3 | No transform-origin equivalent. Must use parent Object3D technique. Use attach() not add(). |
| GSAP + render loop | 2.3 | GSAP tweens values but doesn't trigger Three.js renders. Need rAF, onUpdate, or gsap.ticker. |
| Grid snapping after rolls | 1.3, 2.3 | Floating-point drift. Use gsap.set() to snap to exact grid coords in onComplete. |
preserve-3d not inherited | 1.1 | Must be set on every ancestor that needs to participate in 3D space. |
| Pre-built timeline for random dirs | 1.3, 2.3 | Can't pre-build — directions are random. Use onComplete chaining instead. |
| GSAP directional shortcuts on Three.js | 2.3 | _cw, _short and other GSAP directional shortcuts only work on DOM targets — use raw angle values for Three.js objects. |
/workshop-guideWhen invoked without arguments, give an overview of where the participant likely is (ask what they're working on if unclear) and suggest the next task.
When invoked with a task ID (e.g. /workshop-guide 1.3):
When a participant says "I'm stuck" without a task ID, ask what they're seeing (error? visual bug? doesn't move?) and use the gotcha table to diagnose.
references/resources.md — External reference links and descriptions for all workshop technologies (Desandro, GSAP, Three.js, isometric projection)phase1-css-cube — Desandro 6-div CSS cube technique, face positioning, preserve-3d, isometric projectionisometric-rolling-cube — Rolling animation, pivot technique, direction logic, grid snappinggsap-expert — GSAP timelines, easing, callbacks, ScrollTriggerthreejs-fundamentals — Scene setup, Object3D hierarchy, coordinate systems, BoxGeometrythreejs-materials — MeshBasicMaterial, per-face colours