*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
:root { --bg: #070a12; --text: #f0f4fb; --muted: #8a95a8; --accent: #86e8ff; --border: #263249; --neon-purple: #ae52ff; }
body { background: var(--bg); color: var(--text); font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; min-height: 100vh; overflow-x: hidden; }
/* Nav */
.nav {
position: fixed; top: 0; left: 0; right: 0; z-index: 50;
display: flex; justify-content: center; gap: 0.25rem;
padding: 1rem 2rem;
background: rgba(7,10,18,0.85); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid rgba(134,232,255,0.08);
}
.nav-link {
padding: 0.5rem 1.2rem; border-radius: 8px;
color: var(--muted); text-decoration: none;
font: 600 0.82rem/1 'Inter', system-ui, sans-serif;
transition: color 0.2s, background 0.2s;
}
.nav-link:hover { color: var(--accent); }
.nav-link.active { color: var(--accent); background: rgba(134,232,255,0.1); }
/* Pages */
.page { display: none; min-height: 100vh; padding: 7rem 2rem 4rem; }
.page.active { display: flex; align-items: center; justify-content: center; }
.page-content { max-width: 640px; width: 100%; }
.eyebrow { display: inline-block; font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.18em; color: var(--accent); margin-bottom: 1rem; }
h1 { font-size: clamp(2.2rem, 5vw, 3.5rem); font-weight: 700; letter-spacing: -0.03em; line-height: 1.1; margin-bottom: 1.2rem; }
.page-body { font-size: 1rem; line-height: 1.7; margin-bottom: 1rem; }
.page-body.muted { color: var(--muted); }
code { background: rgba(134,232,255,0.1); color: var(--accent); padding: 0.1rem 0.4rem; border-radius: 4px; font-size: 0.85rem; }
/* Features */
.feature-list { margin-top: 1.5rem; display: flex; flex-direction: column; gap: 0.6rem; }
.feature {
display: flex; align-items: center; gap: 1rem;
padding: 0.8rem 1rem; border-radius: 10px;
background: rgba(134,232,255,0.04); border: 1px solid rgba(134,232,255,0.08);
font-size: 0.9rem;
}
.f-icon { font-size: 0.7rem; font-weight: 700; color: var(--accent); opacity: 0.5; }
/* Work grid */
.work-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-top: 1.5rem; }
.work-item {
aspect-ratio: 1.4; border-radius: 14px;
background: linear-gradient(135deg, hsl(var(--hue,200) 40% 12%), hsl(var(--hue,200) 50% 18%));
border: 1px solid hsl(var(--hue,200) 30% 25%);
display: flex; align-items: center; justify-content: center;
font: 600 0.9rem/1 'Inter', system-ui, sans-serif;
color: hsl(var(--hue,200) 60% 75%);
}
.btn-gallery {
display: inline-block; margin-top: 1.5rem; padding: 0.7rem 2rem; border-radius: 999px;
border: 1px solid rgba(134,232,255,0.3); color: var(--accent); text-decoration: none;
font: 600 0.85rem/1 'Inter', system-ui, sans-serif; transition: all 0.25s;
}
.btn-gallery:hover { background: rgba(134,232,255,0.08); border-color: var(--accent); }
/* View Transition animations */
::view-transition-old(page-title),
::view-transition-new(page-title) {
animation-duration: 0.35s;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
::view-transition-old(root) { animation: slide-out-left 0.3s ease-in forwards; }
::view-transition-new(root) { animation: slide-in-right 0.3s ease-out forwards; }
/* Reverse direction */
.nav-back ::view-transition-old(root) { animation-name: slide-out-right; }
.nav-back ::view-transition-new(root) { animation-name: slide-in-left; }
@keyframes slide-out-left { to { transform: translateX(-30px); opacity: 0; } }
@keyframes slide-in-right { from { transform: translateX(30px); opacity: 0; } }
@keyframes slide-out-right { to { transform: translateX(30px); opacity: 0; } }
@keyframes slide-in-left { from { transform: translateX(-30px); opacity: 0; } }
@media (max-width: 640px) {
.nav { gap: 0; }
.nav-link { padding: 0.5rem 0.8rem; font-size: 0.75rem; }
.work-grid { grid-template-columns: 1fr; }
}
if (!window.MotionPreference) {
const __mql = window.matchMedia("(prefers-reduced-motion: reduce)");
const __listeners = new Set();
const MotionPreference = {
prefersReducedMotion() {
return __mql.matches;
},
setOverride(value) {
const reduced = Boolean(value);
document.documentElement.classList.toggle("reduced-motion", reduced);
window.dispatchEvent(new CustomEvent("motion-preference", { detail: { reduced } }));
for (const listener of __listeners) {
try {
listener({ reduced, override: reduced, systemReduced: __mql.matches });
} catch {}
}
},
onChange(listener) {
__listeners.add(listener);
try {
listener({
reduced: __mql.matches,
override: null,
systemReduced: __mql.matches,
});
} catch {}
return () => __listeners.delete(listener);
},
getState() {
return { reduced: __mql.matches, override: null, systemReduced: __mql.matches };
},
};
window.MotionPreference = MotionPreference;
}
function prefersReducedMotion() {
return window.MotionPreference.prefersReducedMotion();
}
function initDemoShell() {
// No-op shim in imported standalone snippets.
}
initDemoShell({ title: 'Page Routing Transitions', category: 'transitions', tech: ['view-transitions-api', 'spa-routing'] });
const supportsVT = typeof document.startViewTransition === 'function';
const pages = ['home', 'about', 'work', 'contact'];
let currentPage = 'home';
let currentIndex = 0;
const navLinks = document.querySelectorAll('.nav-link');
function navigateTo(pageName) {
if (pageName === currentPage) return;
const newIndex = pages.indexOf(pageName);
const goingForward = newIndex > currentIndex;
const updateDOM = () => {
// Hide current page
document.getElementById(`page-${currentPage}`).classList.remove('active');
// Show new page
document.getElementById(`page-${pageName}`).classList.add('active');
// Update nav
navLinks.forEach((link) => {
link.classList.toggle('active', link.dataset.page === pageName);
});
currentPage = pageName;
currentIndex = newIndex;
};
if (supportsVT && !prefersReducedMotion()) {
// Set direction class for CSS animation direction
document.documentElement.classList.toggle('nav-back', !goingForward);
document.startViewTransition(updateDOM);
} else {
updateDOM();
}
}
// Nav click handlers
navLinks.forEach((link) => {
link.addEventListener('click', (e) => {
e.preventDefault();
navigateTo(link.dataset.page);
});
});
// Handle hash-based navigation
function handleHash() {
const hash = window.location.hash.replace('#', '') || 'home';
if (pages.includes(hash)) {
navigateTo(hash);
}
}
window.addEventListener('hashchange', handleHash);
// Keyboard navigation
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight') {
const next = pages[Math.min(currentIndex + 1, pages.length - 1)];
navigateTo(next);
window.location.hash = next;
} else if (e.key === 'ArrowLeft') {
const prev = pages[Math.max(currentIndex - 1, 0)];
navigateTo(prev);
window.location.hash = prev;
}
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Page Routing Transitions — stealthisdesign</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Shared nav (persists across transitions) -->
<nav class="nav" style="view-transition-name: main-nav;">
<a href="#home" class="nav-link active" data-page="home">Home</a>
<a href="#about" class="nav-link" data-page="about">About</a>
<a href="#work" class="nav-link" data-page="work">Work</a>
<a href="#contact" class="nav-link" data-page="contact">Contact</a>
</nav>
<!-- Page container -->
<main id="page-container">
<!-- Home -->
<div class="page active" id="page-home">
<div class="page-content">
<span class="eyebrow">Demo 12</span>
<h1 style="view-transition-name: page-title;">Page Routing<br>Transitions</h1>
<p class="page-body">Click the navigation links above to see smooth View Transitions between pages. The nav bar and page title animate as shared elements.</p>
<p class="page-body muted">Each page slides in while the previous slides out. The nav highlight transitions smoothly between active items.</p>
</div>
</div>
<!-- About -->
<div class="page" id="page-about">
<div class="page-content">
<h1 style="view-transition-name: page-title;">About</h1>
<p class="page-body">This demo simulates multi-page routing using SPA-style DOM swaps wrapped in <code>document.startViewTransition()</code>.</p>
<p class="page-body muted">In a real application, you could use the cross-document View Transitions API with <code>@view-transition { navigation: auto; }</code> for actual multi-page apps.</p>
<div class="feature-list">
<div class="feature"><span class="f-icon">01</span><span>Shared element navigation bar</span></div>
<div class="feature"><span class="f-icon">02</span><span>Page title morphing</span></div>
<div class="feature"><span class="f-icon">03</span><span>Slide direction based on nav position</span></div>
<div class="feature"><span class="f-icon">04</span><span>Fallback for unsupported browsers</span></div>
</div>
</div>
</div>
<!-- Work -->
<div class="page" id="page-work">
<div class="page-content">
<h1 style="view-transition-name: page-title;">Work</h1>
<p class="page-body">A portfolio-style page demonstrating how content transitions can maintain context during navigation.</p>
<div class="work-grid">
<div class="work-item" style="--hue: 200;">Scroll Animations</div>
<div class="work-item" style="--hue: 270;">WebGL Scenes</div>
<div class="work-item" style="--hue: 330;">View Transitions</div>
<div class="work-item" style="--hue: 45;">Canvas Effects</div>
</div>
</div>
</div>
<!-- Contact -->
<div class="page" id="page-contact">
<div class="page-content">
<h1 style="view-transition-name: page-title;">Contact</h1>
<p class="page-body">The final page in the routing demo. Navigate back to any other page to see the reverse transition direction.</p>
<a href="/" class="btn-gallery">Back to Showcase</a>
</div>
</div>
</main>
<script src="script.js"></script>
</body>
</html>