feat: add hero section and navigation components
Deploy Portfolio Selfmade / deploy (push) Successful in 31s
Deploy Portfolio Selfmade / deploy (push) Successful in 31s
- Created HeroSection component for the landing page. - Implemented Navigation component with smooth scrolling to sections. - Added useNavigation hook to manage active section and scroll state. - Introduced useScrollToSection hook for smooth scrolling functionality. feat: add projects section with project cards - Developed ProjectCard component to display individual project details. - Created ProjectsSection component to showcase featured projects with filtering options. - Added sample project data for demonstration. feat: add skills section with skill cards - Implemented SkillCard component to display individual skills. - Created SkillsSection component to showcase skills with category filtering. - Added sample skills data for demonstration. style: update global styles and add custom scrollbar - Imported Urbanist font and set it as the default font. - Updated body background and text colors. - Customized scrollbar styles for better aesthetics. feat: add shared components and constants - Created AnimatedSection, Button, and Footer components for reusability. - Added COLORS and GRADIENT constants for consistent theming. - Updated index files for shared components and hooks. chore: update dependencies and configuration - Added framer-motion for animations. - Updated Tailwind CSS configuration for custom animations and colors. - Adjusted TypeScript configuration for better path resolution.
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Mail, MapPin } from 'lucide-react';
|
||||
import { FaGithub, FaLinkedin, FaTwitter } from 'react-icons/fa';
|
||||
import Button from '../../../shared/components/Button';
|
||||
import AnimatedSection from '../../../shared/components/AnimatedSection';
|
||||
|
||||
export const ContactSection: React.FC = () => {
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.2,
|
||||
delayChildren: 0.3,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: { duration: 0.6 },
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatedSection id="contact" className="py-20 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{/* Section Header */}
|
||||
<div className="text-center mb-16">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="inline-block px-4 py-2 rounded-full border border-green-500/50 bg-green-500/10 mb-6"
|
||||
>
|
||||
<span className="text-green-400 text-sm font-medium">GET IN TOUCH</span>
|
||||
</motion.div>
|
||||
|
||||
<h2 className="text-4xl sm:text-5xl font-bold text-white mb-4">
|
||||
Let's Work Together
|
||||
</h2>
|
||||
<p className="text-gray-400 text-lg max-w-2xl mx-auto">
|
||||
Have a project in mind? Let's discuss how we can bring your ideas to life
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Content Grid */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
|
||||
{/* Contact Form */}
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
className="space-y-6"
|
||||
>
|
||||
<motion.div variants={itemVariants}>
|
||||
<label className="block text-white font-medium mb-3">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Your name"
|
||||
className="w-full px-4 py-3 rounded-lg bg-gray-900 border border-green-500/30 text-white placeholder-gray-500 focus:border-green-500 focus:outline-none transition-colors"
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<motion.div variants={itemVariants}>
|
||||
<label className="block text-white font-medium mb-3">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="your.email@example.com"
|
||||
className="w-full px-4 py-3 rounded-lg bg-gray-900 border border-green-500/30 text-white placeholder-gray-500 focus:border-green-500 focus:outline-none transition-colors"
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<motion.div variants={itemVariants}>
|
||||
<label className="block text-white font-medium mb-3">Message</label>
|
||||
<textarea
|
||||
placeholder="Tell me about your project"
|
||||
rows={5}
|
||||
className="w-full px-4 py-3 rounded-lg bg-gray-900 border border-green-500/30 text-white placeholder-gray-500 focus:border-green-500 focus:outline-none transition-colors resize-none"
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<motion.div variants={itemVariants}>
|
||||
<Button variant="primary" size="lg" className="w-full">
|
||||
Send Message
|
||||
</Button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
{/* Contact Info */}
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
className="space-y-6"
|
||||
>
|
||||
<motion.div variants={itemVariants}>
|
||||
<h3 className="text-2xl font-bold text-white mb-4">Let's Connect</h3>
|
||||
<p className="text-gray-400">
|
||||
I'm always open to discussing new projects, creative ideas, or opportunities
|
||||
to be part of your vision. Feel free to reach out!
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Contact Items */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-4 p-4 rounded-lg border border-green-500/30 bg-green-500/5">
|
||||
<div className="p-3 rounded-lg bg-green-500/20">
|
||||
<Mail className="text-green-500" size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-400 text-sm">Email</p>
|
||||
<p className="text-white font-medium">alex@himetoprogram.com</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 p-4 rounded-lg border border-green-500/30 bg-green-500/5">
|
||||
<div className="p-3 rounded-lg bg-green-500/20">
|
||||
<MapPin className="text-green-500" size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-400 text-sm">Location</p>
|
||||
<p className="text-white font-medium">San Francisco, CA</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Social Links */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<p className="text-white font-medium mb-4">Connect with me</p>
|
||||
<div className="flex gap-4">
|
||||
<motion.a
|
||||
href="#"
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="p-3 rounded-lg border border-green-500/30 bg-green-500/5 hover:bg-green-500/20 text-green-500 transition-colors"
|
||||
>
|
||||
<FaGithub size={24} />
|
||||
</motion.a>
|
||||
<motion.a
|
||||
href="#"
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="p-3 rounded-lg border border-green-500/30 bg-green-500/5 hover:bg-green-500/20 text-green-500 transition-colors"
|
||||
>
|
||||
<FaLinkedin size={24} />
|
||||
</motion.a>
|
||||
<motion.a
|
||||
href="#"
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="p-3 rounded-lg border border-green-500/30 bg-green-500/5 hover:bg-green-500/20 text-green-500 transition-colors"
|
||||
>
|
||||
<FaTwitter size={24} />
|
||||
</motion.a>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</AnimatedSection>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { ContactSection } from './components/ContactSection';
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { experienceData } from '../data/experienceData';
|
||||
import { TimelineItem } from './TimelineItem';
|
||||
import AnimatedSection from '../../../shared/components/AnimatedSection';
|
||||
|
||||
export const ExperienceSection: React.FC = () => {
|
||||
return (
|
||||
<AnimatedSection id="experience" className="py-20 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{/* Section Header */}
|
||||
<div className="text-center mb-16">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="inline-block px-4 py-2 rounded-full border border-green-500/50 bg-green-500/10 mb-6"
|
||||
>
|
||||
<span className="text-green-400 text-sm font-medium">My Journey</span>
|
||||
</motion.div>
|
||||
|
||||
<h2 className="text-4xl sm:text-5xl font-bold text-white mb-4">
|
||||
Professional Experience
|
||||
</h2>
|
||||
<p className="text-gray-400 text-lg">
|
||||
A timeline of my work experience and achievements
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Timeline */}
|
||||
<div className="relative">
|
||||
{experienceData.map((experience, index) => (
|
||||
<TimelineItem
|
||||
key={experience.id}
|
||||
experience={experience}
|
||||
index={index}
|
||||
isLast={index === experienceData.length - 1}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</AnimatedSection>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import type { Experience } from '../../../shared/types';
|
||||
|
||||
interface TimelineItemProps {
|
||||
experience: Experience;
|
||||
index: number;
|
||||
isLast: boolean;
|
||||
}
|
||||
|
||||
export const TimelineItem: React.FC<TimelineItemProps> = ({
|
||||
experience,
|
||||
index,
|
||||
isLast,
|
||||
}) => {
|
||||
const isEven = index % 2 === 0;
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
className="relative"
|
||||
>
|
||||
{/* Timeline dot and line */}
|
||||
<div className="absolute left-1/2 transform -translate-x-1/2">
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
whileInView={{ scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.4, delay: index * 0.1 + 0.2 }}
|
||||
className="w-4 h-4 bg-green-500 rounded-full border-4 border-black"
|
||||
/>
|
||||
{!isLast && (
|
||||
<div className="w-1 h-32 bg-gradient-to-b from-green-500 to-transparent mx-auto -mt-4"></div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div
|
||||
className={`w-full md:w-5/12 ${isEven ? 'md:ml-0 md:text-right' : 'md:ml-auto'}`}
|
||||
>
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.02 }}
|
||||
className="p-6 rounded-xl border border-green-500/30 bg-gradient-to-br from-green-500/5 to-emerald-500/5 backdrop-blur-sm hover:border-green-500/60 transition-all duration-300"
|
||||
>
|
||||
{/* Year Badge */}
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="px-3 py-1 rounded-full bg-green-500/20 border border-green-500/50">
|
||||
<span className="text-green-400 font-bold text-sm">
|
||||
{experience.startYear}
|
||||
{experience.endYear ? `-${experience.endYear}` : '-Present'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Position */}
|
||||
<h3 className="text-xl font-bold text-white mb-2">
|
||||
{experience.position}
|
||||
</h3>
|
||||
|
||||
{/* Company */}
|
||||
{experience.company && (
|
||||
<p className="text-green-400 font-medium mb-3">{experience.company}</p>
|
||||
)}
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-gray-400 text-sm mb-4">{experience.description}</p>
|
||||
|
||||
{/* Technologies */}
|
||||
{experience.technologies && (
|
||||
<div className="flex flex-wrap gap-2 justify-end">
|
||||
{experience.technologies.map((tech) => (
|
||||
<span
|
||||
key={tech}
|
||||
className="text-xs px-2 py-1 rounded border border-green-500/30 text-green-400 bg-green-500/10"
|
||||
>
|
||||
{tech}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { Experience } from '../../../shared/types';
|
||||
|
||||
export const experienceData: Experience[] = [
|
||||
{
|
||||
id: '1',
|
||||
position: 'Senior React Developer',
|
||||
company: 'Tech Startup Inc.',
|
||||
startYear: 2023,
|
||||
description:
|
||||
'Led the development of multiple high-performance React applications, mentored junior developers, and implemented modern design systems',
|
||||
technologies: ['React', 'TypeScript', 'Next.js', 'GraphQL'],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
position: 'Full Stack Developer',
|
||||
company: 'Digital Agency Co.',
|
||||
startYear: 2022,
|
||||
endYear: 2023,
|
||||
description:
|
||||
'Developed responsive web applications for clients, collaborated with designers, and optimized performance metrics',
|
||||
technologies: ['React', 'Node.js', 'MongoDB', 'Tailwind CSS'],
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
position: 'Junior Frontend Developer',
|
||||
company: 'Web Solutions Ltd.',
|
||||
startYear: 2021,
|
||||
endYear: 2022,
|
||||
description:
|
||||
'Built responsive web interfaces, fixed bugs, and participated in code reviews to improve code quality',
|
||||
technologies: ['React', 'JavaScript', 'CSS', 'HTML'],
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
position: 'Freelance Web Developer',
|
||||
startYear: 2020,
|
||||
endYear: 2021,
|
||||
description:
|
||||
'Created custom websites for various clients, managed projects independently, and delivered quality work on time',
|
||||
technologies: ['React', 'WordPress', 'JavaScript', 'jQuery'],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1 @@
|
||||
export { ExperienceSection } from './components/ExperienceSection';
|
||||
@@ -0,0 +1,113 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { ArrowDown } from 'lucide-react';
|
||||
import Button from '../../../shared/components/Button';
|
||||
|
||||
export const HeroSection: React.FC = () => {
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.2,
|
||||
delayChildren: 0.3,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: { duration: 0.8 },
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<section
|
||||
id="home"
|
||||
className="min-h-screen flex items-center justify-center relative overflow-hidden pt-20 px-4 sm:px-6 lg:px-8"
|
||||
>
|
||||
{/* Background gradient */}
|
||||
<div className="absolute inset-0 -z-10">
|
||||
<div className="absolute top-0 left-0 w-96 h-96 bg-green-500/20 rounded-full blur-3xl"></div>
|
||||
<div className="absolute bottom-0 right-0 w-96 h-96 bg-emerald-500/20 rounded-full blur-3xl"></div>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
className="max-w-5xl mx-auto text-center"
|
||||
>
|
||||
{/* Badge */}
|
||||
<motion.div variants={itemVariants} className="mb-8">
|
||||
<div className="inline-block px-4 py-2 border border-green-500/50 rounded-full bg-green-500/10 backdrop-blur-sm">
|
||||
<span className="text-green-400 text-sm font-medium flex items-center gap-2">
|
||||
<span className="w-2 h-2 bg-green-500 rounded-full"></span>
|
||||
React Developer & UI/UX Enthusiast | Based in San Francisco, CA
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Main Heading */}
|
||||
<motion.h1 variants={itemVariants} className="text-5xl sm:text-6xl lg:text-7xl font-bold mb-6">
|
||||
<span className="text-white">React is Developer</span>
|
||||
<br />
|
||||
<span className="bg-gradient-to-r from-green-500 to-emerald-500 bg-clip-text text-transparent">
|
||||
Portfolio
|
||||
</span>
|
||||
</motion.h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
<motion.p
|
||||
variants={itemVariants}
|
||||
className="text-gray-300 text-lg sm:text-xl max-w-2xl mx-auto mb-8"
|
||||
>
|
||||
Building modern, scalable web applications with React, JavaScript, and cutting-edge technologies.
|
||||
Transforming ideas into exceptional digital experiences.
|
||||
</motion.p>
|
||||
|
||||
{/* Stats */}
|
||||
<motion.div
|
||||
variants={itemVariants}
|
||||
className="grid grid-cols-3 gap-4 sm:gap-8 mb-12 max-w-2xl mx-auto"
|
||||
>
|
||||
<div className="border border-green-500/30 rounded-lg p-4 bg-green-500/5">
|
||||
<div className="text-2xl sm:text-3xl font-bold text-green-500">3+</div>
|
||||
<div className="text-gray-400 text-sm">Years Experience</div>
|
||||
</div>
|
||||
<div className="border border-green-500/30 rounded-lg p-4 bg-green-500/5">
|
||||
<div className="text-2xl sm:text-3xl font-bold text-green-500">50+</div>
|
||||
<div className="text-gray-400 text-sm">Projects Completed</div>
|
||||
</div>
|
||||
<div className="border border-green-500/30 rounded-lg p-4 bg-green-500/5">
|
||||
<div className="text-2xl sm:text-3xl font-bold text-green-500">98%</div>
|
||||
<div className="text-gray-400 text-sm">Client Satisfaction</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* CTA Buttons */}
|
||||
<motion.div variants={itemVariants} className="flex flex-col sm:flex-row gap-4 justify-center mb-12">
|
||||
<Button variant="primary" size="lg">
|
||||
Get in Touch
|
||||
</Button>
|
||||
<Button variant="secondary" size="lg">
|
||||
View My Work
|
||||
</Button>
|
||||
</motion.div>
|
||||
|
||||
{/* Scroll Indicator */}
|
||||
<motion.div
|
||||
variants={itemVariants}
|
||||
animate={{ y: [0, 10, 0] }}
|
||||
transition={{ duration: 2, repeat: Infinity }}
|
||||
className="flex justify-center"
|
||||
>
|
||||
<ArrowDown className="text-green-500" size={32} />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { HeroSection } from './components/HeroSection';
|
||||
@@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useNavigation } from '../hooks/useNavigation';
|
||||
import { useScrollToSection } from '../../../shared/hooks/useScrollToSection';
|
||||
import Button from '../../../shared/components/Button';
|
||||
|
||||
const navItems = [
|
||||
{ id: 'home', label: 'About' },
|
||||
{ id: 'skills', label: 'Skills' },
|
||||
{ id: 'projects', label: 'Projects' },
|
||||
{ id: 'experience', label: 'Experience' },
|
||||
{ id: 'contact', label: 'Contact' },
|
||||
];
|
||||
|
||||
export const Navigation: React.FC = () => {
|
||||
const { activeSection, isScrolled } = useNavigation();
|
||||
const { scrollToSection } = useScrollToSection();
|
||||
|
||||
return (
|
||||
<motion.nav
|
||||
initial={{ y: -100 }}
|
||||
animate={{ y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className={`fixed top-0 w-full z-50 transition-all duration-300 ${
|
||||
isScrolled
|
||||
? 'bg-black/80 backdrop-blur-md border-b border-green-500/20'
|
||||
: 'bg-transparent'
|
||||
}`}
|
||||
>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between items-center h-16">
|
||||
{/* Logo */}
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
className="text-2xl font-bold text-green-500 cursor-pointer"
|
||||
onClick={() => scrollToSection('home')}
|
||||
>
|
||||
</> Alex
|
||||
</motion.div>
|
||||
|
||||
{/* Nav Items */}
|
||||
<div className="hidden md:flex gap-8">
|
||||
{navItems.map((item) => (
|
||||
<motion.button
|
||||
key={item.id}
|
||||
onClick={() => scrollToSection(item.id)}
|
||||
className={`text-sm font-medium transition-colors duration-300 pb-2 border-b-2 ${
|
||||
activeSection === item.id
|
||||
? 'text-green-500 border-green-500'
|
||||
: 'text-gray-300 border-transparent hover:text-white'
|
||||
}`}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
{item.label}
|
||||
</motion.button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* CTA Button */}
|
||||
<Button variant="primary" size="sm">
|
||||
Hire Me
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.nav>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const useNavigation = () => {
|
||||
const [activeSection, setActiveSection] = useState<string>('home');
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 50);
|
||||
|
||||
// Update active section based on viewport
|
||||
const sections = ['home', 'skills', 'projects', 'experience', 'contact'];
|
||||
for (const section of sections) {
|
||||
const element = document.getElementById(section);
|
||||
if (element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
if (rect.top < 100 && rect.bottom > 100) {
|
||||
setActiveSection(section);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
return { activeSection, isScrolled };
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { Navigation } from './components/Navigation';
|
||||
@@ -0,0 +1,87 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { ExternalLink } from 'lucide-react';
|
||||
import { FaGithub } from 'react-icons/fa';
|
||||
import type { Project } from '../../../shared/types';
|
||||
|
||||
interface ProjectCardProps {
|
||||
project: Project;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export const ProjectCard: React.FC<ProjectCardProps> = ({ project, index }) => {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
whileHover={{ y: -10 }}
|
||||
className="group rounded-xl overflow-hidden border border-green-500/30 bg-gradient-to-br from-green-500/5 to-emerald-500/5 backdrop-blur-sm hover:border-green-500/60 transition-all duration-300"
|
||||
>
|
||||
{/* Image */}
|
||||
<div className="relative h-64 overflow-hidden bg-gradient-to-br from-gray-900 to-black">
|
||||
<img
|
||||
src={project.image}
|
||||
alt={project.title}
|
||||
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/80 to-transparent"></div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-6">
|
||||
{/* Category Badge */}
|
||||
<div className="inline-block mb-4 px-3 py-1 rounded-full bg-green-500/20 border border-green-500/50">
|
||||
<span className="text-green-400 text-xs font-semibold">{project.category}</span>
|
||||
</div>
|
||||
|
||||
<h3 className="text-xl font-bold text-white mb-2">{project.title}</h3>
|
||||
<p className="text-gray-400 text-sm mb-4 line-clamp-2">{project.description}</p>
|
||||
|
||||
{/* Tags */}
|
||||
<div className="flex flex-wrap gap-2 mb-6">
|
||||
{project.tags.slice(0, 3).map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="text-xs px-2 py-1 rounded border border-green-500/30 text-green-400"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
{project.tags.length > 3 && (
|
||||
<span className="text-xs px-2 py-1 text-gray-500">
|
||||
+{project.tags.length - 3}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Links */}
|
||||
<div className="flex gap-3">
|
||||
{project.link && (
|
||||
<motion.a
|
||||
href={project.link}
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-green-500/20 hover:bg-green-500/30 text-green-400 transition-colors text-sm font-medium"
|
||||
>
|
||||
<ExternalLink size={16} />
|
||||
Live
|
||||
</motion.a>
|
||||
)}
|
||||
{project.github && (
|
||||
<motion.a
|
||||
href={project.github}
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-lg border border-green-500/30 hover:border-green-500/60 text-gray-300 hover:text-white transition-colors text-sm font-medium"
|
||||
>
|
||||
<FaGithub size={16} />
|
||||
Code
|
||||
</motion.a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import React, { useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { projectsData } from '../data/projectsData';
|
||||
import { ProjectCard } from './ProjectCard';
|
||||
import AnimatedSection from '../../../shared/components/AnimatedSection';
|
||||
import type { Project } from '../../../shared/types';
|
||||
|
||||
type Category = 'All' | 'Web Apps' | 'UI Components' | 'Full Stack';
|
||||
|
||||
export const ProjectsSection: React.FC = () => {
|
||||
const [selectedCategory, setSelectedCategory] = useState<Category>('All');
|
||||
|
||||
const categories: Category[] = ['All', 'Web Apps', 'UI Components', 'Full Stack'];
|
||||
|
||||
const filteredProjects = projectsData.filter(
|
||||
(project: Project) =>
|
||||
selectedCategory === 'All' || project.category === selectedCategory
|
||||
);
|
||||
|
||||
return (
|
||||
<AnimatedSection id="projects" className="py-20 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Section Header */}
|
||||
<div className="text-center mb-16">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="inline-block px-4 py-2 rounded-full border border-green-500/50 bg-green-500/10 mb-6"
|
||||
>
|
||||
<span className="text-green-400 text-sm font-medium">My Work</span>
|
||||
</motion.div>
|
||||
|
||||
<h2 className="text-4xl sm:text-5xl font-bold text-white mb-4">
|
||||
Featured Projects
|
||||
</h2>
|
||||
<p className="text-gray-400 text-lg max-w-2xl mx-auto">
|
||||
Showcasing my best work and achievements
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Category Filter */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
className="flex flex-wrap justify-center gap-3 mb-12"
|
||||
>
|
||||
{categories.map((category) => (
|
||||
<motion.button
|
||||
key={category}
|
||||
onClick={() => setSelectedCategory(category)}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className={`px-6 py-2 rounded-full font-medium transition-all duration-300 ${
|
||||
selectedCategory === category
|
||||
? 'bg-green-500 text-black'
|
||||
: 'border border-green-500/30 text-gray-300 hover:border-green-500/60'
|
||||
}`}
|
||||
>
|
||||
{category}
|
||||
</motion.button>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* Projects Grid */}
|
||||
<motion.div
|
||||
layout
|
||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
|
||||
>
|
||||
{filteredProjects.map((project: Project, index: number) => (
|
||||
<ProjectCard key={project.id} project={project} index={index} />
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
</AnimatedSection>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { Project } from '../../../shared/types';
|
||||
|
||||
export const projectsData: Project[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'E-Commerce Platform',
|
||||
description:
|
||||
'Full-stack e-commerce platform with product catalog, shopping cart, and payment integration using Stripe',
|
||||
image: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=800&h=600&fit=crop',
|
||||
tags: ['React', 'Next.js', 'TypeScript', 'Tailwind CSS', 'MongoDB'],
|
||||
category: 'Full Stack',
|
||||
link: '#',
|
||||
github: '#',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Task Management Dashboard',
|
||||
description:
|
||||
'A collaborative task management application with real-time updates, user authentication, and advanced filtering',
|
||||
image: 'https://images.unsplash.com/photo-1552664730-d307ca884978?w=800&h=600&fit=crop',
|
||||
tags: ['React', 'Firebase', 'TypeScript', 'Tailwind CSS'],
|
||||
category: 'Web Apps',
|
||||
link: '#',
|
||||
github: '#',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'Component Library',
|
||||
description:
|
||||
'A comprehensive reusable component library with Storybook documentation and design system tokens',
|
||||
image: 'https://images.unsplash.com/photo-1517694712202-14dd9538aa97?w=800&h=600&fit=crop',
|
||||
tags: ['React', 'Storybook', 'Tailwind CSS', 'TypeScript'],
|
||||
category: 'UI Components',
|
||||
link: '#',
|
||||
github: '#',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1 @@
|
||||
export { ProjectsSection } from './components/ProjectsSection';
|
||||
@@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { iconMap } from '../data/skillsData';
|
||||
import type { Skill } from '../../../shared/types';
|
||||
|
||||
interface SkillCardProps {
|
||||
skill: Skill;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export const SkillCard: React.FC<SkillCardProps> = ({ skill, index }) => {
|
||||
const levelColors = {
|
||||
Expert: 'text-green-500',
|
||||
Advanced: 'text-emerald-400',
|
||||
Intermediate: 'text-cyan-400',
|
||||
Beginner: 'text-blue-400',
|
||||
};
|
||||
|
||||
const levelBg = {
|
||||
Expert: 'bg-green-500/20',
|
||||
Advanced: 'bg-emerald-500/20',
|
||||
Intermediate: 'bg-cyan-500/20',
|
||||
Beginner: 'bg-blue-500/20',
|
||||
};
|
||||
|
||||
// Get icon from the mapped skill name
|
||||
const IconComponent = iconMap[skill.name as keyof typeof iconMap];
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
whileHover={{ scale: 1.05, y: -10 }}
|
||||
className="p-6 rounded-xl border border-green-500/30 bg-gradient-to-br from-green-500/5 to-emerald-500/5 backdrop-blur-sm hover:border-green-500/60 transition-all duration-300"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="p-3 rounded-lg bg-green-500/20">
|
||||
{IconComponent && <IconComponent className="text-green-500" size={24} />}
|
||||
</div>
|
||||
<span
|
||||
className={`text-xs font-semibold px-3 py-1 rounded-full ${levelBg[skill.level]}`}
|
||||
>
|
||||
<span className={levelColors[skill.level]}>{skill.level}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h3 className="text-lg font-bold text-white mb-2">{skill.name}</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
{/* Progress Bar */}
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-400">Proficiency</span>
|
||||
<span className="text-green-400 font-medium">{skill.years}+ yrs</span>
|
||||
</div>
|
||||
|
||||
<div className="h-2 bg-gray-700 rounded-full overflow-hidden">
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
whileInView={{ width: `${(skill.years / 5) * 100}%` }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 1, delay: index * 0.1 + 0.3 }}
|
||||
className="h-full bg-gradient-to-r from-green-500 to-emerald-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
import React, { useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { skillsData } from '../data/skillsData';
|
||||
import { SkillCard } from './SkillCard';
|
||||
import AnimatedSection from '../../../shared/components/AnimatedSection';
|
||||
|
||||
type Category = 'All' | 'Frontend' | 'Backend' | 'Tools' | 'Design';
|
||||
|
||||
export const SkillsSection: React.FC = () => {
|
||||
const [selectedCategory, setSelectedCategory] = useState<Category>('All');
|
||||
|
||||
const categories: Category[] = ['All', 'Frontend', 'Backend', 'Tools', 'Design'];
|
||||
|
||||
const filteredSkills = skillsData.filter(
|
||||
(skill) => selectedCategory === 'All' || skill.category === selectedCategory
|
||||
);
|
||||
|
||||
return (
|
||||
<AnimatedSection id="skills" className="py-20 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Section Header */}
|
||||
<div className="text-center mb-16">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="inline-block px-4 py-2 rounded-full border border-green-500/50 bg-green-500/10 mb-6"
|
||||
>
|
||||
<span className="text-green-400 text-sm font-medium">My Expertise</span>
|
||||
</motion.div>
|
||||
|
||||
<h2 className="text-4xl sm:text-5xl font-bold text-white mb-4">
|
||||
Skills & Technologies
|
||||
</h2>
|
||||
<p className="text-gray-400 text-lg max-w-2xl mx-auto">
|
||||
A comprehensive overview of my technical skills and proficiency levels
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Category Filter */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
className="flex flex-wrap justify-center gap-3 mb-12"
|
||||
>
|
||||
{categories.map((category) => (
|
||||
<motion.button
|
||||
key={category}
|
||||
onClick={() => setSelectedCategory(category)}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className={`px-6 py-2 rounded-full font-medium transition-all duration-300 ${
|
||||
selectedCategory === category
|
||||
? 'bg-green-500 text-black'
|
||||
: 'border border-green-500/30 text-gray-300 hover:border-green-500/60'
|
||||
}`}
|
||||
>
|
||||
{category}
|
||||
</motion.button>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* Skills Grid */}
|
||||
<motion.div
|
||||
layout
|
||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
|
||||
>
|
||||
{filteredSkills.map((skill, index: number) => (
|
||||
<SkillCard key={skill.name} skill={skill} index={index} />
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
</AnimatedSection>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
import {
|
||||
Code,
|
||||
Zap,
|
||||
Layout,
|
||||
Database,
|
||||
GitBranch,
|
||||
Wind,
|
||||
Package,
|
||||
} from 'lucide-react';
|
||||
import type { Skill } from '../../../shared/types';
|
||||
|
||||
export const iconMap = {
|
||||
'React.js': Code,
|
||||
'JavaScript': Zap,
|
||||
'TypeScript': Code,
|
||||
'Next.js': Package,
|
||||
'Node.js': Database,
|
||||
'REST APIs': Code,
|
||||
'Git & GitHub': GitBranch,
|
||||
'Responsive Design': Layout,
|
||||
'Figma': Wind,
|
||||
'Tailwind CSS': Wind,
|
||||
};
|
||||
|
||||
export const skillsData: Array<Omit<Skill, 'icon'> & { iconName: keyof typeof iconMap }> = [
|
||||
{
|
||||
name: 'React.js',
|
||||
iconName: 'React.js',
|
||||
level: 'Expert',
|
||||
years: 3,
|
||||
category: 'Frontend',
|
||||
},
|
||||
{
|
||||
name: 'JavaScript',
|
||||
iconName: 'JavaScript',
|
||||
level: 'Expert',
|
||||
years: 4,
|
||||
category: 'Frontend',
|
||||
},
|
||||
{
|
||||
name: 'TypeScript',
|
||||
iconName: 'TypeScript',
|
||||
level: 'Advanced',
|
||||
years: 2,
|
||||
category: 'Frontend',
|
||||
},
|
||||
{
|
||||
name: 'Next.js',
|
||||
iconName: 'Next.js',
|
||||
level: 'Advanced',
|
||||
years: 2,
|
||||
category: 'Frontend',
|
||||
},
|
||||
{
|
||||
name: 'Node.js',
|
||||
iconName: 'Node.js',
|
||||
level: 'Intermediate',
|
||||
years: 2,
|
||||
category: 'Backend',
|
||||
},
|
||||
{
|
||||
name: 'REST APIs',
|
||||
iconName: 'REST APIs',
|
||||
level: 'Advanced',
|
||||
years: 3,
|
||||
category: 'Backend',
|
||||
},
|
||||
{
|
||||
name: 'Git & GitHub',
|
||||
iconName: 'Git & GitHub',
|
||||
level: 'Advanced',
|
||||
years: 4,
|
||||
category: 'Tools',
|
||||
},
|
||||
{
|
||||
name: 'Responsive Design',
|
||||
iconName: 'Responsive Design',
|
||||
level: 'Expert',
|
||||
years: 3,
|
||||
category: 'Design',
|
||||
},
|
||||
{
|
||||
name: 'Figma',
|
||||
iconName: 'Figma',
|
||||
level: 'Intermediate',
|
||||
years: 2,
|
||||
category: 'Design',
|
||||
},
|
||||
{
|
||||
name: 'Tailwind CSS',
|
||||
iconName: 'Tailwind CSS',
|
||||
level: 'Expert',
|
||||
years: 3,
|
||||
category: 'Frontend',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1 @@
|
||||
export { SkillsSection } from './components/SkillsSection';
|
||||
Reference in New Issue
Block a user