[ Parable build guide ]
How Peony was built
A fireworks studio, rendered with fireworks. The hero is a canvas particle engine — shells that rise, decelerate, and burst into gravity-bound sparks with fading trails. Click the sky and you fire one yourself.
The idea
You cannot photograph a fireworks show and keep what makes it a show: the rise, the pause, the break, the fall. So the hero doesn't try — it runs one. The sky is a live canvas, always firing, and the single interaction ("click the sky") turns a viewer into the pyrotechnician for one shell. The whole brand promise — "we build the three seconds you'll remember" — is on screen, moving, before you've read a word.
The stack
One HTML file, one stylesheet, one script. No framework, no libraries — the engine is a few hundred lines of vanilla canvas 2D. Type is Cinzel (a ceremonial Roman capital, for the occasion), Inter for body, and Space Mono for the measured, technical voice a pyro crew actually uses (shell counts, durations, fall-out radii).
Signature technique — the fireworks engine
Two particle types. A shell launches from the bottom with just enough upward velocity to reach its target height, decelerating under gravity; at apex it's swapped for a burst of 60–100 sparks fired radially, each with its own drag and decay. The trails come from a trick: instead of clearing the canvas each frame, we paint a translucent dark rectangle over it, so old positions fade rather than vanish.
// the trail: fade last frame instead of clearing
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle = 'rgba(10,10,20,0.22)';
ctx.fillRect(0, 0, W, H);
ctx.globalCompositeOperation = 'lighter'; // additive — sparks glow where they overlap
Additive blending ('lighter') makes overlapping sparks bloom toward white, the way
real ones do. "Willow" bursts get lighter gravity and slower decay so they hang and droop.
Details that matter
- The sky is never empty. Three shells are seeded on load and the engine auto-fires every 1.4–2.6s, so the hero always has something breaking — including in a screenshot.
- Cost is bounded. Device-pixel-ratio caps at 1.5, at most three shells are
airborne at once, and the loop pauses via
IntersectionObserverthe moment the hero scrolls away. - Reduced motion draws stills. Under
prefers-reduced-motionthe engine paints two static bursts and never starts the loop; the shell-catalogue diagrams appear fully drawn instead of animating in. - The catalogue is generated. Each shell type (peony, chrysanthemum, willow,
crossette) has its burst diagram built in SVG from its own geometry, then drawn on with
stroke-dashoffsetas it scrolls into view. - Content survives without JS. Reveals and the hero rise are gated behind a
synchronous
.jsclass; the sky is decorative andaria-hidden.
The booking form is a demo — it validates and confirms in-place but sends nothing. And, obviously: this is a design concept, not a licensed operator. Don't recreate any of it in the real world.
Ship it on GitHub Pages
gh repo create bswxyz/peony --public --source . --push
gh api --method POST /repos/bswxyz/peony/pages -f 'source[branch]=main' -f 'source[path]=/'
Relative paths and a .nojekyll file — it serves from the project subpath with no
config at all.