Nothing broke in the render — first pass clean. Two check warnings were fixed before rendering: the footer sat at 18px under the 19px type floor (bumped to 19px), and the closing line's hold is short by the checker's reading-speed rule (1.86s vs 2.0s) — accepted, it is a 4-word caption over an already-readable end state.
Banned list —Beat grid —Build method —Self-review —
<direction>
Build a 1280x720, 9-second roadmap animation in HyperFrames + GSAP: a single SVG path draws itself across the frame and four milestone nodes pop in exactly as the draw reaches them. House style: background #0a0a0a, white type, ONE orange accent #ff5c1a, Arial/Helvetica bold, NO webfonts, no gradients, no glows, no 3D. The plan is fictional sample content: the title carries "SAMPLE PLAN" and the footer reads "ILLUSTRATIVE SAMPLE ROADMAP" for the whole film.
Quarter labels, in order: Q1, Q2, Q3, Q4
Milestones, in order: Launch beta, Public API, Team plans, Mobile app
Banned: stock imagery, map pins, extra colours, lorem ipsum, any real company or product name presented as fact.
</direction>
<structure>
0.0-0.8s: orange kicker "ROADMAP ANIMATION — SAMPLE PLAN" and the title "2026 ROADMAP — SAMPLE PLAN" settle at top-left; footer fades in.
0.8-1.2s: a faint ghost copy of the S-curve path is already visible so the route reads before it draws.
1.2-6.2s: the white path draws itself left to right over exactly 5 seconds (stroke-dashoffset, linear ease). Four nodes pop in ON the path at the moment the draw reaches them (about 1.6s, 3.1s, 4.4s, 5.7s): quarter label above the node, milestone text below it. The fourth node is filled with the orange accent.
6.2-7.6s: closing beat — all four nodes stay lit and a thin orange progress underline completes left to right under the stage, captioned "FOUR MILESTONES · ONE PATH".
7.6-9.0s: hold the finished roadmap, everything readable.
</structure>
<build>
One paused GSAP timeline registered as window.__timelines["main"]. Draw the path with getTotalLength() + strokeDasharray/strokeDashoffset so any replacement path still times correctly. Place the four node coordinates ON the path curve itself (build the path through the nodes, not near them) and time each pop to the fraction of total length the draw has covered when it arrives. Nodes: 30px circles, dark fill, 5px light border; final node accent-filled with a small overshoot pop. Quarter labels 27px/900 above, milestones 23px/700 below, each 260px wide and centred on its node so the first and last never clip the frame edges.
</build>
<gotchas>
- A node placed "close to" the path reads as a mistake at full size — the path must pass through each node's centre.
- Linear draw ease is mandatory: any easing makes the draw arrive early or late at the middle nodes and the pops desync.
- The two middle labels sit on opposite sides of the curve's swing; check the milestone text under a peak node does not touch the descending curve.
- Keep the ghost path at #2c2c2c — brighter and it competes with the drawn path; darker and the route disappears.
</gotchas>
Before you finish: step through the timeline at five points and fix anything that overlaps, clips the frame, or drifts off the beat grid.
The whole trick is synchronisation: the path is built through the four node coordinates and drawn with a linear dashoffset tween, so each pop lands the exact frame the line arrives. Swapping in a different curve without moving the nodes is the one change that silently breaks it.