feat: rebuild portfolio with React + TanStack Start

This commit is contained in:
2026-04-17 22:16:44 +02:00
commit 3bda58288d
42 changed files with 3740 additions and 0 deletions

275
src/routes/index.tsx Normal file
View File

@@ -0,0 +1,275 @@
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>
)
}