Skip to content

Commit a1ef2f0

Browse files
committed
Refine homepage typing hero
1 parent 55e76b6 commit a1ef2f0

3 files changed

Lines changed: 81 additions & 28 deletions

File tree

app/globals.css

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,19 @@
186186
transform: translateY(100%);
187187
}
188188

189+
.typewriter-char {
190+
opacity: 0;
191+
filter: blur(8px);
192+
transform: translateY(0.16em);
193+
animation: typewriter-char 420ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
194+
will-change: opacity, transform, filter;
195+
}
196+
197+
.typewriter-caret {
198+
animation: typewriter-caret 900ms steps(2, end) infinite;
199+
transform: translateY(0.08em);
200+
}
201+
189202
.brand-wordmark-text {
190203
background: linear-gradient(110deg, var(--foreground) 0%, var(--foreground) 30%, #06b6d4 42%, #f97316 50%, #67e8f9 58%, var(--foreground) 70%, var(--foreground) 100%);
191204
background-size: 240% 100%;
@@ -243,6 +256,24 @@
243256
}
244257
}
245258

259+
@keyframes typewriter-char {
260+
0% {
261+
opacity: 0;
262+
filter: blur(8px);
263+
transform: translateY(0.16em);
264+
}
265+
100% {
266+
opacity: 1;
267+
filter: blur(0);
268+
transform: translateY(0);
269+
}
270+
}
271+
272+
@keyframes typewriter-caret {
273+
0%, 45% { opacity: 1; }
274+
46%, 100% { opacity: 0; }
275+
}
276+
246277
.noise-overlay {
247278
position: relative;
248279
}

components/landing/hero-section.tsx

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,39 @@ import Link from "next/link";
55
import { Button } from "@/components/ui/button";
66
import { ArrowRight } from "lucide-react";
77
import { TubesBackground } from "./tubes-background";
8-
import { AnimatedSphere } from "./animated-sphere";
98
import { useReducedMotion } from "@/hooks/use-reduced-motion";
109

1110
const words = ["learn", "build", "practice", "ship"];
1211

