chore: create initial project snapshot
Capture the portfolio source, design references, and production assets with repository-specific ignore rules.
This commit is contained in:
79
src/components/EducationSection.module.css
Normal file
79
src/components/EducationSection.module.css
Normal 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);
|
||||
}
|
||||
45
src/components/EducationSection.tsx
Normal file
45
src/components/EducationSection.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
184
src/components/ExperienceSection.module.css
Normal file
184
src/components/ExperienceSection.module.css
Normal 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);
|
||||
}
|
||||
88
src/components/ExperienceSection.tsx
Normal file
88
src/components/ExperienceSection.tsx
Normal 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'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>
|
||||
);
|
||||
};
|
||||
111
src/components/FooterSection.module.css
Normal file
111
src/components/FooterSection.module.css
Normal 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);
|
||||
}
|
||||
92
src/components/FooterSection.tsx
Normal file
92
src/components/FooterSection.tsx
Normal 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'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>
|
||||
);
|
||||
};
|
||||
160
src/components/HeroSection.module.css
Normal file
160
src/components/HeroSection.module.css
Normal 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);
|
||||
}
|
||||
102
src/components/HeroSection.tsx
Normal file
102
src/components/HeroSection.tsx
Normal 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'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>
|
||||
);
|
||||
};
|
||||
118
src/components/MilestonesSection.module.css
Normal file
118
src/components/MilestonesSection.module.css
Normal 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;
|
||||
}
|
||||
69
src/components/MilestonesSection.tsx
Normal file
69
src/components/MilestonesSection.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
222
src/components/ProjectsSection.module.css
Normal file
222
src/components/ProjectsSection.module.css
Normal 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;
|
||||
}
|
||||
140
src/components/ProjectsSection.tsx
Normal file
140
src/components/ProjectsSection.tsx
Normal 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'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>
|
||||
);
|
||||
};
|
||||
172
src/components/RiverJourney.module.css
Normal file
172
src/components/RiverJourney.module.css
Normal 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;
|
||||
}
|
||||
116
src/components/RiverJourney.tsx
Normal file
116
src/components/RiverJourney.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
24
src/components/ScenicHeroJourney.module.css
Normal file
24
src/components/ScenicHeroJourney.module.css
Normal 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;
|
||||
}
|
||||
38
src/components/ScenicHeroJourney.tsx
Normal file
38
src/components/ScenicHeroJourney.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
162
src/components/SkillsSection.module.css
Normal file
162
src/components/SkillsSection.module.css
Normal 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;
|
||||
}
|
||||
133
src/components/SkillsSection.tsx
Normal file
133
src/components/SkillsSection.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user