Parallax Design is a web design technique that creates the illusion of depth and spatial dimension by moving background elements at a different speed than foreground content as the user scrolls. Inspired by the multiplane camera technique pioneered in classic animation, this approach layers visual elements at varying depths along the Z-axis, producing a cinematic sense of immersion within a two-dimensional browser viewport. Parallax websites are particularly effective for narrative-driven experiences, product showcases, and brand storytelling, where the scroll itself becomes a mechanism for guiding the user through a curated visual journey. The technique rose to mainstream prominence around 2011 and has remained a durable staple of modern web design, evolving from simple background-attachment tricks to sophisticated CSS 3D transforms and scroll-driven animations. At its best, parallax design transforms passive scrolling into an engaging experience that rewards exploration with fluid motion and layered depth.
| Swatch | Hex | Role |
|---|---|---|
| Deep Space | #0B0E17 |
Primary dark background for hero and immersive sections |
| Midnight Navy | #141B2D |
Secondary dark background for alternating sections |
| Charcoal Depth | #1E2538 |
Card and panel surface color on dark backgrounds |
| Slate Mist | #2A3247 |
Elevated surface, navbar backgrounds |
| Steel Gray | #8892A8 |
Secondary text, muted labels, and borders |
| Silver Fog | #C5CDD9 |
Body text on dark backgrounds |
| Cloud White | #F0F2F5 |
Primary text on dark backgrounds, light section base |
| Electric Blue | #3B82F6 |
Primary accent for CTAs, links, and active states |
| Cyan Glow | #06B6D4 |
Secondary accent for highlights and gradients |
| Warm Amber | #F59E0B |
Tertiary accent for emphasis and warnings |
| Soft Violet | #8B5CF6 |
Gradient accent for decorative depth layers |
| Coral Pulse | #EF4444 |
Alert accent and hover state contrast |
:root {
/* Backgrounds */
--color-deep-space: #0B0E17;
--color-midnight-navy: #141B2D;
--color-charcoal-depth: #1E2538;
--color-slate-mist: #2A3247;
/* Text & Neutrals */
--color-steel-gray: #8892A8;
--color-silver-fog: #C5CDD9;
--color-cloud-white: #F0F2F5;
/* Accents */
--color-electric-blue: #3B82F6;
--color-cyan-glow: #06B6D4;
--color-warm-amber: #F59E0B;
--color-soft-violet: #8B5CF6;
--color-coral-pulse: #EF4444;
/* Functional Aliases */
--bg-primary: var(--color-deep-space);
--bg-secondary: var(--color-midnight-navy);
--bg-surface: var(--color-charcoal-depth);
--text-primary: var(--color-cloud-white);
--text-secondary: var(--color-silver-fog);
--text-muted: var(--color-steel-gray);
--accent-primary: var(--color-electric-blue);
--accent-secondary: var(--color-cyan-glow);
}
| Font | Weight(s) | Usage | Link |
|---|---|---|---|
| Inter | 400, 500, 600, 700 | Primary body text and UI elements | fonts.google.com/specimen/Inter |
| Space Grotesk | 500, 700 | Display headings and hero text | fonts.google.com/specimen/Space+Grotesk |
| Sora | 400, 600, 700 | Alternative heading font with geometric clarity | fonts.google.com/specimen/Sora |
| JetBrains Mono | 400, 500 | Code snippets and technical labels | fonts.google.com/specimen/JetBrains+Mono |
| Playfair Display | 600, 700 | Elegant editorial headings for storytelling sections | fonts.google.com/specimen/Playfair+Display |
| Heading | Body | Vibe |
|---|---|---|
| Space Grotesk 700 | Inter 400 | Modern tech-forward, clean and authoritative |
| Playfair Display 700 | Inter 400 | Editorial elegance meets functional readability |
| Sora 700 | Inter 400 | Geometric precision with approachable warmth |
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;700&family=Inter:wght@400;500;600;700&display=swap');
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 1rem;
line-height: 1.7;
color: var(--text-primary);
-webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4 {
font-family: 'Space Grotesk', sans-serif;
font-weight: 700;
line-height: 1.15;
letter-spacing: -0.02em;
}
h1 { font-size: clamp(2.5rem, 6vw, 5rem); }
h2 { font-size: clamp(1.8rem, 4vw, 3rem); }
h3 { font-size: clamp(1.25rem, 2.5vw, 1.75rem); }
p {
font-size: clamp(1rem, 1.25vw, 1.125rem);
max-width: 65ch;
color: var(--text-secondary);
}
.label {
font-family: 'Inter', sans-serif;
font-weight: 600;
font-size: 0.75rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--accent-primary);
}
min-height: 100vh) to create immersive, self-contained visual stages that the user scrolls through one at a timeperspective on a parent element, allowing child layers translated along the Z-axis to move at naturally different scroll speeds without JavaScripttranslateZ values (move them farther away so they scroll slower) and are scaled back up to compensate for the perspective size reductionposition: sticky for elements that should pin in place during a scroll range, and position: fixed sparingly for persistent navigation overlaysThe foundation of CSS-only parallax: a perspective container with child layers at different Z-depths.
.parallax-container {
height: 100vh;
overflow-x: hidden;
overflow-y: auto;
perspective: 8px;
perspective-origin: 50% 50%;
}
.parallax-group {
position: relative;
height: 100vh;
transform-style: preserve-3d;
}
.parallax-layer--back {
position: absolute;
inset: 0;
transform: translateZ(-4px) scale(1.5);
z-index: -1;
}
.parallax-layer--base {
position: relative;
transform: translateZ(0);
z-index: 1;
}
.parallax-layer--deep {
position: absolute;
inset: 0;
transform: translateZ(-8px) scale(2);
z-index: -2;
}
Sections that animate into view as the user scrolls, using Intersection Observer fallback classes and modern scroll-driven animations.
.scroll-reveal {
opacity: 0;
transform: translateY(60px);
transition: opacity 0.8s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.8s cubic-bezier(0.16, 1, 0.3, 1);
}
.scroll-reveal.is-visible {
opacity: 1;
transform: translateY(0);
}
.scroll-reveal--delay-1 { transition-delay: 0.1s; }
.scroll-reveal--delay-2 { transition-delay: 0.2s; }
.scroll-reveal--delay-3 { transition-delay: 0.3s; }
/* Modern: scroll-driven animation (Chrome 115+) */
@supports (animation-timeline: scroll()) {
.scroll-reveal-native {
animation: reveal-up linear both;
animation-timeline: view();
animation-range: entry 0% entry 40%;
}
@keyframes reveal-up {
from { opacity: 0; transform: translateY(60px); }
to { opacity: 1; transform: translateY(0); }
}
}
Dark, layered cards with subtle borders and hover lift effects that reinforce the depth theme.
.parallax-card {
background: var(--bg-surface);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 16px;
padding: 2rem;
position: relative;
overflow: hidden;
transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1),
box-shadow 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.parallax-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.3), transparent);
}
.parallax-card:hover {
transform: translateY(-8px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4), 0 0 60px rgba(59, 130, 246, 0.08);
}
Buttons with gradient fills and a glow effect on hover, fitting the depth-forward aesthetic.
.btn-parallax {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.875rem 2rem;
font-family: 'Inter', sans-serif;
font-weight: 600;
font-size: 0.95rem;
color: #fff;
background: linear-gradient(135deg, var(--color-electric-blue), var(--color-cyan-glow));
border: none;
border-radius: 12px;
cursor: pointer;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.btn-parallax:hover {
transform: translateY(-2px);
box-shadow: 0 8px 30px rgba(59, 130, 246, 0.35), 0 0 60px rgba(6, 182, 212, 0.15);
}
.btn-parallax--outline {
background: transparent;
border: 2px solid var(--color-electric-blue);
color: var(--color-electric-blue);
}
.btn-parallax--outline:hover {
background: rgba(59, 130, 246, 0.1);
box-shadow: 0 0 30px rgba(59, 130, 246, 0.15);
}
A fixed transparent navbar that gains a solid backdrop on scroll, staying unobtrusive over parallax content.
.parallax-nav {
position: fixed;
top: 0; left: 0; right: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 2rem;
background: transparent;
transition: background 0.4s ease, backdrop-filter 0.4s ease;
}
.parallax-nav.is-scrolled {
background: rgba(11, 14, 23, 0.85);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.parallax-nav__link {
color: var(--text-secondary);
text-decoration: none;
font-size: 0.9rem;
font-weight: 500;
transition: color 0.3s ease;
position: relative;
}
.parallax-nav__link::after {
content: '';
position: absolute;
bottom: -4px; left: 0;
width: 0; height: 2px;
background: var(--accent-primary);
transition: width 0.3s ease;
}
.parallax-nav__link:hover::after { width: 100%; }
A full-viewport hero with layered background elements and centered content.
.parallax-hero {
position: relative;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
overflow: hidden;
background: var(--bg-primary);
}
.parallax-hero__bg {
position: absolute;
inset: -20%;
background:
radial-gradient(ellipse at 30% 50%, rgba(59, 130, 246, 0.12) 0%, transparent 60%),
radial-gradient(ellipse at 70% 50%, rgba(139, 92, 246, 0.1) 0%, transparent 60%);
pointer-events: none;
}
.parallax-hero__title {
font-family: 'Space Grotesk', sans-serif;
font-size: clamp(3rem, 7vw, 5.5rem);
font-weight: 700;
line-height: 1.05;
background: linear-gradient(135deg, var(--text-primary), var(--text-secondary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.parallax-hero__scroll-indicator {
position: absolute;
bottom: 2rem; left: 50%;
transform: translateX(-50%);
color: var(--text-muted);
font-size: 0.75rem;
letter-spacing: 0.1em;
text-transform: uppercase;
animation: float 2.5s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateX(-50%) translateY(0); }
50% { transform: translateX(-50%) translateY(-10px); }
}
translate3d, will-change: transform) to keep parallax scrolling smooth at 60 frames per secondprefers-reduced-motion: reduce)srcset to prevent scroll jank from oversized assetsbackground-attachment: fixed as your only parallax method, since it is unsupported on iOS Safari and forces full-page repaints that destroy performanceoverflow: hidden on parallax groups, or translated background layers will bleed outside their intended section boundaries| Aesthetic | Relationship |
|---|---|
| Glassmorphism | Shares the use of backdrop blur, layered depth, and translucent surfaces to create dimensional UI elements |
| Dark Mode Neon | Complements parallax with dark backgrounds and vibrant accent glows that enhance the cinematic, immersive mood |
| Minimalism | Parallax design benefits from minimalist content strategy, letting generous whitespace and restrained layouts amplify the depth effect |
| Aurora Gradient | Gradient-heavy backgrounds pair naturally with parallax layering, creating atmospheric color shifts as layers move |
| Cyberpunk | Shares the dark, high-contrast color palette and tech-forward visual language common in immersive parallax storytelling sites |
| Flat Design | Serves as a historical counterpoint; parallax design deliberately rejects flatness in favor of dimensional depth and spatial illusion |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Parallax Design Template</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--deep-space: #0B0E17; --midnight: #141B2D; --charcoal: #1E2538;
--slate: #2A3247; --steel: #8892A8; --silver: #C5CDD9;
--cloud: #F0F2F5; --blue: #3B82F6; --cyan: #06B6D4;
--amber: #F59E0B; --violet: #8B5CF6;
}
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
color: var(--cloud); background: var(--deep-space);
line-height: 1.7; -webkit-font-smoothing: antialiased;
}
h1, h2, h3 { font-family: 'Space Grotesk', sans-serif; font-weight: 700; line-height: 1.15; letter-spacing: -0.02em; }
a { color: var(--blue); text-decoration: none; }
/* -- Nav -- */
.nav {
position: fixed; top: 0; left: 0; right: 0; z-index: 1000;
display: flex; align-items: center; justify-content: space-between;
padding: 1.25rem 2.5rem; background: transparent;
transition: background 0.4s, backdrop-filter 0.4s;
}
.nav.scrolled {
background: rgba(11,14,23,0.88); backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-bottom: 1px solid rgba(255,255,255,0.06);
}
.nav-logo { font-family: 'Space Grotesk', sans-serif; font-weight: 700; font-size: 1.3rem; color: var(--cloud); text-decoration: none; }
.nav-logo span { color: var(--blue); }
.nav-links { display: flex; gap: 2rem; list-style: none; }
.nav-links a {
color: var(--silver); font-size: 0.9rem; font-weight: 500;
text-decoration: none; transition: color 0.3s; position: relative;
}
.nav-links a:hover { color: var(--cloud); }
.nav-links a::after {
content: ''; position: absolute; bottom: -4px; left: 0;
width: 0; height: 2px; background: var(--blue); transition: width 0.3s;
}
.nav-links a:hover::after { width: 100%; }
/* -- Buttons -- */
.btn {
display: inline-flex; align-items: center; padding: 0.875rem 2rem;
font-family: 'Inter', sans-serif; font-weight: 600; font-size: 0.95rem;
color: #fff; background: linear-gradient(135deg, var(--blue), var(--cyan));
border: none; border-radius: 12px; cursor: pointer;
transition: transform 0.3s, box-shadow 0.3s;
}
.btn:hover { transform: translateY(-2px); box-shadow: 0 8px 30px rgba(59,130,246,0.35); }
.btn-outline {
background: transparent; border: 2px solid var(--blue); color: var(--blue);
}
.btn-outline:hover { background: rgba(59,130,246,0.08); box-shadow: 0 0 30px rgba(59,130,246,0.15); }
/* -- Hero -- */
.hero {
position: relative; min-height: 100vh; display: flex;
align-items: center; justify-content: center; text-align: center; overflow: hidden;
}
.hero-bg {
position: absolute; inset: -20%; pointer-events: none;
background:
radial-gradient(ellipse at 20% 50%, rgba(139,92,246,0.15), transparent 50%),
radial-gradient(ellipse at 80% 50%, rgba(59,130,246,0.12), transparent 50%);
animation: drift 20s ease-in-out infinite alternate;
}
.hero-bg-2 {
position: absolute; inset: -20%; pointer-events: none;
background:
radial-gradient(ellipse at 60% 30%, rgba(6,182,212,0.1), transparent 40%),
radial-gradient(ellipse at 40% 70%, rgba(245,158,11,0.06), transparent 40%);
animation: drift 15s ease-in-out infinite alternate-reverse;
}
@keyframes drift {
0% { transform: translate(0,0) scale(1); }
100% { transform: translate(30px,-20px) scale(1.05); }
}
.hero-content { position: relative; z-index: 2; max-width: 800px; padding: 2rem; }
.hero-label { font-weight: 600; font-size: 0.8rem; letter-spacing: 0.15em; text-transform: uppercase; color: var(--blue); margin-bottom: 1.5rem; }
.hero h1 {
font-size: clamp(2.8rem, 7vw, 5.5rem); margin-bottom: 1.5rem;
background: linear-gradient(135deg, var(--cloud), var(--silver));
-webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
}
.hero p { font-size: clamp(1.05rem, 1.8vw, 1.25rem); color: var(--silver); max-width: 560px; margin: 0 auto 2.5rem; }
.hero-actions { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; }
.scroll-hint {
position: absolute; bottom: 2.5rem; left: 50%; transform: translateX(-50%);
color: var(--steel); font-size: 0.7rem; letter-spacing: 0.12em;
text-transform: uppercase; animation: float 2.5s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateX(-50%) translateY(0); }
50% { transform: translateX(-50%) translateY(-8px); }
}
.fade-bottom {
position: absolute; bottom: 0; left: 0; right: 0; height: 180px;
background: linear-gradient(to top, var(--midnight), transparent);
pointer-events: none; z-index: 3;
}
.fade-top {
position: absolute; top: 0; left: 0; right: 0; height: 180px;
background: linear-gradient(to bottom, var(--deep-space), transparent);
pointer-events: none; z-index: 3;
}
/* -- Sections -- */
.section {
position: relative; min-height: 100vh; display: flex;
align-items: center; overflow: hidden;
}
.section-dark { background: var(--deep-space); }
.section-navy { background: var(--midnight); }
.section-inner { position: relative; z-index: 2; width: 100%; max-width: 1200px; margin: 0 auto; padding: 6rem 2rem; }
.section-header { text-align: center; margin-bottom: 4rem; }
.section-label { font-weight: 600; font-size: 0.8rem; letter-spacing: 0.15em; text-transform: uppercase; color: var(--cyan); margin-bottom: 1rem; display: block; }
.section-header h2 { font-size: clamp(2rem, 4vw, 3rem); margin-bottom: 1rem; }
.section-header p { font-size: 1.05rem; color: var(--silver); max-width: 600px; margin: 0 auto; }
/* -- Cards -- */
.card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 2rem; }
.card {
background: var(--charcoal); border: 1px solid rgba(255,255,255,0.06);
border-radius: 16px; padding: 2rem; position: relative; overflow: hidden;
transition: transform 0.4s cubic-bezier(0.16,1,0.3,1), box-shadow 0.4s cubic-bezier(0.16,1,0.3,1);
}
.card::before {
content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px;
background: linear-gradient(90deg, transparent, rgba(59,130,246,0.3), transparent);
}
.card:hover { transform: translateY(-6px); box-shadow: 0 20px 40px rgba(0,0,0,0.3); }
.card-icon {
width: 48px; height: 48px; border-radius: 12px; display: flex;
align-items: center; justify-content: center; font-size: 1.4rem;
margin-bottom: 1.25rem; background: rgba(59,130,246,0.1); color: var(--blue);
}
.card-icon.cyan { background: rgba(6,182,212,0.1); color: var(--cyan); }
.card-icon.violet { background: rgba(139,92,246,0.1); color: var(--violet); }
.card-icon.amber { background: rgba(245,158,11,0.1); color: var(--amber); }
.card h3 { font-family: 'Space Grotesk', sans-serif; font-size: 1.2rem; margin-bottom: 0.75rem; }
.card p { font-size: 0.95rem; color: var(--silver); line-height: 1.7; }
/* -- Stats -- */
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 2rem; text-align: center; padding: 4rem 0; }
.stat-num { font-family: 'Space Grotesk', sans-serif; font-size: clamp(2rem, 4vw, 3rem); font-weight: 700; color: var(--blue); }
.stat-label { font-size: 0.85rem; color: var(--steel); text-transform: uppercase; letter-spacing: 0.08em; margin-top: 0.5rem; }
/* -- Footer -- */
.footer {
background: var(--midnight); border-top: 1px solid rgba(255,255,255,0.06);
padding: 3rem 2rem; text-align: center; color: var(--steel); font-size: 0.9rem;
}
/* -- Scroll Reveal -- */
.reveal { opacity: 0; transform: translateY(40px); transition: opacity 0.7s cubic-bezier(0.16,1,0.3,1), transform 0.7s cubic-bezier(0.16,1,0.3,1); }
.reveal.visible { opacity: 1; transform: translateY(0); }
.reveal.d1 { transition-delay: 0.1s; }
.reveal.d2 { transition-delay: 0.2s; }
.reveal.d3 { transition-delay: 0.3s; }
/* -- Reduced Motion -- */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
.reveal { opacity: 1; transform: none; }
}
/* -- Responsive -- */
@media (max-width: 768px) {
.nav-links { display: none; }
.stats { grid-template-columns: repeat(2, 1fr); }
.card-grid { grid-template-columns: 1fr; }
.section-inner { padding: 4rem 1.5rem; }
}
</style>
</head>
<body>
<!-- Navigation -->
<nav class="nav" id="nav">
<a href="#" class="nav-logo">Parallax<span>.</span></a>
<ul class="nav-links">
<li><a href="#features">Features</a></li>
<li><a href="#about">About</a></li>
<li><a href="#stats">Stats</a></li>
<li><a href="#techniques">Techniques</a></li>
</ul>
</nav>
<!-- Hero -->
<section class="hero">
<div class="hero-bg"></div>
<div class="hero-bg-2"></div>
<div class="hero-content">
<p class="hero-label">Immersive Web Experiences</p>
<h1>Design with Depth</h1>
<p>Create layered, scroll-driven experiences that guide users through your story with cinematic motion and spatial depth.</p>
<div class="hero-actions">
<button class="btn">Get Started</button>
<button class="btn btn-outline">Learn More</button>
</div>
</div>
<div class="scroll-hint">Scroll ↓</div>
<div class="fade-bottom"></div>
</section>
<!-- Features -->
<section class="section section-navy" id="features">
<div class="fade-top"></div>
<div class="section-inner">
<div class="section-header reveal">
<span class="section-label">Features</span>
<h2>Layered by Design</h2>
<p>Every element exists at a purposeful depth, creating visual hierarchy through spatial relationships.</p>
</div>
<div class="card-grid">
<div class="card reveal d1">
<div class="card-icon">◆</div>
<h3>Depth Layering</h3>
<p>Background, midground, and foreground elements move at independent scroll speeds, producing natural three-dimensional space.</p>
</div>
<div class="card reveal d2">
<div class="card-icon cyan">◊</div>
<h3>Scroll Reveals</h3>
<p>Content animates into view as users scroll, using eased transitions tied to viewport intersection for progressive disclosure.</p>
</div>
<div class="card reveal d3">
<div class="card-icon violet">✧</div>
<h3>Cinematic Pacing</h3>
<p>Full-viewport sections create a scene-by-scene rhythm, giving each content block room to breathe and make its impact.</p>
</div>
</div>
</div>
</section>
<!-- About -->
<section class="section section-dark" id="about">
<div class="section-inner">
<div class="section-header reveal">
<span class="section-label" style="color: var(--amber);">About the Technique</span>
<h2>Motion with Meaning</h2>
<p>Parallax design transforms the humble scroll into a storytelling device. By assigning different movement rates to layered elements, designers create depth that mirrors how we perceive the real world.</p>
</div>
</div>
</section>
<!-- Stats -->
<section class="section section-navy" id="stats">
<div class="section-inner">
<div class="section-header reveal">
<span class="section-label">By the Numbers</span>
<h2>Performance Matters</h2>
</div>
<div class="stats reveal">
<div><div class="stat-num">60</div><div class="stat-label">FPS Target</div></div>
<div><div class="stat-num">3</div><div class="stat-label">Max Layers</div></div>
<div><div class="stat-num">8px</div><div class="stat-label">Perspective</div></div>
<div><div class="stat-num">0ms</div><div class="stat-label">JS Required</div></div>
</div>
</div>
</section>
<!-- Techniques -->
<section class="section section-dark" id="techniques">
<div class="section-inner">
<div class="section-header reveal">
<span class="section-label">Techniques</span>
<h2>Build with Confidence</h2>
<p>Core CSS techniques that power smooth, accessible parallax experiences without JavaScript dependencies.</p>
</div>
<div class="card-grid">
<div class="card reveal d1">
<div class="card-icon amber">■</div>
<h3>CSS Perspective</h3>
<p>Set perspective on the scroll container and translateZ on child layers. Elements farther away scroll slower -- pure CSS.</p>
</div>
<div class="card reveal d2">
<div class="card-icon">▲</div>
<h3>GPU Acceleration</h3>
<p>Use translate3d and will-change to promote layers to the GPU compositor, eliminating paint bottlenecks during scroll.</p>
</div>
<div class="card reveal d3">
<div class="card-icon cyan">●</div>
<h3>Scroll Timelines</h3>
<p>The animation-timeline: scroll() API ties CSS animations directly to scroll progress for declarative parallax.</p>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<p>Parallax Design Template — Built with CSS layers, depth, and purpose.</p>
</footer>
<script>
// Scroll reveal via Intersection Observer
const obs = new IntersectionObserver((entries) => {
entries.forEach(e => { if (e.isIntersecting) e.target.classList.add('visible'); });
}, { threshold: 0.15, rootMargin: '0px 0px -60px 0px' });
document.querySelectorAll('.reveal').forEach(el => obs.observe(el));
// Nav background on scroll
const nav = document.getElementById('nav');
window.addEventListener('scroll', () => {
nav.classList.toggle('scrolled', window.scrollY > 80);
}, { passive: true });
</script>
</body>
</html>
perspective on the scroll container, not individual layers: Setting perspective: 8px on the parent and transform: translateZ(-Npx) scale(1 + N/perspective) on children is the most reliable pure-CSS parallax pattern, avoiding the inconsistencies of background-attachment: fixedtranslateZ with a compensating scale: When you move a layer back with translateZ(-4px), perspective makes it appear smaller; multiply its scale by (perspective + offset) / perspective (e.g., scale(1.5) for -4px at 8px perspective) to restore its intended visual sizewill-change: transform sparingly and purposefully: Apply it only to actively animating parallax layers to promote them to GPU compositing; overusing it on static elements wastes memory and can degrade performanceprefers-reduced-motion media query: Users who experience motion sickness should receive a static version of the layout with all content visible and no scroll-triggered animationssrcset with 2x variants and serve WebP/AVIF formats