1+ import { useEffect , useRef , useState } from 'react' ;
12import { AnimatedElement } from '../ui/AnimatedElement' ;
23
34interface TimelineItem {
45 date : string ;
56 title : string ;
67 description : string ;
78 status : 'completed' | 'inprog' | 'activ' | 'done' ;
9+ url ?: string ;
810}
911
1012const timelineItems : TimelineItem [ ] = [
@@ -136,6 +138,7 @@ const timelineItems: TimelineItem[] = [
136138 title : 'Conceal Bridge' ,
137139 description : 'Swap your CCX to wCCX and back the other way with our Bridge tool.' ,
138140 status : 'completed' ,
141+ url : 'https://bridge.conceal.network' ,
139142 } ,
140143 {
141144 date : 'Q2 2021' ,
@@ -162,40 +165,60 @@ const timelineItems: TimelineItem[] = [
162165 title : 'Conceal Web Wallet' ,
163166 description : 'Release of our 100% Client-Side Web Wallet for Conceal.' ,
164167 status : 'completed' ,
168+ url : 'https://wallet.conceal.network' ,
165169 } ,
166170 {
167171 date : 'Q3 2023' ,
168172 title : 'Web wallet improvements' ,
169173 description :
170174 'Improving speed and making optimizations. Now send encrypted Messages from your smartphone' ,
171175 status : 'completed' ,
176+ url : 'https://wallet.conceal.network' ,
172177 } ,
173178 {
174179 date : 'Q3 2024' ,
175180 title : 'Web wallet improvements' ,
176181 description :
177182 'Improving anonymity by randomly picking nodes from a bigger list, now accessing SSL SmartNodes.' ,
178183 status : 'completed' ,
184+ url : 'https://wallet.conceal.network' ,
179185 } ,
180186 {
181187 date : 'Q3 2024' ,
182188 title : 'Conceal Marketplace' ,
183189 description :
184190 'A great place to interact with other Concealers, Buy, Sell in a peer 2 peer way trading with your CCX!' ,
185191 status : 'completed' ,
192+ url : 'https://conceal.network/marketplace' ,
186193 } ,
187194 {
188195 date : 'Q4 2024' ,
189196 title : 'Web wallet improvements' ,
190197 description :
191198 'Now available in 14 languages. Access Deposits (view-only). Use qr code scanning feature to send messages. Get notified of new messages.' ,
192199 status : 'completed' ,
200+ url : 'https://wallet.conceal.network' ,
193201 } ,
194202 {
195- date : '' ,
196- title : 'Q2 2025, Web wallet deposits' ,
203+ date : 'Q2 2025 ' ,
204+ title : 'Web wallet deposits' ,
197205 description : 'Bringing deposits to client side web wallet' ,
198206 status : 'completed' ,
207+ url : 'https://wallet.conceal.network' ,
208+ } ,
209+ {
210+ date : 'Q4 2025' ,
211+ title : 'Conceal Labs' ,
212+ description : 'Conceal Authenticator app is launched, your 2FA keys are now stored on the blockchain' ,
213+ status : 'completed' ,
214+ url : 'https://f-droid.org/en/packages/com.acktarius.concealauthenticator/' ,
215+ } ,
216+ {
217+ date : 'Q1 2026' ,
218+ title : 'Conceal Labs' ,
219+ description : 'Conceal-Faucet-API is launched, "one stop shop" for developpers to create faucet or game rewards' ,
220+ status : 'completed' ,
221+ url : 'https://github.com/ConcealNetwork/conceal-faucet-api' ,
199222 } ,
200223 {
201224 date : '' ,
@@ -230,8 +253,65 @@ const timelineItems: TimelineItem[] = [
230253] ;
231254
232255export function RoadmapSection ( ) {
256+ const sectionRef = useRef < HTMLElement > ( null ) ;
257+ const itemRefs = useRef < Map < number , HTMLDivElement > > ( new Map ( ) ) ;
258+ const [ itemScales , setItemScales ] = useState < Map < number , number > > ( new Map ( ) ) ;
259+
260+ useEffect ( ( ) => {
261+ const handleScroll = ( ) => {
262+ if ( ! sectionRef . current ) return ;
263+
264+ const sectionRect = sectionRef . current . getBoundingClientRect ( ) ;
265+
266+ // Only apply effect if roadmap section is in viewport
267+ const isSectionVisible = sectionRect . bottom > 0 && sectionRect . top < window . innerHeight ;
268+
269+ if ( ! isSectionVisible ) {
270+ // Reset all scales if section is not visible
271+ const resetScales = new Map < number , number > ( ) ;
272+ itemRefs . current . forEach ( ( _ , index ) => {
273+ resetScales . set ( index , 1.0 ) ;
274+ } ) ;
275+ setItemScales ( resetScales ) ;
276+ return ;
277+ }
278+
279+ const newScales = new Map < number , number > ( ) ;
280+ const centerY = window . innerHeight / 2 ;
281+ const maxDistance = 400 ; // Maximum distance for scaling effect
282+
283+ itemRefs . current . forEach ( ( element , index ) => {
284+ if ( ! element ) return ;
285+
286+ const rect = element . getBoundingClientRect ( ) ;
287+ const itemY = rect . top + rect . height / 2 ;
288+ const distanceFromCenter = Math . abs ( centerY - itemY ) ;
289+
290+ // Map distance to scale: 1.22 at center, 1.0 at maxDistance
291+ const normalizedDistance = Math . min ( 1 , distanceFromCenter / maxDistance ) ;
292+ const scale = 1.0 + ( 0.22 * ( 1 - normalizedDistance ) ) ; // 1.6 at center, 1.0 at maxDistance
293+
294+ newScales . set ( index , scale ) ;
295+ } ) ;
296+
297+ setItemScales ( newScales ) ;
298+ } ;
299+
300+ // Initial check
301+ handleScroll ( ) ;
302+
303+ // Listen to scroll events
304+ window . addEventListener ( 'scroll' , handleScroll , { passive : true } ) ;
305+ window . addEventListener ( 'resize' , handleScroll , { passive : true } ) ;
306+
307+ return ( ) => {
308+ window . removeEventListener ( 'scroll' , handleScroll ) ;
309+ window . removeEventListener ( 'resize' , handleScroll ) ;
310+ } ;
311+ } , [ ] ) ;
312+
233313 return (
234- < section id = "roadmap" className = "py-16 px-4 border-b border-[rgba(255,255,255,0.2)] relative" >
314+ < section ref = { sectionRef } id = "roadmap" className = "py-16 px-4 border-b border-[rgba(255,255,255,0.2)] relative" >
235315 { /* Background image */ }
236316 < div
237317 id = "herobg"
@@ -282,14 +362,27 @@ export function RoadmapSection() {
282362 const isActiv = item . status === 'activ' ;
283363 const isDone = item . status === 'done' ;
284364
365+ const scale = itemScales . get ( index ) ?? 1.0 ;
366+
285367 return (
286368 < AnimatedElement
287369 key = { `${ item . date } -${ item . title } ` }
288370 types = { [ 'fadeIn' ] }
289371 triggerImmediately = { false }
290372 >
291373 < div
292- className = { `single-timeline flex items-center mb-[22px] ${ isEven ? 'flex-row-reverse' : '' } ` }
374+ ref = { ( el ) => {
375+ if ( el ) {
376+ itemRefs . current . set ( index , el ) ;
377+ } else {
378+ itemRefs . current . delete ( index ) ;
379+ }
380+ } }
381+ className = { `single-timeline flex items-center mb-[22px] transition-transform duration-300 ease-out ${ isEven ? 'flex-row-reverse' : '' } ` }
382+ style = { {
383+ transform : `scale(${ scale } )` ,
384+ transformOrigin : 'center center' ,
385+ } }
293386 >
294387 < div className = "timeline-blank w-1/2" > </ div >
295388 < div
@@ -339,7 +432,7 @@ export function RoadmapSection() {
339432 { item . title }
340433 </ h6 >
341434 ) }
342- { item . description && < span > — { item . description } </ span > }
435+ { item . description && < span > — { item . url ? < a href = { item . url } target = "_blank" rel = "noopener noreferrer" className = "text-inherit hover:text-[var(--color1)]" title = { `Visit ${ item . url } ` } > { item . description } </ a > : item . description } </ span > }
343436 </ span >
344437 </ div >
345438 </ div >
0 commit comments