Portfolio & Resume for Ezra Hulsman
Warm amber palette · Syne + Outfit typography · Interactive terminal · Scroll animations
Personal portfolio and resume site. Static-first with Astro, animated with GSAP, deployed on Cloudflare Workers with a server-side contact form.
| Layer | Tool |
|---|---|
| Framework | Astro 5 — static-first, islands architecture |
| Styling | Tailwind CSS 3 with CSS custom properties for theming |
| Animation | GSAP ScrollTrigger, CSS keyframes |
| Content | MDX with auto-discovery for project case studies |
| Deployment | Cloudflare Workers (server-side contact form via Resend) |
| Fonts | Syne (display), Outfit (body), JetBrains Mono (code) |
npm install
npm run dev # Astro dev server at localhost:4321
npm run build # Production build to ./dist
npm run preview # Preview production build locallyFor the contact form locally:
npx wrangler dev # Runs the Cloudflare Worker locally- Dark / Light themes with system preference detection and manual toggle
- Scroll-triggered animations via GSAP ScrollTrigger
- Interactive terminal hidden in the About section — 25+ commands, tab completion, command history, easter eggs
- MDX project pages with auto-discovery and frontmatter-driven routing
- Contact form powered by Cloudflare Workers + Resend email API
- Resume PDF generation via Playwright (CI-ready)
- Print-optimized styles —
/resumepage generates a clean PDF without UI chrome - SEO — Open Graph, Twitter Cards, structured metadata per page
src/
├── components/ # Astro section components, icons/, ThemeToggle (React island)
├── scripts/ # Interactive terminal engine (vanilla TS, ~1100 lines)
├── config/ # site.ts (personal info, projects) + resume.ts (experience, education)
├── layouts/ # BaseLayout + ProjectLayout for MDX
├── pages/ # File-based routing — homepage, /resume, /contact, /projects/*.mdx
├── lib/ # Theme utilities, Gravatar integration
├── styles/global.css # Theme system, keyframes, print styles
└── worker.ts # Cloudflare Worker for contact form (Resend API)
scripts/
└── generate-pdf.js # Playwright-based resume PDF generation (CI-ready)
All personal content lives in two config files:
src/config/site.ts— name, role, company, location, skills, social links, projects, employment status, SEO metadatasrc/config/resume.ts— experience timeline, education, certifications, skill categories, spoken languages
To update content, edit these files. The rest of the site reads from them.
Project case studies live in src/pages/projects/*.mdx. Each file is auto-discovered and routed. Frontmatter controls display:
---
title: "Project Name"
description: "Description for SEO and project cards"
date: 2025-01-01
tech: ["Python", "Docker", "Rust"]
github: "repo-name" # or full URL
preview: "https://demo.com" # optional live link
layout: "../../layouts/ProjectLayout.astro"
showcase: true # appears on homepage
---Projects with showcase: true appear on the homepage. All projects appear in the /projects gallery.
The About section contains an interactive terminal disguised as a decorative element. After the typing animation plays (or is skipped by clicking), visitors can type real commands:
help— full command listcat resume,cat skills,cat education— browse contentls projects/,cat projects/<name>— explore projectscd resume,open contact— navigate to pagesneofetch— ASCII art info cardsudo hire-me,cowsay,fortune,sl,matrix— easter eggs- Tab completion and command history (arrow keys)
The contact form runs on Cloudflare Workers with the Resend API. Environment variables needed:
TO_EMAIL=your-email@example.com
FROM_EMAIL=contact@your-domain.com
FROM_NAME=Your Contact Form
RESEND_API_KEY=re_your_api_key_hereFor local development, add these to .dev.vars. For production, use npx wrangler secret put <KEY>.
The CI pipeline generates a PDF of the /resume page using Playwright:
npm run build
npx http-server ./dist -p 8180 &
node scripts/generate-pdf.js # outputs dist/resume.pdfThe GitHub Actions workflow (.github/workflows/build.yml) runs this automatically on push to main and uploads the PDF as an artifact.
npx wrangler deployDeploys to Cloudflare Workers. The wrangler.toml is configured with the build command and asset directory.