Skip to content

Add rotating word containers to hero and fix animated GIF optimization#51

Merged
rioredwards merged 3 commits intomainfrom
claude/review-changes-mlvs7wcmz24bdddg-joKRs
Feb 21, 2026
Merged

Add rotating word containers to hero and fix animated GIF optimization#51
rioredwards merged 3 commits intomainfrom
claude/review-changes-mlvs7wcmz24bdddg-joKRs

Conversation

@rioredwards
Copy link
Owner

@rioredwards rioredwards commented Feb 21, 2026

Summary

This PR adds a new rotating word containers feature to the hero section and fixes an issue where animated GIFs were not being properly handled by Next.js Image optimization.

Key Changes

  • Hero Section Enhancement: Added three rotating word containers displaying "create", "thoughtful", and "products" with corresponding E2E tests
  • Updated Hero Tagline: Changed the hero tagline text from "I build" to "I build thoughtful software."
  • Animated GIF Handling: Implemented auto-detection of .gif files in the ImageOverlay component to set the unoptimized property on Next.js Image, preventing optimization warnings for animated images
  • Page Object Model: Added heroTaglineVisible() and rotatingWordContainers() methods to the HomePage POM for better test selectors
  • Documentation: Updated TODO.md to mark the animated GIF optimization issue as completed

Implementation Details

  • The rotating word containers are queried using a .rotating-word-container class selector within the hero tagline element
  • GIF detection is case-insensitive and checks the file extension of both static and string image sources
  • The unoptimized property is conditionally applied only to animated GIFs, allowing other image formats to benefit from Next.js optimization

https://claude.ai/code/session_011eAwNMUyVSdr5wbmctsYV3

Summary by CodeRabbit

  • Bug Fixes

    • Fixed animated GIF optimization warnings in image overlays; GIFs are now detected and handled to prevent warnings.
  • New Features

    • Hero tagline enhanced with three rotating words and improved, drift-free rotation timing for smoother, consistent animations.
  • Tests

    • End-to-end tests updated to assert the full hero tagline and verify the three rotating word containers and their initial words.

@vercel
Copy link

vercel bot commented Feb 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
portfolio Ready Ready Preview, Comment Feb 21, 2026 5:03am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 21, 2026

Warning

Rate limit exceeded

@rioredwards has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 2 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Fixes ImageOverlay to auto-detect animated GIFs and disable Next.js image optimization for them, and adds deterministic, drift-free timing to RotatingWord plus new e2e tests and POM helpers verifying three rotating word containers and exact hero tagline text.

Changes

Cohort / File(s) Summary
GIF Optimization Fix
TODO.md, components/image-overlay/image-overlay.tsx
Detects .gif sources (case-insensitive) and sets unoptimized on Next.js Image; documents the fix in TODO.md.
Rotating Word Timing
components/ui/RotatingWord.tsx
Replaces relative timeouts with absolute, cycle-based timing using startTimeRef and cycleCountRef to prevent drift; adjusts scheduling for pre/during/post swap phases and cleans up timers.
E2E Tests & POM
e2e/home.spec.ts, e2e/pom/home-page.ts
Updates hero tagline assertion to exact text, adds test asserting three rotating word containers with initial words, and adds heroTaglineVisible() and rotatingWordContainers() locators to HomePage POM.

Sequence Diagram(s)

(omitted — changes are component-local and do not introduce multi-component sequential interactions)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nibble code by moonlit glow,
GIFs no longer trip the show,
Words that spin keep perfect time,
Tests confirm each little rhyme,
Hooray — the portfolio hops just so! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main changes: adding rotating word containers to the hero section and fixing animated GIF optimization in ImageOverlay.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/review-changes-mlvs7wcmz24bdddg-joKRs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

- Auto-detect animated GIFs in ImageOverlay and set `unoptimized` on
  next/image, silencing the Next.js optimization warning for
  ohm-logo-progression.gif (and any future GIFs) without requiring
  per-callsite configuration
