# Landing Hero (Lumen Halo): prompt.md (v1.1.0)

- id: `glass-landing-hero` · version 1.1.0 · block · pro (All-Access)
- category: Marketing
- build: Base UI (one set of files; its dependencies follow the build)
- install (this build): `npx shadcn@latest add @beautiful-ui-pro/glass-landing-hero`
- npm dependencies: @web-kits/audio@^0.2
- registry dependencies: https://beautiful-ui.dev/r/beautiful-ui.json
- docs: https://beautiful-ui.dev/components/glass-landing-hero
- The install command carries everything this item needs (files, CSS, tokens, npm and registry dependencies). Prefer it to copying source by hand.

A liquid-glass landing hero in dark and light: the nav pill with the gliding lens, an announcement pill, a two-tone headline, the call to action pair and a live trend chart as the product visual (or a video, your own node, or nothing).

## Build it
- Stack: React 19 (`ref` is a plain prop), TypeScript, Tailwind CSS v4 utilities, a shadcn-initialised project with the `@/*` alias.
- Packages: `@web-kits/audio`.
- Source: `components/beautiful-ui/blocks/glass-landing-hero.tsx`, shared code in `lib/beautiful-ui/`.
- Exports to keep: `GlassLandingHero`, `GlassLandingHeroNav`, and every exported type.
- CSS: none to add. Themes, tokens and keyframes are inlined by the component (a deduped `<style>` built with `lumenThemeCss` from `lib/beautiful-ui/glass.tsx`); retheme through the `--glass-*` variables, never with Tailwind colour classes inside the component.

```tsx
import { GlassLandingHero } from "@/components/beautiful-ui/blocks/glass-landing-hero";
```

## Parts

| Part | data-slot | What it is for |
|---|---|---|
| `GlassLandingHero` | — | The landing hero block: headline, calls to action and the live chart visual. |
| `GlassLandingHeroNav` | — | The hero's own top bar. |

## Sound
- Keep every `data-slot` and `data-sound` attribute: the sound layer reads them.
- Installing this item adds no audio. Nothing plays until the app mounts `GlassSoundProvider` once (install: `npx shadcn@latest add https://beautiful-ui.dev/r/glass-sound.json`, import from `@/components/beautiful-ui/glass-sound`); `GlassSoundToggle` is its mute control. Without a provider the audio engine never loads; `sound={false}` silences one instance.
- This item: tap on the CTAs and links, open / close on the phone menu (close on Escape too), select (pitched by position) on the chart's period tabs, a very quiet tick on arrow moves through the chart, open on the video play button. Nothing on hover or mount. Needs a GlassSoundProvider; sound={false} silences this instance; motion settings do not affect sound.

