ghosty-reveal
Builds a CSS mask reveal in which images bleed into view through a feathered, cloudy edge by sliding an oversized gradient mask across each box with mask-position, then fade back out and swap, on stag
SKILL.mdvault/ghosty-reveal/skill/SKILL.md
GitHubGhosty reveal
Photographs that bleed in and out of view through a soft, cloudy mask, like forming out of fog. See it live at https://design.how/vault/ghosty-reveal.
What to build
Seven photographs sit in a staggered scatter on a white card, each in a 4:3 box between 13 and 24 percent of the card's width. None of them fades. Each one bleeds in through a soft, cloudy front that rises from the bottom over 1.5 seconds, holds for 1.5 seconds, and dissolves the same way, then comes back as a different photograph. The spots start 0.7 seconds apart, so the field breathes instead of pulsing.
The whole trick is one mask. A feathered ramp, transparent for its first quarter and opaque past two thirds, is laid over the box at 500 percent of its height, so only a sliver of the soft edge ever crosses it. Transitioning mask-position from one end to the other slides that edge across the image, and a gliding ease-out makes it arrive fast and settle slowly. The ramp is pushed around by fractal noise, so the front is cloudy rather than a clean gradient. It is drawn as an inline SVG, so there is nothing to load.
Left and right reveals use a horizontal copy of the ramp sized 500 percent wide, so the feather always runs along the wipe and no element is ever rotated. The image is swapped only after the out-transition has fully finished plus 220ms, so a new photograph never flashes in over the old one.
Workflow
- Copy
assets/template/into the project, for example tocomponents/ghosty-reveal/. It is React and TypeScript:params.ts,mask.ts,ghost-reveal.tsx,loop.ts,card.tsx, plusruntime/, the small helpers they import. - Read the FIXED steps below before touching the code. They are what makes the effect; keep them.
- Change only the VARIABLE values in the params file, or pass them as props where the card takes them.
- Mount the card in a box with a fixed aspect ratio of 16 / 9, so the page never shifts when it paints.
- Copy
assets/media/to the app's static folder aspublic/ghosty-reveal/. Every file there is a placeholder, a neutral gradient or a short abstract clip;assets/media/README.mdlists what each one stands for. Replace them with the project's own images or clips at the same paths. The template loads nothing from any other host. - Read
references/perf.mdbefore changing the engine or mounting several visuals on one page. - Run
node scripts/check.mjs components/ghosty-revealon the copied folder, fix every line it prints, and rerun until it prints ok.
FIXED
- Wrap each image in a box whose mask-image is the feathered ramp, mask-size 100% by MASK_SCALE percent and mask-repeat none.
- Hidden is mask-position 0% 0%, shown is 0% 100% for an upward reveal; transition mask-position over DURATION with EASING.
- For left and right, use the horizontal ramp at MASK_SCALE percent by 100% and slide along x.
- Loop each box: a 140ms beat, reveal, hold, hide, wait DURATION plus 220ms, swap the image, repeat; stagger the boxes.
- Draw the ramp as an SVG linear gradient displaced by feTurbulence, so the edge is cloudy.
- Under reduced motion, show every image and switch the transition to a short opacity fade.
VARIABLE
| Name | Default | What it changes |
|---|---|---|
IMAGES |
17 photographs | The image set, as bucket paths. |
SLOTS |
7 staggered spots | Where the images sit, in percent of the card. |
MASK_SCALE |
500 | The mask's size as a percent of the box. Bigger is a softer, slower bleed. |
DURATION |
1500 | One reveal in or out, in ms. |
HOLD |
1500 | How long an image stays fully shown, in ms. |
EASING |
cubic-bezier(0.16, 1, 0.3, 1) | The reveal curve. |
DIRECTION |
up | Which way the soft edge travels: up, down, left or right. |