From eb6610e7d0d3e516caa2804ca4bc9696b43e7425 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Fri, 21 Mar 2025 11:42:30 -0400 Subject: [PATCH] chore: add chart.js and react-chartjs-2 dependencies, update skills and work timeline in content options, and enhance about page styling --- package-lock.json | 30 +++++ package.json | 2 + src/components/SkillsRadar/index.js | 140 +++++++++++++++++++++++ src/components/SkillsRadar/style.css | 121 ++++++++++++++++++++ src/content_option.js | 77 ++++--------- src/index.css | 12 ++ src/pages/about/index.js | 67 +++++------ src/pages/about/style.css | 165 +++++++++++++++++++++++++++ yarn.lock | 17 +++ 9 files changed, 544 insertions(+), 87 deletions(-) create mode 100644 src/components/SkillsRadar/index.js create mode 100644 src/components/SkillsRadar/style.css diff --git a/package-lock.json b/package-lock.json index 1fda2ec..a2ff252 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 321effd..dfa21bd 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/SkillsRadar/index.js b/src/components/SkillsRadar/index.js new file mode 100644 index 0000000..32332db --- /dev/null +++ b/src/components/SkillsRadar/index.js @@ -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 ( +
+
+ +
+
+ ); +}; + +export default SkillsRadar; diff --git a/src/components/SkillsRadar/style.css b/src/components/SkillsRadar/style.css new file mode 100644 index 0000000..7ff6257 --- /dev/null +++ b/src/components/SkillsRadar/style.css @@ -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; + } +} diff --git a/src/content_option.js b/src/content_option.js index 88c81b3..be10499 100644 --- a/src/content_option.js +++ b/src/content_option.js @@ -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, }, ]; diff --git a/src/index.css b/src/index.css index 590f802..05c3af0 100644 --- a/src/index.css +++ b/src/index.css @@ -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, diff --git a/src/pages/about/index.js b/src/pages/about/index.js index e821845..b74da17 100644 --- a/src/pages/about/index.js +++ b/src/pages/about/index.js @@ -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 = () => { -

Work Timline

+

Work Timeline

- - - {worktimeline.map((data, i) => { - return ( - - - - - - ); - })} - -
{data.jobtitle}{data.where}{data.date}
+
+ {worktimeline.map((data, i) => { + return ( +
+
+

{data.jobtitle}

+
+ {data.where} + {data.url && ( + + + )} +
+ {data.date} + {data.description && ( +

{data.description}

+ )} +
+
+ ); + })} +
@@ -76,24 +95,8 @@ export const About = () => {

Skills

- - {skills.map((data, i) => { - return ( -
-

{data.name}

-
-
-
{data.value}%
-
-
-
- ); - })} + +
diff --git a/src/pages/about/style.css b/src/pages/about/style.css index 96f48a4..111947d 100644 --- a/src/pages/about/style.css +++ b/src/pages/about/style.css @@ -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; + } +} diff --git a/yarn.lock b/yarn.lock index b88573b..ece7d86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"