feat(web): crossfade mascot GIF states with Motion#198
Merged
Conversation
Replace the canvas ImageDecoder renderer with native <img> GIFs crossfaded via motion/react AnimatePresence. Idle <-> build/plan state changes now fade over ~280ms instead of hard-cutting. All three sources render in one fixed 84x96 box (object-contain, bottom-anchored) so the mascot no longer resizes between states; a per-source SCALE knob is left for in-browser fine-tuning. Honors prefers-reduced-motion with an instant swap. Net -163 lines. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Integrates Motion (
motion/react) into the web app and uses it to smoothly crossfade the chat composer mascot between its GIF states (idle ↔ build-typing ↔ plan-typing) instead of the previous hard cut.Why
The mascot swapped GIFs instantly on state change, and the three source GIFs render at different footprints (idle 512², build 484×552 w/ keyboard, plan 256²) so the mascot appeared to resize between states.
Changes
motion@^12.42.2to@t3tools/web.ChatMascot.tsx: replace the ~170-line canvasImageDecoder/requestAnimationFramerenderer with native<img>GIFs crossfaded viaAnimatePresence(~280ms). The browser plays the GIFs natively, dropping the decoder, the ~100MB-of-bitmaps concern, and the sprite fallback.object-contain object-bottom) so the box/baseline never move between states.SCALEknob (defaults1/1/1) to fine-tune the ghost's apparent size in-browser.prefers-reduced-motion(instant swap)..gits-mascotCSS; onlyChatViewmounts it.Verification
bun run --filter @t3tools/web typecheck✅oxlint✅ ·oxfmt --check✅vite build✅ (Motion bundles clean)Follow-up
The
SCALEknob likely needs a nudge (probablybuild) after eyeballing the running UI — I couldn't see the browser. Alternatively the source GIFs could be re-exported to a common canvas.🤖 Generated with Claude Code