# Shadow Assembly

> Drag a low spotlight freely across the floor to reshape the word or monogram cast by a suspended matte black sculpture. Release to hold its position, then choose Align light to restore the artwork.

Canonical: https://gsapvault.com/effects/shadow-assembly
Live demo: https://gsapvault.com/demos/shadow-assembly/index.html

| Property | Value |
|----------|-------|
| Type | effect |
| Tier | paid |
| Price | £5 |
| Difficulty | advanced |
| Plugins | Core GSAP only |
| Techniques | webgl-shader, pointer-effects, text-animation, 3d-transforms, click-toggle |
| Uses Lenis | No |

## Lighthouse, as measured

Google Lighthouse on the demo, 15 September 2026. A measurement of the demo as shipped, not a promise for your page.

| Category | Score |
|----------|-------|
| Accessibility | 100 |
| Best practices | 100 |

No performance score is published: it depends on the deployment (server compression, caching, CDN, connection and device) rather than on the code, so measure it where it will live.

## Overview

Beveled matte black pieces float against a pale, seamless plaster backdrop. A compact black spotlight casts their shadows into a readable word. The light begins slightly off alignment. Drag the visible lamp freely across the floor to separate the lettering, release to hold the result, and choose Align light to return.

Physically based materials, room reflections and native spotlight shadows give the sculpture and its surroundings a shared lighting response. The installation includes FORM and a geometric monogram as outlined SVG artwork, dedicated desktop and portrait perspectives, four-way keyboard movement, and Aligned / Separated stills for reduced motion or unavailable WebGL.

## Features

- Pale seamless plaster setting, a visible low spotlight and dedicated perspective camera framing
- Stationary beveled matte black solids, lit with physically based materials and a room environment
- Native three.js spotlight shadows cast by the sculpture's geometry
- A slightly misaligned opening, with precise assembly when you choose Align light
- Free two-dimensional lamp dragging across the floor; release, leave or cancel to hold the chosen position, with no hover movement
- Explicit Align light action with an interruptible return, plus four-way lamp arrow-key controls and Home to align
- FORM and a geometric monogram supplied as editable outlined SVG artwork
- Dedicated portrait framing and direct touch dragging
- Aligned and Separated still views for reduced motion or unavailable WebGL; the opening artwork remains visible without JavaScript
- Independent instances, container resize handling, context recovery and a documented revert API

## Use Cases

- A studio landing page built around a short word or geometric identity
- An exhibition page that introduces an artwork through light and shadow
- A product launch with a distinctive outlined mark
- An editorial feature about form, material or spatial perception

## Vibe-Code Ready Setup

This effect includes `START-HERE-AI.md`, a product-specific copy-paste setup prompt for Cursor, Claude Code, ChatGPT, GitHub Copilot, Windsurf, and other coding assistants. It tells the assistant to inspect the existing stack, integrate the supplied files, preserve the design, scope selectors, retain accessibility and responsive behaviour, add framework-appropriate GSAP cleanup, and report what it tested.

