feat: enhance skills section

This commit is contained in:
Alvalens 2025-05-26 22:09:21 +07:00
parent 0457420a45
commit 661482c405
6 changed files with 337 additions and 238 deletions

View file

@ -0,0 +1,84 @@
function ActivityIcon(props) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2" />
</svg>
);
}
function CodepenIcon(props) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2" />
<line x1="12" x2="12" y1="22" y2="15.5" />
<polyline points="22 8.5 12 15.5 2 8.5" />
<polyline points="2 15.5 12 8.5 22 15.5" />
<line x1="12" x2="12" y1="2" y2="8.5" />
</svg>
);
}
function WebhookIcon(props) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<path d="M18 16.98h-5.99c-1.1 0-1.95.94-2.48 1.9A4 4 0 0 1 2 17c.01-.7.2-1.4.57-2" />
<path d="m6 17 3.13-5.78c.53-.97.1-2.18-.5-3.1a4 4 0 1 1 6.89-4.06" />
<path d="m12 6 3.13 5.73C15.66 12.7 16.9 13 18 13a4 4 0 0 1 0 8" />
</svg>
);
}
function MobileIcon(props) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<rect x="5" y="2" width="14" height="20" rx="2" ry="2" />
<line x1="12" x2="12" y1="18" y2="18" />
</svg>
);
}
export {
ActivityIcon,
CodepenIcon,
WebhookIcon,
MobileIcon,
};

View file

