AI CSS Animations

Ink in Water

Against a white light box, carmine, ultramarine and saffron drops plunge into still water, roll into sinking mushroom caps, split into smaller rings and trail fine tendrils, and mix like stained glass into violet, green and vermilion before the motion ramps to a freeze.

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
What Claude made from the prompt below in Claude Code: one HTML file rendered frame by frame to video. Silent preview.

About this style

Ink is slightly denser than water, so a drop sinks as a vortex ring: seen from the side it rolls into a mushroom cap, sheds smaller rings and leaves thin tendrils behind, the same kind of instability that shapes smoke plumes. Before computer graphics, effects artists injected paint into tanks of water to film clouds and nebulae. Shot at high frame rates against a lit white background, the look is now a staple of title sequences, music videos and brand films.

The prompt

Make a 10-second slow-motion film of ink dropped into still water: three inks sink as curling plumes, roll into mushroom caps and tendrils, and mix like filters against a white light box.

<inputs>
If I don't give you something, use the default and don't ask.
- Title: "Let it settle."
- Line: "Carmine, ultramarine and saffron, slowed 8×"
- Brand: Tamsel & Stone Inks (fictional).
- Length: 10 seconds.
</inputs>

<direction>
- Palette: light box #FBFAF6 at the center falling to #E9ECEC at the corners; tank glass edges #CFD8D8 with a 1px white highlight; meniscus #DDE5E5; type #2B2724, secondary #6B655E. Inks are defined by what they absorb, not by an RGB fill.
- Color (Beer–Lambert): each pixel = light-box color × exp(−(k₁ρ₁ + k₂ρ₂ + k₃ρ₃)) per channel, with ρ each ink's density and absorption k per (R, G, B): carmine (0.15, 2.3, 1.5), ultramarine (2.4, 1.5, 0.25), saffron (0.05, 0.55, 2.6). Carmine over ultramarine reads violet, ultramarine over saffron green, carmine over saffron vermilion, and thin veils stay clean and pale. Never blend ink colors by alpha.
- The tank: water fills the frame below a meniscus at y 92, with the tank's glass edges as faint vertical lines at x 90 and 1830.
- The fluid, simulated once at load: a 2D incompressible fluid on a 408 × 232 grid spanning the water (x 90–1830, y 92–1080, about 4.3px per cell), dt 1/30 s, with three ink density fields; semi-Lagrangian advection with MacCormack correction (clamped) for velocity and inks, 40 Jacobi iterations for pressure, vorticity confinement ε 0.3, and a downward force g′·(ρ₁ + ρ₂ + ρ₃) with g′ about 12 cells/s², tuned so the plumes reach about y 880 by 8 s. Ink is heavier than water: that negative buoyancy rolls each drop into a sinking vortex ring, seen side-on as a mushroom cap, and stretches tendrils behind it. Seed ±3% density noise into each drop so caps split unevenly into secondary rings. Store the three ink fields every second step (15 per second) as Uint8 and interpolate between stored frames.
- Drops: a slim glass pipette (a tapered rounded shape #E6ECEC with white edge highlights and a column of ink inside) lowers in from the top edge and releases each drop: carmine at x 720 at 0.6 s, ultramarine at x 1120 at 1.6 s, saffron at x 920 at 2.8 s. Each drop shows for 60px as a 14px falling sphere with a highlight, breaks the meniscus with one small ring on the surface line, and enters the sim as a 9-cell disc moving down at 14 cells/s with a thin trailing stream for 0.12 s.
- Rendering: one WebGL pass samples the stored fields with bicubic (Catmull–Rom) filtering so plumes stay smooth at 4.3× scale; adds filament detail by modulating density with a fine noise at 15% whose coordinates drift down with the plumes so it never boils; deepens dense cores slightly (density^1.15); then applies Beer–Lambert. A 2% push-in runs across the piece.
- Time: sim time follows t until 7.6 s, then ramps down like a camera speed ramp to a freeze at 8.6 s, so the end holds still.
- Layout: plumes descend through x 600–1700 and spread low; keep the title area (x 120–620, y 140–380) and the readout area (x 1400–1800, y 120–200) free of ink.
- Type: title in Gloock 400, 84px, #2B2724, at (120, 230); line in Work Sans 400, 24px, #6B655E, beneath; brand in Work Sans 500, 20px, tracked 0.2em, uppercase, at (120, 990); a readout right-aligned at (1800, 170) in Work Sans 400, 22px, tabular: "Real time 00:00.00", showing sim time ÷ 8, which stops at the freeze.
- Easing: the pipette moves on easeInOutSine; text rises 12px as it fades in over 0.6 s on cubic-bezier(0.22, 1, 0.36, 1).
- Premium: ink with weight, rolling into caps, splitting into smaller rings and trailing fine tendrils, overlaps that glow like stained glass. Cheap: blurred colored blobs swelling in place, colors that turn brown, a smoke filter.
Banned: brown or grey mud from RGB mixing, glow, hard outlines, dots that read as particles, bubbles, rainbow colors, anything flashing more than 3 times per second.
</direction>

<structure>
- 0.0–0.6s: the still, bright tank; the pipette lowers into frame; the title and line rise in.
- 0.6–1.6s: carmine drops in; a red vortex ring forms and sinks, rolling into a cap.
- 1.6–2.8s: the pipette moves right; ultramarine drops in and its cap sinks beside the carmine plume, violet where they brush.
- 2.8–4.0s: saffron drops between them; the pipette lifts out of frame.
- 4.0–7.6s: the three plumes sink, split into smaller rings and stretch tendrils; violet, green and vermilion bloom where they cross; the brand and readout rise in at 4.2 s.
- 7.6–8.6s: the motion ramps down to a freeze as the clouds spread in the lower half.
- 8.6–10.0s: hold, completely still.
For other lengths, stretch the sinking; keep the ramp and 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. The fluid is simulated on the CPU once at load (keep load under 15 s); one WebGL canvas (preserveDrawingBuffer: true, drawn once per seek), its backing store sized from CSS size × devicePixelRatio, uploads the two stored frames around the current sim time and composites them; the pipette is drawn on a 2D canvas above; type is HTML.
- Sim time is a closed-form remap of t; seek(t) reads stored frames and never steps the simulation.
</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 every plume sinks and rolls into a cap before spreading, overlaps read violet, green and vermilion (never brown), edges keep fine tendrils at 1280×720 instead of blurring, no ink enters the title area, the 6 s frame shows all three plumes mid-fall, and the hold is 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: Particle animation inspiration