12 CSS Scroll Animations 12 / 12
Sticky Product Tour
Classic scrollytelling — a sticky device on the left swaps its screen to match each step as the copy scrolls past on the right.
This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.
The code
<div class="lead">
<span class="badge">Product Tour</span>
<h1>Watch the interface change as you read.</h1>
<p>A sticky device on the left holds its place while the steps on the right scroll past — each step swaps the screen to match.</p>
</div>
<section class="tour">
<div class="stage">
<div class="sticky">
<div class="device">
<div class="screen">
<div class="panel pA on" data-i="0">
<div class="glyph">🔍</div>
<div class="pt">Capture</div>
<div class="pd">Drop in any source — the workspace ingests it instantly.</div>
</div>
<div class="panel pB" data-i="1">
<div class="glyph">🧩</div>
<div class="pt">Organise</div>
<div class="pd">Auto-grouped into boards you can rearrange by hand.</div>
</div>
<div class="panel pC" data-i="2">
<div class="glyph">⚡</div>
<div class="pt">Automate</div>
<div class="pd">Rules fire on change — no manual upkeep.</div>
</div>
<div class="panel pD" data-i="3">
<div class="glyph">📤</div>
<div class="pt">Ship</div>
<div class="pd">Publish anywhere with one reviewed export.</div>
</div>
<div class="pips">
<span class="pip on"></span><span class="pip"></span>
<span class="pip"></span><span class="pip"></span>
</div>
</div>
</div>
</div>
</div>
<div class="steps">
<div class="step" data-step="0">
<div class="n">Step 01</div>
<h2>Capture everything in one inbox</h2>
<p>Links, files, notes, screenshots — anything you feed the workspace lands in a single capture stream, parsed and tagged the moment it arrives.</p>
</div>
<div class="step" data-step="1">
<div class="n">Step 02</div>
<h2>Organise without the busywork</h2>
<p>Captured items group themselves into boards. You stay in control — drag, merge, or split — but you never start from an empty canvas.</p>
</div>
<div class="step" data-step="2">
<div class="n">Step 03</div>
<h2>Automate the parts you repeat</h2>
<p>Set a rule once and it runs forever. When something changes upstream, the workspace reacts before you'd think to.</p>
</div>
<div class="step" data-step="3">
<div class="n">Step 04</div>
<h2>Ship to anywhere, reviewed</h2>
<p>A single export pipeline pushes to every destination — with a review gate so nothing leaves before you say so.</p>
</div>
</div>
</section> <div class="lead">
<span class="badge">Product Tour</span>
<h1>Watch the interface change as you read.</h1>
<p>A sticky device on the left holds its place while the steps on the right scroll past — each step swaps the screen to match.</p>
</div>
<section class="tour">
<div class="stage">
<div class="sticky">
<div class="device">
<div class="screen">
<div class="panel pA on" data-i="0">
<div class="glyph">🔍</div>
<div class="pt">Capture</div>
<div class="pd">Drop in any source — the workspace ingests it instantly.</div>
</div>
<div class="panel pB" data-i="1">
<div class="glyph">🧩</div>
<div class="pt">Organise</div>
<div class="pd">Auto-grouped into boards you can rearrange by hand.</div>
</div>
<div class="panel pC" data-i="2">
<div class="glyph">⚡</div>
<div class="pt">Automate</div>
<div class="pd">Rules fire on change — no manual upkeep.</div>
</div>
<div class="panel pD" data-i="3">
<div class="glyph">📤</div>
<div class="pt">Ship</div>
<div class="pd">Publish anywhere with one reviewed export.</div>
</div>
<div class="pips">
<span class="pip on"></span><span class="pip"></span>
<span class="pip"></span><span class="pip"></span>
</div>
</div>
</div>
</div>
</div>
<div class="steps">
<div class="step" data-step="0">
<div class="n">Step 01</div>
<h2>Capture everything in one inbox</h2>
<p>Links, files, notes, screenshots — anything you feed the workspace lands in a single capture stream, parsed and tagged the moment it arrives.</p>
</div>
<div class="step" data-step="1">
<div class="n">Step 02</div>
<h2>Organise without the busywork</h2>
<p>Captured items group themselves into boards. You stay in control — drag, merge, or split — but you never start from an empty canvas.</p>
</div>
<div class="step" data-step="2">
<div class="n">Step 03</div>
<h2>Automate the parts you repeat</h2>
<p>Set a rule once and it runs forever. When something changes upstream, the workspace reacts before you'd think to.</p>
</div>
<div class="step" data-step="3">
<div class="n">Step 04</div>
<h2>Ship to anywhere, reviewed</h2>
<p>A single export pipeline pushes to every destination — with a review gate so nothing leaves before you say so.</p>
</div>
</div>
</section>*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
body{background:#0A0A0F;font-family:'Sora',sans-serif;color:#E8E8EC}
.lead{
padding:8rem 6vw 5rem;max-width:760px;
}
.lead .badge{
display:inline-block;font-family:'IBM Plex Mono',monospace;
font-size:11px;letter-spacing:0.14em;text-transform:uppercase;
color:#34D399;border:1px solid rgba(52,211,153,0.3);
padding:5px 12px;border-radius:20px;margin-bottom:1.5rem;
}
.lead h1{
font-size:clamp(34px,5.5vw,62px);font-weight:700;
line-height:1.05;letter-spacing:-0.025em;
}
.lead p{margin-top:1.25rem;font-size:17px;color:#9A9AA5;line-height:1.7;max-width:54ch}
/* scrollytelling section: sticky visual + scrolling steps */
.tour{
display:grid;grid-template-columns:1fr 1fr;gap:4vw;
padding:0 6vw 8rem;
}
/* left: sticky device */
.stage{position:relative}
.sticky{
position:sticky;top:14vh;height:72vh;
display:flex;align-items:center;justify-content:center;
}
.device{
width:300px;height:430px;border-radius:34px;
background:#16161E;border:1px solid rgba(255,255,255,0.08);
padding:14px;position:relative;overflow:hidden;
box-shadow:0 40px 80px -30px rgba(0,0,0,0.8);
}
.screen{
width:100%;height:100%;border-radius:22px;overflow:hidden;
position:relative;background:#0A0A0F;
}
/* each panel stacked, cross-faded */
.panel{
position:absolute;inset:0;
display:flex;flex-direction:column;
align-items:center;justify-content:center;gap:1rem;
opacity:0;transform:scale(1.05);
transition:opacity 0.6s ease,transform 0.6s ease;
padding:2rem;text-align:center;
}
.panel.on{opacity:1;transform:scale(1)}
.panel .glyph{font-size:54px}
.panel .pt{font-weight:600;font-size:18px}
.panel .pd{font-size:12px;color:#9A9AA5;line-height:1.6}
.pA{background:linear-gradient(160deg,#1E1B4B,#0A0A0F)}
.pB{background:linear-gradient(160deg,#064E3B,#0A0A0F)}
.pC{background:linear-gradient(160deg,#7C2D12,#0A0A0F)}
.pD{background:linear-gradient(160deg,#4C1D95,#0A0A0F)}
/* step progress pips on device frame */
.pips{
position:absolute;bottom:26px;left:50%;transform:translateX(-50%);
display:flex;gap:7px;
}
.pip{width:6px;height:6px;border-radius:50%;background:rgba(255,255,255,0.18);
transition:background 0.4s,width 0.4s}
.pip.on{background:#34D399;width:18px;border-radius:4px}
/* right: scrolling steps */
.steps{display:flex;flex-direction:column}
.step{
min-height:78vh;display:flex;flex-direction:column;justify-content:center;
}
.step .n{
font-family:'IBM Plex Mono',monospace;font-size:13px;
color:#34D399;margin-bottom:0.9rem;
}
.step h2{
font-size:clamp(24px,3vw,38px);font-weight:600;
letter-spacing:-0.015em;line-height:1.15;margin-bottom:1rem;
}
.step p{font-size:15px;color:#9A9AA5;line-height:1.75;max-width:42ch}
@media(max-width:760px){
.tour{grid-template-columns:1fr}
.sticky{height:auto;position:relative;top:0;margin-bottom:2rem}
} *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
body{background:#0A0A0F;font-family:'Sora',sans-serif;color:#E8E8EC}
.lead{
padding:8rem 6vw 5rem;max-width:760px;
}
.lead .badge{
display:inline-block;font-family:'IBM Plex Mono',monospace;
font-size:11px;letter-spacing:0.14em;text-transform:uppercase;
color:#34D399;border:1px solid rgba(52,211,153,0.3);
padding:5px 12px;border-radius:20px;margin-bottom:1.5rem;
}
.lead h1{
font-size:clamp(34px,5.5vw,62px);font-weight:700;
line-height:1.05;letter-spacing:-0.025em;
}
.lead p{margin-top:1.25rem;font-size:17px;color:#9A9AA5;line-height:1.7;max-width:54ch}
/* scrollytelling section: sticky visual + scrolling steps */
.tour{
display:grid;grid-template-columns:1fr 1fr;gap:4vw;
padding:0 6vw 8rem;
}
/* left: sticky device */
.stage{position:relative}
.sticky{
position:sticky;top:14vh;height:72vh;
display:flex;align-items:center;justify-content:center;
}
.device{
width:300px;height:430px;border-radius:34px;
background:#16161E;border:1px solid rgba(255,255,255,0.08);
padding:14px;position:relative;overflow:hidden;
box-shadow:0 40px 80px -30px rgba(0,0,0,0.8);
}
.screen{
width:100%;height:100%;border-radius:22px;overflow:hidden;
position:relative;background:#0A0A0F;
}
/* each panel stacked, cross-faded */
.panel{
position:absolute;inset:0;
display:flex;flex-direction:column;
align-items:center;justify-content:center;gap:1rem;
opacity:0;transform:scale(1.05);
transition:opacity 0.6s ease,transform 0.6s ease;
padding:2rem;text-align:center;
}
.panel.on{opacity:1;transform:scale(1)}
.panel .glyph{font-size:54px}
.panel .pt{font-weight:600;font-size:18px}
.panel .pd{font-size:12px;color:#9A9AA5;line-height:1.6}
.pA{background:linear-gradient(160deg,#1E1B4B,#0A0A0F)}
.pB{background:linear-gradient(160deg,#064E3B,#0A0A0F)}
.pC{background:linear-gradient(160deg,#7C2D12,#0A0A0F)}
.pD{background:linear-gradient(160deg,#4C1D95,#0A0A0F)}
/* step progress pips on device frame */
.pips{
position:absolute;bottom:26px;left:50%;transform:translateX(-50%);
display:flex;gap:7px;
}
.pip{width:6px;height:6px;border-radius:50%;background:rgba(255,255,255,0.18);
transition:background 0.4s,width 0.4s}
.pip.on{background:#34D399;width:18px;border-radius:4px}
/* right: scrolling steps */
.steps{display:flex;flex-direction:column}
.step{
min-height:78vh;display:flex;flex-direction:column;justify-content:center;
}
.step .n{
font-family:'IBM Plex Mono',monospace;font-size:13px;
color:#34D399;margin-bottom:0.9rem;
}
.step h2{
font-size:clamp(24px,3vw,38px);font-weight:600;
letter-spacing:-0.015em;line-height:1.15;margin-bottom:1rem;
}
.step p{font-size:15px;color:#9A9AA5;line-height:1.75;max-width:42ch}
@media(max-width:760px){
.tour{grid-template-columns:1fr}
.sticky{height:auto;position:relative;top:0;margin-bottom:2rem}
}const panels=[...document.querySelectorAll('.panel')];
const pips=[...document.querySelectorAll('.pip')];
const steps=[...document.querySelectorAll('.step')];
function setActive(i){
panels.forEach(p=>p.classList.toggle('on',+p.dataset.i===i));
pips.forEach((p,j)=>p.classList.toggle('on',j===i));
}
const io=new IntersectionObserver((entries)=>{
entries.forEach(e=>{
if(e.isIntersecting) setActive(+e.target.dataset.step);
});
},{rootMargin:'-45% 0px -45% 0px'});
steps.forEach(s=>io.observe(s)); const panels=[...document.querySelectorAll('.panel')];
const pips=[...document.querySelectorAll('.pip')];
const steps=[...document.querySelectorAll('.step')];
function setActive(i){
panels.forEach(p=>p.classList.toggle('on',+p.dataset.i===i));
pips.forEach((p,j)=>p.classList.toggle('on',j===i));
}
const io=new IntersectionObserver((entries)=>{
entries.forEach(e=>{
if(e.isIntersecting) setActive(+e.target.dataset.step);
});
},{rootMargin:'-45% 0px -45% 0px'});
steps.forEach(s=>io.observe(s));