<html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/><title>Free The Web — A Hypermedia Manifesto</title><meta name="description" content="The web was built on hypermedia. We broke it with SPAs. It's time to fix it. A manifesto for returning to what works."/><meta property="og:title" content="Free The Web — A Hypermedia Manifesto"/><meta property="og:description" content="The web was built on hypermedia. We broke it with SPAs. It's time to fix it."/><meta property="og:type" content="website"/><meta name="twitter:card" content="summary_large_image"/><meta name="theme-color" content="#08080c"/><link rel="icon" type="image/svg+xml" href="/favicon.svg"/><link rel="stylesheet" href="/styles.css"/><script src="/htmx.min.js" defer=""></script></head><body><main><section class="min-h-screen flex flex-col items-center justify-center px-6 relative"><div class="absolute inset-0 pointer-events-none" aria-hidden="true" style="background:radial-gradient(ellipse 80% 50% at 50% 20%, rgba(224,255,0,0.03) 0%, transparent 70%)"></div><div class="relative z-10 text-center"><div class="mb-10 md:mb-14"><div class="hero-reveal d0"><span class="hero-type text-[#e8e6e3]">FREE</span></div><div class="hero-reveal d1 my-1 md:my-2"><span class="font-mono text-muted text-xs md:text-base tracking-[0.4em] uppercase">the</span></div><div class="hero-reveal d2"><span class="hero-type text-accent glow">WEB.</span></div></div><div class="hero-reveal d3 max-w-2xl mx-auto"><p class="text-base md:text-2xl font-light leading-relaxed text-dim">The web was built on <span class="text-[#e8e6e3] font-normal">hypermedia</span>. We broke it with <span class="text-danger line-through">SPAs</span>.<br class="hidden md:block"/> It's time to <span class="text-accent font-normal blink">fix it</span></p></div><div class="hero-reveal d5 mt-24 md:mt-32 flex flex-col items-center gap-3"><span class="font-mono text-muted text-[10px] tracking-[0.3em] uppercase">Scroll</span><div class="scroll-line"></div></div></div></section><section id="problem" class="px-6 py-24 md:py-32"><div class="sep mb-24 md:mb-32"></div><div class="max-w-3xl mx-auto"><div class="sr"><span class="font-mono text-accent text-xs tracking-wider">$ cat the-problem.txt</span><h2 class="font-serif italic text-4xl md:text-6xl mt-4 mb-4">What SPAs Broke</h2><p class="text-dim text-lg md:text-xl mb-16 md:mb-20">SPAs turned the browser from a <span class="text-accent">hypermedia client</span> into a <span class="text-danger">JavaScript runtime</span>. The result:</p></div><div class="stagger space-y-10 md:space-y-12"><div class="sr flex gap-5 md:gap-7"><span class="font-mono text-accent text-base font-bold mt-0.5 shrink-0 tabular-nums">01</span><div><h3 class="font-mono text-sm tracking-wider mb-1.5">DEVELOPER SUFFERING</h3><p class="text-dim leading-relaxed">Build tools, bundlers, hydration, RSC, islands... a tower of complexity to solve problems we created. You shouldn't need a PhD in webpack to ship a form.</p></div></div><div class="sr flex gap-5 md:gap-7"><span class="font-mono text-accent text-base font-bold mt-0.5 shrink-0 tabular-nums">02</span><div><h3 class="font-mono text-sm tracking-wider mb-1.5">CORPORATE CAPTURE</h3><p class="text-dim leading-relaxed">React is Meta's. Next.js is Vercel's. Your "open" stack locks you into their cloud, their tooling, their pricing page. The web was meant to be <em>yours</em>.</p></div></div><div class="sr flex gap-5 md:gap-7"><span class="font-mono text-accent text-base font-bold mt-0.5 shrink-0 tabular-nums">03</span><div><h3 class="font-mono text-sm tracking-wider mb-1.5">INACCESSIBLE</h3><p class="text-dim leading-relaxed">Screen readers choke on virtual DOMs. Buttons that aren't buttons. Forms that don't submit. We broke the web for everyone.</p></div></div><div class="sr flex gap-5 md:gap-7"><span class="font-mono text-accent text-base font-bold mt-0.5 shrink-0 tabular-nums">04</span><div><h3 class="font-mono text-sm tracking-wider mb-1.5">BLOATED BUNDLES</h3><p class="text-dim leading-relaxed">2MB of JavaScript to render a list. Your users are on mobile. They notice.</p></div></div><div class="sr flex gap-5 md:gap-7"><span class="font-mono text-accent text-base font-bold mt-0.5 shrink-0 tabular-nums">05</span><div><h3 class="font-mono text-sm tracking-wider mb-1.5">BROKEN URLs</h3><p class="text-dim leading-relaxed">Routes that don't work without JavaScript. Try sharing a link. Go ahead.</p></div></div><div class="sr flex gap-5 md:gap-7"><span class="font-mono text-accent text-base font-bold mt-0.5 shrink-0 tabular-nums">06</span><div><h3 class="font-mono text-sm tracking-wider mb-1.5">FRAGILE STATE</h3><p class="text-dim leading-relaxed">Client-side state management is an entire career. Redux, MobX, Zustand, Jotai — we invented solutions for problems we created.</p></div></div></div><div class="sr mt-16 md:mt-24 pl-6 border-l border-accent/30"><p class="font-serif italic text-xl md:text-2xl text-dim leading-relaxed">"We took the most successful hypermedia system in history and replaced it with distributed state machines running in hostile environments."</p></div></div></section><section id="manifesto" class="px-6 py-24 md:py-32"><div class="sep mb-24 md:mb-32"></div><div class="max-w-3xl mx-auto"><div class="sr"><span class="font-mono text-accent text-xs tracking-wider">$ cat manifesto.md</span><h2 class="font-serif italic text-4xl md:text-6xl mt-4 mb-3">The Manifesto</h2><p class="text-dim italic mb-20 md:mb-24">Like the Agile Manifesto — we value the items on the left more than the items on the right.</p></div><div class="sr relative py-12 md:py-16"><span class="ghost">01</span><div class="relative z-10"><div class="flex items-baseline gap-3 md:gap-5 flex-wrap mb-5"><span class="font-serif italic text-accent text-3xl md:text-5xl">HTML</span><span class="font-mono text-muted text-[10px] tracking-[0.25em] uppercase">over</span><span class="font-mono text-dim text-2xl md:text-4xl line-through decoration-muted">JSON</span></div><p class="text-dim leading-relaxed max-w-xl">The server should return documents, not data blobs for the client to assemble. HTML is the universal format — every browser, every screen reader, every LLM understands it.</p></div></div><div class="sep"></div><div class="sr relative py-12 md:py-16"><span class="ghost">02</span><div class="relative z-10"><div class="flex items-baseline gap-3 md:gap-5 flex-wrap mb-5"><span class="font-serif italic text-accent text-3xl md:text-5xl">Server Authority</span><span class="font-mono text-muted text-[10px] tracking-[0.25em] uppercase">over</span><span class="font-mono text-dim text-2xl md:text-4xl line-through decoration-muted">Client State</span></div><p class="text-dim leading-relaxed max-w-xl">The server knows the state. The server <em>is</em> the state. Stop duplicating it in Redux, syncing it, debugging drift, and pretending client-side state is simpler.</p></div></div><div class="sep"></div><div class="sr relative py-12 md:py-16"><span class="ghost">03</span><div class="relative z-10"><div class="flex items-baseline gap-3 md:gap-5 flex-wrap mb-5"><span class="font-serif italic text-accent text-3xl md:text-5xl">Progressive Enhancement</span><span class="font-mono text-muted text-[10px] tracking-[0.25em] uppercase">over</span><span class="font-mono text-dim text-2xl md:text-4xl line-through decoration-muted">JS Dependency</span></div><p class="text-dim leading-relaxed max-w-xl">The web works without JavaScript. Forms submit. Links navigate. Your app should respect that. JavaScript is for enhancement, not existence.</p></div></div><div class="sep"></div><div class="sr relative py-12 md:py-16"><span class="ghost">04</span><div class="relative z-10"><div class="flex items-baseline gap-3 md:gap-5 flex-wrap mb-5"><span class="font-serif italic text-accent text-3xl md:text-5xl">Real URLs</span><span class="font-mono text-muted text-[10px] tracking-[0.25em] uppercase">over</span><span class="font-mono text-dim text-2xl md:text-4xl line-through decoration-muted">Client Routes</span></div><p class="text-dim leading-relaxed max-w-xl">Every view should have a URL that works when shared, bookmarked, or crawled. If your route only exists in JavaScript, it doesn't exist.</p></div></div><div class="sep"></div><div class="sr relative py-12 md:py-16"><span class="ghost">05</span><div class="relative z-10"><div class="flex items-baseline gap-3 md:gap-5 flex-wrap mb-5"><span class="font-serif italic text-accent text-3xl md:text-5xl">Hypermedia Client</span><span class="font-mono text-muted text-[10px] tracking-[0.25em] uppercase">over</span><span class="font-mono text-dim text-2xl md:text-4xl line-through decoration-muted">App Runtime</span></div><p class="text-dim leading-relaxed max-w-xl">The browser already knows how to follow links, submit forms, and render HTML. Let it. We don't need to rebuild the browser in JavaScript.</p></div></div><div class="sr mt-16 md:mt-20 text-center"><a href="#sign" class="inline-block px-10 py-4 bg-accent text-ink font-mono font-bold text-sm tracking-wider uppercase hover:bg-[#e8e6e3] transition-colors duration-200">Sign the Manifesto</a></div></div></section><section class="px-6 py-24 md:py-32"><div class="sep mb-24 md:mb-32"></div><div class="max-w-3xl mx-auto"><div class="sr text-center mb-16"><span class="font-mono text-accent text-xs tracking-wider">$ diff spa.arch hypermedia.arch</span><h2 class="font-serif italic text-4xl md:text-6xl mt-4 mb-3">How Hypermedia Works</h2><p class="text-dim text-lg md:text-xl max-w-xl mx-auto">The server sends HTML with built-in controls — links and forms. The browser follows them. That's the whole architecture.</p></div><div class="sr mb-20"><svg viewBox="0 0 460 240" class="w-full max-w-xl mx-auto" xmlns="http://www.w3.org/2000/svg"><defs><marker id="a-accent" markerWidth="7" markerHeight="7" refX="7" refY="3.5" orient="auto"><path d="M0,0.5 L6,3.5 L0,6.5" fill="none" stroke="#e0ff00" stroke-width="1.2"></path></marker><marker id="a-dim" markerWidth="7" markerHeight="7" refX="7" refY="3.5" orient="auto"><path d="M0,0.5 L6,3.5 L0,6.5" fill="none" stroke="#888898" stroke-width="1.2"></path></marker></defs><rect x="20" y="85" width="160" height="70" rx="4" fill="#111118" stroke="#252530" stroke-width="1.5"></rect><text x="100" y="115" style="text-anchor:middle;fill:#e8e6e3;font-size:13px;font-family:monospace;letter-spacing:0.05em">BROWSER</text><text x="100" y="135" style="text-anchor:middle;fill:#76768a;font-size:9px;font-family:sans-serif">displays HTML, follows links</text><rect x="280" y="85" width="160" height="70" rx="4" fill="#111118" stroke="#e0ff00" stroke-width="1" stroke-opacity="0.25"></rect><text x="360" y="115" style="text-anchor:middle;fill:#e0ff00;font-size:13px;font-family:monospace;letter-spacing:0.05em">SERVER</text><text x="360" y="135" style="text-anchor:middle;fill:#76768a;font-size:9px;font-family:sans-serif">renders HTML, owns state</text><path d="M280,90 C230,30 230,30 180,90" stroke="#e0ff00" stroke-width="1.5" fill="none" marker-end="url(#a-accent)"></path><rect x="170" y="35" width="120" height="22" rx="3" fill="#08080c"></rect><text x="230" y="50" style="text-anchor:middle;fill:#e0ff00;font-size:10px;font-family:monospace">HTML + Controls</text><path d="M180,150 C230,210 230,210 280,150" stroke="#888898" stroke-width="1.5" fill="none" marker-end="url(#a-dim)"></path><rect x="170" y="183" width="120" height="22" rx="3" fill="#08080c"></rect><text x="230" y="198" style="text-anchor:middle;fill:#888898;font-size:10px;font-family:monospace">HTTP Request</text><text x="100" y="178" style="text-anchor:middle;fill:#76768a;font-size:8px;font-family:monospace">↑ user clicks link</text><text x="100" y="190" style="text-anchor:middle;fill:#76768a;font-size:8px;font-family:monospace">or submits form</text><text x="360" y="178" style="text-anchor:middle;fill:#76768a;font-size:8px;font-family:monospace">returns only the</text><text x="360" y="190" style="text-anchor:middle;fill:#76768a;font-size:8px;font-family:monospace">fragment that changed</text></svg><div class="flex justify-center gap-8 md:gap-16 mt-8 font-mono text-[10px] tracking-wider uppercase"><span class="text-muted">No virtual DOM</span><span class="text-muted">No client state</span><span class="text-muted">No JSON parsing</span></div></div><div class="sr grid md:grid-cols-2 gap-6 mb-20"><div class="p-6 border border-danger/20 bg-surface/50"><h3 class="font-mono text-danger text-[11px] tracking-[0.15em] uppercase mb-5">SPA: 8 steps to render</h3><ol class="space-y-2 font-mono text-[11px] text-dim"><li class="flex gap-2"><span class="text-danger/60">1.</span> Download JS bundle (2MB)</li><li class="flex gap-2"><span class="text-danger/60">2.</span> Parse & execute JavaScript</li><li class="flex gap-2"><span class="text-danger/60">3.</span> Initialize framework runtime</li><li class="flex gap-2"><span class="text-danger/60">4.</span> Mount component tree</li><li class="flex gap-2"><span class="text-danger/60">5.</span> Fetch data from API (JSON)</li><li class="flex gap-2"><span class="text-danger/60">6.</span> Parse JSON, update state</li><li class="flex gap-2"><span class="text-danger/60">7.</span> Reconcile virtual DOM</li><li class="flex gap-2"><span class="text-danger/60">8.</span> Patch real DOM</li></ol></div><div class="p-6 border border-accent/20 bg-surface/50"><h3 class="font-mono text-accent text-[11px] tracking-[0.15em] uppercase mb-5">Hypermedia: 2 steps</h3><ol class="space-y-2 font-mono text-[11px] text-[#e8e6e3]"><li class="flex gap-2"><span class="text-accent/60">1.</span> Browser requests URL</li><li class="flex gap-2"><span class="text-accent/60">2.</span> Server returns HTML. Done.</li></ol><div class="mt-8 pt-5 border-t border-rule/30"><p class="text-muted text-[11px] font-mono">For updates, HTMX sends a request and swaps the HTML fragment the server returns. Same two steps.</p></div></div></div><div class="sr"><h3 class="font-mono text-accent text-xs tracking-[0.2em] uppercase mb-8 text-center">Try it live</h3><div class="border border-rule bg-surface overflow-hidden"><div class="border-b border-rule bg-ink px-5 py-3"><span class="font-mono text-muted text-[11px] tracking-wider">demo.html</span></div><div class="p-5 md:p-6 border-b border-rule/50 bg-ink/50"><pre class="font-mono text-[12px] md:text-[13px] leading-relaxed overflow-x-auto"><code><<span class="text-[#e8e6e3]">button</span> <span class="text-accent">hx-get</span>=<span class="text-dim">"/api/demo"</span> <span class="text-accent">hx-target</span>=<span class="text-dim">"#result"</span> <span class="text-accent">hx-swap</span>=<span class="text-dim">"innerHTML"</span>> Fetch from server </button> <<span class="text-[#e8e6e3]">div</span> id=<span class="text-dim">"result"</span>></div></code></pre></div><div class="p-5 md:p-6 space-y-4"><div class="flex items-center gap-4"><button hx-get="/api/demo" hx-target="#demo-result" hx-swap="innerHTML" class="px-5 py-2.5 bg-accent text-ink font-mono font-bold text-xs tracking-wider uppercase cursor-pointer hover:bg-[#e8e6e3] transition-colors">Fetch from server</button><span class="font-mono text-muted text-[10px] tracking-wider">click to send hx-get</span></div><div id="demo-result" class="min-h-[48px] border border-rule/30 bg-ink p-4 font-mono text-muted text-xs">Server response will appear here...</div></div></div><p class="font-mono text-muted text-[10px] tracking-wider mt-4 text-center">Three HTML attributes. Zero JavaScript. That's HTMX.</p></div></div></section><section id="sign" class="px-6 py-24 md:py-32"><div class="sep mb-24 md:mb-32"></div><div class="max-w-2xl mx-auto"><div class="sr text-center mb-16 md:mb-20"><span class="font-mono text-accent text-xs tracking-wider">$ vim signature.txt</span><h2 class="font-serif italic text-4xl md:text-6xl mt-4 mb-3">Sign the Manifesto</h2><p class="text-dim text-lg">Join the movement. Pledge to build hypermedia-first.</p></div><div class="sr text-center mb-16" hx-get="/api/count" hx-trigger="every 30s" hx-swap="innerHTML"><div class="font-mono text-accent text-6xl md:text-8xl font-bold glow">57</div><div class="font-mono text-muted text-[10px] tracking-[0.25em] uppercase mt-4">developers have signed</div></div><div id="sign-form-container" class="sr"><form hx-post="/api/sign" hx-target="#sign-form-container" hx-swap="innerHTML" class="space-y-8"><div><label for="name" class="block font-mono text-accent text-[11px] tracking-[0.2em] uppercase mb-3">Name *</label><input type="text" id="name" name="name" required="" class="w-full px-0 py-3 bg-transparent border-0 border-b border-rule text-lg transition-colors placeholder:text-muted/40" placeholder="Your name"/></div><div><label for="title" class="block font-mono text-accent text-[11px] tracking-[0.2em] uppercase mb-3">Title / Role <span class="text-muted">(optional)</span></label><input type="text" id="title" name="title" class="w-full px-0 py-3 bg-transparent border-0 border-b border-rule text-lg transition-colors placeholder:text-muted/40" placeholder="e.g., Senior Developer, CTO"/></div><div class="flex items-start gap-3 pt-2"><input type="checkbox" id="pledge" name="pledge" required="" class="mt-1.5 w-4 h-4"/><label for="pledge" class="text-dim leading-relaxed">I pledge to <strong class="text-[#e8e6e3]">build my next project with hypermedia</strong>. I will serve HTML, respect URLs, and let the browser be a hypermedia client.</label></div><button type="submit" class="w-full py-4 bg-accent text-ink font-mono font-bold text-sm tracking-wider uppercase hover:bg-[#e8e6e3] transition-colors duration-200">Sign the Manifesto</button><p class="font-mono text-muted text-[10px] tracking-wider text-center uppercase">This form uses HTMX. Zero frameworks. Zero client routing.</p></form></div></div></section><section id="ai" class="px-6 py-24 md:py-32"><div class="sep mb-24 md:mb-32"></div><div class="max-w-3xl mx-auto"><div class="sr"><span class="font-mono text-accent text-xs tracking-wider">$ echo "one more thing"</span><h2 class="font-serif italic text-4xl md:text-6xl mt-4 mb-3">Oh, and It's AI-Ready</h2><p class="text-dim text-lg md:text-xl mb-16 md:mb-20">There's a bonus to building the web properly.</p></div><div class="sr space-y-6 text-dim text-lg leading-relaxed mb-12"><p>LLMs and AI agents interact with the web through <strong class="text-accent font-semibold">HTTP and HTML</strong> — they make GET requests, parse markup, follow links, submit forms. That's hypermedia. It's what we've been talking about this whole time.</p><p>SPAs require a <strong class="text-danger font-semibold">JavaScript runtime</strong> to render content. AI agents don't have one. Your client-rendered app is invisible to them.</p><p>This isn't the <em>reason</em> to build with hypermedia — developer freedom, accessibility, and simplicity are. But it means every hypermedia app you build is automatically an app that AI agents can read, navigate, and use. No special APIs required.</p></div><div class="sr bg-surface rounded-lg border border-rule overflow-hidden mb-12"><div class="flex items-center gap-2 px-4 py-3 border-b border-rule bg-elevated/40"><span class="w-2.5 h-2.5 rounded-full bg-danger/40"></span><span class="w-2.5 h-2.5 rounded-full bg-accent/20"></span><span class="w-2.5 h-2.5 rounded-full bg-accent/40"></span><span class="ml-3 font-mono text-[11px] text-muted">bonus.sh</span></div><div class="p-6 md:p-8 font-mono text-sm md:text-base leading-loose"><p class="text-muted">$ curl https://your-hypermedia-app.com</p><div class="mt-4 space-y-1"><p>> AI agents can read this HTML.</p><p>> They can follow the links.</p><p>> They can submit the forms.</p><p class="text-accent mt-3">> You didn't have to do anything extra.</p></div></div></div><div class="sr"><p class="text-dim text-lg leading-relaxed">Build for humans, for accessibility, for simplicity, for <em>yourself</em>. The AI compatibility comes free.</p></div></div></section><section id="proof" class="px-6 py-24 md:py-32 pb-16"><div class="sep mb-24 md:mb-32"></div><div class="max-w-3xl mx-auto"><div class="sr text-center mb-12"><h2 class="font-serif italic text-3xl md:text-5xl mb-4">The Proof</h2><p class="text-dim text-lg md:text-xl">This entire site is built with <span class="text-accent font-semibold">Hono + HTMX</span>.</p></div><div class="sr grid grid-cols-2 md:grid-cols-4 gap-8 md:gap-12 py-12 border-y border-rule mb-12"><div class="text-center"><div class="font-mono text-accent text-4xl md:text-5xl font-bold glow">0</div><div class="font-mono text-muted text-[10px] tracking-[0.15em] uppercase mt-3">JS Frameworks</div></div><div class="text-center"><div class="font-mono text-accent text-4xl md:text-5xl font-bold glow">0</div><div class="font-mono text-muted text-[10px] tracking-[0.15em] uppercase mt-3">Client Routing</div></div><div class="text-center"><div class="font-mono text-accent text-4xl md:text-5xl font-bold glow">0</div><div class="font-mono text-muted text-[10px] tracking-[0.15em] uppercase mt-3">Hydration</div></div><div class="text-center"><div class="font-mono text-accent text-4xl md:text-5xl font-bold glow">0</div><div class="font-mono text-muted text-[10px] tracking-[0.15em] uppercase mt-3">Build Steps</div></div></div><div class="sr text-center mb-16"><p class="font-serif italic text-2xl md:text-4xl">The HTML <span class="text-accent">IS</span> the application.</p></div><div class="sr flex flex-wrap justify-center gap-4 mb-20"><a href="/view-source" target="_blank" class="px-6 py-3 border border-rule font-mono text-sm text-dim hover:text-accent hover:border-accent/40 transition-colors duration-200">View Source</a><a href="https://github.com/stukennedy/freetheweb" target="_blank" rel="noopener noreferrer" class="px-6 py-3 border border-rule font-mono text-sm text-dim hover:text-accent hover:border-accent/40 transition-colors duration-200">GitHub</a></div><div class="sep mb-14"></div><div class="sr"><h3 class="font-mono text-accent text-xs tracking-[0.2em] uppercase mb-8 text-center">Learn More</h3><div class="grid md:grid-cols-2 gap-3"><a href="https://htmx.org" target="_blank" rel="noopener noreferrer" class="group p-5 bg-surface border border-rule hover:border-accent/30 transition-colors duration-200"><h4 class="font-mono text-sm text-accent group-hover:text-[#e8e6e3] transition-colors mb-1">HTMX</h4><p class="text-muted text-sm">High power tools for HTML.</p></a><a href="https://hono.dev" target="_blank" rel="noopener noreferrer" class="group p-5 bg-surface border border-rule hover:border-accent/30 transition-colors duration-200"><h4 class="font-mono text-sm text-accent group-hover:text-[#e8e6e3] transition-colors mb-1">Hono</h4><p class="text-muted text-sm">Fast, lightweight web framework for the edge.</p></a><a href="https://hypermedia.systems" target="_blank" rel="noopener noreferrer" class="group p-5 bg-surface border border-rule hover:border-accent/30 transition-colors duration-200"><h4 class="font-mono text-sm text-accent group-hover:text-[#e8e6e3] transition-colors mb-1">Hypermedia Systems</h4><p class="text-muted text-sm">The book on hypermedia-driven applications.</p></a><a href="https://htmx.org/essays/" target="_blank" rel="noopener noreferrer" class="group p-5 bg-surface border border-rule hover:border-accent/30 transition-colors duration-200"><h4 class="font-mono text-sm text-accent group-hover:text-[#e8e6e3] transition-colors mb-1">HTMX Essays</h4><p class="text-muted text-sm">Deep dives on hypermedia and REST.</p></a></div></div><div class="sep mt-20 mb-8"></div><footer class="text-center space-y-4 pb-8"><p class="text-muted text-sm">Built by <a href="https://stukennedy.com" target="_blank" rel="noopener noreferrer" class="text-accent uline hover:text-[#e8e6e3] transition-colors">Stu Kennedy</a></p><div class="flex justify-center gap-6"><a href="https://stukennedy.com" target="_blank" rel="noopener noreferrer" class="font-mono text-[11px] text-muted hover:text-accent transition-colors tracking-wider uppercase">Website</a><a href="https://twitter.com/stukennedydev" target="_blank" rel="noopener noreferrer" class="font-mono text-[11px] text-muted hover:text-accent transition-colors tracking-wider uppercase">Twitter</a><a href="https://github.com/stukennedy" target="_blank" rel="noopener noreferrer" class="font-mono text-[11px] text-muted hover:text-accent transition-colors tracking-wider uppercase">GitHub</a><a href="https://linkedin.com/in/stu-kennedy" target="_blank" rel="noopener noreferrer" class="font-mono text-[11px] text-muted hover:text-accent transition-colors tracking-wider uppercase">LinkedIn</a></div><p class="font-mono text-muted text-[9px] tracking-[0.15em] pt-6">CLOUDFLARE WORKERS + HONO + HTMX + ZERO JS FRAMEWORKS</p></footer></div></section></main><div style="position:fixed;inset:0;z-index:9999;pointer-events:none;opacity:0.03" aria-hidden="true"><svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg"><filter id="grain"><feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"></feTurbulence></filter><rect width="100%" height="100%" filter="url(#grain)"></rect></svg></div><script> (function(){ var io=new IntersectionObserver(function(e){e.forEach(function(x){if(x.isIntersecting){x.target.classList.add('in-view');io.unobserve(x.target)}})},{threshold:.08,rootMargin:'0px 0px -40px 0px'}); var so=new IntersectionObserver(function(e){e.forEach(function(x){if(x.isIntersecting){x.target.querySelectorAll('.sr').forEach(function(c){c.classList.add('in-view')});so.unobserve(x.target)}})},{threshold:.05,rootMargin:'0px 0px -30px 0px'}); document.querySelectorAll('.sr:not(.stagger .sr)').forEach(function(el){io.observe(el)}); document.querySelectorAll('.stagger').forEach(function(el){so.observe(el)}); })(); </script></body></html>