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.
Giant condensed type, gradient-painted album art with a sliding vinyl, a play/pause morph and a pointer-reactive spectrum.
Browser support · All evergreen browsers. Icon morph uses CSS `d` transitions (Chromium, Firefox); Safari swaps instantly.
npx shadcn@latest add https://freedesignmd.com/r/hero-music-waveform.json1/**2 * HeroMusicWaveform — an album-launch hero for a streaming service. Giant3 * condensed type, album art painted entirely with CSS gradients (a slowly4 * turning conic "sun" over grain), a vinyl that slides out of the sleeve, and5 * a full-width spectrum of bars that dances to a synthetic signal and bulges6 * toward the pointer. The play button morphs to pause (SVG path morph) and7 * drives the energy of the spectrum. On scroll the sleeve tilts back and the8 * bars flatten.9 *10 * Props: artist, album, release, tracks, primaryCta, accent colours.11 * CSS vars: --fd-wav-bg, --fd-wav-ink, --fd-wav-hot, --fd-wav-cool.12 *13 * Browser support: all evergreen browsers. No audio is played; the14 * spectrum is procedural.15 * Motion: one rAF loop (paused off-screen / hidden tab) writes scaleY on the16 * bars and the sleeve transform; the play/pause morph is a CSS `d` transition17 * on Chromium/Firefox, with a cross-fade fallback in Safari.18 * prefers-reduced-motion: static bars, no spin, button still toggles.19 * Accessibility: play button is a real <button aria-pressed>; track list is20 * an <ol>; the spectrum is aria-hidden.21 */22import { useEffect, useRef, useState } from "react";23 24export interface HeroMusicWaveformProps {25 artist?: string;26 album?: string;27 release?: string;28 tracks?: { title: string; time: string }[];29 primaryCta?: { label: string; href: string };30 bars?: number;31}32 33const DEFAULT_TRACKS = [34 { title: "Saltwater Hymn", time: "3:41" },35 { title: "Low Orbit (feat. Ana Brava)", time: "4:12" },36 { title: "Nine Floors Up", time: "2:58" },37];38 39const PLAY = "M8 5 L8 19 L13.5 15.5 L13.5 8.5 Z M13.5 8.5 L13.5 15.5 L19 12 L19 12 Z";40const PAUSE = "M7 5 L7 19 L10.5 19 L10.5 5 Z M13.5 5 L13.5 19 L17 19 L17 5 Z";41 42export function HeroMusicWaveform({43 artist = "Noor Okafor",44 album = "TIDAL HEART",45 release = "Out now · 11 tracks · 41 min",46 tracks = DEFAULT_TRACKS,47 primaryCta = { label: "Pre-save the vinyl", href: "#vinyl" },48 bars = 72,49}: HeroMusicWaveformProps) {50 const root = useRef<HTMLElement>(null);51 const barRefs = useRef<(HTMLSpanElement | null)[]>([]);52 const sleeve = useRef<HTMLDivElement>(null);53 const big = useRef<HTMLHeadingElement>(null);54 const prog = useRef<HTMLSpanElement>(null);55 const clock = useRef<HTMLSpanElement>(null);56 const [playing, setPlaying] = useState(true);57 const playingRef = useRef(true);58 playingRef.current = playing;59 60 useEffect(() => {61 const el = root.current;62 if (!el) return;63 if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {64 barRefs.current.forEach((b, i) => b && (b.style.transform = `scaleY(${(0.25 + 0.6 * Math.abs(Math.sin(i * 0.37) * Math.cos(i * 0.11))).toFixed(3)})`));65 return;66 }67 const n = barRefs.current.length;68 const level = new Float32Array(n);69 let px = -10;70 let pTarget = -10;71 let energy = 1;72 const onMove = (e: PointerEvent) => {73 const r = el.getBoundingClientRect();74 pTarget = (e.clientX - r.left) / r.width;75 };76 const onLeave = () => {77 pTarget = -10;78 };79 el.addEventListener("pointermove", onMove);80 el.addEventListener("pointerleave", onLeave);81 let raf = 0;82 let visible = true;83 let last = performance.now();84 let songT = 74;85 const frame = (now: number) => {86 raf = 0;87 const dt = Math.min((now - last) / 1000, 0.05);88 last = now;89 const t = now / 1000;90 energy += ((playingRef.current ? 1 : 0.12) - energy) * Math.min(1, dt * 4);91 if (playingRef.current) songT += dt;92 px = pTarget < -1 ? px + (-10 - px) * 0.05 : px + (pTarget - px) * 0.12;93 // a beat at 96 bpm with a kick envelope94 const beat = (t * 1.6) % 1;95 const kick = Math.exp(-beat * 7);96 const r = el.getBoundingClientRect();97 const sc = Math.min(1, Math.max(0, -r.top / Math.max(1, r.height)));98 for (let i = 0; i < n; i++) {99 const u = i / (n - 1);100 // spectrum envelope: bass heavy, mid hump, airy highs101 const env = 0.55 * Math.exp(-u * 3.2) + 0.35 * Math.exp(-Math.pow((u - 0.42) / 0.16, 2)) + 0.12;102 const wob = 0.5 + 0.5 * Math.sin(t * (3.1 + u * 9) + i * 1.7) * Math.sin(t * (1.3 + u * 3) + i * 0.45);103 const k = u < 0.18 ? kick * (1 - u / 0.18) : kick * 0.15;104 const near = Math.exp(-Math.pow((u - px) / 0.07, 2));105 const target = Math.min(1, (env * (0.35 + 0.65 * wob) + k * 0.5) * energy * (1 - sc * 0.8) + near * 0.55 + 0.04);106 level[i] += (target - level[i]) * (target > level[i] ? 0.45 : 0.12);107 const b = barRefs.current[i];108 if (b) b.style.transform = `scaleY(${level[i].toFixed(3)})`;109 }110 if (sleeve.current) sleeve.current.style.transform = `perspective(1400px) rotateX(${(sc * 28).toFixed(2)}deg) rotateY(${(-14 + (px > -1 ? (px - 0.5) * 10 : 0)).toFixed(2)}deg) translateY(${(sc * -60).toFixed(1)}px) scale(${(1 + kick * 0.008 * energy).toFixed(4)})`;111 if (big.current) big.current.style.transform = `translate3d(${(sc * -8).toFixed(2)}vw, 0, 0)`;112 if (prog.current) prog.current.style.transform = `scaleX(${((songT % 221) / 221).toFixed(4)})`;113 if (clock.current) {114 const s = Math.floor(songT % 221);115 clock.current.textContent = `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;116 }117 if (visible && !document.hidden) raf = requestAnimationFrame(frame);118 };119 const start = () => {120 if (!raf && visible && !document.hidden) {121 last = performance.now();122 raf = requestAnimationFrame(frame);123 }124 };125 const io = new IntersectionObserver(([e]) => {126 visible = e.isIntersecting;127 if (visible) start();128 });129 io.observe(el);130 document.addEventListener("visibilitychange", start);131 start();132 return () => {133 cancelAnimationFrame(raf);134 io.disconnect();135 document.removeEventListener("visibilitychange", start);136 el.removeEventListener("pointermove", onMove);137 el.removeEventListener("pointerleave", onLeave);138 };139 }, [bars]);140 141 return (142 <section ref={root} className="fd-wav" data-playing={playing ? "" : undefined} aria-label={`${artist} — ${album}`}>143 <style>{css}</style>144 <div className="fd-wav-glow" aria-hidden="true" />145 <div className="fd-wav-top">146 <p className="fd-wav-artist">{artist}</p>147 <p className="fd-wav-release">{release}</p>148 </div>149 <h1 ref={big} className="fd-wav-big" aria-label={album}>150 {album.split(" ").map((w, wi) => (151 <span key={wi} className="fd-wav-word" aria-hidden="true">152 {Array.from(w).map((c, ci) => (153 <span key={ci} className="fd-wav-c" style={{ animationDelay: `${wi * 220 + ci * 45}ms` }}>154 {c}155 </span>156 ))}157 </span>158 ))}159 </h1>160 161 <div className="fd-wav-stage">162 <div ref={sleeve} className="fd-wav-sleeve" aria-hidden="true">163 <div className="fd-wav-vinyl">164 <div className="fd-wav-label">165 <span>{artist.split(" ")[0].toUpperCase()}</span>166 </div>167 </div>168 <div className="fd-wav-art">169 <div className="fd-wav-sun" />170 <div className="fd-wav-horizon" />171 <div className="fd-wav-grain" />172 <span className="fd-wav-art-t">{album}</span>173 <span className="fd-wav-art-a">{artist}</span>174 </div>175 </div>176 177 <div className="fd-wav-player">178 <button179 type="button"180 className="fd-wav-play"181 aria-pressed={playing}182 aria-label={playing ? "Pause preview" : "Play preview"}183 onClick={() => setPlaying((p) => !p)}184 >185 <svg viewBox="0 0 24 24" width="28" height="28" aria-hidden="true">186 <path className="fd-wav-icon" d={playing ? PAUSE : PLAY} fill="currentColor" />187 </svg>188 </button>189 <div className="fd-wav-now">190 <span className="fd-wav-now-k">{playing ? "Now playing" : "Paused"}</span>191 <b>{tracks[0]?.title}</b>192 <div className="fd-wav-bar">193 <span ref={prog} />194 </div>195 <div className="fd-wav-times">196 <span ref={clock}>1:14</span>197 <span>{tracks[0]?.time}</span>198 </div>199 </div>200 <ol className="fd-wav-tracks">201 {tracks.map((t, i) => (202 <li key={t.title} className={i === 0 ? "is-on" : undefined}>203 <span className="fd-wav-n">{String(i + 1).padStart(2, "0")}</span>204 <span className="fd-wav-tt">{t.title}</span>205 <span className="fd-wav-tm">{t.time}</span>206 </li>207 ))}208 </ol>209 <a className="fd-wav-cta" href={primaryCta.href}>210 {primaryCta.label}211 </a>212 </div>213 </div>214 215 <div className="fd-wav-spec" aria-hidden="true">216 {Array.from({ length: bars }, (_, i) => (217 <span key={i} ref={(n) => { barRefs.current[i] = n; }} className="fd-wav-b" style={{ ["--u" as string]: i / (bars - 1) }} />218 ))}219 </div>220 </section>221 );222}223 224const noise = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .45 0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)'/%3E%3C/svg%3E")`;225 226const css = `227.fd-wav, .fd-wav *, .fd-wav *::before, .fd-wav *::after { box-sizing: border-box; }228.fd-wav {229 --fd-ease-out: cubic-bezier(0.22, 1, 0.36, 1);230 --fd-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);231 --fd-spring: cubic-bezier(0.34, 1.56, 0.64, 1);232 --fd-dur-1: 120ms; --fd-dur-2: 240ms; --fd-dur-3: 420ms; --fd-dur-4: 700ms;233 --fd-wav-bg: #0E0A0B;234 --fd-wav-ink: #F6EDE4;235 --fd-wav-hot: #FF5A36;236 --fd-wav-cool: #FFB547;237 --fd-wav-cond: "Helvetica Neue", "Arial Narrow", "Roboto Condensed", "DejaVu Sans Condensed", ui-sans-serif, -apple-system, "Inter", sans-serif;238 --fd-wav-sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter", "Segoe UI", Roboto, sans-serif;239 position: relative; display: block; overflow: hidden; margin: 0; padding: 0; min-height: 880px;240 background: var(--fd-wav-bg); color: var(--fd-wav-ink); font: 400 1rem/1.5 var(--fd-wav-sans); isolation: isolate;241}242.fd-wav-glow { position: absolute; inset: auto -10% -30% -10%; height: 70%; background: radial-gradient(50% 60% at 50% 100%, rgba(255,90,54,0.35), transparent 70%); pointer-events: none; filter: blur(10px); }243.fd-wav-top { position: relative; z-index: 2; display: flex; justify-content: space-between; gap: 16px; max-width: 1320px; margin: 0 auto; padding: 96px 40px 0; animation: fd-wav-fade var(--fd-dur-4) var(--fd-ease-out) 600ms both; }244.fd-wav-artist, .fd-wav-release { margin: 0; padding: 0; font: 600 0.75rem/1 var(--fd-wav-sans); letter-spacing: 0.24em; text-transform: uppercase; color: rgba(246,237,228,0.7); }245.fd-wav-artist { color: var(--fd-wav-hot); }246.fd-wav-big {247 position: relative; z-index: 1; display: flex; justify-content: center; gap: 0.18em; margin: 6px 0 0; padding: 0 20px; white-space: nowrap; will-change: transform;248 font: 800 clamp(5rem, 17vw, 17rem)/0.82 var(--fd-wav-cond); font-stretch: condensed; letter-spacing: -0.055em; color: var(--fd-wav-ink);249 transform-origin: center;250}251.fd-wav-word { display: inline-flex; overflow: hidden; padding: 0.02em 0 0.04em; }252.fd-wav-c { display: inline-block; transform: scaleY(1.12); transform-origin: bottom; animation: fd-wav-in 900ms var(--fd-ease-out) both; }253.fd-wav-word:last-child .fd-wav-c { -webkit-text-stroke: 2px var(--fd-wav-ink); color: transparent; }254 255.fd-wav-stage { position: relative; z-index: 3; display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 56px; align-items: start; max-width: 1140px; margin: -9vw auto 0; padding: 0 40px; }256.fd-wav-sleeve { position: relative; width: 340px; aspect-ratio: 1; transform-style: preserve-3d; will-change: transform; animation: fd-wav-sleevein 1200ms var(--fd-ease-out) 300ms both; }257.fd-wav-art {258 position: absolute; inset: 0; overflow: hidden; border-radius: 6px;259 background: linear-gradient(180deg, #2b0f12 0%, #6b1a1a 48%, #12090a 49.5%, #0b0708 100%);260 box-shadow: 0 1px 0 rgba(255,255,255,0.15) inset, 0 40px 80px -30px rgba(0,0,0,0.9), 0 0 0 1px rgba(255,255,255,0.06);261}262.fd-wav-sun {263 position: absolute; left: 50%; top: 48%; width: 76%; aspect-ratio: 1; border-radius: 50%; transform: translate(-50%, -50%);264 background: linear-gradient(180deg, #FFE1A8 0%, #FFB547 30%, #FF5A36 52%);265 -webkit-mask-image: linear-gradient(180deg, #000 0 30%, transparent 30% 33%, #000 33% 40%, transparent 40% 42.5%, #000 42.5% 47%, transparent 47% 49%, #000 49% 52%, transparent 52%);266 mask-image: linear-gradient(180deg, #000 0 30%, transparent 30% 33%, #000 33% 40%, transparent 40% 42.5%, #000 42.5% 47%, transparent 47% 49%, #000 49% 52%, transparent 52%);267 overflow: hidden;268}269.fd-wav-sun::after { content: ""; position: absolute; inset: 0; border-radius: 50%; background: conic-gradient(from 0deg, transparent, rgba(255,255,255,0.35), transparent 30%, rgba(255,60,40,0.4) 55%, transparent 70%); mix-blend-mode: overlay; animation: fd-wav-spin 18s linear infinite; }270.fd-wav-horizon { position: absolute; left: 0; right: 0; top: 49.5%; bottom: 0; background: repeating-linear-gradient(180deg, rgba(255,120,60,0.55) 0 2px, transparent 2px 9px); -webkit-mask-image: linear-gradient(180deg, #000, transparent 85%); mask-image: linear-gradient(180deg, #000, transparent 85%); }271.fd-wav-grain { position: absolute; inset: 0; background: ${noise}; mix-blend-mode: multiply; opacity: 0.8; }272.fd-wav-art-t { position: absolute; left: 18px; bottom: 16px; font: 800 1.375rem/0.9 var(--fd-wav-cond); font-stretch: condensed; letter-spacing: -0.03em; color: #FFE1A8; }273.fd-wav-art-a { position: absolute; right: 18px; top: 16px; font: 600 0.625rem/1 var(--fd-wav-sans); letter-spacing: 0.24em; text-transform: uppercase; color: rgba(255,225,168,0.8); }274.fd-wav-vinyl {275 position: absolute; inset: 3%; border-radius: 50%;276 background: repeating-radial-gradient(circle at 50% 50%, #161112 0 1px, #0a0708 1px 3px), radial-gradient(circle, #1a1213, #050404);277 box-shadow: 0 20px 50px -20px rgba(0,0,0,0.9), inset 0 0 0 1px rgba(255,255,255,0.05);278 transform: translateX(0); animation: fd-wav-slide 1400ms var(--fd-ease-out) 1100ms both;279}280.fd-wav-vinyl::before { content: ""; position: absolute; inset: 0; border-radius: 50%; background: conic-gradient(from 20deg, transparent 0 10%, rgba(255,255,255,0.12) 14%, transparent 20% 55%, rgba(255,255,255,0.08) 60%, transparent 66%); }281.fd-wav-label { position: absolute; left: 50%; top: 50%; width: 34%; aspect-ratio: 1; margin: -17% 0 0 -17%; border-radius: 50%; display: grid; place-items: center; background: radial-gradient(circle, #0E0A0B 0 5%, #FF5A36 6% 100%); animation: fd-wav-spin 1.8s linear infinite; animation-play-state: paused; }282.fd-wav[data-playing] .fd-wav-label { animation-play-state: running; }283.fd-wav-label span { font: 800 0.5625rem/1 var(--fd-wav-sans); letter-spacing: 0.2em; color: #0E0A0B; transform: translateY(-14px); }284 285.fd-wav-player { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 18px 22px; align-items: center; max-width: 520px; margin: 11vw 0 0 auto; animation: fd-wav-rise var(--fd-dur-4) var(--fd-ease-out) 900ms both; }286.fd-wav-play {287 appearance: none; -webkit-appearance: none; cursor: pointer; display: grid; place-items: center; width: 84px; height: 84px; margin: 0; padding: 0; border: 0; border-radius: 9999px;288 background: var(--fd-wav-ink); color: var(--fd-wav-bg); box-shadow: 0 0 0 8px rgba(246,237,228,0.06), 0 20px 50px -14px rgba(255,90,54,0.65);289 transition: transform var(--fd-dur-2) var(--fd-spring), box-shadow var(--fd-dur-3) var(--fd-ease-out);290}291.fd-wav-play svg { display: block; }292.fd-wav-icon { transition: d var(--fd-dur-3) var(--fd-ease-in-out); }293.fd-wav-play:hover { box-shadow: 0 0 0 12px rgba(246,237,228,0.08), 0 24px 60px -14px rgba(255,90,54,0.8); }294.fd-wav-play:active { transform: scale(0.96); transition-duration: var(--fd-dur-1); }295.fd-wav-play:focus-visible, .fd-wav-cta:focus-visible { outline: 2px solid rgba(255,181,71,0.8); outline-offset: 3px; }296.fd-wav-now { min-width: 0; }297.fd-wav-now-k { display: block; margin: 0 0 6px; font: 600 0.6875rem/1 var(--fd-wav-sans); letter-spacing: 0.18em; text-transform: uppercase; color: var(--fd-wav-cool); }298.fd-wav-now b { display: block; font: 600 1.375rem/1.1 var(--fd-wav-sans); letter-spacing: -0.02em; color: var(--fd-wav-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }299.fd-wav-bar { position: relative; height: 3px; margin: 14px 0 8px; border-radius: 9999px; background: rgba(246,237,228,0.14); overflow: hidden; }300.fd-wav-bar span { position: absolute; inset: 0; background: linear-gradient(90deg, var(--fd-wav-hot), var(--fd-wav-cool)); transform-origin: left; transform: scaleX(0.33); }301.fd-wav-times { display: flex; justify-content: space-between; font: 500 0.75rem/1 ui-monospace, "SF Mono", Menlo, monospace; color: rgba(246,237,228,0.5); font-variant-numeric: tabular-nums; }302.fd-wav-tracks { grid-column: 1 / -1; margin: 6px 0 0; padding: 0; list-style: none; border-top: 1px solid rgba(246,237,228,0.1); }303.fd-wav-tracks li { display: grid; grid-template-columns: 34px minmax(0, 1fr) auto; gap: 8px; margin: 0; padding: 12px 0; border-bottom: 1px solid rgba(246,237,228,0.08); font: 400 0.9375rem/1.2 var(--fd-wav-sans); color: rgba(246,237,228,0.6); }304.fd-wav-tracks li.is-on { color: var(--fd-wav-ink); }305.fd-wav-tracks li.is-on .fd-wav-n { color: var(--fd-wav-hot); }306.fd-wav-n, .fd-wav-tm { font: 500 0.75rem/1.6 ui-monospace, "SF Mono", Menlo, monospace; color: rgba(246,237,228,0.4); }307.fd-wav-tt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }308.fd-wav-cta { grid-column: 1 / -1; justify-self: start; display: inline-flex; align-items: center; height: 46px; margin: 4px 0 0; padding: 0 22px; border-radius: 9999px; border: 1px solid rgba(246,237,228,0.25); color: var(--fd-wav-ink); text-decoration: none; font: 600 0.875rem/1 var(--fd-wav-sans); letter-spacing: 0.02em; transition: background var(--fd-dur-2), color var(--fd-dur-2), transform var(--fd-dur-2) var(--fd-spring); }309.fd-wav-cta:hover { background: var(--fd-wav-ink); color: var(--fd-wav-bg); }310.fd-wav-cta:active { transform: scale(0.96); }311 312.fd-wav-spec { position: absolute; z-index: 2; left: 0; right: 0; bottom: 0; height: 200px; display: flex; align-items: flex-end; gap: 3px; padding: 0 3px; pointer-events: none; -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }313.fd-wav-b { flex: 1; height: 100%; border-radius: 3px 3px 0 0; transform-origin: bottom; transform: scaleY(0.1); will-change: transform; background: linear-gradient(180deg, color-mix(in srgb, var(--fd-wav-cool) calc(var(--u) * 100%), var(--fd-wav-hot)) 0%, rgba(255,90,54,0.05) 100%); opacity: 0.85; }314 315@keyframes fd-wav-in { from { transform: translateY(100%) scaleY(1.6); } to { transform: scaleY(1.12); } }316@keyframes fd-wav-fade { from { opacity: 0; } to { opacity: 1; } }317@keyframes fd-wav-rise { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: none; } }318@keyframes fd-wav-sleevein { from { opacity: 0; transform: perspective(1400px) rotateY(-50deg) translateX(-80px); filter: blur(10px); } to { opacity: 1; filter: none; } }319@keyframes fd-wav-slide { from { transform: translateX(0) rotate(0deg); } to { transform: translateX(38%) rotate(140deg); } }320@keyframes fd-wav-spin { to { transform: rotate(360deg); } }321 322@media (max-width: 900px) {323 .fd-wav { min-height: 844px; }324 .fd-wav-top { padding: 84px 20px 0; }325 .fd-wav-release { display: none; }326 .fd-wav-big { flex-direction: column; align-items: center; gap: 0; font-size: 27vw; margin-top: 14px; }327 .fd-wav-stage { grid-template-columns: 1fr; gap: 28px; margin-top: 24px; padding: 0 20px; }328 .fd-wav-sleeve { width: 210px; margin: 0 auto 0 8px; }329 .fd-wav-player { margin: 0; grid-template-columns: auto minmax(0, 1fr); }330 .fd-wav-stage { align-items: start; }331 .fd-wav-play { width: 64px; height: 64px; }332 .fd-wav-tracks, .fd-wav-cta { display: none; }333 .fd-wav-spec { height: 120px; }334}335@media (prefers-reduced-motion: reduce) {336 .fd-wav *, .fd-wav *::before { animation: none !important; }337 .fd-wav-c { transform: scaleY(1.12); }338 .fd-wav-vinyl { transform: translateX(38%); }339}340`;341 342/* ───────────────────────── Demo ───────────────────────── */343 344export function Demo() {345 return (346 <div className="fd-wav-demo">347 <style>{demoCss}</style>348 <header className="fd-wav-demo-nav">349 <a className="fd-wav-demo-logo" href="#top">350 <svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true">351 <path d="M3 14v-4M7 18V6M11 15V9M15 20V4M19 16V8" stroke="#FF5A36" strokeWidth="2.4" strokeLinecap="round" />352 </svg>353 Resonant354 </a>355 <nav aria-label="Primary">356 <a href="#home">Home</a>357 <a href="#new">New releases</a>358 <a href="#live">Live</a>359 <a href="#library">Library</a>360 </nav>361 <a className="fd-wav-demo-cta" href="#trial">Try 3 months free</a>362 </header>363 <HeroMusicWaveform />364 </div>365 );366}367 368const demoCss = `369.fd-wav-demo { position: relative; margin: 0; padding: 0; min-height: 100vh; background: #0E0A0B; }370.fd-wav-demo *, .fd-wav-demo *::before, .fd-wav-demo *::after { box-sizing: border-box; }371.fd-wav-demo-nav { position: absolute; z-index: 10; top: 0; left: 0; right: 0; max-width: 1320px; height: 72px; margin: 0 auto; padding: 0 40px; display: flex; align-items: center; justify-content: space-between; gap: 20px; }372.fd-wav-demo-logo { display: inline-flex; align-items: center; gap: 9px; margin: 0; color: #F6EDE4; text-decoration: none; font: 700 1.125rem/1 ui-sans-serif, -apple-system, "Inter", sans-serif; letter-spacing: -0.03em; }373.fd-wav-demo-logo svg { display: block; }374.fd-wav-demo-nav nav { display: flex; gap: 28px; }375.fd-wav-demo-nav nav a { color: rgba(246,237,228,0.6); text-decoration: none; font: 500 0.875rem/1 ui-sans-serif, -apple-system, "Inter", sans-serif; transition: color 120ms; }376.fd-wav-demo-nav nav a:hover { color: #F6EDE4; }377.fd-wav-demo-cta { display: inline-flex; align-items: center; height: 36px; padding: 0 16px; border-radius: 9999px; background: #FF5A36; color: #0E0A0B; text-decoration: none; font: 700 0.8125rem/1 ui-sans-serif, -apple-system, "Inter", sans-serif; }378.fd-wav-demo-nav a:focus-visible { outline: 2px solid rgba(255,181,71,.8); outline-offset: 2px; border-radius: 8px; }379@media (max-width: 900px) { .fd-wav-demo-nav { padding: 0 20px; height: 64px; } .fd-wav-demo-nav nav { display: none; } }380`;A photoreal CSS-3D metal card that tilts to the cursor, then flips and docks on scroll while spend stats count up.
A terminal types a prompt and streams an agent's plan while the generated dashboard assembles itself beside it.
A giant serif masthead and procedural duotone fabric frames revealed by clip-path wipes, drifting apart on scroll.