11'use client'
22
33import { useRef , useState } from 'react'
4- import { type MotionValue , motion , useScroll , useTransform } from 'framer-motion'
4+ import { AnimatePresence , type MotionValue , motion , useScroll , useTransform } from 'framer-motion'
55import Image from 'next/image'
66import Link from 'next/link'
77import { Badge , ChevronDown } from '@/components/emcn'
@@ -168,6 +168,8 @@ function DotGrid({
168168 )
169169}
170170
171+ const INDICATOR_TRANSITION_MS = 300
172+
171173export default function Features ( ) {
172174 const sectionRef = useRef < HTMLDivElement > ( null )
173175 const [ activeTab , setActiveTab ] = useState ( 0 )
@@ -259,7 +261,10 @@ export default function Features() {
259261 aria-selected = { index === activeTab }
260262 onClick = { ( ) => setActiveTab ( index ) }
261263 className = { `relative h-full flex-1 items-center justify-center whitespace-nowrap px-[12px] font-medium font-season text-[#212121] text-[12px] uppercase lg:px-0 lg:text-[14px]${ tab . hideOnMobile ? ' hidden lg:flex' : ' flex' } ${ index > 0 ? ' border-[#E9E9E9] border-l' : '' } ` }
262- style = { { backgroundColor : index === activeTab ? '#FDFDFD' : '#F6F6F6' } }
264+ style = { {
265+ backgroundColor : index === activeTab ? '#FDFDFD' : '#F6F6F6' ,
266+ transition : `background-color ${ INDICATOR_TRANSITION_MS } ms ease` ,
267+ } }
263268 >
264269 { tab . mobileLabel ? (
265270 < >
@@ -269,21 +274,25 @@ export default function Features() {
269274 ) : (
270275 tab . label
271276 ) }
272- { index === activeTab && (
273- < div className = 'absolute right-0 bottom-0 left-0 flex h-[6px]' >
274- { tab . segments . map ( ( [ opacity , width ] , i ) => (
275- < div
276- key = { i }
277- className = 'h-full shrink-0'
278- style = { {
279- width : `${ width } %` ,
280- backgroundColor : tab . color ,
281- opacity,
282- } }
283- />
284- ) ) }
285- </ div >
286- ) }
277+ < div
278+ className = 'pointer-events-none absolute right-0 bottom-0 left-0 flex h-[6px]'
279+ style = { {
280+ opacity : index === activeTab ? 1 : 0 ,
281+ transition : `opacity ${ INDICATOR_TRANSITION_MS } ms ease` ,
282+ } }
283+ >
284+ { tab . segments . map ( ( [ segOpacity , width ] , i ) => (
285+ < div
286+ key = { i }
287+ className = 'h-full shrink-0'
288+ style = { {
289+ width : `${ width } %` ,
290+ backgroundColor : tab . color ,
291+ opacity : segOpacity ,
292+ } }
293+ />
294+ ) ) }
295+ </ div >
287296 </ button >
288297 ) ) }
289298 </ div >
@@ -299,38 +308,57 @@ export default function Features() {
299308 </ div >
300309
301310 < div className = 'mt-[32px] flex flex-col gap-[24px] px-[24px] lg:mt-[60px] lg:grid lg:grid-cols-[1fr_2.8fr] lg:gap-[60px] lg:px-[120px]' >
302- < div className = 'flex flex-col items-start justify-between gap-[24px] pt-[20px] lg:h-[560px] lg:gap-0' >
303- < div className = 'flex flex-col items-start gap-[16px]' >
304- < h3 className = 'font-[430] font-season text-[#1C1C1C] text-[24px] leading-[120%] tracking-[-0.02em] lg:text-[28px]' >
305- { FEATURE_TABS [ activeTab ] . title }
306- </ h3 >
307- < p className = 'font-[430] font-season text-[#1C1C1C]/50 text-[16px] leading-[150%] tracking-[0.02em] lg:text-[18px]' >
308- { FEATURE_TABS [ activeTab ] . description }
309- </ p >
310- </ div >
311- < Link
312- href = '/signup'
313- 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]'
314- >
315- { FEATURE_TABS [ activeTab ] . cta }
316- < span className = 'relative h-[10px] w-[10px] shrink-0' >
317- < ChevronDown className = '-rotate-90 absolute inset-0 h-[10px] w-[10px] transition-opacity duration-150 group-hover/cta:opacity-0' />
318- < svg
319- className = 'absolute inset-0 h-[10px] w-[10px] opacity-0 transition-opacity duration-150 group-hover/cta:opacity-100'
320- viewBox = '0 0 10 10'
321- fill = 'none'
311+ < div className = 'relative flex flex-col items-start justify-between gap-[24px] pt-[20px] lg:h-[560px] lg:gap-0' >
312+ < AnimatePresence mode = 'wait' >
313+ < motion . div
314+ key = { activeTab }
315+ className = 'flex flex-col items-start gap-[16px]'
316+ initial = { { opacity : 0 , y : 8 } }
317+ animate = { { opacity : 1 , y : 0 } }
318+ exit = { { opacity : 0 , y : - 8 } }
319+ transition = { { duration : 0.25 , ease : [ 0.4 , 0 , 0.2 , 1 ] } }
320+ >
321+ < h3 className = 'font-[430] font-season text-[#1C1C1C] text-[24px] leading-[120%] tracking-[-0.02em] lg:text-[28px]' >
322+ { FEATURE_TABS [ activeTab ] . title }
323+ </ h3 >
324+ < p className = 'font-[430] font-season text-[#1C1C1C]/50 text-[16px] leading-[150%] tracking-[0.02em] lg:text-[18px]' >
325+ { FEATURE_TABS [ activeTab ] . description }
326+ </ p >
327+ </ motion . div >
328+ </ AnimatePresence >
329+ < AnimatePresence mode = 'wait' >
330+ < motion . div
331+ key = { activeTab }
332+ initial = { { opacity : 0 } }
333+ animate = { { opacity : 1 } }
334+ exit = { { opacity : 0 } }
335+ transition = { { duration : 0.2 , ease : [ 0.4 , 0 , 0.2 , 1 ] } }
336+ >
337+ < Link
338+ href = '/signup'
339+ 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]'
322340 >
323- < path
324- d = 'M1 5H8M5.5 2L8.5 5L5.5 8'
325- stroke = 'currentColor'
326- strokeWidth = '1.33'
327- strokeLinecap = 'square'
328- strokeLinejoin = 'miter'
329- fill = 'none'
330- />
331- </ svg >
332- </ span >
333- </ Link >
341+ { FEATURE_TABS [ activeTab ] . cta }
342+ < span className = 'relative h-[10px] w-[10px] shrink-0' >
343+ < ChevronDown className = '-rotate-90 absolute inset-0 h-[10px] w-[10px] transition-opacity duration-150 group-hover/cta:opacity-0' />
344+ < svg
345+ className = 'absolute inset-0 h-[10px] w-[10px] opacity-0 transition-opacity duration-150 group-hover/cta:opacity-100'
346+ viewBox = '0 0 10 10'
347+ fill = 'none'
348+ >
349+ < path
350+ d = 'M1 5H8M5.5 2L8.5 5L5.5 8'
351+ stroke = 'currentColor'
352+ strokeWidth = '1.33'
353+ strokeLinecap = 'square'
354+ strokeLinejoin = 'miter'
355+ fill = 'none'
356+ />
357+ </ svg >
358+ </ span >
359+ </ Link >
360+ </ motion . div >
361+ </ AnimatePresence >
334362 </ div >
335363
336364 < FeaturesPreview activeTab = { activeTab } />
0 commit comments