## Match the original
- Read `components/beautiful-ui/blocks/glass-landing-hero.tsx` as the reference implementation before changing or recreating anything, and match it: sizes, colours per theme, motion timings, copy and behaviour.
- If you deviate (a prop you can't honour, a style you changed, a dependency you swapped), say so in your reply, part by part.
- Keep the accessibility contract, the keyboard map and the motion levels listed below.

## Use it when
- landing page hero, above the fold, product launch page, SaaS homepage header, hero with live chart, hero with product video, marketing header with nav and call to action
- The first screen of a SaaS or data product's landing page: say what it does, show it working, get the Start free click
- Launch pages: the announcement pill points to the release notes, the chart or video shows the product
- With your real metric: pass a weekly series (and push new ones for live data) so the visual is the product, not a screenshot
- Inner marketing pages: use GlassLandingHeroNav alone, or pass nav={<GlassNav />} for the mega-menu nav

### Not when
- In-app dashboards: use glass-trend (the full chart with milestones and markers) or glass-stat-cards
- Pricing or plan selection: use a pricing section below the hero
- Navigation only: use glass-nav (mega-menu, condensed bar and phone sheet)

## Mistakes
- Import from @/components/beautiful-ui/blocks/glass-landing-hero (blocks install under blocks/)
- Memoize series when you build it in render: a new array counts as new data and blinks the live dot
- Pass values oldest first; give dates or end when the last point isn't this week, or the months on the axis will be off
- Milestone index points into the full series, not the shown period; milestones outside the period are not drawn
- Don't leave simulateLive on with real data: it adds made-up points (it is off by default whenever you pass series)
- A custom nav node sets its own theme: pass the same theme to it (e.g. <GlassNav theme="dark" />) when you pin the hero's
- The layout follows the hero's width, not the viewport: in a narrow column it shows the phone layout on purpose
- It is a client component: pass functions (onClick, onPeriodChange, formatters) from a client component, or keep links as plain hrefs when rendering from a Server Component
- Use linkComponent={Link} for client-side routing instead of onClick handlers that call router.push

## Usage

```tsx
import { GlassLandingHero } from "@/components/beautiful-ui/blocks/glass-landing-hero";

// Zero props: the designed Lumen hero with a live sample chart
<GlassLandingHero />

// Your product, your metric (pass a new array when data arrives: it animates in)
<GlassLandingHero
  nav={{
    brand: { name: "Kitelabs", href: "/", mark: <KitelabsLogo /> },
    links: [{ label: "Product", href: "/product" }, { label: "Pricing", href: "/pricing" }],
    signIn: { label: "Log in", href: "/login" },
    cta: { label: "Try it free", href: "/signup" },
  }}
  announcement={{ tag: "NEW", label: "Kitelabs 3.0 is out", href: "/blog/3-0" }}
  headline={["Analytics your whole team reads.", "Live, clear, in one place."]}
  subline="Connect your warehouse and share live charts in minutes."
  primary={{ label: "Try it free", href: "/signup" }}
  secondary={{ label: "Talk to sales", onClick: openContact }}
  note="14-day trial · no card needed"
  visual={{
    kind: "chart",
    series: weeklyActiveTeams, // oldest first
    end: "2026-09-21",
    eyebrow: "WEEKLY ACTIVE TEAMS",
    unit: "weekly active teams",
    milestones: [{ index: 84, label: "3.0 · May 4" }],
    onPeriodChange: (p) => track("hero_period", p),
  }}
/>

// A product video instead of the chart
<GlassLandingHero visual={{ kind: "video", src: "/tour.mp4", poster: "/tour.jpg", duration: "1:12", caption: null }} />

// Your own nav, no visual
<GlassLandingHero nav={<GlassNav />} visual={null} />
```

## Props

| Prop | Type | Default | What it does |
|---|---|---|---|
| `nav` | `{ brand?, links?, signIn?, cta?, menuLabel? } \| ReactNode \| null` | `the Lumen pill` | Options configure the built-in nav pill (links get the gliding lens; below 760px they move into the menu dropdown). A node (e.g. <GlassNav />) replaces it; null hides it. Every link is { label, href?, onClick?, target?, rel?, aria-label? }; href renders an <a> (or linkComponent), onClick alone a <button>. |
| `linkComponent` | `ElementType` | `"a"` | Renders every link that has an href, e.g. Next's Link or React Router's Link, for client-side navigation. |
| `announcement` | `{ tag?, label, href?, onClick? } \| null` | `{ tag: "NEW", label: "Story charts are here" }` | The pill above the headline. tag: null hides the amber tag. |
| `headline` | `string \| [lead, muted?]` | `["Charts that move with your data.", "Clear in a second, alive all day."]` | The one <h1>. The second half is set in the muted tone. Balanced wrapping; long headlines (over 90 characters) widen to 1000px. |
| `headlineMaxWidth` | `number` | `860 (1000 when long)` | Headline max width in px. |
| `subline` | `ReactNode \| null` |  | The sentence under the headline (max 560px). null hides it. |
| `primary / secondary` | `{ label, href?, onClick? } \| null` | `"Start free" / "Book a demo"` | The solid pill (with the round arrow) and the glass pill. Labels may be long: the pair wraps, and a label wraps inside its pill before it overflows. |
| `note` | `ReactNode \| null` | `"Free for 3 seats · no card needed"` | Mono line under the buttons. null hides it. |
| `visual` | `{ kind: "chart", … } \| { kind: "video", … } \| { kind: "none" } \| ReactNode \| null` | `{ kind: "chart" }` | The product visual under the copy. Without one (none or null) the copy centres with more room above and below. A node is shown with the same entrance and no card. |
| `visual (chart)` | `{ series?, dates?, end?, interval?, labels?, periods?, period?, defaultPeriod?, onPeriodChange?, live?, simulateLive?, liveInterval?, milestones?, compareTo?, eyebrow?, unit?, liveLabel?, latestLabel?, pulse?, locale?, messages?, formatValue?, formatTick?, formatDate?, formatChange?, height?, aria-label? }` |  | series: values oldest first (weekly by default); new arrays animate in (the line shifts, the number rolls, the live dot blinks). dates or end + interval (week, day, month) place them in time (default: your data ends this week). periods: { id?, label, points } tabs of the latest N points (default 3M/13, 6M/26, 12M/52; null hides them); period + onPeriodChange control them by id (the label when there is no id). milestones: { index, label }[] draw amber dots on the line with a pill that fades in as the trace passes. compareTo: points back for the change (default one year; null hides it). simulateLive: demo ticks every liveInterval ms (default on only with the built-in sample). pulse: false stops the idle halo (the loop then sleeps). locale and messages translate the default formats and fixed strings. |
| `visual (video)` | `{ src?, poster?, duration?, title?, caption?, label?, onPlay?, videoProps?, tracks? }` | `"1:12 · product video goes here"` | A 16:9 glass card with the play pill. With src, play swaps in a real <video> with controls; return false from onPlay to open your own player instead. Without a poster the glass card shows. videoProps go on the <video> (muted, loop, preload …); tracks add captions. |
| `background` | `boolean` | `true` | Paints the hero's own background and indigo/cyan glows. false keeps it transparent over your page. |
| `motion` | `"full" \| "subtle" \| "off"` | `"full"` | full: the designed entrance, trace, rolling digits and pulse. subtle: calm 30% shorter entrance, no stagger, no trace or pulse. off: everything instant. prefers-reduced-motion always wins: 150ms fades, no trace, instant morphs, a still dot. |
| `theme` | `"system" \| "dark" \| "light"` | `"system"` | system follows a .dark / .light class or data-theme on an ancestor (next-themes, shadcn), else the OS. |
| `sound` | `boolean \| "subtle"` | `true` | Built-in sound: true plays Lumen cues when a GlassSoundProvider (glass-sound) enables sound; false silences this instance (its cues and its clicks); "subtle" plays at 55%. Without a provider it is silent. |
| `className / style / ref / …div props` | `HTML div props` |  | Spread on the root, which is also the container the layout queries (it switches at 760px of its own width, not the viewport). |

Full docs: https://beautiful-ui.dev/components/glass-landing-hero

## Customising
- Theme: `theme="system"` (default) follows a `.dark` / `.light` class or `data-theme` on an ancestor, else the OS. `"dark"` / `"light"` pin one.
- Motion: `motion="full"` (default) | `"subtle"` | `"off"`. prefers-reduced-motion is always respected.
- Colours: the family variables (`--glass-accent`, `--glass-good`, `--glass-series-1` …) on the component or any ancestor; the neutral glass is `--lg-*` in `lib/beautiful-ui/glass.tsx`.
- Update later by re-running the install with `--overwrite` (review the diff if you edited it). Changelog: https://beautiful-ui.dev/r/changelog.json

## Keyboard

| Keys | Action |
|---|---|
| Tab | Nav links, Sign in, Start free (or the menu button), the announcement, the buttons, then the period tabs |
| ← / → (period tabs) | Previous / next period (wraps); Home / End jump to the first and last |
| ← / → (chart focused) | Move the playhead one point; Home / End jump to the first and last; Escape returns to the latest |
| Enter / Space (menu button) | Open or close the phone menu; focus moves to its first link |
| Escape (menu open) | Close the menu and return focus to the button |

## Motion inventory

| Interaction | What moves |
|---|---|
| Sound | tap on the CTAs and links, open / close on the phone menu (close on Escape too), select (pitched by position) on the chart's period tabs, a very quiet tick on arrow moves through the chart, open on the video play button. Nothing on hover or mount. Needs a GlassSoundProvider; sound={false} silences this instance; motion settings do not affect sound |
| Page load | When a quarter of the hero is on screen the pill, headline, sub-line, buttons and visual rise in one after another: 14px up (28px for the visual), .8s fade, 1s cubic-bezier(.22,1,.36,1), 90ms stagger |
| Chart trace | 520ms after the entrance starts the line draws left to right over 1.7s (ease-out cubic), led by a vertical light beam and a glowing dot; the number shows the value at the dot without rolling, so it counts up with the line. After the trace the digits roll (.7s, 25ms stagger) |
| Hover | The dot snaps to the nearest point and the beam follows; the number, change (vs the same week last year) and "Week of …" update, and return to This week on leave |
| Idle and live | The latest dot pulses (sine, 420ms). New data (a new series, or a demo tick every 3.6s) shifts the line in, rolls the number and blinks the green dot |
| Period switch | The line (always 72 points) and its y range ease to the new period (k .085 per frame); the thumb glides in .6s |
| Nav | The glass lens glides between links (.6s cubic-bezier(.22,1,.36,1)), appearing in place on first hover; the phone menu drops 8px in and the burger turns into × |
| motion="subtle" / "off" / reduced motion | Subtle: calm curve, 30% shorter, no stagger, no trace or pulse, faster morphs. Off: instant. Reduced motion: 150ms opacity fades, no trace, instant morphs, a still dot |

## Accessibility contract (preserve when editing)
- One <h1>, labelling the hero section; the nav is a labelled landmark
- The chart is role=img with a summary ("Weekly active teams over 12M, now 8,940."), and a visually hidden table lists every point of the shown period
- The number has its full value and unit as text for screen readers; the rolling digits are hidden from them
- Period tabs are role=tab with aria-selected, ids and roving focus; the chart is their tabpanel
- The chart panel is focusable: arrow keys move the playhead point by point and the value and week are announced politely
- The phone menu button has aria-expanded and aria-controls; opening it moves focus to the first link; Escape closes it and returns focus to the button; a click outside closes it
- Every button and link is at least 40px tall (the 30px period tabs and the 32px announcement pill have larger hit areas)
- Visible focus rings; secondary text meets 4.5:1 and the muted headline half 3:1 (large text) in dark and light; the change is never colour alone (sign and words)

## Install

```bash
npx shadcn@latest add @beautiful-ui-pro/glass-landing-hero
```

Pro item: needs the `@beautiful-ui-pro` registry in `components.json` and `BEAUTIFUL_UI_TOKEN` in `.env.local` (https://beautiful-ui.dev/account). Setup: https://beautiful-ui.dev/docs/pro

## Credits
- Sound by @web-kits/audio (https://www.npmjs.com/package/@web-kits/audio)
