Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude finished @rabden's task in 2m 38s —— View job PR Review SummaryI've reviewed this PR which adds a splash screen, intro section, structured data enhancements, and animations. I fixed one critical issue and identified several areas that need attention. ✅ Fixed IssuesCritical syntax error fixed (
|
📝 WalkthroughSummary by CodeRabbitRelease Notes
WalkthroughAdds two client-side intro experiences (IntroSection, SplashScreen) with reduced‑motion support and scroll‑locking; defers homepage navigation until intro completion. Updates global CSS (fonts, gradients, scrollbars, animate‑fade‑in), removes local fade‑in CSS from animated‑background, injects expanded JSON‑LD generators/scripts across pages (including SITE_URL and website schema), and bumps a few dependencies. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Homepage as "Homepage (client)"
participant Intro as "IntroSection"
participant Splash as "SplashScreen"
participant Router
User->>Homepage: load
Homepage->>Intro: render IntroSection (may lock scroll)
Homepage->>Splash: render SplashScreen (may lock scroll)
Note over Intro,Splash: both observe prefers-reduced-motion and manage timers/visibility
User->>Homepage: attempt navigation (hash/link)
Homepage->>Homepage: is introFinished?
alt intro not finished
Homepage->>Homepage: store pendingHash (defer navigation)
Homepage-->>User: navigation deferred
else intro finished
Homepage->>Router: navigate immediately
end
Intro->>Homepage: onComplete -> set introFinished
Homepage->>Router: if pendingHash -> navigate and clear pendingHash
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
4 new issues
|
…reen - Add prefers-reduced-motion check to skip splash for users who prefer reduced motion - Add onError handler to gracefully hide splash if video fails to load - Add aria-hidden="true" for screen reader accessibility - Remove unused videoRef - Fix comment to match actual 1.5s delay Co-authored-by: Md Rabiul Hossain Jahed <undefined@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/components/splash-screen.tsx`:
- Around line 7-39: The SplashScreen component can get stuck because
handleVideoEnd sets a timeout (setTimeout in handleVideoEnd) without
storing/clearing it and there is no fallback if the video never fires ended;
update SplashScreen to track timer IDs (useRef for initialDelay and endDelay),
clear any active timers in the cleanup of useEffect and when dismissing, add an
onError handler on the video element and a max-duration fallback timer (e.g.,
start a maxTimeout when showVideo becomes true or when videoRef.current loads)
that calls the same dismiss logic, and ensure document.body.style.overflow is
always reset when clearing/discarding the splash to avoid locking scroll.
- Around line 44-49: The splash overlay's motion.div in splash-screen.tsx uses
the className containing "pointer-events-none", allowing clicks to pass through;
update the className on the motion.div (the splash overlay element) to remove
"pointer-events-none" or replace it with "pointer-events-auto" so the overlay
blocks pointer events while visible (ensure the change is applied to the
motion.div that has initial/exit/transition props).
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@src/components/splash-screen.tsx`:
- Around line 44-54: Modify the splash screen error handler to report video load
failures via the existing reportError function before hiding the splash; update
handleVideoError to accept the event/error and call reportError(errorOrEvent, {
component: 'SplashScreen', handler: 'handleVideoError' }) and then call
setIsVisible(false). Ensure you import reportError from src/lib/logger.ts and
keep handleVideoEnd unchanged.
- Around line 31-42: The effect that toggles scrolling in useEffect currently
forces document.body.style.overflow = "unset" which clobbers any pre-existing
scroll locks; capture the previous overflow (e.g., const prev =
document.body.style.overflow) when isVisible becomes true, set overflow to
"hidden", and on both the isVisible false branch and the cleanup return restore
document.body.style.overflow = prev so the original value is preserved; update
the useEffect surrounding isVisible to store and restore the prior overflow
instead of unconditionally setting "unset".
- Around line 7-8: The exported React component SplashScreen currently lacks an
explicit return type; update its signature to include an explicit type that
allows null (e.g., change export function SplashScreen() to export function
SplashScreen(): JSX.Element | null or React.ReactNode) so the public API is
typed correctly given the conditional return of null in the component; adjust
any imports/types if needed to reference React.ReactNode.
- Around line 11-29: Derive the reduced-motion state and initial visibility
before the effect to avoid the setState-in-effect flash: compute
prefersReducedMotion once (e.g., a top-level constant or a lazy state
initializer) and use it to set the initial values for isVisible and showVideo
instead of calling setIsVisible inside useEffect; then keep the useEffect for
the timed showVideo logic only with an empty dependency array and clear the
timeout (replace/remove the setState-for-reduced-motion and the suggested
[prefersReducedMotion] dependency). Ensure you reference the existing symbols
useEffect, prefersReducedMotion, setIsVisible, setShowVideo, and initialDelay
when making the changes.
- Around line 56-57: Remove the early return that returns null when isVisible is
false so AnimatePresence can handle exit animations; instead keep rendering the
AnimatePresence wrapper and conditionally render the motion.div inside it based
on isVisible (referencing isVisible, AnimatePresence, and the motion.div used
for exit animation in the SplashScreen component) so the exit transition can run
before unmounting.
- Add explicit return type (JSX.Element | null) for public API - Use lazy state initializer to avoid setState in useEffect flash - Use reportError from lib/logger for proper Sentry error tracking - Preserve and restore original overflow value instead of setting 'unset' - Remove early return to allow AnimatePresence exit animations Co-authored-by: Md Rabiul Hossain Jahed <undefined@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/components/splash-screen.tsx`:
- Around line 86-92: The splash overlay motion.div is missing a data-testid
which the test-suite requires; update the JSX element (the motion.div in the
SplashScreen component) to include a descriptive data-testid attribute (e.g.
data-testid="splash-overlay") so tests can reliably select it while retaining
existing props like initial, exit, transition, className and aria-hidden.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/components/splash-screen.tsx`:
- Around line 60-75: The effect in useEffect (in SplashScreen component)
unconditionally restores document.body.style.overflow from
originalOverflowRef.current even when it never changed it; update the effect to
only store/modify overflow when you actually lock scrolling (when isVisible
becomes true) and only restore on cleanup or when isVisible becomes false if and
only if you previously changed it—use the existing originalOverflowRef (or add a
didLockRef flag) to record that you performed the lock, set
document.body.style.overflow = "hidden" only when locking, and on cleanup/when
hiding only restore document.body.style.overflow from
originalOverflowRef.current if that flag indicates you changed it.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Md Rabiul Hossain Jahed <163085056+rabden@users.noreply.github.com>
Avoid clobbering existing scroll state when splash is hidden (e.g., for users with prefers-reduced-motion). Now properly captures and restores the previous overflow value only when the splash actually locks scroll. Co-authored-by: Md Rabiul Hossain Jahed <undefined@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/components/splash-screen.tsx`:
- Around line 12-21: The server/client mismatch comes from
getPrefersReducedMotion() returning true on SSR but different on the client;
replace that initializer in SplashScreen with a useSyncExternalStore-based hook
that returns a server snapshot of true and on the client subscribes to
window.matchMedia("(prefers-reduced-motion: reduce)") to provide the live value;
use that hook (e.g., usePrefersReducedMotion) to derive the initial isVisible
state (const [isVisible, setIsVisible] = useState(() =>
!usePrefersReducedMotion())) or simply compute isVisible from the hook, ensure
the subscription callback updates subscribers and you clean up the media query
listener on unmount, and keep references to getPrefersReducedMotion only for
legacy fallback if needed.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/components/intro-section.tsx`:
- Around line 62-70: handleComplete currently captures the onComplete prop
directly which defeats the purpose of onCompleteRef; update handleComplete to
call onCompleteRef.current() instead of onComplete() and change its dependency
array to reference onCompleteRef (and keep unlockScroll) so the callback no
longer re-creates when onComplete identity changes — ensure it still uses
isSkippingRef.current guard, sets isSkippingRef.current = true, calls
unlockScroll(), setPhase("interactive"), setIsVisible(false), and then invokes
onCompleteRef.current() if present.
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Signed-off-by: Md Rabiul Hossain Jahed <163085056+rabden@users.noreply.github.com>
- Fixed critical syntax error in homepage-client.tsx where checkAndScroll function was duplicated/malformed causing application crash - Updated handleComplete in intro-section.tsx to use onCompleteRef.current instead of onComplete directly to prevent unnecessary effect re-runs when onComplete callback identity changes Co-authored-by: Md Rabiul Hossain Jahed <undefined@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/homepage-client.tsx (1)
82-120:⚠️ Potential issue | 🔴 CriticalCritical: duplicated/nested
checkAndScrollcauses double navigation and broken hash handling.The effect body contains two overlapping definitions of
checkAndScroll— the inner one (lines 95-102) is nested inside the outer'sif (element)branch (line 93). When the target element exists on the first frame:
- Outer calls
handleNavigate(hash)(line 94).- Execution falls into the inner definition +
requestAnimationFrame(checkAndScroll)(line 106), which starts a second polling loop that will callhandleNavigateagain.- Line 112 also kicks off the outer loop.
This results in double navigation, redundant rAF loops, and
setPendingHash(null)only firing inside the success branch instead of unconditionally before the loop.This looks like a bad merge or copy-paste artifact. The intended logic is a single
checkAndScrollwith thependingHashclearing moved outside.🐛 Proposed fix — flatten to a single checkAndScroll
useEffect(() => { if (!introFinished) return; const hash = pendingHash || window.location.hash.slice(1); + // Clear pendingHash synchronously to prevent re-entry + if (pendingHash) setPendingHash(null); + if (hash) { const startTime = performance.now(); const timeout = 2000; let animationFrameId: number | undefined; const checkAndScroll = () => { const element = document.getElementById(hash); if (element) { handleNavigate(hash); - const checkAndScroll = () => { - const element = document.getElementById(hash); - if (element) { - handleNavigate(hash); - } else if (performance.now() - startTime < timeout) { - animationFrameId = requestAnimationFrame(checkAndScroll); - } - }; - - // Clear pendingHash synchronously to prevent re-entry - if (pendingHash) setPendingHash(null); - animationFrameId = requestAnimationFrame(checkAndScroll); } else if (performance.now() - startTime < timeout) { animationFrameId = requestAnimationFrame(checkAndScroll); } }; animationFrameId = requestAnimationFrame(checkAndScroll); return () => { if (animationFrameId !== undefined) { cancelAnimationFrame(animationFrameId); } }; } }, [introFinished, pendingHash, handleNavigate]);
🤖 Fix all issues with AI agents
In `@src/components/homepage-client.tsx`:
- Around line 163-166: IntroSection currently renders JS-only content and
there's no <noscript> fallback or LCP preloads; add a simple non-JS fallback and
preload hints in the root layout. In the IntroSection component (and where it's
used with handleIntroComplete) add a <noscript> element that either renders
essential static markup or an accessible link/button that jumps to `#main-content`
so non-JS users can reach the main app; ensure the fallback duplicates only the
minimal, meaningful content/CTA. In the app root layout (the default exported
layout component) add <link rel="preload" as="image" href="..."> or <link
rel="preconnect"> entries for the Largest Contentful Paint assets referenced by
IntroSection/Hero (images/fonts) so crawlers and real users get LCP hints; keep
the hrefs matching the actual asset imports used by IntroSection and Hero.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/homepage-client.tsx (1)
82-110:⚠️ Potential issue | 🟠 MajorEffect can trigger double navigation when
pendingHashis cleared.When
pendingHashis truthy, the effect clears it synchronously (Line 101), which triggers a re-run. On the second run,pendingHashisnullbutwindow.location.hashstill contains the section (set byhandleNavigatein the first run's rAF), socheckAndScrollfires a second time for the same target — causing a redundant smooth scroll.Additionally, calling
setPendingHash(null)directly in the effect body is flagged by ESLint (react-hooks/set-state-in-effect) as it causes cascading renders.Fix both by moving the clear into the rAF callback (after navigation succeeds) and tracking whether this effect already handled a hash:
Proposed fix
useEffect(() => { if (!introFinished) return; const hash = pendingHash || window.location.hash.slice(1); if (hash) { const startTime = performance.now(); const timeout = 2000; let animationFrameId: number | undefined; + let handled = false; const checkAndScroll = () => { const element = document.getElementById(hash); if (element) { + handled = true; handleNavigate(hash); + if (pendingHash) setPendingHash(null); } else if (performance.now() - startTime < timeout) { animationFrameId = requestAnimationFrame(checkAndScroll); } }; - // Clear pendingHash synchronously to prevent re-entry - if (pendingHash) setPendingHash(null); animationFrameId = requestAnimationFrame(checkAndScroll); return () => { if (animationFrameId !== undefined) { cancelAnimationFrame(animationFrameId); } + // Clear pendingHash on cleanup if not yet handled + if (!handled && pendingHash) setPendingHash(null); }; } }, [introFinished, pendingHash, handleNavigate]);
🤖 Fix all issues with AI agents
In `@src/components/intro-section.tsx`:
- Around line 114-127: The scroll-based transforms created with
useScroll/useTransform (scrollYProgress, scale, opacity, blur, y tied to
containerRef) never run because body scroll is locked during the "text" phase
and when phase becomes "interactive" the component immediately returns null via
the isVisible check; either remove these unused transforms or postpone
unmounting so the exit-parallax can run: update the logic around isVisible/phase
(the place that calls setIsVisible(false) / handleComplete and the conditional
if (!isVisible && phase === "interactive") return null) to delay setting
isVisible to false (or delay returning null) until the scroll unlock occurs and
the scrollYProgress-based animations reach their end (or a short timeout after
unlock), ensuring the useScroll/useTransform values have time to animate before
the component unmounts.
| // Scroll animations for exit | ||
| const { scrollYProgress } = useScroll({ | ||
| target: containerRef, | ||
| offset: ["start start", "end start"], | ||
| }); | ||
|
|
||
| const scale = useTransform(scrollYProgress, [0, 1], [1, 0.9]); | ||
| const opacity = useTransform(scrollYProgress, [0, 0.5], [1, 0]); | ||
| const blur = useTransform(scrollYProgress, [0, 0.4], ["0px", "10px"]); | ||
| const y = useTransform(scrollYProgress, [0, 1], ["0%", "-10%"]); | ||
|
|
||
| const videoSrc = isDark ? "/animated-logo.webm" : "/animated-logo-white.mp4"; | ||
|
|
||
| if (!isVisible && phase === "interactive") return null; |
There was a problem hiding this comment.
Scroll-based exit transforms won't activate while scroll is locked.
useScroll / useTransform (Lines 115-123) compute parallax values based on scroll position, but body scroll is locked (overflow: hidden) during the entire "text" phase. These transforms only take effect after handleComplete unlocks scroll and transitions to "interactive" — at which point the component returns null (Line 127: !isVisible && phase === "interactive"). This means the parallax exit effect is effectively dead code.
If the parallax exit is intended as a scroll-away effect after the intro completes, the component unmounts before the user can scroll. Either remove the unused scroll transforms or delay setIsVisible(false) / return null to allow the scroll-away animation to play.
🤖 Prompt for AI Agents
In `@src/components/intro-section.tsx` around lines 114 - 127, The scroll-based
transforms created with useScroll/useTransform (scrollYProgress, scale, opacity,
blur, y tied to containerRef) never run because body scroll is locked during the
"text" phase and when phase becomes "interactive" the component immediately
returns null via the isVisible check; either remove these unused transforms or
postpone unmounting so the exit-parallax can run: update the logic around
isVisible/phase (the place that calls setIsVisible(false) / handleComplete and
the conditional if (!isVisible && phase === "interactive") return null) to delay
setting isVisible to false (or delay returning null) until the scroll unlock
occurs and the scrollYProgress-based animations reach their end (or a short
timeout after unlock), ensuring the useScroll/useTransform values have time to
animate before the component unmounts.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Md Rabiul Hossain Jahed <163085056+rabden@users.noreply.github.com>
The splash-screen.tsx file had a duplicate motion.div element on lines 118-123 which would cause a React render error. This was likely a merge artifact that duplicated the opening motion.div tag. Co-authored-by: Md Rabiul Hossain Jahed <undefined@users.noreply.github.com>
| } | ||
|
|
||
| .animate-fade-in { | ||
| animation: custom-fade-in 3s 3s ease-out forwards; |
There was a problem hiding this comment.
LCP Performance Impact: The .animate-fade-in animation uses a 3s delay + 3s duration (total 6s of invisible content). Any above-the-fold content using this class will significantly harm Largest Contentful Paint (LCP) metrics — Google's target is < 2.5s.
Consider:
- Reducing the delay to sync with intro completion (~1.5s max)
- Reducing
translateY(200px)to a smaller value (30-50px) for smaller viewports - Triggering via JS after the intro completes rather than a fixed CSS delay
| animation: custom-fade-in 3s 3s ease-out forwards; | |
| .animate-fade-in { | |
| animation: custom-fade-in 1.5s 1.5s ease-out forwards; | |
| opacity: 0; | |
| } |
|
|
||
| const videoSrc = isDark ? "/animated-logo.webm" : "/animated-logo-white.mp4"; | ||
|
|
||
| if (!isVisible && phase === "interactive") return null; |
There was a problem hiding this comment.
Dead Code - Unused scroll transforms: The scroll-based transforms (lines 115-123: scale, opacity, blur, y) will never activate. The body scroll is locked during the "text" phase, and when phase becomes "interactive", the component returns null on this line.
These transforms are effectively dead code since users can't scroll while the intro is visible. Consider either:
- Removing the unused
useScroll/useTransformhooks to reduce bundle size - Implementing a scroll-away exit animation after intro completes (delaying unmount)
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/components/splash-screen.tsx`:
- Around line 56-62: The dismiss function is duplicated — remove the local
`dismiss` declaration and replace its usages with the existing `dismissSplash`
to avoid redundant code; ensure `dismissSplash` (which calls `setIsDismissed`
and clears `initialTimerRef.current`, `dismissTimerRef.current`, and
`fallbackTimerRef.current`) remains exported/accessible in the component where
`dismiss` was used and delete the now-unused `dismiss` symbol to prevent linter
warnings.
- Around line 132-145: The video element used with videoRef and event handlers
handleVideoEnd/handleVideoError should include preload="none" so the browser
won't eagerly download /animated-logo.webm before showVideo mounts it; add
preload="none" to the <video> attributes (next to autoPlay, muted, playsInline)
to defer fetching until the element is actually inserted/played.
🤖 Morph Preview TestPreview URL: https://volvox-website-8crg663ew-volvox-llc.vercel.app AI SummaryIssues:
Verified:
RecordingAutomated testing by Morph |

Important
Adds splash screen with animations, structured data enhancements, and updates dependencies and CSS styles.
SplashScreencomponent insplash-screen.tsxwith video animation and reduced motion handling.usePrefersReducedMotionhook for motion preference detection.blog/[slug]/page.tsx,blog/page.tsx,privacy/page.tsx,products/[slug]/page.tsx,products/page.tsx,team/[slug]/page.tsx,team/page.tsx,terms/page.tsx.generateBreadcrumbSchema,generateSoftwareApplicationSchema,generatePersonSchematostructured-data.ts.custom-fade-inanimation inglobals.css.AnimatedBackgroundinanimated-background.tsxwith new particle configurations.framer-motionto^12.33.0inpackage.json.@types/nodeto^25.2.1and@types/reactto^19.2.13inpackage.json.This description was created by
for 151d943. You can customize this summary. It will automatically update as commits are pushed.