@ -1,164 +1,249 @@
import { motion } from "framer-motion";
import Stack from "./stack";
import Tools from "./tools";
"use client";
import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";
import { CodepenIcon, WebhookIcon, ActivityIcon, MobileIcon } from "./icons"
const skillCategories = {
web: {
title: "Web Development",
icon: CodepenIcon,
description: "Building modern, responsive web applications",
languages: [
"HTML",
"CSS",
"JavaScript",
"TypeScript",
"PHP",
"Python",
"React",
"NextJS",
"TailwindCSS",
"Bootstrap",
"NodeJS",
"ExpressJS",
"Laravel",
"Flask",
"Django",
"Firebase"
],
tools: ["Visual Studio Code", "Git", "Github", "Figma", "Vite", "Docker", "Kubernetes", "Google Cloud", "Postman"],
},
api: {
title: "REST API",
icon: WebhookIcon,
description: "Creating robust and scalable backend services",
languages: [
"NodeJS",
"ExpressJS",
"PHP",
"Laravel",
"Python",
"FastAPI",
"Flask",
"Django",
"MySQL",
"PostgreSQL",
"MongoDB",
"Firebase",
],
tools: ["Postman", "Docker", "Kubernetes", "Swagger", "Git", "Github", "Google Cloud", "IBM Cloud"],
},
ai: {
title: "AI & Machine Learning",
icon: ActivityIcon,
description: "Developing intelligent solutions with ML/AI",
languages: [
"Python",
"TensorFlow",
"PyTorch",
"Scikit-learn",
"Pandas",
"NumPy",
"Jupyter",
"OpenAI API",
"Gemini API",
"LangChain",
],
tools: [
"Jupyter Notebook",
"Google Colab",
"Google Cloud AI",
"AWS SageMaker",
"IBM Watson",
],
},
mobile: {
title: "Mobile Development",
icon: MobileIcon,
description: "Cross-platform mobile app development",
languages: [
"React Native",
"JavaScript",
"TypeScript",
"Dart",
"Flutter",
],
tools: [
"Android Studio",
"React Native CLI",
],
},
};
function SkillCard({ skill, isSelected, onClick }) {
const Icon = skill.icon;
function Wrapper({ children }) {
return (
<div className="mx-auto container gap-4 p-10 grid grid-cols-1 mt-10">
<motion.div
onClick={onClick}
className={`relative cursor-pointer group p-6 rounded-2xl backdrop-blur-lg border transition-all duration-300 ${
isSelected
? "bg-white/20 border-black border-2 shadow-lg"
: "bg-white/10 border-gray-300/20 hover:bg-white/20 hover:border-gray-300/30"
}`}
whileHover={{
scale: 1.05,
rotateY: 5,
}}
whileTap={{ scale: 0.95 }}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{
type: "spring",
stiffness: 300,
damping: 20,
}}>
{/* Glow effect - removed for selected state */}
{!isSelected && (
<div className="absolute inset-0 rounded-2xl transition-opacity duration-300 opacity-0 group-hover:opacity-50 bg-gradient-to-r from-gray-400/20 to-gray-600/20 blur-xl" />
)}
<div className="relative z-10 flex flex-col items-center text-center space-y-4">
<div
className={`p-4 rounded-xl transition-all duration-300 ${
isSelected ? "bg-white/30" : "bg-white/10 group-hover:bg-white/20"
}`}>
<Icon className="w-8 h-8 text-black" />
</div>
<div>
<h3 className="font-semibold text-black text-lg mb-2">
{skill.title}
</h3>
<p className="text-gray-600 text-sm leading-relaxed">
{skill.description}
</p>
</div>
</div>
</motion.div>
);
}
function SkillDetails({ selectedSkill }) {
if (!selectedSkill) return null;
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.5 }}
className="mt-12 space-y-8">
{/* Languages & Frameworks */}
<motion.div
className="flex justify-center items-center flex-col mb-5 "
initial={{
opacity: 0,
x: -200,
}}
whileInView={{
opacity: 1,
x: 0,
}}
transition={{
delay: 0.5,
type: "spring",
}}>
{children}
className="backdrop-blur-lg bg-white/20 border border-gray-300/30 rounded-2xl p-8"
initial={{ opacity: 0, x: -50 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.2 }}>
<h3 className="text-2xl font-semibold text-black mb-6 text-center">
Languages & Frameworks
</h3>
<div className="flex flex-wrap justify-center gap-3">
{selectedSkill.languages.map((lang, index) => (
<motion.span
key={lang}
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.3 + index * 0.1 }}
className="px-4 py-2 bg-gradient-to-r from-gray-200/60 to-white/40
border border-gray-400/40 rounded-full text-black font-medium
backdrop-blur-sm hover:scale-105 transition-transform cursor-default
hover:bg-gradient-to-r hover:from-gray-300/60 hover:to-white/50">
{lang}
</motion.span>
))}
</div>
</motion.div>
</div>
{/* Tools */}
<motion.div
className="backdrop-blur-lg bg-white/20 border border-gray-300/30 rounded-2xl p-8"
initial={{ opacity: 0, x: 50 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.4 }}>
<h3 className="text-2xl font-semibold text-black mb-6 text-center">
Tools & Technologies
</h3>
<div className="flex flex-wrap justify-center gap-3">
{selectedSkill.tools.map((tool, index) => (
<motion.span
key={tool}
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.5 + index * 0.1 }}
className="px-4 py-2 bg-gradient-to-r from-gray-300/60 to-gray-100/40
border border-gray-500/40 rounded-full text-black font-medium
backdrop-blur-sm hover:scale-105 transition-transform cursor-default
hover:bg-gradient-to-r hover:from-gray-400/60 hover:to-gray-200/50">
{tool}
</motion.span>
))}
</div>
</motion.div>
</motion.div>
);
}
export default function Skills() {
const [selectedCategory, setSelectedCategory] = useState("web");
return (
<>
<Wrapper>
<section className="grid gap-8 md:gap-12">
<div className="text-center space-y-2">
<h2 className="text-3xl font-bold mt-3 text-black">
Skills and Expertise
</h2>
<p className="text-muted-foreground max-w-[800px] mx-auto">
Explore some skills I&apos;m proficient in to
deliver high-quality solutions.
</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 md:gap-8">
<div className="flex flex-col items-center gap-2">
<CodepenIcon className="w-12 h-12" />
<div className="font-medium">Web Development</div>
</div>
<div className="flex flex-col items-center gap-2">
<WebhookIcon className="w-12 h-12" />
<div className="font-medium">REST API</div>
</div>
<div className="flex flex-col items-center gap-2">
<ActivityIcon className="w-12 h-12" />
<div className="font-medium">Machine Learning</div>
</div>
<div className="flex flex-col items-center gap-2">
<MobileIcon className="w-12 h-12" />
<div className="font-medium">
Mobile Development
</div>
</div>
{/* <div className="flex flex-col items-center gap-2">
<CloudIcon className="w-12 h-12" />
<div className="font-medium">Cloud Computing</div>
<div className="relative">
<div className="mx-auto container px-6 py-20">
<motion.div
initial={{ opacity: 0, y: -20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="text-center space-y-4 mb-16">
<h2 className="text-5xl font-bold bg-gradient-to-r from-black to-gray-600 bg-clip-text text-transparent">
Skills & Expertise
</h2>
<p className="text-gray-600 max-w-2xl mx-auto text-lg leading-relaxed">
Explore my technical skills across different domains. Click on any
category to see the specific technologies and tools I work with.
</p>
</motion.div>
{/* Skill Categories Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
{Object.entries(skillCategories).map(([key, skill], index) => (
<motion.div
key={key}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}>
<SkillCard
skill={skill}
isSelected={selectedCategory === key}
onClick={() => setSelectedCategory(key)}
/>
</motion.div>
))}
</div>
<div className="flex flex-col items-center gap-2">
<BarChartIcon className="w-12 h-12" />
<div className="font-medium">Data Analysis</div>
</div>
<div className="flex flex-col items-center gap-2">
<ComputerIcon className="w-12 h-12" />
<div className="font-medium">Cybersecurity</div>
</div>
<div className="flex flex-col items-center gap-2">
<PackageIcon className="w-12 h-12" />
<div className="font-medium">Product Management</div>
</div> */}
</div>
</section>
</Wrapper>
<div className=" mx-auto container gap-4 p-10 grid grid-cols-1 md:grid-cols-2 mt-10 mb-24">
<Stack />
<Tools />
{/* Skill Details */}
<AnimatePresence mode="wait">
<SkillDetails selectedSkill={skillCategories[selectedCategory]} />
</AnimatePresence>
</div>
</>
);
}
function ActivityIcon(props) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2" />
</svg>
);
}
function CodepenIcon(props) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2" />
<line x1="12" x2="12" y1="22" y2="15.5" />
<polyline points="22 8.5 12 15.5 2 8.5" />
<polyline points="2 15.5 12 8.5 22 15.5" />
<line x1="12" x2="12" y1="2" y2="8.5" />
</svg>
);
}
function WebhookIcon(props) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<path d="M18 16.98h-5.99c-1.1 0-1.95.94-2.48 1.9A4 4 0 0 1 2 17c.01-.7.2-1.4.57-2" />
<path d="m6 17 3.13-5.78c.53-.97.1-2.18-.5-3.1a4 4 0 1 1 6.89-4.06" />
<path d="m12 6 3.13 5.73C15.66 12.7 16.9 13 18 13a4 4 0 0 1 0 8" />
</svg>
);
}
function MobileIcon(props) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<rect x="5" y="2" width="14" height="20" rx="2" ry="2" />
<line x1="12" x2="12" y1="18" y2="18" />
</svg>
</div>
);
}

