Kluth Studios

GUIDE — HOW THIS SITE WAS BUILT

The lap,
explained.

Everything on kluthstudios.com is hand-written — authored as Astro components, compiled to pure static HTML, zero client-side framework, no stock assets. This page is the pit-lane walkthrough so anyone on the team can extend it or rebuild it from zero.

§ 00

The concept

The studio’s first project, Traumrunde, is a Nürburgring driving sim — so the portfolio borrows its metaphor: the entire page is one lap, driven from dawn to night. “Traumrunde” is German for dream lap.

Scrolling is the throttle. A fixed WebGL landscape sits behind every section and its palette interpolates through three keyframes — dawn, dusk, night — as you descend. A lap-timer HUD in the corner draws a stylised circuit, moves a marker along it, ticks sectors S1–S4 and maps your scroll position onto a reference lap time of 6:57.000.

The architecture matches the ethos. Astro renders every card, sector and slot to static HTML at build time from one typed data file; the only JavaScript the visitor downloads is three small, classic, hand-written scripts that enhance that HTML — the scene, the HUD, the flourishes. No client-side framework, no hydration, nothing to parse before the page is readable.

FILE MAP
src/pages/index.astro        the lap
src/pages/guide/index.astro  this page (no JS)
src/pages/404.astro          gravel trap
src/layouts/Base.astro       head, meta, fonts
src/components/*.astro       header, HUD, works…
src/data/site.ts             ★ site data — edit me
src/styles/main.css          design system
public/js/scene.js           WebGL dawn→night
public/js/main.js            HUD, reveals, cursor
public/js/three.min.js       three.js r128 (MIT)
public/fonts/*.woff2         self-hosted, subset
.github/workflows/deploy.yml push → build → Pages
§ 01

The design system

The palette avoids the default “dark portfolio” black-and-neon. It is fog, forest and petrol — the Eifel at 6 AM — with one marshal-flag orange used strictly as a signal, never as decoration.

--nebel#EDEFEA · fog paper
--tanne#0E1613 · forest ink
--nacht#0A100E · night
--moos#49594F · moss
--asche#96A29A · ash
--signal#E8420E · marshal
--amber#FFB454 · headlight

Archivo variable — display

wdth 125 · wght 860 · one file carries the entire weight/width range (119 KB)

Instrument Serif italic — the dream voice

used only for the lyrical asides, never for UI

Martian Mono — telemetry: labels, timers, coordinates

tabular numerals keep the lap timer from jittering

All three families are subset to Latin and self-hosted as woff2 (≈ 213 KB total), so the site makes zero third-party requests. Because Archivo is a variable font, every width/weight on the site — from the hairline nav to the 9-rem pit-wall headline — comes from a single file.

§ 02

The lap system

One requestAnimationFrame-throttled scroll handler computes progress p = scrollY / (scrollHeight − innerHeight) and fans it out to everything:

The scene. scene.js owns a single fixed canvas. Terrain is a plane displaced in the vertex shader by two octaves of value noise; a JS twin of the same noise places a glowing ribbon — the dream lap — onto that terrain as a closed Catmull-Rom tube. Sky, fog, terrain, ribbon and dust motes all read one uniform, uCycle, and mix through three keyframed palettes (dawn → dusk → night). Scroll eases uCycle toward p; the pointer adds a small camera parallax.

The HUD. The circuit is one closed SVG path duplicated twice: a faint “track” and a bright “run” whose stroke-dashoffset equals length × (1 − p), so it draws itself as you scroll. The marker dot rides getPointAtLength(length × p). The timer is just p × 417 000 ms — a 6:57.000 reference lap — and sectors flip when the viewport centre passes each section.

The cards. Every project card’s artwork is generated at load by a seeded PRNG (mulberry32): a speed trace, a throttle/brake band, a mini circuit and a ghost number. Change the seed, get new art. No images anywhere on the site.

GRACEFUL DEGRADATION

No WebGL → a static gradient sky takes over (body.no-webgl).

prefers-reduced-motion → marquee, cursor, tilt and reveals switch off; the scene renders single frames instead of animating.

No JavaScript → because Astro pre-renders everything, the full works grid, copy and links are all still there; only the flourishes are missing.

§ 03

Adding a project

The grid, numbering, artwork and layout are all generated from one typed file: src/data/site.ts. Append an object, save, and the dev server (npm run dev) hot-reloads the grid — the types will refuse to build anything malformed. Two redacted slots are already on the board so the grid never looks empty while work is in flight.

{
  kind:  'card',        // 'featured' = spotlight · 'card' = grid · 'reserved' = redacted slot
  id:    'project-id',
  title: 'Project name',
  kicker:'One-line hook',
  year:  '2026',
  type:  'Prototype',    // shown in the card footer
  status:'In development',
  desc:  'Two or three sentences. Plain text — Astro escapes it for you.',
  tags:  ['Three.js', 'WebAudio'],
  stats: [['Label', 'Value']],   // featured cards only
  links: [{ label: 'View repository', href: 'https://github.com/KluthStudios/…' }],
  seed:  42            // any integer — regenerates the card art. Reroll until you like it.
}

Keep one or two kind: 'featured' entries at most; everything else reads best as cards. When a real project is ready, replace a reserved slot with it and add a fresh one at the end if you want to keep the “more coming” signal.

§ 04

Deploying to kluthstudios.com

  1. Push the project to GitHub. Create a repository under the org — e.g. KluthStudios/kluthstudios.com — and push everything to main. The deploy workflow (.github/workflows/deploy.yml) and the CNAME file (in public/) are already included.
  2. Turn on Pages. Repo → Settings → Pages → Source: GitHub Actions. From now on every push to main builds the site and deploys it automatically — no manual steps.
  3. Point the DNS. At the domain registrar, add four A records on the apex (@): 185.199.108.153, 185.199.109.153, 185.199.110.153, 185.199.111.153 — and a CNAME on www pointing to kluthstudios.github.io.
  4. Bind the domain. Back in Settings → Pages, enter kluthstudios.com as the custom domain, wait for the DNS check, then tick Enforce HTTPS. Propagation is usually minutes, occasionally a few hours.
  5. Verify the lap. Hard-refresh, scroll to the bottom, confirm the timer crosses the line at 6:57.000. You’re live.

Prefer Netlify, Vercel or Cloudflare Pages? All three auto-detect Astro: connect the repo, accept the defaults (npm run build, output dist), attach the domain there instead. Locally, npm run dev serves the site at localhost:4321 and npm run preview serves a production build.

HONEST NOTES

① The Traumrunde repo is currently private — its “View repository” button will 404 for the public until the repo is flipped to public (or the link is pointed at a release/demo).

hello@kluthstudios.com is a placeholder inbox — set the real address once in src/data/site.ts.

③ DNS and hosting need account access, so those two steps are yours — everything else ships in this repo.

§ 05

The four passes

Before sign-off, every element went through four full iteration passes — the same routine to reuse whenever the site changes:

PASS 1
FUNCTION

The Astro build must compile clean — types included — plus a syntax check on every runtime script, HTML validation of the built output, and a mechanical sweep that every referenced asset path (fonts, scripts, styles, icons, links between pages) resolves to a real file in dist/.

PASS 2
ROBUSTNESS

Exercise the failure paths: WebGL unavailable, JavaScript disabled, prefers-reduced-motion, tiny viewports, resize mid-scroll. A DOM smoke-run boots the built page headlessly and asserts the HUD, reveals and card art all initialise cleanly on top of the pre-rendered HTML.

PASS 3
DESIGN

Fine-tooth comb on the system itself: compute WCAG contrast ratios for every text/background pair in the palette, audit the type scale and spacing rhythm across all four breakpoints, and check that the signal colour is never used where it can’t hold contrast.

PASS 4
POLISH

The last 2%: cursor labels, focus-visible states, tab order, meta/OG tags, micro-copy, easter eggs (press G for the layout grid), and a final read of every sentence on the site out loud.

§ 06

Colophon

Type: Archivo (Omnibus-Type), Instrument Serif (Rodrigo Fuenzalida & Jordan Egstad), Martian Mono (Evil Martians) — all under the SIL Open Font License, subset and self-hosted.

Code: built with Astro (MIT), which compiles the components to plain static HTML; three.js r128 (MIT) is the only runtime dependency. Everything else — shaders, the ribbon’s geometry, HUD, cursor, reveals, procedural card art, this guide — is original and hand-written. Zero client-side framework, no trackers, no cookies, no stock assets, no generated imagery.

Structure: the build output in dist/ is plain HTML + CSS + three classic scripts, so it runs identically on a $0 static host, GitHub Pages, or the custom domain.