Cinematic Device Scroll Stage
A standalone, Apple-style product narrative where a pinned device stage evolves through multiple chapters while the page scrolls.
This experiment requires full-page rendering.
Open live demoHTML
<div class="cinematic-device-scroll-stage" data-device-story data-active-scene="design">
<section class="device-story-hero">
<p class="device-story-hero__eyebrow">Scroll-driven launch narrative</p>
<h1>One device, four cinematic chapters.</h1>
<p class="device-story-hero__intro">
This experiment turns a long product page into a staged sequence: the hardware stays pinned while color, display,
motion and workflow layers evolve as the reader moves through the story.
</p>
</section>
<section class="device-story-experience">
<div class="device-story-track">
<article class="device-story-step is-active" data-device-step="design" data-scene-label="Scene 01: Palette">
<p class="device-story-step__eyebrow">Scene 01</p>
<h2>Palette and presence.</h2>
<p>
The page opens with a calm hero composition. Color chips, a slim silhouette and a slow parallax glow make the
product feel immediate before the detail work begins.
</p>
</article>
<article class="device-story-step" data-device-step="display" data-scene-label="Scene 02: Display">
<p class="device-story-step__eyebrow">Scene 02</p>
<h2>Display that behaves like a light source.</h2>
<p>
The screen area brightens, edge reflections intensify and image framing expands, turning the display from a
frame into the visual engine of the page.
</p>
</article>
<article class="device-story-step" data-device-step="motion" data-scene-label="Scene 03: Motion">
<p class="device-story-step__eyebrow">Scene 03</p>
<h2>Media blocks enter with rhythm.</h2>
<p>
Filmstrip elements, progress lines and motion surfaces slide into the stage, so the transition feels less
like swapping slides and more like directing a sequence.
</p>
</article>
<article class="device-story-step" data-device-step="workflow" data-scene-label="Scene 04: Workflow">
<p class="device-story-step__eyebrow">Scene 04</p>
<h2>Utility becomes part of the spectacle.</h2>
<p>
Floating interface cards, metrics and connected modules surround the device to show how product pages can
stay practical without losing their sense of occasion.
</p>
</article>
</div>
<div class="device-story-stage-wrap">
<div class="device-story-stage" data-device-stage>
<div class="device-story-backdrop" aria-hidden="true">
<div class="device-story-glow device-story-glow--cool"></div>
<div class="device-story-glow device-story-glow--warm"></div>
<div class="device-story-grid"></div>
<div class="device-story-halo"></div>
</div>
<div class="device-product">
<div class="device-product__screen-shell">
<div class="device-product__camera"></div>
<div class="device-product__screen">
<div class="screen-panel screen-panel--design is-active" data-screen-panel="design">
<div class="screen-ribbon">
<span>Four finishes</span>
<span>13-inch class</span>
</div>
<div class="screen-swatches">
<span class="screen-swatch screen-swatch--silver"></span>
<span class="screen-swatch screen-swatch--blue"></span>
<span class="screen-swatch screen-swatch--gold"></span>
<span class="screen-swatch screen-swatch--ink"></span>
</div>
<div class="screen-design-visual">
<div class="screen-design-visual__disc"></div>
<div class="screen-design-visual__beam"></div>
<div class="screen-design-visual__beam screen-design-visual__beam--alt"></div>
</div>
<p class="screen-panel__headline">Thin. Fast. Quietly confident.</p>
</div>
<div class="screen-panel screen-panel--display" data-screen-panel="display">
<div class="screen-display-photo">
<div class="screen-display-photo__sun"></div>
<div class="screen-display-photo__ridge"></div>
<div class="screen-display-photo__foreground"></div>
</div>
<div class="screen-display-metrics">
<span>High contrast</span>
<span>Wide color</span>
<span>Brightness-led focus</span>
</div>
</div>
<div class="screen-panel screen-panel--motion" data-screen-panel="motion">
<div class="screen-video">
<div class="screen-video__hero">
<div class="screen-video__play"></div>
<div class="screen-video__caption">Chapter trailer</div>
</div>
<div class="screen-video__timeline">
<span></span>
</div>
<div class="screen-video__thumbs">
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
<div class="screen-panel screen-panel--workflow" data-screen-panel="workflow">
<div class="screen-workflow">
<div class="workflow-card workflow-card--large">
<p class="workflow-card__eyebrow">Connected tasks</p>
<h3>Launch assets synced</h3>
<div class="workflow-bars">
<span></span>
<span></span>
<span></span>
</div>
</div>
<div class="workflow-card workflow-card--small">
<p class="workflow-card__eyebrow">Team status</p>
<strong>Ready to publish</strong>
</div>
<div class="workflow-card workflow-card--message">
<p class="workflow-card__eyebrow">Review</p>
<strong>Visual approved</strong>
</div>
</div>
</div>
</div>
</div>
<div class="device-product__base">
<div class="device-product__keys">
<span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span>
<span></span><span></span><span></span><span></span><span></span><span></span>
</div>
<div class="device-product__trackpad"></div>
</div>
<div class="device-product__shadow"></div>
</div>
<div class="device-floating-card device-floating-card--palette" data-floating-scene="design">
<p class="device-floating-card__eyebrow">Finish set</p>
<strong>Silver, Sky, Sand, Ink</strong>
</div>
<div class="device-floating-card device-floating-card--display" data-floating-scene="display">
<p class="device-floating-card__eyebrow">Image block</p>
<strong>Full-bleed visual</strong>
</div>
<div class="device-floating-card device-floating-card--motion" data-floating-scene="motion">
<p class="device-floating-card__eyebrow">Media layer</p>
<strong>Trailer, thumbnails, progress</strong>
</div>
<div class="device-floating-card device-floating-card--workflow" data-floating-scene="workflow">
<p class="device-floating-card__eyebrow">Workflow</p>
<strong>Cards, stats, context</strong>
</div>
<div class="device-stage-caption">
<p class="device-stage-caption__eyebrow">Pinned stage</p>
<p class="device-stage-caption__title" data-device-scene-title>Scene 01: Palette</p>
</div>
</div>
</div>
</section>
</div>
CSS
.cinematic-device-scroll-stage {
--pointer-x: 50;
--pointer-y: 50;
--tilt-x: 8deg;
--tilt-y: -16deg;
--device-lift: 0px;
--accent: #5b8cff;
--accent-soft: #b0d0ff;
--accent-warm: #ffcf7b;
--page-bg: #f5f5f7;
max-width: 1380px;
margin: 0 auto;
color: #111111;
}
.device-story-hero {
margin-bottom: 24px;
padding: clamp(34px, 6vw, 52px);
border-radius: 36px;
background:
radial-gradient(circle at 78% 18%, rgba(120, 168, 255, 0.24), transparent 24%),
radial-gradient(circle at 24% 78%, rgba(255, 207, 123, 0.18), transparent 22%),
linear-gradient(145deg, #0b0f17 0%, #111726 44%, #171019 100%);
box-shadow: 0 24px 90px rgba(0, 0, 0, 0.24);
}
.device-story-hero__eyebrow,
.device-story-step__eyebrow,
.device-stage-caption__eyebrow,
.device-floating-card__eyebrow,
.workflow-card__eyebrow,
.screen-panel__eyebrow {
margin: 0 0 14px;
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.18em;
color: rgba(17, 17, 17, 0.46);
}
.device-story-hero__eyebrow {
color: rgba(255, 255, 255, 0.58);
}
.device-story-hero h1 {
margin: 0;
max-width: 10ch;
font-size: clamp(3.8rem, 9vw, 7.4rem);
line-height: 0.9;
letter-spacing: -0.05em;
text-wrap: balance;
color: #ffffff;
}
.device-story-hero__intro {
max-width: 52ch;
margin: 22px 0 0;
font-size: 1.12rem;
line-height: 1.9;
color: rgba(255, 255, 255, 0.78);
}
.device-story-experience {
display: grid;
grid-template-columns: minmax(300px, 0.86fr) minmax(360px, 1.14fr);
gap: 34px;
align-items: start;
}
.device-story-track {
display: grid;
gap: 22vh;
padding-bottom: 18vh;
}
.device-story-step {
min-height: 82vh;
padding: clamp(26px, 4vw, 42px);
border-radius: 32px;
border: 1px solid rgba(17, 17, 17, 0.08);
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.82), rgba(255, 255, 255, 0.56)),
rgba(255, 255, 255, 0.78);
box-shadow: 0 26px 60px rgba(15, 23, 42, 0.06);
opacity: 0.42;
transform: translateY(30px);
transition: opacity 220ms ease, transform 220ms ease, box-shadow 220ms ease;
will-change: opacity, transform;
}
.device-story-step.is-active {
opacity: 1;
transform: translateY(0);
box-shadow: 0 28px 80px rgba(15, 23, 42, 0.1);
}
.device-story-step h2 {
margin: 0;
max-width: 12ch;
font-size: clamp(2.4rem, 4vw, 3.6rem);
line-height: 0.94;
letter-spacing: -0.04em;
}
.device-story-step p:last-child {
max-width: 34ch;
margin: 18px 0 0;
font-size: 1.04rem;
line-height: 1.85;
color: rgba(17, 17, 17, 0.68);
}
.device-story-stage-wrap {
position: sticky;
top: 28px;
height: calc(100vh - 56px);
}
.device-story-stage {
position: relative;
height: 100%;
overflow: hidden;
border-radius: 42px;
border: 1px solid rgba(17, 17, 17, 0.06);
background:
radial-gradient(circle at calc(var(--pointer-x) * 1%) calc(var(--pointer-y) * 1%), rgba(91, 140, 255, 0.12), transparent 24%),
linear-gradient(180deg, #ffffff 0%, #edf1f7 100%);
box-shadow: 0 36px 120px rgba(15, 23, 42, 0.12);
isolation: isolate;
}
.device-story-backdrop,
.device-product,
.device-stage-caption,
.device-floating-card {
position: absolute;
}
.device-story-backdrop {
inset: 0;
pointer-events: none;
}
.device-story-glow {
position: absolute;
width: 380px;
height: 380px;
border-radius: 999px;
filter: blur(30px);
opacity: 0.72;
}
.device-story-glow--cool {
top: calc(7% + (var(--pointer-y) * 0.08%));
right: calc(20% - (var(--pointer-x) * 0.08%));
background: rgba(176, 208, 255, 0.56);
}
.device-story-glow--warm {
left: 8%;
bottom: 16%;
background: rgba(255, 207, 123, 0.36);
}
.device-story-grid {
position: absolute;
inset: 18% 8% 8%;
border-radius: 36px;
background-image:
linear-gradient(rgba(0, 0, 0, 0.032) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 0, 0, 0.032) 1px, transparent 1px);
background-size: 56px 56px;
mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.88), transparent 86%);
}
.device-story-halo {
position: absolute;
inset: 14% 12% auto;
height: 420px;
border-radius: 50%;
background:
radial-gradient(circle at 50% 50%, rgba(91, 140, 255, 0.12), transparent 54%),
radial-gradient(circle at 62% 46%, rgba(255, 207, 123, 0.12), transparent 44%);
transform: rotate(-8deg);
}
.device-product {
left: 50%;
top: 52%;
width: min(760px, 88%);
height: min(620px, 74%);
transform: translate(-50%, -48%);
pointer-events: none;
}
.device-product__screen-shell {
position: absolute;
left: 50%;
top: 8%;
width: min(640px, 94%);
height: min(420px, 72%);
border-radius: 26px 26px 18px 18px;
padding: 14px;
background: linear-gradient(180deg, #1f2127 0%, #08090b 100%);
box-shadow:
0 34px 70px rgba(15, 23, 42, 0.18),
inset 0 1px 0 rgba(255, 255, 255, 0.14);
transform:
translateX(-50%)
translateY(calc(var(--device-lift) * 1))
perspective(1600px)
rotateX(var(--tilt-x))
rotateY(var(--tilt-y));
transform-style: preserve-3d;
transition: transform 220ms ease-out;
will-change: transform;
}
.device-product__camera {
position: absolute;
top: 7px;
left: 50%;
width: 72px;
height: 8px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.07);
transform: translateX(-50%);
}
.device-product__screen {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
border-radius: 18px 18px 10px 10px;
background:
radial-gradient(circle at 22% 22%, rgba(255, 255, 255, 0.18), transparent 22%),
linear-gradient(145deg, #101828 0%, #162036 44%, #09111f 100%);
}
.screen-panel {
position: absolute;
inset: 0;
padding: 26px;
opacity: 0;
visibility: hidden;
transform: scale(1.04);
transition: opacity 320ms ease, transform 320ms ease, visibility 0s linear 320ms;
will-change: opacity, transform;
}
.screen-panel.is-active {
opacity: 1;
visibility: visible;
transform: scale(1);
transition-delay: 0s;
}
.screen-ribbon {
display: flex;
justify-content: space-between;
gap: 16px;
color: rgba(255, 255, 255, 0.76);
font-size: 0.82rem;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.screen-swatches {
display: flex;
gap: 12px;
margin-top: 24px;
}
.screen-swatch {
width: 42px;
height: 42px;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.24);
box-shadow: inset 0 2px 6px rgba(255, 255, 255, 0.26);
}
.screen-swatch--silver {
background: linear-gradient(145deg, #f2f5fb, #b9c0cf);
}
.screen-swatch--blue {
background: linear-gradient(145deg, #9fd6ff, #5b8cff);
}
.screen-swatch--gold {
background: linear-gradient(145deg, #fff0ba, #f1b94d);
}
.screen-swatch--ink {
background: linear-gradient(145deg, #67749b, #19243f);
}
.screen-design-visual {
position: relative;
height: 224px;
margin-top: 28px;
overflow: hidden;
border-radius: 26px;
background:
radial-gradient(circle at 28% 28%, rgba(255, 255, 255, 0.22), transparent 20%),
linear-gradient(140deg, #5e8eff 0%, #8ecfff 38%, #ffe5a2 70%, #ffb48e 100%);
}
.screen-design-visual__disc {
position: absolute;
width: 180px;
height: 180px;
top: 22px;
right: 46px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.18);
filter: blur(8px);
}
.screen-design-visual__beam {
position: absolute;
left: 14%;
top: 22%;
width: 280px;
height: 22px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.82);
transform: rotate(-18deg);
box-shadow: 0 0 18px rgba(255, 255, 255, 0.3);
}
.screen-design-visual__beam--alt {
top: 62%;
left: 22%;
width: 220px;
background: rgba(17, 17, 17, 0.14);
}
.screen-panel__headline {
max-width: 14ch;
margin: 26px 0 0;
font-size: 2.2rem;
line-height: 0.96;
letter-spacing: -0.04em;
color: #ffffff;
}
.screen-display-photo {
position: relative;
height: 252px;
border-radius: 28px;
overflow: hidden;
background:
linear-gradient(180deg, #92c9ff 0%, #fce7af 42%, #f38d72 100%);
}
.screen-display-photo__sun {
position: absolute;
top: 32px;
right: 62px;
width: 94px;
height: 94px;
border-radius: 999px;
background: rgba(255, 250, 235, 0.8);
box-shadow: 0 0 40px rgba(255, 250, 235, 0.46);
}
.screen-display-photo__ridge {
position: absolute;
inset: auto 0 62px;
height: 108px;
background:
linear-gradient(180deg, rgba(98, 63, 124, 0.2), rgba(51, 34, 88, 0.78));
clip-path: polygon(0 100%, 18% 56%, 32% 62%, 52% 22%, 70% 56%, 100% 18%, 100% 100%);
}
.screen-display-photo__foreground {
position: absolute;
inset: auto -5% -10px;
height: 92px;
background:
linear-gradient(180deg, rgba(27, 32, 54, 0.16), rgba(8, 12, 20, 0.92));
clip-path: polygon(0 100%, 0 62%, 24% 34%, 42% 48%, 58% 18%, 82% 46%, 100% 22%, 100% 100%);
}
.screen-display-metrics {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 22px;
}
.screen-display-metrics span {
padding: 10px 14px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
color: rgba(255, 255, 255, 0.78);
font-size: 0.9rem;
}
.screen-video {
display: grid;
gap: 18px;
}
.screen-video__hero {
position: relative;
height: 250px;
border-radius: 28px;
overflow: hidden;
background:
radial-gradient(circle at 22% 24%, rgba(255, 255, 255, 0.18), transparent 18%),
linear-gradient(135deg, #12203a 0%, #5b8cff 34%, #89b7ff 52%, #ffe2b1 80%, #ff9b7c 100%);
}
.screen-video__hero::before,
.screen-video__hero::after {
content: "";
position: absolute;
border-radius: 999px;
background: rgba(255, 255, 255, 0.24);
}
.screen-video__hero::before {
width: 240px;
height: 240px;
right: -34px;
top: -24px;
}
.screen-video__hero::after {
width: 340px;
height: 18px;
left: 48px;
bottom: 42px;
transform: rotate(-18deg);
}
.screen-video__play {
position: absolute;
inset: 50% auto auto 50%;
width: 82px;
height: 82px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.82);
transform: translate(-50%, -50%);
}
.screen-video__play::before {
content: "";
position: absolute;
top: 50%;
left: 54%;
width: 0;
height: 0;
border-top: 12px solid transparent;
border-bottom: 12px solid transparent;
border-left: 20px solid #0f1726;
transform: translate(-50%, -50%);
}
.screen-video__caption {
position: absolute;
left: 24px;
bottom: 22px;
font-size: 1rem;
color: rgba(255, 255, 255, 0.82);
}
.screen-video__timeline {
height: 8px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
overflow: hidden;
}
.screen-video__timeline span {
display: block;
width: 46%;
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, #89b7ff, #ffe2b1);
animation: deviceTimeline 5s linear infinite;
}
.screen-video__thumbs {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 10px;
}
.screen-video__thumbs span {
height: 76px;
border-radius: 18px;
background:
linear-gradient(145deg, rgba(255, 255, 255, 0.18), rgba(255, 255, 255, 0.04)),
linear-gradient(145deg, #294375, #121c33);
}
.screen-workflow {
display: grid;
grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
gap: 16px;
align-items: start;
}
.workflow-card {
padding: 18px;
border-radius: 24px;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.06)),
rgba(12, 18, 32, 0.72);
border: 1px solid rgba(255, 255, 255, 0.14);
color: #ffffff;
}
.workflow-card h3,
.workflow-card strong {
display: block;
margin: 8px 0 0;
font-size: 1.28rem;
line-height: 1.05;
}
.workflow-card--large {
min-height: 226px;
}
.workflow-bars {
display: grid;
gap: 10px;
margin-top: 18px;
}
.workflow-bars span {
display: block;
height: 12px;
border-radius: 999px;
background: linear-gradient(90deg, #89b7ff, #ffe2b1);
}
.workflow-bars span:nth-child(1) {
width: 88%;
}
.workflow-bars span:nth-child(2) {
width: 68%;
}
.workflow-bars span:nth-child(3) {
width: 76%;
}
.workflow-card--small {
min-height: 120px;
}
.workflow-card--message {
grid-column: 2;
min-height: 88px;
}
.device-product__base {
position: absolute;
left: 50%;
bottom: 14%;
width: min(700px, 98%);
height: 110px;
border-radius: 0 0 34px 34px;
background:
linear-gradient(180deg, #f6f7fa 0%, #dfe5ee 100%);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.76),
0 14px 28px rgba(15, 23, 42, 0.12);
transform: translateX(-50%) perspective(1600px) rotateX(76deg);
}
.device-product__base::before {
content: "";
position: absolute;
inset: 0 0 auto;
height: 14px;
border-radius: 10px 10px 0 0;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0));
}
.device-product__keys {
position: absolute;
top: 18px;
left: 50%;
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 8px;
width: 54%;
transform: translateX(-50%);
}
.device-product__keys span {
height: 10px;
border-radius: 6px;
background: rgba(24, 32, 50, 0.16);
}
.device-product__trackpad {
position: absolute;
left: 50%;
bottom: 18px;
width: 22%;
height: 24px;
border-radius: 10px;
border: 1px solid rgba(24, 32, 50, 0.08);
transform: translateX(-50%);
}
.device-product__shadow {
position: absolute;
left: 50%;
bottom: 8%;
width: min(420px, 56%);
height: 86px;
border-radius: 999px;
background: rgba(15, 23, 42, 0.16);
filter: blur(26px);
transform: translateX(-50%);
}
.device-floating-card {
max-width: 220px;
padding: 16px 18px;
border-radius: 22px;
border: 1px solid rgba(17, 17, 17, 0.08);
background: rgba(255, 255, 255, 0.72);
box-shadow: 0 20px 50px rgba(15, 23, 42, 0.12);
backdrop-filter: blur(16px);
opacity: 0;
visibility: hidden;
transform: translateY(18px) scale(0.96);
transition: opacity 220ms ease, transform 220ms ease, visibility 0s linear 220ms;
will-change: opacity, transform;
}
.device-floating-card strong {
display: block;
font-size: 1.06rem;
line-height: 1.25;
}
.device-floating-card.is-active {
opacity: 1;
visibility: visible;
transform: translateY(0) scale(1);
transition-delay: 0s;
}
.device-floating-card--palette {
top: 18%;
left: 6%;
}
.device-floating-card--display {
top: 18%;
right: 6%;
}
.device-floating-card--motion {
bottom: 24%;
left: 10%;
}
.device-floating-card--workflow {
bottom: 18%;
right: 8%;
}
.device-stage-caption {
left: 36px;
right: 36px;
bottom: 26px;
display: flex;
justify-content: space-between;
align-items: end;
gap: 18px;
}
.device-stage-caption__title {
margin: 0;
font-size: clamp(1.6rem, 3vw, 2.4rem);
line-height: 0.94;
letter-spacing: -0.04em;
}
.cinematic-device-scroll-stage[data-active-scene="design"] {
--accent: #5b8cff;
--accent-soft: #b0d0ff;
--accent-warm: #ffcf7b;
}
.cinematic-device-scroll-stage[data-active-scene="display"] {
--accent: #7aa8ff;
--accent-soft: #d8e6ff;
--accent-warm: #f9d391;
}
.cinematic-device-scroll-stage[data-active-scene="motion"] {
--accent: #8c87ff;
--accent-soft: #c0beff;
--accent-warm: #ffc29e;
}
.cinematic-device-scroll-stage[data-active-scene="workflow"] {
--accent: #47b3a9;
--accent-soft: #b9ece6;
--accent-warm: #ffd39b;
}
.cinematic-device-scroll-stage[data-active-scene="display"] .device-story-stage {
background:
radial-gradient(circle at calc(var(--pointer-x) * 1%) calc(var(--pointer-y) * 1%), rgba(122, 168, 255, 0.14), transparent 24%),
linear-gradient(180deg, #ffffff 0%, #edf3ff 100%);
}
.cinematic-device-scroll-stage[data-active-scene="motion"] .device-story-stage {
background:
radial-gradient(circle at calc(var(--pointer-x) * 1%) calc(var(--pointer-y) * 1%), rgba(140, 135, 255, 0.16), transparent 24%),
linear-gradient(180deg, #ffffff 0%, #f3f0ff 100%);
}
.cinematic-device-scroll-stage[data-active-scene="workflow"] .device-story-stage {
background:
radial-gradient(circle at calc(var(--pointer-x) * 1%) calc(var(--pointer-y) * 1%), rgba(71, 179, 169, 0.14), transparent 24%),
linear-gradient(180deg, #ffffff 0%, #eef7f4 100%);
}
@keyframes deviceTimeline {
0% {
transform: translateX(-48%);
}
100% {
transform: translateX(128%);
}
}
@media (max-width: 1120px) {
.device-story-experience {
grid-template-columns: 1fr;
gap: 22px;
}
.device-story-stage-wrap {
order: -1;
position: sticky;
top: 16px;
height: min(74vh, 760px);
z-index: 2;
}
.device-story-track {
gap: 14vh;
padding-bottom: 14vh;
}
.device-story-step {
min-height: 58vh;
}
}
@media (max-width: 720px) {
.device-story-hero {
padding: 24px;
border-radius: 28px;
}
.device-story-hero h1 {
max-width: 11ch;
font-size: clamp(2.9rem, 14vw, 4.6rem);
}
.device-story-hero__intro {
font-size: 1rem;
line-height: 1.75;
}
.device-story-stage {
border-radius: 28px;
}
.device-story-step {
min-height: 44vh;
}
.device-story-step,
.device-story-stage {
padding: 0;
}
.device-story-step {
padding: 24px;
}
.device-story-stage-wrap {
top: 12px;
height: min(62vh, 620px);
min-height: 520px;
}
.device-story-stage {
height: 100%;
}
.device-product {
width: 94%;
height: 72%;
top: 50%;
}
.device-product__screen-shell {
width: 96%;
top: 10%;
height: 54%;
padding: 10px;
}
.screen-panel {
padding: 16px;
}
.screen-ribbon {
flex-wrap: wrap;
gap: 8px;
}
.screen-swatches {
margin-top: 16px;
}
.screen-panel__headline {
font-size: 1.64rem;
}
.screen-design-visual,
.screen-display-photo,
.screen-video__hero {
height: 188px;
}
.screen-video {
gap: 12px;
}
.screen-video__thumbs {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.screen-workflow {
grid-template-columns: 1fr;
}
.workflow-card--message {
grid-column: auto;
}
.device-product__base {
width: 96%;
bottom: 18%;
height: 84px;
}
.device-floating-card {
display: none;
}
.device-stage-caption {
left: 18px;
right: 18px;
bottom: 18px;
flex-direction: column;
align-items: start;
padding: 14px 16px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.76);
backdrop-filter: blur(14px);
box-shadow: 0 18px 40px rgba(15, 23, 42, 0.1);
}
}
@media (prefers-reduced-motion: reduce) {
.device-story-step,
.screen-panel,
.device-floating-card,
.device-product__screen-shell {
transition: none;
}
.screen-video__timeline span {
animation: none;
}
}
JavaScript
(function () {
const root = document.querySelector('[data-device-story]');
if (!root) return;
const stage = root.querySelector('[data-device-stage]');
const steps = Array.from(root.querySelectorAll('[data-device-step]'));
const panels = Array.from(root.querySelectorAll('[data-screen-panel]'));
const floatingCards = Array.from(root.querySelectorAll('[data-floating-scene]'));
const sceneTitle = root.querySelector('[data-device-scene-title]');
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const isCoarsePointer = window.matchMedia('(pointer: coarse)').matches;
if (!stage || !steps.length || !panels.length || !sceneTitle) return;
let activeScene = null;
let pointerX = 50;
let pointerY = 50;
let targetX = 50;
let targetY = 50;
let scrollTicking = false;
function setScene(sceneName, label) {
if (activeScene === sceneName) return;
activeScene = sceneName;
root.dataset.activeScene = sceneName;
steps.forEach(step => {
step.classList.toggle('is-active', step.dataset.deviceStep === sceneName);
});
panels.forEach(panel => {
panel.classList.toggle('is-active', panel.dataset.screenPanel === sceneName);
});
floatingCards.forEach(card => {
card.classList.toggle('is-active', card.dataset.floatingScene === sceneName);
});
sceneTitle.textContent = label;
}
function updatePointer(event) {
const rect = stage.getBoundingClientRect();
targetX = ((event.clientX - rect.left) / rect.width) * 100;
targetY = ((event.clientY - rect.top) / rect.height) * 100;
}
function resetPointer() {
targetX = 50;
targetY = 50;
}
function animatePointer() {
if (prefersReducedMotion) {
root.style.setProperty('--pointer-x', '50');
root.style.setProperty('--pointer-y', '50');
root.style.setProperty('--tilt-x', '7deg');
root.style.setProperty('--tilt-y', '-16deg');
root.style.setProperty('--device-lift', '0px');
return;
}
pointerX += (targetX - pointerX) * 0.08;
pointerY += (targetY - pointerY) * 0.08;
root.style.setProperty('--pointer-x', pointerX.toFixed(2));
root.style.setProperty('--pointer-y', pointerY.toFixed(2));
const tiltRangeX = isCoarsePointer ? 2.4 : 4;
const tiltRangeY = isCoarsePointer ? 4.2 : 7;
const liftRange = isCoarsePointer ? -3 : -6;
const tiltX = 7 + ((pointerY - 50) / 50) * -tiltRangeX;
const tiltY = -16 + ((pointerX - 50) / 50) * tiltRangeY;
const lift = Math.sin(Date.now() / 900) * liftRange;
root.style.setProperty('--tilt-x', tiltX.toFixed(2) + 'deg');
root.style.setProperty('--tilt-y', tiltY.toFixed(2) + 'deg');
root.style.setProperty('--device-lift', lift.toFixed(2) + 'px');
requestAnimationFrame(animatePointer);
}
function updateActiveScene() {
const viewportAnchor = window.innerWidth <= 720
? window.innerHeight * 0.3
: window.innerWidth <= 1120
? window.innerHeight * 0.38
: window.innerHeight * 0.5;
let closestStep = steps[0];
let smallestDistance = Number.POSITIVE_INFINITY;
steps.forEach(step => {
const rect = step.getBoundingClientRect();
const stepCenter = rect.top + rect.height / 2;
const distance = Math.abs(stepCenter - viewportAnchor);
if (distance < smallestDistance) {
smallestDistance = distance;
closestStep = step;
}
});
if (!closestStep) return;
setScene(
closestStep.dataset.deviceStep,
closestStep.dataset.sceneLabel || closestStep.dataset.deviceStep
);
}
function requestSceneUpdate() {
if (scrollTicking) return;
scrollTicking = true;
window.requestAnimationFrame(function () {
updateActiveScene();
scrollTicking = false;
});
}
if (!isCoarsePointer) {
stage.addEventListener('pointermove', updatePointer);
stage.addEventListener('pointerleave', resetPointer);
} else {
resetPointer();
}
window.addEventListener('scroll', requestSceneUpdate, { passive: true });
window.addEventListener('resize', requestSceneUpdate);
setScene(
steps[0].dataset.deviceStep,
steps[0].dataset.sceneLabel || 'Scene'
);
updateActiveScene();
if (!prefersReducedMotion) {
animatePointer();
} else {
root.style.setProperty('--pointer-x', '50');
root.style.setProperty('--pointer-y', '50');
root.style.setProperty('--tilt-x', '7deg');
root.style.setProperty('--tilt-y', isCoarsePointer ? '-12deg' : '-16deg');
root.style.setProperty('--device-lift', '0px');
}
})();
What it solves
Shows how long-form product pages can feel more cinematic and orchestrated by animating images, media layers and interface elements in sync with scroll progression.
Cinematic Device Scroll Stage explores a more structured launch-page pattern: a sticky hardware stage on one side and a sequence of editorial chapters on the other. As the reader moves through the page, the product scene shifts from palette and materials to display, media and workflow modules.
The aim is to capture the rhythm of premium product storytelling using only the existing Nebula experiment architecture. Instead of relying on external libraries or heavyweight rendering, the effect comes from layered HTML, CSS perspective, animated media surfaces and scroll-driven state changes.