Skip to content

feat: About page graph typography, grid & animation improvements (closes #676)#677

Merged
Zahnentferner merged 1 commit intoAOSSIE-Org:mainfrom
ankitkr104:feature/about-graph-improvements
Mar 16, 2026
Merged

feat: About page graph typography, grid & animation improvements (closes #676)#677
Zahnentferner merged 1 commit intoAOSSIE-Org:mainfrom
ankitkr104:feature/about-graph-improvements

Conversation

@ankitkr104
Copy link
Contributor

@ankitkr104 ankitkr104 commented Mar 16, 2026

Addressed Issues

Fixes #676

This PR improves the visual clarity, readability, and animation behavior of the line chart displayed on the About page. The changes focus on improving typography, grid visibility, and implementing a smoother chart animation that aligns better with the timeline-style data visualization.

Recordings:

Aossie.web.mp4

Changes Implemented

1. Axis Tick Typography Improvement
Previously, the axis tick labels (years on the x-axis and project counts on the y-axis) were rendered using font.weight: 'bold' in the Chart.js configuration. This caused the tick labels to appear visually heavy and reduced readability.
This PR updates the font weight to normal, making the tick labels lighter and more consistent with the rest of the UI typography.

2. Grid Line Visibility Enhancement
The grid lines were previously faint and difficult to notice, especially on larger screens. To improve visual structure and readability of the chart:

The lineWidth for both x-axis and y-axis grid lines has been increased to 3.
The existing grid color rgba(254,212,30,0.25) was kept unchanged to preserve the current design language.

This change improves the ability to visually follow data points across the chart.

3. Left-to-Right Line Drawing Animation
The default Chart.js animation draws the line from bottom to top, which does not align well with a timeline-style dataset.
To improve the storytelling of the graph, a custom animation plugin was implemented that:

Clips the canvas progressively
Animates the chart line from left to right
Runs over 4 seconds
Uses an easeInOutQuart easing curve for smooth motion

The animation is triggered when the chart enters the viewport using useInView, ensuring the animation only runs when visible to the user.

4. Fixed Axis Labels Hidden During Animation
Initially, the clipping logic used beforeDraw, which unintentionally clipped axis tick labels along with the dataset.
This issue was resolved by moving the clipping logic to beforeDatasetsDraw, ensuring that:

Only the dataset (line and fill) is clipped and animated
Axis labels remain visible at all times

5. Improved "Year" Label Visibility
The "Year" label below the chart had poor visibility due to low contrast:

text-zinc-400 dark:text-zinc-600
This has been updated to:
text-zinc-600 dark:text-zinc-300 font-bold
This improves contrast in both light and dark modes, making the label easier to read.

AI Usage Disclosure:

We encourage contributors to use AI tools responsibly when creating Pull Requests. While AI can be a valuable aid, it is essential to ensure that your contributions meet the task requirements, build successfully, include relevant tests, and pass all linters. Submissions that do not meet these standards may be closed without warning to maintain the quality and integrity of the project. Please take the time to understand the changes you are proposing and their impact. AI slop is strongly discouraged and may lead to banning and blocking. Do not spam our repos with AI slop.

Check one of the checkboxes below:

  • This PR does not contain AI-generated code at all.
  • This PR contains AI-generated code. I have read the AI Usage Policy and this PR complies with this policy. I have tested the code locally and I am responsible for it.

I have used the following AI models and tools: TODO

Checklist

  • My PR addresses a single issue, fixes a single bug or makes a single improvement.
  • My code follows the project's code style and conventions
  • If applicable, I have made corresponding changes or additions to the documentation
  • If applicable, I have made corresponding changes or additions to tests
  • My changes generate no new warnings or errors
  • I have joined the Discord server and I will share a link to this PR with the project maintainers there
  • I have read the Contribution Guidelines
  • Once I submit my PR, CodeRabbit AI will automatically review it and I will address CodeRabbit's comments.
  • I have filled this PR template completely and carefully, and I understand that my PR may be closed without review otherwise.

Summary by CodeRabbit

Release Notes

  • New Features
    • Enhanced charts with gradient fills and progressive reveal animations for better visual impact
    • Smooth number animations for statistics cards that trigger when sections come into view
    • Improved dark mode support with adaptive chart styling and colors
    • Updated stats cards with enhanced hover effects, borders, and shadows for better interactivity

@coderabbitai
Copy link

coderabbitai bot commented Mar 16, 2026

📝 Walkthrough

Walkthrough

Enhanced the About page chart with visual improvements and animations. Added left-to-right line animation via custom Chart.js plugin, AnimatedNumber component for stats, dark mode detection using MutationObserver, gradient-filled areas, bold grid lines, and updated axis styling. Maintained existing data fetching logic.

Changes

Cohort / File(s) Summary
About Page Chart Enhancements
src/app/about/page.jsx
Reworked chart implementation with Legend and gradient registration; added custom left-to-right animation plugin with canvas clipping; implemented AnimatedNumber component for animated stats; added dark mode detection via MutationObserver for dynamic axis label colors; enhanced chart data with 2025 labels, gradient fills, and improved styling; refactored stats cards layout with hover effects, borders, and shadows.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • Zahnentferner

Poem

🐰✨ A chart that dances left to right,
Numbers blooming into sight,
Dark mode whispers through the night,
Stats animate with pure delight,
Our About page now shines so bright!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The AI-generated summary mentions AnimatedNumber component, dynamic dark mode detection, and stats cards rework, which extend beyond the stated objectives of typography, grid, and animation improvements for the line chart. Clarify whether AnimatedNumber, dark mode detection, and stats card changes are part of issue #676 or represent scope creep; if unrelated, isolate them into a separate pull request.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title accurately and specifically summarizes the main changes: typography improvements, grid enhancements, and animation additions to the About page graph, with clear scope.
Linked Issues check ✅ Passed The pull request addresses all five key coding requirements from issue #676: removing bold from axis labels, increasing grid lineWidth, implementing left-to-right animation via canvas clipping, fixing axis label visibility, and improving 'Year' label styling.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Copy link

@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: 4

🧹 Nitpick comments (1)
src/app/about/page.jsx (1)

258-265: Consider sourcing all stats from the API for consistency.

years, projects, and contributors come from the API, but repos, mentors, and community members are hardcoded. This creates a maintenance burden and potential for data drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/about/page.jsx` around lines 258 - 265, The hardcoded metrics in the
JSX map should be sourced from the API-owned stats object instead of literals;
update the code that builds/receives the stats object so it includes repos,
mentors, and communityMembers (or similar keys), then replace the hardcoded
values in the array (currently using 203, '88', '7500+') with stats.repos,
stats.mentors, and stats.communityMembers in the mapped array; also add safe
fallbacks (e.g., 0 or '—') where stats may be undefined and update any
state/prop initialization or type definitions that construct stats to include
these new fields so rendering remains stable.
🤖 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/about/page.jsx`:
- Around line 64-79: The effect that re-triggers chart animation (useEffect)
starts a requestAnimationFrame loop via animate but does not cancel it on
unmount or when graphInView changes; move the drawProgress ref declaration
(drawProgress) next to this useEffect for locality and add a cleanup that
cancels the scheduled animationFrame (store the id from requestAnimationFrame
and call cancelAnimationFrame in the returned cleanup). Ensure
chartRef.update('none') and setChartKey usages remain unchanged but the animate
loop is properly torn down to avoid callbacks running after unmount or
dependency changes.
- Around line 29-41: The useEffect animation using requestAnimationFrame
(function step) can continue after unmount and call setDisplay; update the
effect to store the request id (e.g., let rafId) and an "aborted" or "mounted"
flag (ref/variable) and in the cleanup return function call
cancelAnimationFrame(rafId) and set the flag so step exits early (avoid calling
setDisplay when aborted/unmounted); reference the existing useEffect, step,
requestAnimationFrame, setDisplay, inView, target, and duration to locate and
fix the logic.
- Around line 52-55: The initial graphData fallback has mismatched arrays
(graphData.labels length 9 vs graphData.data length 10); update the graphData
object so labels and data are the same length by either adding the missing year
to graphData.labels (e.g., include the extra year that corresponds to the extra
value) or removing the extra numeric entry from graphData.data so both arrays
align; edit the graphData constant in page.jsx (the graphData -> labels and data
fields) to ensure consistent lengths.
- Around line 113-114: Replace the hardcoded chart constants so the chart uses
the fetched API response: stop using rawData and the hardcoded labels and
instead read graphData.labels and graphData.data (or graphData?.labels /
graphData?.data) when rendering the chart; update the variables referenced in
the chart component (rawData, labels) to use graphData values with a safe
fallback (e.g., empty arrays) so chart rendering won’t break if graphData is
undefined; ensure the component that originally declared rawData/labels now maps
to graphData from your fetch (refer to the graphData variable and wherever
rawData/labels are passed into the Chart).

---

Nitpick comments:
In `@src/app/about/page.jsx`:
- Around line 258-265: The hardcoded metrics in the JSX map should be sourced
from the API-owned stats object instead of literals; update the code that
builds/receives the stats object so it includes repos, mentors, and
communityMembers (or similar keys), then replace the hardcoded values in the
array (currently using 203, '88', '7500+') with stats.repos, stats.mentors, and
stats.communityMembers in the mapped array; also add safe fallbacks (e.g., 0 or
'—') where stats may be undefined and update any state/prop initialization or
type definitions that construct stats to include these new fields so rendering
remains stable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b4369fae-41be-464e-baa5-42c6417b2df9

📥 Commits

Reviewing files that changed from the base of the PR and between 84c46fd and ddd14dd.

📒 Files selected for processing (1)
  • src/app/about/page.jsx

Comment on lines +29 to +41
useEffect(() => {
if (!inView) return;
const numeric = parseInt(String(target).replace(/\D/g, ''), 10) || 0;
let start = null;
const step = (ts) => {
if (!start) start = ts;
const progress = Math.min((ts - start) / duration, 1);
const ease = 1 - Math.pow(1 - progress, 3);
setDisplay(Math.floor(ease * numeric));
if (progress < 1) requestAnimationFrame(step);
};
requestAnimationFrame(step);
}, [inView, target, duration]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing cleanup for requestAnimationFrame can cause state update on unmounted component.

If the component unmounts during the animation, setDisplay will still be called, potentially causing a React warning. Add a cleanup mechanism using a ref or abort flag.

🛡️ Proposed fix
   useEffect(() => {
     if (!inView) return;
     const numeric = parseInt(String(target).replace(/\D/g, ''), 10) || 0;
     let start = null;
+    let rafId;
+    let cancelled = false;
     const step = (ts) => {
+      if (cancelled) return;
       if (!start) start = ts;
       const progress = Math.min((ts - start) / duration, 1);
       const ease = 1 - Math.pow(1 - progress, 3);
       setDisplay(Math.floor(ease * numeric));
-      if (progress < 1) requestAnimationFrame(step);
+      if (progress < 1) rafId = requestAnimationFrame(step);
     };
-    requestAnimationFrame(step);
+    rafId = requestAnimationFrame(step);
+    return () => {
+      cancelled = true;
+      cancelAnimationFrame(rafId);
+    };
   }, [inView, 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 (!inView) return;
const numeric = parseInt(String(target).replace(/\D/g, ''), 10) || 0;
let start = null;
const step = (ts) => {
if (!start) start = ts;
const progress = Math.min((ts - start) / duration, 1);
const ease = 1 - Math.pow(1 - progress, 3);
setDisplay(Math.floor(ease * numeric));
if (progress < 1) requestAnimationFrame(step);
};
requestAnimationFrame(step);
}, [inView, target, duration]);
useEffect(() => {
if (!inView) return;
const numeric = parseInt(String(target).replace(/\D/g, ''), 10) || 0;
let start = null;
let rafId;
let cancelled = false;
const step = (ts) => {
if (cancelled) return;
if (!start) start = ts;
const progress = Math.min((ts - start) / duration, 1);
const ease = 1 - Math.pow(1 - progress, 3);
setDisplay(Math.floor(ease * numeric));
if (progress < 1) rafId = requestAnimationFrame(step);
};
rafId = requestAnimationFrame(step);
return () => {
cancelled = true;
cancelAnimationFrame(rafId);
};
}, [inView, target, duration]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/about/page.jsx` around lines 29 - 41, The useEffect animation using
requestAnimationFrame (function step) can continue after unmount and call
setDisplay; update the effect to store the request id (e.g., let rafId) and an
"aborted" or "mounted" flag (ref/variable) and in the cleanup return function
call cancelAnimationFrame(rafId) and set the flag so step exits early (avoid
calling setDisplay when aborted/unmounted); reference the existing useEffect,
step, requestAnimationFrame, setDisplay, inView, target, and duration to locate
and fix the logic.

Comment on lines +52 to +55
graphData: {
labels: ['2016', '2017', '2018', '2019', '2020', '2021', '2022', '2023', '2024'],
data: [4, 8, 12, 9, 9, 11, 8, 6, 18, 22],
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Array length mismatch in initial state: 9 labels vs 10 data points.

The initial graphData has 9 labels (2016–2024) but 10 data values. While this fallback state appears unused since the chart uses hardcoded rawData/labels at lines 113-114, keeping it consistent avoids confusion.

🔧 Suggested fix
     graphData: {
-      labels: ['2016', '2017', '2018', '2019', '2020', '2021', '2022', '2023', '2024'],
+      labels: ['2016', '2017', '2018', '2019', '2020', '2021', '2022', '2023', '2024', '2025'],
       data: [4, 8, 12, 9, 9, 11, 8, 6, 18, 22],
     },
📝 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
graphData: {
labels: ['2016', '2017', '2018', '2019', '2020', '2021', '2022', '2023', '2024'],
data: [4, 8, 12, 9, 9, 11, 8, 6, 18, 22],
},
graphData: {
labels: ['2016', '2017', '2018', '2019', '2020', '2021', '2022', '2023', '2024', '2025'],
data: [4, 8, 12, 9, 9, 11, 8, 6, 18, 22],
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/about/page.jsx` around lines 52 - 55, The initial graphData fallback
has mismatched arrays (graphData.labels length 9 vs graphData.data length 10);
update the graphData object so labels and data are the same length by either
adding the missing year to graphData.labels (e.g., include the extra year that
corresponds to the extra value) or removing the extra numeric entry from
graphData.data so both arrays align; edit the graphData constant in page.jsx
(the graphData -> labels and data fields) to ensure consistent lengths.

Comment on lines +64 to +79
// re-trigger chart animation each time it enters view
useEffect(() => {
if (!graphInView) return;
setChartKey((k) => k + 1);
drawProgress.current = 0;
const start = performance.now();
const duration = 4000;
const easeInOutQuart = (t) => t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2;
const animate = (now) => {
const t = Math.min((now - start) / duration, 1);
drawProgress.current = easeInOutQuart(t);
chartRef.current?.update('none');
if (t < 1) requestAnimationFrame(animate);
};
requestAnimationFrame(animate);
}, [graphInView]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing cleanup for chart animation requestAnimationFrame.

Similar to AnimatedNumber, if the component unmounts during animation, the callback will continue executing. Also, drawProgress is referenced here but declared later at line 144, which is valid JavaScript but reduces readability.

🛡️ Proposed fix
   useEffect(() => {
     if (!graphInView) return;
     setChartKey((k) => k + 1);
     drawProgress.current = 0;
     const start = performance.now();
     const duration = 4000;
     const easeInOutQuart = (t) => t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2;
+    let rafId;
+    let cancelled = false;
     const animate = (now) => {
+      if (cancelled) return;
       const t = Math.min((now - start) / duration, 1);
       drawProgress.current = easeInOutQuart(t);
       chartRef.current?.update('none');
-      if (t < 1) requestAnimationFrame(animate);
+      if (t < 1) rafId = requestAnimationFrame(animate);
     };
-    requestAnimationFrame(animate);
+    rafId = requestAnimationFrame(animate);
+    return () => {
+      cancelled = true;
+      cancelAnimationFrame(rafId);
+    };
   }, [graphInView]);

Consider moving the drawProgress ref declaration (line 144) closer to this effect for better code locality.

📝 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
// re-trigger chart animation each time it enters view
useEffect(() => {
if (!graphInView) return;
setChartKey((k) => k + 1);
drawProgress.current = 0;
const start = performance.now();
const duration = 4000;
const easeInOutQuart = (t) => t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2;
const animate = (now) => {
const t = Math.min((now - start) / duration, 1);
drawProgress.current = easeInOutQuart(t);
chartRef.current?.update('none');
if (t < 1) requestAnimationFrame(animate);
};
requestAnimationFrame(animate);
}, [graphInView]);
// re-trigger chart animation each time it enters view
useEffect(() => {
if (!graphInView) return;
setChartKey((k) => k + 1);
drawProgress.current = 0;
const start = performance.now();
const duration = 4000;
const easeInOutQuart = (t) => t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2;
let rafId;
let cancelled = false;
const animate = (now) => {
if (cancelled) return;
const t = Math.min((now - start) / duration, 1);
drawProgress.current = easeInOutQuart(t);
chartRef.current?.update('none');
if (t < 1) rafId = requestAnimationFrame(animate);
};
rafId = requestAnimationFrame(animate);
return () => {
cancelled = true;
cancelAnimationFrame(rafId);
};
}, [graphInView]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/about/page.jsx` around lines 64 - 79, The effect that re-triggers
chart animation (useEffect) starts a requestAnimationFrame loop via animate but
does not cancel it on unmount or when graphInView changes; move the drawProgress
ref declaration (drawProgress) next to this useEffect for locality and add a
cleanup that cancels the scheduled animationFrame (store the id from
requestAnimationFrame and call cancelAnimationFrame in the returned cleanup).
Ensure chartRef.update('none') and setChartKey usages remain unchanged but the
animate loop is properly torn down to avoid callbacks running after unmount or
dependency changes.

Comment on lines +113 to +114
const rawData = [4, 8, 12, 9, 9, 11, 8, 6, 18, 22];
const labels = ['2016', '2017', '2018', '2019', '2020', '2021', '2022', '2023', '2024', '2025'];
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fetched graphData from API is unused; chart uses hardcoded data instead.

The API response contains dynamic graphData with labels and data (see lines 95-96), but the chart uses hardcoded rawData and labels constants. This means the chart won't reflect actual project counts from the API.

🔧 Suggested fix to use API data
-  const rawData = [4, 8, 12, 9, 9, 11, 8, 6, 18, 22];
-  const labels  = ['2016', '2017', '2018', '2019', '2020', '2021', '2022', '2023', '2024', '2025'];
+  const { labels, data: rawData } = stats.graphData;

   const data = {
     labels,
     datasets: [
       {
         label: 'Completed Projects',
         data: rawData,
📝 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
const rawData = [4, 8, 12, 9, 9, 11, 8, 6, 18, 22];
const labels = ['2016', '2017', '2018', '2019', '2020', '2021', '2022', '2023', '2024', '2025'];
const { labels, data: rawData } = stats.graphData;
const data = {
labels,
datasets: [
{
label: 'Completed Projects',
data: rawData,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/about/page.jsx` around lines 113 - 114, Replace the hardcoded chart
constants so the chart uses the fetched API response: stop using rawData and the
hardcoded labels and instead read graphData.labels and graphData.data (or
graphData?.labels / graphData?.data) when rendering the chart; update the
variables referenced in the chart component (rawData, labels) to use graphData
values with a safe fallback (e.g., empty arrays) so chart rendering won’t break
if graphData is undefined; ensure the component that originally declared
rawData/labels now maps to graphData from your fetch (refer to the graphData
variable and wherever rawData/labels are passed into the Chart).

@Zahnentferner Zahnentferner merged commit d0a9823 into AOSSIE-Org:main Mar 16, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]: About Page Graph — Typography, Grid & Animation Improvements

2 participants