Personal portfolio for Anes Hodza, Full-Stack Software- and Security-Engineer.
Bilingual: German default (/), English (/en/).
The brief described this direction as Source Serif, EB Garamond, or Newsreader paired with Inter. Generous leading. NYT obituary calm. Drop caps allowed only on long-form pages. The site reads like a typeset newspaper: a narrow column, hairline rules between sections, italic captions, oxblood accent for links, small-caps Inter labels above serif headings, and a drop cap reserved for the About page.
- Source Serif 4 (Google Fonts, self-hosted via
@fontsource/source-serif-4) — body, headings, display. - Inter (Google Fonts, self-hosted via
@fontsource/inter) — labels, navigation, captions, buttons. The role split is strict: every serif is content, every sans is metadata. - JetBrains Mono (Google Fonts, self-hosted via
@fontsource/jetbrains-mono) — only inside<code>/<pre>.
No system fonts. No Google Fonts CDN.
| Token | Light | Dark |
|---|---|---|
| paper | #FBFAF7 (warm cream) |
#14110D (deep ink) |
| ink | #1A1815 |
#ECE6D7 |
| muted | #4A463F / #807A6E |
#B8B0A0 / #7A7468 |
| rule | #D8D3C5 |
#2B2620 |
| accent | #6B2B2B (deep oxblood) |
#C97373 (softened) |
One accent. Used only on links on hover, the drop cap, the favicon rule, and the focused link border.
- Astro 5 with TypeScript strict mode.
- Tailwind CSS v4 via
@tailwindcss/vite, design tokens in@theme. - Astro i18n routing (
defaultLocale: 'de',prefixDefaultLocale: false). - View Transitions via
<ClientRouter />. - Content Collections for
projects,experience,education, all with Zod schemas. - Zero client framework: vanilla TypeScript for the theme toggle and the projects-page tech filter. No React, no Vue, no Solid.
bun install
bun dev…or npm install && npm run dev. Visit http://localhost:4321/ (German) or
http://localhost:4321/en/ (English).
Build:
bun run build
bun run previewType-check:
bun run check.github/workflows/deploy.yml builds on every push to main and publishes
dist/ to GitHub Pages via the official actions/deploy-pages flow.
One-time setup:
- Push the repo to GitHub.
- Repo Settings -> Pages -> Build and deployment -> Source: GitHub Actions.
- Repo Settings -> Pages -> Custom domain:
aneshodza.ch(thepublic/CNAMEfile is committed so this survives every deploy). - Point the DNS for
aneshodza.chat GitHub Pages (apexArecords to the four GitHub IPs, or aCNAMEfor thewwwsubdomain to<your-user>.github.io). - Tick Enforce HTTPS once the certificate provisions.
The workflow needs bun.lock committed (it is). public/.nojekyll is shipped
so GitHub Pages does not strip Astro's _astro/ chunk directory.
If you want https://<user>.github.io/<repo>/ instead of aneshodza.ch:
- Delete
public/CNAME. - In
astro.config.mjs, changesitetohttps://<user>.github.ioand addbase: '/<repo>'. - Remove the Custom domain setting in repo Settings -> Pages.
- One column, one rule, one accent. Sections are separated by thin
horizontal rules (
var(--rule)), never by colour blocks. The accent is oxblood and appears only where a recruiter actually has to look (link hover, drop cap, focused link border, favicon underline). - Inter is metadata, Source Serif is content. Anything tracked, uppercase, and small (labels, dates, nav, captions, tags, button text) is sans. Everything with semantic weight (headlines, body, names) is serif.
- Hero is a masthead. A label (“Portfolio”), an issue line (date / location), then the name and tagline as the largest type on the site, followed by a single supporting sentence and two text-link CTAs. The portrait sits to the right of the masthead, with an italic figure caption.
- Projects index uses a vanilla-TS tech filter. Buttons are text with an
underline, no chips. Filtering hides nothing fancy, just
display: nonewith an empty-state line. - Project detail uses a two-column layout. The tech stack is a sticky sidebar on desktop; the body is a narrow column for readability.
- CV page prints cleanly. A
.no-printclass hides the chrome and@media printswaps to black-on-white. The print button callswindow.print(). - About page is the only place with a drop cap. Faithful to the brief — drop caps allowed only on long-form pages.
- i18n via shared view components. Each route file is a 6-line wrapper that
imports a
Viewcomponent from~/components/pages/. The view detects the locale from the URL and renders the right strings and collection entries. This keeps the route tree mirrored without duplicating layout code. - Theme toggle is islandless. A single inline
<script>in the<head>setsclass="dark"before paint to avoid a flash; the toggle button itself is a tiny inline script in the component. - Favicon is a lowercase serif “a” rendered in Source Serif 4 in the oxblood accent, sized to read clean at 16×16 and 32×32. SVG primary, ICO fallback.
- Portrait placeholder is a real 400×500 JPEG. Swapping the real portrait
is a one-file replacement at
public/portrait.jpg.
- No em-dashes or en-dashes in user-facing text. (This README is the only exception.)
- No
ß. Swiss German usesssthroughout. - Swiss German vocabulary and Swiss date format (
14.01.2026). - Technical keywords are bolded inline; the Centro Labs, Hack Winterthur 2026, Co-founder, Teaching Assistant signals are bolded too.
- Skills and tech stacks repeat the vocabulary recruiters search for: TypeScript, C#, Angular, NestJS, ASP.NET, Ruby on Rails, LangGraph.js, MCP, RAG, Qdrant, PostgreSQL, Bun, Docker, CI/CD, Scrum, TDD, security engineering.
public/ favicon.svg, favicon.ico, portrait.jpg
src/
components/ Header, Footer, Hero, ThemeToggle, LanguageSwitcher,
ProjectCard, ExperienceItem, EducationItem
components/pages/ HomeView, ProjectsView, ProjectView, CVView,
AboutView, ContactView, NotFoundView
content/ projects/{de,en}, experience/{de,en}, education/{de,en}
content.config.ts Zod schemas for the three collections
i18n/ ui.ts (strings), utils.ts (locale helpers)
layouts/Layout.astro Single layout, ClientRouter, OG meta, alt hreflang
pages/ index, projects/[slug], cv, about, contact, 404
pages/en/ mirror of the above
styles/global.css @theme tokens, base, components, print rules
astro.config.mjs i18n routing + Tailwind
tsconfig.json strict, noUncheckedIndexedAccess, path alias ~/*