piruicy
The collection

Helmet reveal

after OFF+BRAND

A portrait that turns with the pointer; where it passes, a helmet surfaces through a trail of fluid.

Make it yours

Paste this brief into your coding agent. You make four images; it does the rest, step by step, and checks each one.

HANDOFF.md
# Helmet reveal: agent handoff

Paste this whole brief into your coding agent (Claude Code, Cursor, Codex, or another) in the project where you want the effect. You will make four images in an image generator such as ChatGPT. The agent does everything else and checks each step before moving on.

---

## For the agent: your job

You are guiding a person through putting **Helmet reveal** into their project with **their own character**. The effect is a portrait that turns slightly with the pointer. Where the pointer passes, a trail of fluid reveals a helmet or armour the character is wearing, and the face behind it darkens as if inside. A faint wireframe hologram of the helmet is always scanning over them. It is after OFF+BRAND's hero for landonorris.com.

**The person only makes images.** You do everything else: install, run the scripts, measure, fit, wire up, verify. Do not hand them technical steps.

**How to work:**
- Go **one step at a time**. At each step, tell them in a sentence or two what is happening and why, and what you need from them.
- **Wait for each image, then check it with numbers before moving on.** These checks are where the effect is won or lost; never skip them.
- When an image fails a check, say plainly what's wrong and give them the exact fix prompt. Never accept "close enough" on framing or alignment.
- Keep the person's words short. Write every image prompt for them in full, ready to paste.

**What's installed:** the component is `components/piruicy/helmet-reveal.tsx`. The scripts, and this brief, are in `scripts/helmet-reveal/`:
- `depth.mjs`: raw depth from an image, with Depth Anything V2 Small (Apache-2.0).
- `head.py`: the head's maps, plus framing and alignment checks.
- `helmet.py`: the helmet's maps, plus a `--measure` mode.

Pip (a teddy bear in a brass diving helmet) is the worked example throughout; his numbers are the defaults in the component.

---

## Step 0: Set up (you, quietly)

1. If the files aren't there yet, install with `npx shadcn@latest add @piruicy/helmet-reveal` (it needs the piruicy pass and token), or copy them in. Make sure `three` is a dependency and `@types/three` a dev dependency.
2. Check Python 3 with `numpy` and `Pillow` (`pip install numpy pillow`), and Node 18+.
3. Make a scratch folder outside the app (for example `.helmet-reveal-work/`, git-ignored) and run `npm i @huggingface/transformers` in it, for `depth.mjs`. The model downloads once (~100 MB) and runs on the CPU in about 10 seconds an image.
4. Pick where the maps will be served from, for example `public/helmet-reveal/head/` and `public/helmet-reveal/helmet/`.

## Step 1: Choose the character (ask the person)

Ask them two questions:
- **Who is the character?**
- **What do they wear on their head that means something to them?** For example a racing helmet, a diving helmet, an astronaut's, a knight's, or a beekeeper's veil.

**Tell them this:**
- If the site is public or commercial, the character must be **original**. Studio characters (Disney, Pixar and so on) are trademarks, and image generators often refuse or distort them.
- Offer to make an original "in the spirit of" one.
- Emotional details sell it: a worn patch, a hand-stitched repair, something homemade.

**Rules for the design:**
- The headgear must **enclose the head**.
- It needs **one clear opening or glass where the face shows**.
- It must **end at the neck**: no shoulders, no breastplate.
- If the character has ears, horns or a crest, the helmet needs housings for them, as Pip's has ear domes.

## Step 2: The portrait (image 1)

Write the prompt for them from this template, filling in the character. Tell them:
- **Start a new chat and choose the image tool** (in ChatGPT, "Create image" from the + menu).
- Paste it as one block.
- **Never let a prompt open with "a still from a film".** Generators read that as "reproduce an existing film" and refuse or ask for an upload.

> Create a new, original image: a chest-up character portrait in the look of a high-end 3D animated feature film, rendered with full path tracing, soft subsurface scattering and gentle bounce light. [The palette, in 4–5 colours.] No neon, no lens flares, no vignette, no film grain, no text, no watermark, no logos. The character is an original design, not any existing character.
>
> The character is [name], [who they are, in one line]. [Face, eyes, colours, materials, in 3–5 sentences, including one or two emotional details.] [Their expression: looking straight into the camera, quiet and warm, closed mouth.] [What they wear around the neck or shoulders.]
>
> Camera: a long portrait lens, about 85mm, perfectly level and front-on at eye height, with no perspective distortion. Framing: square, perfectly centred and symmetrical. The top of the head (or ears) is about 15% below the top edge, the eyes at about 40%, the chin at about 60%. The shoulders reach both bottom corners and are cropped by the bottom edge, at mid-chest.
>
> Lighting: an even, soft studio key light from the front-left, a gentle fill from the right and a faint warm rim. Shadows are soft and shallow; the face is clearly and evenly readable.
>
> Background: a perfectly flat, uniform pure white, with no shadow, gradient or floor. Every edge is crisp against the white, with no halo.

**Don't ask for a transparent background.** It often makes generation fail; you cut the image out yourself.