- Add E2E tests for the multi-word rotating hero: verifies three
  RotatingWord containers render and display their initial words
  (create / thoughtful / products) on load; tightens the existing
  accessible-tagline assertion to the full static string
- Mark the GIF warning as resolved in TODO.md

https://claude.ai/code/session_011eAwNMUyVSdr5wbmctsYV3
@rioredwards rioredwards force-pushed the claude/review-changes-mlvs7wcmz24bdddg-joKRs branch from afe7e64 to 4f97eca Compare February 21, 2026 04:33
@rioredwards rioredwards marked this pull request as ready for review February 21, 2026 04:34
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
e2e/pom/home-page.ts (2)

28-30: Minor naming ambiguity: heroTaglineVisible() alongside heroTagline().

heroTagline() returns the canonical (hidden) element; heroTaglineVisible() returns the visible one. The suffix Visible reads as if it might return a boolean (like isVisible()), which differs from every other Locator-returning method in this POM. A name like heroTaglineDisplay() or heroTaglineElement() would be less ambiguous, though this is a low-priority nit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/pom/home-page.ts` around lines 28 - 30, The method name
heroTaglineVisible() is ambiguous because it suggests a boolean but returns a
Locator; rename it to a Locator-returning name (e.g., heroTaglineElement() or
heroTaglineDisplay()) and update all references; modify the declaration of
heroTaglineVisible() to the chosen name and replace every call site that
currently uses heroTaglineVisible() (and any tests/fixtures importing it) so the
POM remains consistent with other Locator-returning methods like heroTagline().

32-34: CSS class selector in rotatingWordContainers() is fragile — prefer data-testid.

.rotating-word-container is a styling class; if it's ever renamed or extracted, this locator breaks silently. Adding a data-testid="rotating-word-container" to each container element in the component and switching to getByTestId(...) here would make the selector implementation-agnostic and consistent with the rest of the POM.

♻️ Suggested change (after adding data-testid to the component)
-  rotatingWordContainers(): Locator {
-    return this.heroTaglineVisible().locator(".rotating-word-container");
-  }
+  rotatingWordContainers(): Locator {
+    return this.heroTaglineVisible().getByTestId("rotating-word-container");
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/pom/home-page.ts` around lines 32 - 34, The locator in
rotatingWordContainers() currently uses the styling class
".rotating-word-container", which is fragile; after adding
data-testid="rotating-word-container" to each container in the component, update
rotatingWordContainers() to use getByTestId("rotating-word-container") (scoped
off heroTaglineVisible() if needed) so the POM targets the test id instead of
the CSS class and becomes implementation-agnostic.
components/image-overlay/image-overlay.tsx (1)

39-40: endsWith(".gif") misses URLs with query parameters; variable name overstates detection capability.

Two issues in these two lines:

  1. Query params/fragments: A CDN URL like /ohm-logo-progression.gif?v=2 or https://cdn.example.com/img.gif#anchor would pass endsWith(".gif") as false, silently leaving optimization enabled and reproducing the original warning.

  2. Naming: isAnimatedGif implies the code can detect animation; it only detects the .gif extension. Non-animated GIFs also get unoptimized=true. A name like isGif is more accurate.

