Menu
Public repository / master
AkurAI-Framework / site/frontend/spa.html
Latest change 3a1d4dc0df9425a910df82402300a2aeb40318ae - feat(frontend): client-side SPA router with View Transitions by Ólafur Búi Ólafsson
{% include "header" %}
<main class="container" id="spa-main">
<header class="page-head">
<span class="eyebrow">Live demo</span>
<h1>SPA router</h1>
<p class="lede">The links below navigate <em>without</em> a full page reload. The
router intercepts the click, fetches the next page, swaps this
<code><main></code>, and updates the URL — animated with the View
Transitions API. It's pure progressive enhancement: turn JS off and every
link is an ordinary navigation. <a href="/docs/spa-router">Read how it works →</a></p>
</header>
<section class="split">
<!-- What it does -->
<article class="card">
<header class="rt-head">
<h3>What just happened</h3>
<span id="spa-status" class="status">ready</span>
</header>
<p class="muted small">Click an in-site link. Watch the URL change and the
content cross-fade — but notice the page never blanks out or re-runs the
header. That's a client-side swap, not a reload.</p>
<p class="muted small">Navigations handled this session:
<strong id="spa-count">0</strong></p>
<ul class="rt-log" id="spa-log" aria-live="polite"></ul>
</article>
<!-- Try it -->
<article class="card">
<header class="rt-head">
<h3>Try it — instant navigation</h3>
</header>
<p class="muted small">These go through the SPA router on this page:</p>
<nav class="btn-row" id="spa-links">
<a class="btn btn-secondary" href="/">Home</a>
<a class="btn btn-secondary" href="/components">Components</a>
<a class="btn btn-secondary" href="/realtime">Realtime</a>
<a class="btn btn-secondary" href="/reactivity">Reactivity</a>
</nav>
<p class="muted small">And these deliberately <em>opt out</em> — full
navigation, as the rules dictate:</p>
<nav class="btn-row">
<a class="btn btn-secondary" href="https://github.com/olibuijr/AkurAI-Framework" rel="external">GitHub (external)</a>
<a class="btn btn-secondary" href="/docs" data-no-spa>Docs (data-no-spa)</a>
</nav>
</article>
</section>
<section class="container cta">
<h2>Smooth navigation, zero build step.</h2>
<p class="lede">One native ES module, modern browser APIs, no dependencies.</p>
<div class="btn-row center">
<a class="btn btn-secondary" href="/docs/spa-router">SPA router docs</a>
<a class="btn btn-secondary" href="https://github.com/olibuijr/AkurAI-Framework">Source on GitHub</a>
</div>
</section>
</main>
<!-- Opt in to the SPA router on this page only. -->
<script type="module" src="/spa.js"></script>
<script type="module">
// Tiny on-page indicator so the demo is visibly "live". Re-binds after each
// swap via the spa:navigated hook (the swapped <main> brings new elements).
const start = location.href;
let count = 0;
document.addEventListener("spa:navigated", (e) => {
count++;
const status = document.getElementById("spa-status");
const countEl = document.getElementById("spa-count");
const log = document.getElementById("spa-log");
if (status) {
status.textContent = e.detail.mode === "pop" ? "back/forward" : "swapped";
status.classList.add("ok");
}
if (countEl) countEl.textContent = String(count);
if (log) {
const li = document.createElement("li");
const path = new URL(e.detail.url).pathname;
li.textContent = `${e.detail.mode} → ${path}`;
log.prepend(li);
while (log.children.length > 8) log.lastChild.remove();
}
void start;
});
</script>
{% include "footer" %}