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 (
-
- );
- })}
+
+
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"