Skip to content

Add interactive animated logo and favicon integration#45

Merged
TheEagleByte merged 5 commits intomainfrom
feature/animated-logo-integration
Sep 30, 2025
Merged

Add interactive animated logo and favicon integration#45
TheEagleByte merged 5 commits intomainfrom
feature/animated-logo-integration

Conversation

@TheEagleByte
Copy link
Copy Markdown
Owner

@TheEagleByte TheEagleByte commented Sep 30, 2025

Summary

This PR adds tasteful animated logo integration across key brand touchpoints and comprehensive favicon support.

Changes

Interactive Animated Logo Component

  • Created InteractiveAnimatedLogo wrapper component with smart animation logic
  • Play-once on mount: Animates once per session using sessionStorage
  • Hover easter egg: Re-triggers animation on hover with cooldown (3.5s) to prevent spam
  • Accessibility: Respects prefers-reduced-motion user preference
  • Non-looping: Animation completes to final logo state for professional feel

Logo Integration Points

  • Header (src/components/layout/Header.tsx): 32px logo with subtle animation on first visit
  • Auth Page (src/app/auth/page.tsx): 64px logo for welcoming onboarding
  • Dashboard (src/app/dashboard/page.tsx): 56px logo celebrates user arrival

Favicon Support

  • Added favicon.ico and PNG variants (16x16, 32x32)
  • Apple touch icon for iOS devices (180x180)
  • Android Chrome icons (192x192, 512x512)
  • Configured site.webmanifest with ScrumKit branding (violet theme color)
  • Updated app metadata with comprehensive icon configuration

Design Philosophy

  • ✅ Adds elevated polish at strategic brand moments
  • ✅ Non-distracting: plays once, then remains static
  • ✅ Delightful hover interaction for curious users
  • ✅ Work pages keep static logos for focus
  • ✅ Respects accessibility preferences

Testing

  • Logo animates on first page load
  • Hover triggers animation with proper cooldown
  • Session storage prevents re-animation on navigation
  • Favicon appears correctly in browser tabs
  • Respects prefers-reduced-motion

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a PWA manifest enabling installability, icons, theme/background colors.
    • Introduced an interactive animated logo across header, dashboard, and auth pages that plays on mount, on hover/focus/touch, respects reduced‑motion, and limits replay frequency per session.
  • Improvements

    • Expanded site metadata (open graph, Twitter, icons, manifest) for richer previews and platform integration.
    • Updated dashboard hero timing for smoother entrance and renamed/expanded New Board page to "Create New Retro Board" with template details.

- Create InteractiveAnimatedLogo component with hover-to-animate easter egg
  - Plays once on mount per session (uses sessionStorage)
  - Hover triggers animation cycle with cooldown to prevent spam
  - Respects prefers-reduced-motion for accessibility
  - Non-looping animation completes to final logo state

- Integrate animated logo in key brand touchpoints:
  - Header: 32px logo animates once on site first visit
  - Auth page: 64px logo for welcoming onboarding experience
  - Dashboard: 56px logo celebrates user arrival

- Add favicon support across all platforms:
  - Configure favicon.ico, 16x16, and 32x32 PNG favicons
  - Add Apple touch icon for iOS devices
  - Include Android Chrome icons (192x192 and 512x512)
  - Update site.webmanifest with ScrumKit branding
  - Update app metadata with comprehensive icon configuration

The animated logo adds elevated polish at strategic moments while
maintaining focus during productive work sessions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 30, 2025 23:31
@vercel
Copy link
Copy Markdown

vercel bot commented Sep 30, 2025

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

Project Deployment Preview Comments Updated (UTC)
scrumkit Ready Ready Preview Comment Sep 30, 2025 11:50pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Sep 30, 2025

Warning

Rate limit exceeded

@TheEagleByte has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 3 minutes and 18 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.

📥 Commits

Reviewing files that changed from the base of the PR and between e8404fa and e688862.

