A technical maker's blog + project archive. Every project is a build log: dated, step-by-step, with a bill of materials, figures, and a "what I'd do differently" outro.
| Layer | Tech | Notes |
|---|---|---|
| Framework | Astro 7 | Fully static output (astro build → dist/), zero client JS except the theme toggle |
| Content | MDX + Astro content collections | Build logs in src/content/builds/, frontmatter validated by a Zod schema |
| Styling | Tailwind CSS v4 | CSS-first config via @theme in src/styles/global.css; light/dark via data-theme |
| Typography | Fontsource | Self-hosted Space Grotesk Variable (display) + JetBrains Mono Variable (annotations) |
| Images | astro:assets |
Build-time optimization to responsive WebP; placeholder-swap system (see below) |
| SEO | @astrojs/sitemap |
Sitemap generated from the site URL in astro.config.mjs |
| Runtime | Node 22+ | Build-time only — the deployed site is static files |
| Deploy | Docker (multi-stage) → nginx:alpine | Coolify-ready; config in deploy/nginx.conf |
npm install
npm run dev # http://localhost:4321
npm run build # static site -> dist/
npm run preview # serve the built site locally
npx astro check # typecheck + frontmatter validationsrc/
content/builds/*.mdx the build logs (frontmatter validated by src/content.config.ts)
assets/builds/<slug>/ PHOTO DROP ZONES — see below
components/ BuildImage, PartsTable, Callout, StatusBadge, ...
layouts/ BaseLayout (shell), BuildLogLayout (log page anatomy)
pages/ / , /builds/ , /builds/[slug] , 404
lib/site.ts name, email, socials, nav — edit personal info here
Every image on the site renders as a styled "PHOTO PENDING" placeholder until a real file exists. To swap one in, drop the file into the build's asset folder and rebuild — no code changes.
The placeholder tells you the expected filename (any of .jpg .jpeg .png .webp .avif works).
Expected files per build:
| Build | Folder | Expected image names |
|---|---|---|
| eVTOL drone | src/assets/builds/evtol-drone/ |
flight-test (hero), airframe-print, wing-assembly, prop-layout, wiring, ground-station |
| Retro handheld | src/assets/builds/retro-handheld/ |
final-assembled (hero), retropie-boot, gpio-soldering, wiring-loom, cad-enclosure, printed-shell, assembly |
| Studentify | src/assets/builds/studentify/ |
telegram-chat (hero), architecture, plan-output |
| Motorsports PCB | src/assets/builds/motorsports-pcb/ |
kicad-layout (hero), enclosure-fit, kicad-before-after, board-in-enclosure |
Images are optimized at build time (responsive WebP with srcset). Layout is identical between placeholder and real photo, so swapping never shifts the page.
- Copy any file in
src/content/builds/and edit the frontmatter (schema:src/content.config.ts).buildNumberdrives theBUILD 00Nstamps and prev/next order;datedrives archive sorting. - Create
src/assets/builds/<slug>/for its photos. - Reference figures as
<BuildImage src="<slug>/<name>" ... />— they render as placeholders until photos land. - Convention: every log ends with a
## What I'd do differentlysection.
docker build -t portfolio .
docker run -p 8080:80 portfolioMulti-stage build: Node builds dist/, nginx:alpine serves it (config in deploy/nginx.conf —
gzip, immutable caching for hashed /_astro/ assets, styled 404).
Before going live, set the real domain in astro.config.mjs (site:) and public/robots.txt
(sitemap URL).