init
This commit is contained in:
@@ -1,26 +1,183 @@
|
||||
@import "tailwindcss";
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* ========== CSS VARIABLES / DESIGN TOKENS ========== */
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
/* Colors - Light Mode */
|
||||
--background: #fafafa;
|
||||
--foreground: #0a0a0a;
|
||||
--card: #ffffff;
|
||||
--card-foreground: #0a0a0a;
|
||||
--muted: #f0f0f0;
|
||||
--muted-foreground: #6b6b6b;
|
||||
--border: #e5e5e5;
|
||||
--ring: #0a0a0a;
|
||||
|
||||
/* Accent - Enterprise Blue-Violet */
|
||||
--accent: #6366f1;
|
||||
--accent-foreground: #ffffff;
|
||||
--accent-glow: rgba(99, 102, 241, 0.15);
|
||||
|
||||
/* Status */
|
||||
--success: #22c55e;
|
||||
--error: #ef4444;
|
||||
|
||||
/* Gradients */
|
||||
--gradient-primary: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%);
|
||||
--gradient-subtle: linear-gradient(135deg, rgba(99,102,241,0.08) 0%, rgba(168,85,247,0.08) 100%);
|
||||
--gradient-text: linear-gradient(135deg, #6366f1 0%, #a855f7 100%);
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
|
||||
--shadow-md: 0 4px 12px rgba(0,0,0,0.08);
|
||||
--shadow-lg: 0 8px 30px rgba(0,0,0,0.12);
|
||||
--shadow-glow: 0 0 40px rgba(99,102,241,0.15);
|
||||
|
||||
/* Glassmorphism */
|
||||
--glass-bg: rgba(255,255,255,0.6);
|
||||
--glass-border: rgba(255,255,255,0.3);
|
||||
--glass-blur: 16px;
|
||||
|
||||
/* Section padding */
|
||||
--section-py: 6rem;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
.dark {
|
||||
--background: #09090b;
|
||||
--foreground: #fafafa;
|
||||
--card: #18181b;
|
||||
--card-foreground: #fafafa;
|
||||
--muted: #27272a;
|
||||
--muted-foreground: #a1a1aa;
|
||||
--border: #27272a;
|
||||
--ring: #fafafa;
|
||||
|
||||
--accent-glow: rgba(99, 102, 241, 0.25);
|
||||
|
||||
--shadow-sm: 0 1px 2px rgba(0,0,0,0.3);
|
||||
--shadow-md: 0 4px 12px rgba(0,0,0,0.4);
|
||||
--shadow-lg: 0 8px 30px rgba(0,0,0,0.5);
|
||||
--shadow-glow: 0 0 60px rgba(99,102,241,0.2);
|
||||
|
||||
--glass-bg: rgba(24,24,27,0.6);
|
||||
--glass-border: rgba(255,255,255,0.08);
|
||||
|
||||
--gradient-subtle: linear-gradient(135deg, rgba(99,102,241,0.12) 0%, rgba(168,85,247,0.12) 100%);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
/* ========== BASE STYLES ========== */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--muted-foreground) transparent;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-family: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--accent);
|
||||
color: var(--accent-foreground);
|
||||
}
|
||||
|
||||
/* ========== UTILITY CLASSES ========== */
|
||||
.gradient-text {
|
||||
background: var(--gradient-text);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.glass {
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(var(--glass-blur));
|
||||
-webkit-backdrop-filter: blur(var(--glass-blur));
|
||||
border: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
.gradient-border {
|
||||
position: relative;
|
||||
}
|
||||
.gradient-border::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
padding: 1px;
|
||||
border-radius: inherit;
|
||||
background: var(--gradient-primary);
|
||||
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
mask-composite: exclude;
|
||||
-webkit-mask-composite: xor;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.section-padding {
|
||||
padding-top: var(--section-py);
|
||||
padding-bottom: var(--section-py);
|
||||
}
|
||||
|
||||
/* Noise texture overlay for premium feel */
|
||||
.noise::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.03'/%3E%3C/svg%3E");
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* Grid pattern background */
|
||||
.grid-pattern {
|
||||
background-image:
|
||||
linear-gradient(var(--border) 1px, transparent 1px),
|
||||
linear-gradient(90deg, var(--border) 1px, transparent 1px);
|
||||
background-size: 60px 60px;
|
||||
}
|
||||
|
||||
/* Animated gradient orbs */
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0px) rotate(0deg); }
|
||||
33% { transform: translateY(-20px) rotate(5deg); }
|
||||
66% { transform: translateY(10px) rotate(-3deg); }
|
||||
}
|
||||
|
||||
@keyframes pulse-glow {
|
||||
0%, 100% { opacity: 0.4; transform: scale(1); }
|
||||
50% { opacity: 0.7; transform: scale(1.05); }
|
||||
}
|
||||
|
||||
.animate-float {
|
||||
animation: float 8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.animate-pulse-glow {
|
||||
animation: pulse-glow 4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Timeline connector line */
|
||||
.timeline-line {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
background: linear-gradient(to bottom, transparent, var(--accent), transparent);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.timeline-line {
|
||||
left: 1.5rem;
|
||||
}
|
||||
:root {
|
||||
--section-py: 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import { ThemeProvider } from "@/shared/components/theme-provider";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
@@ -13,8 +14,25 @@ const geistMono = Geist_Mono({
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: "Yolando — Backend Developer | Enterprise Banking Systems",
|
||||
description:
|
||||
"Building Secure, Scalable, Enterprise-Grade Systems. 3+ Years in Banking Technology. Specializing in Java Spring Boot, Microservices, and Enterprise Security.",
|
||||
keywords: [
|
||||
"Backend Developer",
|
||||
"Java",
|
||||
"Spring Boot",
|
||||
"Microservices",
|
||||
"Banking",
|
||||
"Enterprise",
|
||||
"Portfolio",
|
||||
],
|
||||
authors: [{ name: "Yolando" }],
|
||||
openGraph: {
|
||||
title: "Yolando — Backend Developer | Enterprise Banking Systems",
|
||||
description:
|
||||
"Building Secure, Scalable, Enterprise-Grade Systems. 3+ Years in Banking Technology.",
|
||||
type: "website",
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
@@ -25,9 +43,12 @@ export default function RootLayout({
|
||||
return (
|
||||
<html
|
||||
lang="en"
|
||||
suppressHydrationWarning
|
||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
||||
>
|
||||
<body className="min-h-full flex flex-col">{children}</body>
|
||||
<body className="min-h-full flex flex-col">
|
||||
<ThemeProvider>{children}</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,65 +1,23 @@
|
||||
import Image from "next/image";
|
||||
import { Navbar } from "@/shared/components/navbar";
|
||||
import { Footer } from "@/shared/components/footer";
|
||||
import { HeroSection } from "@/features/hero/hero-section";
|
||||
import { ExperienceSection } from "@/features/experience/experience-section";
|
||||
import { TechStackSection } from "@/features/skills/tech-stack-section";
|
||||
import { ProjectsSection } from "@/features/projects/projects-section";
|
||||
import { ContactSection } from "@/features/messages/contact-section";
|
||||
|
||||
export default function Home() {
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={100}
|
||||
height={20}
|
||||
priority
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
||||
To get started, edit the page.tsx file.
|
||||
</h1>
|
||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
||||
Looking for a starting point or more instructions? Head over to{" "}
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Templates
|
||||
</a>{" "}
|
||||
or the{" "}
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Learning
|
||||
</a>{" "}
|
||||
center.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Deploy Now
|
||||
</a>
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
</div>
|
||||
<>
|
||||
<Navbar />
|
||||
<main className="flex-1">
|
||||
<HeroSection />
|
||||
<ExperienceSection />
|
||||
<TechStackSection />
|
||||
<ProjectsSection />
|
||||
<ContactSection />
|
||||
</main>
|
||||
</div>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
137
src/features/experience/experience-section.tsx
Normal file
137
src/features/experience/experience-section.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
"use client";
|
||||
|
||||
import { AnimatedSection } from "@/shared/components/animated-section";
|
||||
import { SectionHeading } from "@/shared/components/section-heading";
|
||||
import { Briefcase, Award, Rocket, Code2, Building2 } from "lucide-react";
|
||||
|
||||
interface TimelineItem {
|
||||
year: string;
|
||||
title: string;
|
||||
company: string;
|
||||
description: string;
|
||||
achievements: string[];
|
||||
icon: React.ReactNode;
|
||||
}
|
||||
|
||||
const timelineData: TimelineItem[] = [
|
||||
{
|
||||
year: "2024 — Present",
|
||||
title: "Senior Backend Developer",
|
||||
company: "Enterprise Banking Corp",
|
||||
description:
|
||||
"Leading microservices architecture design and implementation for core banking platform.",
|
||||
achievements: [
|
||||
"Architected event-driven system processing 500K+ transactions/day",
|
||||
"Reduced API response time by 40% through caching optimization",
|
||||
"Mentored team of 4 junior developers",
|
||||
],
|
||||
icon: <Rocket size={20} />,
|
||||
},
|
||||
{
|
||||
year: "2023 — 2024",
|
||||
title: "Backend Developer",
|
||||
company: "Digital Banking Solutions",
|
||||
description:
|
||||
"Developed and maintained Spring Boot microservices for payment processing and customer management.",
|
||||
achievements: [
|
||||
"Built real-time notification service with Apache Kafka",
|
||||
"Implemented OAuth2 + JWT authentication system",
|
||||
"Achieved 99.9% uptime on production services",
|
||||
],
|
||||
icon: <Code2 size={20} />,
|
||||
},
|
||||
{
|
||||
year: "2021 — 2023",
|
||||
title: "Junior Backend Developer",
|
||||
company: "FinTech Startup",
|
||||
description:
|
||||
"Started career building REST APIs and database management for financial applications.",
|
||||
achievements: [
|
||||
"Designed PostgreSQL schema handling 1M+ records",
|
||||
"Created automated CI/CD pipeline with Jenkins",
|
||||
"Developed unit & integration test coverage to 85%",
|
||||
],
|
||||
icon: <Building2 size={20} />,
|
||||
},
|
||||
];
|
||||
|
||||
export function ExperienceSection() {
|
||||
return (
|
||||
<section id="experience" className="section-padding relative">
|
||||
<div className="max-w-5xl mx-auto px-6">
|
||||
<AnimatedSection>
|
||||
<SectionHeading
|
||||
badge="Career Journey"
|
||||
title="Experience & Evolution"
|
||||
subtitle="A timeline of building enterprise-grade systems in the banking technology industry."
|
||||
/>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Timeline */}
|
||||
<div className="relative">
|
||||
{/* Vertical line */}
|
||||
<div className="timeline-line" />
|
||||
|
||||
<div className="space-y-12 md:space-y-16">
|
||||
{timelineData.map((item, index) => (
|
||||
<AnimatedSection key={item.year} delay={index * 0.15}>
|
||||
<div
|
||||
className={`relative flex flex-col md:flex-row items-start gap-6 md:gap-12 ${
|
||||
index % 2 === 0 ? "md:flex-row" : "md:flex-row-reverse"
|
||||
}`}
|
||||
>
|
||||
{/* Content */}
|
||||
<div
|
||||
className={`flex-1 ml-12 md:ml-0 ${
|
||||
index % 2 === 0 ? "md:text-right" : "md:text-left"
|
||||
}`}
|
||||
>
|
||||
<span className="inline-block font-mono text-xs text-accent font-semibold tracking-wider uppercase mb-2">
|
||||
{item.year}
|
||||
</span>
|
||||
<h3 className="text-xl font-bold mb-1">{item.title}</h3>
|
||||
<p className="text-muted-foreground text-sm font-medium mb-3 flex items-center gap-1.5 md:justify-start">
|
||||
<Briefcase size={14} className="text-accent/60" />
|
||||
{item.company}
|
||||
</p>
|
||||
<p className="text-muted-foreground text-sm leading-relaxed mb-4">
|
||||
{item.description}
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
{item.achievements.map((achievement) => (
|
||||
<div
|
||||
key={achievement}
|
||||
className={`flex items-start gap-2 text-sm ${
|
||||
index % 2 === 0
|
||||
? "md:flex-row-reverse md:text-right"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<Award
|
||||
size={14}
|
||||
className="text-accent mt-0.5 flex-shrink-0"
|
||||
/>
|
||||
<span className="text-muted-foreground">
|
||||
{achievement}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Center dot */}
|
||||
<div className="absolute left-0 md:left-1/2 md:-translate-x-1/2 w-12 h-12 rounded-xl bg-card border-2 border-accent/30 flex items-center justify-center text-accent shadow-lg shadow-accent/10 z-10">
|
||||
{item.icon}
|
||||
</div>
|
||||
|
||||
{/* Empty space for other side */}
|
||||
<div className="hidden md:block flex-1" />
|
||||
</div>
|
||||
</AnimatedSection>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
122
src/features/hero/hero-section.tsx
Normal file
122
src/features/hero/hero-section.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { ArrowDown, FileText, Send } from "lucide-react";
|
||||
|
||||
export function HeroSection() {
|
||||
return (
|
||||
<section
|
||||
id="hero"
|
||||
className="relative min-h-screen flex items-center justify-center overflow-hidden"
|
||||
>
|
||||
{/* Background effects */}
|
||||
<div className="absolute inset-0 grid-pattern opacity-30" />
|
||||
|
||||
{/* Gradient orbs */}
|
||||
<div className="absolute top-1/4 -left-32 w-96 h-96 rounded-full bg-accent/20 blur-[120px] animate-pulse-glow" />
|
||||
<div className="absolute bottom-1/4 -right-32 w-96 h-96 rounded-full bg-purple-500/15 blur-[120px] animate-pulse-glow" style={{ animationDelay: "2s" }} />
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] rounded-full bg-indigo-500/5 blur-[100px]" />
|
||||
|
||||
<div className="relative z-10 max-w-5xl mx-auto px-6 text-center">
|
||||
{/* Status badge */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
>
|
||||
<span className="inline-flex items-center gap-2 px-4 py-2 rounded-full text-xs font-mono font-medium bg-success/10 text-success border border-success/20 mb-8">
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-success opacity-75" />
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-success" />
|
||||
</span>
|
||||
Available for opportunities
|
||||
</span>
|
||||
</motion.div>
|
||||
|
||||
{/* Main heading */}
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.7, delay: 0.3 }}
|
||||
className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold tracking-tight leading-[1.1] mb-6"
|
||||
>
|
||||
Building{" "}
|
||||
<span className="gradient-text">Secure, Scalable</span>
|
||||
<br />
|
||||
Enterprise-Grade Systems
|
||||
</motion.h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.7, delay: 0.5 }}
|
||||
className="text-lg md:text-xl text-muted-foreground max-w-2xl mx-auto mb-10 leading-relaxed"
|
||||
>
|
||||
<span className="font-mono text-accent font-semibold">3+ Years</span>{" "}
|
||||
in Banking Technology.{" "}
|
||||
<span className="text-foreground">Backend Developer</span> specializing in
|
||||
Java Spring Boot, Microservices Architecture, and Enterprise Security.
|
||||
</motion.p>
|
||||
|
||||
{/* CTA buttons */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.7, delay: 0.7 }}
|
||||
className="flex flex-col sm:flex-row items-center justify-center gap-4"
|
||||
>
|
||||
<a
|
||||
href="#contact"
|
||||
className="group inline-flex items-center gap-2 px-7 py-3.5 rounded-xl bg-gradient-to-r from-accent to-purple-500 text-white font-semibold text-sm shadow-lg shadow-accent/25 hover:shadow-accent/40 hover:scale-105 transition-all duration-300"
|
||||
>
|
||||
<Send size={16} />
|
||||
Get in Touch
|
||||
</a>
|
||||
<a
|
||||
href="#projects"
|
||||
className="inline-flex items-center gap-2 px-7 py-3.5 rounded-xl font-semibold text-sm border border-border hover:bg-muted/50 transition-all duration-300 hover:scale-105"
|
||||
>
|
||||
<FileText size={16} />
|
||||
View Projects
|
||||
</a>
|
||||
</motion.div>
|
||||
|
||||
{/* Tech badges */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 1, delay: 1 }}
|
||||
className="mt-16 flex flex-wrap items-center justify-center gap-3"
|
||||
>
|
||||
{["Java", "Spring Boot", "Kafka", "PostgreSQL", "Docker", "Kubernetes"].map((tech) => (
|
||||
<span
|
||||
key={tech}
|
||||
className="px-3 py-1.5 rounded-lg text-xs font-mono text-muted-foreground bg-muted/50 border border-border/50 hover:border-accent/30 hover:text-accent transition-all duration-300"
|
||||
>
|
||||
{tech}
|
||||
</span>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Scroll indicator */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 1, delay: 1.5 }}
|
||||
className="absolute bottom-8 left-1/2 -translate-x-1/2"
|
||||
>
|
||||
<a href="#experience" className="flex flex-col items-center gap-2 text-muted-foreground hover:text-accent transition-colors">
|
||||
<span className="text-xs font-mono">Scroll</span>
|
||||
<motion.div
|
||||
animate={{ y: [0, 8, 0] }}
|
||||
transition={{ duration: 1.5, repeat: Infinity }}
|
||||
>
|
||||
<ArrowDown size={16} />
|
||||
</motion.div>
|
||||
</a>
|
||||
</motion.div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
153
src/features/messages/contact-section.tsx
Normal file
153
src/features/messages/contact-section.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { AnimatedSection } from "@/shared/components/animated-section";
|
||||
import { SectionHeading } from "@/shared/components/section-heading";
|
||||
import { Send, Mail, User, MessageSquare, CheckCircle, Loader2 } from "lucide-react";
|
||||
|
||||
export function ContactSection() {
|
||||
const [formState, setFormState] = useState<"idle" | "loading" | "success">("idle");
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
email: "",
|
||||
message: "",
|
||||
});
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setFormState("loading");
|
||||
|
||||
// Simulate submission (will connect to Server Action in Phase 2)
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
setFormState("success");
|
||||
|
||||
setTimeout(() => {
|
||||
setFormState("idle");
|
||||
setFormData({ name: "", email: "", message: "" });
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
return (
|
||||
<section id="contact" className="section-padding relative bg-muted/30">
|
||||
<div className="absolute inset-0 grid-pattern opacity-20" />
|
||||
|
||||
<div className="relative max-w-4xl mx-auto px-6">
|
||||
<AnimatedSection>
|
||||
<SectionHeading
|
||||
badge="Let's Connect"
|
||||
title="Get in Touch"
|
||||
subtitle="Interested in working together? Whether you're a recruiter, hiring manager, or potential collaborator — I'd love to hear from you."
|
||||
/>
|
||||
</AnimatedSection>
|
||||
|
||||
<AnimatedSection delay={0.2}>
|
||||
<div className="relative p-8 md:p-10 rounded-2xl bg-card border border-border/50 shadow-lg">
|
||||
{/* Success overlay */}
|
||||
{formState === "success" && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-card/95 rounded-2xl z-10">
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 rounded-full bg-success/10 flex items-center justify-center mx-auto mb-4">
|
||||
<CheckCircle size={32} className="text-success" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-2">Message Sent!</h3>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Thank you for reaching out. I'll get back to you soon.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Name */}
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="contact-name"
|
||||
className="flex items-center gap-2 text-sm font-medium"
|
||||
>
|
||||
<User size={14} className="text-accent" />
|
||||
Full Name
|
||||
</label>
|
||||
<input
|
||||
id="contact-name"
|
||||
type="text"
|
||||
required
|
||||
value={formData.name}
|
||||
onChange={(e) =>
|
||||
setFormData((prev) => ({ ...prev, name: e.target.value }))
|
||||
}
|
||||
placeholder="John Doe"
|
||||
className="w-full px-4 py-3 rounded-xl bg-muted/50 border border-border/50 text-sm placeholder:text-muted-foreground/50 focus:outline-none focus:ring-2 focus:ring-accent/50 focus:border-accent/50 transition-all"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Email */}
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="contact-email"
|
||||
className="flex items-center gap-2 text-sm font-medium"
|
||||
>
|
||||
<Mail size={14} className="text-accent" />
|
||||
Email Address
|
||||
</label>
|
||||
<input
|
||||
id="contact-email"
|
||||
type="email"
|
||||
required
|
||||
value={formData.email}
|
||||
onChange={(e) =>
|
||||
setFormData((prev) => ({ ...prev, email: e.target.value }))
|
||||
}
|
||||
placeholder="john@company.com"
|
||||
className="w-full px-4 py-3 rounded-xl bg-muted/50 border border-border/50 text-sm placeholder:text-muted-foreground/50 focus:outline-none focus:ring-2 focus:ring-accent/50 focus:border-accent/50 transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Message */}
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="contact-message"
|
||||
className="flex items-center gap-2 text-sm font-medium"
|
||||
>
|
||||
<MessageSquare size={14} className="text-accent" />
|
||||
Message
|
||||
</label>
|
||||
<textarea
|
||||
id="contact-message"
|
||||
required
|
||||
rows={5}
|
||||
value={formData.message}
|
||||
onChange={(e) =>
|
||||
setFormData((prev) => ({ ...prev, message: e.target.value }))
|
||||
}
|
||||
placeholder="Tell me about the opportunity or project you have in mind..."
|
||||
className="w-full px-4 py-3 rounded-xl bg-muted/50 border border-border/50 text-sm placeholder:text-muted-foreground/50 focus:outline-none focus:ring-2 focus:ring-accent/50 focus:border-accent/50 transition-all resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Submit */}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={formState === "loading"}
|
||||
className="w-full md:w-auto inline-flex items-center justify-center gap-2 px-8 py-3.5 rounded-xl bg-gradient-to-r from-accent to-purple-500 text-white font-semibold text-sm shadow-lg shadow-accent/25 hover:shadow-accent/40 hover:scale-[1.02] transition-all duration-300 disabled:opacity-60 disabled:cursor-not-allowed disabled:hover:scale-100"
|
||||
>
|
||||
{formState === "loading" ? (
|
||||
<>
|
||||
<Loader2 size={16} className="animate-spin" />
|
||||
Sending...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Send size={16} />
|
||||
Send Message
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</AnimatedSection>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
224
src/features/projects/projects-section.tsx
Normal file
224
src/features/projects/projects-section.tsx
Normal file
@@ -0,0 +1,224 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { AnimatedSection } from "@/shared/components/animated-section";
|
||||
import { SectionHeading } from "@/shared/components/section-heading";
|
||||
import {
|
||||
ExternalLink,
|
||||
GitFork,
|
||||
TrendingUp,
|
||||
Server,
|
||||
Smartphone,
|
||||
Globe,
|
||||
Layers,
|
||||
} from "lucide-react";
|
||||
|
||||
interface Project {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
category: "backend" | "frontend" | "mobile";
|
||||
tags: string[];
|
||||
metrics?: string;
|
||||
repoUrl?: string;
|
||||
liveUrl?: string;
|
||||
}
|
||||
|
||||
const projects: Project[] = [
|
||||
{
|
||||
id: "1",
|
||||
title: "Core Banking API Gateway",
|
||||
description:
|
||||
"High-performance API Gateway handling 500K+ daily transactions with rate limiting, circuit breaker pattern, and distributed tracing across 12 microservices.",
|
||||
category: "backend",
|
||||
tags: ["Spring Boot", "Kafka", "Redis", "Docker"],
|
||||
metrics: "Increased throughput by 40%, 99.9% uptime",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "Payment Processing Engine",
|
||||
description:
|
||||
"Event-driven payment system with Saga pattern for distributed transactions, supporting real-time transfers, bill payments, and batch processing.",
|
||||
category: "backend",
|
||||
tags: ["Java", "Kafka", "PostgreSQL", "gRPC"],
|
||||
metrics: "Processing 200K+ payments/day",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
title: "Customer Onboarding Portal",
|
||||
description:
|
||||
"Modern onboarding portal with multi-step KYC verification, document upload, and real-time status tracking for banking customers.",
|
||||
category: "frontend",
|
||||
tags: ["React", "Next.js", "TypeScript", "Tailwind"],
|
||||
repoUrl: "#",
|
||||
liveUrl: "#",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
title: "Internal Dashboard",
|
||||
description:
|
||||
"Admin dashboard for monitoring API performance, user analytics, and system health with real-time WebSocket updates.",
|
||||
category: "frontend",
|
||||
tags: ["React", "Chart.js", "WebSocket", "REST"],
|
||||
repoUrl: "#",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
title: "Mobile Banking App",
|
||||
description:
|
||||
"Cross-platform mobile banking application with biometric authentication, push notifications, and offline transaction history.",
|
||||
category: "mobile",
|
||||
tags: ["React Native", "TypeScript", "Redux"],
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
title: "Authentication Microservice",
|
||||
description:
|
||||
"Centralized auth service with OAuth2, JWT, MFA, and session management. Supports SSO across 8 internal applications.",
|
||||
category: "backend",
|
||||
tags: ["Spring Security", "OAuth2", "JWT", "Redis"],
|
||||
metrics: "Securing 50K+ active users",
|
||||
},
|
||||
];
|
||||
|
||||
const filters = [
|
||||
{ value: "all", label: "All Projects", icon: <Layers size={16} /> },
|
||||
{ value: "backend", label: "Backend", icon: <Server size={16} /> },
|
||||
{ value: "frontend", label: "Frontend", icon: <Globe size={16} /> },
|
||||
{ value: "mobile", label: "Mobile", icon: <Smartphone size={16} /> },
|
||||
];
|
||||
|
||||
export function ProjectsSection() {
|
||||
const [activeFilter, setActiveFilter] = useState("all");
|
||||
|
||||
const filteredProjects =
|
||||
activeFilter === "all"
|
||||
? projects
|
||||
: projects.filter((p) => p.category === activeFilter);
|
||||
|
||||
return (
|
||||
<section id="projects" className="section-padding relative">
|
||||
<div className="max-w-6xl mx-auto px-6">
|
||||
<AnimatedSection>
|
||||
<SectionHeading
|
||||
badge="Portfolio"
|
||||
title="Projects & Case Studies"
|
||||
subtitle="Real-world enterprise solutions built for scale, security, and reliability."
|
||||
/>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Filter tabs */}
|
||||
<AnimatedSection delay={0.1}>
|
||||
<div className="flex flex-wrap items-center justify-center gap-2 mb-12">
|
||||
{filters.map((filter) => (
|
||||
<button
|
||||
key={filter.value}
|
||||
onClick={() => setActiveFilter(filter.value)}
|
||||
className={`inline-flex items-center gap-2 px-4 py-2.5 rounded-xl text-sm font-medium transition-all duration-300 ${activeFilter === filter.value
|
||||
? "bg-accent text-accent-foreground shadow-lg shadow-accent/25"
|
||||
: "bg-muted/50 text-muted-foreground border border-border/50 hover:border-accent/30 hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
{filter.icon}
|
||||
{filter.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</AnimatedSection>
|
||||
|
||||
{/* Projects grid */}
|
||||
<motion.div
|
||||
layout
|
||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5"
|
||||
>
|
||||
<AnimatePresence mode="popLayout">
|
||||
{filteredProjects.map((project) => (
|
||||
<motion.div
|
||||
key={project.id}
|
||||
layout
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.9 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<div className="group relative h-full flex flex-col p-6 rounded-2xl bg-card border border-border/50 hover:border-accent/30 transition-all duration-500 hover:shadow-lg hover:shadow-accent/5">
|
||||
{/* Category badge */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<span
|
||||
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-[10px] font-mono font-semibold uppercase tracking-wider ${project.category === "backend"
|
||||
? "bg-blue-500/10 text-blue-500 dark:text-blue-400"
|
||||
: project.category === "frontend"
|
||||
? "bg-violet-500/10 text-violet-500 dark:text-violet-400"
|
||||
: "bg-orange-500/10 text-orange-500 dark:text-orange-400"
|
||||
}`}
|
||||
>
|
||||
{project.category === "backend" ? (
|
||||
<Server size={12} />
|
||||
) : project.category === "frontend" ? (
|
||||
<Globe size={12} />
|
||||
) : (
|
||||
<Smartphone size={12} />
|
||||
)}
|
||||
{project.category}
|
||||
</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{project.repoUrl && (
|
||||
<a
|
||||
href={project.repoUrl}
|
||||
className="p-1.5 rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors"
|
||||
aria-label="GitHub"
|
||||
>
|
||||
<GitFork size={14} />
|
||||
</a>
|
||||
)}
|
||||
{project.liveUrl && (
|
||||
<a
|
||||
href={project.liveUrl}
|
||||
className="p-1.5 rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors"
|
||||
aria-label="Live demo"
|
||||
>
|
||||
<ExternalLink size={14} />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Title & description */}
|
||||
<h3 className="text-lg font-bold mb-2 group-hover:text-accent transition-colors">
|
||||
{project.title}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed mb-4 flex-1">
|
||||
{project.description}
|
||||
</p>
|
||||
|
||||
{/* Metrics badge */}
|
||||
{project.metrics && (
|
||||
<div className="flex items-center gap-2 mb-4 px-3 py-2 rounded-lg bg-success/5 border border-success/10">
|
||||
<TrendingUp size={14} className="text-success" />
|
||||
<span className="text-xs font-mono font-medium text-success">
|
||||
{project.metrics}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tags */}
|
||||
<div className="flex flex-wrap gap-1.5 mt-auto">
|
||||
{project.tags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="px-2 py-1 rounded-md text-[10px] font-mono bg-muted/50 text-muted-foreground border border-border/30"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
141
src/features/skills/tech-stack-section.tsx
Normal file
141
src/features/skills/tech-stack-section.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
"use client";
|
||||
|
||||
import { AnimatedSection } from "@/shared/components/animated-section";
|
||||
import { SectionHeading } from "@/shared/components/section-heading";
|
||||
import {
|
||||
Database,
|
||||
Server,
|
||||
Smartphone,
|
||||
Globe,
|
||||
Container,
|
||||
Shield,
|
||||
Cpu,
|
||||
Layers,
|
||||
GitBranch,
|
||||
MonitorSmartphone,
|
||||
Cloud,
|
||||
Workflow,
|
||||
} from "lucide-react";
|
||||
|
||||
interface TechItem {
|
||||
name: string;
|
||||
icon: React.ReactNode;
|
||||
}
|
||||
|
||||
interface TechCategory {
|
||||
title: string;
|
||||
description: string;
|
||||
items: TechItem[];
|
||||
accent: string;
|
||||
}
|
||||
|
||||
const techCategories: TechCategory[] = [
|
||||
{
|
||||
title: "Enterprise Backend",
|
||||
description: "Core systems & microservices",
|
||||
accent: "from-blue-500 to-cyan-500",
|
||||
items: [
|
||||
{ name: "Java", icon: <Cpu size={22} /> },
|
||||
{ name: "Spring Boot", icon: <Layers size={22} /> },
|
||||
{ name: "Apache Kafka", icon: <Workflow size={22} /> },
|
||||
{ name: "REST API", icon: <Server size={22} /> },
|
||||
{ name: "gRPC", icon: <GitBranch size={22} /> },
|
||||
{ name: "Spring Security", icon: <Shield size={22} /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Database & Infrastructure",
|
||||
description: "Data management & DevOps",
|
||||
accent: "from-emerald-500 to-teal-500",
|
||||
items: [
|
||||
{ name: "PostgreSQL", icon: <Database size={22} /> },
|
||||
{ name: "Redis", icon: <Database size={22} /> },
|
||||
{ name: "Docker", icon: <Container size={22} /> },
|
||||
{ name: "Kubernetes", icon: <Cloud size={22} /> },
|
||||
{ name: "Jenkins CI/CD", icon: <GitBranch size={22} /> },
|
||||
{ name: "Nginx", icon: <Server size={22} /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Frontend Development",
|
||||
description: "Modern web interfaces",
|
||||
accent: "from-violet-500 to-purple-500",
|
||||
items: [
|
||||
{ name: "React / Next.js", icon: <Globe size={22} /> },
|
||||
{ name: "TypeScript", icon: <Cpu size={22} /> },
|
||||
{ name: "Tailwind CSS", icon: <Layers size={22} /> },
|
||||
{ name: "Framer Motion", icon: <Workflow size={22} /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Mobile Development",
|
||||
description: "Cross-platform apps",
|
||||
accent: "from-orange-500 to-rose-500",
|
||||
items: [
|
||||
{ name: "React Native", icon: <Smartphone size={22} /> },
|
||||
{ name: "Flutter", icon: <MonitorSmartphone size={22} /> },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export function TechStackSection() {
|
||||
return (
|
||||
<section
|
||||
id="tech-stack"
|
||||
className="section-padding relative bg-muted/30"
|
||||
>
|
||||
{/* Subtle background */}
|
||||
<div className="absolute inset-0 grid-pattern opacity-20" />
|
||||
|
||||
<div className="relative max-w-6xl mx-auto px-6">
|
||||
<AnimatedSection>
|
||||
<SectionHeading
|
||||
badge="Tech Arsenal"
|
||||
title="Technology Stack"
|
||||
subtitle="A comprehensive toolkit forged through years of enterprise development, from backend infrastructure to mobile interfaces."
|
||||
/>
|
||||
</AnimatedSection>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{techCategories.map((category, catIndex) => (
|
||||
<AnimatedSection key={category.title} delay={catIndex * 0.1}>
|
||||
<div className="group relative h-full p-6 rounded-2xl bg-card border border-border/50 hover:border-accent/30 transition-all duration-500 hover:shadow-lg">
|
||||
{/* Category header */}
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div
|
||||
className={`w-10 h-10 rounded-xl bg-gradient-to-br ${category.accent} flex items-center justify-center text-white shadow-lg`}
|
||||
>
|
||||
<Layers size={18} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-bold text-sm">{category.title}</h3>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{category.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tech items grid */}
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||
{category.items.map((tech) => (
|
||||
<div
|
||||
key={tech.name}
|
||||
className="flex items-center gap-2.5 px-3 py-2.5 rounded-xl bg-muted/50 border border-border/30 hover:border-accent/30 hover:bg-accent/5 transition-all duration-300 group/item"
|
||||
>
|
||||
<span className="text-muted-foreground group-hover/item:text-accent transition-colors">
|
||||
{tech.icon}
|
||||
</span>
|
||||
<span className="text-xs font-mono font-medium truncate">
|
||||
{tech.name}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</AnimatedSection>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
35
src/shared/components/animated-section.tsx
Normal file
35
src/shared/components/animated-section.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import { useRef } from "react";
|
||||
import { motion, useInView } from "framer-motion";
|
||||
|
||||
interface AnimatedSectionProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
export function AnimatedSection({
|
||||
children,
|
||||
className = "",
|
||||
delay = 0,
|
||||
}: AnimatedSectionProps) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const isInView = useInView(ref, { once: true, margin: "-80px" });
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
ref={ref}
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 40 }}
|
||||
transition={{
|
||||
duration: 0.7,
|
||||
delay,
|
||||
ease: [0.25, 0.4, 0.25, 1],
|
||||
}}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
69
src/shared/components/footer.tsx
Normal file
69
src/shared/components/footer.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import { GitFork, Link2, Mail, ArrowUp } from "lucide-react";
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="relative border-t border-border/50 bg-card/50">
|
||||
<div className="max-w-6xl mx-auto px-6 py-12">
|
||||
<div className="flex flex-col md:flex-row items-center justify-between gap-6">
|
||||
{/* Brand */}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-accent to-purple-500 flex items-center justify-center text-white font-bold text-xs">
|
||||
A
|
||||
</div>
|
||||
<span className="font-mono font-bold tracking-tight">
|
||||
ando<span className="text-accent">.dev</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Social Links */}
|
||||
<div className="flex items-center gap-3">
|
||||
<a
|
||||
href="https://github.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-2.5 rounded-xl bg-muted/50 border border-border/50 hover:bg-accent hover:text-accent-foreground hover:border-accent transition-all duration-300 hover:scale-105"
|
||||
aria-label="GitHub"
|
||||
>
|
||||
<GitFork size={18} />
|
||||
</a>
|
||||
<a
|
||||
href="https://linkedin.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-2.5 rounded-xl bg-muted/50 border border-border/50 hover:bg-accent hover:text-accent-foreground hover:border-accent transition-all duration-300 hover:scale-105"
|
||||
aria-label="LinkedIn"
|
||||
>
|
||||
<Link2 size={18} />
|
||||
</a>
|
||||
<a
|
||||
href="mailto:hello@ando.dev"
|
||||
className="p-2.5 rounded-xl bg-muted/50 border border-border/50 hover:bg-accent hover:text-accent-foreground hover:border-accent transition-all duration-300 hover:scale-105"
|
||||
aria-label="Email"
|
||||
>
|
||||
<Mail size={18} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Back to top */}
|
||||
<a
|
||||
href="#"
|
||||
className="flex items-center gap-2 text-sm text-muted-foreground hover:text-accent transition-colors group"
|
||||
>
|
||||
Back to top
|
||||
<ArrowUp
|
||||
size={14}
|
||||
className="transition-transform group-hover:-translate-y-0.5"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 pt-6 border-t border-border/30 text-center">
|
||||
<p className="text-sm text-muted-foreground font-mono">
|
||||
© {new Date().getFullYear()} Yolando. Built with Next.js & crafted
|
||||
with purpose.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
103
src/shared/components/navbar.tsx
Normal file
103
src/shared/components/navbar.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { Menu, X } from "lucide-react";
|
||||
import { ThemeToggle } from "@/shared/components/theme-toggle";
|
||||
|
||||
const navLinks = [
|
||||
{ href: "#experience", label: "Experience" },
|
||||
{ href: "#tech-stack", label: "Tech Stack" },
|
||||
{ href: "#projects", label: "Projects" },
|
||||
{ href: "#contact", label: "Contact" },
|
||||
];
|
||||
|
||||
export function Navbar() {
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => setIsScrolled(window.scrollY > 20);
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<motion.header
|
||||
initial={{ y: -100 }}
|
||||
animate={{ y: 0 }}
|
||||
transition={{ duration: 0.6, ease: [0.25, 0.4, 0.25, 1] }}
|
||||
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-500 ${
|
||||
isScrolled
|
||||
? "glass shadow-lg border-b border-border/50"
|
||||
: "bg-transparent"
|
||||
}`}
|
||||
>
|
||||
<nav className="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between">
|
||||
{/* Logo */}
|
||||
<a href="#" className="flex items-center gap-2 group">
|
||||
<div className="w-9 h-9 rounded-lg bg-gradient-to-br from-accent to-purple-500 flex items-center justify-center text-white font-bold text-sm shadow-lg shadow-accent/25 group-hover:shadow-accent/40 transition-shadow">
|
||||
A
|
||||
</div>
|
||||
<span className="font-mono font-bold text-lg tracking-tight">
|
||||
ando<span className="text-accent">.dev</span>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
{/* Desktop Nav */}
|
||||
<div className="hidden md:flex items-center gap-1">
|
||||
{navLinks.map((link) => (
|
||||
<a
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className="relative px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors group"
|
||||
>
|
||||
{link.label}
|
||||
<span className="absolute bottom-0 left-1/2 -translate-x-1/2 w-0 h-0.5 bg-accent rounded-full transition-all duration-300 group-hover:w-6" />
|
||||
</a>
|
||||
))}
|
||||
<div className="ml-4 pl-4 border-l border-border/50">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<div className="flex md:hidden items-center gap-2">
|
||||
<ThemeToggle />
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="p-2.5 rounded-xl bg-muted/50 border border-border/50"
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
{isOpen ? <X size={18} /> : <Menu size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* Mobile Nav */}
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: "auto" }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
className="md:hidden glass border-t border-border/50 overflow-hidden"
|
||||
>
|
||||
<div className="px-6 py-4 flex flex-col gap-1">
|
||||
{navLinks.map((link) => (
|
||||
<a
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="px-4 py-3 text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted/50 rounded-lg transition-colors"
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</motion.header>
|
||||
);
|
||||
}
|
||||
25
src/shared/components/section-heading.tsx
Normal file
25
src/shared/components/section-heading.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
interface SectionHeadingProps {
|
||||
badge?: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
}
|
||||
|
||||
export function SectionHeading({ badge, title, subtitle }: SectionHeadingProps) {
|
||||
return (
|
||||
<div className="text-center mb-16">
|
||||
{badge && (
|
||||
<span className="inline-block px-4 py-1.5 rounded-full text-xs font-mono font-semibold tracking-wider uppercase bg-accent/10 text-accent border border-accent/20 mb-4">
|
||||
{badge}
|
||||
</span>
|
||||
)}
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold tracking-tight mb-4">
|
||||
{title}
|
||||
</h2>
|
||||
{subtitle && (
|
||||
<p className="text-muted-foreground text-lg max-w-2xl mx-auto leading-relaxed">
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
12
src/shared/components/theme-provider.tsx
Normal file
12
src/shared/components/theme-provider.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export function ThemeProvider({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<NextThemesProvider attribute="class" defaultTheme="dark" enableSystem>
|
||||
{children}
|
||||
</NextThemesProvider>
|
||||
);
|
||||
}
|
||||
40
src/shared/components/theme-toggle.tsx
Normal file
40
src/shared/components/theme-toggle.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
"use client";
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Sun, Moon, Monitor } from "lucide-react";
|
||||
|
||||
export function ThemeToggle() {
|
||||
const { theme, setTheme } = useTheme();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => setMounted(true), []);
|
||||
|
||||
if (!mounted) {
|
||||
return (
|
||||
<button className="p-2 rounded-lg bg-muted" aria-label="Toggle theme">
|
||||
<Monitor size={18} />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
||||
className="relative p-2.5 rounded-xl bg-muted/50 hover:bg-muted border border-border/50 transition-all duration-300 hover:scale-105 group"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
{theme === "dark" ? (
|
||||
<Sun
|
||||
size={18}
|
||||
className="text-amber-400 transition-transform duration-500 group-hover:rotate-90"
|
||||
/>
|
||||
) : (
|
||||
<Moon
|
||||
size={18}
|
||||
className="text-indigo-500 transition-transform duration-500 group-hover:-rotate-12"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user