📒 Files selected for processing (1)
  • src/app/__tests__/layout.test.tsx (1 hunks)

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a new InteractiveAnimatedLogo component and replaces static logo images in auth, dashboard, and header with it. Introduces a PWA manifest file and expands app metadata (icons, openGraph, twitter, manifest). Adjusts dashboard hero ordering and small animation timing delays.

Changes

Cohort / File(s) Summary
PWA manifest & app metadata
public/site.webmanifest, src/app/layout.tsx
Add public/site.webmanifest (name/short_name, start_url, scope, display, colors, description, icons). Expand metadata in src/app/layout.tsx with keywords, authors, creator, openGraph, twitter, icons, manifest, and metadataBase; remove previous generator.
Interactive animated logo component
src/components/InteractiveAnimatedLogo.tsx
New default-exported component that wraps AnimatedLogo, supports props (size, playOnMount, enableHover, sessionKey, className, ariaHidden), session-scoped autoplay, hover/focus/touch replay with cooldown, reduced-motion respect, and accessibility attributes.
Logo replacements & UI tweaks
src/app/auth/page.tsx, src/app/dashboard/page.tsx, src/components/layout/Header.tsx
Replace Next.js Image logo with InteractiveAnimatedLogo in auth page and header. Insert centered InteractiveAnimatedLogo block into dashboard hero before banner; adjust h1/paragraph animation delays (h1 +0.1s, p +0.1s).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant P as Page (Auth / Dashboard / Header)
  participant L as InteractiveAnimatedLogo
  participant A as AnimatedLogo
  participant S as sessionStorage
  participant R as PrefersReducedMotion

  U->>P: Load page
  P->>L: Mount (props: size, playOnMount, enableHover, sessionKey)
  L->>R: Query prefers-reduced-motion
  alt playOnMount && !reduced-motion
    L->>S: read sessionKey
    alt not played this session
      L->>A: play once (loop:false)
      L->>S: set sessionKey=played
    else already played
      L-->>A: idle
    end
  else skip autoplay
    L-->>A: idle
  end
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant L as InteractiveAnimatedLogo
  participant A as AnimatedLogo
  participant R as PrefersReducedMotion

  U->>L: hover / focus / touch
  L->>R: Query prefers-reduced-motion
  alt enableHover && !reduced-motion && not coolingDown
    L->>A: trigger play once
    note right of L #D8F3DC: start ~3.5s cooldown
  else ignore interaction
    L-->>U: no action
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

I nibble code and twitch my ear,
A tiny hop — a logo clear.
Manifests tucked in pockets neat,
Icons ready, animations greet.
ScrumKit shines; I thump in cheer. 🐇✨

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title accurately reflects the primary changes by referencing both the addition of the interactive animated logo component and the integration of favicon support, which are the two main objectives of the PR. It is concise and specific without including unnecessary detail or noise. The title clearly conveys the focus of the changes, making it easy for reviewers to understand the scope at a glance.

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Adds an interactive animated logo component with session-based playback and comprehensive favicon support across the application. The logo provides a polished brand experience while respecting user accessibility preferences.

  • Creates reusable InteractiveAnimatedLogo wrapper with smart animation logic and hover interactions
  • Integrates animated logos at key brand touchpoints (header, auth, dashboard) with session-based play-once behavior
  • Adds complete favicon configuration including multiple formats and web manifest for cross-platform support

Reviewed Changes

Copilot reviewed 6 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/components/InteractiveAnimatedLogo.tsx New component providing animated logo with accessibility and session management
src/components/layout/Header.tsx Replaces static logo with interactive animated version
src/app/dashboard/page.tsx Adds animated logo to dashboard with adjusted animation timing
src/app/auth/page.tsx Replaces static logo with interactive animated version
src/app/layout.tsx Adds comprehensive favicon configuration
public/site.webmanifest Web manifest for PWA support and branding

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