♻️ Suggested fix
-  const srcString = isStaticImage ? src.src : src;
-  const isAnimatedGif = srcString.toLowerCase().endsWith(".gif");
+  const srcString = isStaticImage ? src.src : src;
+  const isGif = srcString.split(/[?#]/)[0].toLowerCase().endsWith(".gif");
-        unoptimized={isAnimatedGif}
+        unoptimized={isGif}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/image-overlay/image-overlay.tsx` around lines 39 - 40, The GIF
detection misses query strings/fragments and the name overstates capability:
update the logic that uses srcString and isAnimatedGif to instead compute isGif
by parsing srcString (the value assigned from isStaticImage ? src.src : src) and
testing only the pathname portion or using a case-insensitive regex that ignores
query/fragment (e.g., match /\.gif(\?|#|$)/i) so URLs like "image.gif?v=2" or
"image.gif#anchor" are detected; rename the variable from isAnimatedGif to isGif
to reflect that this only checks the extension.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@components/image-overlay/image-overlay.tsx`:
- Around line 39-40: The GIF detection misses query strings/fragments and the
name overstates capability: update the logic that uses srcString and
isAnimatedGif to instead compute isGif by parsing srcString (the value assigned
from isStaticImage ? src.src : src) and testing only the pathname portion or
using a case-insensitive regex that ignores query/fragment (e.g., match
/\.gif(\?|#|$)/i) so URLs like "image.gif?v=2" or "image.gif#anchor" are
detected; rename the variable from isAnimatedGif to isGif to reflect that this
only checks the extension.

In `@e2e/pom/home-page.ts`:
- Around line 28-30: The method name heroTaglineVisible() is ambiguous because
it suggests a boolean but returns a Locator; rename it to a Locator-returning
name (e.g., heroTaglineElement() or heroTaglineDisplay()) and update all
references; modify the declaration of heroTaglineVisible() to the chosen name
and replace every call site that currently uses heroTaglineVisible() (and any
tests/fixtures importing it) so the POM remains consistent with other
Locator-returning methods like heroTagline().
- Around line 32-34: The locator in rotatingWordContainers() currently uses the
styling class ".rotating-word-container", which is fragile; after adding
data-testid="rotating-word-container" to each container in the component, update
rotatingWordContainers() to use getByTestId("rotating-word-container") (scoped
off heroTaglineVisible() if needed) so the POM targets the test id instead of
the CSS class and becomes implementation-agnostic.

Three independent setTimeout chains accumulated small timing errors
separately, causing the words to drift out of their 600ms stagger
after ~30-60 seconds.

Root cause: each setTimeout fires "now + delay" rather than at a
fixed absolute time, and tiny inaccuracies compound across cycles.

Fix: record a startTimeRef epoch once on init and a cycleCountRef.
Each phase transition is scheduled at
  startTimeRef + cycleCount * CYCLE_DURATION + phaseOffset
instead of a relative delay from "now". Since CYCLE_DURATION
(pauseDuration + ANIMATION_DURATION) is mathematically constant
regardless of word length, no drift can accumulate.

Also adds the missing clearTimeout cleanup to the phase effect.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
components/ui/RotatingWord.tsx (1)

134-134: Remove commented-out debug span before merging.

This {/* <span className="debug">... */} is a development artifact. Consider removing it to keep the component clean, or extracting it behind a process.env.NODE_ENV === 'development' guard if you use it frequently during development.

🧹 Proposed cleanup
-      {/* <span className="debug">{animationPhase}</span> */}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/RotatingWord.tsx` at line 134, Remove the commented-out debug
span in the RotatingWord component: delete the line {/* <span
className="debug">{animationPhase}</span> */} to clean up the component, or if
you want to keep it for development, replace it with a conditional render that
only shows in development (e.g., use process.env.NODE_ENV === 'development' &&
<span className="debug">{animationPhase}</span>) so the debug output
(animationPhase, className "debug") is not present in production.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@components/ui/RotatingWord.tsx`:
- Line 134: Remove the commented-out debug span in the RotatingWord component:
delete the line {/* <span className="debug">{animationPhase}</span> */} to clean
up the component, or if you want to keep it for development, replace it with a
conditional render that only shows in development (e.g., use
process.env.NODE_ENV === 'development' && <span
className="debug">{animationPhase}</span>) so the debug output (animationPhase,
className "debug") is not present in production.

The hero paragraph is rendered twice (desktop + mobile layouts), both
with data-testid="hero-tagline". The previous selector matched both,
returning 6 containers instead of the expected 3. Use :visible to
scope to the currently rendered layout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rioredwards rioredwards merged commit 984a7e1 into main Feb 21, 2026
6 checks passed
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