- Astro 43.5%
- JavaScript 26%
- TypeScript 19.9%
- CSS 7.9%
- HTML 2.3%
- Other 0.4%
|
|
||
|---|---|---|
| .claude | ||
| .forgejo/workflows | ||
| public | ||
| scripts | ||
| src | ||
| .dockerignore | ||
| .gitattributes | ||
| .gitignore | ||
| astro.config.ts | ||
| DESIGN.md | ||
| Dockerfile | ||
| nginx.conf | ||
| package-lock.json | ||
| package.json | ||
| PLAN.md | ||
| PRODUCT.md | ||
| README.md | ||
| resume.yaml | ||
| tsconfig.json | ||
Portfolio
Dragos Bajanica's personal portfolio, built with Astro and Tailwind CSS. Statically generated. No client JavaScript runs for content, navigation, or SEO metadata.
Editing projects
Each project is a Markdown file in src/content/projects/, one file per project. The homepage grid and each project's detail page are both generated from this folder. Nothing else needs to change to add, edit, or remove one.
Adding a new project
- Create
src/content/projects/<slug>.md.<slug>becomes the URL,/projects/<slug>/. - Fill in the frontmatter (see field reference below) and write the long-form write-up as the Markdown body.
- If it has a photo, drop the original into
src/assets/projects/and pointimageat it with a relative path (e.g.../../assets/projects/board.png). Astro resizes and re-encodes it to WebP at build time, so use the original file, not a pre-converted one. - If it's a PCB, export and compress the
.glb(see 3D models below), drop the compressed file intopublic/models/, and pointglbPathat it. - Set
orderto control where it sits in the project grid.
Frontmatter fields
title,summary,category(hardwareorsoftware),tags,orderare required.repoUrlandliveUrlare optional links.imageis an optional preview photo (see step 3 above).glbPathis an optional path to a.glbmodel underpublic/models/, rendered with<model-viewer>on the project's detail page (and on the homepage hero, for UART Board).specsandhighlightsare optional arrays feeding the detail page's spec sheet and bullet list.
Your own photo for the About section: src/assets/portrait.png. It needs to be a true-color image, not a palette/indexed PNG, or it'll show visible banding once rendered at full size.
3D models
.glb files are exported from KiCad with kicad-cli pcb export glb, not parsed in the browser. The raw export needs cleanup before it's usable: kicad-cli leaves every part material at glTF's raw defaults (metalness=1.0, roughness=1.0), which renders flat no matter how the scene is lit; ships silkscreen as translucent, so it fights the copper traces beneath it at glancing angles; leaves hundreds of same-material primitives per part unmerged; and, on boards that aren't green, gets the soldermask, copper, and substrate colors wrong.
scripts/process-pcb-model.ts fixes all of that in one pass:
node scripts/process-pcb-model.ts board_raw.glb public/models/board.glb
It patches metalness and roughness from each part's own flat color, fixes the silkscreen render order, merges same-material primitives into fewer draw calls, corrects the board's color scheme (detected from the soldermask automatically, or pass --scheme=green / --scheme=blue if detection isn't confident), and compresses the result with Draco. Commit the output to public/models/, not the raw export.
It won't recolor individual parts, like turning one connector black. That's a one-off judgment call, done by hand on top of the script's output when a board needs it.
scripts/render-poster.ts renders the static poster image each model shows before the interactive viewer takes over. It drives the real <model-viewer> in a headless browser (scripts/poster-harness.html is the page it loads), using the site's actual lighting and camera, so the poster matches the live model instead of jump-cutting into it on load. Run the dev server first, then:
node scripts/render-poster.ts /models/board.glb src/assets/projects/board-poster.png 798 760
The width and height need to match the real pixel size of wherever the poster is shown. <model-viewer>'s camera distance depends on the container's aspect ratio, so a poster rendered at the wrong size ends up looking like a different zoom level than the live model that replaces it.
Resume
There's no resume page. The header and hero's Resume links open resume.pdf directly. resume.yaml (RenderCV-style YAML) is a source of truth to sync against by hand whenever the PDF changes, but nothing on the site parses or displays it.
Commands
npm install # install dependencies
npm run dev # start the dev server
npm run build # type-check and build to dist/
npm run check # type-check the Astro project
npm run typecheck # type-check scripts/ on its own
npm run preview # preview the production build locally
npm run process-pcb-model # clean up a raw kicad-cli glb export
npm run render-poster # render a model's static poster image
Deploying
Dockerfile builds the site and serves it from nginx-unprivileged. nginx.conf sets cache headers, gzip, and security headers (CSP, HSTS, X-Frame-Options, and so on). .forgejo/workflows/build.yml builds and pushes the image on every push to main.
Docs
PRODUCT.md covers the product brief: audience, goals, and content constraints. DESIGN.md covers the visual design system: colors, type, layout, and component rules.