- Update root layout metadata from retrospective-only to platform-wide
  - New title: "ScrumKit - Open Source Tools for Better Sprints"
  - Expanded description to mention all ceremony tools
  - Added comprehensive keywords for better SEO
  - Added Open Graph tags for social sharing
  - Added Twitter Card metadata
  - Added authors and creator metadata
  - Added metadataBase for proper URL resolution

- Enhanced Create Board page metadata
  - Updated description to mention template variety

SEO now properly reflects that ScrumKit is a complete scrum toolkit
with retrospectives, planning poker, daily standups, and team health
checks, aligning with PROJECT.md vision.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown

@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.

Actionable comments posted: 5

🧹 Nitpick comments (5)
src/app/dashboard/page.tsx (1)

33-41: Avoid dual first‑load animations

Header and hero logos will both animate on the first dashboard visit. If that’s too busy, either disable playOnMount for one, or reuse a shared sessionKey so only one runs per session. Optionally support onTouchStart if you want the hover easter egg on mobile.

src/components/layout/Header.tsx (1)

21-29: Header logo integration looks good; consider decorative logo for a11y

Since the link already has visible text “ScrumKit”, make the logo container aria‑decorative to avoid redundant labeling (e.g., expose a prop to set aria-hidden or override/remove the internal aria-label in InteractiveAnimatedLogo).

public/site.webmanifest (1)

1-1: PWA polish: add start_url/scope and maskable icons
Add start_url and scope, and include "purpose": "any maskable" on your icons to improve install UX and ensure adaptive icon rendering.

