chore: create initial project snapshot

Capture the portfolio source, design references, and production assets with repository-specific ignore rules.
This commit is contained in:
2026-09-04 18:23:26 +03:30
commit 75c9a62b37
131 changed files with 4171 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
.educationSection {
position: relative;
width: 1440px;
padding-top: 60px;
padding-bottom: 80px;
}
.card {
width: 1320px;
background-color: var(--bg-surface);
border: 1px solid var(--border-default);
border-radius: var(--radius-xl);
padding: 36px 44px;
display: flex;
gap: 56px;
box-shadow: 0 4px 20px var(--shadow-color);
transition: border-color 0.2s ease;
}
.card:hover {
border-color: var(--accent-default);
}
.crestCol {
width: 240px;
flex-shrink: 0;
display: flex;
flex-direction: column;
}
.crestContainer {
width: 180px;
height: 60px;
margin-bottom: 12px;
}
.crestImage {
object-fit: contain;
width: 100%;
height: 100%;
}
.institution {
font-size: var(--font-size-body-default);
line-height: var(--line-height-body-default);
font-weight: 700;
color: var(--text-primary);
margin-bottom: 4px;
}
.date {
font-size: var(--font-size-label-small);
line-height: var(--line-height-label-small);
font-weight: 700;
letter-spacing: var(--letter-spacing-eyebrow);
color: var(--text-muted);
text-transform: uppercase;
}
.detailsCol {
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.degreeTitle {
font-size: var(--font-size-h3);
line-height: var(--line-height-h3);
font-weight: 700;
color: var(--text-primary);
margin-bottom: 12px;
}
.description {
font-size: var(--font-size-body-default);
line-height: var(--line-height-body-large);
color: var(--text-secondary);
}

View File

@@ -0,0 +1,45 @@
'use client';
import React from 'react';
import Image from 'next/image';
import { education } from '@/data/education';
import styles from './EducationSection.module.css';
export const EducationSection: React.FC = () => {
return (
<section id="education" className={styles.educationSection}>
<div className="content-container">
{/* Section Header */}
<div className="section-header">
<span className="section-eyebrow">EDUCATION</span>
<div className="section-title-wrap">
<h2 className="section-title">Where I Built the Foundation</h2>
<div className="section-underline" />
</div>
</div>
{/* Degree Card */}
<div className={styles.card}>
<div className={styles.crestCol}>
<div className={styles.crestContainer}>
<Image
src={education.logo}
alt={education.institution}
width={180}
height={60}
className={styles.crestImage}
/>
</div>
<div className={styles.institution}>{education.institution}</div>
<div className={styles.date}>{education.date}</div>
</div>
<div className={styles.detailsCol}>
<h3 className={styles.degreeTitle}>{education.degree}</h3>
<p className={styles.description}>{education.description}</p>
</div>
</div>
</div>
</section>
);
};

View File

@@ -0,0 +1,184 @@
.experienceSection {
position: relative;
width: 1440px;
padding-top: 60px;
padding-bottom: 80px;
}
.timeline {
display: flex;
flex-direction: column;
position: relative;
margin-top: 20px;
}
.timelineItem {
display: flex;
position: relative;
min-height: 180px;
}
.spineColumn {
position: relative;
width: 40px;
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
}
.nodeDot {
width: 14px;
height: 14px;
border-radius: 50%;
background-color: var(--color-slate-400);
margin-top: 6px;
z-index: 2;
transition: all 0.2s ease;
}
.nodeCurrent {
background-color: var(--accent-default);
box-shadow: 0 0 0 4px rgba(61, 138, 250, 0.25);
}
.verticalLine {
position: absolute;
top: 20px;
bottom: 0;
width: 2px;
background-color: var(--border-default);
z-index: 1;
}
.itemContent {
display: flex;
flex-grow: 1;
padding-left: 20px;
padding-bottom: 56px;
gap: 48px;
}
.identityCol {
width: 380px;
flex-shrink: 0;
}
.metaRow {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 16px;
}
.dateBadge {
font-size: var(--font-size-label-small);
line-height: var(--line-height-label-small);
font-weight: 700;
letter-spacing: var(--letter-spacing-eyebrow);
color: var(--text-muted);
text-transform: uppercase;
}
.locationWrapper {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: var(--font-size-label-small);
color: var(--text-muted);
}
.locationIcon {
color: var(--text-muted);
}
.locationText {
font-weight: 500;
}
.roleIdentityRow {
display: flex;
align-items: flex-start;
gap: 16px;
}
.logoContainer {
width: 72px;
height: 72px;
border-radius: var(--radius-md);
overflow: hidden;
background-color: var(--bg-surface);
border: 1px solid var(--border-default);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.logoImage {
object-fit: contain;
width: 100%;
height: 100%;
}
.roleTitles {
display: flex;
flex-direction: column;
padding-top: 4px;
}
.roleTitle {
font-size: var(--font-size-h3);
line-height: var(--line-height-h3);
font-weight: 700;
color: var(--text-primary);
margin-bottom: 4px;
}
.companyName {
font-size: var(--font-size-body-default);
line-height: var(--line-height-body-default);
color: var(--action-link);
font-weight: 500;
}
.detailsCol {
flex-grow: 1;
max-width: 820px;
padding-top: 4px;
}
.summary {
font-size: var(--font-size-body-default);
line-height: var(--line-height-body-default);
color: var(--text-secondary);
margin-bottom: 16px;
}
.bulletList {
list-style: none;
display: flex;
flex-direction: column;
gap: 8px;
}
.bulletItem {
display: flex;
align-items: flex-start;
gap: 10px;
}
.bulletDot {
width: 5px;
height: 5px;
border-radius: 50%;
background-color: var(--text-muted);
margin-top: 10px;
flex-shrink: 0;
}
.bulletText {
font-size: var(--font-size-body-small);
line-height: var(--line-height-body-small);
color: var(--text-secondary);
}

View File

@@ -0,0 +1,88 @@
'use client';
import React from 'react';
import Image from 'next/image';
import { MapPin } from 'lucide-react';
import { experiences } from '@/data/experience';
import styles from './ExperienceSection.module.css';
export const ExperienceSection: React.FC = () => {
return (
<section id="experience" className={styles.experienceSection}>
<div className="content-container">
{/* Section Header */}
<div className="section-header">
<span className="section-eyebrow">EXPERIENCES</span>
<div className="section-title-wrap">
<h2 className="section-title">Where I&apos;ve Made an Impact</h2>
<div className="section-underline" />
</div>
</div>
{/* Timeline Container */}
<div className={styles.timeline}>
{experiences.map((item, index) => (
<div key={item.id} className={styles.timelineItem}>
{/* Vertical Spine & Node Indicator */}
<div className={styles.spineColumn}>
<div
className={`${styles.nodeDot} ${
item.isCurrent ? styles.nodeCurrent : ''
}`}
/>
{index < experiences.length - 1 && (
<div className={styles.verticalLine} />
)}
</div>
{/* Item Content Layout: Left Identity & Right Details */}
<div className={styles.itemContent}>
{/* Left Identity Column */}
<div className={styles.identityCol}>
<div className={styles.metaRow}>
<span className={styles.dateBadge}>{item.date}</span>
<div className={styles.locationWrapper}>
<MapPin size={14} className={styles.locationIcon} />
<span className={styles.locationText}>{item.location}</span>
</div>
</div>
<div className={styles.roleIdentityRow}>
<div className={styles.logoContainer}>
<Image
src={item.logo}
alt={`${item.company} logo`}
width={72}
height={72}
className={styles.logoImage}
/>
</div>
<div className={styles.roleTitles}>
<h3 className={styles.roleTitle}>{item.role}</h3>
<div className={styles.companyName}>{item.company}</div>
</div>
</div>
</div>
{/* Right Details Column */}
<div className={styles.detailsCol}>
<p className={styles.summary}>{item.summary}</p>
<ul className={styles.bulletList}>
{item.bullets.map((bullet, bIdx) => (
<li key={bIdx} className={styles.bulletItem}>
<span className={styles.bulletDot} />
<span className={styles.bulletText}>{bullet}</span>
</li>
))}
</ul>
</div>
</div>
</div>
))}
</div>
</div>
</section>
);
};

View File

@@ -0,0 +1,111 @@
.footerSection {
position: relative;
width: 1440px;
height: 480px;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.bgContainer {
position: absolute;
top: 0;
left: 0;
width: 1440px;
height: 480px;
z-index: 1;
}
.bgImage {
object-fit: cover;
object-position: bottom center;
}
.content {
position: relative;
z-index: 10;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
margin-top: -60px; /* aligns with visual layout */
}
.ctaTitle {
font-size: var(--font-size-display-footer);
line-height: var(--line-height-display-footer);
color: var(--text-primary);
margin-bottom: 12px;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.ctaSubtitle {
font-size: var(--font-size-body-large);
line-height: var(--line-height-body-large);
color: var(--text-secondary);
font-weight: 500;
letter-spacing: 0.04em;
margin-bottom: 40px;
}
.socialRow {
display: flex;
align-items: center;
gap: 48px;
}
.socialItem {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
color: var(--text-primary);
text-decoration: none;
transition: transform 0.2s ease;
}
.socialItem:hover {
transform: translateY(-4px);
}
.iconCircle {
display: flex;
align-items: center;
justify-content: center;
width: var(--size-social-button);
height: var(--size-social-button);
border-radius: 50%;
background: rgba(255, 255, 255, 0.6);
backdrop-filter: blur(12px);
border: 1px solid var(--border-default);
color: var(--text-primary);
box-shadow: 0 4px 12px var(--shadow-color);
transition: all 0.2s ease;
}
.socialItem:hover .iconCircle {
border-color: var(--accent-default);
color: var(--accent-default);
background: rgba(255, 255, 255, 0.9);
}
[data-theme="dark"] .iconCircle {
background: rgba(9, 28, 63, 0.7);
border-color: var(--border-default);
color: var(--text-primary);
}
[data-theme="dark"] .socialItem:hover .iconCircle {
background: rgba(16, 63, 127, 0.9);
border-color: var(--accent-default);
color: #ffffff;
}
.socialLabel {
font-size: var(--font-size-body-small);
line-height: var(--line-height-body-small);
font-weight: 600;
color: var(--text-primary);
}

View File

@@ -0,0 +1,92 @@
'use client';
import React from 'react';
import Image from 'next/image';
import { Phone, Send, Coffee } from 'lucide-react';
import { useTheme } from '@/context/ThemeContext';
import styles from './FooterSection.module.css';
export const FooterSection: React.FC = () => {
const { theme } = useTheme();
return (
<footer id="footer" className={styles.footerSection}>
{/* Background artwork */}
<div className={styles.bgContainer}>
<Image
src={
theme === 'dark'
? '/assets/footer-background-dark.png'
: '/assets/footer-background 1.png'
}
alt="Footer mountains background"
fill
className={styles.bgImage}
/>
</div>
{/* Content */}
<div className={styles.content}>
<h2 className={`font-display ${styles.ctaTitle}`}>
Let&apos;s build something meaningful.
</h2>
<p className={styles.ctaSubtitle}>
Open to opportunities and interesting projects.
</p>
{/* 4 Social / Contact Links */}
<div className={styles.socialRow}>
{/* Phone */}
<a href="tel:+989388263077" className={styles.socialItem}>
<div className={styles.iconCircle}>
<Phone size={24} />
</div>
<span className={styles.socialLabel}>09388263077</span>
</a>
{/* LinkedIn */}
<a
href="https://linkedin.com/in/amiirkhl"
target="_blank"
rel="noopener noreferrer"
className={styles.socialItem}
>
<div className={styles.iconCircle}>
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14m-.5 15.5v-5.3a3.26 3.26 0 0 0-3.26-3.26c-.85 0-1.84.52-2.28 1.3v-1.11h-2.79v8.37h2.79v-4.93c0-.77.62-1.4 1.39-1.4a1.4 1.4 0 0 1 1.4 1.4v4.93h2.75M6.46 8.76a1.64 1.64 0 0 0 1.64-1.64A1.64 1.64 0 0 0 6.46 5.5a1.64 1.64 0 0 0-1.64 1.62c0 .9.74 1.64 1.64 1.64m1.39 9.74v-8.37H5.07v8.37h2.78Z" />
</svg>
</div>
<span className={styles.socialLabel}>@amiirkhl</span>
</a>
{/* Git / Coffee */}
<a
href="https://git.amiirkhl.ir"
target="_blank"
rel="noopener noreferrer"
className={styles.socialItem}
>
<div className={styles.iconCircle}>
<Coffee size={24} />
</div>
<span className={styles.socialLabel}>git.amiirkhl.ir</span>
</a>
{/* Telegram */}
<a
href="https://t.me/amiirkhl"
target="_blank"
rel="noopener noreferrer"
className={styles.socialItem}
>
<div className={styles.iconCircle}>
<Send size={24} />
</div>
<span className={styles.socialLabel}>@amiirkhl</span>
</a>
</div>
</div>
</footer>
);
};

View File

@@ -0,0 +1,160 @@
.heroContentWrapper {
position: relative;
width: 1440px;
height: 680px;
z-index: 5;
}
.topBar {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
padding: 36px 60px 0 60px;
}
.iconButton {
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--size-icon-button);
height: var(--size-icon-button);
border-radius: 50%;
background: rgba(255, 255, 255, 0.4);
backdrop-filter: blur(10px);
border: 1px solid var(--border-default);
cursor: pointer;
transition: all 0.2s ease;
color: var(--text-primary);
}
.iconButton:hover {
background: rgba(255, 255, 255, 0.7);
transform: scale(1.05);
}
[data-theme="dark"] .iconButton {
background: rgba(9, 28, 63, 0.6);
border-color: var(--border-default);
color: var(--text-primary);
}
[data-theme="dark"] .iconButton:hover {
background: rgba(16, 63, 127, 0.8);
}
.controlIcon {
color: var(--text-primary);
}
.heroContent {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding-top: 10px;
}
.avatarWrapper {
margin-bottom: 24px;
}
.avatarRingOuter {
padding: 4px;
border-radius: 50%;
border: 3px solid var(--color-blue-700);
background: transparent;
display: inline-block;
box-shadow: 0 8px 24px var(--shadow-color);
}
[data-theme="dark"] .avatarRingOuter {
border-color: var(--color-blue-300);
}
.avatarContainer {
width: 192px;
height: 192px;
border-radius: 50%;
overflow: hidden;
position: relative;
border: 2px solid #ffffff;
}
.avatarImg {
object-fit: cover;
width: 100%;
height: 100%;
}
.heroTitle {
font-size: var(--font-size-display-hero);
line-height: var(--line-height-display-hero);
color: var(--text-primary);
margin-bottom: 12px;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.heroSubtitle {
font-size: 15px;
font-weight: 700;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--text-primary);
margin-bottom: 32px;
opacity: 0.9;
}
.actionButtons {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 36px;
}
.actionButtons a {
min-width: 110px;
}
.contactBar {
display: inline-flex;
align-items: center;
gap: 20px;
padding: 10px 24px;
border-radius: var(--radius-pill);
background: rgba(255, 255, 255, 0.4);
backdrop-filter: blur(12px);
border: 1px solid var(--border-default);
font-size: var(--font-size-body-small);
line-height: var(--line-height-body-small);
font-weight: 500;
color: var(--text-primary);
}
[data-theme="dark"] .contactBar {
background: rgba(9, 28, 63, 0.6);
border-color: var(--border-default);
}
.contactItem {
display: inline-flex;
align-items: center;
gap: 8px;
color: inherit;
transition: opacity 0.2s ease;
}
.contactItem:hover {
opacity: 0.8;
}
.contactIcon {
color: var(--accent-default);
}
.contactDivider {
width: 1px;
height: 16px;
background-color: var(--border-default);
}

View File

@@ -0,0 +1,102 @@
'use client';
import React from 'react';
import Image from 'next/image';
import { Moon, Sun, Languages, Mail, Phone, MapPin } from 'lucide-react';
import { useTheme } from '@/context/ThemeContext';
import styles from './HeroSection.module.css';
export const HeroSection: React.FC = () => {
const { theme, toggleTheme } = useTheme();
return (
<div className={styles.heroContentWrapper}>
{/* Top Floating Controls */}
<div className={styles.topBar}>
<button
onClick={toggleTheme}
className={styles.iconButton}
aria-label="Toggle theme"
title={`Switch to ${theme === 'light' ? 'Dark' : 'Light'} Mode`}
>
{theme === 'light' ? (
<Moon size={20} className={styles.controlIcon} />
) : (
<Sun size={20} className={styles.controlIcon} />
)}
</button>
<button
className={styles.iconButton}
aria-label="Switch Language"
title="Farsi version coming soon"
>
<Languages size={20} className={styles.controlIcon} />
</button>
</div>
{/* Hero Central Content */}
<div className={styles.heroContent}>
{/* Avatar with double ring */}
<div className={styles.avatarWrapper}>
<div className={styles.avatarRingOuter}>
<div className={styles.avatarContainer}>
<Image
src="/images/avatar.png"
alt="Amirhossein Khalili"
width={192}
height={192}
className={styles.avatarImg}
priority
/>
</div>
</div>
</div>
{/* Display Typography */}
<h1 className={`font-display ${styles.heroTitle}`}>
Hi, I&apos;m Amirhossein Khalili
</h1>
<p className={styles.heroSubtitle}>
A PASSIONATE PRODUCT ENGINEER
</p>
{/* Action Buttons */}
<div className={styles.actionButtons}>
<a href="#about" className="pill-btn pill-btn-outline">
About
</a>
<a href="#projects" className="pill-btn pill-btn-outline">
Work
</a>
<a href="#footer" className="pill-btn pill-btn-primary">
Contact
</a>
</div>
{/* Contact Info Strip */}
<div className={styles.contactBar}>
<a href="mailto:amirhosseinkhalili901@gmail.com" className={styles.contactItem}>
<Mail size={16} className={styles.contactIcon} />
<span>amirhosseinkhalili901@gmail.com</span>
</a>
<div className={styles.contactDivider} />
<a href="tel:+989388263077" className={styles.contactItem}>
<Phone size={16} className={styles.contactIcon} />
<span>+98 9388263077</span>
</a>
<div className={styles.contactDivider} />
<div className={styles.contactItem}>
<MapPin size={16} className={styles.contactIcon} />
<span>Karaj, Alborz, Iran</span>
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,118 @@
.milestonesSection {
position: relative;
width: 1440px;
padding-top: 60px;
padding-bottom: 80px;
}
.cardsList {
display: flex;
flex-direction: column;
gap: 24px;
}
.certCard {
width: 1320px;
background-color: var(--bg-surface);
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
padding: 24px 32px;
display: flex;
gap: 36px;
box-shadow: 0 4px 16px var(--shadow-color);
transition: all 0.25s ease;
position: relative;
}
.certCard:hover {
border-color: var(--accent-default);
transform: translateY(-2px);
box-shadow: 0 8px 24px var(--shadow-color);
}
.thumbnailContainer {
position: relative;
width: 200px;
height: 128px;
flex-shrink: 0;
border-radius: var(--radius-sm);
overflow: hidden;
border: 1px solid var(--border-default);
background-color: #f3f4f6;
}
.certImage {
object-fit: cover;
transition: transform 0.3s ease;
}
.certCard:hover .certImage {
transform: scale(1.02);
}
.certDetails {
flex-grow: 1;
display: flex;
flex-direction: column;
position: relative;
}
.titleRow {
margin-bottom: 4px;
}
.certTitle {
font-size: var(--font-size-h3);
line-height: var(--line-height-h3);
font-weight: 700;
color: var(--text-primary);
}
.issuerRow {
margin-bottom: 2px;
}
.issuerText {
font-size: var(--font-size-body-small);
line-height: var(--line-height-body-small);
color: var(--text-secondary);
font-weight: 500;
}
.dateBadge {
font-size: var(--font-size-label-small);
line-height: var(--line-height-label-small);
font-weight: 700;
letter-spacing: var(--letter-spacing-eyebrow);
color: var(--text-muted);
text-transform: uppercase;
margin-bottom: 12px;
}
.description {
font-size: var(--font-size-body-small);
line-height: var(--line-height-body-small);
color: var(--text-secondary);
max-width: 900px;
margin-bottom: 16px;
}
.actionRow {
margin-top: auto;
display: flex;
justify-content: flex-end;
}
.viewCertLink {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: var(--font-size-body-small);
font-weight: 600;
color: var(--action-link);
transition: gap 0.2s ease;
}
.viewCertLink:hover {
gap: 10px;
}

View File

@@ -0,0 +1,69 @@
'use client';
import React from 'react';
import Image from 'next/image';
import { ArrowUpRight } from 'lucide-react';
import { milestones } from '@/data/milestones';
import styles from './MilestonesSection.module.css';
export const MilestonesSection: React.FC = () => {
return (
<section id="milestones" className={styles.milestonesSection}>
<div className="content-container">
{/* Section Header */}
<div className="section-header">
<span className="section-eyebrow">AWARDS & ACHIEVEMENTS</span>
<div className="section-title-wrap">
<h2 className="section-title">Milestones Along the Way</h2>
<div className="section-underline" />
</div>
</div>
{/* Certificate Cards List */}
<div className={styles.cardsList}>
{milestones.map((item) => (
<div key={item.id} className={styles.certCard}>
{/* Thumbnail */}
<div className={styles.thumbnailContainer}>
<Image
src={item.image}
alt={item.title}
fill
className={styles.certImage}
/>
</div>
{/* Content Details */}
<div className={styles.certDetails}>
<div className={styles.titleRow}>
<h3 className={styles.certTitle}>{item.title}</h3>
</div>
<div className={styles.issuerRow}>
<span className={styles.issuerText}>{item.issuer}</span>
</div>
<div className={styles.dateBadge}>{item.date}</div>
<p className={styles.description}>{item.description}</p>
{/* View Certificate Link */}
<div className={styles.actionRow}>
<a
href={item.link || '#'}
className={styles.viewCertLink}
target="_blank"
rel="noopener noreferrer"
>
<span>View certificate</span>
<ArrowUpRight size={16} />
</a>
</div>
</div>
</div>
))}
</div>
</div>
</section>
);
};

View File

@@ -0,0 +1,222 @@
.projectsSection {
position: relative;
width: 1440px;
padding-top: 60px;
padding-bottom: 80px;
}
.headerRow {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 40px;
}
.controls {
display: flex;
align-items: center;
gap: 20px;
}
.counter {
font-size: var(--font-size-body-large);
font-weight: 500;
color: var(--text-muted);
}
.counterActive {
font-weight: 700;
color: var(--text-primary);
}
.counterDivider {
margin: 0 4px;
}
.arrowButtons {
display: flex;
gap: 12px;
}
.navButton {
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--size-icon-button);
height: var(--size-icon-button);
border-radius: 50%;
border: 1px solid var(--border-default);
background: var(--bg-surface);
color: var(--text-primary);
cursor: pointer;
transition: all 0.2s ease;
}
.navButton:hover:not(:disabled) {
border-color: var(--accent-default);
background: var(--bg-subtle);
transform: scale(1.05);
}
.navButton:disabled {
opacity: 0.35;
cursor: not-allowed;
}
.carouselViewport {
width: 1320px;
overflow: visible;
position: relative;
}
.carouselTrack {
display: flex;
gap: 24px;
transition: transform 0.4s cubic-bezier(0.2, 0, 0.2, 1);
will-change: transform;
}
.card {
flex: 0 0 360px;
width: 360px;
background-color: var(--bg-surface);
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 4px 16px var(--shadow-color);
transition: all 0.25s ease;
}
.card:hover {
border-color: var(--accent-default);
transform: translateY(-4px);
box-shadow: 0 12px 28px var(--shadow-color);
}
.mediaContainer {
position: relative;
width: 100%;
height: var(--size-project-media-height);
overflow: hidden;
background-color: #0c1a3a;
}
.projectImage {
object-fit: cover;
transition: transform 0.3s ease;
}
.card:hover .projectImage {
transform: scale(1.03);
}
.cardBody {
padding: 24px;
display: flex;
flex-direction: column;
flex-grow: 1;
}
.cardTitleRow {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 6px;
}
.cardTitle {
font-size: var(--font-size-h3);
line-height: var(--line-height-h3);
font-weight: 700;
color: var(--text-primary);
}
.linkIcon {
color: var(--accent-default);
}
.cardDate {
font-size: var(--font-size-label-small);
line-height: var(--line-height-label-small);
font-weight: 700;
letter-spacing: var(--letter-spacing-eyebrow);
color: var(--text-muted);
text-transform: uppercase;
margin-bottom: 14px;
}
.cardDesc {
font-size: var(--font-size-body-small);
line-height: var(--line-height-body-small);
color: var(--text-secondary);
margin-bottom: 20px;
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
}
.tagList {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 24px;
}
.tagBadge {
padding: 4px 12px;
border-radius: var(--radius-pill);
background-color: var(--bg-subtle);
color: var(--action-link);
font-size: var(--font-size-label-small);
line-height: var(--line-height-label-small);
font-weight: 600;
border: 1px solid var(--border-default);
}
.cardFooter {
margin-top: auto;
}
.caseStudyLink {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: var(--font-size-body-small);
font-weight: 600;
color: var(--action-link);
transition: gap 0.2s ease;
}
.caseStudyLink:hover {
gap: 12px;
}
.arrowIcon {
font-size: 16px;
transition: transform 0.2s ease;
}
.progressTrackWrapper {
display: flex;
justify-content: center;
margin-top: 48px;
}
.progressTrack {
width: 480px;
height: 4px;
background-color: var(--border-default);
border-radius: var(--radius-pill);
overflow: hidden;
position: relative;
}
.progressBar {
height: 100%;
background-color: var(--accent-default);
border-radius: var(--radius-pill);
transition: transform 0.3s ease;
}

View File

@@ -0,0 +1,140 @@
'use client';
import React, { useState, useRef } from 'react';
import Image from 'next/image';
import { ArrowLeft, ArrowRight, ArrowUpRight } from 'lucide-react';
import { projects } from '@/data/projects';
import styles from './ProjectsSection.module.css';
export const ProjectsSection: React.FC = () => {
const [currentIndex, setCurrentIndex] = useState(0);
const trackRef = useRef<HTMLDivElement>(null);
const totalCards = projects.length;
// Step by 1 card, up to totalCards - 3
const maxIndex = Math.max(0, totalCards - 3);
const handlePrev = () => {
setCurrentIndex((prev) => Math.max(0, prev - 1));
};
const handleNext = () => {
setCurrentIndex((prev) => Math.min(maxIndex, prev + 1));
};
// Card width (360px) + gap (24px) = 384px
const translateX = currentIndex * 384;
const progressPercent = maxIndex > 0 ? (currentIndex / maxIndex) * 100 : 0;
return (
<section id="projects" className={styles.projectsSection}>
<div className="content-container">
{/* Section Header with Controls */}
<div className={styles.headerRow}>
<div className="section-header" style={{ marginBottom: 0 }}>
<span className="section-eyebrow">PROJECTS</span>
<div className="section-title-wrap">
<h2 className="section-title">Products I&apos;ve Brought to Life</h2>
<div className="section-underline" />
</div>
</div>
{/* Pagination Counter & Navigation Arrows */}
<div className={styles.controls}>
<span className={styles.counter}>
<strong className={styles.counterActive}>{currentIndex + 1}</strong>
<span className={styles.counterDivider}> / </span>
{totalCards}
</span>
<div className={styles.arrowButtons}>
<button
onClick={handlePrev}
disabled={currentIndex === 0}
className={styles.navButton}
aria-label="Previous Project"
>
<ArrowLeft size={20} />
</button>
<button
onClick={handleNext}
disabled={currentIndex >= maxIndex}
className={styles.navButton}
aria-label="Next Project"
>
<ArrowRight size={20} />
</button>
</div>
</div>
</div>
{/* Carousel Viewport */}
<div className={styles.carouselViewport}>
<div
ref={trackRef}
className={styles.carouselTrack}
style={{ transform: `translateX(-${translateX}px)` }}
>
{projects.map((p) => (
<div key={p.id} className={styles.card}>
{/* Project Image Media */}
<div className={styles.mediaContainer}>
<Image
src={p.image}
alt={p.title}
fill
className={styles.projectImage}
/>
</div>
{/* Project Details */}
<div className={styles.cardBody}>
<div className={styles.cardTitleRow}>
<h3 className={styles.cardTitle}>{p.title}</h3>
<ArrowUpRight size={18} className={styles.linkIcon} />
</div>
<div className={styles.cardDate}>{p.date}</div>
<p className={styles.cardDesc}>{p.description}</p>
{/* Tech Tags */}
<div className={styles.tagList}>
{p.tags.map((tag) => (
<span key={tag} className={styles.tagBadge}>
{tag}
</span>
))}
</div>
{/* Case Study Action */}
<div className={styles.cardFooter}>
<a href={p.link || '#'} className={styles.caseStudyLink}>
<span>View case study</span>
<span className={styles.arrowIcon}>→</span>
</a>
</div>
</div>
</div>
))}
</div>
</div>
{/* Progress Bar Track */}
<div className={styles.progressTrackWrapper}>
<div className={styles.progressTrack}>
<div
className={styles.progressBar}
style={{
width: '45%',
transform: `translateX(${progressPercent * 1.2}%)`,
}}
/>
</div>
</div>
</div>
</section>
);
};

View File

@@ -0,0 +1,172 @@
.journeySection {
position: relative;
width: 1440px;
height: 1050px;
/* Seamless continuation of scenic artwork */
}
.introBlock {
position: absolute;
left: 60px;
top: 170px;
width: 380px;
z-index: 5;
}
.eyebrow {
display: block;
font-size: var(--font-size-label-small);
line-height: var(--line-height-label-small);
letter-spacing: var(--letter-spacing-eyebrow);
text-transform: uppercase;
font-weight: 700;
color: var(--text-secondary);
margin-bottom: 8px;
}
.heading {
font-size: var(--font-size-h2);
line-height: var(--line-height-h2);
font-weight: 700;
color: var(--text-primary);
margin-bottom: 12px;
}
.underlineBar {
width: 120px;
height: 3px;
background-color: var(--accent-default);
border-radius: var(--radius-pill);
margin-bottom: 24px;
}
.description {
font-size: var(--font-size-body-large);
line-height: var(--line-height-body-large);
color: var(--text-secondary);
font-weight: 400;
}
/* Step Typography */
.stepNumber {
font-size: 20px;
font-weight: 700;
color: var(--accent-default);
margin-bottom: 4px;
}
.stepTitle {
font-size: var(--font-size-h3);
line-height: var(--line-height-h3);
font-weight: 700;
color: var(--text-primary);
margin-bottom: 6px;
}
.stepDesc {
font-size: var(--font-size-body-small);
line-height: var(--line-height-body-small);
color: var(--text-secondary);
}
.islandImage {
object-fit: contain;
filter: drop-shadow(0 10px 20px rgba(0, 0, 0, 0.08));
transition: transform 0.3s ease;
}
.islandImage:hover {
transform: translateY(-4px) scale(1.02);
}
/* Freely Positioned Milestones */
/* Step 05: Product Engineer */
.step05Graphic {
position: absolute;
left: 1030px;
top: 100px;
width: 180px;
height: 240px;
z-index: 6;
}
.step05Text {
position: absolute;
left: 1220px;
top: 150px;
width: 190px;
z-index: 6;
}
/* Step 04: Software Engineer */
.step04Text {
position: absolute;
left: 650px;
top: 230px;
width: 200px;
z-index: 6;
}
.step04Graphic {
position: absolute;
left: 810px;
top: 200px;
width: 250px;
height: 220px;
z-index: 6;
}
/* Step 03: Computer Science */
.step03Text {
position: absolute;
left: 590px;
top: 425px;
width: 200px;
z-index: 6;
}
.step03Graphic {
position: absolute;
left: 750px;
top: 390px;
width: 240px;
height: 220px;
z-index: 6;
}
/* Step 02: UI/UX Designer */
.step02Text {
position: absolute;
left: 490px;
top: 570px;
width: 180px;
z-index: 6;
}
.step02Graphic {
position: absolute;
left: 620px;
top: 550px;
width: 210px;
height: 200px;
z-index: 6;
}
/* Step 01: Digital Artist */
.step01Text {
position: absolute;
left: 80px;
top: 650px;
width: 210px;
z-index: 6;
}
.step01Graphic {
position: absolute;
left: 260px;
top: 610px;
width: 230px;
height: 200px;
z-index: 6;
}

View File

@@ -0,0 +1,116 @@
'use client';
import React from 'react';
import Image from 'next/image';
import styles from './RiverJourney.module.css';
export const RiverJourney: React.FC = () => {
return (
<section id="about" className={styles.journeySection}>
{/* Intro Header & Description on the Left */}
<div className={styles.introBlock}>
<span className={styles.eyebrow}>ABOUT ME</span>
<h2 className={styles.heading}>From Pixel to Product</h2>
<div className={styles.underlineBar} />
<p className={styles.description}>
I began as a digital artist, designing visual stories. Curiosity turned
into code, and code into systems that solve real problems. Today, I
build products end-to-end using agentic workflows that blend creativity,
engineering, and product thinking.
</p>
</div>
{/* 5 Freely Positioned Milestones along the River */}
{/* Step 05: Product Engineer */}
<div className={styles.step05Graphic}>
<Image
src="/assets/step-05-product-engineer.png"
alt="Product Engineer Milestone"
width={180}
height={240}
className={styles.islandImage}
/>
</div>
<div className={styles.step05Text}>
<div className={styles.stepNumber}>05</div>
<h3 className={styles.stepTitle}>Product Engineer</h3>
<p className={styles.stepDesc}>
Merging product strategy with engineering to ship impactful products.
</p>
</div>
{/* Step 04: Software Engineer */}
<div className={styles.step04Text}>
<div className={styles.stepNumber}>04</div>
<h3 className={styles.stepTitle}>Software Engineer</h3>
<p className={styles.stepDesc}>
Engineered scalable applications and delivered real-world solutions.
</p>
</div>
<div className={styles.step04Graphic}>
<Image
src="/assets/step-04-software-engineer.png"
alt="Software Engineer Milestone"
width={250}
height={220}
className={styles.islandImage}
/>
</div>
{/* Step 03: Computer Science */}
<div className={styles.step03Text}>
<div className={styles.stepNumber}>03</div>
<h3 className={styles.stepTitle}>Computer Science</h3>
<p className={styles.stepDesc}>
Built strong foundations in algorithms and systems thinking.
</p>
</div>
<div className={styles.step03Graphic}>
<Image
src="/assets/step-02-computer-science.png"
alt="Computer Science Milestone"
width={240}
height={220}
className={styles.islandImage}
/>
</div>
{/* Step 02: UI/UX Designer */}
<div className={styles.step02Text}>
<div className={styles.stepNumber}>02</div>
<h3 className={styles.stepTitle}>UI/UX Designer</h3>
<p className={styles.stepDesc}>
Designed Intuitive interfaces and user-centered experiences.
</p>
</div>
<div className={styles.step02Graphic}>
<Image
src="/assets/step-03-ui-ux-designer.png"
alt="UI/UX Designer Milestone"
width={210}
height={200}
className={styles.islandImage}
/>
</div>
{/* Step 01: Digital Artist */}
<div className={styles.step01Text}>
<div className={styles.stepNumber}>01</div>
<h3 className={styles.stepTitle}>Digital Artist</h3>
<p className={styles.stepDesc}>
Explored visual design, illustration and storytelling.
</p>
</div>
<div className={styles.step01Graphic}>
<Image
src="/assets/step-01-digital-artist.png"
alt="Digital Artist Milestone"
width={230}
height={200}
className={styles.islandImage}
/>
</div>
</section>
);
};

View File

@@ -0,0 +1,24 @@
.scenicContainer {
position: relative;
width: 1440px;
height: 1730px;
overflow: hidden;
background-color: var(--bg-page);
}
.artworkContainer {
position: absolute;
top: 0;
left: 0;
width: 1440px;
height: 1576px;
z-index: 1;
pointer-events: none;
}
.artworkImage {
object-fit: contain;
object-position: top center;
width: 1440px;
height: 1576px;
}

View File

@@ -0,0 +1,38 @@
'use client';
import React from 'react';
import Image from 'next/image';
import { useTheme } from '@/context/ThemeContext';
import { HeroSection } from './HeroSection';
import { RiverJourney } from './RiverJourney';
import styles from './ScenicHeroJourney.module.css';
export const ScenicHeroJourney: React.FC = () => {
const { theme } = useTheme();
return (
<div className={styles.scenicContainer}>
{/* Seamless Continuous Landscape & River Artwork */}
<div className={styles.artworkContainer}>
<Image
src={
theme === 'dark'
? '/assets/landing-to-river-artwork-dark.png'
: '/assets/landing-to-river-artwork.png'
}
alt="Mountain landscape to river journey artwork"
width={1440}
height={1576}
priority
className={styles.artworkImage}
/>
</div>
{/* Hero Section Component */}
<HeroSection />
{/* About Me River Journey Component */}
<RiverJourney />
</div>
);
};

View File

@@ -0,0 +1,162 @@
.skillsSection {
position: relative;
width: 1440px;
padding-top: 60px;
padding-bottom: 80px;
}
.mainCard {
width: 1320px;
background-color: var(--bg-surface);
border: 1px solid var(--border-default);
border-radius: var(--radius-xl);
padding: 36px 40px;
box-shadow: 0 4px 20px var(--shadow-color);
margin-bottom: 32px;
}
.cardEyebrow {
display: block;
font-size: var(--font-size-label-small);
line-height: var(--line-height-label-small);
letter-spacing: var(--letter-spacing-eyebrow);
text-transform: uppercase;
font-weight: 700;
color: var(--text-muted);
margin-bottom: 24px;
}
.skillsBlock {
margin-bottom: 40px;
}
.skillsGrid {
display: grid;
grid-template-columns: repeat(4, 1fr);
row-gap: 28px;
column-gap: 40px;
margin-bottom: 32px;
}
.skillItem {
display: flex;
flex-direction: column;
gap: 10px;
}
.skillName {
font-size: var(--font-size-body-default);
line-height: var(--line-height-body-default);
font-weight: 700;
color: var(--text-primary);
}
.meterSegments {
display: flex;
gap: 6px;
width: 100%;
}
.segment {
flex: 1;
height: 6px;
border-radius: 3px;
background-color: var(--border-default);
transition: background-color 0.2s ease;
}
.segmentFilled {
background-color: var(--accent-default);
}
.badgeCloud {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.badgePill {
padding: 6px 16px;
border-radius: var(--radius-pill);
background-color: var(--bg-subtle);
border: 1px solid var(--border-default);
font-size: var(--font-size-label-small);
line-height: var(--line-height-label-small);
color: var(--text-secondary);
font-weight: 500;
transition: all 0.2s ease;
}
.badgePill:hover {
border-color: var(--accent-default);
color: var(--text-primary);
}
.toolsBlock {
padding-top: 16px;
}
.toolsGrid {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 24px;
}
.toolTile {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 132px;
height: 136px;
background-color: var(--bg-raised);
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
padding: 16px 8px;
transition: all 0.25s cubic-bezier(0.2, 0, 0.2, 1);
cursor: default;
}
.toolTile:hover {
border-color: var(--accent-default);
transform: translateY(-4px);
box-shadow: 0 8px 24px var(--shadow-color);
}
.toolDimmed {
opacity: 0.35;
filter: grayscale(0.8);
}
.toolIconContainer {
width: 64px;
height: 64px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 12px;
}
.toolIcon {
object-fit: contain;
width: 100%;
height: 100%;
}
.toolLabel {
font-size: var(--font-size-body-small);
line-height: var(--line-height-body-small);
font-weight: 600;
color: var(--text-primary);
text-align: center;
}
.filterTabsRow {
display: flex;
justify-content: center;
gap: 16px;
}
.filterButton {
min-width: 180px;
}

View File

@@ -0,0 +1,133 @@
'use client';
import React, { useState } from 'react';
import Image from 'next/image';
import { Code, BarChart2, Wand2 } from 'lucide-react';
import {
skillCategories,
keywordBadges,
toolsList,
filterTabs,
FilterTab,
} from '@/data/skills';
import styles from './SkillsSection.module.css';
export const SkillsSection: React.FC = () => {
const [activeFilter, setActiveFilter] = useState<FilterTab['id']>('software-engineering');
const filteredTools = toolsList.filter((tool) =>
tool.category.includes(activeFilter)
);
const getFilterIcon = (id: FilterTab['id']) => {
switch (id) {
case 'software-engineering':
return <Code size={18} />;
case 'product-management':
return <BarChart2 size={18} />;
case 'design-ux':
return <Wand2 size={18} />;
}
};
return (
<section id="skills" className={styles.skillsSection}>
<div className="content-container">
{/* Section Header */}
<div className="section-header">
<span className="section-eyebrow">SKILLS & TOOLS</span>
<div className="section-title-wrap">
<h2 className="section-title">The Stack Behind the Work...</h2>
<div className="section-underline" />
</div>
</div>
{/* Main Card Container */}
<div className={styles.mainCard}>
{/* Section: Skills */}
<div className={styles.skillsBlock}>
<span className={styles.cardEyebrow}>SKILLS</span>
{/* 8 Skill Meters Grid */}
<div className={styles.skillsGrid}>
{skillCategories.map((skill) => (
<div key={skill.name} className={styles.skillItem}>
<div className={styles.skillName}>{skill.name}</div>
<div className={styles.meterSegments}>
{[1, 2, 3, 4, 5].map((seg) => (
<div
key={seg}
className={`${styles.segment} ${
seg <= skill.level ? styles.segmentFilled : ''
}`}
/>
))}
</div>
</div>
))}
</div>
{/* Keyword Badges */}
<div className={styles.badgeCloud}>
{keywordBadges.map((badge) => (
<span key={badge} className={styles.badgePill}>
{badge}
</span>
))}
</div>
</div>
{/* Section: Tools */}
<div className={styles.toolsBlock}>
<span className={styles.cardEyebrow}>TOOLS</span>
{/* 8-Column Grid */}
<div className={styles.toolsGrid}>
{toolsList.map((tool) => {
const isMatching = tool.category.includes(activeFilter);
return (
<div
key={tool.id}
className={`${styles.toolTile} ${
!isMatching ? styles.toolDimmed : ''
}`}
>
<div className={styles.toolIconContainer}>
<Image
src={tool.icon}
alt={tool.name}
width={64}
height={64}
className={styles.toolIcon}
/>
</div>
<span className={styles.toolLabel}>{tool.name}</span>
</div>
);
})}
</div>
</div>
</div>
{/* Filter Tabs Under Card */}
<div className={styles.filterTabsRow}>
{filterTabs.map((tab) => {
const isActive = activeFilter === tab.id;
return (
<button
key={tab.id}
onClick={() => setActiveFilter(tab.id)}
className={`pill-btn ${
isActive ? 'pill-btn-primary' : 'pill-btn-outline'
} ${styles.filterButton}`}
>
{getFilterIcon(tab.id)}
<span>{tab.label}</span>
</button>
);
})}
</div>
</div>
</section>
);
};