AI CSS Animations

Cymatics

Pale sand showers onto a black steel plate, then dances into four Chladni figures, each more intricate than the last, as the tone glides from 306 to 1,170 Hz, with a live frequency readout, mode label and scope beside the plate.

Written and rendered by AI CSS Animations Oct 18, 2026

Type
prompt
Made with
Claude Opus 5.5 in Claude Code
Built in
Canvas
Topics
Particles · Music
What Claude made from the prompt below in Claude Code: one HTML file rendered frame by frame to video. Silent preview.

About this style

In 1787 the German physicist and musician Ernst Chladni published his sound figures: he sprinkled sand on a metal plate, bowed its edge, and the sand gathered along the nodal lines where the plate stays still. Each resonant frequency gives its own figure, and higher tones give more intricate ones. The word cymatics came much later, from Hans Jenny in the 1960s. Violin and guitar makers still use the method to tune tops and backs, and science museums and music visuals return to it often.

The prompt

Make a 10-second cymatics film: pale sand on a black steel plate jumps into Chladni figures, each more intricate than the last, as the tone rises.

<inputs>
If I don't give you something, use the default and don't ask.
- Title: "Sand finds the still lines."
- Line: "A 30 cm steel plate, driven from 306 to 1,170 Hz"
- Figures, as mode (n, m) and frequency: (3, 5) at 306 Hz · (3, 7) at 522 Hz · (5, 7) at 666 Hz · (7, 9) at 1,170 Hz.
- Length: 10 seconds.
</inputs>

<direction>
- Palette: room #141312 with a warm pool of light #2A2622 under the plate; plate #101011, satin black with a fine brushed grain (1–2% contrast) and a soft reflection gradient brightening to #232326 toward the upper left; a 6px bevel with a #4B4B50 highlight on the top and left edges; a brushed-steel hex nut #9EA2A8, 26px, at the center; sand #EBDFC6 with highlights #FFF7E6 and 1px shadows (black at 45%); type #EDE6D8, secondary #9A9286; scope trace amber #F2B544.
- Plate: a square 840 × 840px centered at (1240, 540), seen straight from above, its edges dead straight.
- The figures: the plate's displacement in mode (n, m) is z(u, v) = cos(nπu)·cos(mπv) + cos(mπu)·cos(nπv), with u and v from 0 to 1 across the plate. Sand collects on the nodal lines, z = 0, where the plate stays still: (3, 5) is a ring crossed by a plus with arcs in the corners, and (3, 7), (5, 7) and (7, 9) grow into nested rings, diamonds and loops, each busier than the last. Frequency = 9 Hz × (n² + m²).
- Sand, simulated once at load at a fixed 1/60 s step from a seeded PRNG: 28,000 grains. Each step, a grain at (u, v) shakes with local amplitude a = A(t)·|z|/2: it hops by a random step of length 0.0045·a·(0.5 + rand) and drifts down the slope of z² by −0.00012·A·∇(z²); where a < 0.04 it rests. Grains that hop past the plate's edge slide off and fade over 0.2 s; keep losses under 15%. Store each grain's position every frame (Int16 per axis) and its hop height (Uint8). Sand only ever moves by hopping and drifting, never by snapping to a line.
- Drive: A(t) is 1 at each resonance and falls to 0.12 during each glide between figures (off resonance the plate barely moves, so the old figure holds, then shakes apart when the next resonance arrives). During a glide the frequency moves on easeInOutSine and z blends from the old mode to the new.
- Drawing grains: a 2.4px rounded square per grain, brightness varied ±10%, with a 1px shadow down-right; a hopping grain lifts up to 3px off its shadow and the shadow shrinks. Dense lines build up bright and thick; stray grains read as dust.
- Opening shower: grains fall onto the plate over 0.9 s, each appearing at 1.6× size and shrinking onto its shadow as it lands, spread evenly.
- Readout (left column, x 120–640): the frequency in Chivo Mono 400, 96px, tabular, baseline at (120, 560) ("306 Hz", gliding through the values between figures); the mode in Chivo Mono 400, 22px, at (120, 610) ("mode (3, 5)"); a 440 × 90px scope at x 120–560, y 660–750, whose amber sine shows 2 to 7 cycles across in step with the pitch and whose height follows A(t); a log scale from 100 to 2,000 Hz at y 830 with a marker at the current pitch.
- Type: title in Chivo 300, 60px, #EDE6D8, two lines from (120, 150); line in Chivo 400, 22px, #9A9286, beneath.
- Camera locked, with a 2% push across the piece. Text rises 12px as it fades in over 0.6 s on cubic-bezier(0.22, 1, 0.36, 1).
- Premium: grains that dance and drift until crisp bright lines remain, each figure clearly richer than the last, an instrument-grade readout. Cheap: lines stroked on like a drawing, particles teleporting into place, neon colors.
Banned: drawn lines standing in for sand, grains snapping into a pattern, colored sand, glow, motion blur, a strobing scope, anything flashing more than 3 times per second.
</direction>

<structure>
- 0.0–1.0s: the black plate under its pool of light; sand showers down and spreads evenly; the title, line and readout rise in.
- 1.0–2.6s: the tone starts at 306 Hz: the sand shivers, then runs into the ring-and-cross figure.
- 2.6–3.1s: glide to 522 Hz; the figure holds, then breaks up.
- 3.1–4.4s: the second figure forms.
- 4.4–4.9s: glide to 666 Hz.
- 4.9–6.2s: the third figure forms, crisp by 5.9 s.
- 6.2–6.7s: glide to 1,170 Hz.
- 6.7–8.4s: the fourth and most intricate figure forms; the drive eases to zero by 8.4 s and the sand comes to rest.
- 8.4–10.0s: hold: the finished figure, every grain still, the readout at 1,170 Hz.
For other lengths, stretch each figure's beat equally; keep at least 1 s of hold.
</structure>

<build>
- One self-contained index.html (inline CSS and JS), a 1920×1080 (16:9) stage scaled to fit the window, designed for 60fps. No image, video, texture or model files: every shape and texture is generated in code.
- window.seek(t), t in seconds, computes every visual from t alone: no CSS transitions or CSS animations, no setTimeout/setInterval, no state carried between frames, randomness only from a seeded PRNG. Anything cumulative is solved analytically from t or precomputed once at load at a fixed step. Past the end, seek clamps to the last frame.
- Without ?render in the URL, drive seek from requestAnimationFrame so it plays in a browser (loop after a 1s end hold); with ?render, don't start that loop.
- Google Fonts via <link> are fine; await document.fonts.load() for each face before defining window.seek.
- Engine: Canvas 2D, repainting every frame from scratch, the backing store sized from CSS size × devicePixelRatio. Canvas won't fetch fonts itself, so the awaited loads matter.
- The sand is simulated once at load into typed arrays (keep load under 10 s) and seek(t) only reads them; frequency, A(t), the mode blend and the scope are closed-form functions of t. The plate, its grain and the room light are drawn once at load into an offscreen canvas.
</build>

<check>
Before showing me, step through one frame per second (t = 0…10): screenshot each and look at it. Fix overlaps, clipped or cramped text, and anything off-style or Banned. Check each figure matches the nodal lines of its formula (overlay z = 0 on one frame per figure), the sand moves only while the plate is driven, the readout names the figure on screen, grains only appear in the shower and only leave over the edges, the 6 s frame shows a crisp third figure, and the final figure is crisp and still. Confirm seek(10) and seek(11) match, so the final frame holds.
</check>

Ours, and free to use under CC0: copy it, change it, ship it. No credit needed.

Similar prompts

More like this on the motion design inspiration board: Music video inspirationParticle animation inspiration