chore: add chart.js and react-chartjs-2 dependencies, update skills and work timeline in content options, and enhance about page styling

This commit is contained in:
Harivansh Rathi 2025-03-21 11:42:30 -04:00
parent 0383f88ba2
commit eb6610e7d0
9 changed files with 544 additions and 87 deletions

30
package-lock.json generated
View file

@ -12,11 +12,13 @@
"@types/node": "^22.13.10",
"@types/react": "^19.0.11",
"bootstrap": "^5.2.3",
"chart.js": "^4.4.8",
"clsx": "^2.1.1",
"emailjs-com": "^3.2.0",
"framer-motion": "^12.5.0",
"react": "^18.2.0",
"react-bootstrap": "^2.7.0",
"react-chartjs-2": "^5.3.0",
"react-dom": "^18.2.0",
"react-helmet-async": "^1.0.7",
"react-icons": "^4.12.0",
@ -3179,6 +3181,12 @@
"@jridgewell/sourcemap-codec": "1.4.14"
}
},
"node_modules/@kurkle/color": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
"license": "MIT"
},
"node_modules/@leichtgewicht/ip-codec": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
@ -5533,6 +5541,18 @@
"node": ">=10"
}
},
"node_modules/chart.js": {
"version": "4.4.8",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.8.tgz",
"integrity": "sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==",
"license": "MIT",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
"engines": {
"pnpm": ">=8"
}
},
"node_modules/check-types": {
"version": "11.2.2",
"resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.2.tgz",
@ -13450,6 +13470,16 @@
}
}
},
"node_modules/react-chartjs-2": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz",
"integrity": "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==",
"license": "MIT",
"peerDependencies": {
"chart.js": "^4.1.1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-dev-utils": {
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",

View file

@ -7,11 +7,13 @@
"@types/node": "^22.13.10",
"@types/react": "^19.0.11",
"bootstrap": "^5.2.3",
"chart.js": "^4.4.8",
"clsx": "^2.1.1",
"emailjs-com": "^3.2.0",
"framer-motion": "^12.5.0",
"react": "^18.2.0",
"react-bootstrap": "^2.7.0",
"react-chartjs-2": "^5.3.0",
"react-dom": "^18.2.0",
"react-helmet-async": "^1.0.7",
"react-icons": "^4.12.0",

View file

@ -0,0 +1,140 @@
import React, { useEffect, useState } from 'react';
import { Chart as ChartJS, RadialLinearScale, PointElement, LineElement, Filler, Tooltip, Legend } from 'chart.js';
import { Radar } from 'react-chartjs-2';
import './style.css';
// Register ChartJS components
ChartJS.register(RadialLinearScale, PointElement, LineElement, Filler, Tooltip, Legend);
const SkillsRadar = ({ skills }) => {
const [chartData, setChartData] = useState({
labels: [],
datasets: []
});
// Set dark mode detection
const [isDarkMode, setIsDarkMode] = useState(
document.documentElement.getAttribute('data-theme') !== 'light'
);
useEffect(() => {
// Watch for theme changes
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === 'data-theme') {
setIsDarkMode(document.documentElement.getAttribute('data-theme') !== 'light');
}
});
});
observer.observe(document.documentElement, { attributes: true });
return () => observer.disconnect();
}, []);
useEffect(() => {
if (skills && skills.length > 0) {
// Get accent color from CSS vars
const accentColor = 'rgba(204, 0, 0, 0.7)'; // Using the red accent
// Format data for the radar chart
const data = {
labels: skills.map(skill => skill.name),
datasets: [
{
label: 'Skill Proficiency',
data: skills.map(skill => skill.value),
backgroundColor: isDarkMode
? 'rgba(204, 0, 0, 0.2)' // Slight red tint
: 'rgba(204, 0, 0, 0.1)',
borderColor: accentColor,
borderWidth: 2,
pointBackgroundColor: isDarkMode
? 'rgba(255, 255, 255, 1)'
: 'rgba(0, 0, 0, 1)',
pointBorderColor: accentColor,
pointHoverBackgroundColor: accentColor,
pointHoverBorderColor: isDarkMode ? '#fff' : '#000',
},
],
};
setChartData(data);
}
}, [skills, isDarkMode]);
// Chart options
const options = {
scales: {
r: {
angleLines: {
color: isDarkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)',
},
grid: {
color: isDarkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)',
},
pointLabels: {
color: isDarkMode ? 'rgba(255, 255, 255, 0.9)' : 'rgba(0, 0, 0, 0.9)',
font: {
size: 16,
family: 'Raleway',
weight: '600'
},
},
ticks: {
backdropColor: 'transparent',
color: isDarkMode ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.7)',
showLabelBackdrop: false,
beginAtZero: true,
max: 100,
stepSize: 20,
font: {
size: 12
}
},
},
},
plugins: {
legend: {
display: true,
position: 'top',
labels: {
color: isDarkMode ? '#fff' : '#000',
font: {
size: 14,
family: 'Raleway',
weight: 'bold'
},
boxWidth: 15,
padding: 15
}
},
tooltip: {
backgroundColor: isDarkMode ? 'rgba(10, 10, 10, 0.9)' : 'rgba(255, 255, 255, 0.9)',
titleColor: isDarkMode ? '#fff' : '#000',
bodyColor: isDarkMode ? '#fff' : '#000',
borderColor: 'rgba(204, 0, 0, 0.5)',
borderWidth: 1,
padding: 12,
cornerRadius: 8,
displayColors: false,
callbacks: {
title: (items) => items[0].label,
label: (context) => `Proficiency: ${context.raw}%`,
},
},
},
maintainAspectRatio: false,
responsive: true,
};
return (
<div className="skills-radar-container">
<div className="skills-radar-chart">
<Radar data={chartData} options={options} />
</div>
</div>
);
};
export default SkillsRadar;

