The homepage of daniellocatelli.com displayed on a laptop.

Portfolio Website

Date2024-04-27
OrganizationDaniel Locatelli

PlaceOnline
Linkdaniellocatelli.com

This is the website you are reading right now. It started in April 2024 as a small Astro site and has since grown into a playground for the way I like to build things: fast static pages, content that is easy for both humans and AI tools to read, and a few interactive pieces to spice things up.

Tech Stack

  • Astro with TypeScript for the site itself, React for the few islands that need interactivity, and Tailwind CSS for styling.
  • Astro Content Collections for all content, written as markdown and MDX with typed frontmatter and validated at build time.
  • Claude (Anthropic) for the homepage chat, with Supabase as a vector store for retrieval.
  • Three.js for the geodesic sphere.
  • Cloudflare Workers with Static Assets for hosting, edge caching, and the agent-facing endpoints; prerendered pages are served straight from the edge, and the Worker only runs for the chat and MCP endpoints.

Claude Code as content management system

All the content lives as plain text files (written in markdown) in the same place as the code, the public repository on GitHub: one small file per project, research entry, publication, teaching item, or CV section, with a short header that holds the facts (title, dates, tags) above the text of the page, and a copy in each of the three languages. There is no database and no separate content system behind the pages.

The src/ folder of the repository: assets/ expanded down to the cover image of this page, content/ down to its markdown file, the other folders collapsed.
The src/ folder of the repository: assets/ expanded down to the cover image of this page, content/ down to its markdown file, the other folders collapsed.

The point of this setup is to make the content directly accessible to AI harnesses such as Claude Code. Because the content is just files next to the code, Claude Code can read, edit, create, and cross-check entries the same way it works on source code. In practice this means I use Claude Code as the content management system (CMS), the tool you would normally log into to add a page or fix a typo: I describe a new project or a correction in a sentence, and it writes or updates the files, keeps the headers consistent, and checks the related entries in the other languages. This very page was written that way. Everything on this site is co-created, from the code to the content.

Keeping the content in the repository as plain text has a second payoff: it is straightforward to chunk it, embed it, and feed it to a language model. That is what makes the AI chat on the homepage possible (more on it below).

Translation by Claude Code

The site is available in English, Portuguese, and German. There is no translation service in the pipeline: when a content file changes in one locale, Claude Code translates it and updates the corresponding files in the other two. Structural fields such as dates, links, and places are kept in sync, while translatable fields such as country and city names are localised. The same applies to the interface strings, which live as typed objects per locale.

How a change in one language reaches the other two: Claude Code reads the repository rule and writes the matching files, keeping dates and links identical and translating names and text.
How a change in one language reaches the other two: Claude Code reads the repository rule and writes the matching files, keeping dates and links identical and translating names and text.

AI chat on the homepage

The homepage opens with a chat powered by Claude. Visitors can ask what I am working on, where I studied, which tools I use, or anything else covered by the site, and get an answer grounded in the actual content rather than a generic reply.

The chat input on the homepage: a rounded text field reading "Ask me something..." with a send arrow, and the caption "Powered by Claude Haiku 4.5" underneath.
The chat input on the homepage: a rounded text field reading "Ask me something..." with a send arrow, and the caption "Powered by Claude Haiku 4.5" underneath.

Under the hood, a knowledge pipeline turns the content collections into small text chunks per locale (individual pages, CV entries, a chronological timeline, and a set of pre-written FAQ answers for the most common visitor questions), embeds them with Voyage AI, and stores the vectors in Supabase. When a question comes in, the API endpoint retrieves the most similar chunks and passes them to Claude as context. Whenever content changes, a single command regenerates the knowledge files and uploads fresh embeddings, and a benchmark script runs a fixed set of common questions against the chat to make sure it still answers all of them correctly.

Architecture diagram: at build time the site's markdown content is split into knowledge chunks, embedded with Voyage AI and stored in Supabase; at runtime a visitor's question is embedded, the nearest chunks are retrieved and passed to Claude, which streams a grounded answer back to the page.
Architecture diagram: at build time the site's markdown content is split into knowledge chunks, embedded with Voyage AI and stored in Supabase; at runtime a visitor's question is embedded, the nearest chunks are retrieved and passed to Claude, which streams a grounded answer back to the page.

The geodesic sphere

Further down the home page, between the service offerings and the “Architect + Programmer” section, sits a geodesic sphere rendered with Three.js. It follows the construction Buckminster Fuller made famous: start from an icosahedron, subdivide each face, project the vertices onto a sphere, and take the dual, so that the twelve original vertices become pentagons and everything else becomes hexagons. The sphere rotates as you scroll, tying the motion of the page to the geometry. The green polygon edges are drawn as thin screen-space strips rather than raw one-pixel GL lines, so they stay smooth and evenly thick on any screen, and the faces are nudged back slightly in depth so the edges never flicker against the surface. A light fog toward the black page background fades the faces at the back of the sphere, giving the view a sense of depth.

It is also a nod to my own path: geodesic and lightweight structures are a recurring theme in the projects and research on this site, from the O3 Pavilion, where it started for real, through Common Sky and my master thesis Building Across Scales to my doctoral work on timber structures. Three.js is fetched right after the first screen has painted, in an idle moment, so it never sits on the critical path of the initial page load but is ready by the time you scroll down to the sphere.