**Check it.** Load it with Pillow and measure the silhouette (pixels more than about 30 away from the corner colour). It passes when:
- the top of the subject is **8–18%** from the top (the hero crops about the top 10%);
- the widest part of the head, ears included, spans **55–70%** of the width;
- the face is centred within **±2%**, with the eyes level;
- the body runs off the bottom edge across most of the width.

**If it is too close** (the commonest failure), give them this edit, with their image attached:

> Zoom out. Keep this exact character: the same face, expression, colours, lighting and style. Change nothing about them; only reframe as if the camera stepped back. The top of the head sits about 17% below the top edge and the head is about 60% of the image width; the eyes at about 42%, the chin at about 60%. Show more of the body below, continuing past both bottom corners and off the bottom edge. Perfectly centred, head level. The same flat pure white background.

If it redraws the face, they reply: *"Keep the face exactly as in the image I attached; only make them smaller in the frame and extend the body downward."*

## Step 3: The shaded copy (image 2)

This is the face as it looks inside the helmet. It **must be an edit of image 1**, not a new image. Have them attach image 1 and use edit mode:

> Edit this exact image. Keep every pixel of the character's position, pose, framing, features and clothing exactly where it is. Do not move, resize, redraw or restyle anything. Change only the lighting, as if [the helmet] were now around the head, lit from inside it: the head and neck become darker and slightly cooler, about one to two stops down; the light falls off smoothly towards the outer edges of the head; a soft shadow band crosses where the helmet's rim would sit; a faint warm bounce from [the helmet's material] glows on the cheeks; the catchlights in the eyes become one small reflection, like [the opening]. Do not draw the helmet itself, only its light and shadow. The background stays the same flat white. The result must line up with the original exactly when the two are stacked.

You check it in step 5 (`head.py` prints both checks). It passes at **98% silhouette overlap or more**, and **0.55–0.8×** the portrait's brightness. If it moved, they reply: *"You moved the character. Keep the outline and features in exactly the same pixels as the image I attached; change only light and shadow."*

## Step 4: The helmet (image 3)

Have them attach image 1, so it fits:

> A product render of a bespoke [helmet] made for the character in the attached image, sized to fit their head exactly. Show the helmet alone and empty, with nobody inside it. [Its design, in 4–6 sentences: material and wear, the face opening (one large clear glass or opening where the face goes), housings for ears or horns if any, a few small details, and a short neck ring at the bottom.] There is no breastplate and no shoulders; it ends at the neck ring. Exactly front-on, level and centred, the whole helmet visible with a little space around it, orthographic-looking, with no perspective distortion. Soft, even, diffuse studio light; only soft, broad reflections. A plain flat light grey background (#E6E6E6), with no shadow and no floor. Square.

**Check it:**
- Run `python scripts/helmet-reveal/helmet.py helmet.png --measure`.
- The **glass guess** must sit on the face opening; compare it with the image, and correct it by eye if needed.
- Find the **neck ring's rows** from the colour printout: a hue change with a dark seam just above it.
- The background must be flat (the corners match), and the view must be front-on and symmetrical.
- A three-quarter view is not needed.

## Step 5: Make the maps (you)

```bash
node depth.mjs portrait.png portrait-depth.png          # in the scratch folder
node depth.mjs helmet.png helmet-depth.png
python scripts/helmet-reveal/head.py portrait.png shaded.png portrait-depth.png public/helmet-reveal/head
python scripts/helmet-reveal/helmet.py helmet.png helmet-depth.png public/helmet-reveal/helmet --glass CX,CY,R
```

**Read every line `head.py` prints.**
- Framing, alignment and brightness must all pass. If any fails, go back to that image; don't fit around a bad image.
- Then look at the results yourself: a clean cutout with no halo, and depth bright on the nose and dark at the edges.

## Step 6: Fit the helmet (you): the step people get wrong

**Coordinates:**
- The portrait is the head plane, 1×1, centred on 0, with y up.
- A portrait pixel (column c, row r) of a P-pixel image is at x = c/P − 0.5, y = 0.5 − r/P.
- The helmet is a plane `helmetSize` wide, centred at (0, `helmetY`).
- A helmet-render pixel (c, r) of a Q-pixel image is at x = helmetSize·(c/Q − 0.5), y = helmetY + helmetSize·(0.5 − r/Q).
- `headY` then moves both together.

**Measure first:**
- From the portrait: the head's half-width at eye level; the rows of the eyes, the mouth, the chin and the neck (the top of the collar or scarf); and the centres of any ears or horns.
- From the helmet render: the shell's half-width, the glass (centre and radius), the housings' centres, and the neck ring's rows.

**Then fit by these rules, in order:**
1. **Generous size.** The real helmet sits well clear of the head: OFF+BRAND's shell passes the head by about a third of its width on each side, with a wide neck. Choose `helmetSize` so the shell's half-width is **1.4–1.7×** the head's half-width. If there are housings, set it instead so the housings' spacing equals the ears' spacing, then check it is still generous.
2. **Face in the glass.** Choose `helmetY` so the glass centre sits about **30% of the way from the eyes down to the mouth**. The glass must reach from just above the eyes to the mouth. If it can't, the helmet is too small: go back to rule 1.
3. **Protrusions inside their housings.** No ear or horn should poke out. If one does, raise `helmetSize` a little, or move `helmetY`.
4. **The neck ring ends at the neck, never on the chest.**
   - Work out where its bottom lands. If it lands below the top of the collar or scarf, squash it: re-run `helmet.py` with `--collar TOP,END --squash S`, where S = (target bottom row − TOP) ÷ (END − TOP), in render rows.
   - Pip: the ring ran from rows 962 to 1120 and was squashed to **0.35**.
   - Keep its lips and rivets readable; if S is under 0.25, lift the helmet a little instead.
5. **Everything in frame.**
   - The camera shows ±`0.1317·cameraZ` vertically.
   - Choose `cameraZ` so that's at least the helmet's top (housings included) + `headY` + 0.01.
   - Keep `headY` ≤ 0.5 − 0.1317·cameraZ − 0.01, so the portrait's bottom edge never shows.
   - On narrow stages the component steps the camera back and drops the subject by itself; you fit the desktop stage.

**Pip's fit as a check of your arithmetic:** `{ cameraZ: 3.7, headY: -0.02, helmetY: 0.092, helmetSize: 0.95, helmetDepth: 0.3 }`. Keep `helmetDepth` at 0.3 unless the helmet looks flat (raise it) or warps at its edges (lower it).

## Step 7: Wire it up (you)

```tsx
import { HelmetReveal } from '@/components/piruicy/helmet-reveal';

<HelmetReveal
  head="/helmet-reveal/head"
  helmet="/helmet-reveal/helmet"
  fit={{ cameraZ: 3.7, headY: -0.02, helmetY: 0.092, helmetSize: 0.95, helmetDepth: 0.3 }}
  label="[Name]; move the pointer over them to reveal the helmet"
  className="hero" // give it a real size, e.g. .hero { width: 100%; height: 100svh; }
/>
```

- The component fills its box, so give it a real size: full width and 100svh for a hero, or for example 1040×480 for a panel.
- It pauses when it is off screen and releases its WebGL context when it unmounts.

## Step 8: Verify (you, in a browser): don't skip any

**With `showHelmet` on** (the whole helmet, for fitting):
- The whole helmet is on screen, housings included.
- No edge of either plane shows anywhere.
- Ears and horns are fully inside their housings.
- The face sits in the glass from the eyes to the mouth, and reads through it, dimmed.
- The neck ring sits at the neck, with the collar or scarf visible below.
- There is no grey or white fringe around the helmet or the subject.

**With it off** (the real thing):
- **Hologram:** a faint, fine dark wireframe of the helmet, with **one** scan band climbing it about once a second.
  - Several bands at once is wrong.
  - White lines are wrong: they would be holes punched in the head.
  - Thick or heavy lines are wrong.
- **Reveal:** moving the pointer leaves a trail; inside it, the helmet shows and the face darkens. The trail lingers about a second and has soft, swirling edges.
- **Idle:** two seconds after the pointer stops, a scripted path sweeps the reveal by itself.
- **Turn:** the portrait and helmet tilt slightly with the pointer, and the helmet bulges more than the head.
- **Reduced motion** (emulate it): no idle sweep and no drifting contours; the reveal still follows a real pointer.
- **Narrow screen** (375px): the helmet still fits, and the bottom edge is still hidden.

Show the person a screenshot with the helmet on and one mid-reveal. Fix anything that fails, by re-fitting or by sending them back to an image, before you call it done.

## Step 9: Hand back

Tell the person what's done and where it lives. Name the four images to keep, so they can remake the maps, and give the fit numbers. If they want changes later, it's usually a new image (the look) or a fit number (the placement), never the code.

---

*Credits:*
- *Effect after OFF+BRAND (landonorris.com).*
- *Cursor fluid after fluid-three by mnmxmx (MIT).*
- *Depth from Depth Anything V2 Small (Apache-2.0). Don't swap in the larger models: they are non-commercial.*
- *Component and brief © 2026 Dhaval Khandhadia, MIT with the Commons Clause.*

 

Details

  • Three passes and a composite: contour noise, a stable-fluids cursor at 10% of the stage, and the head and helmet each in their own target.
  • The head and the helmet are both photos on 128×128 planes lifted by their depth maps; the helmet lifts deeper (0.3 to 0.25), so it bulges as the head turns.
  • The reveal is the fluid itself: wherever it moves faster than a threshold the helmet shows, and the face swaps to a pre-shaded copy, as if inside.
  • Two seconds after the pointer stops, a scripted cursor sweeps a path so the piece shows itself; with reduced motion it stays still and the contours stop drifting.
  • The hologram is the helmet’s own depth surface as a wireframe, with one faint scan band climbing every 0.69 head-heights, as theirs.
  • Make your own subject with four images and the agent handoff below: you make the images, your agent does the rest.

© 2026 Dhaval Khandhadia. Use it in anything you build; don’t resell it. MIT with the Commons Clause.