{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "arc-reveal-hero",
  "type": "registry:ui",
  "title": "Arc Reveal Hero",
  "description": "A multilingual greeting cycles on a calm white surface, then a curved black curtain rises from below to reveal the landing — the boundary stays a single smooth arc the whole way up, never a flat slide.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/ruixenui/arc-reveal-hero.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  animate,\n  AnimatePresence,\n  motion,\n  useMotionValue,\n  useReducedMotion,\n  useTransform,\n} from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\n/* ── types ───────────────────────────────────────────────────── */\n\nexport type ArcRevealGreeting = {\n  /** Greeting text in the target script */\n  text: string;\n  /** Optional `lang` attribute applied to the span (helps screen readers / font rendering) */\n  lang?: string;\n};\n\nexport interface ArcRevealHeroProps {\n  /** Greetings cycled before the arc reveal. */\n  greetings?: ArcRevealGreeting[];\n  /** How long each greeting is held on screen (ms). */\n  greetingHold?: number;\n  /** Duration of the curved curtain reveal (ms). */\n  revealDuration?: number;\n  /** Outer `<section>` class. Receives the *post-reveal* surface. */\n  className?: string;\n  /** Class for the intro (pre-reveal) overlay surface. */\n  introClassName?: string;\n  /** Class for the cycled greeting `<span>`. */\n  greetingClassName?: string;\n  /** Class for the wrapper around `children` (the revealed content). */\n  revealClassName?: string;\n  /**\n   * Optional `sessionStorage` key — when set, the intro plays only once per\n   * session for the same key. Leave unset to replay on every mount.\n   */\n  storageKey?: string;\n  /** Content shown after the curtain reveal (the \"landing\"). */\n  children?: React.ReactNode;\n}\n\n/* ── defaults ────────────────────────────────────────────────── */\n\nconst DEFAULT_GREETINGS: ArcRevealGreeting[] = [\n  { text: \"Quiet.\" },\n  { text: \"Sharp.\" },\n  { text: \"Calm.\" },\n  { text: \"Crafted.\" },\n  { text: \"Considered.\" },\n  { text: \"Composed.\" },\n  { text: \"Honest.\" },\n  { text: \"Ready.\" },\n];\n\ntype Phase = \"intro\" | \"reveal\" | \"done\";\n\n/* ── component ───────────────────────────────────────────────── */\n\nexport function ArcRevealHero({\n  greetings = DEFAULT_GREETINGS,\n  greetingHold = 620,\n  revealDuration = 1500,\n  className,\n  introClassName,\n  greetingClassName,\n  revealClassName,\n  storageKey,\n  children,\n}: ArcRevealHeroProps) {\n  const prefersReducedMotion = useReducedMotion();\n\n  const [phase, setPhase] = React.useState<Phase>(\"intro\");\n  const [index, setIndex] = React.useState(0);\n\n  // Drive the arc shape from a single 0→1 progress.\n  // The curve is a quadratic bezier with a fixed concavity (control point\n  // sits 25 viewBox units below the chord), translated upward over time:\n  //   t=0 → chord at y=110 (off-screen below)  → no curtain visible\n  //   t=1 → chord at y=-30 (off-screen above)  → full-screen curtain\n  const progress = useMotionValue(0);\n  const arcPath = useTransform(progress, (p: number) => {\n    const edge = 110 - p * 140;\n    const control = edge + 25;\n    return `M 0 ${edge} Q 50 ${control} 100 ${edge} L 100 110 L 0 110 Z`;\n  });\n\n  // Honor reduced-motion + replay-suppression on mount.\n  React.useEffect(() => {\n    if (prefersReducedMotion) {\n      setPhase(\"done\");\n      return;\n    }\n    if (storageKey && typeof window !== \"undefined\") {\n      try {\n        if (window.sessionStorage.getItem(storageKey) === \"done\") {\n          setPhase(\"done\");\n        }\n      } catch {\n        /* sessionStorage can throw in private mode — fall through */\n      }\n    }\n  }, [prefersReducedMotion, storageKey]);\n\n  // Greeting cycle.\n  React.useEffect(() => {\n    if (phase !== \"intro\") return;\n    const isLast = index >= greetings.length - 1;\n    if (isLast) {\n      const t = window.setTimeout(() => setPhase(\"reveal\"), greetingHold + 220);\n      return () => window.clearTimeout(t);\n    }\n    const t = window.setTimeout(() => setIndex((i) => i + 1), greetingHold);\n    return () => window.clearTimeout(t);\n  }, [phase, index, greetingHold, greetings.length]);\n\n  // Drive the curtain reveal.\n  React.useEffect(() => {\n    if (phase !== \"reveal\") return;\n    const controls = animate(progress, 1, {\n      duration: revealDuration / 1000,\n      ease: [0.85, 0, 0.15, 1],\n      onComplete: () => {\n        if (storageKey && typeof window !== \"undefined\") {\n          try {\n            window.sessionStorage.setItem(storageKey, \"done\");\n          } catch {\n            /* ignore */\n          }\n        }\n        setPhase(\"done\");\n      },\n    });\n    return () => controls.stop();\n  }, [phase, progress, revealDuration, storageKey]);\n\n  const showOverlay = phase !== \"done\";\n  const current = greetings[Math.min(index, greetings.length - 1)];\n\n  return (\n    <section\n      aria-label=\"Hero\"\n      className={cn(\n        \"relative isolate min-h-screen w-full overflow-hidden bg-background text-foreground\",\n        className,\n      )}\n    >\n      <div className={cn(\"relative z-0\", revealClassName)}>{children}</div>\n\n      <AnimatePresence>\n        {showOverlay && (\n          <motion.div\n            key=\"arc-reveal-overlay\"\n            initial={{ opacity: 1 }}\n            exit={{ opacity: 0 }}\n            transition={{ duration: 0.18, ease: [0.4, 0, 0.2, 1] }}\n            className={cn(\n              \"absolute inset-x-0 top-0 z-30 h-screen overflow-hidden bg-foreground\",\n              introClassName,\n            )}\n          >\n            {/* Cycled greeting */}\n            <div className=\"absolute inset-0 flex items-center justify-center\">\n              <AnimatePresence mode=\"wait\">\n                {phase === \"intro\" && current && (\n                  <motion.span\n                    key={`${index}-${current.text}`}\n                    lang={current.lang}\n                    initial={{ opacity: 0, y: 8 }}\n                    animate={{ opacity: 1, y: 0 }}\n                    exit={{ opacity: 0, y: -8 }}\n                    transition={{ duration: 0.42, ease: [0.22, 1, 0.36, 1] }}\n                    className={cn(\n                      \"select-none px-6 text-center text-5xl font-semibold tracking-tight text-background sm:text-6xl md:text-7xl\",\n                      greetingClassName,\n                    )}\n                  >\n                    {current.text}\n                  </motion.span>\n                )}\n              </AnimatePresence>\n            </div>\n\n            {/* Rising curved curtain */}\n            <svg\n              className=\"pointer-events-none absolute inset-0 h-full w-full\"\n              viewBox=\"0 0 100 100\"\n              preserveAspectRatio=\"none\"\n              aria-hidden\n            >\n              <motion.path d={arcPath} style={{ fill: \"var(--background)\" }} />\n            </svg>\n          </motion.div>\n        )}\n      </AnimatePresence>\n    </section>\n  );\n}\n\nexport default ArcRevealHero;\n",
      "type": "registry:ui",
      "target": "components/ruixen/arc-reveal-hero.tsx"
    }
  ]
}