Five database tables slide onto a graphite canvas, foreign-key lines draw between their columns with crow's-foot ends, then a SQL join types in, lights its path table by table and returns seats sold per film.
Written and rendered by AI CSS Animations Oct 19, 2026
Entity-relationship diagrams go back to Peter Chen's 1976 paper on the entity-relationship model, and crow's-foot notation, with its three-pronged mark for the many side of a link, became a common way to draw them in database tools. Schema diagrams are now a standard feature of database clients, ORMs and developer platforms, because a picture of tables and keys explains a data model faster than a list of migrations. In promos they show that a product understands the developer's data: tables appear with real column names and types, relationships connect the right columns, and a query traced across the diagram makes a join easy to follow. Clear versions keep each table compact and the lines tidy.
Make a 10-second database schema demo: five tables slide onto a graphite canvas, foreign-key lines draw between their columns with crow's-foot ends, then a SQL query types in, lights its join path table by table, runs and returns its result while the unused tables dim. <inputs> If I don't give you something, use the default and don't ask. - Product: Shoal Studio (fictional), a database client. Wordmark "shoal" beside an original mark: three short horizontal strokes of decreasing length stacked like sandbars. - Database "cinema_prod", schema "public"; summary "5 tables · 4 relations". - Tables (columns as name · type, with keys) and row counts: customers (12,408 rows): id · uuid · PK; email · text; name · text; created_at · timestamptz bookings (86,215 rows): id · uuid · PK; customer_id · uuid · FK → customers.id; screening_id · uuid · FK → screenings.id; seats · int2; total_cents · int4; booked_at · timestamptz screenings (1,930 rows): id · uuid · PK; film_id · uuid · FK → films.id; hall_id · uuid · FK → halls.id; starts_at · timestamptz films (48 rows): id · uuid · PK; title · text; runtime_min · int2; rating · text halls (9 rows): id · uuid · PK; name · text; capacity · int2 - Query, exactly: SELECT f.title, SUM(b.seats) AS seats_sold FROM bookings b JOIN screenings s ON s.id = b.screening_id JOIN films f ON f.id = s.film_id WHERE s.starts_at >= now() - interval '7 days' GROUP BY f.title ORDER BY seats_sold DESC; - Result (title · seats_sold): Lantern Orchard · 1,284; The Ninth Tide · 1,102; Salt and Cinder · 896; A Small Weather · 641; total 3,923; status "4 rows · 38 ms". All titles are fictional. - Tooltip on the bookings → screenings line: "many bookings → one screening · on screening_id". - Length: 10 seconds. </inputs> <direction> - Look: a precise schema canvas, chalk on graphite: compact table cards, tidy right-angled lines with crow's-foot ends, and one signal yellow for the query's path. The pacing is deliberate: each table and each relation gets its own moment, then the query traces the join like a finger following a route on a map. - Palette: canvas #1B1D21 with 1.5px dots #2A2D33 every 24px; table cards #23262B with 1px #34383F borders, radius 12; headers #2A2D33 with a 4px band along their top edge in the table's color: customers #8FB8DE, bookings #E1A95F, screenings #7FB3A8, films #D58A9E, halls #A9B47A; text #E6E8EB; types and muted text #8B929B; relation lines chalk #9BA1A9 at 1.5px; the query path #F5D547 at 2.5px, and its row bands #3A3524; SQL panel #202227 with a 1px #33363C border; SQL colors: keywords #F5D547, functions #7FB3A8, strings #D58A9E, identifiers #E6E8EB, aliases #8FB8DE, operators #8B929B. Dimmed tables drop to 40% opacity. - Type: Sora 600 for the wordmark (22px) and table names (18px); Sora 400 16px for the top bar; Inconsolata 500 18px for column names, Inconsolata 400 16px for types and 14px for row counts; Inconsolata 500 19px on a 30px line for SQL; Inconsolata 500 18px for result rows. - Layout: - Top bar y 40–100: the wordmark at x 120; "cinema_prod / public" at x 300 (the first part muted); "5 tables · 4 relations" right-aligned at x 1800 in muted. - Every table is 300px wide with a 44px header (the name at x + 16, the row count right-aligned at x + 284) and 34px rows (a 14px key glyph for PK or link glyph for FK at x + 16, the column name at x + 40, the type right-aligned at x + 284). Positions: customers x 120–420, y 250–430; bookings x 560–860, y 330–578; screenings x 1000–1300, y 250–430; films x 1440–1740, y 250–430; halls x 1000–1300, y 560–706. - Relation lines, orthogonal with 8px rounded corners, from the FK row's edge to the PK row's edge: bookings.customer_id (560, 425) → (490, 425) → (490, 311) → (420, 311); bookings.screening_id (860, 459) → (930, 459) → (930, 311) → (1000, 311); screenings.film_id (1300, 345) → (1370, 345) → (1370, 311) → (1440, 311); screenings.hall_id (1300, 379) → (1340, 379) → (1340, 621) → (1300, 621). The many end gets a crow's foot (three 12px prongs) and the one end a short bar across the line. - SQL panel x 120–1180, y 740–1020: header y 740–784 with "query.sql" (Inconsolata 15px muted) and a "Run ⌘↵" button at the right; code from x 152, first baseline y 820, 7 lines (the last at y 1000). - Result panel x 1240–1800, y 740–1020: header with "Result" and the status at the right; before running it reads "Run a query to see results" in muted. Four 44px rows from y 790: the title at x 1268 and seats_sold right-aligned at x 1772, each row backed by a bar in #F5D547 at 18% whose width is in proportion to its value (1,284 = 500px); the total "3,923" under a 1px rule at baseline y 1004. - The cursor: a white arrow with a 1.5px #1B1D21 outline, 26px tall. - Table entry: each table slides up 20px and fades in over 0.45 s (easeOutCubic), its rows cascading 30 ms apart. - Relations: each draws from its FK end to its PK end over 0.3 s (stroke-dashoffset, easeInOutCubic); its end marks pop in (scale 0 → 1, 0.15 s) and the two connected rows take a #2E3238 band for 0.4 s. - Query tracing, as each clause appears: "FROM bookings b" → bookings' border turns yellow and its seats and screening_id rows take the yellow band; "JOIN screenings…" → the query path draws over the bookings → screenings line (0.3 s) and screenings lights its id, film_id and starts_at rows; "JOIN films…" → the path draws over the screenings → films line and films lights its id and title rows, while customers and halls dim to 40% (0.3 s); "WHERE s.starts_at…" → a small tag "≥ now() − 7 days" (Inconsolata 13px, yellow on #3A3524, radius 6) appears beside the starts_at row. - Run: one bright 40px #FFF4B8 segment travels the whole path, bookings → screenings → films, over 0.5 s; then the result rows appear one by one, their numbers counting up over 0.3 s. - Premium: a schema a real team would ship, compact cards, lines that never cross or overlap a table, notation that means something, a query whose lit path matches its joins exactly. Cheap: neon tables floating in 3D, spaghetti lines, "Table 1" and random columns, flashing highlights. - Banned: real company names, logos and UI copied from existing products, including real database products; real film titles; glow; 3D tilt; lines crossing tables; lorem ipsum; anything flashing more than 3 times per second. </direction> <structure> - 0.0–0.4s: the canvas dots and the top bar fade in. - 0.3–2.0s: tables enter at 0.3 (customers), 0.6 (bookings), 0.9 (screenings), 1.2 (films) and 1.5 s (halls). - 1.7–2.9s: relations draw at 1.7 (customers ↔ bookings), 2.0 (bookings ↔ screenings), 2.3 (screenings ↔ films) and 2.6 s (screenings ↔ halls). - 2.8–3.1s: the SQL and result panels rise 16px and fade in. - 3.1–5.25s: the query types at 120 characters per second with a 0.05 s pause at each line break, behind a 2px yellow caret: line 1 at 3.10 s, "FROM bookings b" at 3.50 s (bookings lights), "JOIN screenings…" at 3.68 s (its line and screenings light), "JOIN films…" at 4.08 s (its line and films light; customers and halls dim), WHERE at 4.40 s (the tag), GROUP BY at 4.83 s and ORDER BY at 5.01 s, done at about 5.22 s. - 5.3–5.95s: the Run button presses at 5.3 s (a yellow ring, 0 → 24px over 0.3 s); the bright segment travels the path from 5.3 to 5.8 s; the status reads "4 rows · 38 ms"; the result rows appear 60 ms apart from 5.6 s and the total at 5.9 s. - 6.0s: the poster: all five tables and four relations, the yellow path through bookings, screenings and films, customers and halls dimmed, the full query with the WHERE tag, and the complete result. - 6.3–6.8s: the cursor glides in from the lower right to the bookings → screenings line; at 6.8 s the line thickens to 3.5px and a tooltip (#2A2D33, 1px #34383F border, radius 8, Inconsolata 15px) shows the tooltip text. - 7.5–8.0s: the cursor moves to the "Lantern Orchard" result row; at 8.0 s the row takes the yellow band and films' title row brightens to match; the tooltip fades and the line returns to 2.5px. - 8.0–10.0s: hold; nothing moves. For other lengths, stretch the gaps between tables and relations; keep the typing speed 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: icons, glyphs and the cursor are drawn 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 if any is used. The typed query is sliced from its full text by t, and every highlight is keyed to the time its clause appears. 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: one inline <svg> with viewBox "0 0 1920 1080" holding the canvas, tables, lines, panels and text; relation and path lines are <path> elements measured once at load with getTotalLength; seek sets transforms, stroke-dashoffset, opacity, fills and text directly. </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 that each relation runs from the right FK row to the right PK row with the crow's foot on the many side, no line crosses a table or another line, the lit path covers exactly bookings → screenings → films, customers and halls are dimmed from 4.08 s on, the query matches the given text exactly, the result values sum to the total 3,923 and their bars are in proportion, the tooltip names the right relation, nothing flashes, the 6.0 s frame matches the poster description, and the final frame shows the lit path, the full query and the result with the Lantern Orchard row highlighted. 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.
A slow, ribbon-like gradient mesh flows in a skewed band behind sharp headline type, while white payment, API and chart…
A device built in three.js rises out of total darkness while a softbox reflection glides along its titanium edge, then…
A dark-mode work tool drawn in hairlines and quiet radial glows: issue rows populate, ⌘K opens a command menu, a comman…
Rounded tiles spring into a bento grid on warm paper, each acting out one feature in a small animation, then the tiles…
More like this on the motion design inspiration board: SaaS video inspirationDiagram animation inspirationCode animation inspiration