Presentation mode

Content items can carry a slide deck that lives alongside the writeup, in the same folder and the same repository. Decks are authored in MDX with a small YAML shorthand for the common slide types (title, text, image, image row, video, overlays), and rendered in the browser with keyboard navigation, an overview of all slides, and a presenter window. I use this for teaching and talks, so that a lecture and its slides are published together, versioned together, and translated together.

The slide overview of the "Computational Architecture in Germany" deck: a grid of slide thumbnails, the first one highlighted in green, with Exit, help, and fullscreen controls in the top right and a 1 / 112 counter in the bottom corner.
The slide overview of the "Computational Architecture in Germany" deck: a grid of slide thumbnails, the first one highlighted in green, with Exit, help, and fullscreen controls in the top right and a 1 / 112 counter in the bottom corner.

Agent-ready on Cloudflare

Since much of the traffic to a site like this will increasingly come from AI agents rather than browsers, the site exposes its content in the formats agents expect:

  • an llms.txt index per locale, generated from the content collections at build time;
  • a markdown companion for every content page (append .md to the URL), plus content negotiation so that a request with Accept: text/markdown receives markdown directly;
  • a robots.txt that explicitly welcomes AI crawlers, a sitemap with image entries, and an API catalog under /.well-known/;
  • a small read-only MCP server, so that agents can query the site’s content as tools;
  • DNS-AID discovery records (_mcp._agents and _index._agents SVCB records, DNSSEC-signed), so that agents can find the MCP endpoint from the domain name alone;
  • a skills index under /.well-known/agent-skills/, following Cloudflare’s Agent Skills discovery RFC, with two SKILL.md files in the Agent Skills format that teach an agent how to query the site via MCP or read it as markdown.

Getting content negotiation to work on prerendered pages took some digging into how Cloudflare’s request pipeline, Workers Static Assets, and Astro’s build-time middleware interact; the solution is a zone-level Cloudflare Snippet that rewrites the URL before it reaches the Worker. On isitagentready.com, the checker that accompanies Cloudflare’s agent-readiness guide, the site went from a 25% score to 71/100, “Level 5, Agent-Native”, with full marks for discoverability, content, and bot access control. The remaining points sit in the API and auth category and are deliberately left open: OAuth discovery, protected-resource metadata, and an auth.md only make sense when there is something to log in to, an A2A agent card describes an agent that offers services to other agents, and WebMCP exposes in-page actions such as forms or checkouts. A read-only portfolio has none of these, so the checker keeps listing them and the site keeps declining them.

Is It Agent Ready? result: 71/100, Level 5, Agent-Native
Is It Agent Ready? result: 71/100, Level 5, Agent-Native

Performance and Lighthouse

Astro renders the site to mostly static HTML, which already gives it a head start. Lighthouse scores of 100 for performance, accessibility, best practices, and SEO then come from not loading what the visitor does not need yet:

  • Images ship in responsive sizes with explicit dimensions, lazy-loaded just ahead of the viewport; fonts are subset and preloaded.
  • Three.js loads during an idle moment and only redraws the sphere while it is moving.
  • The chat window is fetched only once a visitor starts typing, so the hero input itself ships a few kilobytes of JavaScript.
  • The skills-map logos are separate lazy-loaded images instead of inline SVG, which cut the homepage HTML from about 350 KB to under 70 KB.
Lighthouse result: 100 for performance, accessibility, best practices, and SEO
Lighthouse result: 100 for performance, accessibility, best practices, and SEO

A personal toolbox behind the public pages

The site also hosts pages that are not linked from anywhere and exist mainly for my own use. The short CV, the full CV, and the PhD-oriented CV live at unlisted URLs, are rendered from the same content collections as the rest of the site (so an experience or publication only ever needs to be entered once), and carry print styles so that saving the page as a PDF produces a clean, up-to-date document whenever one is needed. A couple of similarly unlisted pages serve as title cards for recorded lectures. In this way the site doubles as a small workspace, not only a showcase for visitors.

The browser print dialog over the CV page: the preview shows the CV laid out as a clean white document with photo, name, "PhD Candidate at ETH Zurich", summary, skills, and professional experience, ready to be saved as a PDF.
The browser print dialog over the CV page: the preview shows the CV laid out as a clean white document with photo, name, "PhD Candidate at ETH Zurich", summary, skills, and professional experience, ready to be saved as a PDF.

Smaller details

  • Link previews at build time. External links listed on a page are rendered as preview cards. Their titles, descriptions, images, and favicons are fetched once and cached in the repository, so the build is reproducible and no third-party request happens at page load.
  • Tooltips everywhere. One small tooltip system (a popover panel with an arrow, positioned and flipped by a few lines of JavaScript) serves every tooltip on the site: markdown footnotes show the note inline on hover so readers do not have to jump to the bottom of the page; each tool in the homepage Map of Knowledge explains where and how I use it; the social icons in the footer, the contact section and the CV header name their destination; and the slide-deck controls show their keyboard shortcut. No native browser tooltips remain.