A site that’s mine, not a template
This is my home on the web — where my work, my writing, and the way I think about the craft live together. I designed it, built it, and shipped it side by side with Astro Rocket, the open-source Astro 6 starter I released at the same time.
Same starting line, different finish. Astro Rocket is the clean, opinionated theme anyone can clone. This site is what happens after the clone — a place that keeps growing, picks up its own personality, and carries the ideas that wouldn’t make sense in a generic starter.
Built on Astro 6 and Tailwind v4
The stack is deliberately small: Astro 6 for the rendering, Tailwind CSS v4 for the styling, TypeScript everywhere. No SPA, no client-side routing, no global state library. Every page is statically generated, every interactive bit ships as the smallest possible island. The only React on the page is the LetterGlitch canvas — and even that is gated to desktop where it earns its place.
The brand colour is blue (data-theme="blue"), but the theme system underneath supports thirteen palettes — orange, amber, lime, emerald, green, indigo, cyan, sky, blue, teal, violet, purple, magenta — each defined in OKLCH so the colour relationships hold across the whole UI. Astro Rocket exposes a dropdown that lets visitors swap between them. On this site, the dropdown is off. Blue is the brand, and the surface should reinforce that, not invite people to talk it out of it.
Lighthouse: 100 across the board
This site scores 100 on every Lighthouse category — 100/100/100/100 — on both desktop and mobile. Performance, Accessibility, Best Practices, and SEO all stay at 100 deliberately, and every new feature is weighed against its Lighthouse cost before it ships. The mobile LetterGlitch was on the table for a while; the 2-point Performance hit it would have caused on phones is exactly why I left it off, which is why mobile holds at 100 alongside desktop. For the full breakdown of what each score actually measures and how to verify it yourself, see What the Lighthouse Score Actually Means.
The hero, distilled
Above the fold the site stays out of its own way. The homepage hero leans on one decision: in dark mode the section fills with a vertical gradient running from --color-brand-600 at the top to pure black at the bottom. No grid, no radial glows, no decorative blobs — just the brand colour bleeding into night.
A handful of small choices make it work:
- Dark-mode only. The gradient is gated behind
.dark .hero-home-gradient, so light mode keeps its existing surface untouched. Light-mode visitors get a clean white hero; dark-mode visitors get the brand statement. - White headline accents. “Web Designer & Developer” flips to pure white in dark mode so the typography reads with maximum contrast against the saturated top of the gradient, instead of competing with it in the brand colour.
- A frosted floating header. The capsule above the hero leaves the gradient mostly visible, just enough surface to keep the navigation legible without breaking the colour wash.
Inner-page heroes — blog, projects, about, contact — drop the decoration entirely and run on the standard surface. One hero carries the brand identity; every other hero gets out of the way and lets the content speak.
The desktop CTA you keep scrolling back to
Every desktop-wide section that ends on a call-to-action — homepage, about, projects, and the Follow Along block on every blog post — finishes with the same composition: a contained, rounded band with the LetterGlitch canvas effect cycling brand-coloured letters behind a near-opaque glass card. The headline and buttons sit on the card; the canvas does its work in the background.
The brand colours feeding the canvas are resolved at runtime through a 1×1 canvas trick that handles the OKLCH → RGB conversion, so the canvas reads the same --brand-300, --brand-600, and --brand-900 variables as the rest of the page instead of carrying a hardcoded copy. The whole pattern is extracted into a single LetterGlitchBand component, which is why dropping it into a new section is one import.
It’s intentionally desktop-only. On a phone the contained band would compress badly and the canvas wouldn’t read; mobile gets a quieter brand-tinted glass card instead, which keeps the rhythm without the overhead. The full breakdown — including the component code and a step-by-step guide for any Astro 6 project — is in Add a LetterGlitch effect to your Astro 6 site.
A floating header that earns its keep
The header is a capsule that floats above the content. At rest it’s barely there — a sliver of background showing through with a brand-tinted hairline border and no shadow. Scroll past the hero and it tightens: the backdrop blurs to 24px, the fill deepens to roughly 90% opacity, the border firms up, and a soft shadow lifts it off the page.
Keep scrolling down and the capsule slides up out of view; reverse direction and it returns — but on return, the colour scheme flips. In dark mode the capsule arrives white; in light mode it arrives near-black. The flip exists because a returning header has a job to do: it should read instantly against whatever section you’ve scrolled into, without forcing the rest of the page to compromise.
The right side carries a theme toggle and a single GitHub CTA. No colour-theme dropdown, no CTA button, no overflow menu — the header should reinforce the brand, not invite visitors to redesign it.
Custom cursor trail
A desktop-only effect with three coordinated parts: a small dot pinned to the pointer, a larger ring that lags behind it elastically, and a comet trail of brand-coloured particles that spawn behind movement and fade out over about 0.66 seconds. Hover anything interactive and the ring blooms to roughly 1.8× and gains a brand-tinted fill, telegraphing the affordance before you click.
It’s gated to fine-pointer devices and disables itself entirely under prefers-reduced-motion. Touch devices get the rest of the site without the overhead.
A favicon that follows the theme
The favicon isn’t a static .ico — it’s drawn at runtime from the active theme. The first letter of the site name renders into a small canvas, filled with the current --brand-500, and pushed to the browser as a data URL. A MutationObserver watches the <html> element for data-theme and dark/light class changes; the moment either flips, the favicon redraws and the tab updates without a reload.
It’s a small piece of polish, but it means the browser tab matches whatever palette is active — including the dark-mode invert — instead of advertising the wrong colour from a separate file that has to be kept in sync.
Reveal on scroll, without the jank
Sections fade and slide into place as they enter the viewport, but the system underneath is more careful than it looks. Two IntersectionObserver instances run in parallel: one with a 0.15 threshold for short elements that should reveal once they’re 15% visible, and one with a 0 threshold and a negative rootMargin for tall blocks that would never reach 15% on a small screen. The above-the-fold reveal is staggered separately — 250ms initial delay, 80ms between elements — so the hero lands as a sequence rather than a wall.
Everything respects prefers-reduced-motion: when that’s on, the reveals turn into instant state changes, the cursor trail switches off entirely, and the LetterGlitch animation pauses.
Composition details
Small things, the kind that compound:
- Zebra section rhythm. Sections alternate between
bg-backgroundandbg-background-secondary, with a single hairline border in between. No card-on-card-on-card pile-ups; just a steady visual breath between blocks. - Mobile-aware hero accent. The brand colour on the hero headline lives on “Hans Martens” at desktop sizes and shifts to “Web Designer” on narrow light-mode viewports — so the page never feels off-balance at small widths.
- Mobile CTA glass cards. On the home, about, and projects pages, the mobile CTA is a brand-tinted glass card on a flat zebra section — the quiet counterpart to the desktop LetterGlitch band, designed to read clearly on a phone without needing the canvas effect.
- Image shadow system. Every embedded screenshot, project image, and blog hero picks up a tuned shadow that gives it real elevation without looking heavy — calibrated separately per theme so it lands the same way on every brand colour.
- Animated Lighthouse scores. Four cards, four metrics, each number counting from 0 to 100 on scroll-into-view, staggered so they land sequentially.
- Scroll-progress back-to-top. A circular button in the corner traces a brand-coloured arc as you scroll the page, doubling as a progress indicator and a way back up. Hidden until you’ve scrolled enough to need it; respects reduced-motion.
- Three-state theme toggle. Light, dark, and system — persisted in localStorage, restored across view transitions, with a subtle icon shift between states.
- Bluesky share button on every blog post — alongside X, LinkedIn, and Copy. Same brand-fill style as the others.
What lands here first
Astro Rocket is published, listed on the official Astro themes directory, and stable. From here on, the new work — features, experiments, writing — lands on this site first. Some of it gets folded back into the starter; some of it stays here because it only makes sense in context. Either way, this is where the next thing shows up.