# Hero section

> Three full screen SaaS heroes: a live dashboard rising from the bottom edge over a drifting mesh, a workflow graph that routes sample events node by node, and editorial type over a mesh gradient.

A shadcn/ui block from the [@uiarc](https://uiarc.dev) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/uiarc/hero-section).

- Page, with a live preview: https://shadcn.exchange/r/uiarc/hero-section
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/uiarc/hero-section-light-fc7f25bcc151554f.png
- Registry item: https://uiarc.dev/r/hero-section.json
- Categories: blocks
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://uiarc.dev/r/hero-section.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `lucide-react`, `motion`
- Registry items: `https://uiarc.dev/r/arc-motion-tokens.json`, `https://uiarc.dev/r/avatar.json`, `https://uiarc.dev/r/button.json`, `https://uiarc.dev/r/copy-button.json`, `https://uiarc.dev/r/line-chart.json`, `https://uiarc.dev/r/segmented-control.json`, `https://uiarc.dev/r/sparkline.json`

## Notes from the registry

Use this as the first section of a landing page and replace the sample copy and data with your own. Events in the workflow preview are simulated.

## Files

### `registry/blocks/hero-section/hero-section.tsx`

```tsx
"use client";

import { useState } from "react";
import type { ReactNode } from "react";
import SegmentedControl from "../../segmented-control/segmented-control";
import { HeroLumen } from "./hero-lumen";
import { HeroRelay } from "./hero-relay";
import { HeroCadence } from "./hero-cadence";
import { HeroMesh } from "./hero-mesh";
import type { HeroMeshPoint } from "./hero-mesh";
import { HeroContent } from "./hero-content";
import type { HeroAction, HeroInstallCommand } from "./hero-content";
import styles from "./hero-section.module.css";

export { HeroCadence, HeroContent, HeroLumen, HeroMesh, HeroRelay };
export type { HeroAction, HeroInstallCommand, HeroMeshPoint };

export type HeroSectionVariant = "centered" | "split" | "minimal";

export interface HeroSectionProps {
  /**
   * `centered` is a headline over a drifting mesh with a live dashboard rising from the bottom edge, `split` sets the copy
   * beside a live workflow graph that runs sample events node by node, and `minimal` is large type over a mesh gradient.
   * Each design fills one full screen.
   */
  variant?: HeroSectionVariant;
  /** Plays the entrance once on mount. Defaults to true. */
  animateIn?: boolean;
  /** The main call to action. With `doneLabel` and no `href`, the button confirms in place. */
  primaryAction?: HeroAction | null;
  /** The second call to action. */
  secondaryAction?: HeroAction | null;
  /**
   * Your own headline. When set, the hero renders your content in the chosen layout (see `HeroContent`) instead of the
   * designed sample, together with the props below.
   */
  title?: string;
  description?: string;
  announcement?: HeroAction | null;
  /** Install commands for your own content; the first one is shown with a copy button. */
  install?: HeroInstallCommand[] | null;
  /** A visual beside your own content in the split layout. */
  media?: ReactNode;
  /** Small facts under your own content, such as the version and license. */
  meta?: string[];
  /** Kept for compatibility; highlighted phrases render as plain text. */
  highlight?: string;
  className?: string;
}

/**
 * A full screen landing page hero in three designs: a product screenshot in perspective over a drifting mesh, a live workflow
 * graph that routes sample events, and editorial type over a mesh gradient.
 */
export function HeroSection({ variant = "centered", animateIn = true, primaryAction, secondaryAction, title, description, announcement, install, media, meta, className }: HeroSectionProps) {
  if (title) return <HeroContent layout={variant} title={title} description={description} announcement={announcement} primaryAction={primaryAction} secondaryAction={secondaryAction} install={install} media={media} meta={meta} animateIn={animateIn} className={className} />;
  const actions = { primaryAction: primaryAction ?? undefined, secondaryAction: secondaryAction ?? undefined };
  if (variant === "split") return <HeroRelay animateIn={animateIn} className={className} {...actions} />;
  if (variant === "minimal") return <HeroCadence animateIn={animateIn} className={className} {...actions} />;
  return <HeroLumen animateIn={animateIn} className={className} {...actions} />;
}

const variantOptions = [
  { value: "centered", label: "Screenshot" },
  { value: "split", label: "Workflow" },
  { value: "minimal", label: "Mesh" },
];

/**
 * Preview: the hero, full screen, with a small glass switch floating over its top edge. The switch takes no space of its own,
 * so every design fills the screen exactly. Switching replays the entrance.
 */
export function HeroSectionBlock({ variant: initial = "centered" }: { variant?: HeroSectionVariant }) {
  const [variant, setVariant] = useState<HeroSectionVariant>(initial);
  return <div className={styles.preview}>
    <HeroSection key={variant} variant={variant} />
    <div className={styles.switcher}>
      <SegmentedControl label="Hero design" options={variantOptions} value={variant} onValueChange={value => setVariant(value as HeroSectionVariant)} className={styles.switch} />
    </div>
  </div>;
}

export default HeroSectionBlock;
```

### `registry/blocks/hero-section/hero-section.module.css`

```css
/* Shared shell for the three hero variants. Each variant adds its own module for its one idea. */
.hero { position: relative; container: hero / inline-size; overflow: clip; background: var(--background); color: var(--foreground); font-family: var(--font-body); letter-spacing: var(--tracking-body); }

.title { margin: 0; font-family: var(--font-display); font-size: clamp(2.25rem, 1.3rem + 4.4cqi, 4.5rem); font-weight: 500; letter-spacing: var(--tracking-display); line-height: var(--leading-display); text-wrap: balance; }
.description { margin: 0; color: var(--text-secondary); font-size: var(--text-lg); line-height: var(--leading-body); text-wrap: pretty; }

@container hero (max-width: 560px) {
  .description { font-size: var(--text-base); }
}

/* The designed variants are one full screen each, edge to edge, like Hero signup. The small viewport unit keeps them from
   hiding under mobile browser bars. Your own content (HeroContent) keeps its natural height inside your page. */
.screen { width: 100%; min-height: 100vh; min-height: 100svh; }

/* Preview: the hero owns the whole frame; the design switch floats over its top edge as a small glass pill. */
.preview { position: relative; display: grid; width: 100%; min-height: 100vh; min-height: 100svh; }
.preview > .hero { min-height: inherit; }
.switcher {
  position: absolute; z-index: 20; top: var(--space-4); left: 50%; translate: -50% 0;
  display: flex; max-width: calc(100% - 2 * var(--space-4));
  border: 1px solid color-mix(in oklab, var(--foreground) 10%, transparent); border-radius: var(--radius-pill);
  background: color-mix(in oklab, var(--surface) 64%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(1.5); backdrop-filter: blur(18px) saturate(1.5);
  box-shadow: 0 1px 1px rgb(20 24 40 / .04), 0 10px 30px -12px rgb(20 24 40 / .22);
}
:global(:root[data-theme="dark"]) .switcher { border-color: color-mix(in oklab, var(--foreground) 14%, transparent); background: color-mix(in oklab, var(--surface) 58%, transparent); box-shadow: inset 0 1px 0 color-mix(in oklab, var(--foreground) 8%, transparent), 0 10px 30px -12px rgb(0 0 0 / .6); }
/* The segmented control drops its own well; the glass is its surface. */
.switcher .switch { border: 0; border-radius: var(--radius-pill); background: transparent; }
.switcher .switch :global(button) { border-radius: var(--radius-pill); }

.srOnly { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
```

### `registry/blocks/hero-section/hero-content.tsx`

```tsx
"use client";

import { useEffect, useState } from "react";
import type { ReactNode } from "react";
import { MotionConfig, motion } from "motion/react";
import { ArrowRight, ArrowUpRight, Check } from "lucide-react";
import { Button } from "../../button/button";
import { CopyButton } from "../../copy-button/copy-button";
import { heroGroup, heroRise } from "./hero-motion";
import shell from "./hero-section.module.css";
import styles from "./hero-content.module.css";

export interface HeroAction {
  label: string;
  href?: string;
  onClick?: () => void;
  /** Opens in a new tab and shows an outward arrow. */
  external?: boolean;
  /** Shown in place of the label for a moment after a press, such as "Trial started". Buttons only. */
  doneLabel?: string;
}

export interface HeroInstallCommand {
  /** Short label, such as npm or pnpm. The first command is shown. */
  label: string;
  command: string;
}

export interface HeroContentProps {
  layout: "centered" | "split" | "minimal";
  announcement?: HeroAction | null;
  title: string;
  description?: string;
  primaryAction?: HeroAction | null;
  secondaryAction?: HeroAction | null;
  install?: HeroInstallCommand[] | null;
  media?: ReactNode;
  meta?: string[];
  animateIn?: boolean;
  className?: string;
}

/** A link or button that looks like an Arc button. Links keep native navigation; a button with `doneLabel` confirms in place. */
export function ActionLink({ action, kind, className }: { action: HeroAction; kind: "primary" | "secondary"; className?: string }) {
  const [done, setDone] = useState(false);
  useEffect(() => {
    if (!done) return;
    const timer = window.setTimeout(() => setDone(false), 2400);
    return () => window.clearTimeout(timer);
  }, [done]);
  const cls = [styles.action, styles[kind], className].filter(Boolean).join(" ");
  const icon = action.external ? <ArrowUpRight size={16} strokeWidth={1.75} aria-hidden="true" /> : kind === "primary" ? <ArrowRight className={styles.arrow} size={16} strokeWidth={1.75} aria-hidden="true" /> : null;
  if (action.href) return <a className={cls} href={action.href} onClick={action.onClick} {...(action.external ? { target: "_blank", rel: "noopener noreferrer" } : {})}>{action.label}{icon}</a>;
  return <Button variant={kind} size="lg" className={[styles.button, className].filter(Boolean).join(" ")} onClick={() => { action.onClick?.(); if (action.doneLabel) setDone(true); }}>
    {done ? <><Check size={16} strokeWidth={1.75} aria-hidden="true" />{action.doneLabel}</> : <>{action.label}{icon}</>}
  </Button>;
}

/**
 * The hero with your own content: announcement, headline, description, actions, an optional install line, and an
 * optional visual beside the copy. Use it when none of the three designed variants fits the product.
 */
export function HeroContent({ layout, announcement, title, description, primaryAction, secondaryAction, install, media, meta, animateIn = true, className }: HeroContentProps) {
  const item = heroRise;
  const command = install?.[0];
  // Reduced motion: Motion skips every transform (rise, zoom, tilt entrance); opacity still fades briefly.
  return <MotionConfig reducedMotion="user"><section className={[shell.hero, styles.hero, styles[layout], className].filter(Boolean).join(" ")}>
    <div className={styles.inner}>
      <motion.div className={styles.copy} variants={heroGroup} initial={animateIn ? "hidden" : false} animate="shown">
        {announcement && <motion.div variants={item}>
          {announcement.href
            ? <a className={styles.announcement} href={announcement.href} onClick={announcement.onClick}>{announcement.label}<ArrowRight className={styles.arrow} size={14} strokeWidth={1.75} aria-hidden="true" /></a>
            : <button type="button" className={styles.announcement} onClick={announcement.onClick}>{announcement.label}<ArrowRight className={styles.arrow} size={14} strokeWidth={1.75} aria-hidden="true" /></button>}
        </motion.div>}
        <motion.h1 variants={item} className={[shell.title, styles.title].join(" ")}>{title}</motion.h1>
        {description && <motion.p variants={item} className={[shell.description, styles.description].join(" ")}>{description}</motion.p>}
        {(primaryAction || secondaryAction) && <motion.div variants={item} className={styles.actions}>
          {primaryAction && <ActionLink action={primaryAction} kind="primary" />}
          {secondaryAction && <ActionLink action={secondaryAction} kind="secondary" />}
        </motion.div>}
        {command && <motion.div variants={item} className={styles.install}>
          <span className={styles.prompt} aria-hidden="true">$</span>
          <code className={styles.command}>{command.command}</code>
          <CopyButton value={command.command} label="Copy install command" iconOnly variant="plain" />
        </motion.div>}
        {meta && meta.length > 0 && <motion.ul variants={item} className={styles.meta}>{meta.map(entry => <li key={entry}>{entry}</li>)}</motion.ul>}
      </motion.div>
      {media && <motion.div className={styles.media} variants={item} initial={animateIn ? "hidden" : false} animate="shown">{media}</motion.div>}
    </div>
  </section></MotionConfig>;
}

export default HeroContent;
```

### `registry/blocks/hero-section/hero-content.module.css`

```css
.inner { display: grid; max-width: 1200px; margin: 0 auto; padding: var(--space-24) var(--space-10); }
.copy { display: grid; grid-template-columns: minmax(0, 1fr); justify-items: center; gap: var(--space-5); max-width: 760px; margin: 0 auto; text-align: center; }
.title { max-width: 16ch; }
.description { max-width: 560px; }

.announcement { display: inline-flex; max-width: 100%; min-height: 32px; align-items: center; gap: 6px; border: 1px solid var(--border); border-radius: var(--radius-pill); padding: 4px var(--space-3); background: var(--surface); color: var(--text-secondary); font: inherit; font-size: var(--text-sm); text-align: left; text-decoration: none; cursor: pointer; transition: color var(--duration-fast) var(--ease-standard), border-color var(--duration-fast) var(--ease-standard); }
.arrow { flex: none; transition: transform var(--duration-fast) var(--ease-standard); }

.actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-3); margin-top: var(--space-3); }
.action { display: inline-flex; height: var(--control-height-md); align-items: center; justify-content: center; gap: var(--space-2); border: 1px solid transparent; border-radius: var(--radius-control); padding: 0 var(--space-5); font: inherit; font-size: var(--text-sm); font-weight: 500; text-decoration: none; white-space: nowrap; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: transform var(--duration-fast) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard), opacity var(--duration-fast) var(--ease-standard); }
.action:active { transform: scale(.97); }
.primary { background: var(--foreground); color: var(--background); }
.secondary { border-color: var(--border); background: var(--surface); color: var(--foreground); }
@media (hover: hover) and (pointer: fine) {
  .primary:hover { opacity: .9; }
  .secondary:hover { background: var(--surface-muted); }
  .announcement:hover { border-color: var(--border-strong); color: var(--foreground); }
  :is(.action, .announcement):hover .arrow { transform: translateX(2px); }
}

.install { display: flex; width: min(100%, 440px); height: var(--control-height-md); align-items: center; gap: var(--space-2); margin-top: var(--space-2); border: 1px solid var(--border); border-radius: var(--radius-control); padding: 0 var(--space-1) 0 var(--space-4); background: var(--surface); font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 13px; text-align: left; }
.prompt { color: var(--text-muted); }
.command { flex: 1; overflow: hidden; color: var(--foreground); font: inherit; text-overflow: ellipsis; white-space: nowrap; }

.meta { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-5); margin: var(--space-4) 0 0; border-top: 1px solid var(--border-subtle); padding: var(--space-5) 0 0; color: var(--text-muted); font-size: var(--text-sm); list-style: none; }

.media { min-width: 0; }

.split .inner { grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); align-items: center; gap: var(--space-12); }
.split .copy, .minimal .copy { justify-items: start; margin: 0; text-align: left; }
.split .actions, .minimal .actions { justify-content: flex-start; }
.minimal .copy { max-width: 860px; }

@container hero (max-width: 800px) {
  .split .inner { grid-template-columns: minmax(0, 1fr); gap: var(--space-10); }
}
@container hero (max-width: 560px) {
  .inner { padding: var(--space-16) var(--space-4); }
  .actions { width: 100%; }
  .action { flex: 1 1 auto; }
}

@media (prefers-reduced-motion: reduce) {
  .action, .announcement, .arrow { transition: none; }
  .action:active { transform: none; }
}

/* Buttons are Arc buttons; this only keeps them the height of the link actions beside them. */
.button { height: var(--control-height-md); min-height: var(--control-height-md); border-radius: var(--radius-control); font-size: var(--text-sm); }
```

### `registry/blocks/hero-section/hero-motion.ts`

```ts
import { useSyncExternalStore } from "react";
import { useReducedMotion } from "motion/react";
import type { Transition, Variants } from "motion/react";
import { motionTokens } from "../../lib/motion-tokens";

export type Bezier = [number, number, number, number];
export const ease = {
  enter: [...motionTokens.ease.enter] as Bezier,
  standard: [...motionTokens.ease.standard] as Bezier,
  inOut: [...motionTokens.ease.inOut] as Bezier,
};

/** The one entrance a hero plays: children rise in reading order, once. */
export const heroGroup: Variants = { hidden: {}, shown: { transition: { staggerChildren: motionTokens.stagger.line, delayChildren: .04 } } };
export const heroRise: Variants = {
  hidden: { opacity: 0, y: 12, filter: `blur(${motionTokens.blur.subtle}px)` },
  shown: { opacity: 1, y: 0, filter: "blur(0px)", transition: { duration: .64, ease: ease.enter } },
};
export const heroFade: Variants = { hidden: { opacity: 0 }, shown: { opacity: 1, transition: { duration: motionTokens.duration.standard } } };

export const instant: Transition = { duration: 0 };

const noop = () => () => {};
/**
 * Reduced motion, read only after hydration. The server cannot know the preference, so the first client render matches the
 * server's full motion markup and the reduced branch takes over on the next render, before anything has moved.
 */
export function useHeroReducedMotion() {
  const hydrated = useSyncExternalStore(noop, () => true, () => false);
  return !!useReducedMotion() && hydrated;
}
```

### `registry/blocks/hero-section/hero-mesh.tsx`

```tsx
"use client";

import { useEffect, useRef } from "react";
import type { CSSProperties } from "react";
import { useReducedMotion } from "motion/react";
import styles from "./hero-mesh.module.css";

/**
 * One point of a mesh, in the same model the Gradient mesh editor saves: a position as a fraction of the box (0 to 1 from the
 * top left) and a reach. The color is not given here: it is read from the CSS variable `--mesh-{n}` on the element, so light
 * and dark themes each author their own palette in CSS and the canvas follows a theme switch without a remount.
 */
export interface HeroMeshPoint {
  x: number;
  y: number;
  /** Reach of the color as a fraction of the box, 0.15 to 1.2. */
  spread: number;
}

export interface HeroMeshProps {
  points: readonly HeroMeshPoint[];
  /** Film grain, 0 to 1. */
  grain?: number;
  /** Drift speed. 1 loops every 20 seconds; 0 holds the mesh still. */
  speed?: number;
  className?: string;
  style?: CSSProperties;
}

type Rgb = [number, number, number];

/** Same loop as the Gradient mesh editor: integer frequencies, so the drift is seamless. */
const PERIOD = 20, TRAVEL = .075, BREATH = .08, MAX = 8;
/** The drift is slow, so thirty frames a second is indistinguishable from sixty and halves the cost. */
const FRAME = 1000 / 30;

const VS = `attribute vec2 a; void main() { gl_Position = vec4(a, 0.0, 1.0); }`;
const FS = `
precision mediump float;
uniform vec2 uRes;
uniform vec3 uBase;
uniform vec4 uP[${MAX}];
uniform vec3 uC[${MAX}];
uniform int uN;
uniform float uGrain;
float hash(vec2 p) { vec3 q = fract(vec3(p.xyx) * .1031); q += dot(q, q.yzx + 33.33); return fract((q.x + q.y) * q.z); }
void main() {
  vec2 uv = vec2(gl_FragCoord.x / uRes.x, 1.0 - gl_FragCoord.y / uRes.y);
  vec3 col = uBase;
  for (int i = 0; i < ${MAX}; i++) {
    if (i >= uN) break;
    vec4 p = uP[i];
    float d = length((uv - p.xy) / max(p.z, 1e-4));
    col = mix(col, uC[i], 1.0 - smoothstep(0.0, 1.0, d));
  }
  float n = hash(floor(gl_FragCoord.xy)) + hash(floor(gl_FragCoord.xy) + 19.19) - 1.0;
  float lum = dot(col, vec3(.2126, .7152, .0722));
  col += n * (uGrain * .1 * (.55 + 1.8 * lum * (1.0 - lum)) + .6 / 255.0);
  gl_FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
}`;

function hashOf(text: string) {
  let h = 2166136261;
  for (let i = 0; i < text.length; i++) { h ^= text.charCodeAt(i); h = Math.imul(h, 16777619); }
  return h >>> 0;
}

const UNIFORMS = ["uRes", "uBase", "uP", "uC", "uN", "uGrain"] as const;

/** Resolves any CSS color (hex, oklch, color-mix) to sRGB through a one pixel 2D canvas. */
function toRgb(color: string, scratch: CanvasRenderingContext2D): Rgb {
  scratch.clearRect(0, 0, 1, 1);
  scratch.fillStyle = "#000";
  scratch.fillStyle = color;
  scratch.fillRect(0, 0, 1, 1);
  const [r = 0, g = 0, b = 0] = scratch.getImageData(0, 0, 1, 1).data;
  return [r / 255, g / 255, b / 255];
}

/** The static CSS render of the same mesh: shown on the server, before the canvas paints, and when WebGL is missing. */
function cssMesh(points: readonly HeroMeshPoint[]): CSSProperties {
  const stops = [0, .2, .4, .6, .8, 1].map(t => [t, Math.round((1 - t * t * (3 - 2 * t)) * 100)] as const);
  const layers = points.map((p, i) => `radial-gradient(ellipse ${p.spread * 100}% ${p.spread * 100}% at ${p.x * 100}% ${p.y * 100}%, ${stops.map(([t, a]) => `color-mix(in srgb, var(--mesh-${i + 1}) ${a}%, transparent) ${t * 100}%`).join(", ")})`);
  return { backgroundColor: "var(--mesh-base)", backgroundImage: layers.reverse().join(", ") };
}

/**
 * A lean, non-editing render of the Gradient mesh: a single WebGL triangle composites each point over the last with the
 * editor's smoothstep falloff and grain, drifting on the editor's seamless loop. It draws at thirty frames a second only while
 * it is on screen and the tab is visible, and draws one still frame under reduced motion.
 */
export function HeroMesh({ points, grain = .35, speed = 1, className, style }: HeroMeshProps) {
  const root = useRef<HTMLDivElement>(null);
  const canvas = useRef<HTMLCanvasElement>(null);
  const reduced = !!useReducedMotion();
  const key = JSON.stringify(points);

  useEffect(() => {
    const host = root.current, node = canvas.current;
    if (!host || !node) return;
    const gl = node.getContext("webgl", { alpha: false, antialias: false, depth: false, stencil: false, powerPreference: "low-power" });
    const scratch = document.createElement("canvas").getContext("2d", { willReadFrequently: true });
    if (!gl || !scratch) return;
    const compile = (type: number, src: string) => { const s = gl.createShader(type)!; gl.shaderSource(s, src); gl.compileShader(s); return s; };
    const prog = gl.createProgram()!;
    gl.attachShader(prog, compile(gl.VERTEX_SHADER, VS));
    gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, FS));
    gl.linkProgram(prog);
    if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) return;
    gl.useProgram(prog);
    gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
    const loc = gl.getAttribLocation(prog, "a");
    gl.enableVertexAttribArray(loc);
    gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
    const u = Object.fromEntries(UNIFORMS.map(name => [name, gl.getUniformLocation(prog, name)])) as Record<(typeof UNIFORMS)[number], WebGLUniformLocation | null>;

    const pts = (JSON.parse(key) as HeroMeshPoint[]).slice(0, MAX);
    const drift = pts.map((_, i) => { const h = hashOf(`mesh-${i}`); return { ph1: (h % 628) / 100, ph2: ((h >>> 10) % 628) / 100, k1: 1 + ((h >>> 20) % 2), k2: 1 + ((h >>> 22) % 2) }; });
    const P = new Float32Array(MAX * 4), C = new Float32Array(MAX * 3);
    let base: Rgb = [1, 1, 1];

    const readColors = () => {
      const css = getComputedStyle(host);
      base = toRgb(css.getPropertyValue("--mesh-base").trim() || "#fff", scratch);
      pts.forEach((_, i) => { const c = toRgb(css.getPropertyValue(`--mesh-${i + 1}`).trim() || "#fff", scratch); C.set(c, i * 3); });
    };

    let width = 0, height = 0;
    const size = () => {
      // The mesh has no detail finer than its grain, so one canvas pixel per CSS pixel is enough on any screen.
      width = Math.max(1, Math.round(host.clientWidth));
      height = Math.max(1, Math.round(host.clientHeight));
      node.width = width; node.height = height;
    };

    let t = hashOf(key) % PERIOD;
    const draw = () => {
      const a = (2 * Math.PI * t) / PERIOD;
      pts.forEach((p, i) => {
        const d = drift[i]!;
        P[i * 4] = p.x + TRAVEL * Math.sin(a * d.k1 + d.ph1);
        P[i * 4 + 1] = p.y + TRAVEL * Math.cos(a * d.k2 + d.ph2);
        P[i * 4 + 2] = p.spread * (1 + BREATH * Math.sin(a + d.ph1 * .7));
      });
      gl.viewport(0, 0, width, height);
      gl.uniform2f(u.uRes, width, height);
      gl.uniform3f(u.uBase, base[0], base[1], base[2]);
      gl.uniform4fv(u.uP, P);
      gl.uniform3fv(u.uC, C);
      gl.uniform1i(u.uN, pts.length);
      gl.uniform1f(u.uGrain, grain);
      gl.drawArrays(gl.TRIANGLES, 0, 3);
      host.dataset.painted = "";
    };

    readColors();
    size();
    draw();

    const moving = !reduced && speed > 0;
    let visible = false, frame = 0, last = 0;
    const tick = (now: number) => {
      frame = requestAnimationFrame(tick);
      if (now - last < FRAME) return;
      const dt = last ? Math.min(.1, (now - last) / 1000) : 0;
      last = now;
      t = (t + dt * speed) % PERIOD;
      draw();
    };
    const sync = () => {
      const run = moving && visible && document.visibilityState === "visible";
      if (run && !frame) { last = 0; frame = requestAnimationFrame(tick); }
      if (!run && frame) { cancelAnimationFrame(frame); frame = 0; }
    };
    const io = new IntersectionObserver(([entry]) => { if (!entry) return; visible = entry.isIntersecting; sync(); });
    io.observe(host);
    const ro = new ResizeObserver(() => { size(); draw(); });
    ro.observe(host);
    const theme = () => { readColors(); draw(); };
    const mo = new MutationObserver(theme);
    mo.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme", "data-accent", "class"] });
    const scheme = matchMedia("(prefers-color-scheme: dark)");
    scheme.addEventListener("change", theme);
    document.addEventListener("visibilitychange", sync);
    return () => {
      cancelAnimationFrame(frame);
      io.disconnect(); ro.disconnect(); mo.disconnect();
      scheme.removeEventListener("change", theme);
      document.removeEventListener("visibilitychange", sync);
      delete host.dataset.painted;
    };
  }, [key, grain, speed, reduced]);

  return <div ref={root} className={[styles.mesh, className].filter(Boolean).join(" ")} style={{ ...cssMesh(points), ...style }} aria-hidden="true">
    <canvas ref={canvas} className={styles.canvas} />
  </div>;
}

export default HeroMesh;
```

### `registry/blocks/hero-section/hero-mesh.module.css`

```css
/* The canvas fades in over its own CSS render once it has painted, so the first frame never flashes. */
.mesh { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.canvas { display: block; width: 100%; height: 100%; opacity: 0; transition: opacity var(--duration-considered, .48s) var(--ease-standard); }
.mesh[data-painted] .canvas { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .canvas { transition: none; } }
```

### `registry/blocks/hero-section/hero-cadence.tsx`

```tsx
"use client";

import { MotionConfig, motion } from "motion/react";
import { ActionLink } from "./hero-content";
import type { HeroAction } from "./hero-content";
import { HeroMesh } from "./hero-mesh";
import type { HeroMeshPoint } from "./hero-mesh";
import { heroGroup, heroRise } from "./hero-motion";
import shell from "./hero-section.module.css";
import styles from "./hero-cadence.module.css";

export interface HeroCadenceProps {
  primaryAction?: HeroAction;
  secondaryAction?: HeroAction;
  animateIn?: boolean;
  className?: string;
}

/** Six points, authored the way the Gradient mesh editor saves them. Colors live in CSS, one palette per theme. */
const MESH: HeroMeshPoint[] = [
  { x: .1, y: .88, spread: .72 },
  { x: .86, y: .12, spread: .66 },
  { x: .16, y: .14, spread: .56 },
  { x: .62, y: .62, spread: .5 },
  { x: .94, y: .92, spread: .48 },
  { x: .44, y: .3, spread: .34 },
];

/** Sample customers. The marks are the brands' own icons, drawn in the text color. */
const BRANDS = [
  { name: "Linear", logo: "/block-logos/linear.svg" },
  { name: "Vercel", logo: "/block-logos/vercel.svg" },
  { name: "Raycast", logo: "/block-logos/raycast.svg" },
  { name: "Notion", logo: "/block-logos/notion.svg" },
  { name: "Figma", logo: "/block-logos/figma.svg" },
  { name: "Stripe", logo: "/block-logos/stripe.svg" },
  { name: "Loom", logo: "/block-logos/loom.svg" },
];

/** Minimal hero, one full screen: large type over a slowly drifting mesh gradient with grain, and the teams that use the product along the bottom edge. */
export function HeroCadence({ primaryAction = { label: "Download for Mac", doneLabel: "Download started" }, secondaryAction = { label: "Try it on the web", doneLabel: "Opening Cadence" }, animateIn = true, className }: HeroCadenceProps) {
  const item = heroRise;
  // Reduced motion: Motion skips every transform (rise, zoom, tilt entrance); opacity still fades briefly.
  return <MotionConfig reducedMotion="user"><section className={[shell.hero, shell.screen, styles.hero, className].filter(Boolean).join(" ")}>
    <HeroMesh className={styles.mesh} points={MESH} grain={.55} speed={.45} />
    <motion.div className={styles.inner} variants={heroGroup} initial={animateIn ? "hidden" : false} animate="shown">
      <motion.h1 variants={item} className={[shell.title, styles.title].join(" ")}>Your week, planned before Monday</motion.h1>
      <div className={styles.row}>
        <motion.p variants={item} className={[shell.description, styles.description].join(" ")}>Cadence reads your tasks, meetings and focus goals, then books time for the work that matters. When plans change, it moves everything for you.</motion.p>
        <motion.div variants={item} className={styles.cta}>
          <div className={styles.actions}>
            <ActionLink action={primaryAction} kind="primary" />
            <ActionLink action={secondaryAction} kind="secondary" />
          </div>
          <p className={styles.fine}>Free for personal use. Teams from $8 a seat.</p>
        </motion.div>
      </div>
      <motion.div variants={item} className={styles.brands}>
        <p>Teams at these companies plan with Cadence</p>
        <ul aria-label="Sample customers">
          {BRANDS.map(brand => <li key={brand.name}><i style={{ maskImage: `url(${brand.logo})`, WebkitMaskImage: `url(${brand.logo})` }} aria-hidden="true" />{brand.name}</li>)}
        </ul>
      </motion.div>
    </motion.div>
  </section></MotionConfig>;
}

export default HeroCadence;
```

### `registry/blocks/hero-section/hero-cadence.module.css`

```css
/* Minimal: the mesh is the whole screen, and type does the rest. The copy sits in the optical centre; customers hold the bottom edge. */
.hero { isolation: isolate; display: grid; }
.mesh {
  --mesh-base: #f2ede7;
  --mesh-1: #bdd3ec; --mesh-2: #c3c6ff; --mesh-3: #ffd0b0; --mesh-4: #f6bfd6; --mesh-5: #cfe8dc; --mesh-6: #fff3dc;
  z-index: -1;
}
:global(:root[data-theme="dark"]) .mesh {
  --mesh-base: #09090f;
  --mesh-1: #0d3a47; --mesh-2: #2a2f86; --mesh-3: #5a2a3c; --mesh-4: #3a1f5a; --mesh-5: #7a3a22; --mesh-6: #12142c;
}
/* A theme-aware scrim keeps body text above 4.5:1 wherever the mesh drifts. */
.hero::after { content: ""; position: absolute; z-index: -1; inset: 0; background: linear-gradient(to bottom, transparent 40%, color-mix(in oklab, var(--background) 22%, transparent)); pointer-events: none; }

.inner { display: grid; box-sizing: border-box; width: 100%; max-width: 1320px; grid-template-rows: 1fr auto auto 1fr auto; gap: var(--space-10); margin: 0 auto; padding: clamp(88px, 12svh, 128px) var(--space-10) var(--space-10); }
.title { grid-row: 2; }
.row { grid-row: 3; }
.brands { grid-row: 5; }
.title { max-width: 14ch; font-size: clamp(2.75rem, 1rem + 7cqi, 7rem); }
.row { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: end; gap: var(--space-8) var(--space-16); }
.description { max-width: 520px; color: color-mix(in oklab, var(--foreground) 76%, transparent); }
.cta { display: grid; justify-items: end; gap: var(--space-3); }
.actions { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.fine { margin: 0; color: color-mix(in oklab, var(--foreground) 62%, transparent); font-size: var(--text-sm); }

.brands { display: grid; gap: var(--space-5); border-top: 1px solid color-mix(in oklab, var(--foreground) 14%, transparent); padding-top: var(--space-6); }
.brands p { margin: 0; color: color-mix(in oklab, var(--foreground) 62%, transparent); font-size: var(--text-sm); }
.brands ul { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--space-5) var(--space-8); margin: 0; padding: 0; list-style: none; }
.brands li { display: inline-flex; align-items: center; gap: 9px; color: color-mix(in oklab, var(--foreground) 78%, transparent); font-size: var(--text-lg); font-weight: 500; letter-spacing: var(--tracking-body); }
.brands i { width: 22px; height: 22px; background: currentColor; -webkit-mask-position: center; mask-position: center; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-size: contain; mask-size: contain; }

@container hero (max-width: 800px) {
  .row { grid-template-columns: minmax(0, 1fr); align-items: start; }
  .cta { justify-items: start; }
  .brands ul { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@container hero (max-width: 560px) {
  .inner { gap: var(--space-6); padding: 80px var(--space-4) var(--space-6); }
  .actions { width: 100%; }
  .actions > * { flex: 1 1 auto; }
  .cta { width: 100%; }
  .brands ul { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-4) var(--space-3); }
  .brands li:nth-child(n + 7) { display: none; }
  .brands li { font-size: var(--text-base); }
  .brands i { width: 18px; height: 18px; }
}
```

### `registry/blocks/hero-section/hero-relay.tsx`

```tsx
"use client";

import { useEffect, useLayoutEffect, useRef, useState } from "react";
import type { ReactNode } from "react";
import { AnimatePresence, MotionConfig, motion } from "motion/react";
import { Check, Database, Minus, Split } from "lucide-react";
import { Button } from "../../button/button";
import { motionTokens } from "../../lib/motion-tokens";
import { ActionLink } from "./hero-content";
import type { HeroAction } from "./hero-content";
import { ease, heroGroup, heroRise, useHeroReducedMotion } from "./hero-motion";
import shell from "./hero-section.module.css";
import styles from "./hero-relay.module.css";

type NodeId = "trigger" | "lookup" | "branch" | "slack" | "linear" | "warehouse";
type Status = "idle" | "running" | "done" | "skipped";

/** Sample events. Each one runs the same workflow; the branch decides which actions it reaches. */
const EVENTS = [
  { customer: "Northwind Labs", amount: "$4,800", plan: "Enterprise, 212 seats", routed: true, issue: "ONB-482", total: 412, ms: { lookup: 118, branch: 2, slack: 164, linear: 231, warehouse: 58 } },
  { customer: "Tidepool", amount: "$240", plan: "Team, 9 seats", routed: false, issue: "", total: 171, ms: { lookup: 96, branch: 1, slack: 0, linear: 0, warehouse: 61 } },
  { customer: "Halcyon Health", amount: "$12,600", plan: "Enterprise, 540 seats", routed: true, issue: "ONB-483", total: 436, ms: { lookup: 131, branch: 2, slack: 149, linear: 244, warehouse: 66 } },
] as const;
type RelayEvent = (typeof EVENTS)[number];

/**
 * One run in phases. Odd phases run a step, even phases carry the data along the edges out of it; the last phase holds the
 * finished run on screen before the next event arrives. Durations in milliseconds.
 */
const PHASES = [900, 420, 380, 640, 380, 360, 440, 760, 2800];
const DONE = PHASES.length - 1;
const STEPS: Record<NodeId, [start: number, end: number]> = { trigger: [1, 2], lookup: [3, 4], branch: [5, 6], slack: [7, 8], linear: [7, 8], warehouse: [7, 8] };
const ACTIONS = ["slack", "linear", "warehouse"] as const;

function statusOf(id: NodeId, phase: number, event: RelayEvent): Status {
  const [start, end] = STEPS[id];
  if ((id === "slack" || id === "linear") && !event.routed && phase >= 6) return "skipped";
  return phase < start ? "idle" : phase < end ? "running" : "done";
}

/** The graph is drawn at a fixed size and scaled to its column, like a screenshot, so it never reflows mid run. */
type Box = { x: number; y: number; w: number; h: number };
const LAYOUTS = {
  wide: {
    width: 640, height: 524, summary: 480,
    nodes: { trigger: { x: 170, y: 0, w: 300, h: 60 }, lookup: { x: 170, y: 112, w: 300, h: 60 }, branch: { x: 170, y: 224, w: 300, h: 60 }, slack: { x: 0, y: 356, w: 200, h: 84 }, linear: { x: 220, y: 356, w: 200, h: 84 }, warehouse: { x: 440, y: 356, w: 200, h: 84 } } as Record<NodeId, Box>,
  },
  narrow: {
    width: 344, height: 316, summary: 0,
    nodes: { trigger: { x: 0, y: 0, w: 344, h: 52 }, lookup: { x: 0, y: 78, w: 344, h: 52 }, branch: { x: 0, y: 156, w: 344, h: 52 }, slack: { x: 0, y: 244, w: 108, h: 72 }, linear: { x: 118, y: 244, w: 108, h: 72 }, warehouse: { x: 236, y: 244, w: 108, h: 72 } } as Record<NodeId, Box>,
  },
};
type Layout = (typeof LAYOUTS)[keyof typeof LAYOUTS];

const EDGES: { from: NodeId; to: NodeId; phase: number }[] = [
  { from: "trigger", to: "lookup", phase: 2 },
  { from: "lookup", to: "branch", phase: 4 },
  { from: "branch", to: "slack", phase: 6 },
  { from: "branch", to: "linear", phase: 6 },
  { from: "branch", to: "warehouse", phase: 6 },
];

function edgePath(layout: Layout, from: NodeId, to: NodeId) {
  const a = layout.nodes[from], b = layout.nodes[to];
  const x1 = a.x + a.w / 2, y1 = a.y + a.h, x2 = b.x + b.w / 2, y2 = b.y;
  const mid = (y2 - y1) / 2;
  return `M ${x1} ${y1} C ${x1} ${y1 + mid} ${x2} ${y2 - mid} ${x2} ${y2}`;
}

const RESULTS: Record<(typeof ACTIONS)[number], (event: RelayEvent) => string> = {
  slack: event => `Sent in ${event.ms.slack} ms`,
  linear: event => `${event.issue} in ${event.ms.linear} ms`,
  warehouse: event => `Saved in ${event.ms.warehouse} ms`,
};

const logo = (name: string) => <span className={styles.logo} style={{ backgroundImage: `url(/block-logos/${name}.svg)` }} aria-hidden="true" />;

export interface HeroRelayProps {
  primaryAction?: HeroAction;
  secondaryAction?: HeroAction;
  animateIn?: boolean;
  className?: string;
}

/**
 * Split hero, one full screen: the copy beside a live workflow graph on a dotted canvas. Sample Stripe events arrive on their
 * own and run the workflow node by node; the data draws along each edge, the branch decides which actions run, and every step
 * reports its time. "Send test event" runs the next sample at once. The run pauses off screen, and under reduced motion each
 * event shows its finished run without playing.
 */
export function HeroRelay({ primaryAction = { label: "Start building", doneLabel: "Workspace created" }, secondaryAction = { label: "Read the docs", doneLabel: "Opening docs" }, animateIn = true, className }: HeroRelayProps) {
  const item = heroRise;
  const reduced = useHeroReducedMotion();
  const root = useRef<HTMLElement>(null);
  const stage = useRef<HTMLDivElement>(null);
  const [mode, setMode] = useState<keyof typeof LAYOUTS>("wide");
  const [scale, setScale] = useState(1);
  const [run, setRun] = useState({ index: 0, count: 2418, phase: 0 });
  const [visible, setVisible] = useState(false);

  useLayoutEffect(() => {
    const node = stage.current;
    if (!node) return;
    const measure = () => {
      const width = node.clientWidth;
      const next = width < 520 ? "narrow" : "wide";
      setMode(next);
      setScale(Math.min(1, width / LAYOUTS[next].width));
    };
    measure();
    const observer = new ResizeObserver(measure);
    observer.observe(node);
    return () => observer.disconnect();
  }, []);

  // The run only plays while the hero is on screen and the tab is visible.
  useEffect(() => {
    const node = root.current;
    if (!node) return;
    let onScreen = false;
    const sync = () => setVisible(onScreen && document.visibilityState === "visible");
    const io = new IntersectionObserver(([entry]) => { if (!entry) return; onScreen = entry.isIntersecting; sync(); });
    io.observe(node);
    document.addEventListener("visibilitychange", sync);
    return () => { io.disconnect(); document.removeEventListener("visibilitychange", sync); };
  }, []);

  useEffect(() => {
    if (reduced || !visible) return;
    const timer = window.setTimeout(() => setRun(current => current.phase < DONE
      ? { ...current, phase: current.phase + 1 }
      : { index: (current.index + 1) % EVENTS.length, count: current.count + 1, phase: 0 }), PHASES[run.phase]);
    return () => window.clearTimeout(timer);
  }, [run.phase, run.index, reduced, visible]);

  const sendTest = () => setRun(current => ({ index: (current.index + 1) % EVENTS.length, count: current.count + 1, phase: 1 }));

  const layout = LAYOUTS[mode];
  const event = EVENTS[run.index]!;
  // Reduced motion: every event is shown as its finished run; the button moves to the next one at once.
  const phase = reduced ? DONE : run.phase;
  const status = (id: NodeId) => statusOf(id, phase, event);
  const narrow = mode === "narrow";

  // Reduced motion: Motion skips every transform (rise, tilt entrance); opacity still fades briefly.
  return <MotionConfig reducedMotion="user"><section ref={root} className={[shell.hero, shell.screen, styles.hero, className].filter(Boolean).join(" ")}>
    <div className={styles.inner}>
      <motion.div className={styles.copy} variants={heroGroup} initial={animateIn ? "hidden" : false} animate="shown">
        <motion.h1 variants={item} className={[shell.title, styles.title].join(" ")}>Every event, handled in milliseconds</motion.h1>
        <motion.p variants={item} className={[shell.description, styles.description].join(" ")}>Relay turns webhooks from Stripe, GitHub and Postgres into typed workflows with retries, branches and a trace of every run. Write steps in TypeScript, or wire them on the canvas.</motion.p>
        <motion.div variants={item} className={styles.actions}>
          <ActionLink action={primaryAction} kind="primary" />
          <ActionLink action={secondaryAction} kind="secondary" />
        </motion.div>
        <motion.p variants={item} className={styles.fine}>Free for 10,000 runs a month. No card needed.</motion.p>
      </motion.div>

      <motion.div
        className={styles.canvas}
        initial={animateIn ? { opacity: 0, y: 24 } : false}
        animate={{ opacity: 1, y: 0 }}
        transition={{ duration: 1, ease: ease.enter, delay: .28 }}
      >
        <div className={styles.toolbar}>
          <span className={styles.file}><i className={styles.live} data-on={visible || reduced || undefined} aria-hidden="true" />on-invoice-paid.ts</span>
          <Button variant="secondary" size="sm" onClick={sendTest}>Send test event</Button>
        </div>
        <div ref={stage} className={styles.stage} style={{ height: layout.height * scale }}>
          <div className={styles.graph} role="group" aria-label={`Relay workflow, sample run for ${event.customer}`} style={{ width: layout.width, height: layout.height, transform: `scale(${scale})` }}>
            <svg className={styles.edges} width={layout.width} height={layout.height} viewBox={`0 0 ${layout.width} ${layout.height}`} aria-hidden="true">
              {EDGES.map(edge => {
                const d = edgePath(layout, edge.from, edge.to);
                const skipped = status(edge.to) === "skipped";
                const lit = phase >= edge.phase && !skipped;
                return <g key={`${edge.from}-${edge.to}`}>
                  <path d={d} className={styles.wire} data-skipped={skipped || undefined} />
                  <motion.path
                    d={d}
                    className={styles.flow}
                    initial={false}
                    animate={{ pathLength: lit ? 1 : 0, opacity: lit ? 1 : 0 }}
                    transition={lit && !reduced ? { pathLength: { duration: PHASES[edge.phase]! / 1000, ease: ease.inOut }, opacity: { duration: .08 } } : { duration: 0 }}
                  />
                </g>;
              })}
            </svg>

            <RelayNode box={layout.nodes.trigger} status={status("trigger")} icon={logo("stripe-color")} title="Invoice paid" sub={`${event.customer}, ${event.amount}`} />
            <RelayNode box={layout.nodes.lookup} status={status("lookup")} icon={logo("hubspot-color")} title="Find account in HubSpot" sub={status("lookup") === "done" ? event.plan : "Plan, seats and owner"} meta={`${event.ms.lookup} ms`} />
            <RelayNode box={layout.nodes.branch} status={status("branch")} icon={<Split size={17} strokeWidth={1.75} className={styles.glyph} aria-hidden="true" />} title="Amount over $1,000" sub={status("branch") === "done" ? event.routed ? "Yes, alert the team" : "No, record only" : "Condition"} meta={`${event.ms.branch} ms`} />
            {ACTIONS.map(id => {
              const s = status(id);
              const copy = {
                slack: { icon: logo("slack-color"), title: narrow ? "Slack" : "Post to #revenue", waiting: "Slack" },
                linear: { icon: logo("linear-color"), title: narrow ? "Linear" : "Create Linear issue", waiting: "Onboarding team" },
                warehouse: { icon: <Database size={16} strokeWidth={1.75} className={styles.glyph} aria-hidden="true" />, title: narrow ? "Warehouse" : "Save to warehouse", waiting: "Postgres" },
              }[id];
              const sub = s === "skipped" ? "Skipped" : s === "done" ? narrow ? `${event.ms[id]} ms` : RESULTS[id](event) : s === "running" ? "Running" : narrow ? "Waiting" : copy.waiting;
              return <RelayNode key={id} box={layout.nodes[id]} compact status={s} icon={copy.icon} title={copy.title} sub={sub} />;
            })}

            {!narrow && <div className={styles.summary} style={{ top: layout.summary, width: layout.width }}>
              <span className={styles.event}><code>invoice.paid</code><span>Run <span className={styles.num}>{run.count.toLocaleString("en-US")}</span></span></span>
              <AnimatePresence mode="popLayout" initial={false}>
                <motion.span
                  key={phase === DONE ? `done-${run.count}` : phase === 0 ? "waiting" : "running"}
                  className={styles.result}
                  data-done={phase === DONE || undefined}
                  initial={{ opacity: 0, y: 6 }}
                  animate={{ opacity: 1, y: 0 }}
                  exit={{ opacity: 0, y: -6 }}
                  transition={{ duration: motionTokens.duration.fast, ease: ease.standard }}
                >
                  {phase === DONE ? <><Check size={14} strokeWidth={2} aria-hidden="true" /><span>Completed in <span className={styles.num}>{event.total} ms</span></span></> : phase === 0 ? "Waiting for the next event" : <><i className={styles.spinner} aria-hidden="true" />Running</>}
                </motion.span>
              </AnimatePresence>
            </div>}
          </div>
        </div>
      </motion.div>
    </div>
  </section></MotionConfig>;
}

function RelayNode({ box, status, icon, title, sub, meta, compact = false }: { box: Box; status: Status; icon: ReactNode; title: string; sub: string; meta?: string; compact?: boolean }) {
  return <div className={styles.node} data-status={status} data-compact={compact || undefined} style={{ left: box.x, top: box.y, width: box.w, height: box.h }}>
    <span className={styles.icon}>{icon}</span>
    <span className={styles.text}>
      <strong>{title}</strong>
      <small>{sub}</small>
    </span>
    <span className={styles.state}>
      <AnimatePresence mode="popLayout" initial={false}>
        <motion.span
          key={status}
          className={styles.stateInner}
          initial={{ opacity: 0, scale: .6 }}
          animate={{ opacity: 1, scale: 1 }}
          exit={{ opacity: 0, scale: .6 }}
          transition={motionTokens.spring.snappy}
        >
          {status === "running" ? <i className={styles.spinner} role="img" aria-label="Running" />
            : status === "done" ? <>{meta && <span className={styles.meta}>{meta}</span>}<Check size={14} strokeWidth={2} className={styles.check} role="img" aria-label="Done" /></>
            : status === "skipped" ? <Minus size={14} strokeWidth={2} className={styles.skip} role="img" aria-label="Skipped" />
            : <i className={styles.pending} role="img" aria-label="Waiting" />}
        </motion.span>
      </AnimatePresence>
    </span>
  </div>;
}

export default HeroRelay;
```

### `registry/blocks/hero-section/hero-relay.module.css`

```css
/* Split, one full screen: the whole hero is a workflow canvas. A dot grid fills the screen, strongest behind the graph, and the
   copy sits on the quiet side of it. */
.hero {
  --dot: color-mix(in oklab, var(--foreground) 13%, transparent);
  --wire: color-mix(in oklab, var(--foreground) 17%, transparent);
  --node-shadow: 0 1px 1px rgb(20 24 40 / .04), 0 8px 24px -12px rgb(20 24 40 / .14);
  isolation: isolate;
  display: grid;
  align-items: center;
  background: color-mix(in oklab, var(--accent) 2%, var(--background));
}
:global(:root[data-theme="dark"]) .hero {
  --dot: color-mix(in oklab, var(--foreground) 17%, transparent);
  --wire: color-mix(in oklab, var(--foreground) 20%, transparent);
  --node-shadow: inset 0 1px 0 color-mix(in oklab, var(--foreground) 7%, transparent), 0 12px 32px -14px rgb(0 0 0 / .7);
}
.hero::before {
  content: ""; position: absolute; z-index: -1; inset: 0; pointer-events: none;
  background-image: radial-gradient(circle at 1px 1px, var(--dot) 1px, transparent 1.5px);
  background-size: 22px 22px;
  -webkit-mask-image: radial-gradient(70% 80% at 72% 52%, #000 30%, transparent 85%);
  mask-image: radial-gradient(70% 80% at 72% 52%, #000 30%, transparent 85%);
}

.inner { display: grid; box-sizing: border-box; width: 100%; max-width: 1320px; grid-template-columns: minmax(0, .92fr) minmax(0, 1fr); align-items: center; gap: var(--space-16); margin: 0 auto; padding: clamp(88px, 12svh, 120px) var(--space-10) var(--space-12); }

.copy { display: grid; justify-items: start; gap: var(--space-5); text-align: left; }
.title { max-width: 12ch; font-size: clamp(2.5rem, 1.2rem + 4.6cqi, 4.5rem); }
.description { max-width: 500px; }
.actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-3); }
.fine { margin: 0; color: var(--text-muted); font-size: var(--text-sm); }

/* The graph column: a small toolbar and the scaled graph under it. */
.canvas { display: grid; justify-items: center; gap: var(--space-4); min-width: 0; }
.toolbar { display: flex; width: min(100%, 640px); align-items: center; justify-content: space-between; gap: var(--space-3); }
.file { display: inline-flex; min-width: 0; align-items: center; gap: 8px; color: var(--text-secondary); font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, monospace); font-size: 13px; }
.live { width: 7px; height: 7px; flex: none; border-radius: 50%; background: var(--text-muted); transition: background-color var(--duration-standard) var(--ease-standard); }
.live[data-on] { background: var(--success); }

.stage { position: relative; width: min(100%, 640px); }
.graph { position: absolute; top: 0; left: 50%; translate: -50% 0; transform-origin: 50% 0; }
.edges { position: absolute; inset: 0; overflow: visible; }
.wire { fill: none; stroke: var(--wire); stroke-width: 1.25; }
.wire[data-skipped] { stroke-dasharray: 3 4; opacity: .7; }
.flow { fill: none; stroke: var(--accent); stroke-width: 1.75; stroke-linecap: round; }

/* A node: what the step is, what it found, and its state. The running step takes the accent. */
.node {
  position: absolute; display: flex; align-items: center; gap: 12px;
  border: 1px solid var(--border); border-radius: 14px; padding: 0 14px;
  background: var(--surface); box-shadow: var(--node-shadow);
  font-size: 13px; line-height: 1.3;
  transition: border-color var(--duration-standard) var(--ease-standard), opacity var(--duration-standard) var(--ease-standard), background-color var(--duration-standard) var(--ease-standard);
}
.node[data-status="running"] { border-color: color-mix(in oklab, var(--accent) 70%, var(--border)); background: color-mix(in oklab, var(--accent) 4%, var(--surface)); }
.node[data-status="skipped"] { opacity: .5; box-shadow: none; }
.icon { display: grid; width: 20px; height: 20px; flex: none; place-items: center; }
.logo { width: 18px; height: 18px; background: center / contain no-repeat; }
.glyph { color: var(--text-secondary); }
.text { display: grid; flex: 1; min-width: 0; }
.text strong { overflow: hidden; font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }
.text small { overflow: hidden; color: var(--text-muted); font-size: var(--text-xs); text-overflow: ellipsis; white-space: nowrap; }
.state { display: grid; flex: none; justify-items: end; }
.stateInner { display: inline-flex; align-items: center; gap: 6px; }
.meta { color: var(--text-muted); font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
.check { color: var(--success); }
.skip { color: var(--text-muted); }
.pending { display: block; width: 10px; height: 10px; border: 1.5px solid var(--wire); border-radius: 50%; }
.spinner { display: block; width: 13px; height: 13px; flex: none; border: 1.5px solid color-mix(in oklab, var(--accent) 22%, transparent); border-top-color: var(--accent); border-radius: 50%; animation: spin .7s linear infinite; }
@keyframes spin { to { rotate: 360deg; } }

/* Compact nodes on a phone: icon and state on top, the name and its result under them. */
.node[data-compact] { display: grid; grid-template-columns: 1fr auto; grid-template-rows: auto auto; align-content: center; gap: 8px 4px; padding: 0 14px; }
@container hero (max-width: 560px) { .node[data-compact] { gap: 6px 4px; padding: 0 10px; } }
.node[data-compact] .text { grid-column: 1 / -1; }
.node[data-compact] .state { grid-column: 2; grid-row: 1; }

/* The run summary under the graph. */
.summary { position: absolute; left: 0; display: flex; height: 44px; align-items: center; justify-content: space-between; gap: 12px; border-top: 1px solid var(--border-subtle); padding: 0 2px; color: var(--text-muted); font-size: 13px; }
.event { display: inline-flex; align-items: center; gap: 12px; }
.event code { border: 1px solid var(--border-subtle); border-radius: 6px; padding: 1px 7px; background: var(--surface); color: var(--text-secondary); font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, monospace); font-size: var(--text-xs); }
.num { font-variant-numeric: tabular-nums; }
.result { display: inline-flex; align-items: center; gap: 7px; }
.result[data-done] { color: var(--foreground); }
.result[data-done] svg { color: var(--success); }

@container hero (max-width: 900px) {
  .inner { grid-template-columns: minmax(0, 1fr); gap: var(--space-10); }
  .hero::before { -webkit-mask-image: radial-gradient(90% 60% at 50% 78%, #000 30%, transparent 85%); mask-image: radial-gradient(90% 60% at 50% 78%, #000 30%, transparent 85%); }
}
@container hero (max-width: 560px) {
  .inner { gap: var(--space-8); padding: 80px var(--space-4) var(--space-6); }
  .copy { gap: var(--space-4); }
  .actions { width: 100%; margin-top: var(--space-1); }
  .actions > * { flex: 1 1 auto; }
  .canvas { gap: var(--space-3); }
}

@media (prefers-reduced-motion: reduce) {
  .spinner { animation: none; }
  .node, .live { transition: none; }
}
```

### `registry/blocks/hero-section/hero-lumen.tsx`

```tsx
"use client";

import { useLayoutEffect, useRef, useState } from "react";
import { MotionConfig, motion, useReducedMotion } from "motion/react";
import { BarChart3, BookOpen, ChevronsUpDown, Gauge, LineChart as LineIcon, Search, Settings, Sparkles, Users } from "lucide-react";
import { LineChart } from "../../line-chart/line-chart";
import { Sparkline } from "../../sparkline/sparkline";
import SegmentedControl from "../../segmented-control/segmented-control";
import { Avatar } from "../../avatar/avatar";
import { avatar } from "../../lib/media";
import { motionTokens } from "../../lib/motion-tokens";
import { ActionLink } from "./hero-content";
import type { HeroAction } from "./hero-content";
import { HeroMesh } from "./hero-mesh";
import type { HeroMeshPoint } from "./hero-mesh";
import { heroGroup, heroRise } from "./hero-motion";
import { lumenInsight, lumenKpis, lumenMovers, lumenRanges, lumenSeries } from "./hero-lumen-data";
import type { LumenRange } from "./hero-lumen-data";
import shell from "./hero-section.module.css";
import styles from "./hero-lumen.module.css";

/** A soft mesh across the whole screen: cool tints at the top, a warm one low behind the window, all a few steps off the page. */
const MESH: HeroMeshPoint[] = [
  { x: .1, y: .92, spread: .7 },
  { x: .92, y: .86, spread: .66 },
  { x: .14, y: .08, spread: .64 },
  { x: .86, y: .04, spread: .6 },
  { x: .5, y: .38, spread: .46 },
];

const NAV = [
  { label: "Overview", icon: Gauge, active: true },
  { label: "Revenue", icon: LineIcon },
  { label: "Customers", icon: Users },
  { label: "Cohorts", icon: BarChart3 },
  { label: "Forecasts", icon: Sparkles },
  { label: "Reports", icon: BookOpen },
];
const VIEWS = [{ label: "Enterprise expansion", tone: "accent" }, { label: "Churn risk", tone: "danger" }, { label: "EU customers", tone: "neutral" }];

const money = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 });
const compact = (value: number) => value >= 1000 ? `$${+(value / 1000).toFixed(1)}K` : `$${value}`;

/** Width the dashboard is designed at, wide and narrow. It is drawn at that size and scaled to fit, like a screenshot. */
const WIDE = 1200, NARROW = 600;

export interface HeroLumenProps {
  primaryAction?: HeroAction;
  secondaryAction?: HeroAction;
  animateIn?: boolean;
  className?: string;
}

/**
 * Centered hero, one full screen: a headline over a drifting mesh, and a live revenue dashboard rising from the bottom edge. The
 * window settles from a tilt as it enters and runs off the bottom of the screen into a fade. It is real markup built from Arc
 * charts; its range control works.
 */
export function HeroLumen({ primaryAction = { label: "Start free trial", doneLabel: "Trial started" }, secondaryAction = { label: "Book a demo", doneLabel: "Demo requested" }, animateIn = true, className }: HeroLumenProps) {
  const calm = !!useReducedMotion();
  const item = heroRise;
  const stage = useRef<HTMLDivElement>(null);
  const [box, setBox] = useState({ width: WIDE, scale: 1 });

  useLayoutEffect(() => {
    const node = stage.current;
    if (!node) return;
    const measure = () => {
      const css = getComputedStyle(node);
      const width = node.clientWidth - parseFloat(css.paddingLeft) - parseFloat(css.paddingRight);
      const design = width < 720 ? NARROW : WIDE;
      // On a phone the window keeps a legible size and crops evenly at both edges instead of shrinking to fit.
      setBox({ width: design, scale: design === NARROW ? Math.max(.6, Math.min(1, width / design)) : Math.min(1, width / design) });
    };
    measure();
    const observer = new ResizeObserver(measure);
    observer.observe(node);
    return () => observer.disconnect();
  }, []);

  const narrow = box.width === NARROW;
  const height = narrow ? 700 : 760;

  // Reduced motion: Motion skips every transform (rise, zoom, tilt entrance); opacity still fades briefly.
  return <MotionConfig reducedMotion="user"><section className={[shell.hero, shell.screen, styles.hero, className].filter(Boolean).join(" ")}>
    <HeroMesh className={styles.mesh} points={MESH} grain={.18} speed={.55} />
    <motion.div className={styles.copy} variants={heroGroup} initial={animateIn ? "hidden" : false} animate="shown">
      <motion.h1 variants={item} className={[shell.title, styles.title].join(" ")}>See every dollar of revenue move</motion.h1>
      <motion.p variants={item} className={[shell.description, styles.description].join(" ")}>Lumen connects Stripe, HubSpot and your warehouse, then explains each change in MRR the moment it happens. No SQL, no stale spreadsheets.</motion.p>
      <motion.div variants={item} className={styles.actions}>
        <ActionLink action={primaryAction} kind="primary" />
        <ActionLink action={secondaryAction} kind="secondary" />
      </motion.div>
      <motion.p variants={item} className={styles.fine}>Free for 14 days. Connects in about four minutes.</motion.p>
    </motion.div>

    <motion.div
      ref={stage}
      className={styles.stage}
      initial={animateIn ? { opacity: 0, y: 56 } : false}
      animate={{ opacity: 1, y: 0 }}
      transition={calm ? { duration: motionTokens.duration.standard } : { duration: 1.1, ease: [...motionTokens.ease.enter], delay: .32 }}
    >
      <div className={styles.glow} aria-hidden="true" />
      <div className={styles.perspective}>
        <motion.div
          className={styles.tilt}
          style={{ width: box.width * box.scale, height: height * box.scale }}
          initial={animateIn ? { rotateX: 22, scale: .94 } : false}
          animate={{ rotateX: 0, scale: 1 }}
          transition={{ duration: 1.5, ease: [...motionTokens.ease.enter], delay: .36 }}
        >
          {/* The scale is a Motion value rather than a CSS transform, so layout animations inside (the date range highlight)
              are projected through it and slide exactly between options at any screen width. */}
          <motion.div className={styles.scaler} style={{ width: box.width, height, scale: box.scale, originX: 0, originY: 0 }}>
            <LumenDashboard narrow={narrow} />
          </motion.div>
        </motion.div>
      </div>
    </motion.div>
  </section></MotionConfig>;
}

/** The Lumen app window. Everything is content except the range control, which changes the data the whole screen shows. */
function LumenDashboard({ narrow }: { narrow: boolean }) {
  const [range, setRange] = useState<LumenRange>("30d");
  const insight = lumenInsight[range];
  return <div className={styles.window} data-narrow={narrow || undefined} role="group" aria-label="Lumen revenue dashboard, sample data">
    <div className={styles.chrome} aria-hidden="true">
      <span className={styles.lights}><i /><i /><i /></span>
      <span className={styles.url}>app.lumen.so/acme/overview</span>
    </div>
    <div className={styles.app}>
      {!narrow && <aside className={styles.sidebar}>
        <div className={styles.workspace}>
          <span className={styles.wsMark}>A</span>
          <span className={styles.wsName}>Acme Cloud</span>
          <ChevronsUpDown size={14} strokeWidth={1.75} className={styles.dim} />
        </div>
        <div className={styles.search}><Search size={14} strokeWidth={1.75} />Search<kbd>⌘K</kbd></div>
        <div className={styles.nav}>
          {NAV.map(({ label, icon: Icon, active }) => <div key={label} className={styles.navItem} data-active={active || undefined}><Icon size={15} strokeWidth={1.75} />{label}</div>)}
        </div>
        <div className={styles.groupLabel}>Saved views</div>
        <div className={styles.nav}>
          {VIEWS.map(view => <div key={view.label} className={styles.navItem}><i className={styles.dot} data-tone={view.tone} />{view.label}</div>)}
        </div>
        <div className={styles.me}>
          <Avatar name="Chloe Nguyen" src={avatar("chloe-nguyen")} size="sm" />
          <span><strong>Chloe Nguyen</strong><small>Data analyst</small></span>
          <Settings size={15} strokeWidth={1.75} className={styles.dim} />
        </div>
      </aside>}
      <div className={styles.main}>
        <header className={styles.head}>
          <div>
            <h2 className={styles.h2}>Revenue overview</h2>
            <p className={styles.sub}>Synced with Stripe 2 minutes ago</p>
          </div>
          <SegmentedControl label="Date range" options={lumenRanges} value={range} onValueChange={value => setRange(value as LumenRange)} className={styles.range} />
        </header>

        <div className={styles.kpis}>
          {lumenKpis.map(kpi => {
            const now = kpi.byRange[range];
            return <div key={kpi.id} className={styles.kpi}>
              <Sparkline label={kpi.label} value={now.value} change={now.change} data={now.data} tone={kpi.tone} width={220} height={34} interactive={false} />
            </div>;
          })}
        </div>

        <div className={styles.grid}>
          <div className={styles.card}>
            <div className={styles.cardHead}>
              <p className={styles.insight}><Sparkles size={15} strokeWidth={1.75} className={styles.insightIcon} /><span><strong>{insight.lead}</strong> {insight.rest}</span></p>
              <div className={styles.legend} aria-hidden="true"><span><i className={styles.swatch} />This period</span><span><i className={styles.swatch} data-last="" />Last period</span></div>
            </div>
            <LineChart
              label="Net new MRR"
              data={lumenSeries[range]}
              series={[{ key: "mrr", label: "This period", area: true }, { key: "last", label: "Last period", dashed: true }]}
              height={narrow ? 210 : 232}
              legend={false}
              formatValue={value => money.format(value)}
              formatTick={compact}
            />
          </div>
          {!narrow && <div className={styles.card}>
            <div className={styles.cardTitle}>Biggest movers<span>MRR change</span></div>
            <ul className={styles.movers}>
              {lumenMovers.map(mover => <li key={mover.name}>
                <span className={styles.logo} data-mono={mover.mono || undefined} style={mover.mono ? { maskImage: `url(${mover.logo})`, WebkitMaskImage: `url(${mover.logo})` } : { backgroundImage: `url(${mover.logo})` }} />
                <span className={styles.who}><strong>{mover.name}</strong><small>{mover.change}</small></span>
                <span className={styles.amount} data-down={mover.amount < 0 || undefined}>{mover.amount < 0 ? "−" : "+"}{money.format(Math.abs(mover.amount))}</span>
              </li>)}
            </ul>
          </div>}
        </div>
      </div>
    </div>
  </div>;
}

export default HeroLumen;
```

### `registry/blocks/hero-section/hero-lumen.module.css`

```css
/* Centered, one full screen: copy over a drifting mesh, and a product window that rises from the bottom edge and runs off it.
   The hero is exactly one screen tall (never shorter than 600px); the window takes whatever height the copy leaves. */
/* The section is the container, so its children carry the side gutters and the queries below can change them. */
.hero { isolation: isolate; display: flex; height: 100vh; height: 100svh; min-height: 600px; flex-direction: column; padding-top: clamp(88px, 14svh, 144px); text-align: center; }

/* The mesh fills the screen. It is authored per theme: tints a few steps off the page, so it reads as light rather than color. */
.mesh {
  --mesh-base: var(--background);
  --mesh-1: #ffe6d8; --mesh-2: #e3dcff; --mesh-3: #dbe4ff; --mesh-4: #f0e0ff; --mesh-5: #e0eefc;
  z-index: -1;
}
:global(:root[data-theme="dark"]) .mesh { --mesh-1: #2a1822; --mesh-2: #1c1a44; --mesh-3: #141c40; --mesh-4: #231842; --mesh-5: #0e2133; }

.copy { position: relative; display: grid; justify-items: center; gap: var(--space-5); max-width: 900px; margin: 0 auto; padding-inline: var(--space-10); }
.title { max-width: 14ch; font-size: clamp(2.5rem, 1.2rem + 5cqi, var(--text-5xl)); }
.description { max-width: 590px; }
.actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-3); margin-top: var(--space-3); }
.fine { margin: 0; color: var(--text-muted); font-size: var(--text-sm); }

/* Stage: fills the rest of the screen and is the visible crop. The window runs past its bottom edge; the mask fades it into the
   mesh, so there is no hard edge. The halo above leaves room for the light behind the window. */
.stage { --halo: 96px; position: relative; flex: 1 1 0; box-sizing: border-box; width: 100%; min-height: 0; max-width: calc(1200px + 2 * var(--space-10)); margin: calc(var(--space-12) - var(--halo)) auto 0; overflow: hidden; padding: var(--halo) var(--space-10) 0; -webkit-mask-image: linear-gradient(to bottom, #000 62%, transparent 100%); mask-image: linear-gradient(to bottom, #000 62%, transparent 100%); }
.glow {
  position: absolute; z-index: -1; top: calc(var(--halo) - 110px); left: 10%; right: 10%; height: 240px;
  background: radial-gradient(50% 50% at 50% 50%, var(--hero-glow), transparent 72%);
  filter: blur(36px);
  --hero-glow: color-mix(in oklab, color-mix(in oklab, var(--accent) 35%, #7d8cff) 36%, transparent);
}
:global(:root[data-theme="dark"]) .glow { --hero-glow: color-mix(in oklab, color-mix(in oklab, var(--accent) 30%, #6e7cff) 58%, transparent); }
.window { position: relative; }
/* A thin light catches the top edge, the way a real display does in a dark room. */
:global(:root[data-theme="dark"]) .window::after { content: ""; position: absolute; top: 0; left: 12%; right: 12%; height: 1px; background: linear-gradient(to right, transparent, color-mix(in oklab, #b9c2ff 70%, transparent), transparent); pointer-events: none; }
.perspective { display: flex; justify-content: center; perspective: 2200px; perspective-origin: 50% 0%; }
.tilt { position: relative; flex: none; transform-origin: 50% 0%; will-change: transform; }
.scaler { position: absolute; top: 0; left: 0; transform-origin: 0 0; }
/* Reduced motion: the window is already in place; drop the GPU layer kept for the entrance. */
@media (prefers-reduced-motion: reduce) { .tilt { will-change: auto; } }

/* The window. */
.window {
  display: grid; grid-template-rows: auto minmax(0, 1fr); height: 100%; overflow: hidden;
  border: 1px solid var(--border); border-radius: 16px; background: var(--surface); color: var(--foreground);
  text-align: left; font-size: var(--text-sm);
  box-shadow: 0 1px 1px rgb(20 24 40 / .04), 0 24px 48px -16px rgb(20 24 40 / .16), 0 60px 120px -40px rgb(40 48 110 / .22);
}
:global(:root[data-theme="dark"]) .window {
  border-color: color-mix(in oklab, var(--foreground) 14%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in oklab, var(--foreground) 9%, transparent), 0 24px 48px -16px rgb(0 0 0 / .6), 0 60px 140px -40px rgb(40 50 140 / .35);
}
.chrome { position: relative; display: flex; height: 40px; align-items: center; justify-content: center; border-bottom: 1px solid var(--border-subtle); background: color-mix(in oklab, var(--surface-muted) 55%, var(--surface)); }
.lights { position: absolute; left: 16px; display: flex; gap: 7px; }
.lights i { width: 11px; height: 11px; border-radius: 50%; background: color-mix(in oklab, var(--foreground) 14%, transparent); }
.url { border-radius: 8px; padding: 3px 14px; background: var(--surface); color: var(--text-muted); font-size: var(--text-xs); box-shadow: inset 0 0 0 1px var(--border-subtle); }

.app { display: grid; grid-template-columns: 224px minmax(0, 1fr); min-height: 0; }
.window[data-narrow] .app { grid-template-columns: minmax(0, 1fr); }

.sidebar { display: flex; flex-direction: column; gap: 2px; border-right: 1px solid var(--border-subtle); padding: 14px 12px; background: color-mix(in oklab, var(--surface-muted) 40%, var(--surface)); }
.workspace { display: flex; align-items: center; gap: 10px; padding: 6px 8px 10px; }
.wsMark { display: grid; width: 22px; height: 22px; place-items: center; border-radius: 6px; background: var(--foreground); color: var(--background); font-size: var(--text-xs); font-weight: 500; }
.wsName { flex: 1; font-weight: 500; }
.dim { color: var(--text-muted); }
.search { display: flex; align-items: center; gap: 8px; margin: 0 0 10px; border: 1px solid var(--border-subtle); border-radius: 9px; padding: 6px 8px; background: var(--surface); color: var(--text-muted); }
.search kbd { margin-left: auto; font: inherit; font-size: var(--text-xs); }
.nav { display: grid; gap: 1px; }
.navItem { display: flex; align-items: center; gap: 10px; border-radius: 8px; padding: 6px 8px; color: var(--text-secondary); }
.navItem svg { color: var(--text-muted); }
.navItem[data-active] { background: var(--accent-subtle); color: var(--foreground); font-weight: 500; }
.navItem[data-active] svg { color: var(--accent); }
.groupLabel { margin: 18px 8px 6px; color: var(--text-muted); font-size: var(--text-xs); }
.dot { width: 8px; height: 8px; margin: 0 3px; border-radius: 50%; background: var(--text-muted); }
.dot[data-tone="accent"] { background: var(--accent); }
.dot[data-tone="danger"] { background: var(--danger); }
.me { display: flex; align-items: center; gap: 10px; margin-top: auto; border-top: 1px solid var(--border-subtle); padding: 12px 6px 2px; }
.me span:not([role]) { display: grid; flex: 1; line-height: 1.3; }
.me strong { font-weight: 500; }
.me small { color: var(--text-muted); font-size: var(--text-xs); }

.main { display: grid; align-content: start; gap: 16px; min-width: 0; padding: 22px 24px; }
.head { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.h2 { margin: 0; font-size: var(--text-lg); font-weight: 500; letter-spacing: var(--tracking-body); }
.sub { margin: 2px 0 0; color: var(--text-muted); font-size: var(--text-xs); }
.range { flex: none; }

.kpis { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
.window[data-narrow] .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.kpi { min-width: 0; border: 1px solid var(--border-subtle); border-radius: 12px; padding: 14px 16px 12px; background: var(--surface); }

.grid { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); gap: 12px; }
.window[data-narrow] .grid { grid-template-columns: minmax(0, 1fr); }
.card { display: grid; align-content: start; gap: 14px; min-width: 0; border: 1px solid var(--border-subtle); border-radius: 12px; padding: 16px 18px; background: var(--surface); }
.cardHead { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.insight { display: flex; gap: 8px; max-width: 46ch; margin: 0; color: var(--text-secondary); line-height: 1.45; }
.insight strong { color: var(--foreground); font-weight: 500; }
.insightIcon { flex: none; margin-top: 2px; color: var(--accent); }
.legend { display: flex; flex: none; gap: 14px; color: var(--text-muted); font-size: var(--text-xs); }
.legend span { display: inline-flex; align-items: center; gap: 6px; }
.swatch { width: 14px; height: 2px; border-radius: 2px; background: var(--accent); }
.swatch[data-last] { background: repeating-linear-gradient(to right, color-mix(in oklab, var(--foreground) 46%, var(--surface)) 0 4px, transparent 4px 7px); }
.window[data-narrow] .legend { display: none; }

.cardTitle { display: flex; justify-content: space-between; font-weight: 500; }
.cardTitle span { color: var(--text-muted); font-size: var(--text-xs); font-weight: 400; }
.movers { display: grid; margin: 0; padding: 0; list-style: none; }
.movers li { display: flex; align-items: center; gap: 12px; border-top: 1px solid var(--border-subtle); padding: 10px 0; }
.movers li:first-child { border-top: 0; padding-top: 2px; }
.logo { flex: none; width: 20px; height: 20px; background: center / contain no-repeat; }
.logo[data-mono] { background: var(--foreground); -webkit-mask: center / contain no-repeat; mask: center / contain no-repeat; }
.who { display: grid; flex: 1; min-width: 0; line-height: 1.3; }
.who strong { font-weight: 500; }
.who small { overflow: hidden; color: var(--text-muted); font-size: var(--text-xs); text-overflow: ellipsis; white-space: nowrap; }
.amount { color: var(--success); font-variant-numeric: tabular-nums; font-weight: 500; }
.amount[data-down] { color: var(--danger); }

@container hero (max-width: 560px) {
  .hero { padding-top: 84px; }
  .copy { gap: var(--space-4); padding-inline: var(--space-4); }
  .actions { width: 100%; margin-top: var(--space-2); }
  .actions > * { flex: 1 1 auto; }
  .stage { --halo: 56px; margin-top: calc(var(--space-10) - var(--halo)); padding-inline: 0; }
}

/* KPI tiles reuse Arc Sparkline; its caption wraps so the label sits above a larger value. */
.kpi :global(figcaption) { flex-wrap: wrap; row-gap: 4px; }
.kpi :global(figcaption) > :first-child { flex-basis: 100%; font-size: var(--text-xs); color: var(--text-muted); }
.kpi :global(figcaption) strong { margin-left: 0; font-size: var(--text-2xl); letter-spacing: var(--tracking-body); }
.kpi :global(figcaption) small { align-self: center; }
```

### `registry/blocks/hero-section/hero-lumen-data.ts`

```ts
import type { LineChartDatum } from "../../line-chart/line-chart";

/** Sample data for Lumen, a revenue analytics product. Replace it with your own product's numbers. */

export type LumenRange = "30d" | "90d" | "12m";

export const lumenRanges: { value: LumenRange; label: string }[] = [
  { value: "30d", label: "30D" },
  { value: "90d", label: "90D" },
  { value: "12m", label: "12M" },
];

/** A smooth, deterministic walk: a trend, one slow wave, and a little noise that repeats for the same seed. */
function walk(count: number, start: number, end: number, wave: number, seed: number) {
  let s = seed;
  const rand = () => { s = (s * 16807) % 2147483647; return s / 2147483647 - .5; };
  return Array.from({ length: count }, (_, i) => {
    const t = i / (count - 1);
    return Math.round(start + (end - start) * (t * t * .35 + t * .65) + Math.sin(t * Math.PI * 2.3) * wave + rand() * wave * .6);
  });
}

const months = ["Oct", "Nov", "Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep"];

function series(range: LumenRange): LineChartDatum[] {
  if (range === "12m") {
    const now = walk(12, 9_800, 36_400, 2_600, 7), before = walk(12, 6_200, 21_900, 2_200, 19);
    return months.map((month, i) => ({ key: `m${i}`, label: `${month} ${i < 3 ? 2025 : 2026}`, axisLabel: i % 2 === 0 ? month : undefined, values: { mrr: now[i], last: before[i] } }));
  }
  const days = range === "30d" ? 30 : 13;
  const step = range === "30d" ? 1 : 7;
  const now = walk(days, range === "30d" ? 640 : 4_300, range === "30d" ? 1_960 : 9_800, range === "30d" ? 260 : 900, range === "30d" ? 3 : 11);
  const before = walk(days, range === "30d" ? 520 : 3_600, range === "30d" ? 1_180 : 6_100, range === "30d" ? 220 : 700, 29);
  const end = new Date(Date.UTC(2026, 8, 24));
  return now.map((value, i) => {
    const date = new Date(end.getTime() - (days - 1 - i) * step * 86_400_000);
    const label = date.toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" });
    const every = range === "30d" ? 7 : 3;
    return { key: date.toISOString().slice(0, 10), label, axisLabel: (days - 1 - i) % every === 0 ? label : undefined, values: { mrr: value, last: before[i] } };
  });
}

export const lumenSeries: Record<LumenRange, LineChartDatum[]> = { "30d": series("30d"), "90d": series("90d"), "12m": series("12m") };

export interface LumenKpi {
  id: string;
  label: string;
  tone: "accent" | "success" | "warning" | "danger";
  byRange: Record<LumenRange, { value: string; change: string; data: number[] }>;
}

export const lumenKpis: LumenKpi[] = [
  { id: "mrr", label: "MRR", tone: "accent", byRange: {
    "30d": { value: "$482.9K", change: "+8.2%", data: walk(12, 446, 483, 4, 5) },
    "90d": { value: "$482.9K", change: "+19.9%", data: walk(12, 402, 483, 6, 9) },
    "12m": { value: "$482.9K", change: "+51.7%", data: walk(12, 318, 483, 8, 13) },
  } },
  { id: "nrr", label: "Net retention", tone: "success", byRange: {
    "30d": { value: "118%", change: "+3 pts", data: walk(12, 112, 118, 1.5, 17) },
    "90d": { value: "116%", change: "+5 pts", data: walk(12, 109, 116, 1.8, 21) },
    "12m": { value: "114%", change: "+9 pts", data: walk(12, 103, 114, 2.4, 23) },
  } },
  { id: "new", label: "New customers", tone: "success", byRange: {
    "30d": { value: "214", change: "+12%", data: walk(12, 5, 9, 2, 31) },
    "90d": { value: "602", change: "+18%", data: walk(12, 38, 56, 6, 37) },
    "12m": { value: "2,140", change: "+34%", data: walk(12, 120, 214, 16, 41) },
  } },
  { id: "churn", label: "Churned MRR", tone: "success", byRange: {
    "30d": { value: "$6.1K", change: "−14%", data: walk(12, 9, 6, 1.2, 43) },
    "90d": { value: "$21.4K", change: "−9%", data: walk(12, 26, 21, 2, 47) },
    "12m": { value: "$96.8K", change: "−22%", data: walk(12, 12, 7, 1.4, 53) },
  } },
];

export interface LumenMover {
  name: string;
  logo: string;
  /** Mono marks draw in the text color through a mask; color marks keep their own colors. */
  mono?: boolean;
  change: string;
  amount: number;
}

export const lumenMovers: LumenMover[] = [
  { name: "Linear", logo: "/block-logos/linear-color.svg", change: "Moved to Enterprise", amount: 4_200 },
  { name: "Raycast", logo: "/block-logos/raycast-color.svg", change: "Added 120 seats", amount: 2_850 },
  { name: "Vercel", logo: "/block-logos/vercel.svg", mono: true, change: "Annual prepay", amount: 1_900 },
  { name: "Loom", logo: "/block-logos/loom-color.svg", change: "Removed 40 seats", amount: -1_240 },
  { name: "Supabase", logo: "/block-logos/supabase-color.svg", change: "Added forecasting", amount: 980 },
  { name: "Framer", logo: "/block-logos/framer.svg", mono: true, change: "Upgraded to Scale", amount: 760 },
];

export const lumenInsight: Record<LumenRange, { lead: string; rest: string }> = {
  "30d": { lead: "MRR grew $36.4K this month.", rest: "62% came from 14 expansions on the Scale plan, led by Linear." },
  "90d": { lead: "MRR grew $80.6K this quarter.", rest: "Expansion outpaced new business for the first time since March." },
  "12m": { lead: "MRR grew $164.5K in twelve months.", rest: "Net retention above 110% did more than new logos did." },
};
```

### `lib/media.ts`

```ts
/**
 * Shared demo media. Files live in `public/media/`, credits in `public/media/CREDITS.md`.
 * Portraits are 400 px squares; photos are 1600 px on the long side.
 */

export interface MediaPerson {
  id: string;
  name: string;
  role?: string;
  src: string;
}

export interface MediaPhoto {
  id: string;
  alt: string;
  src: string;
  width: number;
  height: number;
}

export const people = [
  { id: "emma-collins", name: "Emma Collins", role: "Product designer", src: "/media/people/emma-collins.jpg" },
  { id: "marcus-johnson", name: "Marcus Johnson", role: "Frontend engineer", src: "/media/people/marcus-johnson.jpg" },
  { id: "jasmine-brooks", name: "Jasmine Brooks", role: "Design lead", src: "/media/people/jasmine-brooks.jpg" },
  { id: "olivia-bennett", name: "Olivia Bennett", role: "Platform engineer", src: "/media/people/olivia-bennett.jpg" },
  { id: "sofia-ramirez", name: "Sofia Ramirez", role: "Operations lead", src: "/media/people/sofia-ramirez.jpg" },
  { id: "ryan-sullivan", name: "Ryan Sullivan", role: "Account executive", src: "/media/people/ryan-sullivan.jpg" },
  { id: "hannah-walsh", name: "Hannah Walsh", role: "Customer success", src: "/media/people/hannah-walsh.jpg" },
  { id: "chloe-nguyen", name: "Chloe Nguyen", role: "Data analyst", src: "/media/people/chloe-nguyen.jpg" },
  { id: "ava-mitchell", name: "Ava Mitchell", role: "Marketing manager", src: "/media/people/ava-mitchell.jpg" },
  { id: "daniel-kim", name: "Daniel Kim", role: "Backend engineer", src: "/media/people/daniel-kim.jpg" },
  { id: "jordan-reyes", name: "Jordan Reyes", role: "Support specialist", src: "/media/people/jordan-reyes.jpg" },
  { id: "mateo-alvarez", name: "Mateo Alvarez", role: "Mobile engineer", src: "/media/people/mateo-alvarez.jpg" },
  { id: "tyler-hayes", name: "Tyler Hayes", role: "Sales lead", src: "/media/people/tyler-hayes.jpg" },
  { id: "andre-williams", name: "Andre Williams", role: "Finance partner", src: "/media/people/andre-williams.jpg" },
  { id: "nathan-cole", name: "Nathan Cole", role: "Engineering manager", src: "/media/people/nathan-cole.jpg" },
  { id: "diane-foster", name: "Diane Foster", role: "Chief operating officer", src: "/media/people/diane-foster.jpg" },
] as const satisfies readonly MediaPerson[];

export const photos = [
  { id: "lounge-chair", alt: "A woven oak lounge chair with a sheepskin and linen cushion on a concrete floor", src: "/media/photos/lounge-chair.jpg", width: 1280, height: 1600 },
  { id: "table-lamp", alt: "A white mushroom table lamp glowing beside books and a small vase", src: "/media/photos/table-lamp.jpg", width: 1600, height: 900 },
  { id: "linen-throw", alt: "Folded natural linen throws with fringed edges in soft window light", src: "/media/photos/linen-throw.jpg", width: 1067, height: 1600 },
  { id: "glass-carafe", alt: "A hand pouring water from a ribbed glass carafe into tumblers", src: "/media/photos/glass-carafe.jpg", width: 1280, height: 1600 },
  { id: "stoneware-cups", alt: "Two speckled stoneware cups with a lid on a pale table", src: "/media/photos/stoneware-cups.jpg", width: 1067, height: 1600 },
  { id: "stacked-bowls", alt: "Two stacked speckled ceramic bowls against a dark wall", src: "/media/photos/stacked-bowls.jpg", width: 1600, height: 1067 },
  { id: "ceramic-lamp", alt: "A sculptural ceramic lamp with a linen shade on a walnut sideboard", src: "/media/photos/ceramic-lamp.jpg", width: 1067, height: 1600 },
  { id: "living-room", alt: "A bright living room with timber beams, arched windows, and cream sofas", src: "/media/photos/living-room.jpg", width: 1200, height: 1600 },
  { id: "sunroom", alt: "A sunroom with a round dining table, plants, and windows on three sides", src: "/media/photos/sunroom.jpg", width: 1600, height: 1067 },
  { id: "home-office", alt: "A home office with a wooden desk and deep green walls", src: "/media/photos/home-office.jpg", width: 1600, height: 1200 },
  { id: "reading-chair", alt: "A grey armchair and ottoman with a knit throw in a dark green room", src: "/media/photos/reading-chair.jpg", width: 1600, height: 900 },
  { id: "bedroom", alt: "A made bed with striped linen pillows against an oak headboard", src: "/media/photos/bedroom.jpg", width: 1067, height: 1600 },
  { id: "restaurant", alt: "A warm restaurant dining room with woven pendant lights and a tree", src: "/media/photos/restaurant.jpg", width: 1067, height: 1600 },
  { id: "wine-bar", alt: "A glass carafe of red wine on a bar table in low evening light", src: "/media/photos/wine-bar.jpg", width: 1600, height: 1067 },
  { id: "concert-hall", alt: "Curved stainless steel panels of the Walt Disney Concert Hall against a blue sky", src: "/media/photos/concert-hall.jpg", width: 1600, height: 1143 },
  { id: "curved-facade", alt: "A white tiled building facade with curved balconies", src: "/media/photos/curved-facade.jpg", width: 1600, height: 1067 },
  { id: "pool-house", alt: "A modern glass house beside a long pool under a clear sky", src: "/media/photos/pool-house.jpg", width: 1600, height: 900 },
  { id: "terracotta-waves", alt: "Wavy terracotta walls rising toward a blue sky", src: "/media/photos/terracotta-waves.jpg", width: 1067, height: 1600 },
  { id: "mountain-ridges", alt: "Layered mountain ridges under a warm evening sky", src: "/media/photos/mountain-ridges.jpg", width: 1600, height: 1068 },
  { id: "alpine-lake", alt: "A calm alpine lake reflecting a rocky peak at golden hour", src: "/media/photos/alpine-lake.jpg", width: 1067, height: 1600 },
  { id: "coastline", alt: "A long coastline with waves rolling onto a beach below green cliffs", src: "/media/photos/coastline.jpg", width: 1200, height: 1600 },
  { id: "sea-at-dusk", alt: "A calm sea at dusk with a low island on the horizon", src: "/media/photos/sea-at-dusk.jpg", width: 1067, height: 1600 },
  { id: "lisbon-tram", alt: "A yellow tram on a street lined with historic buildings in Lisbon", src: "/media/photos/lisbon-tram.jpg", width: 1600, height: 1064 },
  { id: "lisbon-bridge", alt: "The 25 de Abril Bridge crossing the Tagus in Lisbon", src: "/media/photos/lisbon-bridge.jpg", width: 1600, height: 1166 },
  { id: "lisbon-rooftops", alt: "Terracotta rooftops of Lisbon running down to the river", src: "/media/photos/lisbon-rooftops.jpg", width: 1280, height: 1600 },
  { id: "salmon-dinner", alt: "Seared salmon with a bright herb salsa and a glass of red wine", src: "/media/photos/salmon-dinner.jpg", width: 1067, height: 1600 },
  { id: "chef-plating", alt: "A chef spooning sauce onto a plated dish in a dark kitchen", src: "/media/photos/chef-plating.jpg", width: 1600, height: 1600 },
] as const satisfies readonly MediaPhoto[];

export type PersonId = (typeof people)[number]["id"];
export type PhotoId = (typeof photos)[number]["id"];

/** Looks up a person by id. */
export function person(id: PersonId): MediaPerson {
  return people.find(entry => entry.id === id)!;
}

/** Looks up a photo by id. */
export function photo(id: PhotoId): MediaPhoto {
  return photos.find(entry => entry.id === id)!;
}

/** Square 400 px avatar path for a person id, for `src` props. */
export const avatar = (id: PersonId) => person(id).src;

/** 800 × 1000 portrait crop of the same photo, for image-led layouts. */
export const portrait = (id: PersonId) => `/media/people/portrait/${id}.jpg`;

/** The first `count` people, for avatar stacks and lists. */
export const peopleSample = (count: number) => people.slice(0, count);
```
