AI CSS Animations

Editorial Data Story

A newspaper-grade animated chart: serif headline and dek, a two-line chart that draws year by year, a shaded 2020 band, a crossover callout and a source note, all with fictional but internally consistent data.

Written and rendered by AI CSS Animations Oct 12, 2026

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

About this style

Newspaper graphics desks developed a disciplined chart style: a headline that states the finding, a chart with direct labels and light gridlines, annotations that explain why the data moves, and a source line underneath. Animated, the chart builds in the order a reader should take it in. Publishers and brands borrow the style because it signals rigor and makes a single point unmistakable.

The prompt

<inputs>
You're making an animated newspaper-style data graphic with fictional data. I may supply any of these; if I don't give you something, use the default and don't ask.
- Kicker / headline: "TRANSPORT" / "Bikes now outnumber cars on Marrowford's bridges".
- Dek: "After a decade of steady growth, cycling overtook driving across the Ferrow River in 2025."
- Data, average weekday crossings in thousands, 2016–2025. Bikes: 8.2, 8.9, 9.6, 10.8, 13.9, 15.1, 16.4, 18.0, 19.7, 21.3. Cars: 31.0, 30.4, 29.8, 28.9, 22.1, 24.6, 23.8, 22.5, 21.6, 20.4.
- Source line: "Marrowford City Transport Office, automated bridge counters. 2025 figures are provisional."
- Length: 10 seconds.
</inputs>

<direction>
Page #F8F5EF; text #141414; secondary #66625C; gridlines #DAD4C9 at 1px; baseline #141414 at 1.5px. One series carries the story: bikes #0B7A75 at 4px; cars neutral #9C968C at 3px; 2020 band #ECE5D8.
Type: headline "Source Serif 4" 700, 64px, sentence case, ≤2 lines; dek "Source Serif 4" 400, 30px, #444; kicker "Libre Franklin" 700, 18px, tracking 0.12em, bike color; chart text "Libre Franklin" 500, 20px, tabular numerals; source 16px, #66625C.
Layout: 120px side margins. Kicker, headline and dek stack top-left; the chart title ("Average weekday crossings, Ferrow River bridges") sits above a 1,400×460 plot. Gridlines every 10,000; y labels sit just above them at the left, no axis line, the unit only on the top one ("40,000 crossings"). X labels every year ("2016", "'17"…). Label series at their line ends; no legend.
Annotations interpret rather than restate: short text, a 1px leader ending in a 4px dot on the data, never crossing a line. Every number must match the data (cars −24% in 2020; bikes +160% since 2016).
Easing: easeOutCubic and easeInOutSine only, 0.3–0.6s for text. Lines draw through an evenly advancing clip, a dot and live value riding each leading edge.
Authentic: high data-ink, direct labels, one color doing the talking, a sourced footnote. Cheap: glossy charts, rainbow series.
Banned: bouncy easing, gradients, shadows, dual axes, chart libraries, all-caps headlines, real publication names or logos, emoji.
</direction>

<structure>
- 0–1.6s: kicker, headline, then dek (from 0.9s), each rising 16px as it fades in.
- 1.6–2.4s: chart title, gridlines (left to right, 40ms apart) and axis labels.
- 2.4–5.4s: both lines draw 2016→2025, leading values counting up.
- 5.4–6.4s: the 2020 band shades in; annotation: "Lockdowns cut car crossings by nearly a quarter."
- 6.4–7.8s: a ring pulses once at the crossover; callout: "2025: 21,300 bikes vs. 20,400 cars a day."
- 7.8–8.6s: end labels "Bikes" and "Cars"; "+160% since 2016" by the bike line.
- 8.6–9.2s: the source line ("Source: …") fades in.
- 9.2–10s: hold.
Other lengths: stretch the line draw first; keep the 0.8s hold.
</structure>

<build>
One self-contained index.html (inline CSS and JS; Google Fonts via <link> are fine), a 1920×1080 (16:9) stage scaled to fit the window, built for 60fps. Use SVG with hand-computed scales; three.js isn't needed.
window.seek(t) (t in seconds) sets every visual from t alone, clamping past the end to the last frame. No CSS transitions or CSS animations, no setTimeout/setInterval, no state carried between frames. Any randomness comes from a seeded PRNG keyed to the frame, never Math.random.
With no ?render in the URL, call seek from requestAnimationFrame so it plays in a browser; with ?render, don't start that loop.
</build>

<check>
Before showing me, step through one frame per second (0–10s) and look at each. Fix overlaps, clipped or cramped text and anything off-style: every number matching the data, annotations clear of lines and each other, a clean two-line headline. Confirm the final frame holds.
</check>

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

Similar prompts