-{"name":"ScrumKit","short_name":"ScrumKit","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#7c3aed","background_color":"#000000","display":"standalone"}
+{
+  "name": "ScrumKit",
+  "short_name": "ScrumKit",
+  "start_url": "/",
+  "scope": "/",
+  "display": "standalone",
+  "background_color": "#000000",
+  "theme_color": "#7c3aed",
+  "icons": [
+    { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" },
+    { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" }
+  ]
+}
src/components/InteractiveAnimatedLogo.tsx (2)

31-33: Prefer reactive reduced‑motion handling.

prefersReducedMotion is computed once and won’t track user changes at runtime. Optional: switch to state + matchMedia(...).addEventListener('change', ...) to update on preference changes.


54-70: Touch/keyboard support (optional).

If you want the replay to work beyond hover, add onTouchStart={handleHover} and onFocus={handleHover} or an onKeyDown handler for Enter/Space. If you intentionally scope to hover only, ignore.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03bdbcd and c170eca.

⛔ Files ignored due to path filters (6)
  • public/android-chrome-192x192.png is excluded by !**/*.png
  • public/android-chrome-512x512.png is excluded by !**/*.png
  • public/apple-touch-icon.png is excluded by !**/*.png
  • public/favicon-16x16.png is excluded by !**/*.png
  • public/favicon-32x32.png is excluded by !**/*.png
  • public/favicon.ico is excluded by !**/*.ico
📒 Files selected for processing (6)
  • public/site.webmanifest (1 hunks)
  • src/app/auth/page.tsx (2 hunks)
  • src/app/dashboard/page.tsx (4 hunks)
  • src/app/layout.tsx (1 hunks)
  • src/components/InteractiveAnimatedLogo.tsx (1 hunks)
  • src/components/layout/Header.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use the @/* import alias for modules under src instead of relative paths

Files:

  • src/app/auth/page.tsx
  • src/app/layout.tsx
  • src/components/InteractiveAnimatedLogo.tsx
  • src/app/dashboard/page.tsx
  • src/components/layout/Header.tsx
🧬 Code graph analysis (4)
src/app/auth/page.tsx (1)
src/components/InteractiveAnimatedLogo.tsx (1)
  • InteractiveAnimatedLogo (21-85)
src/components/InteractiveAnimatedLogo.tsx (1)
src/components/AnimatedLogo.tsx (1)
  • AnimatedLogo (18-240)
src/app/dashboard/page.tsx (1)
src/components/InteractiveAnimatedLogo.tsx (1)
  • InteractiveAnimatedLogo (21-85)
src/components/layout/Header.tsx (1)
src/components/InteractiveAnimatedLogo.tsx (1)
  • InteractiveAnimatedLogo (21-85)
🪛 GitHub Check: Run Tests (20.x)
src/components/InteractiveAnimatedLogo.tsx

[warning] 29-29:
'hasPlayedOnMount' is assigned a value but never used

🔇 Additional comments (5)
src/app/auth/page.tsx (1)

4-4: Good import alias usage

Using "@/components/InteractiveAnimatedLogo" adheres to the alias guideline.

src/app/dashboard/page.tsx (3)

43-46: Motion timing tweak looks fine

The added pre-banner motion with small delay is sensible and consistent with the hero timing.


56-56: Headline delay adjustment OK

Incremental delay to 0.3s keeps a smooth entrance cadence.


65-65: Body copy delay adjustment OK

0.4s pairs well after the headline; no concerns.

src/components/InteractiveAnimatedLogo.tsx (1)

4-4: Good: uses @ alias per repo guideline.

Import path follows the @/* alias rule for src modules. As per coding guidelines.

InteractiveAnimatedLogo improvements:
- Remove unused hasPlayedOnMount variable
- Add reactive reduced-motion handling with MediaQuery listener
- Add touch and keyboard support (onTouchStart, onFocus)
- Add ariaHidden prop for decorative logo usage
- Support tabIndex control for accessibility

PWA manifest enhancements:
- Add start_url and scope for better install UX
- Add "any maskable" purpose to icons for adaptive rendering
- Add description field
- Format JSON for readability

Accessibility improvements:
- Set ariaHidden=true on Header logo (decorative, text already present)
- Proper aria-label conditional rendering

Animation optimization:
- Dashboard logo now shares sessionKey with header
- Disabled playOnMount on dashboard to prevent dual animations
- Only header logo animates on first visit, dashboard is hover-only

Addresses feedback from copilot-pull-request-reviewer and coderabbitai.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Updated test expectations to match the new platform-wide metadata:
- New title: "ScrumKit - Open Source Tools for Better Sprints"
- Expanded description covering all ceremony tools
- Added tests for new metadata fields (keywords, openGraph, twitter)
- Adjusted description length limit from 200 to 250 chars

Fixes failing tests after SEO metadata update.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Changed 'retrospectives' to 'Retrospectives' (capital R) to match
the actual metadata description.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@TheEagleByte TheEagleByte merged commit cda2ce4 into main Sep 30, 2025
3 checks passed
Copy link
Copy Markdown

@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.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/components/InteractiveAnimatedLogo.tsx (1)

31-101: Interactive replay currently never works without a remount and correct timing.

Re-raising the earlier review: AnimatedLogo ignores the autoPlay prop, so the wrapper cannot prevent the first animation, cannot replay on hover, and the 2.8 s timeout stops ~600 ms before the logo actually finishes (its totalCycleDuration is 3.4 s). There’s also no changing key, so even after fixing AnimatedLogo you won’t restart the motion, and the timers aren’t cleared on unmount. Please wire in a remount key, align the durations, clear the timers, and update AnimatedLogo itself to honour autoPlay (e.g. skip its animate props when false).

-  const [isAnimating, setIsAnimating] = useState(false);
-  const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
-  const cooldownRef = useRef(false);
+  const [isAnimating, setIsAnimating] = useState(false);
+  const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
+  const [motionPreferenceResolved, setMotionPreferenceResolved] = useState(false);
+  const [animKey, setAnimKey] = useState(0);
+  const cooldownRef = useRef(false);
+  const animationTimerRef = useRef<number | null>(null);
+  const cooldownTimerRef = useRef<number | null>(null);
+  const ANIMATION_DURATION_MS = 3400;
+  const COOLDOWN_DURATION_MS = 3500;
@@
-      setIsAnimating(true);
-      sessionStorage.setItem(sessionKey, "true");
-
-      // Animation duration is ~2.8 seconds (from component)
-      setTimeout(() => {
-        setIsAnimating(false);
-      }, 2800);
+      sessionStorage.setItem(sessionKey, "true");
+      setIsAnimating(true);
+      setAnimKey((key) => key + 1);
+      if (animationTimerRef.current) clearTimeout(animationTimerRef.current);
+      animationTimerRef.current = window.setTimeout(() => {
+        setIsAnimating(false);
+      }, ANIMATION_DURATION_MS);
@@
-    if (!enableHover || prefersReducedMotion || cooldownRef.current) return;
+    if (!motionPreferenceResolved || prefersReducedMotion || cooldownRef.current) return;
@@
-    setIsAnimating(true);
-    cooldownRef.current = true;
-
-    // Animation duration + cooldown
-    setTimeout(() => {
-      setIsAnimating(false);
-    }, 2800);
-
-    // Cooldown period to prevent spam (3.5 seconds)
-    setTimeout(() => {
-      cooldownRef.current = false;
-    }, 3500);
+    setIsAnimating(true);
+    setAnimKey((key) => key + 1);
+    cooldownRef.current = true;
+    if (animationTimerRef.current) clearTimeout(animationTimerRef.current);
+    animationTimerRef.current = window.setTimeout(() => {
+      setIsAnimating(false);
+    }, ANIMATION_DURATION_MS);
+    if (cooldownTimerRef.current) clearTimeout(cooldownTimerRef.current);
+    cooldownTimerRef.current = window.setTimeout(() => {
+      cooldownRef.current = false;
+    }, COOLDOWN_DURATION_MS);
   };
+
+  useEffect(() => {
+    return () => {
+      if (animationTimerRef.current) clearTimeout(animationTimerRef.current);
+      if (cooldownTimerRef.current) clearTimeout(cooldownTimerRef.current);
+    };
+  }, []);
@@
-      <AnimatedLogo
+      <AnimatedLogo
+        key={animKey}
         size={size}
         autoPlay={isAnimating}
         loop={false}
       />
src/app/layout.tsx (1)

50-64: Fix the icon descriptors and expose the theme color.

The icons.other array still lacks rel, so Next.js won’t emit valid <link> tags and type-checking remains unhappy—this was already flagged earlier. Move the Android Chrome PNGs into icons.icon (or supply rel: "icon") and add themeColor to keep metadata aligned with site.webmanifest.

   icons: {
     icon: [
       { url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" },
       { url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" },
+      { url: "/android-chrome-192x192.png", sizes: "192x192", type: "image/png" },
+      { url: "/android-chrome-512x512.png", sizes: "512x512", type: "image/png" },
       { url: "/favicon.ico", sizes: "any" },
     ],
     apple: [
       { url: "/apple-touch-icon.png", sizes: "180x180", type: "image/png" },
     ],
-    other: [
-      { url: "/android-chrome-192x192.png", sizes: "192x192", type: "image/png" },
-      { url: "/android-chrome-512x512.png", sizes: "512x512", type: "image/png" },
-    ],
   },
+  themeColor: "#7c3aed",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c170eca and e8404fa.

📒 Files selected for processing (6)
  • public/site.webmanifest (1 hunks)
  • src/app/boards/new/page.tsx (1 hunks)
  • src/app/dashboard/page.tsx (4 hunks)
  • src/app/layout.tsx (1 hunks)
  • src/components/InteractiveAnimatedLogo.tsx (1 hunks)
  • src/components/layout/Header.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/components/layout/Header.tsx
  • public/site.webmanifest
  • src/app/dashboard/page.tsx
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use the @/* import alias for modules under src instead of relative paths

Files:

  • src/components/InteractiveAnimatedLogo.tsx
  • src/app/boards/new/page.tsx
  • src/app/layout.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-28T15:22:35.016Z
Learnt from: CR
PR: TheEagleByte/scrumkit#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-28T15:22:35.016Z
Learning: Applies to src/components/RetrospectiveBoard.tsx : Implement and maintain the main RetrospectiveBoard in src/components/RetrospectiveBoard.tsx

Applied to files:

  • src/app/boards/new/page.tsx
🧬 Code graph analysis (1)
src/components/InteractiveAnimatedLogo.tsx (1)
src/components/AnimatedLogo.tsx (1)
  • AnimatedLogo (18-240)
🔇 Additional comments (1)
src/app/boards/new/page.tsx (1)

8-9: Metadata copy update LGTM.

The richer description fits the retro templates focus—looks good.

Comment on lines +35 to +82
// Reactive reduced-motion handling
useEffect(() => {
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
setPrefersReducedMotion(mediaQuery.matches);

const handleChange = (e: MediaQueryListEvent) => {
setPrefersReducedMotion(e.matches);
};

mediaQuery.addEventListener("change", handleChange);
return () => mediaQuery.removeEventListener("change", handleChange);
}, []);

// Handle initial animation on mount
useEffect(() => {
if (!playOnMount || prefersReducedMotion) return;

// Check if animation has already played this session
const hasPlayed = sessionStorage.getItem(sessionKey);
if (!hasPlayed) {
setIsAnimating(true);
sessionStorage.setItem(sessionKey, "true");

// Animation duration is ~2.8 seconds (from component)
setTimeout(() => {
setIsAnimating(false);
}, 2800);
}
}, [playOnMount, sessionKey, prefersReducedMotion]);

// Handle hover interaction
const handleHover = () => {
if (!enableHover || prefersReducedMotion || cooldownRef.current) return;

// Start animation
setIsAnimating(true);
cooldownRef.current = true;

// Animation duration + cooldown
setTimeout(() => {
setIsAnimating(false);
}, 2800);

// Cooldown period to prevent spam (3.5 seconds)
setTimeout(() => {
cooldownRef.current = false;
}, 3500);
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Defer play-on-mount until the reduced-motion preference resolves.

Because prefersReducedMotion starts as false, this effect fires before the media query subscription sets the real value. Reduced-motion users still get the very first animation and the session flag flips, which breaks the accessibility promise. Block the animation until you actually know the user’s preference.

-  const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
+  const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
+  const [motionPreferenceResolved, setMotionPreferenceResolved] = useState(false);
@@
-    const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
-    setPrefersReducedMotion(mediaQuery.matches);
+    const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
+    setPrefersReducedMotion(mediaQuery.matches);
+    setMotionPreferenceResolved(true);
@@
-    const handleChange = (e: MediaQueryListEvent) => {
-      setPrefersReducedMotion(e.matches);
-    };
+    const handleChange = (e: MediaQueryListEvent) => {
+      setPrefersReducedMotion(e.matches);
+      setMotionPreferenceResolved(true);
+    };
@@
-    if (!playOnMount || prefersReducedMotion) return;
+    if (!motionPreferenceResolved) return;
+    if (!playOnMount || prefersReducedMotion) return;
@@
-    if (!enableHover || prefersReducedMotion || cooldownRef.current) return;
+    if (!motionPreferenceResolved || prefersReducedMotion || cooldownRef.current) return;

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/components/InteractiveAnimatedLogo.tsx around lines 35 to 82, the
play-on-mount effect runs before the real reduced-motion preference is known
(prefersReducedMotion defaults to false), causing an unwanted first animation
for reduced-motion users; fix by making the reduced-motion query resolution
explicit (e.g., introduce a "prefersReducedMotionResolved" flag or initialize
prefersReducedMotion to undefined) and only run the play-on-mount effect when
the preference has been resolved (add that resolved flag to the effect
dependencies and bail out if unresolved), keeping the existing sessionStorage
guard and timeout logic intact.

@coderabbitai coderabbitai bot mentioned this pull request Oct 3, 2025
5 tasks
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