|
2 | 2 |
|
3 | 3 | import { AnimatePresence, motion } from "motion/react"; |
4 | 4 | import { useEffect, useLayoutEffect, useRef, useState } from "react"; |
| 5 | +import { cn } from "@/lib/utils"; |
5 | 6 |
|
6 | 7 | type HomeHeroProps = { |
7 | 8 | words?: string[]; |
@@ -51,59 +52,60 @@ export function HomeHero({ intervalMs = 2400, className }: HomeHeroProps) { |
51 | 52 | // Width updates are driven by ResizeObserver measuring the hidden mirror node |
52 | 53 |
|
53 | 54 | return ( |
54 | | - <div className={className}> |
55 | | - <h1 className="flex w-full flex-col items-center justify-center gap-y-2 text-center font-semibold text-3xl leading-none tracking-tight sm:flex-row sm:items-baseline sm:gap-y-0 sm:text-4xl md:text-5xl"> |
56 | | - <span className="whitespace-nowrap text-foreground/90"> |
57 | | - Inspect any domain’s |
58 | | - </span> |
59 | | - <motion.span |
60 | | - className="ml-2.5 inline-flex items-center rounded-lg bg-muted/70 px-2 py-0.5 text-foreground shadow-sm ring-1 ring-border/60 backdrop-blur supports-[backdrop-filter]:backdrop-blur-md sm:rounded-xl sm:px-3 sm:py-1" |
61 | | - aria-live="polite" |
62 | | - aria-atomic="true" |
63 | | - initial={false} |
64 | | - animate={{ width: measuredWidth ?? undefined }} |
65 | | - transition={{ duration: 0.85, ease: [0.22, 1, 0.36, 1] }} |
66 | | - style={{ willChange: "width", width: measuredWidth ?? undefined }} |
67 | | - > |
68 | | - <span className="relative flex h-[1.15em] w-full items-center overflow-hidden whitespace-nowrap"> |
69 | | - <span className="-translate-x-1/2 absolute left-1/2"> |
70 | | - <AnimatePresence mode="wait" initial={false}> |
71 | | - <motion.span |
72 | | - key={rotatingWords[index]} |
73 | | - initial={{ y: "100%", opacity: 0, x: 0 }} |
74 | | - animate={{ y: 0, opacity: 1, x: 0 }} |
75 | | - exit={{ y: "-100%", opacity: 0, x: 0 }} |
76 | | - transition={{ |
77 | | - type: "tween", |
78 | | - ease: [0.22, 1, 0.36, 1], |
79 | | - duration: 0.5, |
80 | | - }} |
81 | | - className="inline-block transform-gpu will-change-transform" |
82 | | - > |
83 | | - {rotatingWords[index]} |
84 | | - </motion.span> |
85 | | - </AnimatePresence> |
86 | | - </span> |
87 | | - {/* in-flow baseline shim so the pill aligns with surrounding text baseline */} |
88 | | - <span className="invisible select-none"> |
89 | | - {rotatingWords[index]} |
90 | | - </span> |
91 | | - </span> |
92 | | - </motion.span> |
93 | | - {/* measurement element for smooth width animation (inherits h1 font sizing) */} |
94 | | - <span |
95 | | - ref={measureRef} |
96 | | - className="pointer-events-none invisible absolute inline-flex items-center rounded-lg bg-muted/70 px-2 py-0.5 align-baseline text-foreground shadow-sm ring-1 ring-border/60 sm:rounded-xl sm:px-3 sm:py-1" |
97 | | - aria-hidden="true" |
98 | | - > |
99 | | - <span className="inline-flex items-center whitespace-nowrap"> |
100 | | - {rotatingWords[index]} |
| 55 | + <h1 |
| 56 | + className={cn( |
| 57 | + "flex w-full flex-col items-center justify-center gap-y-2 text-center font-semibold text-3xl leading-none tracking-tight sm:flex-row sm:items-baseline sm:gap-y-0 sm:text-4xl md:text-5xl", |
| 58 | + className, |
| 59 | + )} |
| 60 | + > |
| 61 | + <span className="whitespace-nowrap text-foreground/90"> |
| 62 | + Inspect any domain’s |
| 63 | + </span> |
| 64 | + <motion.span |
| 65 | + className="ml-2.5 inline-flex items-center rounded-lg bg-muted/70 px-2 py-0.5 text-foreground shadow-sm ring-1 ring-border/60 backdrop-blur supports-[backdrop-filter]:backdrop-blur-md sm:rounded-xl sm:px-3 sm:py-1" |
| 66 | + aria-live="polite" |
| 67 | + aria-atomic="true" |
| 68 | + initial={false} |
| 69 | + animate={{ width: measuredWidth ?? undefined }} |
| 70 | + transition={{ duration: 0.85, ease: [0.22, 1, 0.36, 1] }} |
| 71 | + style={{ willChange: "width", width: measuredWidth ?? undefined }} |
| 72 | + > |
| 73 | + <span className="relative flex h-[1.15em] w-full items-center overflow-hidden whitespace-nowrap"> |
| 74 | + <span className="-translate-x-1/2 absolute left-1/2"> |
| 75 | + <AnimatePresence mode="wait" initial={false}> |
| 76 | + <motion.span |
| 77 | + key={rotatingWords[index]} |
| 78 | + initial={{ y: "100%", opacity: 0, x: 0 }} |
| 79 | + animate={{ y: 0, opacity: 1, x: 0 }} |
| 80 | + exit={{ y: "-100%", opacity: 0, x: 0 }} |
| 81 | + transition={{ |
| 82 | + type: "tween", |
| 83 | + ease: [0.22, 1, 0.36, 1], |
| 84 | + duration: 0.5, |
| 85 | + }} |
| 86 | + className="inline-block transform-gpu will-change-transform" |
| 87 | + > |
| 88 | + {rotatingWords[index]} |
| 89 | + </motion.span> |
| 90 | + </AnimatePresence> |
101 | 91 | </span> |
| 92 | + {/* in-flow baseline shim so the pill aligns with surrounding text baseline */} |
| 93 | + <span className="invisible select-none">{rotatingWords[index]}</span> |
102 | 94 | </span> |
103 | | - <span className="hidden whitespace-nowrap text-foreground/90 sm:inline"> |
104 | | - . |
| 95 | + </motion.span> |
| 96 | + {/* measurement element for smooth width animation (inherits h1 font sizing) */} |
| 97 | + <span |
| 98 | + ref={measureRef} |
| 99 | + className="pointer-events-none invisible absolute inline-flex items-center rounded-lg bg-muted/70 px-2 py-0.5 align-baseline text-foreground shadow-sm ring-1 ring-border/60 sm:rounded-xl sm:px-3 sm:py-1" |
| 100 | + aria-hidden="true" |
| 101 | + > |
| 102 | + <span className="inline-flex items-center whitespace-nowrap"> |
| 103 | + {rotatingWords[index]} |
105 | 104 | </span> |
106 | | - </h1> |
107 | | - </div> |
| 105 | + </span> |
| 106 | + <span className="hidden whitespace-nowrap text-foreground/90 sm:inline"> |
| 107 | + . |
| 108 | + </span> |
| 109 | + </h1> |
108 | 110 | ); |
109 | 111 | } |
0 commit comments