Skip to content

feat: add count-up animation to stats on scroll#355

Merged
Sachinchaurasiya360 merged 1 commit into
Sachinchaurasiya360:mainfrom
vv-verse:feat/count-up-animation-cta-stats
May 21, 2026
Merged

feat: add count-up animation to stats on scroll#355
Sachinchaurasiya360 merged 1 commit into
Sachinchaurasiya360:mainfrom
vv-verse:feat/count-up-animation-cta-stats

Conversation

@vv-verse
Copy link
Copy Markdown

@vv-verse vv-verse commented May 21, 2026

Description

Added a smooth count-up animation to the CTA stats section that triggers when the section scrolls into view.

The stats now animate dynamically instead of displaying static values, improving user engagement and making the landing page feel more interactive and polished.


Changes Made

  • Added reusable StatItem component
  • Implemented custom useCountUp hook
  • Added viewport-based animation trigger using useInView
  • Added smooth easing animation for stat counting
  • Preserved existing responsive layout and styling

Files Changed

  • client/src/components/CTASection.tsx

Issue

Fixes #354


Testing

  • Tested locally in development mode
  • Verified animation triggers correctly on scroll
  • Verified responsive layout remains intact
  • Verified dark mode styling remains consistent

Note

The repository currently contains unrelated existing TypeScript/build issues in other files that were not introduced by this PR. The CTA section enhancement itself works correctly and has been tested successfully in local development mode.

Before

image

After

after.Recording.-.Trim.mp4

Summary by CodeRabbit

  • New Features
    • Animated statistics: Stats in the CTA section now smoothly count up to their final values when the section becomes visible, creating a more dynamic user experience.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

📝 Walkthrough

Walkthrough

CTASection replaces static numeric stats displays with animated counters. A new useCountUp hook provides visibility-triggered number animation via requestAnimationFrame, while formatNumber formats large numbers and StatItem renders individual stat blocks. Framer Motion's useInView detects when the stats container enters the viewport, triggering the count-up sequence once.

Changes

CTA Stats Count-up Animation

Layer / File(s) Summary
Count-up animation utilities and StatItem component
client/src/components/CTASection.tsx (lines 1–63)
Imports useInView from Framer Motion; introduces useCountUp hook that increments numeric state on each requestAnimationFrame when visibility flag is true; adds formatNumber utility to format large numbers with "+" suffix; defines StatItem component that renders label, icon, and animated counter value; updates CTASection to create statsRef and isInView hook for triggering.
CTASection stats rendering with animation
client/src/components/CTASection.tsx (lines 121–134)
Replaces hardcoded static stat values with ref-attached container and three StatItem components configured with the original target totals (54230, 8900, 1247) and labels, enabling the visible-triggered count-up animation.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested labels

type:design

A rabbit hops through stats so bright,
Numbers dance into the light,
From zero they leap and climb,
A counting cadence, perfectly timed,
Now engagement takes flight! 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and concisely summarizes the main change: adding count-up animation to stats that triggers on scroll, which directly matches the primary objective of the linked issue #354.
Linked Issues check ✅ Passed The changes successfully implement all coding requirements from issue #354: added count-up animation to CTA stats, implemented custom useCountUp hook, added viewport-based triggering with useInView, and preserved existing styling and responsive layout.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the CTASection component and the implementation of count-up animation functionality, with no unrelated modifications introduced outside the stated objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

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

@vv-verse vv-verse changed the title feat: add count-up animation to stats on scroll #354 feat: add count-up animation to stats on scroll May 21, 2026
Copy link
Copy Markdown
Contributor

@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

🤖 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 `@client/src/components/CTASection.tsx`:
- Around line 9-25: The animation effect in useEffect incorrectly checks
startTime with a falsy check and never cancels the RAF; change the condition
inside the step function to use startTime === null (or startTime == null) so a 0
timestamp isn't treated as unset, capture the requestAnimationFrame id returned
when calling requestAnimationFrame(step) into a variable (e.g., rafId) and call
cancelAnimationFrame(rafId) in the effect cleanup, and ensure you also guard
against continuing to call requestAnimationFrame after unmount by using that
rafId or a mounted flag; update references to startTime, step,
requestAnimationFrame and setCount accordingly.
🪄 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: 9efe6a1e-92e3-449f-a2a6-1e8182e607bf