12+
function TypedText({
13+
text,
14+
delayOffset = 0,
15+
characterClassName = "",
16+
}: {
17+
text: string;
18+
delayOffset?: number;
19+
characterClassName?: string;
20+
}) {
21+
return (
22+
<span aria-hidden="true">
23+
{text.split("").map((char, index) => (
24+
<span
25+
key={`${text}-${index}`}
26+
className={`typewriter-char inline-block ${characterClassName}`}
27+
style={{ animationDelay: `${delayOffset + index * 50}ms` }}
28+
>
29+
{char === " " ? "\u00A0" : char}
30+
</span>
31+
))}
32+
</span>
33+
);
34+
}
35+
1336
export function HeroSection() {
1437
const isVisible = true;
1538
const [wordIndex, setWordIndex] = useState(0);
1639
const prefersReducedMotion = useReducedMotion();
40+
const activeWord = words[wordIndex];
1741

1842
useEffect(() => {
1943
if (prefersReducedMotion) return;
@@ -26,18 +50,14 @@ export function HeroSection() {
2650

2751
return (
2852
<section className="relative flex min-h-screen flex-col justify-center overflow-hidden bg-background text-foreground">
29-
<div className="absolute right-[-12%] top-1/2 hidden h-[620px] w-[620px] -translate-y-1/2 pointer-events-none md:block lg:h-[820px] lg:w-[820px]">
53+
<div className="absolute right-[-6%] top-1/2 hidden h-[620px] w-[620px] -translate-y-1/2 opacity-50 pointer-events-none mix-blend-multiply md:block lg:h-[820px] lg:w-[820px]">
3054
<TubesBackground
3155
tone="light"
3256
enableClickInteraction={false}
33-
className="h-full min-h-full rounded-full border border-cyan-500/10 shadow-[0_30px_120px_rgba(6,182,212,0.16)]"
57+
className="h-full min-h-full"
3458
/>
3559
</div>
3660

37-
<div className="absolute right-0 top-1/2 hidden h-[600px] w-[600px] -translate-y-1/2 opacity-25 pointer-events-none md:block lg:h-[800px] lg:w-[800px]">
38-
<AnimatedSphere />
39-
</div>
40-
4161
<div className="absolute inset-0 overflow-hidden pointer-events-none opacity-30">
4262
{[...Array(8)].map((_, i) => (
4363
<div
@@ -69,27 +89,29 @@ export function HeroSection() {
6989

7090
<div className="mb-12">
7191
<h1
72-
className={`text-[clamp(3rem,12vw,10rem)] font-display leading-[0.9] tracking-tight transition-all duration-1000 ${
92+
aria-label={`The place to ${activeWord} AI`}
93+
className={`max-w-6xl text-[clamp(3rem,12vw,10rem)] font-display leading-[0.92] tracking-normal transition-all duration-1000 ${
7394
isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-8"
7495
}`}
7596
>
76-
<span className="block">The place to</span>
77-
<span className="block">
78-
<span className="relative inline-block brand-wordmark-text">
79-
<span key={wordIndex} className="inline-flex">
80-
{words[wordIndex].split("").map((char, i) => (
81-
<span
82-
key={`${wordIndex}-${i}`}
83-
className={prefersReducedMotion ? "inline-block" : "inline-block animate-char-in"}
84-
style={prefersReducedMotion ? undefined : { animationDelay: `${i * 50}ms` }}
85-
>
86-
{char}
87-
</span>
88-
))}
97+
<span className="block" aria-hidden="true">The place to</span>
98+
<span className="mt-2 block md:mt-3" aria-hidden="true">
99+
<span className="inline-flex items-baseline gap-[0.16em]">
100+
<span className="relative inline-flex min-w-[7.1ch] items-baseline md:min-w-[7.6ch]">
101+
<span key={wordIndex} className="inline-flex">
102+
{prefersReducedMotion ? (
103+
<span className="brand-wordmark-text">{activeWord}</span>
104+
) : (
105+
<TypedText text={activeWord} characterClassName="brand-wordmark-text" />
106+
)}
107+
</span>
108+
{prefersReducedMotion ? (
109+
null
110+
) : <span className="typewriter-caret ml-2 h-[0.72em] w-[0.06em] bg-cyan-600" />}
111+
<span className="absolute -bottom-2 left-0 h-2 w-full origin-left rounded-full bg-cyan-400/15 shadow-[0_0_28px_rgba(6,182,212,0.22)]" />
89112
</span>
90-
<span className="absolute -bottom-2 left-0 right-0 h-3 bg-cyan-400/15 shadow-[0_0_28px_rgba(6,182,212,0.22)]" />
91-
</span>{" "}
92-
AI
113+
<span>AI</span>
114+
</span>
93115
</span>
94116
</h1>
95117
</div>

components/landing/tubes-background.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,15 @@ export function TubesBackground({
107107
"absolute inset-0",
108108
tone === "dark"
109109
? "bg-[radial-gradient(circle_at_25%_20%,rgba(6,182,212,0.28),transparent_32%),radial-gradient(circle_at_76%_12%,rgba(249,115,22,0.2),transparent_28%),linear-gradient(135deg,#050608_0%,#0b1517_54%,#11100a_100%)]"
110-
: "bg-[radial-gradient(circle_at_28%_24%,rgba(6,182,212,0.22),transparent_34%),radial-gradient(circle_at_76%_18%,rgba(249,115,22,0.16),transparent_30%),linear-gradient(135deg,rgba(255,255,255,0.86),rgba(236,254,255,0.48),rgba(255,247,237,0.36))]",
110+
: "bg-transparent",
111111
)}
112112
/>
113113
<div
114114
className={cn(
115115
"absolute inset-0 [background-size:72px_72px]",
116116
tone === "dark"
117117
? "opacity-45 [background-image:linear-gradient(rgba(255,255,255,0.08)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.08)_1px,transparent_1px)]"
118-
: "opacity-55 [background-image:linear-gradient(rgba(15,23,42,0.06)_1px,transparent_1px),linear-gradient(90deg,rgba(15,23,42,0.06)_1px,transparent_1px)]",
118+
: "opacity-0",
119119
)}
120120
/>
121121
{!prefersReducedMotion ? (
@@ -125,7 +125,7 @@ export function TubesBackground({
125125
className={cn(
126126
"absolute inset-0 block h-full w-full transition-opacity duration-700",
127127
tone === "light" ? "mix-blend-multiply saturate-125" : "",
128-
isLoaded ? (tone === "dark" ? "opacity-90" : "opacity-45") : "opacity-0",
128+
isLoaded ? (tone === "dark" ? "opacity-90" : "opacity-40") : "opacity-0",
129129
)}
130130
style={{ touchAction: "none" }}
131131
/>
@@ -135,7 +135,7 @@ export function TubesBackground({
135135
"absolute inset-0",
136136
tone === "dark"
137137
? "bg-[radial-gradient(circle_at_center,transparent_0%,rgba(5,6,8,0.18)_42%,rgba(5,6,8,0.74)_100%)]"
138-
: "bg-[radial-gradient(circle_at_center,transparent_0%,rgba(250,250,246,0.12)_44%,rgba(250,250,246,0.82)_100%)]",
138+
: "bg-transparent",
139139
)}
140140
/>
141141
<div className="relative z-10 h-full pointer-events-none">{children}</div>

0 commit comments

Comments
 (0)