Scroll Split Card
A pinned card splits into three panels that fan out in 3D and flip, one by one, to reveal your services.
Apple-style scrollytelling: pinned media morphs between four states while text steps scroll past a progress rail.
Browser support · All evergreen browsers (position: sticky). Demo media uses color-mix().
npx shadcn@latest add https://freedesignmd.com/r/sticky-storytelling.json1/**2 * StickyStory — Apple-style scrollytelling. The media column stays pinned3 * while text steps scroll past; the active step drives a crossfade/morph4 * between media states and a progress rail.5 *6 * Props:7 * - steps: { eyebrow, title, body, stat? }[] — text column.8 * - renderMedia(active): ReactNode — return every media state; mark the one9 * matching `active` (e.g. data-active) and crossfade with CSS. The media10 * root also receives `--fd-sst-p` (0–1 across the story) and `--fd-sst-k`11 * (fractional step index) every frame for continuous, scrubbed motion.12 * - header: optional ReactNode rendered above the grid.13 * - scrollContainer: optional ref to a scrolling element; otherwise the14 * nearest scrollable ancestor or the window.15 * CSS vars: --fd-sst-accent, --fd-sst-bg, --fd-sst-ink.16 *17 * Browser support: evergreen (position: sticky). Under 760px the media pins18 * to the top half and steps scroll beneath it.19 * Accessibility: all copy is real, in-order text. The active step is mirrored20 * with aria-current. Reduced motion keeps crossfades at 120ms and removes21 * scale and drift.22 */23import { useEffect, useRef, useState } from "react";24import type { CSSProperties, ReactNode, RefObject } from "react";25 26export type StoryStep = { eyebrow: string; title: string; body: string; stat?: { value: string; label: string } };27 28export interface StickyStoryProps {29 steps: StoryStep[];30 renderMedia: (active: number) => ReactNode;31 scrollContainer?: RefObject<HTMLElement | null>;32 header?: ReactNode;33 className?: string;34 style?: CSSProperties;35}36 37const clamp = (v: number, a = 0, b = 1) => (v < a ? a : v > b ? b : v);38 39function findScrollParent(el: HTMLElement): HTMLElement | null {40 let p = el.parentElement;41 while (p && p !== document.body && p !== document.documentElement) {42 const oy = getComputedStyle(p).overflowY;43 if ((oy === "auto" || oy === "scroll" || oy === "overlay") && p.scrollHeight > p.clientHeight + 1) return p;44 p = p.parentElement;45 }46 return null;47}48 49export function StickyStory({ steps, renderMedia, scrollContainer, header, className, style }: StickyStoryProps) {50 const root = useRef<HTMLElement>(null);51 const media = useRef<HTMLDivElement>(null);52 const stepEls = useRef<(HTMLElement | null)[]>([]);53 const fill = useRef<HTMLSpanElement>(null);54 const [active, setActive] = useState(0);55 56 useEffect(() => {57 const el = root.current;58 const md = media.current;59 if (!el || !md) return;60 const sc = scrollContainer?.current ?? findScrollParent(el);61 const target: HTMLElement | Window = sc ?? window;62 let raf = 0;63 let cur = -1;64 let visible = true;65 66 const frame = () => {67 raf = 0;68 // read phase69 const vh = sc ? sc.clientHeight : window.innerHeight;70 const top0 = sc ? sc.getBoundingClientRect().top : 0;71 const line = top0 + vh * 0.55;72 const rects = stepEls.current.map((s) => (s ? s.getBoundingClientRect() : null));73 let k = 0;74 for (let i = 0; i < rects.length; i++) {75 const r = rects[i];76 if (!r) continue;77 const mid = r.top + r.height / 2;78 if (mid < line) k = i;79 }80 // fractional index for continuous morphs81 const first = rects[0];82 const last = rects[rects.length - 1];83 let p = 0;84 if (first && last) {85 const a = first.top + first.height / 2;86 const b = last.top + last.height / 2;87 p = clamp((line - a) / (b - a || 1));88 }89 // write phase90 md.style.setProperty("--fd-sst-p", p.toFixed(4));91 md.style.setProperty("--fd-sst-k", (p * (steps.length - 1)).toFixed(4));92 if (fill.current) fill.current.style.transform = `scaleY(${p.toFixed(4)})`;93 if (k !== cur) {94 cur = k;95 setActive(k);96 }97 };98 const kick = () => {99 if (!raf && visible) raf = requestAnimationFrame(frame);100 };101 const io = new IntersectionObserver(102 ([e]) => {103 visible = e.isIntersecting;104 if (visible) kick();105 },106 { root: sc ?? null },107 );108 io.observe(el);109 frame();110 target.addEventListener("scroll", kick, { passive: true });111 window.addEventListener("resize", kick);112 return () => {113 io.disconnect();114 target.removeEventListener("scroll", kick);115 window.removeEventListener("resize", kick);116 if (raf) cancelAnimationFrame(raf);117 };118 }, [scrollContainer, steps.length]);119 120 return (121 <section ref={root} className={`fd-sst${className ? " " + className : ""}`} style={style}>122 <style>{css}</style>123 {header}124 <div className="fd-sst-grid">125 <div className="fd-sst-pin">126 <div ref={media} className="fd-sst-media" data-active={active}>127 {renderMedia(active)}128 </div>129 <ol className="fd-sst-dots" aria-hidden="true">130 {steps.map((s, i) => (131 <li key={s.title} className="fd-sst-dot" data-on={i === active ? "" : undefined}>132 <span className="fd-sst-dot-n">{String(i + 1).padStart(2, "0")}</span>133 <span className="fd-sst-dot-l">{s.eyebrow}</span>134 </li>135 ))}136 </ol>137 </div>138 <div className="fd-sst-text">139 <span className="fd-sst-rail" aria-hidden="true">140 <span ref={fill} className="fd-sst-fill" />141 </span>142 {steps.map((s, i) => (143 <article144 key={s.title}145 ref={(n) => {146 stepEls.current[i] = n;147 }}148 className="fd-sst-step"149 data-on={i === active ? "" : undefined}150 aria-current={i === active ? "step" : undefined}151 >152 <span className="fd-sst-knot" aria-hidden="true" />153 <p className="fd-sst-eyebrow">154 <span className="fd-sst-n">{String(i + 1).padStart(2, "0")}</span>155 {s.eyebrow}156 </p>157 <h3 className="fd-sst-title">{s.title}</h3>158 <p className="fd-sst-body">{s.body}</p>159 {s.stat && (160 <p className="fd-sst-stat">161 <span className="fd-sst-stat-v">{s.stat.value}</span>162 <span className="fd-sst-stat-l">{s.stat.label}</span>163 </p>164 )}165 </article>166 ))}167 </div>168 </div>169 </section>170 );171}172 173const css = `174.fd-sst{175 --fd-ease-out:cubic-bezier(0.22,1,0.36,1);--fd-ease-in-out:cubic-bezier(0.65,0,0.35,1);176 --fd-spring:cubic-bezier(0.34,1.56,0.64,1);177 --fd-dur-1:120ms;--fd-dur-2:240ms;--fd-dur-3:420ms;--fd-dur-4:700ms;178 --fd-sst-accent:#0E9F8E;--fd-sst-bg:#F4F6F5;--fd-sst-ink:#0B1412;--fd-sst-muted:#5D6B67;179 --fd-sst-sans:ui-sans-serif,-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",Roboto,sans-serif;180 --fd-sst-display:"SF Pro Display",ui-sans-serif,-apple-system,BlinkMacSystemFont,"Inter Display","Inter","Segoe UI",Roboto,sans-serif;181 --fd-sst-mono:ui-monospace,"SF Mono","JetBrains Mono",Menlo,monospace;182 position:relative;display:block;margin:0;padding:0;background:var(--fd-sst-bg);color:var(--fd-sst-ink);183}184.fd-sst,.fd-sst *,.fd-sst *::before,.fd-sst *::after{box-sizing:border-box}185.fd-sst-grid{186 display:grid;grid-template-columns:minmax(0,1.08fr) minmax(0,.92fr);gap:clamp(32px,6vw,96px);187 max-width:1240px;margin:0 auto;padding:0 clamp(16px,4vw,56px);188}189.fd-sst-pin{190 position:sticky;top:0;height:100vh;height:100svh;display:flex;flex-direction:column;justify-content:center;gap:20px;margin:0;padding:24px 0;191}192.fd-sst-media{193 --fd-sst-p:0;--fd-sst-k:0;194 position:relative;width:100%;aspect-ratio:1/1;max-height:72vh;margin:0 auto;border-radius:28px;overflow:hidden;195 background:#0B1412;box-shadow:0 40px 80px -40px rgba(11,20,18,.45),0 0 0 1px rgba(11,20,18,.06);196}197.fd-sst-dots{display:flex;gap:6px;list-style:none;margin:0;padding:0;justify-content:center}198.fd-sst-dot{199 display:inline-flex;align-items:center;gap:6px;height:28px;margin:0;padding:0 12px;border-radius:9999px;200 color:var(--fd-sst-muted);background:transparent;border:1px solid rgba(11,20,18,.1);201 font:500 0.75rem/1 var(--fd-sst-sans);letter-spacing:0;white-space:nowrap;202 transition:background var(--fd-dur-2) var(--fd-ease-out),color var(--fd-dur-2) var(--fd-ease-out),border-color var(--fd-dur-2) var(--fd-ease-out);203}204.fd-sst-dot-n{font:500 0.6875rem/1 var(--fd-sst-mono)}205.fd-sst-dot[data-on]{background:var(--fd-sst-ink);color:#fff;border-color:var(--fd-sst-ink)}206 207.fd-sst-text{position:relative;margin:0;padding:22vh 0 30vh 36px}208.fd-sst-rail{position:absolute;left:5px;top:22vh;bottom:30vh;width:2px;border-radius:9999px;background:rgba(11,20,18,.08);overflow:hidden}209.fd-sst-fill{position:absolute;inset:0;display:block;background:var(--fd-sst-accent);transform:scaleY(0);transform-origin:50% 0;will-change:transform}210.fd-sst-step{211 position:relative;display:block;margin:0;padding:0;min-height:62vh;212 opacity:.28;transition:opacity var(--fd-dur-3) var(--fd-ease-out);213}214.fd-sst-step:last-child{min-height:40vh}215.fd-sst-step[data-on]{opacity:1}216.fd-sst-knot{217 position:absolute;left:-36px;top:4px;width:12px;height:12px;border-radius:50%;218 background:var(--fd-sst-bg);border:2px solid rgba(11,20,18,.2);219 transition:border-color var(--fd-dur-2) var(--fd-ease-out),transform var(--fd-dur-3) var(--fd-spring),background var(--fd-dur-2) var(--fd-ease-out);220}221.fd-sst-step[data-on] .fd-sst-knot{border-color:var(--fd-sst-accent);background:var(--fd-sst-accent);transform:scale(1.25)}222.fd-sst-eyebrow{display:flex;align-items:center;gap:10px;margin:0 0 14px;padding:0;color:var(--fd-sst-accent);font:600 0.8125rem/1.2 var(--fd-sst-sans);letter-spacing:0}223.fd-sst-n{color:var(--fd-sst-muted);font:500 0.75rem/1 var(--fd-sst-mono)}224.fd-sst-title{margin:0 0 14px;padding:0;color:var(--fd-sst-ink);font:650 clamp(1.75rem,3vw,2.75rem)/1.05 var(--fd-sst-display);letter-spacing:-0.03em}225.fd-sst-body{margin:0;padding:0;max-width:440px;color:var(--fd-sst-muted);font:400 1.0625rem/1.55 var(--fd-sst-sans);letter-spacing:-0.005em}226.fd-sst-stat{display:flex;align-items:baseline;gap:12px;margin:28px 0 0;padding:18px 0 0;border-top:1px solid rgba(11,20,18,.1);max-width:440px}227.fd-sst-stat-v{flex:none;white-space:nowrap;color:var(--fd-sst-ink);font:600 2rem/1 var(--fd-sst-display);letter-spacing:-0.025em}228.fd-sst-stat-l{color:var(--fd-sst-muted);font:400 0.8125rem/1.35 var(--fd-sst-sans);letter-spacing:0}229 230@media (max-width:760px){231 .fd-sst-grid{display:block;padding:0}232 .fd-sst-pin{height:auto;top:0;z-index:2;padding:12px 16px 12px;gap:10px;background:var(--fd-sst-bg)}233 .fd-sst-pin::after{content:"";position:absolute;left:0;right:0;top:100%;height:32px;background:linear-gradient(var(--fd-sst-bg),transparent);pointer-events:none}234 .fd-sst-media{aspect-ratio:16/11;max-height:44vh;border-radius:22px}235 .fd-sst-dot-l{display:none}236 .fd-sst-dot{padding:0 10px;height:24px}237 .fd-sst-text{padding:8vh 16px 30vh 50px}238 .fd-sst-rail{left:21px;top:8vh}239 .fd-sst-step{min-height:58vh}240 .fd-sst-title{font-size:1.625rem}241 .fd-sst-body{font-size:0.9375rem}242}243@media (prefers-reduced-motion: reduce){244 .fd-sst *{transition-duration:120ms !important}245}246`;247 248/* ------------------------------------------------------------------ Demo */249 250const STEPS: StoryStep[] = [251 {252 eyebrow: "Design",253 title: "Write the protocol once. In plain language.",254 body: "Describe the experiment the way you’d explain it to a colleague. Helix compiles it into a validated plate map with controls, replicates and randomised well positions.",255 stat: { value: "38 min", label: "median time from idea to a runnable 384-well plan" },256 },257 {258 eyebrow: "Run",259 title: "Robots run it overnight. You sleep.",260 body: "Plans dispatch to liquid handlers, incubators and readers across your bench. Every pipetting step is logged with volume, tip and timestamp — no more lab-notebook archaeology.",261 stat: { value: "11,204", label: "plates executed last month across 62 partner labs" },262 },263 {264 eyebrow: "Analyse",265 title: "Results arrive already curve-fitted.",266 body: "Dose–response fits, Z′ factors and outlier flags compute as the reader streams. Bad wells are caught at 3 a.m., not at Monday stand-up.",267 stat: { value: "Z′ 0.81", label: "average assay quality on the platform this quarter" },268 },269 {270 eyebrow: "Share",271 title: "Every result is reproducible by default.",272 body: "Publish the protocol, raw reads and analysis as one signed bundle. A collaborator in Basel can rerun your exact experiment on their own hardware.",273 stat: { value: "94%", label: "of shared runs reproduced within ±5% on first attempt" },274 },275];276 277// 16 × 24 plate rendered as 8 × 12 for clarity278const ROWS = 8;279const COLS = 12;280const hash = (i: number) => {281 const x = Math.sin(i * 127.1 + 311.7) * 43758.5453;282 return x - Math.floor(x);283};284 285function HelixMedia({ active }: { active: number }) {286 const wells = [];287 for (let r = 0; r < ROWS; r++)288 for (let c = 0; c < COLS; c++) {289 const i = r * COLS + c;290 const conc = c / (COLS - 1);291 const resp = 1 / (1 + Math.pow(0.35 / Math.max(0.02, conc), 2.4));292 const ctrl = c === 0 || c === COLS - 1;293 const outlier = i === 41 || i === 77;294 wells.push(295 <span296 key={i}297 className="fd-sst-demo-well"298 data-ctrl={ctrl ? "" : undefined}299 data-out={outlier ? "" : undefined}300 style={{301 ["--r" as string]: r,302 ["--c" as string]: c,303 ["--v" as string]: (resp * (0.85 + hash(i) * 0.3)).toFixed(3),304 ["--d" as string]: `${Math.round((r + c) * 18)}ms`,305 }}306 />,307 );308 }309 // dose–response points310 const pts = Array.from({ length: 12 }, (_, i) => {311 const x = i / 11;312 const y = 1 / (1 + Math.pow(0.42 / Math.max(0.03, x), 2.6));313 return { x: 40 + x * 320, y: 250 - (y * (0.92 + hash(i + 9) * 0.16)) * 200 };314 });315 const curve = Array.from({ length: 60 }, (_, i) => {316 const x = i / 59;317 const y = 1 / (1 + Math.pow(0.42 / Math.max(0.03, x), 2.6));318 return `${i ? "L" : "M"}${(40 + x * 320).toFixed(1)} ${(250 - y * 200).toFixed(1)}`;319 }).join(" ");320 321 return (322 <div className="fd-sst-demo-stage" data-state={active}>323 <div className="fd-sst-demo-bgl" />324 {/* state 0: protocol editor */}325 <div className="fd-sst-demo-layer fd-sst-demo-proto" data-active={active === 0 ? "" : undefined}>326 <div className="fd-sst-demo-win">327 <div className="fd-sst-demo-winbar">328 <i />329 <i />330 <i />331 <span>kinase-panel-v3.helix</span>332 </div>333 <div className="fd-sst-demo-code">334 <p>335 <b>test</b> 12 compounds <em>against</em> KRAS-G12C336 </p>337 <p>338 <b>dose</b> 10 µM → 0.5 nM, <em>half-log</em>, 11 points339 </p>340 <p>341 <b>replicates</b> 3 · <b>controls</b> DMSO, sotorasib342 </p>343 <p>344 <b>readout</b> luminescence @ 24 h345 </p>346 <p className="fd-sst-demo-ok">✓ compiled · 384 wells · 2 plates</p>347 </div>348 </div>349 </div>350 {/* state 1+2: plate */}351 <div className="fd-sst-demo-layer fd-sst-demo-plate" data-active={active === 1 || active === 2 ? "" : undefined}>352 <div className="fd-sst-demo-platebox">353 <div className="fd-sst-demo-wells">{wells}</div>354 <div className="fd-sst-demo-head">355 <span>HX-0412 · 1/2</span>356 <span className="fd-sst-demo-live">{active >= 2 ? "2 outliers flagged" : "Dispensing · row F"}</span>357 </div>358 </div>359 </div>360 {/* state 3: curve + bundle */}361 <div className="fd-sst-demo-layer fd-sst-demo-curve" data-active={active === 3 ? "" : undefined}>362 <svg viewBox="0 0 400 290" className="fd-sst-demo-svg" aria-hidden="true">363 {[0, 1, 2, 3, 4].map((i) => (364 <line key={i} x1="40" x2="360" y1={50 + i * 50} y2={50 + i * 50} stroke="rgba(255,255,255,.08)" />365 ))}366 <path d={curve} className="fd-sst-demo-path" pathLength={1} />367 {pts.map((p, i) => (368 <circle key={i} cx={p.x} cy={p.y} r="4.5" className="fd-sst-demo-pt" style={{ ["--d" as string]: `${i * 40}ms` }} />369 ))}370 <text x="40" y="280" className="fd-sst-demo-ax">371 0.5 nM372 </text>373 <text x="360" y="280" textAnchor="end" className="fd-sst-demo-ax">374 10 µM375 </text>376 </svg>377 <div className="fd-sst-demo-card">378 <span className="fd-sst-demo-card-k">IC₅₀</span>379 <span className="fd-sst-demo-card-v">42.7 nM</span>380 <span className="fd-sst-demo-card-s">Signed bundle · sha 9f3c…a10e</span>381 </div>382 </div>383 </div>384 );385}386 387export function Demo() {388 return (389 <div className="fd-sst-demo">390 <style>{demoCss}</style>391 <StickyStory392 steps={STEPS}393 renderMedia={(a) => <HelixMedia active={a} />}394 header={395 <header className="fd-sst-demo-hero">396 <p className="fd-sst-demo-kicker">Helix Bio — the lab operating system</p>397 <h2 className="fd-sst-demo-h">From hypothesis to signed result in one loop.</h2>398 <p className="fd-sst-demo-cue" aria-hidden="true">399 <span className="fd-sst-demo-cue-line" />400 Scroll401 </p>402 </header>403 }404 />405 </div>406 );407}408 409const demoCss = `410.fd-sst-demo{display:block;margin:0;padding:0;background:#F4F6F5;min-height:100vh}411.fd-sst-demo-hero{max-width:1240px;margin:0 auto;padding:clamp(48px,10vh,120px) clamp(16px,4vw,56px) 0;text-align:left}412.fd-sst-demo-kicker{margin:0 0 14px;padding:0;color:#0E9F8E;font:600 0.875rem/1.2 ui-sans-serif,-apple-system,"Inter",sans-serif;letter-spacing:0}413.fd-sst-demo-h{margin:0;padding:0;max-width:880px;color:#0B1412;font:650 clamp(2.25rem,6vw,4.5rem)/1 "SF Pro Display",ui-sans-serif,-apple-system,"Inter Display","Inter",sans-serif;letter-spacing:-0.045em}414.fd-sst-demo-cue{display:flex;width:fit-content;align-items:center;gap:10px;margin:28px 0 0;padding:0;color:#5D6B67;font:500 0.75rem/1 ui-monospace,"SF Mono","JetBrains Mono",Menlo,monospace;letter-spacing:.08em;text-transform:uppercase}415.fd-sst-demo-cue-line{position:relative;display:block;width:1px;height:28px;overflow:hidden;background:rgba(11,20,18,.15)}416.fd-sst-demo-cue-line::after{content:"";position:absolute;left:0;top:0;width:1px;height:12px;background:#0B1412;animation:fd-sst-cue 1.6s cubic-bezier(0.65,0,0.35,1) infinite}417@keyframes fd-sst-cue{0%{transform:translateY(-12px)}100%{transform:translateY(28px)}}418 419.fd-sst-demo-stage{position:absolute;inset:0;overflow:hidden;container-type:size;color:#E8F2EF;font-family:ui-sans-serif,-apple-system,"Inter",sans-serif}420.fd-sst-demo-bgl{421 position:absolute;inset:-20%;422 background:423 radial-gradient(40% 40% at calc(30% + var(--fd-sst-p) * 40%) 25%,rgba(14,159,142,.55),transparent 70%),424 radial-gradient(45% 45% at calc(80% - var(--fd-sst-p) * 50%) 85%,rgba(91,108,255,.4),transparent 70%),425 #0B1412;426}427.fd-sst-demo-layer{428 position:absolute;inset:0;display:flex;align-items:center;justify-content:center;padding:8%;429 opacity:0;transform:scale(.94) translate3d(0,16px,0);filter:blur(6px);430 transition:opacity var(--fd-dur-4) var(--fd-ease-out),transform var(--fd-dur-4) var(--fd-ease-out),filter var(--fd-dur-3) var(--fd-ease-out);431 pointer-events:none;432}433.fd-sst-demo-layer[data-active]{opacity:1;transform:none;filter:none}434.fd-sst-demo-win{width:100%;max-width:min(440px,100cqh * 1.25);border-radius:18px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.1);435 -webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);overflow:hidden;box-shadow:0 30px 60px -30px rgba(0,0,0,.6)}436.fd-sst-demo-winbar{display:flex;align-items:center;gap:6px;padding:12px 14px;border-bottom:1px solid rgba(255,255,255,.08);font:500 0.75rem/1 ui-monospace,"SF Mono",Menlo,monospace;color:rgba(232,242,239,.6)}437.fd-sst-demo-winbar i{display:block;width:9px;height:9px;border-radius:50%;background:rgba(255,255,255,.18)}438.fd-sst-demo-winbar span{margin-left:8px}439.fd-sst-demo-code{padding:clamp(10px,4cqh,18px) 18px clamp(12px,4.4cqh,20px);display:grid;gap:clamp(4px,2.2cqh,10px)}440.fd-sst-demo-code p{margin:0;padding:0;font:400 clamp(0.625rem,3.4cqh,0.9375rem)/1.45 ui-monospace,"SF Mono","JetBrains Mono",Menlo,monospace;color:#E8F2EF;441 opacity:0;transform:translate3d(-8px,0,0);transition:opacity var(--fd-dur-3) var(--fd-ease-out),transform var(--fd-dur-3) var(--fd-ease-out)}442.fd-sst-demo-proto[data-active] .fd-sst-demo-code p{opacity:1;transform:none}443.fd-sst-demo-code p:nth-child(2){transition-delay:90ms}.fd-sst-demo-code p:nth-child(3){transition-delay:180ms}444.fd-sst-demo-code p:nth-child(4){transition-delay:270ms}.fd-sst-demo-code p:nth-child(5){transition-delay:420ms}445.fd-sst-demo-code b{font-weight:600;color:#5EEAD4}446.fd-sst-demo-code em{font-style:normal;color:#A5B4FC}447.fd-sst-demo-ok{color:#86EFAC !important;margin-top:6px !important}448 449.fd-sst-demo-platebox{width:100%;max-width:min(480px,86cqh * 1.3);padding:18px;border-radius:20px;background:linear-gradient(160deg,#E7ECEA,#C9D2CF);box-shadow:0 30px 60px -30px rgba(0,0,0,.7),inset 0 1px 0 rgba(255,255,255,.8)}450.fd-sst-demo-wells{display:grid;grid-template-columns:repeat(${COLS},1fr);gap:clamp(3px,.6vw,6px)}451.fd-sst-demo-well{452 display:block;aspect-ratio:1;border-radius:50%;453 background:radial-gradient(circle at 40% 35%,rgba(255,255,255,.7),rgba(255,255,255,0) 50%),#AEB9B5;454 box-shadow:inset 0 1px 2px rgba(0,0,0,.25);455 position:relative;overflow:hidden;456}457.fd-sst-demo-well::after{458 content:"";position:absolute;inset:0;border-radius:50%;459 background:color-mix(in srgb,#0E9F8E calc(var(--v) * 100%),#E9F7A8);460 transform:scale(0);opacity:0;461 transition:transform var(--fd-dur-3) var(--fd-spring) var(--d),opacity var(--fd-dur-2) var(--fd-ease-out) var(--d),background var(--fd-dur-4) var(--fd-ease-out);462}463.fd-sst-demo-well[data-ctrl]::after{background:#64748B}464/* Run: rows dispense progressively, scrubbed by --fd-sst-k (fractional step) */465.fd-sst-demo-stage[data-state="1"] .fd-sst-demo-well::after{466 --fill:clamp(0,(var(--fd-sst-k) - 0.45 - var(--r) * 0.065) * 7,1);467 transform:scale(calc(var(--fill) * .72));opacity:calc(var(--fill) * .9);background:#7DD3C7;468 transition:background var(--fd-dur-4) var(--fd-ease-out);469}470.fd-sst-demo-stage[data-state="1"] .fd-sst-demo-well[data-ctrl]::after{background:#94A3B8}471.fd-sst-demo-stage[data-state="2"] .fd-sst-demo-well::after{transform:scale(.86);opacity:1}472.fd-sst-demo-stage[data-state="2"] .fd-sst-demo-well[data-out]{box-shadow:inset 0 1px 2px rgba(0,0,0,.25),0 0 0 2px #F43F5E}473.fd-sst-demo-head{white-space:nowrap;display:flex;justify-content:space-between;gap:8px;margin:14px 2px 0;font:500 0.75rem/1.2 ui-monospace,"SF Mono",Menlo,monospace;color:#29403A}474.fd-sst-demo-live{color:#0E7C6F}475 476.fd-sst-demo-curve{flex-direction:column;gap:16px}477.fd-sst-demo-svg{display:block;width:100%;max-width:min(460px,64cqh * 1.38);height:auto;overflow:visible}478.fd-sst-demo-path{fill:none;stroke:#5EEAD4;stroke-width:2.5;stroke-linecap:round;stroke-dasharray:1;stroke-dashoffset:1;transition:stroke-dashoffset 1100ms var(--fd-ease-in-out) 200ms}479.fd-sst-demo-curve[data-active] .fd-sst-demo-path{stroke-dashoffset:0}480.fd-sst-demo-pt{fill:#0B1412;stroke:#E8F2EF;stroke-width:2;opacity:0;transform-box:fill-box;transform-origin:center;transform:scale(0);481 transition:opacity var(--fd-dur-2) var(--fd-ease-out) var(--d),transform var(--fd-dur-3) var(--fd-spring) var(--d)}482.fd-sst-demo-curve[data-active] .fd-sst-demo-pt{opacity:1;transform:none}483.fd-sst-demo-ax{fill:rgba(232,242,239,.5);font:500 11px ui-monospace,"SF Mono",Menlo,monospace}484.fd-sst-demo-card{display:flex;align-items:baseline;flex-wrap:wrap;gap:4px 12px;padding:12px 16px;border-radius:14px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.12)}485.fd-sst-demo-card-k{font:500 0.75rem/1 ui-monospace,"SF Mono",Menlo,monospace;color:rgba(232,242,239,.6)}486.fd-sst-demo-card-v{font:650 1.375rem/1 "SF Pro Display",ui-sans-serif,-apple-system,"Inter Display","Inter",sans-serif;letter-spacing:-0.02em;color:#fff}487.fd-sst-demo-card-s{font:500 0.6875rem/1.2 ui-monospace,"SF Mono",Menlo,monospace;color:#86EFAC}488@media (max-width:760px){489 .fd-sst-demo-hero{padding-bottom:12px}490 .fd-sst-demo-layer{padding:6%}491 .fd-sst-demo-platebox{padding:10px}492 .fd-sst-demo-head{margin-top:8px;font-size:0.625rem}493 .fd-sst-demo-curve{gap:8px}494 .fd-sst-demo-card{padding:8px 12px}495 .fd-sst-demo-card-v{font-size:1.0625rem}496 .fd-sst-demo-winbar{padding:8px 12px}497}498@media (prefers-reduced-motion: reduce){499 .fd-sst-demo-layer{transform:none !important;filter:none !important}500 .fd-sst-demo-cue-line::after{animation:none}501}502`;A pinned card splits into three panels that fan out in 3D and flip, one by one, to reveal your services.
Eight hover reveals for profile cards: morph blob, diagonal wipe, strips, pixel dissolve, iris, slices, focus and a liquid lens.
A manifesto whose words light up as you scroll, with keywords that get a hand-drawn marker underline.