Liquid Metal
Flowing chrome: a warped height field reflecting a procedural studio, in silver, gold, titanium, black chrome or oil-slick.
A raymarched drop of mercury that morphs, merges and reaches for your cursor — a hero centrepiece on a transparent canvas.
npx shadcn@latest add https://freedesignmd.com/r/liquid-metal-blob.json1/**2 * LiquidMetalBlob — a morphing drop of mercury for hero centrepieces. A tiny3 * raymarcher blends a few metaball spheres with a smooth-min, wobbles the4 * surface with sine displacement and reflects a procedural studio. The blob5 * leans toward the cursor and a satellite droplet is pulled out by it.6 *7 * Props8 * - tint?: "silver" | "gold" | "titanium" | "black-chrome". Default "silver".9 * - speed?: number Morph speed. Default 1.10 * - size?: number Blob radius relative to the canvas. Default 1.11 * - interactive?: boolean Follows the pointer. Default true.12 * - background?: string Canvas clear colour; "transparent" by default so it sits on any page.13 * - quality?: number Max DPR. Default 1.5.14 * CSS vars: --fd-lmblob-fallback.15 *16 * Browser support: WebGL2 / WebGL1. Without WebGL a CSS chrome sphere is17 * shown. Reduced motion: one still frame. Canvas is aria-hidden.18 * Performance: 48-step raymarch with early exit, bounded by a sphere test —19 * pixels outside the blob's bounds cost almost nothing.20 */21import { useEffect, useRef, useState } from "react";22import type { CSSProperties, MutableRefObject, RefObject } from "react";23 24/* ───────────────────────── tiny WebGL stage ───────────────────────── */25 26type GL = WebGLRenderingContext | WebGL2RenderingContext;27interface Stage {28 gl: GL;29 gl2: boolean;30 prog: WebGLProgram;31 u: Record<string, WebGLUniformLocation | null>;32 w: number;33 h: number;34 dpr: number;35 reduced: boolean;36}37interface StageHooks {38 setup?: (s: Stage) => void;39 /** Set uniforms; return true to keep animating. */40 frame: (s: Stage, time: number, dt: number) => boolean;41}42 43function createStage(canvas: HTMLCanvasElement, frag: string): Stage | null {44 const attrs: WebGLContextAttributes = { antialias: false, alpha: true, premultipliedAlpha: true, powerPreference: "high-performance" };45 let gl: GL | null = canvas.getContext("webgl2", attrs) as WebGL2RenderingContext | null;46 const gl2 = !!gl;47 if (!gl) gl = (canvas.getContext("webgl", attrs) || canvas.getContext("experimental-webgl", attrs)) as WebGLRenderingContext | null;48 if (!gl) return null;49 const head = gl2 ? "#version 300 es\n" : "";50 const vs = head + (gl2 ? "#define attribute in\n" : "") + "attribute vec2 aP;void main(){gl_Position=vec4(aP,0.,1.);}";51 const fs =52 head +53 "#ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n#else\nprecision mediump float;\n#endif\n" +54 (gl2 ? "#define texture2D texture\n#define gl_FragColor fdOut\nout vec4 fdOut;\n" : "") +55 frag;56 const make = (type: number, src: string) => {57 const s = gl!.createShader(type);58 if (!s) return null;59 gl!.shaderSource(s, src);60 gl!.compileShader(s);61 if (!gl!.getShaderParameter(s, gl!.COMPILE_STATUS)) {62 console.warn("[fd-shader]", gl!.getShaderInfoLog(s));63 return null;64 }65 return s;66 };67 const v = make(gl.VERTEX_SHADER, vs);68 const f = make(gl.FRAGMENT_SHADER, fs);69 const prog = gl.createProgram();70 if (!v || !f || !prog) return null;71 gl.attachShader(prog, v);72 gl.attachShader(prog, f);73 gl.bindAttribLocation(prog, 0, "aP");74 gl.linkProgram(prog);75 if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) return null;76 gl.useProgram(prog);77 const buf = gl.createBuffer();78 gl.bindBuffer(gl.ARRAY_BUFFER, buf);79 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);80 gl.enableVertexAttribArray(0);81 gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);82 const u: Stage["u"] = {};83 for (const m of frag.matchAll(/uniform\s+\w+\s+(\w+)/g)) u[m[1]] = gl.getUniformLocation(prog, m[1]);84 return { gl, gl2, prog, u, w: 1, h: 1, dpr: 1, reduced: false };85}86 87function useStage(ref: RefObject<HTMLCanvasElement | null>, frag: string, hooks: MutableRefObject<StageHooks>, maxDpr = 2) {88 const [failed, setFailed] = useState(false);89 const kick = useRef<() => void>(() => {});90 useEffect(() => {91 const canvas = ref.current;92 if (!canvas) return;93 const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;94 let stage: Stage | null = null;95 let raf = 0;96 let last = 0;97 let time = 0;98 let visible = true;99 let alive = true;100 const loop = (now: number) => {101 raf = 0;102 if (!stage || !alive) return;103 const dt = last ? Math.min((now - last) / 1000, 0.05) : 1 / 60;104 last = now;105 if (!reduced) time += dt;106 const more = hooks.current.frame(stage, time, dt);107 stage.gl.drawArrays(stage.gl.TRIANGLES, 0, 3);108 if (more && !reduced && visible && !document.hidden) raf = requestAnimationFrame(loop);109 else last = 0;110 };111 const request = () => {112 if (!raf && alive && stage) raf = requestAnimationFrame(loop);113 };114 const resize = () => {115 if (!stage) return;116 const r = canvas.getBoundingClientRect();117 const dpr = Math.min(window.devicePixelRatio || 1, maxDpr);118 const w = Math.max(1, Math.round(r.width * dpr));119 const h = Math.max(1, Math.round(r.height * dpr));120 if (canvas.width !== w || canvas.height !== h) {121 canvas.width = w;122 canvas.height = h;123 }124 stage.w = w;125 stage.h = h;126 stage.dpr = dpr;127 stage.gl.viewport(0, 0, w, h);128 request();129 };130 const init = () => {131 stage = createStage(canvas, frag);132 if (!stage) {133 setFailed(true);134 return;135 }136 stage.reduced = reduced;137 hooks.current.setup?.(stage);138 resize();139 };140 const onLost = (e: Event) => {141 e.preventDefault();142 cancelAnimationFrame(raf);143 raf = 0;144 stage = null;145 };146 const onRestored = () => init();147 const onVis = () => !document.hidden && request();148 canvas.addEventListener("webglcontextlost", onLost);149 canvas.addEventListener("webglcontextrestored", onRestored);150 document.addEventListener("visibilitychange", onVis);151 const ro = new ResizeObserver(resize);152 ro.observe(canvas);153 const io = new IntersectionObserver(([e]) => {154 visible = e.isIntersecting;155 if (visible) request();156 });157 io.observe(canvas);158 kick.current = request;159 init();160 return () => {161 alive = false;162 cancelAnimationFrame(raf);163 ro.disconnect();164 io.disconnect();165 canvas.removeEventListener("webglcontextlost", onLost);166 canvas.removeEventListener("webglcontextrestored", onRestored);167 document.removeEventListener("visibilitychange", onVis);168 kick.current = () => {};169 };170 }, [ref, frag, hooks, maxDpr]);171 return { failed, kick };172}173 174function makeTexture(gl: GL, filter: number, wrap: number) {175 const t = gl.createTexture();176 gl.bindTexture(gl.TEXTURE_2D, t);177 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);178 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);179 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap);180 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap);181 return t;182}183 184const hexToRgb = (hex: string): [number, number, number] => {185 let h = hex.replace("#", "").trim();186 if (h.length === 3) h = h.split("").map((c) => c + c).join("");187 const n = parseInt(h.slice(0, 6), 16) || 0;188 return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];189};190 191/* ───────────────────────── shader ───────────────────────── */192 193const FRAG = /* glsl */ `194uniform vec2 uRes;195uniform float uTime;196uniform vec2 uPtr; // -1..1 pointer, smoothed197uniform float uPull; // 0..1 pointer presence198uniform float uTint;199uniform float uSize;200 201float smin(float a, float b, float k){202 float h = clamp(.5 + .5 * (b - a) / k, 0., 1.);203 return mix(b, a, h) - k * h * (1. - h);204}205 206vec3 c0, c1, c2, c3;207 208float map(vec3 p){209 float t = uTime;210 float d = length(p - c0) - .62 * uSize;211 d = smin(d, length(p - c1) - .34 * uSize, .32);212 d = smin(d, length(p - c2) - .28 * uSize, .3);213 d = smin(d, length(p - c3) - (.16 + .06 * uPull) * uSize, .3);214 // liquid surface wobble215 d += .035 * sin(p.x * 5. + t * 1.7) * sin(p.y * 4.3 - t * 1.3) * sin(p.z * 4.7 + t);216 return d;217}218 219vec3 normalAt(vec3 p){220 vec2 e = vec2(.0015, 0.);221 return normalize(vec3(map(p + e.xyy) - map(p - e.xyy), map(p + e.yxy) - map(p - e.yxy), map(p + e.yyx) - map(p - e.yyx)));222}223 224vec3 studio(vec3 r){225 float el = r.y;226 float az = atan(r.x, -r.z);227 vec3 sky = mix(vec3(.24, .25, .28), vec3(.96, .97, .99), smoothstep(-.02, .75, el + .2 * abs(az)));228 // studio floor: dark contact band at the horizon, lifting to a soft grey underneath229 vec3 fl = mix(vec3(.04, .04, .045), vec3(.42, .42, .44), smoothstep(-.12, -.85, el));230 vec3 c = mix(fl, sky, smoothstep(-.08, -.03, el));231 c += .7 * exp(-pow((el + .01) / .025, 2.));232 c += 1.6 * smoothstep(.1, 0., abs(az + .7) - .25) * smoothstep(.06, 0., abs(el - .4) - .22);233 c += 1.2 * smoothstep(.05, 0., abs(az - .9) - .07) * smoothstep(.05, 0., abs(el - .15) - .32);234 c += .6 * smoothstep(.2, 0., abs(el - .92) - .05);235 c *= 1. - .85 * smoothstep(.07, 0., abs(az - .2) - .06) * smoothstep(-.05, .1, el);236 c *= 1. - .7 * smoothstep(.12, 0., abs(abs(az) - 2.6) - .3) * smoothstep(-.05, .1, el);237 return c;238}239 240void main(){241 vec2 uv = (gl_FragCoord.xy - .5 * uRes) / min(uRes.x, uRes.y);242 float t = uTime;243 vec2 ptr = uPtr * uPull;244 c0 = vec3(ptr * .1, 0.);245 // satellites orbit just far enough to stretch, pinch off and re-merge246 c1 = vec3(sin(t * .7) * .72, cos(t * .9) * .42, sin(t * .5) * .3) * uSize + vec3(ptr * .15, 0.);247 c2 = vec3(cos(t * .6 + 2.) * .66, sin(t * .8 + 1.) * .6, cos(t * .7) * .3) * uSize;248 c3 = mix(vec3(sin(t * .5 + 4.) * .95, cos(t * .45) * .8, .2) * uSize, vec3(ptr * vec2(.85, .7), .3), uPull * .7);249 250 vec3 ro = vec3(0., 0., 4.);251 vec3 rd = normalize(vec3(uv, -1.62));252 // bounding sphere — skip the march for background pixels253 float R = 1.3 * uSize + .2;254 float b = dot(ro, rd);255 float cc = dot(ro, ro) - R * R;256 float disc = b * b - cc;257 vec4 outc = vec4(0.);258 if (disc > 0.) {259 float tt = max(-b - sqrt(disc), 0.);260 float tEnd = -b + sqrt(disc);261 bool hit = false;262 for (int i = 0; i < 48; i++) {263 float d = map(ro + rd * tt);264 if (d < .0015) { hit = true; break; }265 tt += d * .9;266 if (tt > tEnd) break;267 }268 if (hit) {269 vec3 p = ro + rd * tt;270 vec3 n = normalAt(p);271 vec3 r = reflect(rd, n);272 vec3 env = studio(r);273 float fres = pow(1. - clamp(dot(n, -rd), 0., 1.), 5.);274 vec3 tint = uTint < .5 ? vec3(.96, .965, .975) : uTint < 1.5 ? vec3(1., .78, .43) : uTint < 2.5 ? vec3(.78, .8, .85) : vec3(.5);275 vec3 col = env * mix(tint, vec3(1.), fres);276 if (uTint > 2.5) col = pow(env, vec3(1.6)) * .6 + .02;277 // fake contact occlusion where lobes merge278 float ao = clamp(map(p + n * .12) / .12, 0., 1.);279 col *= .55 + .45 * ao;280 col = col / (1. + col * .25) * 1.15;281 outc = vec4(col, 1.);282 }283 }284 // soft floor shadow / reflection glow under the blob285 vec2 sq = (uv - vec2(ptr.x * .04, -.36 * uSize - .02)) * vec2(1., 6.) / (.24 * uSize);286 float sh = exp(-dot(sq, sq)) * .26;287 outc.a = outc.a + sh * (1. - outc.a);288 gl_FragColor = outc;289}290`;291 292/* ───────────────────────── component ───────────────────────── */293 294type Tint = "silver" | "gold" | "titanium" | "black-chrome";295const TINTS: Record<Tint, number> = { silver: 0, gold: 1, titanium: 2, "black-chrome": 3 };296const FALLBACK: Record<Tint, string> = {297 silver: "radial-gradient(circle at 34% 28%, #ffffff 0 8%, #c9ccd2 22%, #3a3c41 52%, #8a8e96 72%, #1b1c20 100%)",298 gold: "radial-gradient(circle at 34% 28%, #fff6dc 0 8%, #e2bd72 22%, #4d3712 52%, #b0853a 72%, #2a1d08 100%)",299 titanium: "radial-gradient(circle at 34% 28%, #f3f5f8 0 8%, #aeb5bf 22%, #30343a 52%, #737a85 72%, #15171b 100%)",300 "black-chrome": "radial-gradient(circle at 34% 28%, #9a9da4 0 6%, #3f4248 20%, #040405 52%, #2a2c31 72%, #000 100%)",301};302 303export interface LiquidMetalBlobProps {304 tint?: Tint;305 speed?: number;306 size?: number;307 interactive?: boolean;308 background?: string;309 quality?: number;310 className?: string;311 style?: CSSProperties;312}313 314export function LiquidMetalBlob({315 tint = "silver",316 speed = 1,317 size = 1,318 interactive = true,319 background = "transparent",320 quality = 1.5,321 className,322 style,323}: LiquidMetalBlobProps) {324 const rootRef = useRef<HTMLDivElement>(null);325 const canvasRef = useRef<HTMLCanvasElement>(null);326 const st = useRef({ t: 0, x: 0, y: 0, tx: 0, ty: 0, pull: 0, tpull: 0, tint, speed, size });327 st.current.tint = tint;328 st.current.speed = speed;329 st.current.size = size;330 331 const hooks = useRef<StageHooks>({332 setup(s) {333 s.gl.clearColor(0, 0, 0, 0);334 },335 frame(s, _time, dt) {336 const { gl, u } = s;337 const S = st.current;338 if (!s.reduced) S.t += dt * S.speed;339 const k = s.reduced ? 1 : 1 - Math.exp(-dt * 3.2);340 S.x += (S.tx - S.x) * k;341 S.y += (S.ty - S.y) * k;342 S.pull += (S.tpull - S.pull) * (s.reduced ? 1 : 1 - Math.exp(-dt * 2.2));343 gl.clear(gl.COLOR_BUFFER_BIT);344 gl.uniform2f(u.uRes, s.w, s.h);345 gl.uniform1f(u.uTime, S.t + 2.4);346 gl.uniform2f(u.uPtr, S.x, S.y);347 gl.uniform1f(u.uPull, S.pull);348 gl.uniform1f(u.uTint, TINTS[S.tint] ?? 0);349 gl.uniform1f(u.uSize, S.size);350 return true;351 },352 });353 const { failed, kick } = useStage(canvasRef, FRAG, hooks, quality);354 useEffect(() => kick.current(), [tint, size, kick]);355 356 useEffect(() => {357 const el = rootRef.current;358 if (!el || !interactive) return;359 if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return;360 // Track the pointer page-wide so the blob reacts before you reach it.361 const move = (e: PointerEvent) => {362 const r = el.getBoundingClientRect();363 const m = Math.min(r.width, r.height);364 const x = (e.clientX - (r.left + r.width / 2)) / (m / 2);365 const y = -(e.clientY - (r.top + r.height / 2)) / (m / 2);366 const d = Math.hypot(x, y);367 const clamp = d > 1.2 ? 1.2 / d : 1;368 st.current.tx = x * clamp;369 st.current.ty = y * clamp;370 st.current.tpull = Math.max(0, 1 - Math.max(0, d - 1.4) / 1.6);371 };372 const leave = () => {373 st.current.tpull = 0;374 };375 window.addEventListener("pointermove", move, { passive: true });376 document.addEventListener("pointerleave", leave);377 return () => {378 window.removeEventListener("pointermove", move);379 document.removeEventListener("pointerleave", leave);380 };381 }, [interactive]);382 383 return (384 <div ref={rootRef} className={`fd-lmblob${className ? " " + className : ""}`} style={{ background, ["--fd-lmblob-fallback" as string]: FALLBACK[tint], ...style }}>385 <style>{css}</style>386 {failed ? <div className="fd-lmblob-fallback" aria-hidden="true" /> : <canvas ref={canvasRef} className="fd-lmblob-canvas" aria-hidden="true" />}387 </div>388 );389}390 391const css = `392.fd-lmblob, .fd-lmblob *, .fd-lmblob *::before, .fd-lmblob *::after { box-sizing: border-box; }393.fd-lmblob { position: relative; display: block; margin: 0; padding: 0; border: 0; aspect-ratio: 1; }394.fd-lmblob-canvas { position: absolute; inset: 0; display: block; width: 100%; height: 100%; margin: 0; padding: 0; border: 0; }395.fd-lmblob-fallback {396 position: absolute; left: 22%; top: 20%; width: 56%; height: 56%; margin: 0; padding: 0; border: 0; border-radius: 50%;397 background: var(--fd-lmblob-fallback);398 box-shadow: 0 40px 60px -30px rgba(0,0,0,.6);399 animation: fd-lmblob-morph 9s cubic-bezier(0.65, 0, 0.35, 1) infinite alternate;400}401@keyframes fd-lmblob-morph {402 0% { border-radius: 50% 50% 48% 52% / 52% 48% 52% 48%; }403 50% { border-radius: 58% 42% 55% 45% / 45% 58% 42% 55%; }404 100% { border-radius: 44% 56% 46% 54% / 56% 44% 56% 44%; }405}406@media (prefers-reduced-motion: reduce) { .fd-lmblob-fallback { animation: none; } }407`;408 409/* ───────────────────────── demo ───────────────────────── */410 411export function Demo() {412 return (413 <section className="fd-lmblob-demo">414 <style>{demoCss}</style>415 <header className="fd-lmblob-demo-nav">416 <span className="fd-lmblob-demo-logo">417 <span className="fd-lmblob-demo-logodot" aria-hidden="true" /> Quicksilver418 </span>419 <nav className="fd-lmblob-demo-links" aria-label="Primary">420 <a href="#product" onClick={(e) => e.preventDefault()}>Product</a>421 <a href="#research" onClick={(e) => e.preventDefault()}>Research</a>422 <a href="#enterprise" onClick={(e) => e.preventDefault()}>Enterprise</a>423 </nav>424 <a className="fd-lmblob-demo-signin" href="#signin" onClick={(e) => e.preventDefault()}>Sign in</a>425 </header>426 <div className="fd-lmblob-demo-hero">427 <LiquidMetalBlob className="fd-lmblob-demo-blob" />428 <div className="fd-lmblob-demo-copy">429 <p className="fd-lmblob-demo-kicker">Introducing Quicksilver 3</p>430 <h1 className="fd-lmblob-demo-title">An assistant that takes the shape of your work.</h1>431 <p className="fd-lmblob-demo-body">Quicksilver adapts to your docs, your codebase and your team's voice — then gets out of the way. Now with 1M-token memory and on-device mode.</p>432 <div className="fd-lmblob-demo-actions">433 <a className="fd-lmblob-demo-primary" href="#try" onClick={(e) => e.preventDefault()}>Try it free</a>434 <a className="fd-lmblob-demo-secondary" href="#film" onClick={(e) => e.preventDefault()}>Watch the film · 2:14</a>435 </div>436 </div>437 </div>438 <p className="fd-lmblob-demo-trust">Trusted by teams at Halcyon Bio, Nordlicht Energie, Pillar Health and 4,000+ others</p>439 </section>440 );441}442 443const demoCss = `444.fd-lmblob-demo, .fd-lmblob-demo *, .fd-lmblob-demo *::before, .fd-lmblob-demo *::after { box-sizing: border-box; }445.fd-lmblob-demo {446 min-height: 100vh; margin: 0; padding: 0 clamp(20px, 4vw, 56px) 32px; overflow: hidden;447 display: flex; flex-direction: column;448 background: linear-gradient(180deg, #F4F4F6 0%, #E9E9ED 100%); color: #0B0B0F;449 font: 400 15px/1.5 ui-sans-serif, -apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter", "Segoe UI", Roboto, sans-serif;450}451.fd-lmblob-demo-nav { display: flex; align-items: center; gap: 32px; height: 72px; margin: 0; padding: 0; }452.fd-lmblob-demo-logo { display: inline-flex; align-items: center; gap: 9px; margin: 0 auto 0 0; padding: 0; color: #0B0B0F; font: 650 17px/1 ui-sans-serif, -apple-system, "SF Pro Text", "Inter", "Segoe UI", Roboto, sans-serif; letter-spacing: -0.02em; }453.fd-lmblob-demo-logodot { display: block; width: 18px; height: 18px; margin: 0; padding: 0; border-radius: 50%; background: radial-gradient(circle at 35% 30%, #fff 0 12%, #b9bcc3 30%, #2b2c30 62%, #8d9097 100%); box-shadow: 0 2px 4px rgba(0,0,0,.2); }454.fd-lmblob-demo-links { display: flex; gap: 28px; margin: 0; padding: 0; }455.fd-lmblob-demo-links a, .fd-lmblob-demo-signin { margin: 0; padding: 0; color: #4A4A55; text-decoration: none; font: 500 14px/1 ui-sans-serif, -apple-system, "SF Pro Text", "Inter", "Segoe UI", Roboto, sans-serif; letter-spacing: 0; transition: color 120ms cubic-bezier(0.22, 1, 0.36, 1); }456.fd-lmblob-demo-links a:hover, .fd-lmblob-demo-signin:hover { color: #0B0B0F; }457.fd-lmblob-demo-hero { flex: 1; display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); align-items: center; gap: clamp(16px, 4vw, 64px); max-width: 1280px; width: 100%; margin: 0 auto; padding: 0; }458.fd-lmblob-demo-blob.fd-lmblob { width: 118%; max-width: 760px; margin: -6% -9%; justify-self: center; }459.fd-lmblob-demo-copy { max-width: 520px; margin: 0; padding: 0; }460.fd-lmblob-demo-kicker { margin: 0 0 16px; padding: 0; color: #6B6B76; font: 600 13px/1.3 ui-sans-serif, -apple-system, "SF Pro Text", "Inter", "Segoe UI", Roboto, sans-serif; letter-spacing: 0.04em; text-transform: uppercase; }461.fd-lmblob-demo-title { margin: 0 0 20px; padding: 0; color: #0B0B0F; font: 700 clamp(2.5rem, 4.8vw, 4.25rem)/1.0 "SF Pro Display", ui-sans-serif, -apple-system, "Inter", "Segoe UI", Roboto, sans-serif; letter-spacing: -0.04em; }462.fd-lmblob-demo-body { max-width: 44ch; margin: 0 0 32px; padding: 0; color: #4A4A55; font: 400 1.0625rem/1.55 ui-sans-serif, -apple-system, "SF Pro Text", "Inter", "Segoe UI", Roboto, sans-serif; letter-spacing: -0.01em; }463.fd-lmblob-demo-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 24px; margin: 0; padding: 0; }464.fd-lmblob-demo-primary { display: inline-flex; align-items: center; height: 50px; margin: 0; padding: 0 26px; border: 0; border-radius: 9999px; background: #0B0B0F; color: #F7F7F9; text-decoration: none; font: 600 15px/1 ui-sans-serif, -apple-system, "SF Pro Text", "Inter", "Segoe UI", Roboto, sans-serif; letter-spacing: -0.01em; transition: transform 120ms cubic-bezier(0.22, 1, 0.36, 1), background 120ms cubic-bezier(0.22, 1, 0.36, 1); }465.fd-lmblob-demo-primary:hover { background: #2A2A31; }466.fd-lmblob-demo-primary:active { transform: scale(0.96); }467.fd-lmblob-demo-secondary { margin: 0; padding: 0; color: #0B0B0F; text-decoration: none; font: 550 15px/1 ui-sans-serif, -apple-system, "SF Pro Text", "Inter", "Segoe UI", Roboto, sans-serif; }468.fd-lmblob-demo-secondary:hover { text-decoration: underline; text-underline-offset: 4px; }469.fd-lmblob-demo a:focus-visible { outline: 2px solid rgba(0,122,255,.7); outline-offset: 2px; border-radius: 6px; }470.fd-lmblob-demo-trust { margin: 0; padding: 0; text-align: center; color: #8A8A94; font: 500 13px/1.4 ui-sans-serif, -apple-system, "SF Pro Text", "Inter", "Segoe UI", Roboto, sans-serif; }471@media (max-width: 860px) {472 .fd-lmblob-demo-hero { grid-template-columns: 1fr; text-align: left; }473 .fd-lmblob-demo-blob.fd-lmblob { width: 100%; max-width: 380px; margin: -8% 0; }474 .fd-lmblob-demo-links { display: none; }475}476`;Flowing chrome: a warped height field reflecting a procedural studio, in silver, gold, titanium, black chrome or oil-slick.
A capsule CTA with a live chrome rim that ripples from your touch — plus a zero-JS CSS chrome variant.
A giant headline cast in flowing chrome with bevelled edges; tilt your pointer and the studio lights slide across the letters.