Skip to content

feat(app): animate the Go upsell hero in the free-limit dialog - #57694

Merged
kitlangton merged 3 commits into
devfrom
feat/desktop-go-upsell-hero
Oct 21, 2026
Merged

kitlangton merged 3 commits into
devfrom
feat/desktop-go-upsell-hero

Conversation

@kitlangton

@kitlangton kitlangton commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Issue for this PR

Follow-up to #57466

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Adds an animated GO hero above the title of the free-limit (Go upsell) dialog. It plays the Lightspeed intro (energy gathers, the field warps, GO arrives), then hands off to the Ripple loop instead of continuing the warp. The warp's brake becomes Ripple's first ring: that ring's speed is the loop's drift plus a share of the warp's speed, so it slows with the brake and keeps going as a loop ring with no cut. The loop rings keep its cadence.

  • One canvas, no new dependencies. Each frame is a pure function of time, so the reduced-motion version is just one settled loop frame and no loop runs.
  • The loop runs only while the dialog is mounted and the window is visible. Hidden time doesn't advance the art.
  • It uses devicePixelRatio. Colours come from the v2 theme tokens (text, surfaces, and orange for the Peach accent), so it follows light, dark and custom themes and updates when the theme changes.
  • The letters' counters stay dim (15% of the strokes' light) so GO reads as one solid shape.
  • DialogUsageExceeded takes an optional hero. Only the free-limit dialog passes one; the account rate-limit dialog doesn't change.

How did you verify your code works?

  • go-upsell-hero-art.test.ts: under reduced motion it draws one still frame and requests no animation frames. On unmount it cancels the pending frame, removes its listeners and stops drawing.
  • bun run typecheck in packages/app.
  • Rendered the real dialog in the dev app headless (Playwright, fixture session, a stepped clock for exact 60 fps frames).

Screenshots / recordings

Before

before

After (dark, 6.5 s at 60 fps: Lightspeed intro, the brake ring, then the Ripple loop)

go-hero-after.mp4
Light Reduced motion (still)
after light after reduced motion

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@kitlangton

Copy link
Copy Markdown
Contributor Author

Follow-up to review (ff605e4):

Pixel-ratio changes under reduced motion. The hero now watches matchMedia("(resolution: Ndppx)"), re-arms it for each new ratio, and redraws the still. New test: DPR 1→2 resizes the backing store to 960×352, and moving back to 1 is caught too.

Frame cost. The grid is now written as pixels (whole-pixel squares, the same as the old fillRects). In the steady loop it is blended over the bloom in that buffer, so each frame is one putImageData plus GO's 140 cells. The bloom gradient is rendered once and faded with alpha. The intro still draws its layers on the canvas: bloom, streaks, grid image, then GO. CSS colour strings are cached.

Measured headless on the Pro: same page, DPR 2, 60 fps virtual clock, main-thread time per frame including a forced raster. Each number is the median of 3 runs per renderer, alternated old/new. Worker load averages were 12–40, so absolute numbers are noisy; the ratios held across runs.

before after
Ripple loop, avg 10.2–13.5 ms 1.5–1.7 ms
Ripple loop, p95 18–35 ms 3.2–3.9 ms
Intro, p95 18–26 ms 7–11 ms

Intro p95 is still about 8–11 ms on this loaded worker. The warp streaks take most of it, and their cost grows with stroke length, so it can't go lower without changing the look.

Look. I rendered both renderers on the same clock at 12 timestamps across the intro and the loop, composited over the dialog surface. Mean difference is 0.1/255, max 3.7/255, and under 0.04% of channels differ by more than 2/255.

@kitlangton
kitlangton marked this pull request as ready for review October 21, 2026 16:38
@kitlangton
kitlangton merged commit 2fde77c into dev Oct 21, 2026
19 checks passed
@kitlangton
kitlangton deleted the feat/desktop-go-upsell-hero branch October 21, 2026 16:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant