{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hero-cosmic",
  "type": "registry:ui",
  "title": "Hero Cosmic",
  "description": "The cosmic landing hero: a full-height gradient surface carrying a twinkling starfield, shooting stars and meteors behind an eyebrow / headline / tagline / two-CTA column. A preset, not a kit — pass copy and CTAs and the decorative layer is already wired.",
  "author": "ofri-peretz <https://github.com/ofri-peretz>",
  "categories": [
    "marketing",
    "pattern"
  ],
  "dependencies": [],
  "registryDependencies": [
    "https://ds.interlace.tools/r/theme.json",
    "https://ds.interlace.tools/r/cn.json",
    "https://ds.interlace.tools/r/container.json",
    "https://ds.interlace.tools/r/stars-background.json"
  ],
  "files": [
    {
      "path": "registry/interlace-ui/patterns/hero-cosmic.tsx",
      "target": "components/ui/patterns/hero-cosmic.tsx",
      "type": "registry:ui",
      "content": "'use client';\n\nimport * as React from 'react';\n\n// @interlace/hero-cosmic v1.4.0 — Interlace design system.\n// Docs, props and live preview: https://ds.interlace.tools/c/hero-cosmic\n// What changed since: https://ds.interlace.tools/c/hero-cosmic#history\n// Generated banner — keep it, the upgrade diff reads this version.\n\n/**\n * @interlace/ui — HeroCosmic\n *\n * The cosmic landing hero: a full-height gradient surface carrying a twinkling\n * starfield, shooting stars and meteors behind an eyebrow / headline / tagline\n * / two-CTA column. A preset, not a kit — pass copy and CTAs and the decorative\n * layer is already wired.\n *\n * ## Anatomy\n *\n *   HeroCosmic                       (div — data-slot=\"hero-cosmic\", min-viewport 320)\n *     └─ div                         (min-h-screen gradient, text-hero-foreground)\n *         ├─ div aria-hidden         (data-slot=\"hero-cosmic-effects\")\n *         │   ├─ StarsBackground     (canvas)\n *         │   ├─ ShootingStars       (svg)\n *         │   └─ Meteors             (CSS-animated spans)\n *         └─ Container size=content  (data-slot=\"hero-cosmic-body\")\n *             ├─ eyebrow / h1 / tagline\n *             ├─ actions             (primaryCta + secondaryCta)\n *             └─ footer\n *\n * ## The effect layer can legitimately never appear\n *\n * `useEffectColors` reads `--hero-star`, `--hero-trail` and `--hero-meteor`\n * off `document.documentElement` on mount and returns `null` until all three\n * resolve non-empty. While it is `null` no decorative layer renders at all —\n * which also means a consumer who imports this component but not\n * `styles/interlace-theme.css` gets the gradient and the copy and no stars,\n * silently and permanently. That is deliberate: the components concatenate an\n * alpha suffix onto the colour (`${meteorColor}80`) and paint to `<canvas>`,\n * neither of which can consume a `var()` reference, and painting an arbitrary\n * fallback colour over someone's brand is the worse failure.\n *\n * ## Motion\n *\n * This file contains no `useReducedMotion` call — the contract is delegated to\n * the three children in `aceternity/stars-background.tsx`, each of which reads\n * the preference itself. Under `prefers-reduced-motion: reduce` the starfield\n * paints one static frame (no twinkle loop) and `ShootingStars` and `Meteors`\n * both `return null`. So a reduced-motion reader sees a still starfield on the\n * gradient and nothing moving. All three are JS-gated; the CSS reset in\n * `styles/preflight.css` reaches only the meteor keyframe.\n *\n * ## MIN_VIEWPORT — 320\n *\n * The copy column is a `<Container size=\"content\">` on a mobile-first ladder\n * (`py-lg md:py-xl lg:py-2xl`, `text-4xl` → `lg:text-7xl`) and the CTAs stack\n * vertically below `sm`, so the hero reads on the narrowest phone.\n *\n * | Rule | Concept                     | Where in this file                                     |\n * | ---- | --------------------------- | ------------------------------------------------------ |\n * | R4   | Extends native el           | `Omit<React.ComponentProps<'div'>, 'children'\\|'title'>` |\n * | R5   | testid required, no default | `'data-testid': string` + derived part ids             |\n * | R6   | data-slot on every part     | `hero-cosmic` / `-effects` / `-headline` / `-cta` / …  |\n * | R7   | className + rest + ref      | `cn(...)`, `{...props}`, `ref` on the root             |\n * | R10  | Composition seam            | `HeroCosmicCTA.render` clones any element as the button |\n * | R19  | Tokens only                 | effect colours resolve from `--hero-*` — no hex here   |\n * | R21  | Layout primitive            | `<Container size=\"content\">`, not open-coded `mx-auto` |\n * | R23  | CLS=0                       | effects are `absolute` + `aria-hidden`; copy never moves |\n * | R25  | Client component            | canvas + `getComputedStyle` need the DOM               |\n */\n\nimport { cn } from '@/lib/utils';\nimport { Container } from '@/components/ui/container';\nimport {\n  StarsBackground,\n  ShootingStars,\n  Meteors,\n} from '@/components/ui/aceternity/stars-background';\n\n/** Smallest viewport this pattern is laid out for. */\nexport const MIN_VIEWPORT = 320 as const;\n\n/**\n * Brand tokens the decorative layer paints with, in `--custom-property`\n * form. See `styles/interlace-theme.css` (`@layer interlace.brand`).\n *\n * These are read at runtime instead of passed as `var(--hero-star)`\n * because the vendored effect components concatenate an alpha suffix onto\n * the value (`${meteorColor}80`) before handing it to `<canvas>` /\n * `linear-gradient()`. A `var()` reference can't carry that suffix, and\n * canvas ignores the cascade entirely — so the component resolves the\n * computed value once and passes concrete colours down. Keeping the hex\n * exclusively in CSS is what lets a consumer fork the hero's palette\n * without patching this file (R19).\n */\nconst EFFECT_TOKENS = ['--hero-star', '--hero-trail', '--hero-meteor'] as const;\n\ntype EffectColors = { star: string; trail: string; meteor: string };\n\n/**\n * Resolve the three effect tokens against the mounted DOM.\n *\n * Returns `null` until resolution completes. Callers render no decorative\n * layer while it is null — that is deliberate rather than a flash-guard\n * hack: the effects are `<canvas>` + animation that cannot paint during\n * SSR anyway, so gating them on the tokens costs nothing visually and\n * removes any need for a hard-coded fallback colour in this file.\n */\nfunction useEffectColors(): EffectColors | null {\n  const [colors, setColors] = React.useState<EffectColors | null>(null);\n\n  React.useEffect(() => {\n    const computed = getComputedStyle(document.documentElement);\n    const [star, trail, meteor] = EFFECT_TOKENS.map((token) =>\n      computed.getPropertyValue(token).trim(),\n    );\n    // A consumer that imported the components but not the stylesheet gets\n    // empty strings. Skip the decorative layer rather than painting an\n    // arbitrary colour on top of their brand.\n    if (star && trail && meteor) setColors({ star, trail, meteor });\n  }, []);\n\n  return colors;\n}\n\nexport interface HeroCosmicCTA {\n  label: React.ReactNode;\n  href: string;\n  /** Render any element as the button (e.g. `<Link>`, `<ShimmerButton>`). Falls back to a plain anchor. */\n  render?: React.ReactElement<Record<string, unknown>>;\n}\n\n/** Tuning knobs for the decorative starfield layer. */\nexport interface HeroCosmicEffects {\n  /** Stars per square pixel. @default 0.0002 */\n  starDensity?: number;\n  /** Share of stars that twinkle, 0–1. @default 0.8 */\n  twinkleProbability?: number;\n  /** Slowest twinkle cycle, seconds. @default 0.4 */\n  minTwinkleSpeed?: number;\n  /** Fastest twinkle cycle, seconds. @default 1.2 */\n  maxTwinkleSpeed?: number;\n  /** Slowest shooting-star travel speed. @default 10 */\n  shootingMinSpeed?: number;\n  /** Fastest shooting-star travel speed. @default 35 */\n  shootingMaxSpeed?: number;\n  /** Shortest gap between shooting stars, ms. @default 600 */\n  shootingMinDelay?: number;\n  /** Longest gap between shooting stars, ms. @default 2500 */\n  shootingMaxDelay?: number;\n  /** Meteors on screen at once. @default 3 */\n  meteorCount?: number;\n  /** Shortest meteor traversal, seconds. @default 12 */\n  meteorMinDuration?: number;\n  /** Longest meteor traversal, seconds. @default 30 */\n  meteorMaxDuration?: number;\n}\n\nexport interface HeroCosmicProps\n  extends Omit<React.ComponentProps<'div'>, 'children' | 'title'> {\n  /** Eyebrow content rendered above the headline (e.g. a trust chip). */\n  eyebrow?: React.ReactNode;\n  /** Main headline. Pass JSX (`<>foo<br/>bar</>`) for multi-line headlines with gradient spans. */\n  headline: React.ReactNode;\n  /** Sub-headline / tagline. */\n  tagline?: React.ReactNode;\n  /** Primary CTA. */\n  primaryCta?: HeroCosmicCTA;\n  /** Secondary CTA. */\n  secondaryCta?: HeroCosmicCTA;\n  /** Additional content rendered below CTAs (e.g. trust badges). */\n  footer?: React.ReactNode;\n  /**\n   * Tuning knobs for the decorative starfield. Colours are NOT part of this\n   * bag — they come from the `--hero-*` brand tokens so the effect can't\n   * drift from the palette. @default {}\n   */\n  effects?: HeroCosmicEffects;\n  /**\n   * Stable selector hook for E2E tests. Sub-parts derive from it\n   * (`{value}-headline`, `{value}-effects`). Required — no default (R5).\n   */\n  'data-testid': string;\n}\n\nfunction renderCta(cta: HeroCosmicCTA | undefined) {\n  if (!cta) return null;\n  if (cta.render) {\n    return React.cloneElement(cta.render, { href: cta.href }, cta.label);\n  }\n  return (\n    <a\n      href={cta.href}\n      data-slot=\"hero-cosmic-cta\"\n      className=\"inline-flex items-center gap-2 rounded-lg border-2 border-hero-foreground/20 bg-hero-foreground/10 px-md py-sm font-semibold text-hero-foreground backdrop-blur-sm transition-all hover:border-hero-foreground/30 hover:bg-hero-foreground/20\"\n    >\n      {cta.label}\n    </a>\n  );\n}\n\nconst EFFECT_DEFAULTS: Required<HeroCosmicEffects> = {\n  starDensity: 0.0002,\n  twinkleProbability: 0.8,\n  minTwinkleSpeed: 0.4,\n  maxTwinkleSpeed: 1.2,\n  shootingMinSpeed: 10,\n  shootingMaxSpeed: 35,\n  shootingMinDelay: 600,\n  shootingMaxDelay: 2500,\n  meteorCount: 3,\n  meteorMinDuration: 12,\n  meteorMaxDuration: 30,\n};\n\n/**\n * Cosmic landing-hero preset — see the file header for the effect layer, the\n * token-resolution gate, and the reduced-motion behaviour.\n */\nexport const HeroCosmic = React.forwardRef<HTMLDivElement, HeroCosmicProps>(\n  function HeroCosmic(\n    {\n      eyebrow,\n      headline,\n      tagline,\n      primaryCta,\n      secondaryCta,\n      footer,\n      className,\n      effects,\n      'data-testid': testId,\n      ...props\n    },\n    ref,\n  ) {\n    const e = { ...EFFECT_DEFAULTS, ...effects };\n    const colors = useEffectColors();\n\n    return (\n      <div\n        ref={ref}\n        data-slot=\"hero-cosmic\"\n        data-min-viewport={String(MIN_VIEWPORT)}\n        data-testid={testId}\n        className={cn('relative', className)}\n        // Dynamic-only inline style (R18 exception): `contain` and\n        // `clip-path: inset(0)` have no Tailwind utility, and both are\n        // load-bearing — they stop the absolutely-positioned canvas layers\n        // from painting outside the hero and forcing a page-wide repaint.\n        style={{ contain: 'paint', clipPath: 'inset(0)' }}\n        {...props}\n      >\n        {/* The hero owns its surface in BOTH colour schemes — see the\n            `--hero-*` token block in interlace-theme.css for why these\n            don't invert. `text-hero-foreground` here is what every nested\n            slot inherits, so the copy stays 16.4:1 in light mode too. */}\n        <div className=\"relative flex min-h-screen items-center justify-center bg-gradient-to-b from-primary/20 via-hero-surface to-hero-surface-deep text-hero-foreground\">\n          {colors ? (\n            <div\n              aria-hidden\n              data-slot=\"hero-cosmic-effects\"\n              data-testid={`${testId}-effects`}\n              className=\"pointer-events-none absolute inset-0\"\n            >\n              <StarsBackground\n                starDensity={e.starDensity}\n                allStarsTwinkle\n                twinkleProbability={e.twinkleProbability}\n                minTwinkleSpeed={e.minTwinkleSpeed}\n                maxTwinkleSpeed={e.maxTwinkleSpeed}\n              />\n              <ShootingStars\n                minSpeed={e.shootingMinSpeed}\n                maxSpeed={e.shootingMaxSpeed}\n                minDelay={e.shootingMinDelay}\n                maxDelay={e.shootingMaxDelay}\n                starColor={colors.star}\n                trailColor={colors.trail}\n                starWidth={10}\n                starHeight={1}\n              />\n              <Meteors\n                number={e.meteorCount}\n                meteorColor={colors.meteor}\n                minDuration={e.meteorMinDuration}\n                maxDuration={e.meteorMaxDuration}\n              />\n            </div>\n          ) : null}\n\n          <Container\n            size=\"content\"\n            data-slot=\"hero-cosmic-body\"\n            className=\"relative z-10 py-lg text-center md:py-xl lg:py-2xl\"\n          >\n            {eyebrow ? (\n              <div\n                data-slot=\"hero-cosmic-eyebrow\"\n                className=\"mb-md inline-flex\"\n              >\n                {eyebrow}\n              </div>\n            ) : null}\n\n            <h1\n              data-slot=\"hero-cosmic-headline\"\n              data-testid={`${testId}-headline`}\n              className=\"mb-sm text-4xl font-extrabold tracking-tight sm:text-5xl md:text-6xl lg:text-7xl\"\n            >\n              {headline}\n            </h1>\n\n            {tagline ? (\n              <p\n                data-slot=\"hero-cosmic-tagline\"\n                className=\"mx-auto mb-xl max-w-prose text-base leading-relaxed text-hero-foreground/90 drop-shadow sm:text-lg md:text-xl\"\n              >\n                {tagline}\n              </p>\n            ) : null}\n\n            {(primaryCta || secondaryCta) && (\n              <div\n                data-slot=\"hero-cosmic-actions\"\n                className=\"flex flex-col items-center justify-center gap-sm sm:flex-row\"\n              >\n                {renderCta(primaryCta)}\n                {renderCta(secondaryCta)}\n              </div>\n            )}\n\n            {footer ? (\n              <div data-slot=\"hero-cosmic-footer\" className=\"mt-md\">\n                {footer}\n              </div>\n            ) : null}\n          </Container>\n        </div>\n      </div>\n    );\n  },\n);\n"
    }
  ],
  "meta": {
    "tier": "pattern",
    "client": true,
    "minViewport": 320,
    "loading": false,
    "version": "1.4.0",
    "since": "1.0.0"
  },
  "docs": "## @interlace/hero-cosmic\n\nInstalled to `components/ui/patterns/hero-cosmic.tsx`.\n\n```tsx\nimport { /* … */ } from '@/components/ui/patterns/hero-cosmic';\n```\n\nProps, a11y contract, live preview and source: https://ds.interlace.tools/c/hero-cosmic\n\nRequires the `@interlace/theme` CSS baseline (installed automatically as a registry dependency)."
}
