chore: create initial project snapshot
Capture the portfolio source, design references, and production assets with repository-specific ignore rules.
This commit is contained in:
30
src/app/layout.tsx
Normal file
30
src/app/layout.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { ThemeProvider } from '@/context/ThemeContext';
|
||||
import '@/styles/globals.css';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Amirhossein Khalili | Dedicated Product Engineer",
|
||||
description:
|
||||
"Personal portfolio of Amirhossein Khalili, Product Engineer & Software Developer based in Iran.",
|
||||
keywords: [
|
||||
'Amirhossein Khalili',
|
||||
'Product Engineer',
|
||||
'Software Engineer',
|
||||
'Full Stack Developer',
|
||||
'Portfolio',
|
||||
],
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" data-theme="light">
|
||||
<body>
|
||||
<ThemeProvider>{children}</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
35
src/app/page.tsx
Normal file
35
src/app/page.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { ScenicHeroJourney } from '@/components/ScenicHeroJourney';
|
||||
import { ProjectsSection } from '@/components/ProjectsSection';
|
||||
import { ExperienceSection } from '@/components/ExperienceSection';
|
||||
import { SkillsSection } from '@/components/SkillsSection';
|
||||
import { EducationSection } from '@/components/EducationSection';
|
||||
import { MilestonesSection } from '@/components/MilestonesSection';
|
||||
import { FooterSection } from '@/components/FooterSection';
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="desktop-canvas">
|
||||
{/* 1. Hero & About Me River Journey (Seamless Scenic Background) */}
|
||||
<ScenicHeroJourney />
|
||||
|
||||
{/* 2. Projects Carousel Section */}
|
||||
<ProjectsSection />
|
||||
|
||||
{/* 3. Work Experience Timeline Section */}
|
||||
<ExperienceSection />
|
||||
|
||||
{/* 4. Skills & Tools Matrix Section */}
|
||||
<SkillsSection />
|
||||
|
||||
{/* 5. Education Foundation Section */}
|
||||
<EducationSection />
|
||||
|
||||
{/* 6. Milestones & Awards Section */}
|
||||
<MilestonesSection />
|
||||
|
||||
{/* 7. Footer CTA & Social Section */}
|
||||
<FooterSection />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
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>
|
||||
);
|
||||
};
|
||||
49
src/context/ThemeContext.tsx
Normal file
49
src/context/ThemeContext.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
'use client';
|
||||
|
||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||
|
||||
type Theme = 'light' | 'dark';
|
||||
|
||||
interface ThemeContextType {
|
||||
theme: Theme;
|
||||
toggleTheme: () => void;
|
||||
setTheme: (theme: Theme) => void;
|
||||
}
|
||||
|
||||
const ThemeContext = createContext<ThemeContextType>({
|
||||
theme: 'light',
|
||||
toggleTheme: () => {},
|
||||
setTheme: () => {},
|
||||
});
|
||||
|
||||
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [theme, setThemeState] = useState<Theme>('light');
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const savedTheme = localStorage.getItem('portfolio-theme') as Theme | null;
|
||||
const initialTheme = savedTheme || 'light';
|
||||
setThemeState(initialTheme);
|
||||
document.documentElement.setAttribute('data-theme', initialTheme);
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
const setTheme = (newTheme: Theme) => {
|
||||
setThemeState(newTheme);
|
||||
localStorage.setItem('portfolio-theme', newTheme);
|
||||
document.documentElement.setAttribute('data-theme', newTheme);
|
||||
};
|
||||
|
||||
const toggleTheme = () => {
|
||||
const nextTheme = theme === 'light' ? 'dark' : 'light';
|
||||
setTheme(nextTheme);
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, toggleTheme, setTheme }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useTheme = () => useContext(ThemeContext);
|
||||
16
src/data/education.ts
Normal file
16
src/data/education.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export interface EducationItem {
|
||||
degree: string;
|
||||
institution: string;
|
||||
date: string;
|
||||
description: string;
|
||||
logo: string;
|
||||
}
|
||||
|
||||
export const education: EducationItem = {
|
||||
degree: "Bachelor's Degree in Computer Engineering",
|
||||
institution: 'University of Guilan',
|
||||
date: 'JAN 2022 - JUN 2026',
|
||||
logo: '/images/logo-guilan-crest.png',
|
||||
description:
|
||||
"My bachelor's years at the University of Guilan were where I grew from a curious student into someone who could build, teach, lead, and take responsibility for real outcomes. Alongside studying Computer Engineering, I worked on software projects, served as Head of the Computer Engineering Scientific Association, coordinated teaching assistants across several courses, organized technical events, and supported other students in learning and problem-solving. More than any single subject, this period taught me how to think systematically, collaborate with people, communicate ideas clearly, and turn knowledge into something useful for others.",
|
||||
};
|
||||
120
src/data/experience.ts
Normal file
120
src/data/experience.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
export interface ExperienceItem {
|
||||
id: string;
|
||||
role: string;
|
||||
company: string;
|
||||
date: string;
|
||||
location: string;
|
||||
logo: string;
|
||||
isCurrent?: boolean;
|
||||
summary: string;
|
||||
bullets: string[];
|
||||
}
|
||||
|
||||
export const experiences: ExperienceItem[] = [
|
||||
{
|
||||
id: 'quera',
|
||||
role: 'Software Engineer',
|
||||
company: 'Quera',
|
||||
date: 'NOV 2024 - PRESENT',
|
||||
location: 'Tehran (Remote)',
|
||||
logo: '/images/logo-quera.png',
|
||||
isCurrent: true,
|
||||
summary:
|
||||
'Contribute to internal engineering tools and educational products while also supporting developers and learners through mentoring and course development. My role combines software engineering, technical education, and problem design across backend development, databases, and algorithms.',
|
||||
bullets: [
|
||||
'Designed and implemented internal tools and services for staff',
|
||||
'Mentored learners and developers in programming and problem-solving',
|
||||
'Improved and designed educational programming courses in backend development, NoSQL, and SQL',
|
||||
'Designed algorithmic and implementation problems for the Python and Django contest at Technology Olympics 2025',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'fiotrix',
|
||||
role: 'Software Engineer',
|
||||
company: 'Fiotrix',
|
||||
date: 'NOV 2025 - MAR 2026',
|
||||
location: 'Karaj, Alborz',
|
||||
logo: '/images/logo-fiotrix.png',
|
||||
isCurrent: false,
|
||||
summary:
|
||||
'Worked across the backend, frontend, and infrastructure of production systems, with a strong focus on performance, maintainability, and reliable deployment. I contributed to both feature development and architectural improvements, including database migration, backend refactoring, and API optimization.',
|
||||
bullets: [
|
||||
'Developed backend services using Python, Django, and DRF',
|
||||
'Built frontend features with React and TypeScript',
|
||||
'Managed services with Docker, Nginx, PgBouncer, and MinIO',
|
||||
'Collaborated using Git and GitLab for version control and CI',
|
||||
'Refactored backend code and proposed a cleaner base architecture',
|
||||
'Designed a data migration pipeline from SQL Server to PostgreSQL',
|
||||
'Reduced API response times by approximately 80% through indexing and resolving N+1 queries',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'baduno',
|
||||
role: 'Backend Developer',
|
||||
company: 'Baduno',
|
||||
date: 'OCT 2025 - NOV 2025',
|
||||
location: 'Tehran, Tehran',
|
||||
logo: '/images/logo-baduno.png',
|
||||
isCurrent: false,
|
||||
summary:
|
||||
'Built and deployed web scraping services with an emphasis on reusable architecture, reliability, and production readiness. My work included reverse engineering external platforms, handling anti-blocking challenges, and publishing API-based scraping services for external users.',
|
||||
bullets: [
|
||||
'Built Python and FastAPI web scraping services published on RapidAPI',
|
||||
'Developed and reverse-engineered a Google Maps scraper',
|
||||
'Designed reusable scraper workflows using the Template Method pattern',
|
||||
'Managed deployment and routing of FastAPI services using Caddy',
|
||||
'Wrote unit tests with pytest and load tests with Locust',
|
||||
'Implemented proxy rotation to reduce IP-blocking issues',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'sharif-ta',
|
||||
role: 'Teaching Assistant',
|
||||
company: 'Sharif University of Technology',
|
||||
date: 'OCT 2025 - JAN 2026',
|
||||
location: 'Tehran, Tehran',
|
||||
logo: '/images/logo-sharif.png',
|
||||
isCurrent: false,
|
||||
summary:
|
||||
'Supported introductory programming courses by helping students build strong foundations in programming concepts, problem-solving, and practical implementation while working alongside course instructors.',
|
||||
bullets: [
|
||||
'Fundamentals of Programming, Dr. Mohammad Nazari (1404-1)',
|
||||
'Fundamentals of Programming, Dr. Zahra Mafi (1404-1)',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'guilan-assoc',
|
||||
role: 'Head of Scientific Association of Computer Engineering',
|
||||
company: 'University of Guilan',
|
||||
date: 'SEP 2024 - JUN 2026',
|
||||
location: 'Rasht, Guilan',
|
||||
logo: '/images/logo-guilan-assoc.png',
|
||||
isCurrent: false,
|
||||
summary:
|
||||
'Led the Computer Engineering Scientific Association and helped organize its academic and community activities. The role involved coordinating teams, planning events, and managing ongoing tasks across both online and in-person initiatives.',
|
||||
bullets: [
|
||||
'Managed online and on-site events',
|
||||
'Coordinated tasks and team workflows using Notion',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'guilan-ta-head',
|
||||
role: 'Head of Teaching Assistants',
|
||||
company: 'University of Guilan',
|
||||
date: 'SEP 2024 - JAN 2026',
|
||||
location: 'Rasht, Guilan',
|
||||
logo: '/images/logo-guilan-ta.png',
|
||||
isCurrent: false,
|
||||
summary:
|
||||
'Coordinated teaching assistant activities across multiple Computer Engineering courses, combining academic support with team organization and course-level responsibilities across programming, databases, systems, and software engineering.',
|
||||
bullets: [
|
||||
'Fundamentals of Programming (1403-1)',
|
||||
'Computer Workshop (1403-1)',
|
||||
'Database Design (1403-1 & 1403-2)',
|
||||
'System Analysis and Design (1403-2)',
|
||||
'OS Fundamentals & Lab (1403-1)',
|
||||
'Software Engineering (1404-1)',
|
||||
'Data Structures & Algorithms (1404-1)',
|
||||
],
|
||||
},
|
||||
];
|
||||
62
src/data/milestones.ts
Normal file
62
src/data/milestones.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
export interface MilestoneItem {
|
||||
id: string;
|
||||
title: string;
|
||||
issuer: string;
|
||||
date: string;
|
||||
description: string;
|
||||
image: string;
|
||||
link?: string;
|
||||
}
|
||||
|
||||
export const milestones: MilestoneItem[] = [
|
||||
{
|
||||
id: 'hackathon',
|
||||
title: 'Agentic AI Development Hackathon',
|
||||
issuer: 'Startcoach.ir',
|
||||
date: 'AUGUST 2026',
|
||||
description:
|
||||
'This hackathon gave me the opportunity to explore the rapidly evolving field of agentic AI through hands-on problem-solving and experimentation. Working within a limited timeframe encouraged rapid learning, technical decision-making, and creative thinking while deepening my understanding of how AI-driven systems can be designed to solve practical problems.',
|
||||
image: '/images/cert-hackathon.png',
|
||||
link: '#',
|
||||
},
|
||||
{
|
||||
id: 'icpc-2025',
|
||||
title: 'ICPC 2025 Regional Finalist',
|
||||
issuer: 'International Collegiate Programming Contest (ICPC)',
|
||||
date: 'AUGUST 2025',
|
||||
description:
|
||||
'Competing as an ICPC Regional Finalist challenged me to solve complex algorithmic problems under strict time constraints while working closely with my teammates. The experience strengthened my problem-solving, algorithmic thinking, communication, and ability to stay focused when approaching unfamiliar technical challenges.',
|
||||
image: '/images/cert-icpc-2025.png',
|
||||
link: '#',
|
||||
},
|
||||
{
|
||||
id: 'pm-bootcamp',
|
||||
title: 'Product Management Bootcamp',
|
||||
issuer: 'Prodemy.ir',
|
||||
date: 'JANUARY 2026',
|
||||
description:
|
||||
'This bootcamp expanded my perspective beyond engineering and helped me better understand how successful products are shaped around real user needs and business goals. I explored product thinking, discovery, prioritization, planning, and cross-functional collaboration, strengthening my ability to connect technical decisions with the bigger picture of a product.',
|
||||
image: '/images/cert-bootcamp.png',
|
||||
link: '#',
|
||||
},
|
||||
{
|
||||
id: 'icpc-2024',
|
||||
title: 'ICPC 2024 - Online Participant',
|
||||
issuer: 'International Collegiate Programming Contest (ICPC)',
|
||||
date: 'SEPTEMBER 2024',
|
||||
description:
|
||||
'Participating in ICPC gave me valuable exposure to competitive programming and pushed me to approach problems with greater structure, speed, and precision. It helped strengthen my foundations in algorithms and data structures while motivating me to continue developing the skills needed for higher-level programming competitions.',
|
||||
image: '/images/cert-icpc-2024.png',
|
||||
link: '#',
|
||||
},
|
||||
{
|
||||
id: 'django-bootcamp',
|
||||
title: 'Backend development with Django Bootcamp',
|
||||
issuer: 'Quera.org',
|
||||
date: 'NOVEMBER 2024',
|
||||
description:
|
||||
'This bootcamp strengthened my foundations in backend development and helped turn my knowledge of Python into practical web engineering skills. Through Django, I developed a deeper understanding of backend architecture, databases, APIs, authentication, and building maintainable server-side applications—skills that later became a major part of my software engineering work.',
|
||||
image: '/images/cert-django.png',
|
||||
link: '#',
|
||||
},
|
||||
];
|
||||
92
src/data/projects.ts
Normal file
92
src/data/projects.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
export interface Project {
|
||||
id: string;
|
||||
title: string;
|
||||
link?: string;
|
||||
date: string;
|
||||
description: string;
|
||||
tags: string[];
|
||||
image: string;
|
||||
}
|
||||
|
||||
export const projects: Project[] = [
|
||||
{
|
||||
id: 'qlockify',
|
||||
title: 'Qlockify',
|
||||
link: '#',
|
||||
date: 'FEB 2026 - PRESENT',
|
||||
description:
|
||||
'Built a production-ready time tracking platform for teams and freelancers to manage workspaces, projects, billable hours, and reports in one place. Users can track their work time, organize it by project, manage access inside each workspace, generate reports for admins and clients, and export insights.',
|
||||
tags: ['Django', 'React', 'Docker', 'UI/UX Design'],
|
||||
image: '/images/project-qlockify.png',
|
||||
},
|
||||
{
|
||||
id: 'guilan-ace',
|
||||
title: 'Guilan ACE',
|
||||
link: '#',
|
||||
date: 'JUL 2025 - PRESENT',
|
||||
description:
|
||||
'Developed the Scientific Association of Computer Engineering website for event management and article publishing, helping members organize activities and users register for events, read blog posts, and share ideas through comments.',
|
||||
tags: ['Django Ninja', 'Next.js', 'Docker'],
|
||||
image: '/images/project-guilan-ace.png',
|
||||
},
|
||||
{
|
||||
id: 'zarinpal',
|
||||
title: 'Zarinpal Analytic Dashboard',
|
||||
link: '#',
|
||||
date: 'AUG 2026',
|
||||
description:
|
||||
"AI-based dashboard for analytics insight on merchant's transactions in their different terminals and suggesting profitable campaigns to hold in special occasions based on their historical transactions and upcoming events in Persian calendar and international events that affect Iranian markets.",
|
||||
tags: ['Go', 'SvelteKit', 'Docker'],
|
||||
image: '/images/project-zarinpal.png',
|
||||
},
|
||||
{
|
||||
id: 'iran-implant',
|
||||
title: 'Iran Implant',
|
||||
link: '#',
|
||||
date: 'NOV 2025 - MAR 2026',
|
||||
description:
|
||||
'Developed the backend of a comprehensive dental clinic and implant center management system for appointment scheduling, inventory tracking, patient records, and billing, enabling clinic staff to manage daily clinic operations seamlessly from a centralized platform.',
|
||||
tags: ['Django', 'Nginx', 'PostgreSQL', 'Docker'],
|
||||
image: '/images/project-iran-implant.png',
|
||||
},
|
||||
{
|
||||
id: 'agentic-workflow-engine',
|
||||
title: 'Agentic Workflow Engine',
|
||||
link: '#',
|
||||
date: 'JAN 2026 - FEB 2026',
|
||||
description:
|
||||
'Engineered an autonomous multi-agent task execution system utilizing advanced LLM reasoning, background task scheduling, and structured tool calling for end-to-end automated code refactoring and project analysis.',
|
||||
tags: ['Python', 'FastAPI', 'Redis', 'Docker'],
|
||||
image: '/images/project-qlockify.png',
|
||||
},
|
||||
{
|
||||
id: 'rapidapi-scraper-suite',
|
||||
title: 'RapidAPI Scraping Suite',
|
||||
link: '#',
|
||||
date: 'OCT 2025 - NOV 2025',
|
||||
description:
|
||||
'Architected resilient web scraping and data extraction microservices deployed on RapidAPI with automated proxy rotation, anti-bot bypass techniques, and high-concurrency async pipelines.',
|
||||
tags: ['FastAPI', 'Caddy', 'Locust', 'PyTest'],
|
||||
image: '/images/project-guilan-ace.png',
|
||||
},
|
||||
{
|
||||
id: 'sharif-contest-platform',
|
||||
title: 'Tech Olympics Problem Engine',
|
||||
link: '#',
|
||||
date: 'DEC 2025 - JAN 2026',
|
||||
description:
|
||||
'Designed algorithmic and implementation problem suites and automated grading sandbox test runners for Python and Django challenges in national programming competitions.',
|
||||
tags: ['Python', 'Django', 'PostgreSQL', 'Docker'],
|
||||
image: '/images/project-zarinpal.png',
|
||||
},
|
||||
{
|
||||
id: 'home-lab-infra',
|
||||
title: 'Private Cloud & Home Lab',
|
||||
link: '#',
|
||||
date: '2024 - PRESENT',
|
||||
description:
|
||||
'Designed and deployed private on-premise infrastructure hosting containerized services, databases, CI/CD runners, reverse proxies, and self-hosted AI workloads with proactive Grafana/Prometheus monitoring.',
|
||||
tags: ['Linux', 'Docker', 'Nginx', 'Grafana'],
|
||||
image: '/images/project-iran-implant.png',
|
||||
},
|
||||
];
|
||||
81
src/data/skills.ts
Normal file
81
src/data/skills.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
export interface SkillCategory {
|
||||
name: string;
|
||||
level: number; // 1 to 5
|
||||
}
|
||||
|
||||
export interface ToolItem {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
category: ('software-engineering' | 'product-management' | 'design-ux')[];
|
||||
}
|
||||
|
||||
export const skillCategories: SkillCategory[] = [
|
||||
{ name: 'Backend', level: 5 },
|
||||
{ name: 'Databases', level: 4 },
|
||||
{ name: 'Testing & Quality', level: 3 },
|
||||
{ name: 'System Design', level: 3 },
|
||||
{ name: 'Frontend', level: 5 },
|
||||
{ name: 'DevOps', level: 2 },
|
||||
{ name: 'Web Scraping', level: 3 },
|
||||
{ name: 'Version Control', level: 4 },
|
||||
];
|
||||
|
||||
export const keywordBadges: string[] = [
|
||||
'Documentation',
|
||||
'Authentication',
|
||||
'Authorization',
|
||||
'OAuth2',
|
||||
'JWT',
|
||||
'Caching',
|
||||
'Rate Limiting',
|
||||
'Throttling',
|
||||
'WebSockets',
|
||||
'Background Job',
|
||||
'Web Scraping',
|
||||
'Bash Scripting',
|
||||
'Microservices',
|
||||
'Performance Optimization',
|
||||
'Third-Party Integration',
|
||||
];
|
||||
|
||||
export const toolsList: ToolItem[] = [
|
||||
// Row 1
|
||||
{ id: 'vscode', name: 'VS Code', icon: '/images/tools/vscode.png', category: ['software-engineering'] },
|
||||
{ id: 'codex', name: 'Codex', icon: '/images/tools/codex.png', category: ['software-engineering', 'product-management'] },
|
||||
{ id: 'antigravity', name: 'Antigravity', icon: '/images/tools/antigravity.png', category: ['software-engineering', 'product-management'] },
|
||||
{ id: 'git', name: 'Git', icon: '/images/tools/git.png', category: ['software-engineering'] },
|
||||
{ id: 'github', name: 'GitHub', icon: '/images/tools/github.png', category: ['software-engineering', 'product-management'] },
|
||||
{ id: 'python', name: 'Python', icon: '/images/tools/python.png', category: ['software-engineering'] },
|
||||
{ id: 'django', name: 'Django', icon: '/images/tools/django.png', category: ['software-engineering'] },
|
||||
{ id: 'fastapi', name: 'FastAPI', icon: '/images/tools/fastapi.png', category: ['software-engineering'] },
|
||||
// Row 2
|
||||
{ id: 'celery', name: 'Celery', icon: '/images/tools/celery.png', category: ['software-engineering'] },
|
||||
{ id: 'postgresql', name: 'PostgreSQL', icon: '/images/tools/postgresql.png', category: ['software-engineering'] },
|
||||
{ id: 'redis', name: 'Redis', icon: '/images/tools/redis.png', category: ['software-engineering'] },
|
||||
{ id: 'neo4j', name: 'Neo4j', icon: '/images/tools/neo4j.png', category: ['software-engineering'] },
|
||||
{ id: 'mongodb', name: 'MongoDB', icon: '/images/tools/mongodb.png', category: ['software-engineering'] },
|
||||
{ id: 'typescript', name: 'TypeScript', icon: '/images/tools/typescript.png', category: ['software-engineering'] },
|
||||
{ id: 'react', name: 'React', icon: '/images/tools/react.png', category: ['software-engineering', 'design-ux'] },
|
||||
{ id: 'nextjs', name: 'Next.js', icon: '/images/tools/nextjs.png', category: ['software-engineering', 'design-ux'] },
|
||||
// Row 3
|
||||
{ id: 'linux', name: 'Linux', icon: '/images/tools/linux.png', category: ['software-engineering'] },
|
||||
{ id: 'docker', name: 'Docker', icon: '/images/tools/docker.png', category: ['software-engineering'] },
|
||||
{ id: 'nginx', name: 'Nginx', icon: '/images/tools/nginx.png', category: ['software-engineering'] },
|
||||
{ id: 'caddy', name: 'Caddy', icon: '/images/tools/caddy.png', category: ['software-engineering'] },
|
||||
{ id: 'minio', name: 'MinIO', icon: '/images/tools/minio.png', category: ['software-engineering'] },
|
||||
{ id: 'prometheus', name: 'Prometheus', icon: '/images/tools/prometheus.png', category: ['software-engineering'] },
|
||||
{ id: 'grafana', name: 'Grafana', icon: '/images/tools/grafana.png', category: ['software-engineering', 'product-management'] },
|
||||
{ id: 'kubernetes', name: 'Kubernetes', icon: '/images/tools/kubernetes.png', category: ['software-engineering'] },
|
||||
];
|
||||
|
||||
export interface FilterTab {
|
||||
id: 'software-engineering' | 'product-management' | 'design-ux';
|
||||
label: string;
|
||||
}
|
||||
|
||||
export const filterTabs: FilterTab[] = [
|
||||
{ id: 'software-engineering', label: 'Software Engineering' },
|
||||
{ id: 'product-management', label: 'Product Management' },
|
||||
{ id: 'design-ux', label: 'Design & UX' },
|
||||
];
|
||||
150
src/styles/globals.css
Normal file
150
src/styles/globals.css
Normal file
@@ -0,0 +1,150 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Pacifico&family=Plus+Jakarta+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400;1,600&display=swap');
|
||||
@import './tokens.css';
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 100%;
|
||||
min-width: 1440px;
|
||||
background-color: var(--bg-page);
|
||||
color: var(--text-primary);
|
||||
font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
overflow-x: auto;
|
||||
transition: background-color 0.25s ease, color 0.25s ease;
|
||||
}
|
||||
|
||||
/* Master 1440px Fixed Canvas */
|
||||
.desktop-canvas {
|
||||
width: 1440px;
|
||||
min-width: 1440px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
background-color: var(--bg-page);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 1320px Inner Content Container with 60px Gutters */
|
||||
.content-container {
|
||||
width: 1320px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Common Section Header Component Styles */
|
||||
.section-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.section-eyebrow {
|
||||
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;
|
||||
}
|
||||
|
||||
.section-title-wrap {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: var(--font-size-h2);
|
||||
line-height: var(--line-height-h2);
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.section-underline {
|
||||
width: 120px;
|
||||
height: 3px;
|
||||
background-color: var(--accent-default);
|
||||
border-radius: var(--radius-pill);
|
||||
}
|
||||
|
||||
/* Pacifico Display Font Class */
|
||||
.font-display {
|
||||
font-family: 'Pacifico', cursive;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* Pill Button */
|
||||
.pill-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
height: var(--size-button-height);
|
||||
padding: 0 24px;
|
||||
border-radius: var(--radius-pill);
|
||||
font-size: var(--font-size-button);
|
||||
line-height: var(--line-height-button);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.pill-btn-outline {
|
||||
background: rgba(255, 255, 255, 0.4);
|
||||
backdrop-filter: blur(10px);
|
||||
border-color: var(--border-default);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.pill-btn-outline:hover {
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
border-color: var(--accent-default);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pill-btn-outline {
|
||||
background: rgba(9, 28, 63, 0.6);
|
||||
border-color: var(--border-default);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pill-btn-outline:hover {
|
||||
background: rgba(16, 63, 127, 0.8);
|
||||
border-color: var(--accent-default);
|
||||
}
|
||||
|
||||
.pill-btn-primary {
|
||||
background-color: var(--action-primary-bg);
|
||||
color: var(--action-primary-fg);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.pill-btn-primary:hover {
|
||||
background-color: var(--action-primary-hover);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px var(--shadow-color);
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
130
src/styles/tokens.css
Normal file
130
src/styles/tokens.css
Normal file
@@ -0,0 +1,130 @@
|
||||
/* Generated from Figma DTCG Design Tokens */
|
||||
|
||||
:root {
|
||||
--color-blue-100: #F8FAFF;
|
||||
--color-blue-200: #E6F0FE;
|
||||
--color-blue-300: #82B5FF;
|
||||
--color-blue-400: #3D8AFA;
|
||||
--color-blue-500: #1557A6;
|
||||
--color-blue-600: #103F7F;
|
||||
--color-blue-700: #0B2F5F;
|
||||
--color-blue-800: #091C3F;
|
||||
--color-blue-900: #07132F;
|
||||
--color-blue-950: #010D27;
|
||||
--color-blue-white: #FFFFFF;
|
||||
--color-slate-300: #C3CFE0;
|
||||
--color-slate-400: #A3B1C6;
|
||||
--color-slate-600: #667085;
|
||||
--color-slate-800: #38526E;
|
||||
--color-border-light: #D9E2EF;
|
||||
--color-border-dark: #1E3458;
|
||||
--color-border-strong-light: #A9BEDA;
|
||||
--color-border-strong-dark: #426B9D;
|
||||
--color-black: #000000;
|
||||
--space-0: 0px;
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 24px;
|
||||
--space-6: 32px;
|
||||
--space-7: 40px;
|
||||
--space-8: 48px;
|
||||
--space-9: 64px;
|
||||
--space-10: 80px;
|
||||
--space-11: 96px;
|
||||
--space-12: 120px;
|
||||
--radius-sm: 8px;
|
||||
--radius-md: 12px;
|
||||
--radius-lg: 16px;
|
||||
--radius-xl: 24px;
|
||||
--radius-pill: 999px;
|
||||
--stroke-default: 1px;
|
||||
--stroke-strong: 2px;
|
||||
--layout-frame: 1440px;
|
||||
--layout-container: 1320px;
|
||||
--layout-page-gutter: 60px;
|
||||
--layout-grid-gutter: 24px;
|
||||
--layout-grid-column: 88px;
|
||||
--layout-section-gap: 120px;
|
||||
--layout-section-padding: 96px;
|
||||
--layout-text-measure: 720px;
|
||||
--size-icon-sm: 16px;
|
||||
--size-icon-md: 20px;
|
||||
--size-icon-lg: 24px;
|
||||
--size-icon-button: 44px;
|
||||
--size-button-height: 48px;
|
||||
--size-social-button: 56px;
|
||||
--size-avatar: 200px;
|
||||
--size-journey-image: 96px;
|
||||
--size-project-width: 360px;
|
||||
--size-project-media-height: 225px;
|
||||
--size-tool-tile: 72px;
|
||||
--font-size-display-hero: 64px;
|
||||
--font-size-display-footer: 48px;
|
||||
--font-size-h1: 40px;
|
||||
--font-size-h2: 32px;
|
||||
--font-size-h3: 20px;
|
||||
--font-size-body-large: 18px;
|
||||
--font-size-body-default: 16px;
|
||||
--font-size-body-small: 14px;
|
||||
--font-size-label-small: 12px;
|
||||
--font-size-button: 15px;
|
||||
--line-height-display-hero: 72px;
|
||||
--line-height-display-footer: 56px;
|
||||
--line-height-h1: 52px;
|
||||
--line-height-h2: 42px;
|
||||
--line-height-h3: 28px;
|
||||
--line-height-body-large: 30px;
|
||||
--line-height-body-default: 26px;
|
||||
--line-height-body-small: 22px;
|
||||
--line-height-label-small: 18px;
|
||||
--line-height-button: 20px;
|
||||
--letter-spacing-eyebrow: 0.16em;
|
||||
|
||||
/* Semantic Tokens (Light) */
|
||||
--bg-page: #FFFFFF;
|
||||
--bg-surface: #FAFAFA;
|
||||
--bg-raised: #FFFFFF;
|
||||
--bg-subtle: #E6F0FE;
|
||||
--text-primary: #0B2F5F;
|
||||
--text-secondary: #38526E;
|
||||
--text-muted: #667085;
|
||||
--text-on-band: #FFFFFF;
|
||||
--border-default: #D9E2EF;
|
||||
--border-strong: #A9BEDA;
|
||||
--accent-default: #3D8AFA;
|
||||
--action-link: #1557A6;
|
||||
--action-primary-bg: #103F7F;
|
||||
--action-primary-fg: #FFFFFF;
|
||||
--action-primary-hover: #0B3268;
|
||||
--action-primary-pressed: #08284F;
|
||||
--brand-band: #103F7F;
|
||||
--focus-ring: #3D8AFA;
|
||||
--overlay-contact: rgba(255, 255, 255, 0.721569);
|
||||
--shadow-color: rgba(16, 63, 127, 0.078431);
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
/* Semantic Tokens (Dark) */
|
||||
--bg-page: #010D27;
|
||||
--bg-surface: #07132F;
|
||||
--bg-raised: #0C1A3A;
|
||||
--bg-subtle: #091C3F;
|
||||
--text-primary: #F8FAFF;
|
||||
--text-secondary: #C3CFE0;
|
||||
--text-muted: #A3B1C6;
|
||||
--text-on-band: #FFFFFF;
|
||||
--border-default: #1E3458;
|
||||
--border-strong: #426B9D;
|
||||
--accent-default: #82B5FF;
|
||||
--action-link: #82B5FF;
|
||||
--action-primary-bg: #F8FAFF;
|
||||
--action-primary-fg: #010D27;
|
||||
--action-primary-hover: #DCE8F7;
|
||||
--action-primary-pressed: #C3D5EC;
|
||||
--brand-band: #3D8AFA;
|
||||
--focus-ring: #82B5FF;
|
||||
--overlay-contact: rgba(1, 13, 39, 0.721569);
|
||||
--shadow-color: rgba(0, 0, 0, 0.239216);
|
||||
}
|
||||
Reference in New Issue
Block a user