11'use client'
22
3- import { useState } from 'react'
3+ import { useRef , useState } from 'react'
4+ import { type MotionValue , motion , useScroll , useTransform } from 'framer-motion'
45import Image from 'next/image'
56import Link from 'next/link'
67import { Badge , ChevronDown } from '@/components/emcn'
8+ import { FeaturesPreview } from '@/app/(home)/components/features/components/features-preview'
79
810function hexToRgba ( hex : string , alpha : number ) : string {
911 const r = Number . parseInt ( hex . slice ( 1 , 3 ) , 16 )
@@ -115,6 +117,25 @@ const FEATURE_TABS = [
115117 } ,
116118]
117119
120+ const HEADING_TEXT = 'Everything you need to build, deploy, and manage AI agents. '
121+ const HEADING_LETTERS = HEADING_TEXT . split ( '' )
122+
123+ const LETTER_REVEAL_SPAN = 0.85
124+ const LETTER_FADE_IN = 0.04
125+
126+ interface ScrollLetterProps {
127+ scrollYProgress : MotionValue < number >
128+ charIndex : number
129+ children : string
130+ }
131+
132+ function ScrollLetter ( { scrollYProgress, charIndex, children } : ScrollLetterProps ) {
133+ const threshold = ( charIndex / HEADING_LETTERS . length ) * LETTER_REVEAL_SPAN
134+ const opacity = useTransform ( scrollYProgress , [ threshold , threshold + LETTER_FADE_IN ] , [ 0.4 , 1 ] )
135+
136+ return < motion . span style = { { opacity } } > { children } </ motion . span >
137+ }
138+
118139function DotGrid ( {
119140 cols,
120141 rows,
@@ -139,20 +160,26 @@ function DotGrid({
139160 } }
140161 >
141162 { Array . from ( { length : cols * rows } , ( _ , i ) => (
142- < div key = { i } className = 'h-[2px ] w-[2px ] rounded-full bg-[#DEDEDE]' />
163+ < div key = { i } className = 'h-[1.5px ] w-[1.5px ] rounded-full bg-[#DEDEDE]' />
143164 ) ) }
144165 </ div >
145166 )
146167}
147168
148169export default function Features ( ) {
170+ const sectionRef = useRef < HTMLDivElement > ( null )
149171 const [ activeTab , setActiveTab ] = useState ( 0 )
150172
173+ const { scrollYProgress } = useScroll ( {
174+ target : sectionRef ,
175+ offset : [ 'start 0.9' , 'start 0.2' ] ,
176+ } )
177+
151178 return (
152179 < section
153180 id = 'features'
154181 aria-labelledby = 'features-heading'
155- className = 'relative overflow-hidden bg-[#F6F6F6] pb-[144px] '
182+ className = 'relative overflow-hidden bg-[#F6F6F6]'
156183 >
157184 < div aria-hidden = 'true' className = 'absolute top-0 left-0 w-full' >
158185 < Image
@@ -166,7 +193,7 @@ export default function Features() {
166193 </ div >
167194
168195 < div className = 'relative z-10 pt-[100px]' >
169- < div className = 'flex flex-col items-start gap-[20px] px-[80px]' >
196+ < div ref = { sectionRef } className = 'flex flex-col items-start gap-[20px] px-[80px]' >
170197 < Badge
171198 variant = 'blue'
172199 size = 'md'
@@ -186,111 +213,104 @@ export default function Features() {
186213 id = 'features-heading'
187214 className = 'max-w-[900px] font-[430] font-season text-[#1C1C1C] text-[40px] leading-[110%] tracking-[-0.02em]'
188215 >
189- Everything you need to build, deploy, and manage AI agents.{ ' ' }
216+ { HEADING_LETTERS . map ( ( char , i ) => (
217+ < ScrollLetter key = { i } scrollYProgress = { scrollYProgress } charIndex = { i } >
218+ { char }
219+ </ ScrollLetter >
220+ ) ) }
190221 < span className = 'text-[#1C1C1C]/40' >
191222 Design powerful workflows, connect your data, and monitor every run — all in one
192223 platform.
193224 </ span >
194225 </ h2 >
195226 </ div >
196227
197- < div className = 'mt-[73px] flex h-[68px] overflow-hidden border border-[#E9E9E9]' >
198- < DotGrid cols = { 10 } rows = { 8 } width = { 80 } />
199-
200- < div role = 'tablist' aria-label = 'Feature categories' className = 'flex flex-1' >
201- { FEATURE_TABS . map ( ( tab , index ) => (
202- < button
203- key = { tab . label }
204- type = 'button'
205- role = 'tab'
206- aria-selected = { index === activeTab }
207- onClick = { ( ) => setActiveTab ( index ) }
208- className = 'relative flex h-full flex-1 items-center justify-center border-[#E9E9E9] border-l font-medium font-season text-[#212121] text-[14px] uppercase'
209- style = { { backgroundColor : index === activeTab ? '#FDFDFD' : '#F6F6F6' } }
210- >
211- { tab . label }
212- { index === activeTab && (
213- < div className = 'absolute right-0 bottom-0 left-0 flex h-[6px]' >
214- { tab . segments . map ( ( [ opacity , width ] , i ) => (
215- < div
216- key = { i }
217- className = 'h-full shrink-0'
218- style = { {
219- width : `${ width } %` ,
220- backgroundColor : tab . color ,
221- opacity,
222- } }
223- />
224- ) ) }
225- </ div >
226- ) }
227- </ button >
228- ) ) }
229- </ div >
228+ < div className = 'relative mt-[73px] pb-[80px]' >
229+ < div
230+ aria-hidden = 'true'
231+ className = 'absolute top-0 bottom-0 left-[80px] z-20 w-px bg-[#E9E9E9]'
232+ />
233+ < div
234+ aria-hidden = 'true'
235+ className = 'absolute top-0 right-[80px] bottom-0 z-20 w-px bg-[#E9E9E9]'
236+ />
230237
231- < DotGrid cols = { 10 } rows = { 8 } width = { 80 } borderLeft / >
232- </ div >
238+ < div className = 'flex h-[68px] overflow-hidden border border-[#E9E9E9]' >
239+ < DotGrid cols = { 10 } rows = { 8 } width = { 80 } / >
233240
234- < div className = 'mt-[60px] grid grid-cols-[1fr_2.8fr] gap-[60px] px-[80px]' >
235- < div className = 'flex h-[560px] flex-col items-start justify-between pt-[20px]' >
236- < div className = 'flex flex-col items-start gap-[16px]' >
237- < h3 className = 'font-[430] font-season text-[#1C1C1C] text-[28px] leading-[120%] tracking-[-0.02em]' >
238- { FEATURE_TABS [ activeTab ] . title }
239- </ h3 >
240- < p className = 'font-[430] font-season text-[#1C1C1C]/50 text-[18px] leading-[150%] tracking-[0.02em]' >
241- { FEATURE_TABS [ activeTab ] . description }
242- </ p >
243- </ div >
244- < Link
245- href = '/signup'
246- className = 'group/cta inline-flex h-[32px] items-center gap-[6px] rounded-[5px] border border-[#1D1D1D] bg-[#1D1D1D] px-[10px] font-[430] font-season text-[14px] text-white transition-colors hover:border-[#2A2A2A] hover:bg-[#2A2A2A]'
247- >
248- { FEATURE_TABS [ activeTab ] . cta }
249- < span className = 'relative h-[10px] w-[10px] shrink-0' >
250- < ChevronDown className = '-rotate-90 absolute inset-0 h-[10px] w-[10px] transition-opacity duration-150 group-hover/cta:opacity-0' />
251- < svg
252- className = 'absolute inset-0 h-[10px] w-[10px] opacity-0 transition-opacity duration-150 group-hover/cta:opacity-100'
253- viewBox = '0 0 10 10'
254- fill = 'none'
241+ < div role = 'tablist' aria-label = 'Feature categories' className = 'flex flex-1' >
242+ { FEATURE_TABS . map ( ( tab , index ) => (
243+ < button
244+ key = { tab . label }
245+ type = 'button'
246+ role = 'tab'
247+ aria-selected = { index === activeTab }
248+ onClick = { ( ) => setActiveTab ( index ) }
249+ className = 'relative flex h-full flex-1 items-center justify-center border-[#E9E9E9] border-l font-medium font-season text-[#212121] text-[14px] uppercase'
250+ style = { { backgroundColor : index === activeTab ? '#FDFDFD' : '#F6F6F6' } }
255251 >
256- < path
257- d = 'M1 5H8M5.5 2L8.5 5L5.5 8'
258- stroke = 'currentColor'
259- strokeWidth = '1.33'
260- strokeLinecap = 'square'
261- strokeLinejoin = 'miter'
262- fill = 'none'
263- />
264- </ svg >
265- </ span >
266- </ Link >
252+ { tab . label }
253+ { index === activeTab && (
254+ < div className = 'absolute right-0 bottom-0 left-0 flex h-[6px]' >
255+ { tab . segments . map ( ( [ opacity , width ] , i ) => (
256+ < div
257+ key = { i }
258+ className = 'h-full shrink-0'
259+ style = { {
260+ width : `${ width } %` ,
261+ backgroundColor : tab . color ,
262+ opacity,
263+ } }
264+ />
265+ ) ) }
266+ </ div >
267+ ) }
268+ </ button >
269+ ) ) }
270+ </ div >
271+
272+ < DotGrid cols = { 10 } rows = { 8 } width = { 80 } borderLeft />
267273 </ div >
268274
269- < div
270- className = 'flex h-[560px] items-center justify-center rounded-[8px] border-2 border-dashed'
271- style = { {
272- borderColor : hexToRgba (
273- FEATURE_TABS [ activeTab ] . badgeColor ?? FEATURE_TABS [ activeTab ] . color ,
274- 0.3
275- ) ,
276- backgroundColor : hexToRgba (
277- FEATURE_TABS [ activeTab ] . badgeColor ?? FEATURE_TABS [ activeTab ] . color ,
278- 0.04
279- ) ,
280- } }
281- >
282- < span
283- className = 'font-[430] font-season text-[14px] uppercase tracking-[0.08em]'
284- style = { {
285- color : hexToRgba (
286- FEATURE_TABS [ activeTab ] . badgeColor ?? FEATURE_TABS [ activeTab ] . color ,
287- 0.4
288- ) ,
289- } }
290- >
291- { FEATURE_TABS [ activeTab ] . label } preview
292- </ span >
275+ < div className = 'mt-[60px] grid grid-cols-[1fr_2.8fr] gap-[60px] px-[120px]' >
276+ < div className = 'flex h-[560px] flex-col items-start justify-between pt-[20px]' >
277+ < div className = 'flex flex-col items-start gap-[16px]' >
278+ < h3 className = 'font-[430] font-season text-[#1C1C1C] text-[28px] leading-[120%] tracking-[-0.02em]' >
279+ { FEATURE_TABS [ activeTab ] . title }
280+ </ h3 >
281+ < p className = 'font-[430] font-season text-[#1C1C1C]/50 text-[18px] leading-[150%] tracking-[0.02em]' >
282+ { FEATURE_TABS [ activeTab ] . description }
283+ </ p >
284+ </ div >
285+ < Link
286+ href = '/signup'
287+ className = 'group/cta inline-flex h-[32px] items-center gap-[6px] rounded-[5px] border border-[#1D1D1D] bg-[#1D1D1D] px-[10px] font-[430] font-season text-[14px] text-white transition-colors hover:border-[#2A2A2A] hover:bg-[#2A2A2A]'
288+ >
289+ { FEATURE_TABS [ activeTab ] . cta }
290+ < span className = 'relative h-[10px] w-[10px] shrink-0' >
291+ < ChevronDown className = '-rotate-90 absolute inset-0 h-[10px] w-[10px] transition-opacity duration-150 group-hover/cta:opacity-0' />
292+ < svg
293+ className = 'absolute inset-0 h-[10px] w-[10px] opacity-0 transition-opacity duration-150 group-hover/cta:opacity-100'
294+ viewBox = '0 0 10 10'
295+ fill = 'none'
296+ >
297+ < path
298+ d = 'M1 5H8M5.5 2L8.5 5L5.5 8'
299+ stroke = 'currentColor'
300+ strokeWidth = '1.33'
301+ strokeLinecap = 'square'
302+ strokeLinejoin = 'miter'
303+ fill = 'none'
304+ />
305+ </ svg >
306+ </ span >
307+ </ Link >
308+ </ div >
309+
310+ < FeaturesPreview />
293311 </ div >
312+
313+ < div aria-hidden = 'true' className = 'mt-[60px] h-px bg-[#E9E9E9]' />
294314 </ div >
295315 </ div >
296316 </ section >
0 commit comments