feat: choreograph hero devices on scroll

This commit is contained in:
2026-08-21 00:08:50 -04:00
parent ec89696c48
commit b52eff02e1
2 changed files with 100 additions and 22 deletions
+7 -5
View File
@@ -96,7 +96,7 @@ const accessFit = [
export default function HomePage() { export default function HomePage() {
return ( return (
<> <>
<section className="relative isolate min-h-[calc(100svh-4rem)] overflow-hidden px-4 pt-14 sm:px-6 sm:pt-20 lg:pt-24"> <section className="relative isolate min-h-[calc(100svh-4rem)] overflow-x-clip px-4 pt-14 sm:px-6 sm:pt-20 lg:pt-24">
<div className="bg-primary-light/55 absolute top-0 left-1/2 -z-10 h-[38rem] w-[68rem] max-w-[120vw] -translate-x-1/2 rounded-full blur-3xl" /> <div className="bg-primary-light/55 absolute top-0 left-1/2 -z-10 h-[38rem] w-[68rem] max-w-[120vw] -translate-x-1/2 rounded-full blur-3xl" />
<div className="mx-auto max-w-7xl text-center"> <div className="mx-auto max-w-7xl text-center">
<div className="animate-fade-in-up flex items-center justify-center gap-3"> <div className="animate-fade-in-up flex items-center justify-center gap-3">
@@ -127,13 +127,14 @@ export default function HomePage() {
</div> </div>
</div> </div>
<div className="relative mx-auto mt-12 h-[44rem] max-w-5xl sm:h-[58rem] lg:mt-8 lg:h-[64rem]"> <div className="pitch-hero-stage relative mx-auto mt-12 h-[190svh] max-w-5xl lg:mt-8">
<div className="sticky top-16 h-[calc(100svh-4rem)]">
<div className="absolute top-24 left-1/2 w-[17rem] -translate-x-[86%] -rotate-6 sm:top-28 sm:w-[22rem] lg:top-32 lg:w-[26rem]"> <div className="absolute top-24 left-1/2 w-[17rem] -translate-x-[86%] -rotate-6 sm:top-28 sm:w-[22rem] lg:top-32 lg:w-[26rem]">
<ProductDevice <ProductDevice
src="/screenshots/product-bezel-2026-08-20/summary-natural.webp" src="/screenshots/product-bezel-2026-08-20/summary-natural.webp"
darkSrc="/screenshots/product-bezel-2026-08-20/summary-natural-dark.webp" darkSrc="/screenshots/product-bezel-2026-08-20/summary-natural-dark.webp"
alt="Medscribe visit summary in a natural titanium iPhone product bezel" alt="Medscribe visit summary in a natural titanium iPhone product bezel"
className="pitch-device-motion pitch-device-drift-left" className="pitch-hero-phone pitch-hero-phone-left"
sizes="(min-width: 1024px) 416px, (min-width: 640px) 352px, 272px" sizes="(min-width: 1024px) 416px, (min-width: 640px) 352px, 272px"
priority priority
/> />
@@ -143,13 +144,14 @@ export default function HomePage() {
src="/screenshots/product-bezel-2026-08-20/home-black.webp" src="/screenshots/product-bezel-2026-08-20/home-black.webp"
darkSrc="/screenshots/product-bezel-2026-08-20/home-black-dark.webp" darkSrc="/screenshots/product-bezel-2026-08-20/home-black-dark.webp"
alt="Medscribe home screen in a black titanium iPhone product bezel" alt="Medscribe home screen in a black titanium iPhone product bezel"
className="pitch-device-motion pitch-device-drift-right" className="pitch-hero-phone pitch-hero-phone-right"
sizes="(min-width: 1024px) 448px, (min-width: 640px) 368px, 288px" sizes="(min-width: 1024px) 448px, (min-width: 640px) 368px, 288px"
priority priority
/> />
</div> </div>
</div> </div>
<p className="text-muted-foreground relative z-20 mx-auto pb-8 text-center text-xs font-medium tracking-wide"> </div>
<p className="text-muted-foreground relative z-20 mx-auto mt-4 pb-8 text-center text-xs font-medium tracking-wide">
Illustrative profile · Medscribe interface Illustrative profile · Medscribe interface
</p> </p>
</section> </section>
+76
View File
@@ -129,6 +129,61 @@
--pitch-exit-rotate: -1.25deg; --pitch-exit-rotate: -1.25deg;
} }
/* The opening pair gets its own scroll story: meet, settle, then peel apart. */
.pitch-hero-stage {
perspective: 1600px;
}
.pitch-hero-phone {
backface-visibility: hidden;
transform-origin: center;
transform-style: preserve-3d;
}
.pitch-hero-phone-left {
--hero-start-x: -4rem;
--hero-meet-x: 1.25rem;
--hero-exit-x: -5rem;
--hero-start-rotate: -6deg;
--hero-meet-rotate: 1.5deg;
--hero-exit-rotate: -5deg;
}
.pitch-hero-phone-right {
--hero-start-x: 4rem;
--hero-meet-x: -1rem;
--hero-exit-x: 5rem;
--hero-start-rotate: 6deg;
--hero-meet-rotate: -1.5deg;
--hero-exit-rotate: 5deg;
}
@keyframes pitch-hero-phone-scroll {
0% {
opacity: 0.38;
transform: translate3d(var(--hero-start-x), 4rem, -7rem) scale(0.86)
rotate(var(--hero-start-rotate));
}
30% {
opacity: 1;
transform: translate3d(0, 0.75rem, 0) scale(0.985) rotate(0deg);
}
58% {
opacity: 1;
transform: translate3d(var(--hero-meet-x), -2rem, 4rem) scale(1.04)
rotate(var(--hero-meet-rotate));
}
78% {
opacity: 1;
transform: translate3d(0, -3.5rem, 1rem) scale(1.015) rotate(0deg);
}
100% {
opacity: 0.76;
transform: translate3d(var(--hero-exit-x), -10rem, -4rem) scale(0.94)
rotate(var(--hero-exit-rotate));
}
}
@keyframes pitch-device-scroll { @keyframes pitch-device-scroll {
0% { 0% {
opacity: 0.42; opacity: 0.42;
@@ -172,6 +227,14 @@
} }
} }
@supports (animation-timeline: scroll()) {
.pitch-hero-phone {
animation: pitch-hero-phone-scroll linear both;
animation-timeline: scroll(root block);
animation-range: 0px 1500px;
}
}
@supports (animation-timeline: view()) { @supports (animation-timeline: view()) {
.pitch-device-motion { .pitch-device-motion {
animation: pitch-device-scroll linear both; animation: pitch-device-scroll linear both;
@@ -194,6 +257,18 @@
/* Keep the product choreography visible on phones without crowding the copy. */ /* Keep the product choreography visible on phones without crowding the copy. */
@media (max-width: 639px) { @media (max-width: 639px) {
.pitch-hero-phone-left {
--hero-start-x: -1.5rem;
--hero-meet-x: 0.5rem;
--hero-exit-x: -1.75rem;
}
.pitch-hero-phone-right {
--hero-start-x: 1.5rem;
--hero-meet-x: -0.5rem;
--hero-exit-x: 1.75rem;
}
.pitch-device-motion { .pitch-device-motion {
--pitch-enter-y: 4rem; --pitch-enter-y: 4rem;
--pitch-exit-y: -3rem; --pitch-exit-y: -3rem;
@@ -229,6 +304,7 @@
} }
.pitch-device-motion, .pitch-device-motion,
.pitch-hero-phone,
.pitch-copy-motion, .pitch-copy-motion,
.pitch-card-motion { .pitch-card-motion {
animation: none !important; animation: none !important;