Files
portfolio/src/routes/index.tsx

276 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { createFileRoute } from '@tanstack/react-router'
import {
experiences,
educations,
skillGroups,
computeDuration,
formatDate,
CONTACT_EMAIL,
} from '#/data/portfolio'
import type { Experience, Education, SkillGroup } from '#/data/portfolio'
export const Route = createFileRoute('/')({ component: HomePage })
// ── Shared UI primitives ──────────────────────────────────────────────────
type ButtonVariant = 'primary' | 'ghost'
const BUTTON_VARIANT_CLASSES: Record<ButtonVariant, string> = {
primary:
'rounded-full border border-[rgba(50,143,151,0.35)] bg-[rgba(79,184,178,0.15)] px-5 py-2.5 text-sm font-semibold text-[var(--lagoon-deep)] no-underline transition hover:-translate-y-0.5 hover:bg-[rgba(79,184,178,0.26)]',
ghost:
'rounded-full border border-[var(--line)] bg-[var(--surface-strong)] px-5 py-2.5 text-sm font-semibold text-[var(--sea-ink)] no-underline transition hover:-translate-y-0.5 hover:border-[rgba(23,58,64,0.3)]',
}
function ButtonLink({
href,
variant,
children,
}: {
readonly href: string
readonly variant: ButtonVariant
readonly children: React.ReactNode
}) {
return (
<a href={href} className={BUTTON_VARIANT_CLASSES[variant]}>
{children}
</a>
)
}
function TechChip({ label }: { readonly label: string }) {
return (
<span className="inline-block rounded-full border border-[var(--chip-line)] bg-[var(--chip-bg)] px-2.5 py-0.5 text-xs font-semibold text-[var(--lagoon-deep)]">
{label}
</span>
)
}
// ── Section heading ───────────────────────────────────────────────────────
function SectionHeading({
kicker,
title,
id,
}: {
readonly kicker: string
readonly title: string
readonly id: string
}) {
return (
<div id={id} className="mb-8 scroll-mt-20">
<p className="island-kicker mb-2">{kicker}</p>
<h2 className="display-title m-0 text-2xl font-bold tracking-tight text-[var(--sea-ink)] sm:text-3xl">
{title}
</h2>
</div>
)
}
// ── Experience card ───────────────────────────────────────────────────────
function ExperienceCard({
exp,
index,
}: {
readonly exp: Experience
readonly index: number
}) {
const duration = computeDuration(exp.startDate, exp.endDate ?? undefined)
const startLabel = formatDate(exp.startDate)
const endLabel = exp.endDate ? formatDate(exp.endDate) : 'Present'
return (
<div
className="rise-in relative flex gap-4 sm:gap-6"
style={{ animationDelay: `${index * 80}ms` }}
>
{/* Timeline dot + connector line */}
<div className="flex flex-col items-center">
<div className="mt-1.5 h-3.5 w-3.5 flex-shrink-0 rounded-full border-2 border-[var(--lagoon)] bg-[var(--lagoon-deep)] shadow-[0_0_0_4px_rgba(79,184,178,0.18)]" />
<div className="mt-1.5 w-0.5 flex-1 bg-[linear-gradient(to_bottom,var(--lagoon),transparent)]" />
</div>
{/* Card */}
<div className="island-shell feature-card mb-6 flex-1 rounded-2xl p-5 pb-6">
<div className="mb-2 flex flex-wrap items-start justify-between gap-2">
<div>
<p className="island-kicker mb-0.5">{exp.company}</p>
<h3 className="m-0 text-base font-bold text-[var(--sea-ink)]">
{exp.role}
</h3>
</div>
<span className="mt-0.5 flex-shrink-0 rounded-full border border-[var(--chip-line)] bg-[var(--chip-bg)] px-2.5 py-0.5 text-xs font-semibold text-[var(--sea-ink-soft)]">
{exp.type}
</span>
</div>
<div className="mb-3 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-[var(--sea-ink-soft)]">
<span>
{startLabel} {endLabel}
</span>
<span className="h-1 w-1 rounded-full bg-[var(--lagoon)]" />
<span>{duration}</span>
<span className="h-1 w-1 rounded-full bg-[var(--lagoon)]" />
<span>{exp.location}</span>
</div>
{exp.bullets.length > 0 && (
<ul className="mb-3 ml-0 list-none space-y-1.5 p-0">
{exp.bullets.map((bullet) => (
<li
key={bullet}
className="flex gap-2 text-sm text-[var(--sea-ink-soft)]"
>
<span className="mt-1.5 h-1.5 w-1.5 flex-shrink-0 rounded-full bg-[var(--lagoon)]" />
{bullet}
</li>
))}
</ul>
)}
{exp.tech.length > 0 && (
<div className="flex flex-wrap gap-1.5">
{exp.tech.map((t) => (
<TechChip key={t} label={t} />
))}
</div>
)}
</div>
</div>
)
}
// ── Education card ────────────────────────────────────────────────────────
function EducationCard({ edu }: { readonly edu: Education }) {
return (
<div className="island-shell feature-card rise-in rounded-2xl p-6">
<p className="island-kicker mb-2">{edu.institution}</p>
<h3 className="mb-2 text-base font-bold leading-snug text-[var(--sea-ink)]">
{edu.degree}
</h3>
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-[var(--sea-ink-soft)]">
<span>
{edu.startYear} {edu.endYear}
</span>
{edu.grade !== null && (
<>
<span className="h-1 w-1 rounded-full bg-[var(--lagoon)]" />
<span className="font-semibold text-[var(--lagoon-deep)]">
Grade: {edu.grade}
</span>
</>
)}
</div>
</div>
)
}
// ── Skill group card ──────────────────────────────────────────────────────
function SkillGroupCard({ group }: { readonly group: SkillGroup }) {
return (
<div className="island-shell feature-card rise-in rounded-2xl p-5">
<p className="island-kicker mb-3">{group.category}</p>
<div className="flex flex-wrap gap-2">
{group.skills.map((skill) => (
<TechChip key={skill} label={skill} />
))}
</div>
</div>
)
}
// ── Page ──────────────────────────────────────────────────────────────────
function HomePage() {
return (
<main className="page-wrap px-4 pb-16 pt-10">
{/* ── Hero ──────────────────────────────────────────── */}
<section className="island-shell rise-in relative overflow-hidden rounded-[2rem] px-6 py-12 sm:px-12 sm:py-16">
<div className="pointer-events-none absolute -left-24 -top-24 h-72 w-72 rounded-full bg-[radial-gradient(circle,rgba(79,184,178,0.3),transparent_65%)]" />
<div className="pointer-events-none absolute -bottom-24 -right-24 h-72 w-72 rounded-full bg-[radial-gradient(circle,rgba(47,106,74,0.18),transparent_65%)]" />
<div className="pointer-events-none absolute right-1/3 top-0 h-48 w-48 rounded-full bg-[radial-gradient(circle,rgba(79,184,178,0.12),transparent_70%)]" />
<p className="island-kicker mb-4">
Software Engineer · Copenhagen, Denmark
</p>
<h1 className="display-title mb-4 max-w-2xl text-5xl font-bold leading-[1.02] tracking-tight text-[var(--sea-ink)] sm:text-7xl">
Hi, I'm Mohammad.
</h1>
<p className="mb-8 max-w-xl text-base leading-relaxed text-[var(--sea-ink-soft)] sm:text-lg">
Crafting robust full-stack solutions with a passion for clean
architecture, great developer experience, and shipping products people
love.
</p>
<div className="flex flex-wrap gap-3">
<ButtonLink href="#experience" variant="primary">
View Experience
</ButtonLink>
<ButtonLink href={`mailto:${CONTACT_EMAIL}`} variant="ghost">
Get in Touch
</ButtonLink>
</div>
</section>
{/* ── Experience ────────────────────────────────────── */}
<section className="mt-16">
<SectionHeading
kicker="Career"
title="Work Experience"
id="experience"
/>
<div>
{experiences.map((exp, i) => (
<ExperienceCard key={exp.id} exp={exp} index={i} />
))}
</div>
</section>
{/* ── Education ─────────────────────────────────────── */}
<section className="mt-16">
<SectionHeading kicker="Background" title="Education" id="education" />
<div className="grid gap-4 sm:grid-cols-2">
{educations.map((edu) => (
<EducationCard key={edu.id} edu={edu} />
))}
</div>
</section>
{/* ── Skills ────────────────────────────────────────── */}
<section className="mt-16">
<SectionHeading
kicker="Toolkit"
title="Skills & Technologies"
id="skills"
/>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{skillGroups.map((group) => (
<SkillGroupCard key={group.category} group={group} />
))}
</div>
</section>
{/* ── Contact CTA ───────────────────────────────────── */}
<section id="contact" className="mt-16 scroll-mt-20">
<div className="island-shell rise-in relative overflow-hidden rounded-[2rem] px-6 py-12 text-center sm:px-12">
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_center,rgba(79,184,178,0.1),transparent_70%)]" />
<p className="island-kicker mb-3">Let's connect</p>
<h2 className="display-title mb-4 text-2xl font-bold text-[var(--sea-ink)] sm:text-4xl">
Open to new opportunities
</h2>
<p className="mx-auto mb-8 max-w-md text-[var(--sea-ink-soft)]">
I'm always interested in hearing about exciting engineering
challenges and the teams building them.
</p>
<ButtonLink href={`mailto:${CONTACT_EMAIL}`} variant="primary">
Say hello
</ButtonLink>
</div>
</section>
</main>
)
}