@@ -20,10 +20,18 @@ export default function Home() {
2020 < div className = "min-h-screen flex flex-col font-sans" >
2121 < Navbar />
2222 < Hero />
23- < Platforms />
24- < Providers />
25- < Plugins />
26- < Community />
23+ < StickyFadeSection zIndex = { 10 } heightVh = { 220 } fadeStart = { 0.60 } fadeEnd = { 0.92 } >
24+ < Platforms />
25+ </ StickyFadeSection >
26+ < StickyFadeSection zIndex = { 20 } heightVh = { 220 } fadeStart = { 0.60 } fadeEnd = { 0.92 } >
27+ < Providers />
28+ </ StickyFadeSection >
29+ < StickyFadeSection zIndex = { 30 } heightVh = { 200 } fadeStart = { 0.58 } fadeEnd = { 0.9 } >
30+ < Plugins />
31+ </ StickyFadeSection >
32+ < StickyFadeSection zIndex = { 40 } heightVh = { 200 } fadeStart = { 0.58 } fadeEnd = { 0.9 } >
33+ < Community />
34+ </ StickyFadeSection >
2735 < MoreThings />
2836 < GetStarted />
2937 < SiteFooter />
@@ -53,6 +61,67 @@ function useScrollY() {
5361 return scrollY ;
5462}
5563
64+ // Compute a 0..1 fade progress for a tall wrapper with a sticky child
65+ function useSectionFade (
66+ wrapperRef : { current : HTMLElement | null } ,
67+ fadeStart : number = 0.55 ,
68+ fadeEnd : number = 0.9
69+ ) {
70+ const [ opacity , setOpacity ] = useState ( 1 ) ;
71+ useEffect ( ( ) => {
72+ if ( typeof window === "undefined" ) return ;
73+ let raf = 0 as number | 0 ;
74+ const update = ( ) => {
75+ const el = wrapperRef . current as HTMLElement | null ;
76+ if ( ! el ) return setOpacity ( 1 ) ;
77+ const rect = el . getBoundingClientRect ( ) ;
78+ const total = Math . max ( 1 , el . offsetHeight ) ;
79+ const scrolled = Math . min ( Math . max ( 0 , - rect . top ) , total ) ;
80+ const progress = scrolled / total ; // 0..1
81+ const t = Math . min ( 1 , Math . max ( 0 , ( progress - fadeStart ) / Math . max ( 0.0001 , ( fadeEnd - fadeStart ) ) ) ) ;
82+ const eased = 1 - ( t * t * ( 3 - 2 * t ) ) ; // smoothstep ease-out for fade
83+ setOpacity ( eased ) ;
84+ } ;
85+ const onScroll = ( ) => {
86+ if ( ! raf ) raf = requestAnimationFrame ( ( ) => { update ( ) ; raf = 0 as number | 0 ; } ) as unknown as number ;
87+ } ;
88+ const onResize = ( ) => { update ( ) ; } ;
89+ update ( ) ;
90+ window . addEventListener ( "scroll" , onScroll , { passive : true } ) ;
91+ window . addEventListener ( "resize" , onResize , { passive : true } ) ;
92+ return ( ) => {
93+ if ( raf ) cancelAnimationFrame ( raf ) ;
94+ window . removeEventListener ( "scroll" , onScroll ) ;
95+ window . removeEventListener ( "resize" , onResize ) ;
96+ } ;
97+ } , [ wrapperRef , fadeStart , fadeEnd ] ) ;
98+ return opacity ;
99+ }
100+
101+ function StickyFadeSection ( {
102+ children,
103+ zIndex,
104+ heightVh = 200 ,
105+ fadeStart = 0.6 ,
106+ fadeEnd = 0.9 ,
107+ } : {
108+ children : React . ReactNode ;
109+ zIndex : number ;
110+ heightVh ?: number ;
111+ fadeStart ?: number ;
112+ fadeEnd ?: number ;
113+ } ) {
114+ const wrapperRef = useRef < HTMLElement | null > ( null ) ;
115+ const opacity = useSectionFade ( wrapperRef , fadeStart , fadeEnd ) ;
116+ return (
117+ < section ref = { wrapperRef } className = "relative" style = { { height : `${ heightVh } vh` } } >
118+ < div className = "sticky top-16" style = { { zIndex, opacity } } >
119+ { children }
120+ </ div >
121+ </ section >
122+ ) ;
123+ }
124+
56125function Navbar ( ) {
57126 const [ openLang , setOpenLang ] = useState ( false ) ;
58127 const [ openMenu , setOpenMenu ] = useState ( false ) ;
@@ -152,7 +221,7 @@ function Navbar() {
152221 < ChevronDownIcon aria-hidden className = { `w-4 h-4 transition-transform duration-200 ${ openLang ? 'rotate-180' : '' } ` } />
153222 </ button >
154223 { openLang && (
155- < ul className = "absolute right-0 mt-5 w-28 rounded-lg border border-ui bg-background shadow-lg origin-top-right animate-dropdown" >
224+ < ul className = "absolute right-0 mt-3 w-28 rounded-lg border border-ui bg-background shadow-lg origin-top-right animate-dropdown" >
156225 < li className = "px-3 py-2 hover:bg-black/[.04] dark:hover:bg-white/[.06] cursor-pointer" onClick = { ( ) => { setLocale ( "zh-CN" ) ; setOpenLang ( false ) ; } } > 简体中文</ li >
157226 < li className = "px-3 py-2 hover:bg-black/[.04] dark:hover:bg-white/[.06] cursor-pointer" onClick = { ( ) => { setLocale ( "en-US" ) ; setOpenLang ( false ) ; } } > English</ li >
158227 < li className = "px-3 py-2 hover:bg-black/[.04] dark:hover:bg-white/[.06] cursor-pointer" onClick = { ( ) => { setLocale ( "ja-JP" ) ; setOpenLang ( false ) ; } } > 日本語</ li >
@@ -407,6 +476,8 @@ function Platforms() {
407476
408477function Providers ( ) {
409478 const { t } = useI18n ( ) ;
479+ const scrollY = useScrollY ( ) ;
480+ const provParallax = { transform : `translateY(${ scrollY * 0.03 } px)` , willChange : "transform" } as React . CSSProperties ;
410481 const items = [
411482 { name : "OpenAI" , src : "https://registry.npmmirror.com/@lobehub/icons-static-svg/latest/files/icons/openai.svg" } ,
412483 { name : "xAI" , src : "https://registry.npmmirror.com/@lobehub/icons-static-svg/latest/files/icons/xai.svg" } ,
@@ -429,7 +500,11 @@ function Providers() {
429500 { name : "FastGPT" , src : "https://registry.npmmirror.com/@lobehub/icons-static-svg/latest/files/icons/fastgpt-color.svg" } ,
430501 ] ;
431502 return (
432- < section className = "min-h-[calc(100vh-64px)] flex items-center py-12 sm:py-16" >
503+ < section className = "relative overflow-hidden min-h-[calc(100vh-64px)] flex items-center py-12 sm:py-16" style = { { backgroundColor : "var(--section-surface)" } } >
504+ < div aria-hidden className = "pointer-events-none absolute inset-0 -z-10" style = { provParallax } >
505+ < div className = "absolute -top-8 -left-10 w-64 h-64 rounded-full blur-3xl opacity-15 brand-bg animate-orb-pulse" />
506+ < div className = "absolute bottom-[-40px] right-1/4 w-72 h-72 rounded-full blur-3xl opacity-10 animate-orb-pulse" style = { { backgroundColor : "#22d3ee" } } />
507+ </ div >
433508 < div className = "mx-auto max-w-6xl px-4 sm:px-6" >
434509 < Reveal as = "h2" className = "text-center text-3xl sm:text-4xl font-semibold tracking-tight mb-6 sm:mb-8 gradient-title" delay = { 0 } > { t ( "models.title" ) } </ Reveal >
435510 < Reveal as = "p" className = "text-center mt-2 text-sm opacity-80 mb-10 sm:mb-12" delay = { 150 } > { t ( "models.subtitle" ) } </ Reveal >
@@ -583,6 +658,8 @@ function Plugins() {
583658
584659function Community ( ) {
585660 const { t } = useI18n ( ) ;
661+ const scrollY = useScrollY ( ) ;
662+ const commParallax = { transform : `translateY(${ scrollY * 0.02 } px)` , willChange : "transform" } as React . CSSProperties ;
586663 const [ stats , setStats ] = useState < { stars : number ; forks : number ; contributors : number ; plugins : number } > ( { stars : 0 , forks : 0 , contributors : 0 , plugins : 0 } ) ;
587664 useEffect ( ( ) => {
588665 fetch ( "/api/plugins" , { cache : "no-store" } )
@@ -611,7 +688,11 @@ function Community() {
611688 ) ;
612689
613690 return (
614- < section className = "py-12 sm:py-16" >
691+ < section className = "relative overflow-hidden py-12 sm:py-16" style = { { backgroundColor : "var(--section-surface)" } } >
692+ < div aria-hidden className = "pointer-events-none absolute inset-0 -z-10" style = { commParallax } >
693+ < div className = "absolute top-[-20px] right-[-40px] w-60 h-60 rounded-full blur-3xl opacity-10 brand-bg animate-orb-pulse" />
694+ < div className = "absolute bottom-[-50px] left-1/5 w-72 h-72 rounded-full blur-3xl opacity-10" style = { { backgroundColor : "#a78bfa" } } />
695+ </ div >
615696 < div className = "mx-auto max-w-6xl px-4 sm:px-6" >
616697 < Reveal as = "h2" className = "text-center text-3xl sm:text-4xl font-semibold tracking-tight gradient-title" delay = { 0 } > { t ( "community.title" ) } </ Reveal >
617698 < Reveal as = "p" className = "text-center mt-2 mb-10 text-sm opacity-80" delay = { 150 } > { t ( "community.subtitle" ) } </ Reveal >
0 commit comments