import { createFileRoute, Link } from '@tanstack/react-router' import { computeDuration, formatDate, CONTACT_EMAIL } from '#/data/portfolio' import type { TExperience, TEducation, TSkill } from '#/types/strapi' import { fetchExperiences, fetchEducations, fetchProfile, } from '#/data/loaders/portfolio' export const Route = createFileRoute('/')({ loader: async () => { const [experiences, educations, profile] = await Promise.all([ fetchExperiences(), fetchEducations(), fetchProfile(), ]) return { experiences, educations, profile } }, errorComponent: StrapiErrorPage, component: HomePage, }) // ── Error page ──────────────────────────────────────────────────────────── function StrapiErrorPage() { return (

Service Unavailable

We'll be right back

The portfolio data is temporarily unavailable. Please check back in a few minutes.

) } // ── Shared UI primitives ────────────────────────────────────────────────── type ButtonVariant = 'primary' | 'ghost' const BUTTON_VARIANT_CLASSES: Record = { 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 ( {children} ) } function TechChip({ label }: { readonly label: string }) { return ( {label} ) } // ── Section heading ─────────────────────────────────────────────────────── function SectionHeading({ kicker, title, id, }: { readonly kicker: string readonly title: string readonly id: string }) { return (

{kicker}

{title}

) } // ── Experience card ─────────────────────────────────────────────────────── const EMPLOYMENT_TYPE_LABEL: Record<'full-time' | 'part-time', string> = { 'full-time': 'Full-time', 'part-time': 'Part-time', } function ExperienceCard({ exp, index, }: { readonly exp: TExperience readonly index: number }) { const startIso = exp.startDate.slice(0, 7) const endIso = exp.endDate?.slice(0, 7) const duration = computeDuration(startIso, endIso) const startLabel = formatDate(startIso) const endLabel = endIso ? formatDate(endIso) : 'Present' return (
{/* Timeline dot + connector line */}
{/* Card */}

{exp.company}

{exp.role}

{exp.type != null && ( {EMPLOYMENT_TYPE_LABEL[exp.type]} )}
{startLabel} — {endLabel} {duration}
{(exp.objectives ?? []).length > 0 && (
    {(exp.objectives ?? []).map((obj) => (
  • {obj.description}
  • ))}
)} {(exp.technologies ?? []).length > 0 && (
{(exp.technologies ?? []).map((tech) => ( ))}
)}
) } // ── Education card ──────────────────────────────────────────────────────── function EducationCard({ edu }: { readonly edu: TEducation }) { const startYear = new Date(edu.startDate).getFullYear() const endYear = edu.endDate ? new Date(edu.endDate).getFullYear() : null return (

{edu.institution}

{edu.degree} {edu.fieldOfStudy ? ` ${edu.fieldOfStudy}` : ''}

{startYear} – {endYear ?? 'Present'} {edu.grade != null && ( <> Grade: {edu.grade} )}
) } // ── Skill group card ────────────────────────────────────────────────────── function SkillGroupCard({ category, skills, }: { readonly category: string readonly skills: readonly TSkill[] }) { return (

{category}

{skills.map((skill) => ( ))}
) } // ── Page ────────────────────────────────────────────────────────────────── function HomePage() { const { experiences, educations, profile } = Route.useLoaderData() const skillsByGroup = profile.skills.reduce>( (acc, skill) => { const key = skill.group acc[key] = [...(acc[key] ?? []), skill] return acc }, {}, ) return (
{/* ── Hero ──────────────────────────────────────────── */}

Software Engineer · Copenhagen, Denmark

Hi, I'm {profile.firstname}.

{profile.about ?? 'Crafting robust full-stack solutions with a passion for clean architecture, great developer experience, and shipping products people love.'}

View Experience Get in Touch
{/* ── Experience ────────────────────────────────────── */}
{experiences.map((exp, i) => ( ))}
{/* ── Education ─────────────────────────────────────── */}
{educations.map((edu) => ( ))}
{/* ── Skills ────────────────────────────────────────── */}
{Object.entries(skillsByGroup).map(([groupKey, skills]) => ( ))}
{/* ── Contact CTA ───────────────────────────────────── */}

Let's connect

Open to new opportunities

I'm always interested in hearing about exciting engineering challenges and the teams building them.

Say hello →
) }