Skip to content

Ollie blinks, breathes and waves; pages and results land with motion - #242

Merged
Programcode merged 2 commits into
mainfrom
design/debateowl-animations
Oct 27, 2026
Merged

Programcode merged 2 commits into
mainfrom
design/debateowl-animations

Conversation

@Programcode

Copy link
Copy Markdown
Owner

Ollie, the owl, now blinks, breathes and gestures everywhere it already appears. Pages and results also get Duolingo-style motion. The PR only changes motion. Copy, labels, features and layout stay as they are. Every animation is a CSS transform or opacity keyframe, so nothing reflows. Reduced motion stops all of it through the existing global rule in index.css.

The owl (src/components/brand/Owl.tsx)

  • Blink. Two lids drawn over the eyes close for about a tenth of a second every 4.6 to 7 seconds. Each owl gets its own random period and phase, fixed for its lifetime, so owls on the same screen don't blink together and a streaming reply never restarts the clock. I measured the eye positions from each pose's SVG. The owl gets no lids below 28 px or in the sleepy pose, whose eyes are already shut.
  • Breathe. A slow scale anchored at the feet (3.8 s). It is off on the landing hero, where the stylesheet promises nothing moves on its own, and in the loaders, where the owl already bobs.
  • Gestures on arrival and on every pose change. point, coach and arguing wave, celebrating does a double jump with squash and stretch, and surprised and idea hop. The gesture sits inside the keyed pose element, so it replays whenever the pose changes. That is what makes the intro owl cheer on its second beat.
  • Hover. On devices with a fine pointer, hovering the owl makes it hop once.
  • OwlAvatar blink is opt-in because a transcript renders one avatar per message. It is on for the avatars that stand for the owl right now: the Ask header and its "Working…" line, the live-draft status, and the Sparring opponent's latest and live speech.
  • OwlMark (the logo) blinks wherever it is 28 px or larger.

Where the owl now moves:

  • Logo: app header, landing navbar, public page shell, free-tool layout, footer, login.
  • Loading: OwlLoader (the sign-in check, Crossfire, Library, a shared link, Sparring's "Opponent is thinking", section overlays) and ToolRunStatus for Generate, Attack, Brainstorm and Factoids.
  • Empty states: EmptyState on Generate, Attack, Brainstorm, Factoids, Library, History and the current debate page, plus the Ask empty chat and Home's "No current debate" card.
  • Ask coach: the header avatar and the working avatar blink.
  • Sparring opponent: the setup owl perched on the card waves and blinks, the round avatars blink, and the scorecard owl cheers on a win or hops with an idea otherwise.
  • Intro: lands with a hop, cheers on "Hi, I'm Ollie", hops on the idea beat, waves as the coach, blinks throughout.
  • Elsewhere: the 404 owl, the route error owl, the Timer and Generate heading owls, the "saved" toast owl, and the landing hero owl (waves once and blinks, no breathing).

Pages and sections

  • Page enter (App.tsx RouteContent). Signed-in /app pages rise 8 px and fade in over 280 ms when they open. The animation ends on transform: none, so fixed and sticky children behave as before once it has played. Public pages are left alone because they have their own reveals and fixed bars. The route skeleton keeps its delayed fade.
  • Stagger (.enter-stagger). On Home, the heading, current-debate card, Prepare and Practice groups and Utilities come in 60 ms apart, and so do the tool rows inside each group.

Progress, success and numbers

  • Count-ups (useCountUp, <CountUp>). Sparring scorecard totals, Attack's case strength and Factoids' verified percentage tick up from 0 over 900 ms. Screen readers get the final number at once and the ticking digits are aria-hidden. Colour thresholds read the real score, and tabular-nums keeps the row from shifting.
  • Progress reveal (<Progress reveal>). Result bars fill from empty once: Attack case strength, Factoids verification and each factoid's relevance. The live Timer and Sparring countdowns don't use it.
  • Success pop with a drawn check (.success-pop). The badge pops on the overshoot curve and the tick draws itself. It is used on completed turns in the Sparring round plan (mint #14B37D), the Sparring winner badge, "Saved to History", the intro's lines, and every success toast icon.

Buttons, tiles, tabs, switches and checkboxes already had springy press and hover feedback from the restyle, so this PR leaves them unchanged.

Screenshots

Images live on the debateowl-animations-screenshots branch; delete it after this PR closes.

Lids on every pose, half and fully closed:
Lids on every pose

A blink in the app, on the Ask owl and the header mark:
Ask owl blinking Header mark blinking

The intro owl's cheer and the Ask owl's wave, frame by frame (each animation paused and stepped):
Intro cheer filmstrip
Ask wave filmstrip

Home at 390 px, mid-entrance and settled:
Home entering
Home settled

Attack at 1440 px, counting (54 on its way to 62, bar filling) and settled:
Attack counting
Attack settled

The intro at 820 px mid-cheer and at 390 px settled, Sparring setup at 820 px, the landing page at 1440 px, and Sparring with reduced motion:
Intro cheer
Intro settled
Sparring setup
Landing
Reduced motion

Verification

All of this ran locally at the PR head; it is not a CI result.

  • npm run verify passed (exit 0): docs check, lint, typecheck, unit tests 663/663 in 91 files, collaboration 30/30, edge 482/482, build and prerender, the service-worker offline check, and Playwright @smoke 47/47 on desktop Chromium.
  • Motion and performance specs on desktop and mobile Chromium (animation-polish, loading-performance, navigation-performance, sparring, staging-switch, home-navigation): 61 passed, 4 failed and 1 flaky.
    • The 4 failures are the known ones that also fail on main: loading-performance:63 and navigation-performance:344, each on both projects.
    • The flaky test is home-navigation:90, the onboarding tooltip appearing within 100 ms. It also flakes on main: run 16 times each, it failed 2 times with main's src and 3 times with this branch.
    • Both reduced-motion specs pass: no running animations and no animate- classes animating.
  • I drove the app in Playwright at 390, 820 and 1440 px: landing, Home, the intro, Attack with a result, Ask, Sparring setup and 404. There was no horizontal overflow at any width, and the console showed no errors beyond the stubbed backend's failed save. With reduced motion, document.getAnimations() reported nothing running on Home, Ask or Sparring.
  • Over 3 seconds on Home, with the header mark, Home's owl and the entrance all animating: 0 layout shifts, 171 frames (about 57 fps under headless software rendering), longest frame 33 ms.

Not checked: WebKit and real phones, dark mode screenshots (the owl's colours don't change with the theme), and the agentic tests.

🤖 Generated with Claude Code

The owl blinks on its own clock, breathes while idle, and plays a
one-shot gesture when it arrives or changes pose: a wave for point,
coach and arguing, a double jump for celebrating, a hop for surprised
and idea. Hovering it makes it hop. The app mark blinks too.

App pages rise in once as they open, Home's sections and tool rows
follow each other in, result scores count up and their bars fill from
empty, and success checks pop and draw themselves.

Everything is a CSS transform or opacity keyframe, so reduced motion
stops all of it and nothing shifts layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 27, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing.

Comment @coderabbitai help to get the list of available commands.

# Conflicts:
#	src/components/features/tools/result-actions.tsx
@Programcode
Programcode marked this pull request as ready for review October 27, 2026 11:07
Copilot AI balanced review requested due to automatic review settings October 27, 2026 11:07
@Programcode
Programcode merged commit d045977 into main Oct 27, 2026
2 of 8 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants