Try it first
Open the Pelican Ride Demo and the pelican is already rolling. Drag the speed slider to take it from a gentle cruise to a sprint, hit the bell for a ring, or toggle the skeleton to see how the pedals drive its legs. Switch to dark mode in the top-right corner and the daytime road becomes a night ride. The whole scene is a single inline SVG with no image files.
Why a pelican
“Generate an SVG of a pelican riding a bicycle” is the prompt developer Simon Willison uses to test language models, and by now almost every new model release gets its own pelican drawn and compared. It works because the task is awkward in exactly the right way: pelicans are not built for cycling, so a model can’t lean on a familiar picture. It has to reason about what a bike frame looks like, where the bird sits, and how its feet reach the pedals. The results often have a collapsing frame, a bird floating in mid-air, or legs nowhere near the pedals.
Static pelicans have been drawn plenty of times, so we raised the bar one notch: make it move, and make it move correctly.
How I framed the brief
What animation adds on top of a still drawing is constraints between parts, so I spelled those out one by one:
Create the pelican-bicycle static web demo: a 2D SVG animation of a pelican riding a bicycle, pure inline SVG with no images. Requirements: a believable frame (rear wheel, bottom bracket, saddle, head tube and fork forming a real diamond frame); a clearly recognizable pelican — long bill with a big throat pouch underneath, white body, wing resting on the handlebar; both feet must stay on the pedals at all times, with two-segment legs (thigh and shin) whose knee position is solved every frame from the crank angle, knees bending toward the front of the bike; wheel rotation, chain movement and cadence synced through the gear ratio; the body bobs slightly with each pedal stroke and the pouch swings half a beat behind; clouds, far hills, near hills and road scroll with layered parallax. Provide pause, a speed slider, a bell, and a skeleton toggle, and show cadence, speed and distance. Use vanilla HTML, CSS and JavaScript, keep the off-white paper and ink visual style, support dark mode and mobile, and start paused when the system asks for reduced motion. When done, check every angle of a full crank revolution: is the leg long enough, and does the knee ever bend backward?
That last sentence is the important one. Legs are where this kind of animation breaks, and I didn’t want to rely on staring at it to catch a single bad frame.
What you’ll see in action
The canvas is 640×400: a red road bike, a white pelican, an orange bill and an absurdly large pouch, with a wingtip resting on the handlebar. The feet go round with the pedals; the far leg is drawn lighter and sits behind the frame. Each stroke dips the body slightly, and the pouch follows half a beat later, as if it were holding a mouthful of water. Spokes, the dashed chain and the road markings all move together, the far hills drift slower than the near ones, and the clouds slowest of all.
The three numbers below are cadence, speed and total distance. The default is 66 rpm, which, with the 2:1 chainring-to-cog ratio drawn in the scene and a 700c wheel, works out to about 16.6 km/h. Push the slider to 3× and you’re close to 50 km/h. This pelican is in good shape.
Define the experience before picking the stack
I set one rule up front: every moving thing on screen is derived from a single value, the crank angle theta. Each frame advances theta according to cadence, and everything else is computed from it:
- pedal position = bottom bracket + crank length × (cos θ, sin θ), plus π for the far pedal;
- wheel angle = θ × gear ratio 2;
- chain dash offset = θ × chainring radius;
- road offset = wheel angle × wheel radius, with far hills, near hills and clouds following at 0.2, 0.45 and 0.08;
- body bob = −1.8 × cos 2θ, one dip per stroke.
Pausing, changing speed, or switching tabs and coming back can never leave the feet turning while the wheels stand still, because nothing has its own clock. The stack is vanilla HTML, CSS and JavaScript: one requestAnimationFrame loop writing SVG attributes directly, no animation library. Every color is a CSS variable, so dark mode is just a different set of values; the sun turns into the moon using the same circle.
How the legs follow the pedals
The lazy way to keep a leg on the pedal is to draw a straight line from hip to pedal. But then the leg stretches like a spring, and it looks fake instantly. A real leg keeps its length and absorbs the distance change by bending at the knee. That’s the classic two-bone inverse kinematics problem: given hip and foot positions and the thigh and shin lengths, find the knee. The law of cosines solves it:
function solveKnee(hip, foot) {
const dx = foot.x - hip.x, dy = foot.y - hip.y;
const d = Math.min(Math.hypot(dx, dy), THIGH + SHIN - 0.001);
const cosA = (THIGH * THIGH + d * d - SHIN * SHIN) / (2 * THIGH * d);
const a = Math.acos(Math.max(-1, Math.min(1, cosA)));
const base = Math.atan2(dy, dx);
return { x: hip.x + THIGH * Math.cos(base - a), y: hip.y + THIGH * Math.sin(base - a) };
}
Two details decide whether it looks right:
- Which way the knee bends. The triangle has two solutions, knee forward or knee back. Always taking the
base - abranch keeps the knee pointing toward the front of the bike, so the bird never snaps into a flamingo pose for a frame. - Whether the leg is long enough. The
Math.minline is a safety net; the real guarantee is the geometry itself. Saddle height, bottom bracket position, crank length and leg length have to fit together. If the hip-to-foot distance at the bottom of the stroke exceeded thigh plus shin, the leg would lock straight and look like it was tiptoeing into thin air.
Toggle “显示骨架” (show skeleton) and you’ll see the hip, knee and ankle points and the crank circle in blue, recomputed every frame.
Verification and limits
That “check every angle” sentence turned into a Node script that loads the geometry functions straight out of app.js, turns the crank from 0° to 359°, solves both legs with the body bob applied, and records the results. Hip-to-foot distance ranged from 78.8 to 123.0 against a maximum leg reach of 136, so the straighten-out safety net never fired. Across all 720 poses the knee bent backward zero times. The shin length error stayed around 10⁻¹³, which means the legs really don’t stretch anywhere in the cycle.
In the browser I took screenshots at desktop width and at 375 px mobile width, in both light and dark mode. The first version did have a real problem: the neck was a thick white stroke with no outline, so where it met the body there was no edge, and it looked like a separate piece taped on. The fix was to draw a thicker dark stroke underneath as an outline, then the white on top, and move the neck one layer below the body so the body covers the seam. After re-screenshotting, the neck and body finally read as one bird.
Current limits, stated plainly:
- The camera is fixed side-on; the pelican doesn’t turn, climb or dismount.
- The road is endlessly flat, so there’s no difference between pushing hard and coasting.
- The wing is static and only moves with the body bob; it doesn’t actually pull on the handlebar.
- With the system’s reduced-motion setting on, the animation starts paused until you press “出发” (go).
- The demo UI is in Chinese.
What you can reuse
The takeaway here isn’t the pelican, it’s a way of writing animation briefs:
- Find the single driver. Write every moving part as a function of it instead of giving each part its own timer. Sync bugs disappear at the design stage.
- Put constraints in the prompt, not just looks. Sentences like “feet always on the pedals”, “knees bend forward” and “synced through the gear ratio” shape the result far more than “make it cute”.
- Make verification measurable. “Looks fine” isn’t enough; “zero backward knees across 720 poses” is. Write the check into the brief and Clidex treats it as part of the deliverable.
If you want to plan a small demo like this from scratch, start with writing a clear brief before your first demo.