← portfolio
2025–present

moveslow.info

This site. A few things about how it works.

the stack

SvelteKit on a DigitalOcean droplet, behind Cloudflare. Content in a managed Postgres database. No CMS — posts are seeded by script, or written directly through an MCP server during Claude sessions.

FrontendSvelteKit 2, Svelte 5, TypeScript
BackendSvelteKit server routes, adapter-node, pm2
DatabaseManaged Postgres (DigitalOcean)
InfraDroplet + Cloudflare + nginx

the paper

The background is a WebGL fragment shader — fractional Brownian motion noise layered to simulate cotton-rag paper fiber, plus the faint horizontal lines left by a wire mesh in handmade paper (called the laid finish). Both effects are kept subliminal: you feel texture without consciously registering it.

The texture drifts imperceptibly over time so it never reads as a static image. The constants below control exactly how much of each effect you see — and you can change them live.

grain visibility
0 0.06
u_grain_amp = 0.016
grain scale
coarse fine
u_grain_scale = 80
laid lines u_laid_amp = 0.003
drift speed
frozen fast
u_drift = 0.00040

the reader

Posts use a two-voice format: sections alternate between hallie (warm) and claude (cool). The reader keeps the current section fully opaque and lets everything else fade and blur as you scroll — so you're always reading one voice at a time, but you can feel the whole conversation around it.

The fade/blur system is pure CSS driven by an Intersection Observer. No layout reflows, no JS animation loops.

the graph

Content is stored in a knowledge graph built on alkahest — a small TypeScript library for reasoning over sets of support. Every post, explainer, and work item is a node. Relationships between them are labeled edges with confidence scores.

Right now most edges are HAS_SECTION — the graph knows which sections belong to which post, and in what order. As the site grows, semantic edges like REFERENCES and RESPONDS_TO will connect ideas across pieces.

Alkahest also runs an inference loop called Otter — a forward-chaining reasoner that takes the current set of support and derives new edges. When two posts share a tag, Otter proposes a shares-tag:language edge with a confidence score. A human reviews and keeps or discards it. The graph learns without being told.

the mcp layer

An MCP server at /api/mcp exposes the graph to Claude. During writing sessions, Claude can call add_item, add_edge, add_section, and query — reading from and writing to the graph in real time. This is how most of the reader content gets authored: in conversation, with Claude as co-writer.

The endpoint requires a bearer token, so it's not open to the public — but the graph it produces is what the site runs on.