feat: landing page section animations - #142
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughMarketing sections now use Motion animations and a GSAP-powered pinned scroll story. Empowerment cards gain staged transitions and responsive layout updates. Community stories gain directional navigation animations. GSAP is added as a runtime dependency. ChangesMarketing motion updates
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Browser as Browser scroll
participant PracticalPathwaysSection
participant GSAPTimeline
participant TextTrack
Browser->>PracticalPathwaysSection: Provide scroll position
PracticalPathwaysSection->>GSAPTimeline: Scrub pinned timeline
GSAPTimeline->>TextTrack: Translate pathway blocks
GSAPTimeline->>PracticalPathwaysSection: Update pinned section state
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 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 |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (7)
src/features/marketing/components/practical-pathways-section.tsx (3)
99-110: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAlign the comments with the values in the code.
Two comment blocks contradict the code:
- The comment states each transition gets 80% of a viewport and marks
0.8as current, but line 110 computeswindow.innerHeight * 1. The* 1factor is also redundant.- The comment states a direct relationship between scroll and movement, but
scrub: 0.4adds 0.4s of smoothing. A direct relationship requiresscrub: true.♻️ Proposed cleanup
- const transitionDistance = window.innerHeight * 1 + /* Viewport fraction per transition: 1 = slower, 0.8, 0.6 = faster. */ + const transitionDistance = window.innerHeightAlso applies to: 128-131
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/marketing/components/practical-pathways-section.tsx` around lines 99 - 110, Update the transition-distance comments and calculation near transitionDistance to match the intended current value: use the documented 0.8 viewport factor and remove the redundant multiplication by 1. Update the animation configuration near scrub to use scrub: true so it matches the documented direct scroll-to-movement relationship.
112-139: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftConsider disabling the pin for reduced-motion users.
The section pins the viewport and scrubs the text track for every visitor. Users who set
prefers-reduced-motion: reduceget a hijacked scroll. Wrap the timeline ingsap.matchMedia()and register the pinned timeline only for(prefers-reduced-motion: no-preference). The fallback then shows the five blocks in normal document flow.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/marketing/components/practical-pathways-section.tsx` around lines 112 - 139, Update the timeline setup in the practical pathways section to use gsap.matchMedia(), registering the pinned, scrubbed timeline only for "(prefers-reduced-motion: no-preference)". Ensure reduced-motion users skip the GSAP pinning and retain the five blocks in normal document flow, while preserving the existing timeline behavior for other users.
9-52: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove the unused
colorfield.No markup reads
item.color. The fivevar(--hue-N)values are dead data. Delete the field fromItemand from each entry, or apply it in the rendered blocks.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/marketing/components/practical-pathways-section.tsx` around lines 9 - 52, Remove the unused color property from the Item type and all five objects in the items array; leave the remaining item data and rendering behavior unchanged.src/features/marketing/components/stories-from-our-community-section.tsx (2)
59-74: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueType the state as a tuple and rename the setter.
useState([0, 0])infersnumber[], so the destructuring is not type-checked and an extra element would pass. The setter now updates both the index and the direction, so its name is misleading.♻️ Proposed refactor
- const [[activeIndex, direction], setActiveIndex] = useState([0, 0]) + const [[activeIndex, direction], setStoryState] = useState<[number, number]>([0, 0]) const activeStory = stories[activeIndex] function showPreviousStory() { - setActiveIndex(([currentIndex]) => [ + setStoryState(([currentIndex]) => [ currentIndex === 0 ? stories.length - 1 : currentIndex - 1, -1, ]) } function showNextStory() { - setActiveIndex(([currentIndex]) => [ + setStoryState(([currentIndex]) => [ currentIndex === stories.length - 1 ? 0 : currentIndex + 1, 1, ]) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/marketing/components/stories-from-our-community-section.tsx` around lines 59 - 74, Update the state declaration in the stories component to explicitly use a two-element tuple of numbers, preserving type checking for both activeIndex and direction. Rename setActiveIndex to a name that reflects updating both state values, and update all references in showPreviousStory and showNextStory accordingly.
114-123: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAnnounce the story change to assistive technology.
The quote, name, and role swap without any announcement. Screen reader users get no feedback after they activate the navigation buttons. Add
aria-live="polite"andaria-atomic="true"to the content wrapper, or add a visually hidden status region that reports the active story.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/marketing/components/stories-from-our-community-section.tsx` around lines 114 - 123, Update the active story content wrapper in the AnimatePresence block, identified by the motion.div keyed with activeIndex, to include aria-live="polite" and aria-atomic="true" so screen readers announce each story change after navigation.src/features/marketing/components/empowerment-section.tsx (2)
45-72: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftThe fixed delays leave card content invisible for about two seconds.
Each inner
motion.divstarts its ownwhileInViewtimer when that child enters the viewport. The delays of 1.5s, 1.8s, and 2.1s are not chained to the parent slide, so the heading and the fields stay atopacity: 0for that whole period after the card is already visible. Users see empty white panels.Drive the sequence from the parent with variants so the stagger follows the slide instead of a wall-clock delay.
♻️ Proposed refactor sketch
+const panel = { + hidden: { y: 160 }, + shown: { + y: 40, + transition: { duration: 0.7, ease: "easeOut", delayChildren: 0.5, staggerChildren: 0.15 }, + }, +} +const panelItem = { hidden: { opacity: 0 }, shown: { opacity: 1, transition: { duration: 0.5 } } }Then apply
variants={panel}withinitial="hidden"andwhileInView="shown"on the panel, andvariants={panelItem}on each child without its ownwhileInViewortransition.Also applies to: 99-153
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/marketing/components/empowerment-section.tsx` around lines 45 - 72, Replace the fixed per-child delays in the empowerment panel with parent-driven Framer Motion variants. Define or reuse panel and panelItem variants, apply panel with initial="hidden" and whileInView="shown" to the parent container, and apply panelItem to the heading and each Field wrapper; remove their individual whileInView and transition props so the sequence uses parent-controlled staggering.
117-120: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winDrop
fetchPriority="high"from the avatar images.These two avatars are small decorative images inside a card that animates in after the fold. High fetch priority makes them compete with the real LCP resource on the landing page. Remove the attribute and let the default priority apply.
♻️ Proposed fix
- <AvatarImage - fetchPriority="high" - src="/figma-home/M-Anderson.png" - /> + <AvatarImage src="/figma-home/M-Anderson.png" alt="Dr. M. Anderson" />Also applies to: 157-160
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/marketing/components/empowerment-section.tsx` around lines 117 - 120, Remove fetchPriority="high" from both AvatarImage instances in the empowerment section, including the avatars near the visible M-Anderson image and the additionally referenced block, while preserving their src and all other image behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/features/marketing/components/practical-pathways-section.tsx`:
- Line 187: Replace the arbitrary background colors at
src/features/marketing/components/practical-pathways-section.tsx:187-187 and
src/features/marketing/components/empowerment-section.tsx:212-212 with the
repository’s design tokens for the dark section background and deep purple panel
background, respectively; do not introduce new hex values.
- Line 137: Update the ScrollTrigger configuration in the practical pathways
section to disable debug markers by setting markers to false or gating it behind
the development environment, ensuring visitors do not see start/end labels in
production.
- Line 190: Update the heading wrapper div in the practical pathways section to
remove the inert inset-x-0 and top-14 md:top-16 classes, since it uses static
positioning and those offsets have no effect.
- Around line 245-249: Replace the raw img element in the practical pathways
section with Next.js’s Image component, import Image from next/image, and
preserve the existing source, alt text, styling, and displayed aspect ratio
while supplying the sizing information required by the component.
In `@src/features/marketing/components/stories-from-our-community-section.tsx`:
- Around line 91-98: Replace the inline initial, animate, and exit objects in
both AnimatePresence motion.div blocks with shared dynamic variants whose
functions consume the direction custom value, then pass those variants to each
motion.div. Preserve the current opacity, horizontal offsets, and transition
while ensuring exiting elements use the latest direction; remove the
now-ineffective duplicated custom-based inline configuration.
---
Nitpick comments:
In `@src/features/marketing/components/empowerment-section.tsx`:
- Around line 45-72: Replace the fixed per-child delays in the empowerment panel
with parent-driven Framer Motion variants. Define or reuse panel and panelItem
variants, apply panel with initial="hidden" and whileInView="shown" to the
parent container, and apply panelItem to the heading and each Field wrapper;
remove their individual whileInView and transition props so the sequence uses
parent-controlled staggering.
- Around line 117-120: Remove fetchPriority="high" from both AvatarImage
instances in the empowerment section, including the avatars near the visible
M-Anderson image and the additionally referenced block, while preserving their
src and all other image behavior.
In `@src/features/marketing/components/practical-pathways-section.tsx`:
- Around line 99-110: Update the transition-distance comments and calculation
near transitionDistance to match the intended current value: use the documented
0.8 viewport factor and remove the redundant multiplication by 1. Update the
animation configuration near scrub to use scrub: true so it matches the
documented direct scroll-to-movement relationship.
- Around line 112-139: Update the timeline setup in the practical pathways
section to use gsap.matchMedia(), registering the pinned, scrubbed timeline only
for "(prefers-reduced-motion: no-preference)". Ensure reduced-motion users skip
the GSAP pinning and retain the five blocks in normal document flow, while
preserving the existing timeline behavior for other users.
- Around line 9-52: Remove the unused color property from the Item type and all
five objects in the items array; leave the remaining item data and rendering
behavior unchanged.
In `@src/features/marketing/components/stories-from-our-community-section.tsx`:
- Around line 59-74: Update the state declaration in the stories component to
explicitly use a two-element tuple of numbers, preserving type checking for both
activeIndex and direction. Rename setActiveIndex to a name that reflects
updating both state values, and update all references in showPreviousStory and
showNextStory accordingly.
- Around line 114-123: Update the active story content wrapper in the
AnimatePresence block, identified by the motion.div keyed with activeIndex, to
include aria-live="polite" and aria-atomic="true" so screen readers announce
each story change after navigation.
🪄 Autofix
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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 957625e9-176d-4082-b47f-4f2594019eb5
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (4)
package.jsonsrc/features/marketing/components/empowerment-section.tsxsrc/features/marketing/components/practical-pathways-section.tsxsrc/features/marketing/components/stories-from-our-community-section.tsx
|
|
||
| invalidateOnRefresh: true, | ||
|
|
||
| markers: true, |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Remove the ScrollTrigger debug markers.
markers: true renders start/end marker labels on the page. These markers are visible to visitors in production. Set the flag to false, or gate it behind a development check.
🐛 Proposed fix
- markers: true,
+ markers: process.env.NODE_ENV === "development",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| markers: true, | |
| markers: process.env.NODE_ENV === "development", |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/marketing/components/practical-pathways-section.tsx` at line
137, Update the ScrollTrigger configuration in the practical pathways section to
disable debug markers by setting markers to false or gating it behind the
development environment, ensuring visitors do not see start/end labels in
production.
| return ( | ||
| <section | ||
| ref={sectionRef} | ||
| className="relative min-h-screen overflow-hidden bg-[#101827] text-white" |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Arbitrary hex colors bypass the design tokens. Both changed sections hardcode brand surface colors instead of using the repository's tokens. The shared root cause is one missing token mapping for these dark surfaces.
src/features/marketing/components/practical-pathways-section.tsx#L187-L187: replacebg-[#101827]with the design token for the dark section background.src/features/marketing/components/empowerment-section.tsx#L212-L212: replacebg-[#201063]with the design token for the deep purple panel background.
As per coding guidelines: "Do not use arbitrary hex colors or spacing; use the repository's design tokens".
📍 Affects 2 files
src/features/marketing/components/practical-pathways-section.tsx#L187-L187(this comment)src/features/marketing/components/empowerment-section.tsx#L212-L212
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/marketing/components/practical-pathways-section.tsx` at line
187, Replace the arbitrary background colors at
src/features/marketing/components/practical-pathways-section.tsx:187-187 and
src/features/marketing/components/empowerment-section.tsx:212-212 with the
repository’s design tokens for the dark section background and deep purple panel
background, respectively; do not introduce new hex values.
Source: Coding guidelines
| className="relative min-h-screen overflow-hidden bg-[#101827] text-white" | ||
| > | ||
| <div className="relative min-h-screen px-6 py-14 md:px-12 md:py-16 lg:px-20"> | ||
| <div className="inset-x-0 top-14 z-20 px-6 text-center md:top-16"> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
The heading offset classes have no effect.
inset-x-0 and top-14 md:top-16 apply only to a positioned element. This div uses static positioning, so the offsets are ignored and the heading follows normal flow. Add absolute if the heading must overlay the pinned area, or remove the offset classes.
🐛 Proposed fix (remove inert classes)
- <div className="inset-x-0 top-14 z-20 px-6 text-center md:top-16">
+ <div className="z-20 px-6 text-center">📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div className="inset-x-0 top-14 z-20 px-6 text-center md:top-16"> | |
| <div className="z-20 px-6 text-center"> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/marketing/components/practical-pathways-section.tsx` at line
190, Update the heading wrapper div in the practical pathways section to remove
the inert inset-x-0 and top-14 md:top-16 classes, since it uses static
positioning and those offsets have no effect.
| <img | ||
| src="/figma-home/restoring-hope.png" | ||
| alt="Three girls smiling together" | ||
| className="block aspect-[4/3] w-full object-cover" | ||
| /> |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
Restore next/image for the section image.
The raw <img> element skips Next.js image optimization, responsive srcset, and intrinsic size reservation. The ESLint rule @next/next/no-img-element also flags this element, which adds a new lint warning.
♻️ Proposed fix
- <img
- src="/figma-home/restoring-hope.png"
- alt="Three girls smiling together"
- className="block aspect-[4/3] w-full object-cover"
- />
+ <Image
+ src="/figma-home/restoring-hope.png"
+ alt="Three girls smiling together"
+ width={520}
+ height={390}
+ sizes="(min-width: 768px) 520px, 100vw"
+ className="block aspect-[4/3] w-full object-cover"
+ />Add the import at the top of the file:
import Image from "next/image"As per coding guidelines: "Run npm run lint and do not introduce new lint issues".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <img | |
| src="/figma-home/restoring-hope.png" | |
| alt="Three girls smiling together" | |
| className="block aspect-[4/3] w-full object-cover" | |
| /> | |
| <Image | |
| src="/figma-home/restoring-hope.png" | |
| alt="Three girls smiling together" | |
| width={520} | |
| height={390} | |
| sizes="(min-width: 768px) 520px, 100vw" | |
| className="block aspect-[4/3] w-full object-cover" | |
| /> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/marketing/components/practical-pathways-section.tsx` around
lines 245 - 249, Replace the raw img element in the practical pathways section
with Next.js’s Image component, import Image from next/image, and preserve the
existing source, alt text, styling, and displayed aspect ratio while supplying
the sizing information required by the component.
Source: Coding guidelines
| <AnimatePresence mode="wait" custom={direction} initial={false}> | ||
| <motion.div | ||
| key={activeIndex} | ||
| custom={direction} | ||
| initial={{ opacity: 0, x: direction * 48 }} | ||
| animate={{ opacity: 1, x: 0 }} | ||
| exit={{ opacity: 0, x: direction * -48 }} | ||
| transition={{ duration: 0.3, ease: "easeOut" }} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
custom has no effect here, so the exit direction is stale.
custom only feeds dynamic variants. With inline object values, Motion reads exit from the props the element had on its last render. An exiting element is already out of the React tree, so it cannot receive the new direction. The first navigation after a direction change therefore exits toward the wrong side. The Motion documentation states that when a component has been removed from the React tree, its props can no longer be updated, and AnimatePresence's custom prop passes new data down into exiting components, which requires variant functions.
Move the values into dynamic variants. This also removes the duplicated animation config between the two blocks.
🐛 Proposed fix
+const storyVariants = {
+ enter: (direction: number) => ({ opacity: 0, x: direction * 48 }),
+ center: { opacity: 1, x: 0 },
+ exit: (direction: number) => ({ opacity: 0, x: direction * -48 }),
+}
+const storyTransition = { duration: 0.3, ease: "easeOut" } as const <AnimatePresence mode="wait" custom={direction} initial={false}>
<motion.div
key={activeIndex}
custom={direction}
- initial={{ opacity: 0, x: direction * 48 }}
- animate={{ opacity: 1, x: 0 }}
- exit={{ opacity: 0, x: direction * -48 }}
- transition={{ duration: 0.3, ease: "easeOut" }}
+ variants={storyVariants}
+ initial="enter"
+ animate="center"
+ exit="exit"
+ transition={storyTransition}
className="relative min-h-[340px] overflow-hidden rounded-3xl bg-ma-bg sm:min-h-[460px] lg:h-[560px]"
>Apply the same change to the second AnimatePresence block.
Also applies to: 114-121
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/marketing/components/stories-from-our-community-section.tsx`
around lines 91 - 98, Replace the inline initial, animate, and exit objects in
both AnimatePresence motion.div blocks with shared dynamic variants whose
functions consume the direction custom value, then pass those variants to each
motion.div. Preserve the current opacity, horizontal offsets, and transition
while ensuring exiting elements use the latest direction; remove the
now-ineffective duplicated custom-based inline configuration.
Adds scroll-stepper to the practical pathways section (GSAP ScrollTrigger pin + scrub), card animations in the empowerment section (motion whileInView), and directional fade/slide transitions for community stories (motion AnimatePresence).
Summary by CodeRabbit