View file

@ -1,44 +0,0 @@
import { motion } from "framer-motion";
export default function Stack() {
return (
<motion.div
className="flex justify-center items-center flex-col mb-5 "
initial={{
opacity: 0,
x: -200,
}}
whileInView={{
opacity: 1,
x: 0,
}}
transition={{
delay: 0.5,
type: "spring",
}}>
<h2 className="text-2xl md:text-xl font-normal mb-3 md:tracking-[.3rem] lg:tracking-[.5rem] uppercase ">
Language & Framework
</h2>
<p className="text-gray-500 text-center tracking-widest md:px-5">
<span className="text-black font-bold">HTML</span> |{" "}
<span className="text-black font-bold">CSS</span> |{" "}
<span className="text-black font-bold">Javascript</span> |{" "}
<span className="text-black font-bold">Typescript</span> |{" "}
<span className="text-black font-bold">React</span> |{" "}
<span className="text-black font-bold">NextJS</span> |{" "}
<span className="text-black font-bold">TailwindCSS</span> |{" "}
<span className="text-black font-bold">Bootstrap</span> |{" "}
<span className="text-black font-medium">NodeJS</span> |{" "}
<span className="text-black font-medium">ExpressJS</span> |{" "}
<span className="text-black font-bold">MySQL</span> |{" "}
<span className="text-black font-bold">PostgreSQL</span> |{" "}
<span className="text-black font-medium">MongoDB</span> |{" "}
<span className="text-black font-medium">Firebase</span> |{" "}
<span className="text-black font-medium">PHP</span> |{" "}
<span className="text-black font-bold">Laravel</span> |{" "}
<span className="text-black font-medium">Python</span>
</p>
</motion.div>
);
}

View file

@ -1,42 +0,0 @@
import { motion } from "framer-motion";
export default function Tools() {
return (
<motion.div
className="flex justify-start items-center flex-col mb-5 "
initial={{
opacity: 0,
x: 200,
}}
whileInView={{
opacity: 1,
x: 0,
}}
transition={{
delay: 0.6,
type: "spring",
}}>
<h2 className="text-2xl md:text-xl font-normal mb-3 md:tracking-[.3rem] lg:tracking-[.5rem] uppercase">
Tools
</h2>
<p className="text-gray-500 text-center tracking-wider md:px-">
<span className="text-black font-bold">Visual Studio Code</span>{" "}
| <span className="text-black font-medium">Jet Brains</span> |{" "}
<span className="text-black font-bold">Git</span> |{" "}
<span className="text-black font-bold">Github</span> |{" "}
<span className="text-black font-medium">Figma</span> |{" "}
<span className="text-black font-medium">Adobe Photoshop</span>{" "}
|{" "}
<span className="text-black font-medium">
Adobe Premiere Pro
</span>{" "}
|{" "}
<span className="text-black font-medium">
Adobe After Effect
</span>{" "}
|{" "}
</p>
</motion.div>
);
}