New hero: a WebGL shader, an agent console and Opus 5

Back in April I wrote about a 20-minute redesign with 21st.dev. The hero got a pixel Pong game back then — a ball smashing through the AIREPUBLIC lettering. I liked it. But after a four-month break I wanted the homepage to show how far AI has moved in that time: agents, terminals, live computation.
So I opened Claude Code with Opus 5 and asked for roughly this: "push the hero further technically, keep the pixel DNA, it has to work in light and dark mode, in Czech and English, and it can't kill performance."

Two ideas, one story
The hero now has two parts, and both tell the same story — this site is written by someone who actually works with AI agents:
- Signal field — the whole background is a grid of dots rendered by a single WebGL fragment shader. Each dot is one "pixel" of the old Pong. Noise flows across the grid, the big word airepublic is baked into a mask, and a scan wave sweeps over it. Your cursor leaves a ripple behind.
- Agent console — on the right is a replayed Claude Code session:
what's new since last time?→Read src/content/blog/en→ sort → the three latest articles. And those articles are real links, generated from the same MDX files as the blog.
The second point matters to me. A decorative terminal with made-up output would be kitsch. This one actually does something — it replaced the three "featured articles" cards that sat under the old hero and duplicated the Blog section further down the page anyway.
The shader: why a single fragment shader
The easy route would have been Three.js or some particle library. But thousands of DOM elements or hundreds of kilobytes of JavaScript for some dots in the background? No thanks.
The whole field is one triangle covering the screen plus a fragment shader that computes this for every pixel:
vec2 cell = floor(frag / uCell);
vec2 center = (cell + 0.5) * uCell;
float n = fbm(uv * 2.6 + vec2(t * 0.045, -t * 0.03)); // flowing noise
float mask = texture2D(uMask, uv).a; // wordmark
float scan = 0.5 + 0.5 * sin(uv.x * 7.0 - t * 1.1 + n * 5.0);
float ripple = uMouseActive * exp(-d / (uCell * 9.0)); // cursor
float intensity = 0.05 + flow * 0.32 + mask * (0.3 + 0.5 * scan) + ripple * 0.9;
float radius = uCell * (0.1 + 0.3 * intensity);
Intensity drives both the dot radius and the color. No particles, no physics — just math on the GPU. The wordmark is drawn once into a 2D canvas in Space Grotesk (the same font as the site's headings) and uploaded as a texture. When the window resizes, it gets redrawn.
A few things Opus 5 insisted on, and it was right:
- Pause when off-screen.
IntersectionObserverandvisibilitychangestoprequestAnimationFramewhen you can't see the hero. Your battery will thank you. prefers-reduced-motion— it renders a single static frame and that's it.- Device pixel ratio capped at 1.5. Otherwise on a retina display the shader would compute 4× more pixels for a difference you can't see.
- Fallback. If the browser has no WebGL, the original Pong kicks in. So the old hero didn't disappear completely — it just became the safety net.
- Dark mode without a reload. A
MutationObserverwatches thedarkclass on<html>and passes a uniform to the shader.
The console: zero JavaScript
This was the part I enjoyed most. The console looks animated — the command "types itself", lines appear one by one, the cursor blinks — but it's a pure server component. No useEffect, no state.
The typing is a CSS clip-path animation with steps():
@keyframes console-type {
from { clip-path: inset(0 100% 0 0); }
to { clip-path: inset(0 0 0 0); }
}
.console-typed { animation: console-type 1.1s steps(26, end) 0.5s both; }
The lines just have staggered animation-delay values. The upside: the article links are in the HTML from the very first byte, so Google sees them, and so do people with JavaScript turned off. And with prefers-reduced-motion everything shows up immediately.
That leaves the shader as the only client-side part of the whole hero.
What broke
Of course not everything worked on the first try:
- The wordmark covered the content. The first version stretched the word across the full height of the hero, and the dots crawled over the "Last updated" text. The fix: the hero reserves a
calc(18vw + 2rem)band at the bottom and the wordmark sits inside it. - Mobile overflowed. The heading had
whitespace-nowrap, which stretched the grid column past the edge of the screen.min-w-0, an explicitgrid-cols-1, and nowrap only from thesmbreakpoint up fixed it. - Light mode looked grey. The "hot" dot color was too dark and looked dirty on a white background. Opus switched it to teal-700 and lowered the maximum opacity.
I checked every iteration through Chrome DevTools MCP — Claude opened the page itself, took a screenshot, looked at it and fixed things. That "write → look → fix" loop is exactly what makes Claude Code more than autocomplete.

What's new on the site
While I was at it, I also added a few articles about what I missed over the summer:
- Claude Code in summer 2026: what I missed, what's worth it — workflows, Opus 5, auto mode, 2× limits
- Claude Fable 5 and 5.1: Mythos-class and when it's worth it — what Mythos-class means and when it pays off
- FLORA AI: tested through Claude — can AI write Czech? — and whether AI can put Czech text into images
- OpenAI in summer 2026: GPT-5.6, GPT-6 Astra, Sora's end — GPT-6 Astra, the end of Sora and what happened elsewhere
Plus a new FLORA review and updated reviews of Claude and ChatGPT.
Go to the homepage and move your mouse across the dots. And if you have animations turned off, you'll be greeted by one nice static frame.