Add interactive animated logo and favicon integration#45
Conversation
- 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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
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 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. 📒 Files selected for processing (1)
Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughAdds 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
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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Comment |
There was a problem hiding this comment.
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
InteractiveAnimatedLogowrapper 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>
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (5)
src/app/dashboard/page.tsx (1)
33-41: Avoid dual first‑load animationsHeader and hero logos will both animate on the first dashboard visit. If that’s too busy, either disable
playOnMountfor one, or reuse a sharedsessionKeyso only one runs per session. Optionally supportonTouchStartif 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 a11ySince 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-hiddenor override/remove the internalaria-labelin InteractiveAnimatedLogo).public/site.webmanifest (1)
1-1: PWA polish: add start_url/scope and maskable icons
Addstart_urlandscope, 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.
prefersReducedMotionis 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}andonFocus={handleHover}or anonKeyDownhandler for Enter/Space. If you intentionally scope to hover only, ignore.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (6)
public/android-chrome-192x192.pngis excluded by!**/*.pngpublic/android-chrome-512x512.pngis excluded by!**/*.pngpublic/apple-touch-icon.pngis excluded by!**/*.pngpublic/favicon-16x16.pngis excluded by!**/*.pngpublic/favicon-32x32.pngis excluded by!**/*.pngpublic/favicon.icois 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.tsxsrc/app/layout.tsxsrc/components/InteractiveAnimatedLogo.tsxsrc/app/dashboard/page.tsxsrc/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 usageUsing "@/components/InteractiveAnimatedLogo" adheres to the alias guideline.
src/app/dashboard/page.tsx (3)
43-46: Motion timing tweak looks fineThe added pre-banner motion with small delay is sensible and consistent with the hero timing.
56-56: Headline delay adjustment OKIncremental delay to 0.3s keeps a smooth entrance cadence.
65-65: Body copy delay adjustment OK0.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>
There was a problem hiding this comment.
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:
AnimatedLogoignores theautoPlayprop, 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 (itstotalCycleDurationis 3.4 s). There’s also no changingkey, so even after fixingAnimatedLogoyou 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 updateAnimatedLogoitself to honourautoPlay(e.g. skip itsanimateprops 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.otherarray still lacksrel, so Next.js won’t emit valid<link>tags and type-checking remains unhappy—this was already flagged earlier. Move the Android Chrome PNGs intoicons.icon(or supplyrel: "icon") and addthemeColorto keep metadata aligned withsite.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
📒 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.tsxsrc/app/boards/new/page.tsxsrc/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.
| // 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); | ||
| }; |
There was a problem hiding this comment.
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.
Summary
This PR adds tasteful animated logo integration across key brand touchpoints and comprehensive favicon support.
Changes
Interactive Animated Logo Component
InteractiveAnimatedLogowrapper component with smart animation logicprefers-reduced-motionuser preferenceLogo Integration Points
src/components/layout/Header.tsx): 32px logo with subtle animation on first visitsrc/app/auth/page.tsx): 64px logo for welcoming onboardingsrc/app/dashboard/page.tsx): 56px logo celebrates user arrivalFavicon Support
Design Philosophy
Testing
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Improvements