This commit is contained in:
Yolando
2026-03-28 19:48:52 +07:00
parent 0a25960e8f
commit 5b0254d71b
19 changed files with 2016 additions and 1102 deletions

View File

@@ -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;
}
}

View File

@@ -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>
);
}

View File

@@ -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 />
</>
);
}

View 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>
);
}

View 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>
);
}

View 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&apos;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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}