Back to CSS Scroll Animations Sticky Product Tour CSS + JS
Share
HTML
<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>
CSS
*,*::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}
}
JS
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));