Maison Editorial Hero

A giant serif masthead and procedural duotone fabric frames revealed by clip-path wipes, drifting apart on scroll.

  • Heroes
  • Fashion
  • React only · 0 deps
  • 1 file
Full screen

Browser support · All evergreen browsers. Fabric is rendered with SVG feTurbulence + feDisplacementMap.

Install. One command, or copy the file.

$npx shadcn@latest add https://freedesignmd.com/r/hero-fashion-editorial.json
  • Byte-identical to the preview
  • No Tailwind or config needed
  • SSR-safe · keyboard accessible
  • Respects reduced motion & transparency
components/heroes/hero-fashion-editorial.tsx
1/**
2 * HeroFashionEditorial — a luxury-house campaign hero. A giant serif masthead
3 * sets in letter by letter, and three art-directed "photographs" (procedural
4 * duotone fabric folds built from SVG turbulence + displacement and layered
5 * gradients, no image files) are revealed with clip-path wipes. On scroll the
6 * masthead and the image strip drift horizontally in opposite directions; the
7 * pointer adds a gentle parallax inside each frame.
8 *
9 * Props: house, season, collection, lookbook ({ title, caption, tone }[]),
10 * primaryCta, secondaryCta. Tones: "oxblood" | "sand" | "ink" | "sage".
11 * CSS vars: --fd-msn-paper, --fd-msn-ink, --fd-msn-accent.
12 *
13 * Browser support: all evergreen browsers. SVG filters render the fabric; if
14 * a browser drops them the gradients alone still read as art-directed colour
15 * fields.
16 * Motion: CSS entrance (clip-path wipes, letter masks), rAF scroll drift and
17 * pointer parallax (transform only, paused off-screen).
18 * prefers-reduced-motion: final composition, no drift.
19 * Accessibility: frames are role="img" with descriptive labels; masthead is
20 * the page <h1>.
21 */
22import { useEffect, useId, useRef } from "react";
23
24export type FabricTone = "oxblood" | "sand" | "ink" | "sage";
25export type LookFrame = { title: string; caption: string; tone: FabricTone; label: string };
26
27export interface HeroFashionEditorialProps {
28 house?: string;
29 season?: string;
30 collection?: string;
31 intro?: string;
32 looks?: LookFrame[];
33 primaryCta?: { label: string; href: string };
34 secondaryCta?: { label: string; href: string };
35}
36
37const TONES: Record<FabricTone, { a: string; b: string; c: string; hi: string }> = {
38 oxblood: { a: "#2a0a0d", b: "#6e1a20", c: "#b8483f", hi: "#f2b9a0" },
39 sand: { a: "#6b5640", b: "#b99a74", c: "#e4d0b0", hi: "#fff5e4" },
40 ink: { a: "#07090f", b: "#1b2232", c: "#45506a", hi: "#c8d2ea" },
41 sage: { a: "#26302a", b: "#5f7462", c: "#a9b9a3", hi: "#eef3e6" },
42};
43
44// luminance profile across the drape: uneven fold widths read as real cloth
45const FOLDS = [
46 [30, 90, 210, 120, 40, 70, 235, 160, 60, 110, 190, 80, 30],
47 [60, 200, 140, 230, 90, 170, 250, 120, 70, 210, 150],
48 [20, 60, 180, 70, 30, 110, 220, 90, 40, 150, 50],
49];
50
51const DEFAULT_LOOKS: LookFrame[] = [
52 { title: "Look 07", caption: "Double-faced cashmere coat, oxblood", tone: "oxblood", label: "Close crop of oxblood double-faced cashmere falling in deep folds" },
53 { title: "Look 12", caption: "Silk charmeuse slip, bone", tone: "sand", label: "Bone silk charmeuse catching light across soft diagonal folds" },
54 { title: "Look 21", caption: "Wool gabardine trench, nocturne", tone: "ink", label: "Midnight wool gabardine with a cool highlight along the drape" },
55];
56
57export function HeroFashionEditorial({
58 house = "MAISON VÉRANE",
59 season = "Automne — Hiver 2026",
60 collection = "Vérane",
61 intro = "Forty-one looks cut in Biella wool, Como silk and the softest double-faced cashmere we have ever woven. Shown in Paris, 3 March.",
62 looks = DEFAULT_LOOKS,
63 primaryCta = { label: "Discover the collection", href: "#collection" },
64 secondaryCta = { label: "Watch the show — 14 min", href: "#show" },
65}: HeroFashionEditorialProps) {
66 const root = useRef<HTMLElement>(null);
67 const mast = useRef<HTMLHeadingElement>(null);
68 const strip = useRef<HTMLDivElement>(null);
69 const frames = useRef<(HTMLDivElement | null)[]>([]);
70 const uid = useId().replace(/[^a-zA-Z0-9]/g, "");
71
72 // fit the masthead to the full measure, whatever serif the system resolves
73 useEffect(() => {
74 const h = mast.current;
75 const wrap = h?.parentElement;
76 if (!h || !wrap) return;
77 const fit = () => {
78 h.style.fontSize = "";
79 const avail = wrap.clientWidth - parseFloat(getComputedStyle(wrap).paddingLeft) * 2;
80 const fs = parseFloat(getComputedStyle(h).fontSize);
81 const w = h.scrollWidth;
82 if (w > 0) h.style.fontSize = `${Math.floor(fs * (avail / w) * 0.995)}px`;
83 };
84 fit();
85 const r1 = requestAnimationFrame(fit);
86 const t1 = window.setTimeout(fit, 400);
87 document.fonts?.ready.then(fit).catch(() => {});
88 const ro = new ResizeObserver(fit);
89 ro.observe(wrap);
90 return () => {
91 ro.disconnect();
92 cancelAnimationFrame(r1);
93 window.clearTimeout(t1);
94 };
95 }, [collection]);
96
97 useEffect(() => {
98 const el = root.current;
99 if (!el || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
100 const sp = { x: 0, y: 0, tx: 0, ty: 0 };
101 const onMove = (e: PointerEvent) => {
102 if (e.pointerType === "touch") return;
103 const r = el.getBoundingClientRect();
104 sp.tx = ((e.clientX - r.left) / r.width) * 2 - 1;
105 sp.ty = ((e.clientY - r.top) / r.height) * 2 - 1;
106 };
107 el.addEventListener("pointermove", onMove);
108 let raf = 0;
109 let visible = true;
110 const t0 = performance.now();
111 const frame = (now: number) => {
112 raf = 0;
113 sp.x += (sp.tx - sp.x) * 0.06;
114 sp.y += (sp.ty - sp.y) * 0.06;
115 const r = el.getBoundingClientRect();
116 const p = Math.max(-0.2, Math.min(1, -r.top / Math.max(1, r.height)));
117 const t = (now - t0) / 1000;
118 if (mast.current) mast.current.style.transform = `translate3d(${(-p * 22).toFixed(2)}vw, ${(p * 40).toFixed(1)}px, 0)`;
119 if (strip.current) strip.current.style.transform = `translate3d(${(p * 14).toFixed(2)}vw, ${(-p * 60).toFixed(1)}px, 0)`;
120 frames.current.forEach((f, i) => {
121 if (!f) return;
122 const d = [1, -0.7, 0.85][i % 3];
123 const breathe = Math.sin(t * 0.35 + i * 1.7) * 0.012;
124 f.style.transform = `translate3d(${(sp.x * 14 * d).toFixed(2)}px, ${(sp.y * 10 * d + p * 40 * d).toFixed(2)}px, 0) scale(${(1.12 + breathe + p * 0.06).toFixed(4)})`;
125 });
126 if (visible && !document.hidden) raf = requestAnimationFrame(frame);
127 };
128 const start = () => {
129 if (!raf && visible && !document.hidden) raf = requestAnimationFrame(frame);
130 };
131 const io = new IntersectionObserver(([e]) => {
132 visible = e.isIntersecting;
133 if (visible) start();
134 });
135 io.observe(el);
136 document.addEventListener("visibilitychange", start);
137 start();
138 return () => {
139 cancelAnimationFrame(raf);
140 io.disconnect();
141 document.removeEventListener("visibilitychange", start);
142 el.removeEventListener("pointermove", onMove);
143 };
144 }, []);
145
146 const letters = Array.from(collection);
147
148 return (
149 <section ref={root} className="fd-msn" aria-label={`${house} ${season}`}>
150 <style>{css}</style>
151 <svg className="fd-msn-defs" aria-hidden="true" focusable="false">
152 {(["oxblood", "sand", "ink", "sage"] as FabricTone[]).map((tone, i) => {
153 const c = TONES[tone];
154 return (
155 <filter key={tone} id={`${uid}-${tone}`} x="0" y="0" width="100%" height="100%" colorInterpolationFilters="sRGB">
156 <feTurbulence type="fractalNoise" baseFrequency={`${0.0032 + i * 0.0006} ${0.0042 + i * 0.0005}`} numOctaves={2} seed={i * 7 + 3} result="n" />
157 <feDisplacementMap in="SourceGraphic" in2="n" scale={150} xChannelSelector="R" yChannelSelector="G" result="d" />
158 <feGaussianBlur in="d" stdDeviation="5" result="b" />
159 <feComponentTransfer in="b" result="g">
160 <feFuncR type="table" tableValues={hexTable(c, 0)} />
161 <feFuncG type="table" tableValues={hexTable(c, 1)} />
162 <feFuncB type="table" tableValues={hexTable(c, 2)} />
163 </feComponentTransfer>
164 </filter>
165 );
166 })}
167 <filter id={`${uid}-grain`} x="0" y="0" width="100%" height="100%">
168 <feTurbulence type="fractalNoise" baseFrequency="0.85" numOctaves={2} stitchTiles="stitch" />
169 <feColorMatrix values="0 0 0 0 0.5 0 0 0 0 0.5 0 0 0 0 0.5 0 0 0 0.5 0" />
170 </filter>
171 </svg>
172
173 <div className="fd-msn-top">
174 <span>{season}</span>
175 <span className="fd-msn-hide-s">Collection N°&nbsp;38</span>
176 <span>Paris · Milano · Tokyo</span>
177 </div>
178
179 <div className="fd-msn-mastwrap">
180 <h1 ref={mast} className="fd-msn-mast" aria-label={collection}>
181 {letters.map((ch, i) => (
182 <span key={i} className="fd-msn-ch" aria-hidden="true">
183 <span style={{ animationDelay: `${120 + i * 70}ms` }}>{ch}</span>
184 </span>
185 ))}
186 </h1>
187 </div>
188
189 <div className="fd-msn-body">
190 <div className="fd-msn-copy">
191 <p className="fd-msn-kicker">The Autumn–Winter collection</p>
192 <p className="fd-msn-intro">{intro}</p>
193 <div className="fd-msn-ctas">
194 <a className="fd-msn-btn" href={primaryCta.href}>
195 {primaryCta.label}
196 <svg viewBox="0 0 20 10" width="20" height="10" aria-hidden="true">
197 <path d="M0 5h18M14 1l4 4-4 4" fill="none" stroke="currentColor" strokeWidth="1.2" />
198 </svg>
199 </a>
200 <a className="fd-msn-link" href={secondaryCta.href}>
201 {secondaryCta.label}
202 </a>
203 </div>
204 </div>
205
206 <div ref={strip} className="fd-msn-strip">
207 {looks.map((l, i) => {
208 const c = TONES[l.tone];
209 return (
210 <figure key={l.title} className={`fd-msn-fig fd-msn-fig-${i}`}>
211 <div className="fd-msn-frame" role="img" aria-label={l.label} style={{ animationDelay: `${450 + i * 160}ms` }}>
212 <div ref={(n) => { frames.current[i] = n; }} className="fd-msn-photo">
213 <svg viewBox="0 0 400 520" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
214 <defs>
215 <linearGradient id={`${uid}-g${i}`} x1="0" y1="0" x2="1" y2="1" gradientTransform={`rotate(${[18, -22, 8][i % 3]} .5 .5)`}>
216 {FOLDS[i % 3].map((v, k, arr) => (
217 <stop key={k} offset={k / (arr.length - 1)} stopColor={`rgb(${v},${v},${v})`} />
218 ))}
219 </linearGradient>
220 </defs>
221 <rect width="400" height="520" fill={c.b} />
222 <rect x="-80" y="-80" width="560" height="680" fill={`url(#${uid}-g${i})`} filter={`url(#${uid}-${l.tone})`} />
223 </svg>
224 <div className="fd-msn-light" style={{ background: `radial-gradient(80% 60% at ${[30, 70, 40][i % 3]}% ${[20, 30, 15][i % 3]}%, ${c.hi}55, transparent 60%), linear-gradient(180deg, transparent 50%, ${c.a}cc)` }} />
225 <svg className="fd-msn-grain" aria-hidden="true">
226 <rect width="100%" height="100%" filter={`url(#${uid}-grain)`} />
227 </svg>
228 </div>
229 <span className="fd-msn-tag">{l.title}</span>
230 </div>
231 <figcaption className="fd-msn-cap">{l.caption}</figcaption>
232 </figure>
233 );
234 })}
235 </div>
236 </div>
237
238 <div className="fd-msn-foot" aria-hidden="true">
239 <span className="fd-msn-scroll">
240 <i />
241 Scroll
242 </span>
243 </div>
244 </section>
245 );
246}
247
248function hexTable(c: { a: string; b: string; c: string; hi: string }, ch: number) {
249 const stops = [c.a, c.a, c.b, c.c, c.hi];
250 return stops.map((h) => (parseInt(h.slice(1 + ch * 2, 3 + ch * 2), 16) / 255).toFixed(3)).join(" ");
251}
252
253const css = `
254.fd-msn, .fd-msn *, .fd-msn *::before, .fd-msn *::after { box-sizing: border-box; }
255.fd-msn {
256 --fd-ease-out: cubic-bezier(0.22, 1, 0.36, 1);
257 --fd-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
258 --fd-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
259 --fd-dur-1: 120ms; --fd-dur-2: 240ms; --fd-dur-3: 420ms; --fd-dur-4: 700ms;
260 --fd-msn-paper: #F3EEE6;
261 --fd-msn-ink: #16120F;
262 --fd-msn-accent: #7A1F24;
263 --fd-msn-serif: "Didot", "Bodoni 72", "Bodoni MT", ui-serif, "New York", "Iowan Old Style", "Times New Roman", Georgia, serif;
264 --fd-msn-sans: "Helvetica Neue", ui-sans-serif, -apple-system, "Inter", Arial, sans-serif;
265 position: relative; display: block; overflow: hidden; margin: 0; padding: 0 0 28px; min-height: 860px;
266 background: var(--fd-msn-paper); color: var(--fd-msn-ink); font: 400 1rem/1.5 var(--fd-msn-sans);
267 isolation: isolate;
268}
269.fd-msn-defs { position: absolute; width: 0; height: 0; overflow: hidden; display: block; }
270.fd-msn-top {
271 display: flex; justify-content: space-between; gap: 16px; margin: 0; padding: 92px 40px 0;
272 font: 500 0.6875rem/1 var(--fd-msn-sans); letter-spacing: 0.24em; text-transform: uppercase; color: #5a5048;
273 animation: fd-msn-fade 1000ms var(--fd-ease-out) 900ms both;
274}
275.fd-msn-mastwrap { position: relative; z-index: 0; margin: 0; padding: 0 28px; }
276.fd-msn-mast {
277 width: max-content;
278 display: block; margin: 6px 0 0; padding: 0; white-space: nowrap; will-change: transform;
279 font: 400 clamp(5.5rem, 26vw, 26rem)/0.8 var(--fd-msn-serif); letter-spacing: -0.035em;
280 color: var(--fd-msn-ink);
281 font-optical-sizing: auto;
282}
283.fd-msn-ch { display: inline-block; overflow: hidden; padding: 0.04em 0 0.02em; }
284.fd-msn-ch > span { display: inline-block; animation: fd-msn-up 1100ms var(--fd-ease-out) both; }
285.fd-msn-body {
286 position: relative; z-index: 1; display: grid; grid-template-columns: minmax(0, 0.8fr) minmax(0, 2fr); gap: 48px; align-items: start;
287 margin: 28px 0 0; padding: 0 40px;
288}
289.fd-msn-copy { padding-top: 4px; }
290.fd-msn-kicker { margin: 0 0 14px; padding: 0; font: italic 400 1.25rem/1.2 var(--fd-msn-serif); letter-spacing: -0.005em; color: var(--fd-msn-accent); animation: fd-msn-fade var(--fd-dur-4) var(--fd-ease-out) 1000ms both; }
291.fd-msn-intro { margin: 0; padding: 0; max-width: 22rem; font: 400 0.9375rem/1.6 var(--fd-msn-sans); letter-spacing: 0; color: #3a322c; text-wrap: pretty; animation: fd-msn-fade var(--fd-dur-4) var(--fd-ease-out) 1100ms both; }
292.fd-msn-ctas { display: flex; flex-direction: column; align-items: flex-start; gap: 18px; margin: 32px 0 0; animation: fd-msn-fade var(--fd-dur-4) var(--fd-ease-out) 1200ms both; }
293.fd-msn-btn {
294 display: inline-flex; align-items: center; gap: 14px; margin: 0; padding: 0 22px; height: 48px; border: 1px solid var(--fd-msn-ink); border-radius: 0;
295 background: var(--fd-msn-ink); color: var(--fd-msn-paper); text-decoration: none;
296 font: 500 0.6875rem/1 var(--fd-msn-sans); letter-spacing: 0.22em; text-transform: uppercase;
297 transition: background var(--fd-dur-2) var(--fd-ease-out), color var(--fd-dur-2) var(--fd-ease-out);
298}
299.fd-msn-btn svg { display: block; transition: transform var(--fd-dur-3) var(--fd-ease-out); }
300.fd-msn-btn:hover { background: transparent; color: var(--fd-msn-ink); }
301.fd-msn-btn:hover svg { transform: translateX(5px); }
302.fd-msn-btn:active { transform: scale(0.96); }
303.fd-msn-link {
304 margin: 0; padding: 0 0 3px; color: var(--fd-msn-ink); text-decoration: none; border-bottom: 1px solid currentColor;
305 font: 500 0.6875rem/1 var(--fd-msn-sans); letter-spacing: 0.22em; text-transform: uppercase;
306}
307.fd-msn-btn:focus-visible, .fd-msn-link:focus-visible { outline: 2px solid rgba(122,31,36,0.7); outline-offset: 3px; }
308.fd-msn-strip { position: relative; z-index: 2; display: grid; grid-template-columns: 1.15fr 0.85fr 1fr; gap: 20px; align-items: start; margin-top: -15vw; will-change: transform; }
309.fd-msn-fig { margin: 0; padding: 0; }
310.fd-msn-fig-1 { margin-top: 64px; }
311.fd-msn-fig-2 { margin-top: 18px; }
312.fd-msn-frame {
313 position: relative; overflow: hidden; aspect-ratio: 4 / 5; box-shadow: 0 30px 60px -30px rgba(22,18,15,0.45); background: #d9cfc2;
314 clip-path: inset(100% 0 0 0); animation: fd-msn-wipe 1300ms var(--fd-ease-in-out) both;
315}
316.fd-msn-fig-1 .fd-msn-frame { aspect-ratio: 3 / 4; clip-path: inset(0 100% 0 0); animation-name: fd-msn-wipe-x; }
317.fd-msn-photo { position: absolute; inset: 0; will-change: transform; transform: scale(1.12); }
318.fd-msn-photo > svg { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
319.fd-msn-light { position: absolute; inset: 0; mix-blend-mode: soft-light; }
320.fd-msn-grain { position: absolute; inset: 0; width: 100%; height: 100%; display: block; mix-blend-mode: overlay; opacity: 0.35; }
321.fd-msn-tag {
322 position: absolute; left: 14px; top: 14px; padding: 0; margin: 0;
323 font: 500 0.625rem/1 var(--fd-msn-sans); letter-spacing: 0.24em; text-transform: uppercase; color: rgba(255,255,255,0.86);
324}
325.fd-msn-cap { margin: 12px 0 0; padding: 0; font: italic 400 0.9375rem/1.3 var(--fd-msn-serif); color: #3a322c; letter-spacing: 0; animation: fd-msn-fade var(--fd-dur-4) var(--fd-ease-out) 1500ms both; }
326.fd-msn-foot {
327 position: absolute; left: 40px; right: 40px; bottom: 26px; display: flex; justify-content: flex-end; align-items: center;
328 font: 500 0.625rem/1 var(--fd-msn-sans); letter-spacing: 0.3em; text-transform: uppercase; color: #5a5048;
329 animation: fd-msn-fade 1000ms var(--fd-ease-out) 1600ms both;
330}
331.fd-msn-scroll { display: inline-flex; align-items: center; gap: 12px; }
332.fd-msn-scroll i { display: block; width: 40px; height: 1px; background: currentColor; transform-origin: left; animation: fd-msn-line 2400ms var(--fd-ease-in-out) infinite; }
333
334@keyframes fd-msn-up { from { transform: translateY(102%); } to { transform: none; } }
335@keyframes fd-msn-fade { from { opacity: 0; } to { opacity: 1; } }
336@keyframes fd-msn-wipe { from { clip-path: inset(100% 0 0 0); } to { clip-path: inset(0 0 0 0); } }
337@keyframes fd-msn-wipe-x { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
338@keyframes fd-msn-line { 0% { transform: scaleX(0); } 50% { transform: scaleX(1); transform-origin: left; } 50.01% { transform-origin: right; } 100% { transform: scaleX(0); transform-origin: right; } }
339
340@media (max-width: 860px) {
341 .fd-msn { min-height: 844px; padding-bottom: 72px; }
342 .fd-msn-top { padding: 84px 16px 0; letter-spacing: 0.18em; }
343 .fd-msn-hide-s { display: none; }
344 .fd-msn-top span { white-space: nowrap; }
345 .fd-msn-top span:last-child { display: none; }
346 .fd-msn-mastwrap { padding: 0 12px; }
347 .fd-msn-mast { margin-top: 14px; }
348 .fd-msn-body { grid-template-columns: 1fr; gap: 28px; margin-top: 24px; padding: 0 16px; }
349 .fd-msn-strip { grid-template-columns: 1.2fr 1fr; gap: 12px; margin-top: 0; }
350 .fd-msn-fig-1 { margin-top: 40px; }
351 .fd-msn-fig-2 { display: none; }
352 .fd-msn-ctas { flex-direction: row; flex-wrap: wrap; align-items: center; gap: 20px; margin-top: 22px; }
353 .fd-msn-foot { left: 16px; right: 16px; bottom: 22px; }
354 .fd-msn-copy { order: 2; }
355 .fd-msn-intro { max-width: none; }
356}
357@media (prefers-reduced-motion: reduce) {
358 .fd-msn *, .fd-msn *::before { animation: none !important; }
359 .fd-msn-frame { clip-path: none; }
360}
361`;
362
363/* ───────────────────────── Demo ───────────────────────── */
364
365export function Demo() {
366 return (
367 <div className="fd-msn-demo">
368 <style>{demoCss}</style>
369 <header className="fd-msn-demo-nav">
370 <nav aria-label="Shop">
371 <a href="#women">Women</a>
372 <a href="#men">Men</a>
373 <a href="#leather">Leather goods</a>
374 </nav>
375 <a className="fd-msn-demo-logo" href="#top">MAISON VÉRANE</a>
376 <div className="fd-msn-demo-r">
377 <a href="#stores">Stores</a>
378 <a href="#bag" aria-label="Shopping bag, 2 items">Bag (2)</a>
379 </div>
380 </header>
381 <HeroFashionEditorial />
382 </div>
383 );
384}
385
386const demoCss = `
387.fd-msn-demo { position: relative; margin: 0; padding: 0; min-height: 100vh; background: #F3EEE6; }
388.fd-msn-demo *, .fd-msn-demo *::before, .fd-msn-demo *::after { box-sizing: border-box; }
389.fd-msn-demo-nav {
390 position: absolute; z-index: 10; top: 0; left: 0; right: 0; height: 64px; padding: 0 40px; margin: 0;
391 display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
392 border-bottom: 1px solid rgba(22,18,15,0.12);
393 animation: fd-msn-demo-in 900ms cubic-bezier(0.22, 1, 0.36, 1) 800ms both;
394}
395.fd-msn-demo-nav nav, .fd-msn-demo-r { display: flex; gap: 28px; margin: 0; padding: 0; }
396.fd-msn-demo-r { justify-content: flex-end; }
397.fd-msn-demo-nav a { color: #16120F; text-decoration: none; font: 500 0.6875rem/1 "Helvetica Neue", ui-sans-serif, -apple-system, "Inter", Arial, sans-serif; letter-spacing: 0.2em; text-transform: uppercase; }
398.fd-msn-demo-nav a:focus-visible { outline: 2px solid rgba(122,31,36,.7); outline-offset: 3px; }
399.fd-msn-demo-nav .fd-msn-demo-logo { font: 400 1.0625rem/1 "Didot", "Bodoni 72", ui-serif, "New York", "Times New Roman", Georgia, serif; letter-spacing: 0.32em; }
400@keyframes fd-msn-demo-in { from { opacity: 0; } to { opacity: 1; } }
401@media (max-width: 860px) {
402 .fd-msn-demo-nav { padding: 0 16px; grid-template-columns: auto 1fr auto; gap: 12px; }
403 .fd-msn-demo-nav nav a:not(:first-child), .fd-msn-demo-r a:first-child { display: none; }
404 .fd-msn-demo-nav .fd-msn-demo-logo { font-size: 0.875rem; letter-spacing: 0.24em; text-align: center; }
405}
406@media (prefers-reduced-motion: reduce) { .fd-msn-demo-nav { animation: none; } }
407`;

More Heroes. Same file contract.

Ledger Card Hero

A photoreal CSS-3D metal card that tilts to the cursor, then flips and docks on scroll while spend stats count up.

Agent Terminal Hero

A terminal types a prompt and streams an agent's plan while the generated dashboard assembles itself beside it.

Fjord Parallax Hero

A layered SVG landscape with deep pointer and scroll parallax, a mirrored lake and golden hour fading to dusk.