[How AI-assisted setup works](https://gsapvault.com/vibe-coding)

## How It Works

Drag the visible lamp in any direction across the floor while the matte black sculpture stays still. Native spotlight shadows change across the plaster wall as the lamp moves. Release to hold the view, then choose Align light to restore the supplied artwork. GSAP manages the motion preference lifecycle and explicit alignment transition; three.js renders the physically based materials, environment lighting and shadow map. The supplied SVG examples, still images and native controls provide the starting point for your own installation.

## Documentation

How this effect works and how it goes into a page. The reference you use once you own the files (worked examples, events, the programmatic API, the class list) ships with the download.

### Quick Start

Copy the supplied `assets` directory into your project and serve the page over HTTP. Artwork is fetched as SVG, so opening the HTML through `file://` is insufficient.

**1. Add to your HTML `<head>`:**

_Code snippet omitted: it ships with the download._

**2. Add the installation inside `<body>`:**

_Code snippet omitted: it ships with the download._

**3. Add before the closing `</body>` tag:**

_Code snippet omitted: it ships with the download._

The script automatically mounts every `[data-shadow-assembly]` root. Keep the still image visible during loading: `.is-live` replaces it only after a complete canvas frame has drawn. The script probes WebGL before constructing the renderer. Keep the complete supplied assets folder, including `vendor/` (the pinned three.js, SVG loader and GSAP dependencies), `environment.js` and `room-pmrem.bin.gz`. They load the prefiltered HDR room lighting without generating it on each visit. `RoomEnvironment.js` remains an automatic fallback when the baked atlas cannot load; `BufferGeometryUtils.js` and `bevel-pieces.js` construct the sculpture. Update all import paths together if you relocate the assets.

### Using It With Your Own Design

**Required markup.** Keep one `[data-shadow-assembly]` root with its own `[data-shadow-stage]`, `[data-shadow-source]` picture and `[data-shadow-lamp]` button. The lamp button belongs inside the stage: the script positions it over the physical spotlight and uses it as the drag handle. Keep the artwork select and Align button outside the stage. Preserve the `data-shadow-*` hooks, native input types, accessible labels and visible focus styles. Each root owns its own controls and canvas.

**Demo styling.** You can replace the font, control bar, borders and spacing. The global reset, `html` / `body` styles, full-screen height and `body { overflow: hidden }` are demo choices; remove or scope these when embedding the effect in a scrolling page. The sculpture, plaster and fixture materials, environment lighting and spotlight are scene settings in `assets/script.js`, rather than CSS variables. Desktop and portrait use separately composed perspective camera positions.

**Layout dependencies.** Give the root a real height and retain its positioned, clipped stage. Keep the canvas and fallback picture filling the stage, the `.is-live` visibility rules and the rules that switch between live controls and still-view buttons. Avoid hiding the fallback with `.has-js`. Preserve the absolutely positioned `.shadow-assembly__lamp` handle, its generous hit area, focus styling and `touch-action: none`. The surrounding stage can use `touch-action: pan-y` so the page can scroll away from the handle. Drag cancellation holds the current light position. Keep controls clear of the artwork in both orientations.

#### Your artwork and still images

Start with `assets/form.svg` or `assets/monogram.svg`. Short, broad outlines and compact geometric marks suit the fixed installation. Convert text to outlines in your drawing application, flatten transforms, and check both the aligned and fully separated states at desktop and phone sizes. Long headlines, fine detail and very thin outlines do not suit this composition.

For each custom artwork, supply six matching still images:

- `your-mark-initial-desktop.webp`
- `your-mark-initial-mobile.webp`
- `your-mark-aligned-desktop.webp`
- `your-mark-aligned-mobile.webp`
- `your-mark-separated-desktop.webp`
- `your-mark-separated-mobile.webp`

The supplied desktop stills also include `*-initial-desktop-4k.webp`, `*-aligned-desktop-4k.webp` and `*-separated-desktop-4k.webp` at 3840 × 2160. Add `data-still-hd` to an artwork option only when you supply these three extra files. The script then selects the 1200px or 3840px image through `srcset`; custom artwork without this attribute still needs only the six files above. The supplied portrait stills are 780 × 1688.

If you change the opening light coordinates, recapture the initial stills at every supplied resolution so they match the live opening.

Set its option's `data-still="assets/your-mark"`. The script appends the view and viewport suffixes above; it does not generate these images from a custom SVG. Update the initial `<picture>` sources, image `alt`, option `data-label` / `data-description`, root `data-artwork` and `[data-shadow-label]` text to match the initial artwork. The initial select option must match it too. A custom artwork's option must exist before calling `setArtwork()` so fallback views can select the correct stills.

The stills are essential content: they provide the slightly displaced initial view, the no-JavaScript installation, and both Aligned / Separated reduced-motion or unavailable-WebGL views. Describe the artwork in text independently of the canvas. Changing the SVG alone leaves the supplied fallback images showing the old artwork.

#### Supported SVG subset

- A valid SVG with a finite `viewBox` and positive width and height.
- Opaque, closed `<path>` outlines, optionally inside plain `<g>` groups; `<title>` and `<desc>` are allowed.
- SVG path commands `M`, `L`, `H`, `V`, `C`, `S`, `Q`, `T`, `A`, `Z` and their relative forms. Close every subpath explicitly with `Z`.
- Compound outlines and holes using `fill-rule="evenodd"` or the default nonzero winding rule.
- At most 24 paths and 24 filled shapes, a 100,000-character source limit, up to 18,000 triangulated vertices per shape, and at most 80 resulting fragments. Simplify outlines before approaching these limits.

Flatten transforms and expand strokes into filled outlines before export. Text, images, strokes, CSS/style attributes, clipping, masks, filters, transparency, external references and nested SVG documents are outside the contract. SVG fill colours do not change the sculpture material: the sculpture uses one material. Avoid self-intersecting and degenerate contours. This is an outlined-artwork effect, not a live font or arbitrary-image renderer. Check narrow details after beveling and under the native spotlight shadows; fine outlines may need simplifying.

Rejected or unavailable artwork returns the instance to its supplied still view. Supply stills that remain meaningful if the live artwork cannot load.

### Options

Set these on the root before mounting. To change an initialization option later, call `revert()`, change the attribute, then mount again.

| Attribute | Values | Default | Description |
|---|---|---|---|
| `data-enabled` | `true`, `false` | `true` | `false` uses the supplied still views. |
| `data-artwork` | SVG URL | `assets/form.svg` | Initial outlined artwork; resolved relative to the document URL. |
| `data-strength` | `0`–`1` | `1` | Available floor area around the home position. `0` holds the light at alignment; the default gives the full reachable floor. |
| `data-initial-x` | World X coordinate | `-4.622` | Opening lamp position, constrained to the reachable floor. |
| `data-initial-z` | World Z coordinate | `11.625` | Opening distance from the wall. Set X `-5` and Z `12` for an aligned opening. |
| `data-return-duration` | `0.1`–`3` | `0.9` | Align light transition duration in seconds. |

The lamp opens at a fixed, slightly displaced position with no entrance animation. There is no autoplay or hover movement. Changing artwork returns to this opening pose; Align light always returns to exact alignment. Drag the visible lamp handle in any direction across the floor. The two floor coordinates move independently; the lamp stays grounded and points toward the artwork. Movement is limited at the wall and visible stage edges so the handle stays reachable. Far from alignment, shadows can stretch, overlap or leave the original word area. Release, pointerleave and cancellation hold the chosen position; Align light explicitly returns it.

### Accessibility

- The lamp handle, Align light button and artwork select are keyboard-operable.
- Focus the lamp handle and use all four arrow keys to move it in the corresponding screen direction; Home starts Align light. New input interrupts an active alignment transition.
- Mouse or touch dragging on the lamp handle moves the light freely across the floor in two dimensions. Release, pointerleave, cancellation and lost pointer capture hold the chosen position. Only an explicit Align light action starts the return; stage hover has no effect.
- Reduced motion opens on the slightly displaced initial still, then offers instant **Aligned** / **Separated** still changes with no moving lamp. Preference changes during use tear down or rebuild the live scene.
- Without WebGL or with a failed WebGL dependency, JavaScript enables the still-view buttons. Without JavaScript, the initial picture and its alternative text remain visible.
- The canvas is hidden from assistive technology. Keep the meaningful artwork text, image alternatives, labelled controls and visible focus styles in the DOM.
- Each custom artwork needs its own matching stills and descriptions. An SVG alone cannot satisfy the fallback contract.

### Dependencies

| Dependency | Version | Role |
|---|---|---|
| GSAP core | 3.12+; demo pinned to 3.15.0 | Media/lifecycle context and played Align light transitions. |
| three.js | Demo pinned to 0.180.0 | Physically based materials, environment lighting and native spotlight shadow mapping. |
| three.js SVGLoader | Same 0.180.0 release | Reads the supported outlined SVG artwork. |
| `environment.js` and `room-pmrem.bin.gz` | Supplied prefiltered HDR atlas | Loads the unchanged room lighting; no runtime filtering on the normal path. |
| RoomEnvironment and BufferGeometryUtils | Supplied 0.180.0 addon / utility extract | Fallback room generation and crease-aware geometry normals. |
| `bevel-pieces.js` | Supplied effect helper | Builds the beveled sculpture geometry; retain this local file. |

No GSAP plugins or Lenis are required. The rendering code owns its animation-frame loop and continuous input interpolation; GSAP owns the return target and lifecycle.

**Already using ES modules?** Import your existing three.js module and matching addons, plus the supplied bevel helper. Set the five globals shown in Quick Start before loading `assets/script.js`: `THREE`, `ShadowSVGLoader`, `ShadowEnvironment`, `ShadowCreasedNormals` and `ShadowBevelPiece`. Use one consistent three.js version. The import-map setup avoids the discontinued UMD build. Resolve `prepareEnvironment()` from the supplied module before assigning `ShadowEnvironment`. The supplied atlas preserves the original half-float lighting samples; no external HDR image is required.

### Browser Support

The live installation requires WebGL 2 and JavaScript with ES module/import-map support. WebGL is probed before renderer creation. Browsers that cannot run the live scene retain the supplied still artwork. Serve assets over HTTP(S) and allow their requests in your site's content security policy.

### Performance

The live scene uses a native PCF spotlight shadow map: 4096px on fine pointers, 2048px on touch devices, bounded by the GPU texture limit. Its 64px-per-face room environment is prefiltered once and stored as a losslessly compressed HDR atlas (about 90 KB); visitors skip the environment render passes. Hosts may serve that file with or without a gzip content-encoding header. Older browsers without native decompression, or a missing atlas, use the supplied room generator instead. The black surfaces share standard physically based shading; the luminous lens and its local glow do not require a glass transmission pass or full-screen bloom. The demo uses system fonts and local copies of its pinned libraries, so startup does not wait for an external CDN. It preloads both three.js modules, the SVG loader, initial artwork and lighting data, and includes only the geometry-normal utility it needs. It reuses the probed WebGL context and requests asynchronous material compilation while the matching still remains visible. Shadow rendering adds work beyond the main camera view, and beveled artwork costs more geometry than a flat outline. Drawing pauses after the light settles and when the installation is off-screen. Resize follows the component container. Context loss exposes the still view; restoration preserves the held light position.

Desktop rendering allows up to 8,294,400 pixels (3840 × 2160), with a maximum pixel ratio of 2 and 1.5× supersampling on standard-density screens. Touch rendering retains a 1.9-million-pixel budget and maximum ratio of 1.5. The limits live together in `resize()`; lower the desktop budget there if a large installation exceeds your target hardware. Responsive 4K stills keep the loading and reduced-motion views sharp without sending the largest image to every viewport.

For expensive custom artwork, simplify the outlines and reduce isolated detail first. A smaller displayed stage also reduces drawing work; lower shadow-map resolution trades edge definition for reduced shadow-rendering cost. `data-strength` changes travel, not geometry cost. Physical-device phone performance has not been measured. Measure the final page on the devices you need to support; no fixed frame rate is promised.

To rebuild the supplied lighting after editing `RoomEnvironment.js` in this repository, start the local preview, run `bun run build:demos`, then `bun scripts/bake-shadow-environment.ts http://127.0.0.1:4387`, and rebuild the demos. This is an authoring step, never visitor-side work.

The local library copies retain their upstream license notices. After changing the repository CDN pins, run `bun scripts/vendor-shadow-assembly.ts` to refresh these copies and their source manifest, then rebuild and verify the demo.

## What You Get

- `index.html`: working demo page
- `assets/script.js`: commented, readable source
- `assets/style.css`: effect styles
- `README.md`: full documentation with examples and framework integration notes
- `START-HERE-AI.md`: product-specific copy-paste prompt for AI-assisted setup
- `LICENSE.txt`: standard license terms
- Lifetime updates: re-download anytime from your library

## Get the Code

This is a premium effect. The standard license costs £5 one-time and covers unlimited personal and commercial projects with no attribution required for our code. Bundled third-party assets retain their own licences and attribution requirements. The only restrictions: no redistribution of the code itself and no competing effect libraries.

- [Buy Shadow Assembly](https://gsapvault.com/effects/shadow-assembly)
- [The Vault (£99 one-time, best value): every collection in the Vault library, plus future items added to those collections](https://gsapvault.com/effects)

## Judge the Code Quality First

These related effects are free with complete source published, written to the same production standard (cleanup functions, reduced-motion support, framework-agnostic):

- [Typewriter Text](https://gsapvault.com/effects/typewriter-text.md): A typewriter that types a line character by character, holds it, accelerates through deletion, and cycles to the next phrase, with optional cursor, status and progress hooks.
- [Scroll Text Highlight](https://gsapvault.com/effects/scroll-text-highlight.md): A scrubbed orange-to-lime reading front lifts each active word before completed copy settles to white and unread copy remains ghosted.
- [Hover Underline](https://gsapvault.com/effects/hover-underline.md): Four material link underlines (an exit-through line, marker sweep, hand-drawn wave, and an endlessly travelling wave) with coordinated type and active-index responses.

---

From [GSAP Vault](https://gsapvault.com): production-ready GSAP animation effects. Full catalog for agents: https://gsapvault.com/llms-full.txt
