Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,11 @@ Decision: **Cloudflare's Git integration deploys, GitHub Actions runs CI in para
## Content model

- **Experience** (content collection `experience`, one YAML file per entry in `src/content/experience/`, schema in `src/lib/experience.ts`): education and work, shown in two groups (Education, Work) as a **compact list with icons**, deliberately different from the projects timeline (which has dots and cards). Fields: `type`, `title`, `organization`, optional `unit`, `start`/`end` as `YYYY-MM` (no end means "Present"), `highlights` (1-4), optional `coursework`, optional `relatedProject` (id of a project, renders a link to its card `#project-<id>`). Newest first. Not shown: school (Abitur), final degree grades, non-dev jobs, certificates, and no CV download. Never publish a document with personal contact data.
- **Projects** (content collection `projects`, one YAML file per project in `src/content/projects/`, schema in `src/lib/projects.ts`), shown as a vertical timeline, newest first (by end year, then start year). Fields: `title`, `subtitle`, `kind` (project, bachelor-thesis, master-thesis), optional `institution` (shown next to the kind, e.g. a university), optional `grade` (small badge at the top right of the card, e.g. "1.0", explained as best possible in the German system for screen readers), `start`/`end` year, optional `stats` (1-3 project key figures such as "-47% mean delay", shown as a strip; a grade is not a key figure), `highlights` (1-5 bullets), `technologies` (ids from the registry), `code`, an optional `note` and an optional `art` (the small animated picture on the card, one of `projectArts` in `src/lib/projects.ts`; the pictures are pure CSS and SVG in `src/components/project-art/`, calm, a loop of about ten seconds, hidden from assistive technology, still with reduced motion, paused off screen by a tiny script, on the right of the card on wide screens and as a strip below the subtitle on narrow ones; next to the title and the key figures, or, when a project has no key figures, next to the title and the highlights; a new picture is a new component plus an entry in `projectArts`; every picture uses the viewBox `0 0 240 200` and labels of `font-size: 8.5px` in the mono font so that text is equally big in every card, a test checks it). The Defuze picture is generated: change `scripts/generate-defuze-picture.mjs`, run it, commit the result; a test fails if the file differs from what the script makes).
- **Projects** (content collection `projects`, one YAML file per project in `src/content/projects/`, schema in `src/lib/projects.ts`), shown as a vertical timeline, newest first (by end year, then start year). Fields: `title`, `subtitle`, `kind` (project, package, bachelor-thesis, master-thesis; `package` is shown as "Open source package"), optional `institution` (shown next to the kind, e.g. a university), optional `grade` (small badge at the top right of the card, e.g. "1.0", explained as best possible in the German system for screen readers), `start`/`end` year, optional `stats` (1-3 project key figures such as "-47% mean delay", shown as a strip; a grade is not a key figure), `highlights` (1-5 bullets), `technologies` (ids from the registry), `code`, an optional `note` and an optional `npm` (page on npm, `https://www.npmjs.com/package/...`, second button next to GitHub) and an optional `art` (the small animated picture on the card, one of `projectArts` in `src/lib/projects.ts`; the pictures are pure CSS and SVG in `src/components/project-art/`, calm, a loop of about ten seconds, hidden from assistive technology, still with reduced motion, paused off screen by a tiny script, on the right of the card on wide screens and as a strip below the subtitle on narrow ones; next to the title and the key figures, or, when a project has no key figures, next to the title and the highlights; a new picture is a new component plus an entry in `projectArts`; every picture uses the viewBox `0 0 240 200` and labels of `font-size: 8.5px` in the mono font so that text is equally big in every card, a test checks it). The Defuze picture is generated: change `scripts/generate-defuze-picture.mjs`, run it, commit the result; a test fails if the file differs from what the script makes).
- `code.status` is `public` (GitHub URL required and shown), `planned` ("Code coming soon") or `none` ("Private project"). A link is impossible for planned/none (the schema strips it), and a public project needs a github.com URL.
- To publish a thesis later: change its `code` to `status: public` with the URL.
- Start with placeholder data; real content is swapped in later via the collections only.
- Current projects: this website (public), Defuze (MERN + ML, public), Master thesis (GNN + RL for airborne networks, code planned), Bachelor thesis (Tetrys FEC protocol in C++, public), Grade & Study Tracker (2019 Android app, no longer available). Adding a project only needs a new YAML file.
- Current projects: this website (public), playwright-layout-guard (my open source package, public, on npm; the card shows the package kind, an animated picture and the npm button), Defuze (MERN + ML, public), Master thesis (GNN + RL for airborne networks, code planned), Bachelor thesis (Tetrys FEC protocol in C++, public), Grade & Study Tracker (2019 Android app, no longer available). Adding a project only needs a new YAML file.
- Icons: build-time inline SVG from `@iconify-json/lucide` (general symbols) and `@iconify-json/simple-icons` (brands such as GitHub) through our own `Icon.astro`. No icon fonts, no CDN, no runtime JS. Icons appear next to external links (GitHub, LinkedIn, project links).
- Hero: an availability badge ("Open to opportunities", green status dot, links to the contact section; controlled by `availability` in `src/lib/site.ts`, set `open` to false to hide it), a short intro with calls to action "View projects" and "Contact" (no CV download).
- Timeline strip above the experience list: `ExperienceTimeline.astro` draws bars from the experience entries (layout in `src/lib/timeline.ts`), desktop only and hidden from assistive technology; the experience schema has an optional `short` label for the bar. Cross-document page transitions were tried and dropped (Chrome logs an unhandled rejection when a transition is aborted, see PLAN.md).
Expand Down Expand Up @@ -271,7 +271,7 @@ Full plan in PLAN.md, Phase 4. The foundation (4.0) is built and **Tech Words (4
- **Terminal** (type `>` in the palette, or the terminal on the 404 page): the commands are one list, `commands` and `runCommand` in `src/lib/terminal.ts` (pure, tested without a browser; `help` is made from the list and a test runs every listed command), the screen is `src/scripts/terminal-ui.ts` (history in memory, Tab completes). Commands: help, about, projects, games, notes, neofetch (the short version of /uses), open, play, ls, cat, cd, pwd, git log (the last commits of the build, made when the site is built; a build without history prints a note), email, whoami, radar, clear, exit, and some that `help` does not tell (sudo hire leon, rm, vim, echo, ping). **A new command is an entry in `commands` (if it is listed) and a case in `runCommand`, with a test.**
- **The data** is `/palette.json` (`src/pages/palette.json.ts`, built by `src/lib/palette-data.ts` from the sections, the live games, the notes, the projects, the Uses summary and `git log`), so a new note, game or project shows up in the palette and in the terminal by itself. The address is base64 in it. A build-output test (`tests/build-output/palette.test.ts`) checks that every link in it exists and the live check fetches it.
- **Nothing of it loads with a page except a small script** (about 1 KB, the key listener, the Konami code and the console greeting). The palette, the terminal, the radar and their data and styles (about 10 KB) are fetched when it is first opened (the button starts the download when the pointer comes near). **Their styles must not be imported as CSS from a script:** Astro links the styles of every script of a page, also those it only loads later, so the palette CSS was on every page (found by a test on 2026-10-16). They are imported as an address (`?url`) and added by `src/scripts/styles.ts` when needed; `tests/build-output/palette.test.ts` fails when a page links them. The 404 page is the exception on purpose: the lines of its terminal are in the page, so it imports `terminal.css` itself.
- **Budgets:** the home page was at 9.7 KB of 10 KB scripts and 19.4 KB of 20 KB stylesheets after this (2026-10-16; the fifth tile picture, Backbone, brought it to 20.2 KB and the limit to 21 KB on 2026-10-18, and the limit of `/games/` to 28 KB; the five game pages share one stylesheet that holds the CSS of every game, so since Backbone every game page has the 28 KB limit, 27.8 KB measured on 2026-10-20: the next game or a larger Backbone style needs the styles split per game page first); the notes styles (`src/styles/note-prose.css`) and the legal styles (`src/styles/legal.css`) were moved out of `global.css` into the layouts that use them to make room. Any new global style or script has to be paid for the same way; do not raise a budget without a reason.
- **Budgets:** the home page was at 9.7 KB of 10 KB scripts and 19.4 KB of 20 KB stylesheets after this (2026-10-16; the fifth tile picture, Backbone, brought it to 20.2 KB and the limit to 21 KB on 2026-10-18, and the limit of `/games/` to 28 KB; the home page stylesheet was 20.7 KB of 21 KB after the card of playwright-layout-guard and its picture (2026-10-24), so the next picture or global style needs room made first; the five game pages share one stylesheet that holds the CSS of every game, so since Backbone every game page has the 28 KB limit, 27.8 KB measured on 2026-10-20: the next game or a larger Backbone style needs the styles split per game page first); the notes styles (`src/styles/note-prose.css`) and the legal styles (`src/styles/legal.css`) were moved out of `global.css` into the layouts that use them to make room. Any new global style or script has to be paid for the same way; do not raise a budget without a reason.
- **Easter eggs** (none tracks or stores anything): a greeting in the console (`console.info`, with the link to the source), a greeting as a comment in the HTML of every page (before `<html>`; the charset must stay within the first 1024 bytes, a test checks it), the **radar** (Konami code, or the command `radar`; ignored while typing in a field), `/humans.txt` and `/.well-known/security.txt`. **The radar is useful, not only an animation** (`src/scripts/radar.ts`, `src/lib/radar.ts`, `src/styles/radar.css`): a beam turns once around the middle of the screen, every thing of the page that is worth a look and on the screen lights up with a frame and its label when the beam reaches it (the delay comes from its direction, `angleOf` and `pingDelay`), then a panel lists everything the page has as buttons that scroll there and light the thing up again (Escape or the button closes it, a second run replaces it). **What is worth a look is marked by hand** with `data-radar="Label"` on the element (the availability badge, the hero buttons, "What I am doing now", the key figures of a project and of a note, the games, the contact form and the direct contacts, the setup on the Uses page, the game itself, "Play" and "The code on GitHub" on a note, the terminal of the 404 page, and the source link and "How this site is built" in the footer, so no page is empty); nothing is guessed. **A new page or section with something the visitor should find gets a `data-radar` on it** (a label of a few words; the same label counts once). With reduced motion there is only the panel. The layers are fixed and ignore the pointer, so nothing moves. **A frame is never measured once:** it is put at the place of its element in every frame of the screen (the page scrolls smoothly to a chosen find, and a block that fades in is still rising when the page arrives), and after a click in the list it starts only when the element has stood still for six frames (`settled`); a test clicks six finds far apart and checks the frame against the element three times each (the first version measured 450 ms after the click and was up to 1000 px off).

## Decisions and rationale
Expand Down
9 changes: 9 additions & 0 deletions src/components/ProjectCard.astro
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@ const { project } = Astro.props;
{project.code.status === 'planned' ? 'Code coming soon' : 'Private project'}
</p>
)}
{project.npm && (
<ExternalLink
href={project.npm}
class="btn-outline inline-flex items-center gap-2 rounded-lg px-4 py-2 text-fg"
>
<Icon name="simple-icons:npm" class="size-4" />
View on npm
</ExternalLink>
)}
{project.note && <p class="text-muted">{project.note}</p>}
</div>
</article>
Loading
Loading