📥 Commits

Reviewing files that changed from the base of the PR and between b53b78e and 36a8a70.

📒 Files selected for processing (1)
  • client/src/components/CTASection.tsx

Comment on lines +9 to +25
useEffect(() => {
if (!isInView) return;

let startTime: number | null = null;
const startValue = 0;

const step = (timestamp: number) => {
if (!startTime) startTime = timestamp;
const progress = Math.min((timestamp - startTime) / duration, 1);
// easeOutQuart for smooth deceleration
const eased = 1 - Math.pow(1 - progress, 4);
setCount(Math.floor(eased * (target - startValue) + startValue));
if (progress < 1) requestAnimationFrame(step);
};

requestAnimationFrame(step);
}, [isInView, target, duration]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Cancel RAF on cleanup and use a null check for startTime.

On Line 16, if (!startTime) should be startTime === null; a 0 timestamp is falsy.
Also, the effect never cancels the pending requestAnimationFrame, so it can keep running after unmount.

Suggested fix
 useEffect(() => {
   if (!isInView) return;

   let startTime: number | null = null;
+  let frameId: number | null = null;
   const startValue = 0;

   const step = (timestamp: number) => {
-    if (!startTime) startTime = timestamp;
+    if (startTime === null) startTime = timestamp;
     const progress = Math.min((timestamp - startTime) / duration, 1);
     // easeOutQuart for smooth deceleration
     const eased = 1 - Math.pow(1 - progress, 4);
     setCount(Math.floor(eased * (target - startValue) + startValue));
-    if (progress < 1) requestAnimationFrame(step);
+    if (progress < 1) frameId = requestAnimationFrame(step);
   };

-  requestAnimationFrame(step);
+  frameId = requestAnimationFrame(step);
+  return () => {
+    if (frameId !== null) cancelAnimationFrame(frameId);
+  };
 }, [isInView, target, duration]);
📝 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.

Suggested change
useEffect(() => {
if (!isInView) return;
let startTime: number | null = null;
const startValue = 0;
const step = (timestamp: number) => {
if (!startTime) startTime = timestamp;
const progress = Math.min((timestamp - startTime) / duration, 1);
// easeOutQuart for smooth deceleration
const eased = 1 - Math.pow(1 - progress, 4);
setCount(Math.floor(eased * (target - startValue) + startValue));
if (progress < 1) requestAnimationFrame(step);
};
requestAnimationFrame(step);
}, [isInView, target, duration]);
useEffect(() => {
if (!isInView) return;
let startTime: number | null = null;
let frameId: number | null = null;
const startValue = 0;
const step = (timestamp: number) => {
if (startTime === null) startTime = timestamp;
const progress = Math.min((timestamp - startTime) / duration, 1);
// easeOutQuart for smooth deceleration
const eased = 1 - Math.pow(1 - progress, 4);
setCount(Math.floor(eased * (target - startValue) + startValue));
if (progress < 1) frameId = requestAnimationFrame(step);
};
frameId = requestAnimationFrame(step);
return () => {
if (frameId !== null) cancelAnimationFrame(frameId);
};
}, [isInView, target, duration]);
🤖 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 `@client/src/components/CTASection.tsx` around lines 9 - 25, The animation
effect in useEffect incorrectly checks startTime with a falsy check and never
cancels the RAF; change the condition inside the step function to use startTime
=== null (or startTime == null) so a 0 timestamp isn't treated as unset, capture
the requestAnimationFrame id returned when calling requestAnimationFrame(step)
into a variable (e.g., rafId) and call cancelAnimationFrame(rafId) in the effect
cleanup, and ensure you also guard against continuing to call
requestAnimationFrame after unmount by using that rafId or a mounted flag;
update references to startTime, step, requestAnimationFrame and setCount
accordingly.

@Sachinchaurasiya360 Sachinchaurasiya360 added enhancement New feature or request good first issue Good for newcomers level:beginner Good for first-time contributors gssoc:approved Approved for GSSoC scoring labels May 21, 2026
@Sachinchaurasiya360 Sachinchaurasiya360 merged commit 17bbbfc into Sachinchaurasiya360:main May 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request good first issue Good for newcomers gssoc:approved Approved for GSSoC scoring level:beginner Good for first-time contributors

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Add count-up animation to CTA stats on scroll

2 participants