View file

@ -0,0 +1,121 @@
.skills-radar-container {
display: flex;
flex-direction: column;
width: 100%;
height: auto;
margin: 0 auto;
opacity: 0;
animation: fadeIn 0.8s ease-out forwards;
max-width: 700px;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.skills-radar-chart {
position: relative;
height: 500px;
margin-bottom: 1rem;
}
.skills-legend {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
margin-top: 1.5rem;
}
.skill-legend-item {
display: flex;
align-items: center;
font-family: "Raleway", sans-serif;
padding: 0.75rem 0.75rem;
border-radius: 8px;
transition: all 0.3s ease;
background-color: rgba(var(--secondary-rgb), 0.05);
opacity: 0;
transform: translateX(-10px);
animation: slideIn 0.5s ease-out forwards;
}
.skill-legend-item:nth-child(1) {
animation-delay: 0.1s;
}
.skill-legend-item:nth-child(2) {
animation-delay: 0.2s;
}
.skill-legend-item:nth-child(3) {
animation-delay: 0.3s;
}
.skill-legend-item:nth-child(4) {
animation-delay: 0.4s;
}
.skill-legend-item:nth-child(5) {
animation-delay: 0.5s;
}
.skill-legend-item:nth-child(6) {
animation-delay: 0.6s;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-10px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.skill-legend-item:hover {
background-color: rgba(var(--secondary-rgb), 0.1);
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.skill-dot {
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 10px;
}
.skill-legend-name {
flex: 1;
font-size: 0.95rem;
font-weight: 500;
}
.skill-legend-value {
font-weight: 600;
font-size: 1rem;
opacity: 0.9;
color: var(--text-color-3);
}
@media (max-width: 768px) {
.skills-radar-chart {
height: 350px;
}
.skills-legend {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.skill-legend-name {
font-size: 0.85rem;
}
.skill-legend-value {
font-size: 0.9rem;
}
}

View file

@ -20,94 +20,61 @@ const dataabout = {
aboutme: "I am a Computer Science student at the University of Virginia, with a passion for software development and AI. I enjoy building scalable applications and exploring the latest advancements in technology. I am always eager to learn and contribute to innovative projects.",
};
const worktimeline = [{
jobtitle: "Software Development Intern",
where: "SUPPLMEN",
date: "January 2025 - Present",
url: "https://suppl-men.vercel.app/"
},
{
jobtitle: "Co Founder",
where: "SOLVEX",
date: "December 2024 - Present",
url: "https://solvex.live/"
},
{
jobtitle: "Front End Development Intern",
where: "UNIKOVE TECHNOLOGIES",
date: "June 2024 - August 2024",
url: "https://unikove.com/"
},
{
jobtitle: "Software Development Intern",
where: "MOGLIX",
date: "June 2023 - August 2023",
url: "https://www.moglix.com/"
},
{
jobtitle: "Software Engineering Intern",
where: "SAN AUTO",
date: "June 2022 - August 2022",
url: "https://www.sanautomotive.com/"
},
];
const skills = [{
name: "Python",
value: 90,
},
const skills = [
{
name: "Java",
name: "Full-Stack Development",
value: 85,
},
{
name: "Typescript",
value: 80,
},
{
name: "HTML",
name: "Frontend (React/Next.js)",
value: 90,
},
{
name: "CSS/Tailwind",
name: "Backend (Node.js)",
value: 88,
},
{
name: "AI & Machine Learning",
value: 85,
},
{
name: "C/C++",
value: 70,
name: "Database & API Design",
value: 85,
},
{
name: "React.js",
value: 80,
},
{
name: "Node.js",
value: 75,
},
{
name: "Express.js",
value: 75,
},
{
name: "Vite",
value: 80,
},
{
name: "Next.js 14",
value: 70,
},
{
name: "Git/Github",
value: 90,
},
{
name: "PostgreSQL",
value: 80,
},
{
name: "MySQL",
value: 80,
},
{
name: "Supabase",
value: 75,
},
{
name: "Vercel",
value: 80,
},
{
name: "OAuth 2.0",
value: 70,
name: "DevOps & Deployment",
value: 87,
},
];

View file

@ -8,6 +8,12 @@
--overlay-color: rgb(8 8 8 / 63%);
--image-container-bg: #060606;
--card-border: rgba(255, 255, 255, 0.2);
/* RGB Values for opacity manipulation */
--bg-color-rgb: 8, 8, 8;
--primary-rgb: 15, 15, 15;
--secondary-rgb: 255, 255, 255;
--text-color-rgb: 255, 255, 255;
}
[data-theme="light"] {
@ -20,6 +26,12 @@
--overlay-color: rgb(227 218 201 / 70%);
--image-container-bg: #d8c9b2;
--card-border: rgba(0, 0, 0, 0.2);
/* RGB Values for opacity manipulation */
--bg-color-rgb: 227, 218, 201;
--primary-rgb: 227, 218, 201;
--secondary-rgb: 0, 0, 0;
--text-color-rgb: 0, 0, 0;
}
html,

View file

@ -2,6 +2,8 @@ import React from "react";
import "./style.css";
import { Helmet, HelmetProvider } from "react-helmet-async";
import { Container, Row, Col } from "react-bootstrap";
import { RiExternalLinkLine } from "react-icons/ri";
import SkillsRadar from "../../components/SkillsRadar";
import {
dataabout,
meta,
@ -36,22 +38,39 @@ export const About = () => {
</Row>
<Row className=" sec_sp">
<Col lg="5">
<h3 className="color_sec py-4">Work Timline</h3>
<h3 className="color_sec py-4">Work Timeline</h3>
</Col>
<Col lg="7">
<table className="table caption-top">
<tbody>
{worktimeline.map((data, i) => {
return (
<tr key={i}>
<th scope="row">{data.jobtitle}</th>
<td>{data.where}</td>
<td>{data.date}</td>
</tr>
);
})}
</tbody>
</table>
<div className="work-timeline">
{worktimeline.map((data, i) => {
return (
<div key={i} className="timeline-item">
<div className="timeline-content">
<h4 className="timeline-title">{data.jobtitle}</h4>
<div className="timeline-company">
<span>{data.where}</span>
{data.url && (
<a
href={data.url}
target="_blank"
rel="noopener noreferrer"
className="company-link"
aria-label={`Visit ${data.where} website`}
title={`Visit ${data.where} website`}
>
<RiExternalLinkLine aria-hidden="true" />
</a>
)}
</div>
<span className="timeline-date">{data.date}</span>
{data.description && (
<p className="timeline-description">{data.description}</p>
)}
</div>
</div>
);
})}
</div>
</Col>
</Row>
<Row className="sec_sp">
@ -76,24 +95,8 @@ export const About = () => {
<Col lg="5">
<h3 className="color_sec py-4">Skills</h3>
</Col>
<Col lg="7">
{skills.map((data, i) => {
return (
<div key={i}>
<h3 className="progress-title">{data.name}</h3>
<div className="progress">
<div
className="progress-bar"
style={{
width: `${data.value}%`,
}}
>
<div className="progress-value">{data.value}%</div>
</div>
</div>
</div>
);
})}
<Col lg="7" className="d-flex justify-content-center">
<SkillsRadar skills={skills} />
</Col>
</Row>
</Container>

View file

@ -97,3 +97,168 @@
transform: translateZ(0);
}
}
/* Work Timeline styling */
.work-timeline {
display: flex;
flex-direction: column;
gap: 2.5rem;
}
.timeline-item {
display: flex;
flex-direction: column;
position: relative;
border-bottom: 1px solid rgba(var(--secondary-rgb), 0.2);
padding-bottom: 2rem;
transition: all 0.3s ease;
padding-left: 0;
}
.timeline-item:hover {
transform: translateX(8px);
border-bottom-color: rgba(var(--secondary-rgb), 0.5);
padding-left: 0.5rem;
}
.timeline-item:last-child {
border-bottom: none;
}
.timeline-item::before {
content: "";
position: absolute;
left: -1rem;
top: 0.5rem;
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
background-color: rgba(var(--secondary-rgb), 0.6);
transform: scale(0);
transition: transform 0.3s ease;
}
.timeline-item:hover::before {
transform: scale(1);
}
.timeline-content {
display: grid;
grid-template-columns: 1fr;
gap: 0.75rem;
}
.timeline-title {
font-size: 1.25rem;
font-weight: 600;
margin: 0;
color: var(--text-color);
}
.timeline-company {
font-size: 1.1rem;
font-weight: 500;
margin: 0.25rem 0;
color: var(--text-color);
display: flex;
align-items: center;
gap: 0.5rem;
}
.company-link {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
border-radius: 50%;
background: rgba(var(--secondary-rgb), 0.1);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(var(--text-color-rgb), 0.1);
color: var(--text-color);
font-size: 1rem;
transition: all 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275);
margin-left: 0.5rem;
position: relative;
overflow: hidden;
}
.company-link::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
135deg,
var(--secondary) 0%,
var(--text-color) 100%
);
opacity: 0;
transition: opacity 0.25s ease;
z-index: -1;
}
.company-link:hover,
.company-link:focus {
transform: translateY(-3px) scale(1.1);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
color: var(--bg-color);
}
.company-link:hover::before,
.company-link:focus::before {
opacity: 1;
}
.company-link:active {
transform: translateY(0) scale(0.95);
}
.timeline-date {
font-size: 1rem;
font-weight: normal;
margin: 0.25rem 0;
color: var(--text-color);
opacity: 0.9;
}
.timeline-description {
font-size: 0.95rem;
margin-top: 0.75rem;
line-height: 1.6;
color: var(--text-color);
opacity: 0.85;
grid-column: 1 / -1;
border-top: 1px solid rgba(var(--secondary-rgb), 0.1);
padding-top: 0.75rem;
margin-top: 0.5rem;
}
@media (min-width: 768px) {
.timeline-content {
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
}
.timeline-title {
grid-column: 1;
}
.timeline-company {
grid-column: 2;
justify-content: center;
}
.timeline-date {
grid-column: 3;
text-align: right;
}
.timeline-description {
margin-top: 1rem;
padding-top: 1rem;
}
}

View file

@ -1611,6 +1611,11 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@kurkle/color@^0.3.0":
version "0.3.4"
resolved "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz"
integrity sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==
"@leichtgewicht/ip-codec@^2.0.1":
version "2.0.4"
resolved "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz"
@ -3106,6 +3111,13 @@ char-regex@^2.0.0:
resolved "https://registry.npmjs.org/char-regex/-/char-regex-2.0.1.tgz"
integrity sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==
chart.js@^4.4.8:
version "4.4.8"
resolved "https://registry.npmjs.org/chart.js/-/chart.js-4.4.8.tgz"
integrity sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==
dependencies:
"@kurkle/color" "^0.3.0"
check-types@^11.1.1:
version "11.2.2"
resolved "https://registry.npmjs.org/check-types/-/check-types-11.2.2.tgz"
@ -7604,6 +7616,11 @@ react-bootstrap@^2.7.0:
uncontrollable "^7.2.1"
warning "^4.0.3"
react-chartjs-2@^5.3.0:
version "5.3.0"
resolved "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz"
integrity sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==
react-dev-utils@^12.0.1:
version "12.0.1"
resolved "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz"