08 / 12
All 12 in 12 Tailwind CSS Full Page Sections
- 01Tailwind Scroll Snap Full Screen Sections with 100dvh
- 02Tailwind Full Page Scroll Snap Without JavaScript
- 03Tailwind Scroll Snap Stop to Prevent Skipping Sections
- 04Tailwind Sticky Stacking Cards on Scroll
- 05Tailwind Full Page Sections with Sticky Dot Navigation
- 06Tailwind Scroll Spy Active Nav Link Without JavaScript
- 07Tailwind Parallax Scroll Sections with Scroll-Driven Animations
- 08Tailwind Full Page Section Reveal and Scale on Scroll
- 09Tailwind Full Page SaaS Product Feature Showcase
- 10Tailwind Full Screen Portfolio Gallery Scroll Sections
- 11Tailwind Horizontal Scroll Snap Full Screen Panels
- 12Accessible Tailwind Scroll Snap with Keyboard Navigation
Tailwind Full Page Section Reveal and Scale on Scroll
Each incoming sheet rises from 92% scale and 30% opacity to full size exactly as it snaps into place, holds perfectly still, then recedes the same way on the way out — one keyframe set, one tfps08-reveal utility, both halves of the transition. Only scale and opacity animate, and an oversized hue-tinted numeral gives every sheet depth.
Complete Tailwind CSS landing page templates in HTML, React, Next.js and Vue · from $49
Cruip · Affiliate link
The code
<div class="tfps-08 relative isolate w-full min-h-svh bg-tfps08-bg font-tfps08-sans text-tfps08-ink antialiased [&_code]:font-tfps08-mono [&_code]:text-[.92em]">
<input class="peer sr-only" type="checkbox" id="tfps-08-theme">
<label for="tfps-08-theme" class="fixed top-[1.15rem] right-[1.15rem] z-80 grid size-11 cursor-pointer place-items-center rounded-full border border-tfps08-ink/10 bg-white/60 text-tfps08-ink hover:bg-white/85 tfps08-dark:bg-white/[.07] tfps08-dark:hover:bg-white/15 shadow-[0_8px_24px_-12px_var(--color-tfps08-shade)] backdrop-blur-[10px] backdrop-saturate-150 transition-[background-color,scale] duration-200 active:scale-95 peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-tfps08-acc forced-colors:border-[CanvasText]">
<svg class="size-[1.05rem] tfps08-dark:hidden" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M12 2.6v2.3M12 19.1v2.3M2.6 12h2.3M19.1 12h2.3M5.3 5.3l1.7 1.7M17 17l1.7 1.7M18.7 5.3L17 7M7 17l-1.7 1.7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/><circle cx="12" cy="12" r="4.4" stroke="currentColor" stroke-width="1.7"/></svg>
<svg class="hidden size-[1.05rem] tfps08-dark:block" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M20.5 14.4A8.4 8.4 0 0 1 9.6 3.5 8.6 8.6 0 1 0 20.5 14.4Z" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="sr-only">Switch light or dark theme</span>
</label>
<div class="h-dvh overflow-x-hidden overflow-y-auto snap-y snap-mandatory scroll-smooth overscroll-y-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfps08-acc [@media(max-height:40rem)]:snap-proximity motion-reduce:snap-none motion-reduce:scroll-auto" tabindex="0" role="group" aria-label="Reveal and scale sections, five panels">
<section class="[--h:300] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s1" aria-labelledby="tfps-08-h1">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">01</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)] transition-[opacity,translate] duration-700 ease-out starting:translate-y-4 starting:opacity-0 motion-reduce:transition-none">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">01 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">scale .92 → 1</span></p>
<h2 class="max-w-[20ch] text-[clamp(2rem,5.6vw,4rem)] leading-[1.02] font-semibold tracking-[-.042em] text-balance" id="tfps-08-h1">Every panel arrives by growing into place</h2>
<p class="max-w-[58ch] text-[clamp(1rem,1.4vw,1.15rem)] leading-[1.62] text-pretty text-tfps08-ink/75 [&_code]:text-tfps08-ink">One keyframe set drives both halves of the transition: the incoming sheet rises from 92% and 30% opacity, holds perfectly still while it is snapped, then recedes the same way as it leaves.</p>
<a href="#tfps-08-s2" class="inline-flex min-h-11 items-center gap-2.5 rounded-full pe-3 font-tfps08-mono text-[.82rem] text-tfps08-ink/75 no-underline transition-colors hover:text-tfps08-acc focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tfps08-acc"><svg class="size-[1.1rem] animate-tfps08-bob motion-reduce:animate-none" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M12 4.5v13.5M6.5 12.6 12 18.2l5.5-5.6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>Scroll — the next sheet is already growing</a>
</div>
</div>
</section>
<section class="[--h:255] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s2" aria-labelledby="tfps-08-h2">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">02</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)]">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">02 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">one range, two directions</span></p>
<h2 class="max-w-[26ch] text-[clamp(1.7rem,3.7vw,2.7rem)] leading-[1.06] font-semibold tracking-[-.032em] text-balance" id="tfps-08-h2">The cover range already knows the whole journey</h2>
<pre class="w-full overflow-x-auto overflow-y-hidden rounded-2xl border border-tfps08-ink/10 bg-tfps08-ink/[.04] px-[1.1rem] py-4"><code class="text-[clamp(.78rem,1vw,.88rem)] leading-[1.75] whitespace-pre text-tfps08-ink/75 [&_mark]:rounded-[.25rem] [&_mark]:bg-tfps08-acc/15 [&_mark]:px-[.2rem] [&_mark]:text-tfps08-acc">@keyframes tfps08-reveal {
0% { scale: .92; opacity: .3 }
<mark>44%, 56% { scale: 1; opacity: 1 }</mark>
100% { scale: .92; opacity: .3 }
}
<div class="<mark>tfps08-reveal</mark>"> <!-- cover 0% → cover 100% --></code></pre>
<p class="max-w-[58ch] text-[clamp(1rem,1.4vw,1.15rem)] leading-[1.62] text-pretty text-tfps08-ink/75 [&_code]:text-tfps08-ink">No entry animation fighting an exit animation, and no JavaScript deciding which one owns the middle of the screen.</p>
</div>
</div>
</section>
<section class="[--h:210] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s3" aria-labelledby="tfps-08-h3">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">03</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)]">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">03 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">the plateau</span></p>
<h2 class="max-w-[26ch] text-[clamp(1.7rem,3.7vw,2.7rem)] leading-[1.06] font-semibold tracking-[-.032em] text-balance" id="tfps-08-h3">Twelve percent of dead zone in the middle</h2>
<p class="max-w-[58ch] text-[clamp(1rem,1.4vw,1.15rem)] leading-[1.62] text-pretty text-tfps08-ink/75 [&_code]:text-tfps08-ink">Hold full scale from 44% to 56% of the range and a snapped panel is visually locked. Remove the hold and the sheet breathes on every tiny trackpad nudge — the single most common reason this effect feels cheap.</p>
<ul class="grid w-full gap-1">
<li class="grid grid-cols-[6.5rem_1fr] items-baseline gap-4 border-t border-tfps08-ink/10 py-3"><strong class="font-tfps08-mono text-[.82rem] font-medium text-tfps08-acc tabular-nums">0–44%</strong><span class="text-[.95rem] text-tfps08-ink/75">entering — scale and opacity rise</span></li>
<li class="grid grid-cols-[6.5rem_1fr] items-baseline gap-4 border-t border-tfps08-ink/10 py-3"><strong class="font-tfps08-mono text-[.82rem] font-medium text-tfps08-acc tabular-nums">44–56%</strong><span class="text-[.95rem] text-tfps08-ink/75">snapped — nothing moves</span></li>
<li class="grid grid-cols-[6.5rem_1fr] items-baseline gap-4 border-t border-tfps08-ink/10 py-3"><strong class="font-tfps08-mono text-[.82rem] font-medium text-tfps08-acc tabular-nums">56–100%</strong><span class="text-[.95rem] text-tfps08-ink/75">leaving — recedes symmetrically</span></li>
</ul>
</div>
</div>
</section>
<section class="[--h:165] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s4" aria-labelledby="tfps-08-h4">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">04</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)]">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">04 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">compositor only</span></p>
<h2 class="max-w-[26ch] text-[clamp(1.7rem,3.7vw,2.7rem)] leading-[1.06] font-semibold tracking-[-.032em] text-balance" id="tfps-08-h4">Two cheap properties, zero expensive ones</h2>
<div class="grid w-full grid-cols-[repeat(auto-fit,minmax(12rem,1fr))] gap-[clamp(.8rem,2vw,1.4rem)]">
<div class="grid content-start gap-3 rounded-[1.15rem] border border-tfps08-ink/10 bg-tfps08-ink/[.03] p-[clamp(1rem,2.2vw,1.4rem)]"><p class="font-tfps08-mono text-[.68rem] tracking-[.12em] uppercase text-tfps08-yes">Free on the compositor</p><ul class="grid gap-2 font-tfps08-mono text-[.87rem] text-tfps08-ink/75"><li>scale</li><li>opacity</li><li>translate</li><li>rotate</li></ul></div>
<div class="grid content-start gap-3 rounded-[1.15rem] border border-tfps08-ink/10 bg-tfps08-ink/[.03] p-[clamp(1rem,2.2vw,1.4rem)]"><p class="font-tfps08-mono text-[.68rem] tracking-[.12em] uppercase text-tfps08-no">Repaints every frame</p><ul class="grid gap-2 font-tfps08-mono text-[.87rem] text-tfps08-ink/75"><li>filter: blur()</li><li>width / height</li><li>top / left</li><li>box-shadow, border-radius</li></ul></div>
</div>
</div>
</div>
</section>
<section class="[--h:120] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s5" aria-labelledby="tfps-08-h5">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">05</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)]">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">05 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">pick your floor</span></p>
<h2 class="max-w-[26ch] text-[clamp(1.7rem,3.7vw,2.7rem)] leading-[1.06] font-semibold tracking-[-.032em] text-balance" id="tfps-08-h5">Why 0.92 and not 0.8</h2>
<p class="max-w-[58ch] text-[clamp(1rem,1.4vw,1.15rem)] leading-[1.62] text-pretty text-tfps08-ink/75 [&_code]:text-tfps08-ink">At <code>.92</code> the displacement reads as depth while body copy stays legible the whole way in. Below roughly <code>.85</code> headlines visibly reflow, the effect starts to feel like a slideshow, and motion-sensitive readers notice immediately.</p>
<figure class="grid max-w-[46ch] gap-2.5 border-s-2 border-tfps08-acc ps-[1.1rem]">
<blockquote class="text-[clamp(1.05rem,1.9vw,1.35rem)] leading-[1.45] tracking-[-.02em] text-pretty"><p>“We shipped the reveal at .92 with a held middle. Nobody described it as an animation — they described the page as feeling fast.”</p></blockquote>
<figcaption class="font-tfps08-mono text-[.68rem] tracking-[.1em] uppercase text-tfps08-ink/70">Sam Rivera · product designer</figcaption>
</figure>
</div>
</div>
</section>
</div>
</div><div class="tfps-08 relative isolate w-full min-h-svh bg-tfps08-bg font-tfps08-sans text-tfps08-ink antialiased [&_code]:font-tfps08-mono [&_code]:text-[.92em]">
<input class="peer sr-only" type="checkbox" id="tfps-08-theme">
<label for="tfps-08-theme" class="fixed top-[1.15rem] right-[1.15rem] z-80 grid size-11 cursor-pointer place-items-center rounded-full border border-tfps08-ink/10 bg-white/60 text-tfps08-ink hover:bg-white/85 tfps08-dark:bg-white/[.07] tfps08-dark:hover:bg-white/15 shadow-[0_8px_24px_-12px_var(--color-tfps08-shade)] backdrop-blur-[10px] backdrop-saturate-150 transition-[background-color,scale] duration-200 active:scale-95 peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-tfps08-acc forced-colors:border-[CanvasText]">
<svg class="size-[1.05rem] tfps08-dark:hidden" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M12 2.6v2.3M12 19.1v2.3M2.6 12h2.3M19.1 12h2.3M5.3 5.3l1.7 1.7M17 17l1.7 1.7M18.7 5.3L17 7M7 17l-1.7 1.7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/><circle cx="12" cy="12" r="4.4" stroke="currentColor" stroke-width="1.7"/></svg>
<svg class="hidden size-[1.05rem] tfps08-dark:block" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M20.5 14.4A8.4 8.4 0 0 1 9.6 3.5 8.6 8.6 0 1 0 20.5 14.4Z" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="sr-only">Switch light or dark theme</span>
</label>
<div class="h-dvh overflow-x-hidden overflow-y-auto snap-y snap-mandatory scroll-smooth overscroll-y-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfps08-acc [@media(max-height:40rem)]:snap-proximity motion-reduce:snap-none motion-reduce:scroll-auto" tabindex="0" role="group" aria-label="Reveal and scale sections, five panels">
<section class="[--h:300] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s1" aria-labelledby="tfps-08-h1">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">01</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)] transition-[opacity,translate] duration-700 ease-out starting:translate-y-4 starting:opacity-0 motion-reduce:transition-none">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">01 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">scale .92 → 1</span></p>
<h2 class="max-w-[20ch] text-[clamp(2rem,5.6vw,4rem)] leading-[1.02] font-semibold tracking-[-.042em] text-balance" id="tfps-08-h1">Every panel arrives by growing into place</h2>
<p class="max-w-[58ch] text-[clamp(1rem,1.4vw,1.15rem)] leading-[1.62] text-pretty text-tfps08-ink/75 [&_code]:text-tfps08-ink">One keyframe set drives both halves of the transition: the incoming sheet rises from 92% and 30% opacity, holds perfectly still while it is snapped, then recedes the same way as it leaves.</p>
<a href="#tfps-08-s2" class="inline-flex min-h-11 items-center gap-2.5 rounded-full pe-3 font-tfps08-mono text-[.82rem] text-tfps08-ink/75 no-underline transition-colors hover:text-tfps08-acc focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tfps08-acc"><svg class="size-[1.1rem] animate-tfps08-bob motion-reduce:animate-none" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M12 4.5v13.5M6.5 12.6 12 18.2l5.5-5.6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>Scroll — the next sheet is already growing</a>
</div>
</div>
</section>
<section class="[--h:255] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s2" aria-labelledby="tfps-08-h2">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">02</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)]">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">02 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">one range, two directions</span></p>
<h2 class="max-w-[26ch] text-[clamp(1.7rem,3.7vw,2.7rem)] leading-[1.06] font-semibold tracking-[-.032em] text-balance" id="tfps-08-h2">The cover range already knows the whole journey</h2>
<pre class="w-full overflow-x-auto overflow-y-hidden rounded-2xl border border-tfps08-ink/10 bg-tfps08-ink/[.04] px-[1.1rem] py-4"><code class="text-[clamp(.78rem,1vw,.88rem)] leading-[1.75] whitespace-pre text-tfps08-ink/75 [&_mark]:rounded-[.25rem] [&_mark]:bg-tfps08-acc/15 [&_mark]:px-[.2rem] [&_mark]:text-tfps08-acc">@keyframes tfps08-reveal {
0% { scale: .92; opacity: .3 }
<mark>44%, 56% { scale: 1; opacity: 1 }</mark>
100% { scale: .92; opacity: .3 }
}
<div class="<mark>tfps08-reveal</mark>"> <!-- cover 0% → cover 100% --></code></pre>
<p class="max-w-[58ch] text-[clamp(1rem,1.4vw,1.15rem)] leading-[1.62] text-pretty text-tfps08-ink/75 [&_code]:text-tfps08-ink">No entry animation fighting an exit animation, and no JavaScript deciding which one owns the middle of the screen.</p>
</div>
</div>
</section>
<section class="[--h:210] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s3" aria-labelledby="tfps-08-h3">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">03</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)]">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">03 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">the plateau</span></p>
<h2 class="max-w-[26ch] text-[clamp(1.7rem,3.7vw,2.7rem)] leading-[1.06] font-semibold tracking-[-.032em] text-balance" id="tfps-08-h3">Twelve percent of dead zone in the middle</h2>
<p class="max-w-[58ch] text-[clamp(1rem,1.4vw,1.15rem)] leading-[1.62] text-pretty text-tfps08-ink/75 [&_code]:text-tfps08-ink">Hold full scale from 44% to 56% of the range and a snapped panel is visually locked. Remove the hold and the sheet breathes on every tiny trackpad nudge — the single most common reason this effect feels cheap.</p>
<ul class="grid w-full gap-1">
<li class="grid grid-cols-[6.5rem_1fr] items-baseline gap-4 border-t border-tfps08-ink/10 py-3"><strong class="font-tfps08-mono text-[.82rem] font-medium text-tfps08-acc tabular-nums">0–44%</strong><span class="text-[.95rem] text-tfps08-ink/75">entering — scale and opacity rise</span></li>
<li class="grid grid-cols-[6.5rem_1fr] items-baseline gap-4 border-t border-tfps08-ink/10 py-3"><strong class="font-tfps08-mono text-[.82rem] font-medium text-tfps08-acc tabular-nums">44–56%</strong><span class="text-[.95rem] text-tfps08-ink/75">snapped — nothing moves</span></li>
<li class="grid grid-cols-[6.5rem_1fr] items-baseline gap-4 border-t border-tfps08-ink/10 py-3"><strong class="font-tfps08-mono text-[.82rem] font-medium text-tfps08-acc tabular-nums">56–100%</strong><span class="text-[.95rem] text-tfps08-ink/75">leaving — recedes symmetrically</span></li>
</ul>
</div>
</div>
</section>
<section class="[--h:165] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s4" aria-labelledby="tfps-08-h4">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">04</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)]">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">04 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">compositor only</span></p>
<h2 class="max-w-[26ch] text-[clamp(1.7rem,3.7vw,2.7rem)] leading-[1.06] font-semibold tracking-[-.032em] text-balance" id="tfps-08-h4">Two cheap properties, zero expensive ones</h2>
<div class="grid w-full grid-cols-[repeat(auto-fit,minmax(12rem,1fr))] gap-[clamp(.8rem,2vw,1.4rem)]">
<div class="grid content-start gap-3 rounded-[1.15rem] border border-tfps08-ink/10 bg-tfps08-ink/[.03] p-[clamp(1rem,2.2vw,1.4rem)]"><p class="font-tfps08-mono text-[.68rem] tracking-[.12em] uppercase text-tfps08-yes">Free on the compositor</p><ul class="grid gap-2 font-tfps08-mono text-[.87rem] text-tfps08-ink/75"><li>scale</li><li>opacity</li><li>translate</li><li>rotate</li></ul></div>
<div class="grid content-start gap-3 rounded-[1.15rem] border border-tfps08-ink/10 bg-tfps08-ink/[.03] p-[clamp(1rem,2.2vw,1.4rem)]"><p class="font-tfps08-mono text-[.68rem] tracking-[.12em] uppercase text-tfps08-no">Repaints every frame</p><ul class="grid gap-2 font-tfps08-mono text-[.87rem] text-tfps08-ink/75"><li>filter: blur()</li><li>width / height</li><li>top / left</li><li>box-shadow, border-radius</li></ul></div>
</div>
</div>
</div>
</section>
<section class="[--h:120] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s5" aria-labelledby="tfps-08-h5">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">05</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)]">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">05 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">pick your floor</span></p>
<h2 class="max-w-[26ch] text-[clamp(1.7rem,3.7vw,2.7rem)] leading-[1.06] font-semibold tracking-[-.032em] text-balance" id="tfps-08-h5">Why 0.92 and not 0.8</h2>
<p class="max-w-[58ch] text-[clamp(1rem,1.4vw,1.15rem)] leading-[1.62] text-pretty text-tfps08-ink/75 [&_code]:text-tfps08-ink">At <code>.92</code> the displacement reads as depth while body copy stays legible the whole way in. Below roughly <code>.85</code> headlines visibly reflow, the effect starts to feel like a slideshow, and motion-sensitive readers notice immediately.</p>
<figure class="grid max-w-[46ch] gap-2.5 border-s-2 border-tfps08-acc ps-[1.1rem]">
<blockquote class="text-[clamp(1.05rem,1.9vw,1.35rem)] leading-[1.45] tracking-[-.02em] text-pretty"><p>“We shipped the reveal at .92 with a held middle. Nobody described it as an animation — they described the page as feeling fast.”</p></blockquote>
<figcaption class="font-tfps08-mono text-[.68rem] tracking-[.1em] uppercase text-tfps08-ink/70">Sam Rivera · product designer</figcaption>
</figure>
</div>
</div>
</section>
</div>
</div>@import "tailwindcss";
@theme {
--color-tfps08-bg: #eceef2;
--color-tfps08-bg: oklch(93.5% .006 300);
--color-tfps08-panel: #ffffff;
--color-tfps08-panel: oklch(1 0 0);
--color-tfps08-ink: #0b0b12;
--color-tfps08-ink: oklch(12% .015 300);
--color-tfps08-acc: #a21caf;
--color-tfps08-acc: oklch(52% .21 328);
--color-tfps08-yes: #0f766e;
--color-tfps08-yes: oklch(48% .1 175);
--color-tfps08-no: #9f1239;
--color-tfps08-no: oklch(48% .16 12);
--color-tfps08-shade: rgb(11 11 18 / .45);
--font-tfps08-sans: 'Geist', system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tfps08-mono: 'Geist Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;
--animate-tfps08-bob: tfps08-bob 2.4s ease-in-out infinite;
@keyframes tfps08-bob {
0%,
100% {
translate: 0 -2px;
}
50% {
translate: 0 3px;
}
}
}
/* Theme state. Dark = OS preference XOR the toggle, so the switch always flips what you see —
the CSS original's checkbox did nothing for visitors whose OS was already dark. */
@custom-variant tfps08-dark {
@media (prefers-color-scheme: light) {
&:where(.tfps-08:has(#tfps-08-theme:checked), .tfps-08:has(#tfps-08-theme:checked) *) {
@slot;
}
}
@media (prefers-color-scheme: dark) {
&:where(.tfps-08:not(:has(#tfps-08-theme:checked)), .tfps-08:not(:has(#tfps-08-theme:checked)) *) {
@slot;
}
}
}
/* Root rule: the full-bleed contract, stated before the browser build compiles. */
.tfps-08 {
width: 100%;
min-height: 100svh;
@variant tfps08-dark {
--color-tfps08-bg: #070709;
--color-tfps08-bg: oklch(0.13 0.005 285.7);
--color-tfps08-panel: #0d0d13;
--color-tfps08-panel: oklch(0.162 0.013 284.9);
--color-tfps08-ink: #f3f2f8;
--color-tfps08-ink: oklch(0.964 0.008 293.9);
--color-tfps08-acc: #f0abfc;
--color-tfps08-acc: oklch(0.833 0.132 321.4);
--color-tfps08-yes: #5eead4;
--color-tfps08-yes: oklch(0.855 0.125 181.1);
--color-tfps08-no: #fda4af;
--color-tfps08-no: oklch(0.81 0.106 11.6);
--color-tfps08-shade: rgb(0 0 0 / .9);
}
}
/* What stayed CSS: the reveal. One @utility keeps shorthand, timeline and range in order. */
@keyframes tfps08-reveal {
0% {
scale: .92;
opacity: .3;
}
44%,
56% {
scale: 1;
opacity: 1;
}
100% {
scale: .92;
opacity: .3;
}
}
@utility tfps08-reveal {
@supports (animation-timeline: view()) {
@media (prefers-reduced-motion: no-preference) {
animation: tfps08-reveal linear both;
animation-timeline: --tfps08;
animation-range: cover 0% cover 100%;
}
}
}@import "tailwindcss";
@theme {
--color-tfps08-bg: #eceef2;
--color-tfps08-bg: oklch(93.5% .006 300);
--color-tfps08-panel: #ffffff;
--color-tfps08-panel: oklch(1 0 0);
--color-tfps08-ink: #0b0b12;
--color-tfps08-ink: oklch(12% .015 300);
--color-tfps08-acc: #a21caf;
--color-tfps08-acc: oklch(52% .21 328);
--color-tfps08-yes: #0f766e;
--color-tfps08-yes: oklch(48% .1 175);
--color-tfps08-no: #9f1239;
--color-tfps08-no: oklch(48% .16 12);
--color-tfps08-shade: rgb(11 11 18 / .45);
--font-tfps08-sans: 'Geist', system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tfps08-mono: 'Geist Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;
--animate-tfps08-bob: tfps08-bob 2.4s ease-in-out infinite;
@keyframes tfps08-bob {
0%,
100% {
translate: 0 -2px;
}
50% {
translate: 0 3px;
}
}
}
/* Theme state. Dark = OS preference XOR the toggle, so the switch always flips what you see —
the CSS original's checkbox did nothing for visitors whose OS was already dark. */
@custom-variant tfps08-dark {
@media (prefers-color-scheme: light) {
&:where(.tfps-08:has(#tfps-08-theme:checked), .tfps-08:has(#tfps-08-theme:checked) *) {
@slot;
}
}
@media (prefers-color-scheme: dark) {
&:where(.tfps-08:not(:has(#tfps-08-theme:checked)), .tfps-08:not(:has(#tfps-08-theme:checked)) *) {
@slot;
}
}
}
/* Root rule: the full-bleed contract, stated before the browser build compiles. */
.tfps-08 {
width: 100%;
min-height: 100svh;
@variant tfps08-dark {
--color-tfps08-bg: #070709;
--color-tfps08-bg: oklch(0.13 0.005 285.7);
--color-tfps08-panel: #0d0d13;
--color-tfps08-panel: oklch(0.162 0.013 284.9);
--color-tfps08-ink: #f3f2f8;
--color-tfps08-ink: oklch(0.964 0.008 293.9);
--color-tfps08-acc: #f0abfc;
--color-tfps08-acc: oklch(0.833 0.132 321.4);
--color-tfps08-yes: #5eead4;
--color-tfps08-yes: oklch(0.855 0.125 181.1);
--color-tfps08-no: #fda4af;
--color-tfps08-no: oklch(0.81 0.106 11.6);
--color-tfps08-shade: rgb(0 0 0 / .9);
}
}
/* What stayed CSS: the reveal. One @utility keeps shorthand, timeline and range in order. */
@keyframes tfps08-reveal {
0% {
scale: .92;
opacity: .3;
}
44%,
56% {
scale: 1;
opacity: 1;
}
100% {
scale: .92;
opacity: .3;
}
}
@utility tfps08-reveal {
@supports (animation-timeline: view()) {
@media (prefers-reduced-motion: no-preference) {
animation: tfps08-reveal linear both;
animation-timeline: --tfps08;
animation-range: cover 0% cover 100%;
}
}
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tfps08-bg: #eceef2;
--color-tfps08-bg: oklch(93.5% .006 300);
--color-tfps08-panel: #ffffff;
--color-tfps08-panel: oklch(1 0 0);
--color-tfps08-ink: #0b0b12;
--color-tfps08-ink: oklch(12% .015 300);
--color-tfps08-acc: #a21caf;
--color-tfps08-acc: oklch(52% .21 328);
--color-tfps08-yes: #0f766e;
--color-tfps08-yes: oklch(48% .1 175);
--color-tfps08-no: #9f1239;
--color-tfps08-no: oklch(48% .16 12);
--color-tfps08-shade: rgb(11 11 18 / .45);
--font-tfps08-sans: 'Geist', system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tfps08-mono: 'Geist Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;
--animate-tfps08-bob: tfps08-bob 2.4s ease-in-out infinite;
@keyframes tfps08-bob {
0%,
100% {
translate: 0 -2px;
}
50% {
translate: 0 3px;
}
}
}
/* Theme state. Dark = OS preference XOR the toggle, so the switch always flips what you see —
the CSS original's checkbox did nothing for visitors whose OS was already dark. */
@custom-variant tfps08-dark {
@media (prefers-color-scheme: light) {
&:where(.tfps-08:has(#tfps-08-theme:checked), .tfps-08:has(#tfps-08-theme:checked) *) {
@slot;
}
}
@media (prefers-color-scheme: dark) {
&:where(.tfps-08:not(:has(#tfps-08-theme:checked)), .tfps-08:not(:has(#tfps-08-theme:checked)) *) {
@slot;
}
}
}
/* Root rule: the full-bleed contract, stated before the browser build compiles. */
.tfps-08 {
width: 100%;
min-height: 100svh;
@variant tfps08-dark {
--color-tfps08-bg: #070709;
--color-tfps08-bg: oklch(0.13 0.005 285.7);
--color-tfps08-panel: #0d0d13;
--color-tfps08-panel: oklch(0.162 0.013 284.9);
--color-tfps08-ink: #f3f2f8;
--color-tfps08-ink: oklch(0.964 0.008 293.9);
--color-tfps08-acc: #f0abfc;
--color-tfps08-acc: oklch(0.833 0.132 321.4);
--color-tfps08-yes: #5eead4;
--color-tfps08-yes: oklch(0.855 0.125 181.1);
--color-tfps08-no: #fda4af;
--color-tfps08-no: oklch(0.81 0.106 11.6);
--color-tfps08-shade: rgb(0 0 0 / .9);
}
}
/* What stayed CSS: the reveal. One @utility keeps shorthand, timeline and range in order. */
@keyframes tfps08-reveal {
0% {
scale: .92;
opacity: .3;
}
44%,
56% {
scale: 1;
opacity: 1;
}
100% {
scale: .92;
opacity: .3;
}
}
@utility tfps08-reveal {
@supports (animation-timeline: view()) {
@media (prefers-reduced-motion: no-preference) {
animation: tfps08-reveal linear both;
animation-timeline: --tfps08;
animation-range: cover 0% cover 100%;
}
}
}@import "tailwindcss";
@theme {
--color-tfps08-bg: #eceef2;
--color-tfps08-bg: oklch(93.5% .006 300);
--color-tfps08-panel: #ffffff;
--color-tfps08-panel: oklch(1 0 0);
--color-tfps08-ink: #0b0b12;
--color-tfps08-ink: oklch(12% .015 300);
--color-tfps08-acc: #a21caf;
--color-tfps08-acc: oklch(52% .21 328);
--color-tfps08-yes: #0f766e;
--color-tfps08-yes: oklch(48% .1 175);
--color-tfps08-no: #9f1239;
--color-tfps08-no: oklch(48% .16 12);
--color-tfps08-shade: rgb(11 11 18 / .45);
--font-tfps08-sans: 'Geist', system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tfps08-mono: 'Geist Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;
--animate-tfps08-bob: tfps08-bob 2.4s ease-in-out infinite;
@keyframes tfps08-bob {
0%,
100% {
translate: 0 -2px;
}
50% {
translate: 0 3px;
}
}
}
/* Theme state. Dark = OS preference XOR the toggle, so the switch always flips what you see —
the CSS original's checkbox did nothing for visitors whose OS was already dark. */
@custom-variant tfps08-dark {
@media (prefers-color-scheme: light) {
&:where(.tfps-08:has(#tfps-08-theme:checked), .tfps-08:has(#tfps-08-theme:checked) *) {
@slot;
}
}
@media (prefers-color-scheme: dark) {
&:where(.tfps-08:not(:has(#tfps-08-theme:checked)), .tfps-08:not(:has(#tfps-08-theme:checked)) *) {
@slot;
}
}
}
/* Root rule: the full-bleed contract, stated before the browser build compiles. */
.tfps-08 {
width: 100%;
min-height: 100svh;
@variant tfps08-dark {
--color-tfps08-bg: #070709;
--color-tfps08-bg: oklch(0.13 0.005 285.7);
--color-tfps08-panel: #0d0d13;
--color-tfps08-panel: oklch(0.162 0.013 284.9);
--color-tfps08-ink: #f3f2f8;
--color-tfps08-ink: oklch(0.964 0.008 293.9);
--color-tfps08-acc: #f0abfc;
--color-tfps08-acc: oklch(0.833 0.132 321.4);
--color-tfps08-yes: #5eead4;
--color-tfps08-yes: oklch(0.855 0.125 181.1);
--color-tfps08-no: #fda4af;
--color-tfps08-no: oklch(0.81 0.106 11.6);
--color-tfps08-shade: rgb(0 0 0 / .9);
}
}
/* What stayed CSS: the reveal. One @utility keeps shorthand, timeline and range in order. */
@keyframes tfps08-reveal {
0% {
scale: .92;
opacity: .3;
}
44%,
56% {
scale: 1;
opacity: 1;
}
100% {
scale: .92;
opacity: .3;
}
}
@utility tfps08-reveal {
@supports (animation-timeline: view()) {
@media (prefers-reduced-motion: no-preference) {
animation: tfps08-reveal linear both;
animation-timeline: --tfps08;
animation-range: cover 0% cover 100%;
}
}
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400..700&family=Geist+Mono:wght@400;500&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400..700&family=Geist+Mono:wght@400;500&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The sheet's hsl/oklch
@supportsgradient pair and its dark override became two arbitrarybg-[radial-gradient(…)]utilities. - The
@supports (animation-timeline:view())wrapper moved inside@utility tfps08-reveal, with the reduced-motion gate. - Consistency fix over the CSS original: its keyframes also animated
border-radius— a paint property, contradicting its own "compositor only" panel. The port animatesscaleandopacityonly. - UI upgrade: each sheet carries a giant hue-tinted index numeral, so the sequence reads as a numbered set even mid-transition, and the scroll cue is a real link.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Full Page Section Reveal and Scale on Scroll
Source: https://codefronts.com/layouts/tailwind-css-full-page-sections/tailwind-full-page-section-reveal-and-scale-on-scroll/
Each incoming sheet rises from 92% scale and 30% opacity to full size exactly as it snaps into place, holds perfectly still, then recedes the same way on the way out — one keyframe set, one tfps08-reveal utility, both halves of the transition. Only scale and opacity animate, and an oversized hue-tinted numeral gives every sheet depth.
## HTML
```html
<div class="tfps-08 relative isolate w-full min-h-svh bg-tfps08-bg font-tfps08-sans text-tfps08-ink antialiased [&_code]:font-tfps08-mono [&_code]:text-[.92em]">
<input class="peer sr-only" type="checkbox" id="tfps-08-theme">
<label for="tfps-08-theme" class="fixed top-[1.15rem] right-[1.15rem] z-80 grid size-11 cursor-pointer place-items-center rounded-full border border-tfps08-ink/10 bg-white/60 text-tfps08-ink hover:bg-white/85 tfps08-dark:bg-white/[.07] tfps08-dark:hover:bg-white/15 shadow-[0_8px_24px_-12px_var(--color-tfps08-shade)] backdrop-blur-[10px] backdrop-saturate-150 transition-[background-color,scale] duration-200 active:scale-95 peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-tfps08-acc forced-colors:border-[CanvasText]">
<svg class="size-[1.05rem] tfps08-dark:hidden" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M12 2.6v2.3M12 19.1v2.3M2.6 12h2.3M19.1 12h2.3M5.3 5.3l1.7 1.7M17 17l1.7 1.7M18.7 5.3L17 7M7 17l-1.7 1.7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/><circle cx="12" cy="12" r="4.4" stroke="currentColor" stroke-width="1.7"/></svg>
<svg class="hidden size-[1.05rem] tfps08-dark:block" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M20.5 14.4A8.4 8.4 0 0 1 9.6 3.5 8.6 8.6 0 1 0 20.5 14.4Z" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="sr-only">Switch light or dark theme</span>
</label>
<div class="h-dvh overflow-x-hidden overflow-y-auto snap-y snap-mandatory scroll-smooth overscroll-y-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfps08-acc [@media(max-height:40rem)]:snap-proximity motion-reduce:snap-none motion-reduce:scroll-auto" tabindex="0" role="group" aria-label="Reveal and scale sections, five panels">
<section class="[--h:300] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s1" aria-labelledby="tfps-08-h1">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">01</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)] transition-[opacity,translate] duration-700 ease-out starting:translate-y-4 starting:opacity-0 motion-reduce:transition-none">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">01 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">scale .92 → 1</span></p>
<h2 class="max-w-[20ch] text-[clamp(2rem,5.6vw,4rem)] leading-[1.02] font-semibold tracking-[-.042em] text-balance" id="tfps-08-h1">Every panel arrives by growing into place</h2>
<p class="max-w-[58ch] text-[clamp(1rem,1.4vw,1.15rem)] leading-[1.62] text-pretty text-tfps08-ink/75 [&_code]:text-tfps08-ink">One keyframe set drives both halves of the transition: the incoming sheet rises from 92% and 30% opacity, holds perfectly still while it is snapped, then recedes the same way as it leaves.</p>
<a href="#tfps-08-s2" class="inline-flex min-h-11 items-center gap-2.5 rounded-full pe-3 font-tfps08-mono text-[.82rem] text-tfps08-ink/75 no-underline transition-colors hover:text-tfps08-acc focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tfps08-acc"><svg class="size-[1.1rem] animate-tfps08-bob motion-reduce:animate-none" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M12 4.5v13.5M6.5 12.6 12 18.2l5.5-5.6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>Scroll — the next sheet is already growing</a>
</div>
</div>
</section>
<section class="[--h:255] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s2" aria-labelledby="tfps-08-h2">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">02</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)]">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">02 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">one range, two directions</span></p>
<h2 class="max-w-[26ch] text-[clamp(1.7rem,3.7vw,2.7rem)] leading-[1.06] font-semibold tracking-[-.032em] text-balance" id="tfps-08-h2">The cover range already knows the whole journey</h2>
<pre class="w-full overflow-x-auto overflow-y-hidden rounded-2xl border border-tfps08-ink/10 bg-tfps08-ink/[.04] px-[1.1rem] py-4"><code class="text-[clamp(.78rem,1vw,.88rem)] leading-[1.75] whitespace-pre text-tfps08-ink/75 [&_mark]:rounded-[.25rem] [&_mark]:bg-tfps08-acc/15 [&_mark]:px-[.2rem] [&_mark]:text-tfps08-acc">@keyframes tfps08-reveal {
0% { scale: .92; opacity: .3 }
<mark>44%, 56% { scale: 1; opacity: 1 }</mark>
100% { scale: .92; opacity: .3 }
}
<div class="<mark>tfps08-reveal</mark>"> <!-- cover 0% → cover 100% --></code></pre>
<p class="max-w-[58ch] text-[clamp(1rem,1.4vw,1.15rem)] leading-[1.62] text-pretty text-tfps08-ink/75 [&_code]:text-tfps08-ink">No entry animation fighting an exit animation, and no JavaScript deciding which one owns the middle of the screen.</p>
</div>
</div>
</section>
<section class="[--h:210] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s3" aria-labelledby="tfps-08-h3">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">03</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)]">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">03 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">the plateau</span></p>
<h2 class="max-w-[26ch] text-[clamp(1.7rem,3.7vw,2.7rem)] leading-[1.06] font-semibold tracking-[-.032em] text-balance" id="tfps-08-h3">Twelve percent of dead zone in the middle</h2>
<p class="max-w-[58ch] text-[clamp(1rem,1.4vw,1.15rem)] leading-[1.62] text-pretty text-tfps08-ink/75 [&_code]:text-tfps08-ink">Hold full scale from 44% to 56% of the range and a snapped panel is visually locked. Remove the hold and the sheet breathes on every tiny trackpad nudge — the single most common reason this effect feels cheap.</p>
<ul class="grid w-full gap-1">
<li class="grid grid-cols-[6.5rem_1fr] items-baseline gap-4 border-t border-tfps08-ink/10 py-3"><strong class="font-tfps08-mono text-[.82rem] font-medium text-tfps08-acc tabular-nums">0–44%</strong><span class="text-[.95rem] text-tfps08-ink/75">entering — scale and opacity rise</span></li>
<li class="grid grid-cols-[6.5rem_1fr] items-baseline gap-4 border-t border-tfps08-ink/10 py-3"><strong class="font-tfps08-mono text-[.82rem] font-medium text-tfps08-acc tabular-nums">44–56%</strong><span class="text-[.95rem] text-tfps08-ink/75">snapped — nothing moves</span></li>
<li class="grid grid-cols-[6.5rem_1fr] items-baseline gap-4 border-t border-tfps08-ink/10 py-3"><strong class="font-tfps08-mono text-[.82rem] font-medium text-tfps08-acc tabular-nums">56–100%</strong><span class="text-[.95rem] text-tfps08-ink/75">leaving — recedes symmetrically</span></li>
</ul>
</div>
</div>
</section>
<section class="[--h:165] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s4" aria-labelledby="tfps-08-h4">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">04</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)]">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">04 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">compositor only</span></p>
<h2 class="max-w-[26ch] text-[clamp(1.7rem,3.7vw,2.7rem)] leading-[1.06] font-semibold tracking-[-.032em] text-balance" id="tfps-08-h4">Two cheap properties, zero expensive ones</h2>
<div class="grid w-full grid-cols-[repeat(auto-fit,minmax(12rem,1fr))] gap-[clamp(.8rem,2vw,1.4rem)]">
<div class="grid content-start gap-3 rounded-[1.15rem] border border-tfps08-ink/10 bg-tfps08-ink/[.03] p-[clamp(1rem,2.2vw,1.4rem)]"><p class="font-tfps08-mono text-[.68rem] tracking-[.12em] uppercase text-tfps08-yes">Free on the compositor</p><ul class="grid gap-2 font-tfps08-mono text-[.87rem] text-tfps08-ink/75"><li>scale</li><li>opacity</li><li>translate</li><li>rotate</li></ul></div>
<div class="grid content-start gap-3 rounded-[1.15rem] border border-tfps08-ink/10 bg-tfps08-ink/[.03] p-[clamp(1rem,2.2vw,1.4rem)]"><p class="font-tfps08-mono text-[.68rem] tracking-[.12em] uppercase text-tfps08-no">Repaints every frame</p><ul class="grid gap-2 font-tfps08-mono text-[.87rem] text-tfps08-ink/75"><li>filter: blur()</li><li>width / height</li><li>top / left</li><li>box-shadow, border-radius</li></ul></div>
</div>
</div>
</div>
</section>
<section class="[--h:120] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s5" aria-labelledby="tfps-08-h5">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">05</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)]">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">05 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">pick your floor</span></p>
<h2 class="max-w-[26ch] text-[clamp(1.7rem,3.7vw,2.7rem)] leading-[1.06] font-semibold tracking-[-.032em] text-balance" id="tfps-08-h5">Why 0.92 and not 0.8</h2>
<p class="max-w-[58ch] text-[clamp(1rem,1.4vw,1.15rem)] leading-[1.62] text-pretty text-tfps08-ink/75 [&_code]:text-tfps08-ink">At <code>.92</code> the displacement reads as depth while body copy stays legible the whole way in. Below roughly <code>.85</code> headlines visibly reflow, the effect starts to feel like a slideshow, and motion-sensitive readers notice immediately.</p>
<figure class="grid max-w-[46ch] gap-2.5 border-s-2 border-tfps08-acc ps-[1.1rem]">
<blockquote class="text-[clamp(1.05rem,1.9vw,1.35rem)] leading-[1.45] tracking-[-.02em] text-pretty"><p>“We shipped the reveal at .92 with a held middle. Nobody described it as an animation — they described the page as feeling fast.”</p></blockquote>
<figcaption class="font-tfps08-mono text-[.68rem] tracking-[.1em] uppercase text-tfps08-ink/70">Sam Rivera · product designer</figcaption>
</figure>
</div>
</div>
</section>
</div>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tfps08-bg: #eceef2;
--color-tfps08-bg: oklch(93.5% .006 300);
--color-tfps08-panel: #ffffff;
--color-tfps08-panel: oklch(1 0 0);
--color-tfps08-ink: #0b0b12;
--color-tfps08-ink: oklch(12% .015 300);
--color-tfps08-acc: #a21caf;
--color-tfps08-acc: oklch(52% .21 328);
--color-tfps08-yes: #0f766e;
--color-tfps08-yes: oklch(48% .1 175);
--color-tfps08-no: #9f1239;
--color-tfps08-no: oklch(48% .16 12);
--color-tfps08-shade: rgb(11 11 18 / .45);
--font-tfps08-sans: 'Geist', system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tfps08-mono: 'Geist Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;
--animate-tfps08-bob: tfps08-bob 2.4s ease-in-out infinite;
@keyframes tfps08-bob {
0%,
100% {
translate: 0 -2px;
}
50% {
translate: 0 3px;
}
}
}
/* Theme state. Dark = OS preference XOR the toggle, so the switch always flips what you see —
the CSS original's checkbox did nothing for visitors whose OS was already dark. */
@custom-variant tfps08-dark {
@media (prefers-color-scheme: light) {
&:where(.tfps-08:has(#tfps-08-theme:checked), .tfps-08:has(#tfps-08-theme:checked) *) {
@slot;
}
}
@media (prefers-color-scheme: dark) {
&:where(.tfps-08:not(:has(#tfps-08-theme:checked)), .tfps-08:not(:has(#tfps-08-theme:checked)) *) {
@slot;
}
}
}
/* Root rule: the full-bleed contract, stated before the browser build compiles. */
.tfps-08 {
width: 100%;
min-height: 100svh;
@variant tfps08-dark {
--color-tfps08-bg: #070709;
--color-tfps08-bg: oklch(0.13 0.005 285.7);
--color-tfps08-panel: #0d0d13;
--color-tfps08-panel: oklch(0.162 0.013 284.9);
--color-tfps08-ink: #f3f2f8;
--color-tfps08-ink: oklch(0.964 0.008 293.9);
--color-tfps08-acc: #f0abfc;
--color-tfps08-acc: oklch(0.833 0.132 321.4);
--color-tfps08-yes: #5eead4;
--color-tfps08-yes: oklch(0.855 0.125 181.1);
--color-tfps08-no: #fda4af;
--color-tfps08-no: oklch(0.81 0.106 11.6);
--color-tfps08-shade: rgb(0 0 0 / .9);
}
}
/* What stayed CSS: the reveal. One @utility keeps shorthand, timeline and range in order. */
@keyframes tfps08-reveal {
0% {
scale: .92;
opacity: .3;
}
44%,
56% {
scale: 1;
opacity: 1;
}
100% {
scale: .92;
opacity: .3;
}
}
@utility tfps08-reveal {
@supports (animation-timeline: view()) {
@media (prefers-reduced-motion: no-preference) {
animation: tfps08-reveal linear both;
animation-timeline: --tfps08;
animation-range: cover 0% cover 100%;
}
}
}
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Full Page Section Reveal and Scale on Scroll
Source: https://codefronts.com/layouts/tailwind-css-full-page-sections/tailwind-full-page-section-reveal-and-scale-on-scroll/
Each incoming sheet rises from 92% scale and 30% opacity to full size exactly as it snaps into place, holds perfectly still, then recedes the same way on the way out — one keyframe set, one tfps08-reveal utility, both halves of the transition. Only scale and opacity animate, and an oversized hue-tinted numeral gives every sheet depth.
## HTML
```html
<div class="tfps-08 relative isolate w-full min-h-svh bg-tfps08-bg font-tfps08-sans text-tfps08-ink antialiased [&_code]:font-tfps08-mono [&_code]:text-[.92em]">
<input class="peer sr-only" type="checkbox" id="tfps-08-theme">
<label for="tfps-08-theme" class="fixed top-[1.15rem] right-[1.15rem] z-80 grid size-11 cursor-pointer place-items-center rounded-full border border-tfps08-ink/10 bg-white/60 text-tfps08-ink hover:bg-white/85 tfps08-dark:bg-white/[.07] tfps08-dark:hover:bg-white/15 shadow-[0_8px_24px_-12px_var(--color-tfps08-shade)] backdrop-blur-[10px] backdrop-saturate-150 transition-[background-color,scale] duration-200 active:scale-95 peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-tfps08-acc forced-colors:border-[CanvasText]">
<svg class="size-[1.05rem] tfps08-dark:hidden" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M12 2.6v2.3M12 19.1v2.3M2.6 12h2.3M19.1 12h2.3M5.3 5.3l1.7 1.7M17 17l1.7 1.7M18.7 5.3L17 7M7 17l-1.7 1.7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/><circle cx="12" cy="12" r="4.4" stroke="currentColor" stroke-width="1.7"/></svg>
<svg class="hidden size-[1.05rem] tfps08-dark:block" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M20.5 14.4A8.4 8.4 0 0 1 9.6 3.5 8.6 8.6 0 1 0 20.5 14.4Z" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
<span class="sr-only">Switch light or dark theme</span>
</label>
<div class="h-dvh overflow-x-hidden overflow-y-auto snap-y snap-mandatory scroll-smooth overscroll-y-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfps08-acc [@media(max-height:40rem)]:snap-proximity motion-reduce:snap-none motion-reduce:scroll-auto" tabindex="0" role="group" aria-label="Reveal and scale sections, five panels">
<section class="[--h:300] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s1" aria-labelledby="tfps-08-h1">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">01</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)] transition-[opacity,translate] duration-700 ease-out starting:translate-y-4 starting:opacity-0 motion-reduce:transition-none">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">01 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">scale .92 → 1</span></p>
<h2 class="max-w-[20ch] text-[clamp(2rem,5.6vw,4rem)] leading-[1.02] font-semibold tracking-[-.042em] text-balance" id="tfps-08-h1">Every panel arrives by growing into place</h2>
<p class="max-w-[58ch] text-[clamp(1rem,1.4vw,1.15rem)] leading-[1.62] text-pretty text-tfps08-ink/75 [&_code]:text-tfps08-ink">One keyframe set drives both halves of the transition: the incoming sheet rises from 92% and 30% opacity, holds perfectly still while it is snapped, then recedes the same way as it leaves.</p>
<a href="#tfps-08-s2" class="inline-flex min-h-11 items-center gap-2.5 rounded-full pe-3 font-tfps08-mono text-[.82rem] text-tfps08-ink/75 no-underline transition-colors hover:text-tfps08-acc focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tfps08-acc"><svg class="size-[1.1rem] animate-tfps08-bob motion-reduce:animate-none" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><path d="M12 4.5v13.5M6.5 12.6 12 18.2l5.5-5.6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>Scroll — the next sheet is already growing</a>
</div>
</div>
</section>
<section class="[--h:255] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s2" aria-labelledby="tfps-08-h2">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">02</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)]">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">02 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">one range, two directions</span></p>
<h2 class="max-w-[26ch] text-[clamp(1.7rem,3.7vw,2.7rem)] leading-[1.06] font-semibold tracking-[-.032em] text-balance" id="tfps-08-h2">The cover range already knows the whole journey</h2>
<pre class="w-full overflow-x-auto overflow-y-hidden rounded-2xl border border-tfps08-ink/10 bg-tfps08-ink/[.04] px-[1.1rem] py-4"><code class="text-[clamp(.78rem,1vw,.88rem)] leading-[1.75] whitespace-pre text-tfps08-ink/75 [&_mark]:rounded-[.25rem] [&_mark]:bg-tfps08-acc/15 [&_mark]:px-[.2rem] [&_mark]:text-tfps08-acc">@keyframes tfps08-reveal {
0% { scale: .92; opacity: .3 }
<mark>44%, 56% { scale: 1; opacity: 1 }</mark>
100% { scale: .92; opacity: .3 }
}
<div class="<mark>tfps08-reveal</mark>"> <!-- cover 0% → cover 100% --></code></pre>
<p class="max-w-[58ch] text-[clamp(1rem,1.4vw,1.15rem)] leading-[1.62] text-pretty text-tfps08-ink/75 [&_code]:text-tfps08-ink">No entry animation fighting an exit animation, and no JavaScript deciding which one owns the middle of the screen.</p>
</div>
</div>
</section>
<section class="[--h:210] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s3" aria-labelledby="tfps-08-h3">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">03</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)]">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">03 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">the plateau</span></p>
<h2 class="max-w-[26ch] text-[clamp(1.7rem,3.7vw,2.7rem)] leading-[1.06] font-semibold tracking-[-.032em] text-balance" id="tfps-08-h3">Twelve percent of dead zone in the middle</h2>
<p class="max-w-[58ch] text-[clamp(1rem,1.4vw,1.15rem)] leading-[1.62] text-pretty text-tfps08-ink/75 [&_code]:text-tfps08-ink">Hold full scale from 44% to 56% of the range and a snapped panel is visually locked. Remove the hold and the sheet breathes on every tiny trackpad nudge — the single most common reason this effect feels cheap.</p>
<ul class="grid w-full gap-1">
<li class="grid grid-cols-[6.5rem_1fr] items-baseline gap-4 border-t border-tfps08-ink/10 py-3"><strong class="font-tfps08-mono text-[.82rem] font-medium text-tfps08-acc tabular-nums">0–44%</strong><span class="text-[.95rem] text-tfps08-ink/75">entering — scale and opacity rise</span></li>
<li class="grid grid-cols-[6.5rem_1fr] items-baseline gap-4 border-t border-tfps08-ink/10 py-3"><strong class="font-tfps08-mono text-[.82rem] font-medium text-tfps08-acc tabular-nums">44–56%</strong><span class="text-[.95rem] text-tfps08-ink/75">snapped — nothing moves</span></li>
<li class="grid grid-cols-[6.5rem_1fr] items-baseline gap-4 border-t border-tfps08-ink/10 py-3"><strong class="font-tfps08-mono text-[.82rem] font-medium text-tfps08-acc tabular-nums">56–100%</strong><span class="text-[.95rem] text-tfps08-ink/75">leaving — recedes symmetrically</span></li>
</ul>
</div>
</div>
</section>
<section class="[--h:165] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s4" aria-labelledby="tfps-08-h4">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">04</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)]">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">04 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">compositor only</span></p>
<h2 class="max-w-[26ch] text-[clamp(1.7rem,3.7vw,2.7rem)] leading-[1.06] font-semibold tracking-[-.032em] text-balance" id="tfps-08-h4">Two cheap properties, zero expensive ones</h2>
<div class="grid w-full grid-cols-[repeat(auto-fit,minmax(12rem,1fr))] gap-[clamp(.8rem,2vw,1.4rem)]">
<div class="grid content-start gap-3 rounded-[1.15rem] border border-tfps08-ink/10 bg-tfps08-ink/[.03] p-[clamp(1rem,2.2vw,1.4rem)]"><p class="font-tfps08-mono text-[.68rem] tracking-[.12em] uppercase text-tfps08-yes">Free on the compositor</p><ul class="grid gap-2 font-tfps08-mono text-[.87rem] text-tfps08-ink/75"><li>scale</li><li>opacity</li><li>translate</li><li>rotate</li></ul></div>
<div class="grid content-start gap-3 rounded-[1.15rem] border border-tfps08-ink/10 bg-tfps08-ink/[.03] p-[clamp(1rem,2.2vw,1.4rem)]"><p class="font-tfps08-mono text-[.68rem] tracking-[.12em] uppercase text-tfps08-no">Repaints every frame</p><ul class="grid gap-2 font-tfps08-mono text-[.87rem] text-tfps08-ink/75"><li>filter: blur()</li><li>width / height</li><li>top / left</li><li>box-shadow, border-radius</li></ul></div>
</div>
</div>
</div>
</section>
<section class="[--h:120] [view-timeline-name:--tfps08] [view-timeline-axis:block] relative flex min-h-dvh snap-start items-center justify-center px-[clamp(.6rem,2.5vw,1.6rem)] py-[clamp(.6rem,2vh,1.4rem)]" id="tfps-08-s5" aria-labelledby="tfps-08-h5">
<div class="tfps08-reveal relative isolate flex min-h-[calc(100dvh-clamp(1.2rem,4vh,2.8rem))] w-full items-center justify-center overflow-hidden rounded-[1.75rem] border border-tfps08-ink/10 bg-tfps08-panel bg-[radial-gradient(110%_80%_at_88%_8%,oklch(92%_.07_calc(var(--h)*1deg)),transparent_62%)] px-[clamp(1.25rem,5vw,4rem)] py-[clamp(2rem,5vh,3.5rem)] shadow-[0_30px_70px_-40px_var(--color-tfps08-shade)] tfps08-dark:bg-[radial-gradient(110%_80%_at_88%_8%,oklch(28%_.08_calc(var(--h)*1deg)),transparent_62%)] forced-colors:border-[CanvasText]">
<span aria-hidden="true" class="pointer-events-none absolute right-[3%] -bottom-[.14em] -z-10 font-tfps08-mono text-[clamp(8rem,26vw,22rem)] leading-none font-medium tracking-[-.08em] text-[oklch(55%_.12_calc(var(--h)*1deg)/.1)] tfps08-dark:text-[oklch(80%_.1_calc(var(--h)*1deg)/.08)]">05</span>
<div class="grid w-full max-w-[58rem] justify-items-start gap-[clamp(.95rem,2.3vh,1.5rem)]">
<p class="flex flex-wrap items-center gap-3"><span class="font-tfps08-mono text-[.7rem] font-medium tracking-[.14em] uppercase text-tfps08-acc">05 / 05</span><span class="rounded-[.45rem] border border-tfps08-ink/12 px-2 py-[.22rem] font-tfps08-mono text-[.7rem] text-tfps08-ink/75">pick your floor</span></p>
<h2 class="max-w-[26ch] text-[clamp(1.7rem,3.7vw,2.7rem)] leading-[1.06] font-semibold tracking-[-.032em] text-balance" id="tfps-08-h5">Why 0.92 and not 0.8</h2>
<p class="max-w-[58ch] text-[clamp(1rem,1.4vw,1.15rem)] leading-[1.62] text-pretty text-tfps08-ink/75 [&_code]:text-tfps08-ink">At <code>.92</code> the displacement reads as depth while body copy stays legible the whole way in. Below roughly <code>.85</code> headlines visibly reflow, the effect starts to feel like a slideshow, and motion-sensitive readers notice immediately.</p>
<figure class="grid max-w-[46ch] gap-2.5 border-s-2 border-tfps08-acc ps-[1.1rem]">
<blockquote class="text-[clamp(1.05rem,1.9vw,1.35rem)] leading-[1.45] tracking-[-.02em] text-pretty"><p>“We shipped the reveal at .92 with a held middle. Nobody described it as an animation — they described the page as feeling fast.”</p></blockquote>
<figcaption class="font-tfps08-mono text-[.68rem] tracking-[.1em] uppercase text-tfps08-ink/70">Sam Rivera · product designer</figcaption>
</figure>
</div>
</div>
</section>
</div>
</div>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tfps08-bg: #eceef2;
--color-tfps08-bg: oklch(93.5% .006 300);
--color-tfps08-panel: #ffffff;
--color-tfps08-panel: oklch(1 0 0);
--color-tfps08-ink: #0b0b12;
--color-tfps08-ink: oklch(12% .015 300);
--color-tfps08-acc: #a21caf;
--color-tfps08-acc: oklch(52% .21 328);
--color-tfps08-yes: #0f766e;
--color-tfps08-yes: oklch(48% .1 175);
--color-tfps08-no: #9f1239;
--color-tfps08-no: oklch(48% .16 12);
--color-tfps08-shade: rgb(11 11 18 / .45);
--font-tfps08-sans: 'Geist', system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-tfps08-mono: 'Geist Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;
--animate-tfps08-bob: tfps08-bob 2.4s ease-in-out infinite;
@keyframes tfps08-bob {
0%,
100% {
translate: 0 -2px;
}
50% {
translate: 0 3px;
}
}
}
/* Theme state. Dark = OS preference XOR the toggle, so the switch always flips what you see —
the CSS original's checkbox did nothing for visitors whose OS was already dark. */
@custom-variant tfps08-dark {
@media (prefers-color-scheme: light) {
&:where(.tfps-08:has(#tfps-08-theme:checked), .tfps-08:has(#tfps-08-theme:checked) *) {
@slot;
}
}
@media (prefers-color-scheme: dark) {
&:where(.tfps-08:not(:has(#tfps-08-theme:checked)), .tfps-08:not(:has(#tfps-08-theme:checked)) *) {
@slot;
}
}
}
/* Root rule: the full-bleed contract, stated before the browser build compiles. */
.tfps-08 {
width: 100%;
min-height: 100svh;
@variant tfps08-dark {
--color-tfps08-bg: #070709;
--color-tfps08-bg: oklch(0.13 0.005 285.7);
--color-tfps08-panel: #0d0d13;
--color-tfps08-panel: oklch(0.162 0.013 284.9);
--color-tfps08-ink: #f3f2f8;
--color-tfps08-ink: oklch(0.964 0.008 293.9);
--color-tfps08-acc: #f0abfc;
--color-tfps08-acc: oklch(0.833 0.132 321.4);
--color-tfps08-yes: #5eead4;
--color-tfps08-yes: oklch(0.855 0.125 181.1);
--color-tfps08-no: #fda4af;
--color-tfps08-no: oklch(0.81 0.106 11.6);
--color-tfps08-shade: rgb(0 0 0 / .9);
}
}
/* What stayed CSS: the reveal. One @utility keeps shorthand, timeline and range in order. */
@keyframes tfps08-reveal {
0% {
scale: .92;
opacity: .3;
}
44%,
56% {
scale: 1;
opacity: 1;
}
100% {
scale: .92;
opacity: .3;
}
}
@utility tfps08-reveal {
@supports (animation-timeline: view()) {
@media (prefers-reduced-motion: no-preference) {
animation: tfps08-reveal linear both;
animation-timeline: --tfps08;
animation-range: cover 0% cover 100%;
}
}
}
```How this works
One animation, both directions. The cover range spans a panel's whole journey through the scrollport, so a symmetric keyframe set is entry and exit at once:
@keyframes tfps08-reveal {
0% { scale: .92; opacity: .3 }
44%, 56% { scale: 1; opacity: 1 }
100% { scale: .92; opacity: .3 }
}The plateau is the product. The 12% hold keeps a snapped sheet perfectly still; without it the sheet breathes on every trackpad nudge.
Markup stays declarative. The panel publishes the timeline and the sheet applies tfps08-reveal; the @supports and reduced-motion gates live inside the utility.
Make it yours
- Deeper motion: lower the floor to
scale: .88— no further, or headlines reflow visibly. - Remove the plateau (set both mid stops to
50%) for a continuous, breathing effect. - Hide the numeral by deleting the
aria-hiddenspan, or swap it for a section icon.
Gotchas — read before shipping
- Put the timeline on the panel, not the scaling sheet — a timeline on the element being scaled measures a moving box.
- Don't animate
border-radiusor shadows here; they repaint every frame. - An
overflow-hiddenscroller parent is fine, but atransformon it will re-root the fixed toggle.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 115+ (reveal) | 26+ (reveal) | 128+ (static) | 115+ (reveal) |
Without animation-timeline every sheet simply renders at full scale and opacity.