+ "content": "\"use client\";\n\nimport { useId, type ComponentProps, type ReactNode } from \"react\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\nconst DEFAULT_FOIL_PALETTE = [\n \"#ff9bd1\",\n \"#ffd66b\",\n \"#9bffd6\",\n \"#6bb5ff\",\n \"#c89bff\",\n \"#ff9bd1\",\n] as const;\n\ntype IridescentSweepBackgroundProps = {\n className?: string;\n /** Conic-gradient rotation cycle in seconds. */\n speed?: number;\n /** Hue offset in degrees — shifts the whole foil. */\n hue?: number;\n /** Grain intensity 0–1. */\n grain?: number;\n /** Section / vignette base (e.g. `#0e0a14`). */\n baseColor?: string;\n /** Holographic foil stops for the rotating conic gradient. */\n palette?: ReadonlyArray<string>;\n};\n\nexport function IridescentSweepBackground({\n className,\n speed = 22,\n hue = 0,\n grain = 0.35,\n baseColor = \"#0e0a14\",\n palette = DEFAULT_FOIL_PALETTE,\n}: IridescentSweepBackgroundProps) {\n const reduced = useReducedMotion();\n // Clamp speed defensively — negative or non-finite values would yield invalid durations.\n const safeSpeed = Number.isFinite(speed) && speed > 0 ? speed : 0;\n const cycle = reduced ? 0 : safeSpeed;\n const filterId = useId();\n // Default-only fires on undefined; treat an explicit empty array as \"no\n // palette\" so `palette.join(\", \")` can't produce an invalid `conic-gradient(...)`.\n const resolvedPalette =\n palette && palette.length > 0 ? palette : DEFAULT_FOIL_PALETTE;\n\n return (\n <div\n aria-hidden\n className={cn(\"absolute inset-0 overflow-hidden\", className)}\n style={{ backgroundColor: baseColor, filter: `hue-rotate(${hue}deg)` }}\n >\n <svg className=\"absolute -z-10 h-0 w-0\" aria-hidden>\n <filter id={filterId}>\n <feTurbulence type=\"fractalNoise\" baseFrequency=\"0.85\" numOctaves=\"2\" stitchTiles=\"stitch\" />\n <feColorMatrix type=\"matrix\" values=\"0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.7 0\" />\n </filter>\n </svg>\n\n <motion.div\n className=\"absolute inset-[-20%]\"\n style={{\n background: `conic-gradient(from 0deg at 50% 50%, ${resolvedPalette.join(\", \")})`,\n filter: \"blur(60px) saturate(1.4)\",\n mixBlendMode: \"screen\",\n opacity: 0.85,\n }}\n initial={false}\n animate={cycle ? { rotate: 360 } : { rotate: 0 }}\n transition={cycle ? { duration: cycle, ease: \"linear\", repeat: Infinity } : undefined}\n />\n <motion.div\n className=\"absolute inset-[-10%]\"\n style={{\n background:\n \"linear-gradient(120deg, transparent 30%, rgba(255,255,255,0.35) 45%, transparent 60%)\",\n mixBlendMode: \"overlay\",\n }}\n initial={{ x: \"-60%\" }}\n animate={cycle ? { x: \"60%\" } : { x: \"0%\" }}\n transition={cycle ? { duration: 6, ease: \"easeInOut\", repeat: Infinity, repeatDelay: 1.4 } : undefined}\n />\n <div\n className=\"absolute inset-0\"\n style={{\n background: `radial-gradient(ellipse at center, transparent 30%, ${baseColor} 92%)`,\n }}\n />\n <div\n className=\"pointer-events-none absolute inset-0 mix-blend-overlay\"\n style={{ filter: `url(#${filterId})`, opacity: grain }}\n >\n <div className=\"absolute inset-0 bg-white/30\" />\n </div>\n <div\n className=\"pointer-events-none absolute inset-0 opacity-[0.05]\"\n style={{\n backgroundImage:\n \"linear-gradient(0deg, rgba(255,255,255,0.6) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.6) 1px, transparent 1px)\",\n backgroundSize: \"44px 44px\",\n }}\n />\n </div>\n );\n}\n\ntype IridescentSweepHeroProps = Omit<ComponentProps<\"section\">, \"children\"> & {\n children?: ReactNode;\n backgroundProps?: IridescentSweepBackgroundProps;\n};\n\nexport function IridescentSweepHero({\n children,\n className,\n backgroundProps,\n ...rest\n}: IridescentSweepHeroProps) {\n return (\n <section\n {...rest}\n className={cn(\n \"relative isolate flex min-h-[100svh] w-full items-center justify-center overflow-hidden bg-[#0e0a14] text-white\",\n className,\n )}\n >\n <IridescentSweepBackground {...backgroundProps} />\n <div className=\"relative z-10 mx-auto flex max-w-3xl flex-col items-center px-6 text-center\">\n {children ?? <DefaultSweepContent />}\n </div>\n </section>\n );\n}\n\nfunction DefaultSweepContent() {\n return (\n <>\n <motion.span\n initial={{ opacity: 0, scale: 0.96 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={{ delay: 0.06, type: \"spring\", stiffness: 140, damping: 22 }}\n className=\"mb-5 inline-flex items-center gap-2 rounded-full border border-white/20 bg-white/10 px-3 py-1 text-xs uppercase tracking-[0.22em] text-white/85 backdrop-blur\"\n >\n <FoilDot />\n Edition · MMXXVI\n </motion.span>\n <FoilHeadline text=\"Pressed in light.\" />\n <motion.p\n initial={{ opacity: 0, y: 14 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ delay: 0.42, type: \"spring\", stiffness: 120, damping: 18 }}\n className=\"mt-5 max-w-xl text-pretty text-base text-white/75 sm:text-lg\"\n >\n Conic foil under a turbulence grain, with a sheen that sweeps across\n the type on a slow cadence. Editorial energy, zero plug-ins.\n </motion.p>\n <motion.div\n initial={{ opacity: 0, y: 12 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ delay: 0.54, type: \"spring\", stiffness: 120, damping: 18 }}\n className=\"mt-9 flex flex-wrap items-center justify-center gap-3\"\n >\n <button\n type=\"button\"\n className=\"group inline-flex h-11 items-center gap-2 rounded-full bg-white px-6 text-sm font-medium text-neutral-900 transition-transform duration-200 hover:-translate-y-px\"\n >\n <span>Reserve a copy</span>\n <span aria-hidden className=\"transition-transform duration-200 group-hover:translate-x-0.5\">\n →\n </span>\n </button>\n <button\n type=\"button\"\n className=\"inline-flex h-11 items-center gap-2 rounded-full border border-white/25 px-6 text-sm font-medium text-white/90 backdrop-blur transition-colors hover:bg-white/10\"\n >\n Read the editorial\n </button>\n </motion.div>\n </>\n );\n}\n\nfunction FoilDot() {\n return (\n <span\n aria-hidden\n className=\"h-1.5 w-1.5 rounded-full\"\n style={{\n background:\n \"conic-gradient(from 0deg, #ff9bd1, #ffd66b, #9bffd6, #6bb5ff, #c89bff, #ff9bd1)\",\n boxShadow: \"0 0 12px 2px rgba(255,255,255,0.4)\",\n }}\n />\n );\n}\n\nfunction FoilHeadline({ text }: { text: string }) {\n return (\n <h1 className=\"relative inline-block text-balance text-4xl font-semibold tracking-tight sm:text-6xl\">\n <span\n className=\"bg-clip-text text-transparent\"\n style={{\n backgroundImage:\n \"linear-gradient(120deg, #ffffff 0%, #f5e6ff 28%, #ffe9c2 50%, #c8f1ff 72%, #ffffff 100%)\",\n }}\n >\n {text}\n </span>\n <motion.span\n aria-hidden\n className=\"pointer-events-none absolute inset-0 bg-clip-text text-transparent\"\n style={{\n backgroundImage:\n \"linear-gradient(120deg, transparent 30%, rgba(255,255,255,0.95) 50%, transparent 70%)\",\n backgroundSize: \"200% 100%\",\n }}\n initial={{ backgroundPositionX: \"120%\" }}\n animate={{ backgroundPositionX: \"-20%\" }}\n transition={{ delay: 0.3, duration: 1.6, ease: \"easeOut\" }}\n >\n {text}\n </motion.span>\n </h1>\n );\n}\n",
0 commit comments