{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hero-type",
  "type": "registry:block",
  "title": "Kinetic type hero",
  "description": "Oversized display type is the visual: a spring-swapped kinetic word in a fixed slot and inline product pills, with a pause control.",
  "author": "StoreKit <hello@storekit.dev>",
  "categories": [
    "hero",
    "any"
  ],
  "dependencies": [
    "motion",
    "lucide-react"
  ],
  "registryDependencies": [
    "https://storekit-eight.vercel.app/r/cn.json",
    "https://storekit-eight.vercel.app/r/sk-analytics.json"
  ],
  "files": [
    {
      "path": "hero/hero-type.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useState } from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\nimport { Pause, Play } from \"lucide-react\"\nimport { cn } from \"@/components/storekit/lib/cn\"\nimport { useSkBlock } from \"@/components/storekit/lib/sk-analytics\"\n\n/* Typography-first hero. Oversized display type is the visual. Line one\n   ends in a kinetic word that swaps on a spring every few seconds; line two\n   carries small inline product pills set into the sentence. The rotating\n   word sits in a fixed-width slot sized to the longest word, so nothing\n   reflows. Screen readers get the whole sentence once, the animated copy is\n   hidden from them, there is a pause control, and with reduced motion the\n   first word simply stays. */\n\nconst EASE = [0.23, 1, 0.32, 1] as const\n\ntype Cta = { label: string; href: string }\n\nconst PRIMARY =\n  \"sk-btn inline-flex min-h-12 items-center justify-center py-2.5 text-center leading-tight rounded-sk-btn bg-sk-accent px-7 text-[15px] font-medium text-sk-accent-ink outline-none transition-[filter,transform] duration-200 ease-sk-out hover:brightness-[1.06] focus-visible:ring-2 focus-visible:ring-sk-accent focus-visible:ring-offset-2 focus-visible:ring-offset-sk-paper active:scale-[0.97]\"\nconst SECONDARY =\n  \"sk-btn inline-flex min-h-12 items-center justify-center py-2.5 text-center leading-tight rounded-sk-btn border border-sk-line px-7 text-[15px] font-medium text-sk-ink outline-none transition-[background-color,transform] duration-200 ease-sk-out hover:bg-sk-ink/[0.04] focus-visible:ring-2 focus-visible:ring-sk-accent focus-visible:ring-offset-2 focus-visible:ring-offset-sk-paper active:scale-[0.97]\"\n\nexport function HeroType({\n  lead = \"Feel\",\n  words = [\"sharper\", \"calmer\", \"lighter\", \"steadier\"],\n  tail = \"by breakfast.\",\n  pills = [],\n  interval = 2600,\n  subcopy = \"Six daily essentials. Clean ingredients, third-party tested, made to taste good.\",\n  primaryCta = { label: \"Shop the range\", href: \"#\" },\n  secondaryCta = { label: \"Take the quiz\", href: \"#\" },\n  className,\n}: {\n  lead?: string\n  /** The kinetic word. One word shows statically. */\n  words?: string[]\n  tail?: string\n  /** Small inline images set into line two, before the tail. */\n  pills?: { src: string; alt: string }[]\n  /** Milliseconds per word. */\n  interval?: number\n  subcopy?: string\n  primaryCta?: Cta\n  secondaryCta?: Cta\n  className?: string\n}) {\n  const prefersReduced = useReducedMotion()\n  // Server HTML assumes motion; read the preference only after hydration so markup matches.\n  const [hydrated, setHydrated] = useState(false)\n  useEffect(() => setHydrated(true), [])\n  const reduce = hydrated && !!prefersReduced\n  const sk = useSkBlock(\"hero\", \"hero-type\")\n  const [i, setI] = useState(0)\n  const [paused, setPaused] = useState(false)\n  const kinetic = words.length > 1 && !reduce\n\n  useEffect(() => {\n    if (!kinetic || paused) return\n    const id = setInterval(() => setI((n) => (n + 1) % words.length), interval)\n    return () => clearInterval(id)\n  }, [kinetic, paused, interval, words.length])\n\n  const sentence = `${lead} ${words.length > 1 ? words.join(\", \") : words[0]} ${tail}`\n  const longest = words.reduce((a, b) => (b.length > a.length ? b : a), \"\")\n\n  return (\n    <section ref={sk.ref} {...sk.attrs} className={cn(\"overflow-hidden bg-sk-paper text-sk-ink\", className)}>\n      <div className=\"mx-auto w-full max-w-6xl px-4 pb-12 pt-12 sm:px-8 sm:pb-16 sm:pt-16 lg:pb-20 lg:pt-20\">\n        <h1 className=\"font-sk-display font-semibold leading-[0.95] tracking-[-0.045em]\" style={{ fontSize: \"clamp(3rem, 10.5vw, 9rem)\" }}>\n          <span className=\"sr-only\">{sentence}</span>\n          <span aria-hidden className=\"block\">\n            <motion.span initial={{ opacity: 0, y: reduce ? 0 : 24 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.7, ease: EASE }} className=\"inline-block\">\n              {lead}\n            </motion.span>{\" \"}\n            <span className=\"relative inline-grid overflow-hidden whitespace-nowrap pb-[0.08em] align-bottom text-sk-accent\">\n              <span className=\"invisible col-start-1 row-start-1\">{longest}</span>\n              <AnimatePresence initial={false} mode=\"popLayout\">\n                <motion.span\n                  key={words[i]}\n                  initial={{ y: \"100%\", opacity: 0, filter: \"blur(4px)\" }}\n                  animate={{ y: 0, opacity: 1, filter: \"blur(0px)\" }}\n                  exit={{ y: \"-90%\", opacity: 0, filter: \"blur(4px)\" }}\n                  transition={{ type: \"spring\", stiffness: 210, damping: 26, mass: 0.9 }}\n                  className=\"col-start-1 row-start-1\"\n                >\n                  {words[i]}\n                </motion.span>\n              </AnimatePresence>\n            </span>\n          </span>\n          <motion.span\n            aria-hidden\n            initial={{ opacity: 0, y: reduce ? 0 : 24 }}\n            animate={{ opacity: 1, y: 0 }}\n            transition={{ duration: 0.7, ease: EASE, delay: reduce ? 0 : 0.08 }}\n            className=\"mt-[0.06em] flex flex-wrap items-center gap-x-[0.22em]\"\n          >\n            {pills.length ? (\n              <span className=\"inline-flex items-center\">\n                {pills.slice(0, 3).map((p, n) => (\n                  <motion.img\n                    key={p.src + n}\n                    src={p.src}\n                    alt=\"\"\n                    initial={{ scale: reduce ? 1 : 0.6, opacity: 0 }}\n                    animate={{ scale: 1, opacity: 1 }}\n                    transition={{ type: \"spring\", stiffness: 300, damping: 22, delay: reduce ? 0 : 0.25 + n * 0.07 }}\n                    whileHover={reduce ? undefined : { y: -6, rotate: n % 2 ? 4 : -4 }}\n                    className={cn(\"h-[0.78em] w-[0.78em] rounded-full border-[3px] border-sk-paper bg-sk-surface object-cover shadow-[0_10px_30px_-12px_rgb(var(--sk-ink)/0.45)]\", n > 0 && \"-ml-[0.2em]\")}\n                  />\n                ))}\n              </span>\n            ) : null}\n            <span>{tail}</span>\n          </motion.span>\n        </h1>\n\n        <motion.div\n          initial={{ opacity: 0, y: reduce ? 0 : 8 }}\n          animate={{ opacity: 1, y: 0 }}\n          transition={{ duration: 0.5, ease: EASE, delay: reduce ? 0 : 0.2 }}\n          className=\"mt-10 flex flex-col gap-6 border-t border-sk-line pt-6 md:flex-row md:items-center md:justify-between\"\n        >\n          <p className=\"max-w-md text-[15px] leading-[1.55] text-sk-muted\">{subcopy}</p>\n          <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n            <a href={primaryCta.href} onClick={() => sk.emit(\"interact\", \"cta\")} className={PRIMARY}>\n              {primaryCta.label}\n            </a>\n            <a href={secondaryCta.href} onClick={() => sk.emit(\"interact\", \"cta_secondary\")} className={SECONDARY}>\n              {secondaryCta.label}\n            </a>\n            {kinetic ? (\n              <button\n                type=\"button\"\n                onClick={() => setPaused((p) => !p)}\n                aria-pressed={paused}\n                aria-label={paused ? \"Play headline animation\" : \"Pause headline animation\"}\n                className=\"grid h-11 w-11 shrink-0 place-items-center self-start rounded-full sm:self-center text-sk-muted outline-none transition-colors hover:bg-sk-ink/[0.05] hover:text-sk-ink focus-visible:ring-2 focus-visible:ring-sk-accent\"\n              >\n                {paused ? <Play size={15} aria-hidden /> : <Pause size={15} aria-hidden />}\n              </button>\n            ) : null}\n          </div>\n        </motion.div>\n      </div>\n    </section>\n  )\n}\n",
      "type": "registry:block",
      "target": "components/storekit/hero/hero-type.tsx"
    }
  ],
  "docs": "One entry in `words` makes it static. The full sentence is read once by screen readers.",
  "meta": {
    "section": "hero",
    "variant": "type",
    "niche": [
      "any"
    ],
    "commerce": [
      "cta"
    ],
    "previewHeight": 560
  }
}