feat: add responsive hero section with scroll-based animation and smooth interaction#351
Conversation
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 0 minutes and 55 seconds. ⌛ 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. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds Lenis smooth-scrolling (dependency, CSS, provider) and integrates it into the root layout; replaces the previous home content with a new Hero component and introduces Hero and HeroVisuals client components that implement viewport-driven scroll animations and responsive visuals. Changes
Sequence Diagram(s)sequenceDiagram
participant Browser
participant Lenis as Lenis (Smooth Scroll)
participant HeroVisuals as HeroVisuals (Component)
participant DOM
Browser->>Lenis: native scroll input
Lenis->>Browser: interpolated scroll position (raf)
Browser->>HeroVisuals: requestAnimationFrame tick
HeroVisuals->>HeroVisuals: read getBoundingClientRect()
HeroVisuals->>HeroVisuals: compute normalized progress
HeroVisuals->>DOM: set hand opacity
HeroVisuals->>DOM: set phone transform/rotation
DOM-->>Browser: paint frame
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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 |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
src/app/[locale]/page.tsx (1)
14-14: UnuseduseTranslationshook.The
tvariable is declared but never used since the Hero component uses hardcodedTEXTconstants. This can be removed to reduce bundle size and avoid confusion.Proposed cleanup
'use client'; -import { useTranslations } from 'next-intl'; import { Hero } from '@/components/home/Hero'; -import dynamic from 'next/dynamic'; - -// const LanguageSwitcher = dynamic(() => import('@/components/ui/LanguageSwitcher'), { -// ssr: false, -// }); - export default function Home() { - const t = useTranslations('Home'); return ( <div className="flex h-[400vh] flex-col mx-4 sm:mx-8 lg:mx-16 xl:mx-48 "> <Hero /> </div> ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[locale]/page.tsx at line 14, Remove the unused i18n hook usage: delete the call to useTranslations and the unused variable t in the page component (the symbol t from useTranslations('Home')), since Hero currently renders hardcoded TEXT constants; update any imports to remove useTranslations if it's no longer referenced and ensure the Hero usage remains unchanged to avoid introducing regressions.src/components/home/Hero.tsx (1)
6-12: Good i18n preparation pattern, but consider connecting to existing translations.The
TEXTconstants are well-structured for future internationalization. However, thepage.tsxalready importsuseTranslations('Home'), suggesting translations may already exist. Consider connecting these constants to the translation system now rather than using hardcoded strings.If translations exist, connect them
// In Hero.tsx, accept translations as props or use the hook directly import { useTranslations } from 'next-intl'; export function Hero() { const t = useTranslations('Home'); return ( <section ...> <h1>{t('headline')}</h1> {/* etc */} </section> ); }This would require adding the corresponding keys to your
messages/{locale}.jsonfiles.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/home/Hero.tsx` around lines 6 - 12, The TEXT const in Hero.tsx is hardcoded but page.tsx already uses useTranslations('Home'), so update the Hero component to use the translation hook or accept translated strings as props instead of TEXT: inside the Hero component (or its props) call useTranslations('Home') (or receive t/home keys from page.tsx) and replace references to TEXT.headline, TEXT.subHeadline, TEXT.maintainedBy, and TEXT.orgName with t('headline'), t('subHeadline'), t('maintainedBy'), t('orgName'); also add the corresponding keys to your messages/{locale}.json files and remove or deprecate the TEXT constant.src/components/home/HeroVisuals.tsx (1)
73-91: Consider if both mobile images needpriorityloading.Both hand and phone images have
priority={true}, which preloads them in the document head. On mobile, this is likely acceptable since the hero is above the fold. However, if there are other priority images on the page, this may contend for bandwidth.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/home/HeroVisuals.tsx` around lines 73 - 91, The two next/image usages in the HeroVisuals component (the Image rendering hand.webp and the Image rendering phone.webp) both set priority which preloads both resources; remove or relax one of them so only the most critical above-the-fold image uses priority to avoid bandwidth contention (e.g., keep priority on the primary hero visual and remove priority from the secondary phone image, or conditionally set priority based on screen size/SSR detection inside HeroVisuals). Locate the two Image elements (hand.webp and phone.webp) and update the priority prop accordingly so only one Image has priority or implement a small conditional to set priority only for the main hero image.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/app/`[locale]/layout.tsx:
- Around line 95-101: Navbar's scroll reads and handlers are incompatible with
Lenis; instead either move Navbar out of LenisProvider or expose the Lenis
instance via context so Navbar can use Lenis APIs. Update LenisProvider to
create and provide the Lenis instance (e.g., via a LenisContext and a useLenis
hook) and in Navbar replace window.scrollY/scroll event usage and
window.scrollTo with lenis?.on('scroll', ...) subscriptions and
lenis?.scrollTo(0); ensure Lenis is cleaned up on unmount and guard Navbar logic
for null lenis.
In `@src/components/home/HeroVisuals.tsx`:
- Around line 28-66: The RAF loop in the useEffect (animate function) never
stops; capture the requestAnimationFrame id (e.g., let rafId) when calling
requestAnimationFrame(animate), return a cleanup function from the useEffect
that calls cancelAnimationFrame(rafId) and also cancels the loop when isMobile
becomes true or the component unmounts; ensure the cleanup clears any running
animation started by animate and avoid further state/DOM updates to sectionRef,
handRef, and phoneRef after cancellation.
- Around line 17-25: Change isMobile to start as an "unknown" value and render a
shared skeleton until the client measurement runs: initialize state as
useState<boolean | null>(null) (instead of false), keep the useEffect with
checkMobile() that sets true/false, and in the HeroVisuals render return a
neutral skeleton/loading placeholder when isMobile === null so the server and
first-client render match; reference the isMobile/setIsMobile state, the
checkMobile function, and the existing useEffect to implement this.
In `@src/components/providers/lenis-provider.tsx`:
- Around line 12-21: The RAF loop started by requestAnimationFrame(raf) inside
the raf(time: number) function is never cancelled on unmount; store the
animation frame id when scheduling (the value returned by requestAnimationFrame)
and call cancelAnimationFrame in the cleanup along with lenis.destroy(); update
the raf scheduling so you assign the handle (e.g., let rafId =
requestAnimationFrame(raf)) and then in the returned cleanup callback call
cancelAnimationFrame(rafId) before or after calling lenis.destroy().
---
Nitpick comments:
In `@src/app/`[locale]/page.tsx:
- Line 14: Remove the unused i18n hook usage: delete the call to useTranslations
and the unused variable t in the page component (the symbol t from
useTranslations('Home')), since Hero currently renders hardcoded TEXT constants;
update any imports to remove useTranslations if it's no longer referenced and
ensure the Hero usage remains unchanged to avoid introducing regressions.
In `@src/components/home/Hero.tsx`:
- Around line 6-12: The TEXT const in Hero.tsx is hardcoded but page.tsx already
uses useTranslations('Home'), so update the Hero component to use the
translation hook or accept translated strings as props instead of TEXT: inside
the Hero component (or its props) call useTranslations('Home') (or receive
t/home keys from page.tsx) and replace references to TEXT.headline,
TEXT.subHeadline, TEXT.maintainedBy, and TEXT.orgName with t('headline'),
t('subHeadline'), t('maintainedBy'), t('orgName'); also add the corresponding
keys to your messages/{locale}.json files and remove or deprecate the TEXT
constant.
In `@src/components/home/HeroVisuals.tsx`:
- Around line 73-91: The two next/image usages in the HeroVisuals component (the
Image rendering hand.webp and the Image rendering phone.webp) both set priority
which preloads both resources; remove or relax one of them so only the most
critical above-the-fold image uses priority to avoid bandwidth contention (e.g.,
keep priority on the primary hero visual and remove priority from the secondary
phone image, or conditionally set priority based on screen size/SSR detection
inside HeroVisuals). Locate the two Image elements (hand.webp and phone.webp)
and update the priority prop accordingly so only one Image has priority or
implement a small conditional to set priority only for the main hero image.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f2bcfb88-572d-42ef-9989-67ec9da14fb7
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (9)
package.jsonpublic/assets/mockups/hand.webppublic/assets/mockups/phone.webpsrc/app/[locale]/globals.csssrc/app/[locale]/layout.tsxsrc/app/[locale]/page.tsxsrc/components/home/Hero.tsxsrc/components/home/HeroVisuals.tsxsrc/components/providers/lenis-provider.tsx
…keeping Navbar out of lenis and lenis provider memory leak fix
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/home/HeroVisuals.tsx`:
- Around line 50-59: The desktop phone animation is missing the depth/scale
phase: compute a scale factor (e.g., phoneScale derived from current and clamped
to desired min/max) and a Z translate (phoneTranslateZ derived from current to
simulate depth) and include both in the transform for phoneRef.current; update
the transform in the block that sets phoneRef.current.style.transform (and the
analogous block around lines 133-139) to use translate3d(x, y, z) rotate(...)
scale(phoneScale) so the phone both moves in Z and scales during the entrance;
keep handRef logic unchanged. Ensure you reference phoneRef,
phoneTranslateX/phoneTranslateY, phoneRotate and add phoneScale/phoneTranslateZ
variables when implementing.
- Around line 21-23: The JS breakpoint in checkMobile currently uses 768px which
mismatches Tailwind's sm (640px); update checkMobile (or replace it) to use
window.matchMedia with the same Tailwind breakpoint (e.g.
matchMedia("(max-width: 639px)") or matchMedia("(min-width: 640px)") logic) so
isMobile aligns with Tailwind's sm breakpoint, and ensure the resize/matchMedia
listener added via window.addEventListener or mediaQuery.addEventListener is
cleaned up on unmount; locate the checkMobile function, the isMobile state, and
the window.addEventListener call to implement this change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 16666809-f55e-4d1d-933d-492a281253b8
📒 Files selected for processing (3)
src/app/[locale]/layout.tsxsrc/components/home/HeroVisuals.tsxsrc/components/providers/lenis-provider.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- src/app/[locale]/layout.tsx
- src/components/providers/lenis-provider.tsx
| const checkMobile = () => setIsMobile(window.innerWidth < 768); | ||
| checkMobile(); | ||
| window.addEventListener("resize", checkMobile); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Tailwind screen definitions =="
configs="$(fd 'tailwind\.config\.(js|cjs|mjs|ts)$' -HI . || true)"
if [ -z "$configs" ]; then
echo "No tailwind config found; verify whether the repo is using Tailwind defaults."
else
while IFS= read -r file; do
[ -z "$file" ] && continue
echo "--- $file ---"
rg -n "screens|sm|md" "$file"
done <<< "$configs"
fi
echo
echo "== Hero breakpoint usage =="
rg -n 'window\.innerWidth <|sm:|md:' src/components/home/HeroVisuals.tsx src/components/home/Hero.tsxRepository: AOSSIE-Org/Resonate-Website
Length of output: 1792
🌐 Web query:
Tailwind CSS default sm breakpoint screen width
💡 Result:
The default Tailwind CSS sm breakpoint is 40rem (640px).
Citations:
Align the responsive breakpoints: JS uses 768px while Tailwind defaults to sm: 640px.
Between 640px–768px, Tailwind applies sm: styles but the JS threshold keeps isMobile true, causing misaligned layout and potential jumping after checkMobile() runs. Use a shared breakpoint constant or matchMedia to sync JS and Tailwind behavior.
Affects lines: 21, 75, 82, 113
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/home/HeroVisuals.tsx` around lines 21 - 23, The JS breakpoint
in checkMobile currently uses 768px which mismatches Tailwind's sm (640px);
update checkMobile (or replace it) to use window.matchMedia with the same
Tailwind breakpoint (e.g. matchMedia("(max-width: 639px)") or
matchMedia("(min-width: 640px)") logic) so isMobile aligns with Tailwind's sm
breakpoint, and ensure the resize/matchMedia listener added via
window.addEventListener or mediaQuery.addEventListener is cleaned up on unmount;
locate the checkMobile function, the isMobile state, and the
window.addEventListener call to implement this change.
| const phoneRotate = current * -10; | ||
| const phoneTranslateX = Math.min(current * (vw * 0.28), maxTranslateX); | ||
| const phoneTranslateY = current * 1000; | ||
|
|
||
| if (handRef.current) { | ||
| handRef.current.style.opacity = `${handOpacity}`; | ||
| } | ||
|
|
||
| if (phoneRef.current) { | ||
| phoneRef.current.style.transform = `translate3d(${phoneTranslateX}px, ${phoneTranslateY}px, 0) rotate(${phoneRotate}deg)`; |
There was a problem hiding this comment.
The desktop phone animation still misses the scale/depth phase from issue #289.
Line 59 keeps the Z component fixed at 0 and only applies translate/rotate, and there is no scale() term anywhere in the desktop branch. The linked hero spec calls for a drop/scale-to-fit entrance, so this is still a 2D move rather than the requested depth animation.
Also applies to: 133-139
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/home/HeroVisuals.tsx` around lines 50 - 59, The desktop phone
animation is missing the depth/scale phase: compute a scale factor (e.g.,
phoneScale derived from current and clamped to desired min/max) and a Z
translate (phoneTranslateZ derived from current to simulate depth) and include
both in the transform for phoneRef.current; update the transform in the block
that sets phoneRef.current.style.transform (and the analogous block around lines
133-139) to use translate3d(x, y, z) rotate(...) scale(phoneScale) so the phone
both moves in Z and scales during the entrance; keep handRef logic unchanged.
Ensure you reference phoneRef, phoneTranslateX/phoneTranslateY, phoneRotate and
add phoneScale/phoneTranslateZ variables when implementing.
There was a problem hiding this comment.
No the scaling has been removed after discussion and iteration.
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/home/HeroVisuals.tsx`:
- Around line 42-82: The desktop animation useEffect currently runs whenever
isMobile === false; detect the user preference by querying
window.matchMedia('(prefers-reduced-motion: reduce)').matches at the top of the
effect (or use a helper) and if true, skip starting the RAF loop and instead set
the static end-state styles on phoneRef and handRef (the same styles the
animation would produce at rest) so the visual is rendered without motion;
modify the effect that defines animate (and the one at lines ~121-165) to
short-circuit before requestAnimationFrame when prefersReducedMotion is true,
and ensure you still clean up any rafId and do not call requestAnimationFrame in
that case (references: useEffect, isMobile, sectionRef, handRef, phoneRef,
animate, getMarginX).
- Around line 42-82: The RAF loop in useEffect (animate, rafId, current, target,
sectionRef, handRef, phoneRef) currently requeues unconditionally; change it to
gate the loop with an intersection/scroll activity flag and stop rescheduling
when the animation has settled. Add an IntersectionObserver (or reuse a
scroll/lenis flag) to track isIntersecting, compute target as before, then only
call requestAnimationFrame(animate) when isIntersecting OR Math.abs(target -
current) > EPSILON (e.g. 0.001); if both false, cancelAnimationFrame(rafId) and
don't set a new rafId. Also ensure the observer is cleaned up in the effect
cleanup and that rafId is canceled when isMobile changes. This keeps
getBoundingClientRect/style writes off when off-screen and stops the perpetual
RAF once current ≈ target.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ebcd69b8-a81c-40ef-a938-af56856bd854
📒 Files selected for processing (1)
src/components/home/HeroVisuals.tsx
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
Description
Fixes #289
This PR introduces the Hero section for the redesigned landing page, featuring scroll-based animations and a visually engaging, fully responsive layout.
What has been introduced in this PR
Changes done in this PR
##Implementation Details
Additional Info
Please delete options that are not relevant.
How Has This Been Tested?
Please include screenshots below if applicable.
Recording.2026-03-27.173957.mp4
Checklist:
Maintainer Checklist
Summary by CodeRabbit
New Features
Style
Chores