1- 'use client '
1+ import { memo } from 'react '
22
3- import { memo , useEffect , useState } from 'react'
4-
5- /** Shared corner radius from Figma export for all decorative rects. */
63const RX = '2.59574'
74
8- const ENTER_STAGGER = 0.06
9- const ENTER_DURATION = 0.3
10- const EXIT_STAGGER = 0.12
11- const EXIT_DURATION = 0.5
12- const INITIAL_HOLD = 3000
13- const HOLD_BETWEEN = 3000
14- const TRANSITION_PAUSE = 400
15-
165interface BlockRect {
176 opacity : number
187 width : string
@@ -23,8 +12,6 @@ interface BlockRect {
2312 transform ?: string
2413}
2514
26- type AnimState = 'visible' | 'exiting' | 'hidden'
27-
2815const RECTS = {
2916 topRight : [
3017 { opacity : 1 , x : '0' , y : '0' , width : '16.8626' , height : '33.7252' , fill : '#2ABBF8' } ,
@@ -67,76 +54,33 @@ const RECTS = {
6754 fill : '#FA4EDF' ,
6855 } ,
6956 ] ,
70- left : [
71- {
72- opacity : 0.6 ,
73- width : '34.240' ,
74- height : '33.725' ,
75- fill : '#FA4EDF' ,
76- transform : 'matrix(0 1 1 0 0 0)' ,
77- } ,
78- {
79- opacity : 0.6 ,
80- width : '16.8626' ,
81- height : '68.480' ,
82- fill : '#FA4EDF' ,
83- transform : 'matrix(-1 0 0 1 33.727 0)' ,
84- } ,
85- {
86- opacity : 1 ,
87- width : '16.8626' ,
88- height : '16.8626' ,
89- fill : '#FA4EDF' ,
90- transform : 'matrix(-1 0 0 1 33.727 17.378)' ,
91- } ,
92- {
93- opacity : 0.6 ,
94- width : '16.8626' ,
95- height : '33.986' ,
96- fill : '#FA4EDF' ,
97- transform : 'matrix(0 1 1 0 0 51.616)' ,
98- } ,
99- {
100- opacity : 0.6 ,
101- width : '16.8626' ,
102- height : '140.507' ,
103- fill : '#00F701' ,
104- transform : 'matrix(-1 0 0 1 33.986 85.335)' ,
105- } ,
106- {
107- opacity : 0.4 ,
108- x : '17.119' ,
109- y : '136.962' ,
110- width : '34.240' ,
111- height : '16.8626' ,
112- fill : '#FFCC02' ,
113- transform : 'rotate(-90 17.119 136.962)' ,
114- } ,
57+ bottomLeft : [
58+ { opacity : 1 , x : '0' , y : '0' , width : '16.8626' , height : '33.7252' , fill : '#2ABBF8' } ,
59+ { opacity : 0.6 , x : '0' , y : '0' , width : '85.3433' , height : '16.8626' , fill : '#2ABBF8' } ,
60+ { opacity : 1 , x : '0' , y : '0' , width : '16.8626' , height : '16.8626' , fill : '#2ABBF8' } ,
61+ { opacity : 0.6 , x : '34.2403' , y : '0' , width : '34.2403' , height : '33.7252' , fill : '#2ABBF8' } ,
62+ { opacity : 1 , x : '34.2403' , y : '0' , width : '16.8626' , height : '16.8626' , fill : '#2ABBF8' } ,
11563 {
11664 opacity : 1 ,
117- x : '17.119 ' ,
118- y : '136.962 ' ,
65+ x : '51.6188 ' ,
66+ y : '16.8626 ' ,
11967 width : '16.8626' ,
12068 height : '16.8626' ,
121- fill : '#FFCC02' ,
122- transform : 'rotate(-90 17.119 136.962)' ,
123- } ,
124- {
125- opacity : 0.5 ,
126- width : '34.240' ,
127- height : '33.725' ,
128- fill : '#00F701' ,
129- transform : 'matrix(0 1 1 0 0.257 153.825)' ,
69+ fill : '#2ABBF8' ,
13070 } ,
71+ { opacity : 1 , x : '68.4812' , y : '0' , width : '54.6502' , height : '16.8626' , fill : '#00F701' } ,
72+ { opacity : 0.6 , x : '106.268' , y : '0' , width : '34.2403' , height : '33.7252' , fill : '#00F701' } ,
73+ { opacity : 0.6 , x : '106.268' , y : '0' , width : '51.103' , height : '16.8626' , fill : '#00F701' } ,
13174 {
13275 opacity : 1 ,
76+ x : '123.6484' ,
77+ y : '16.8626' ,
13378 width : '16.8626' ,
13479 height : '16.8626' ,
13580 fill : '#00F701' ,
136- transform : 'matrix(0 1 1 0 0.257 153.825)' ,
13781 } ,
13882 ] ,
139- right : [
83+ bottomRight : [
14084 {
14185 opacity : 0.6 ,
14286 width : '16.8626' ,
@@ -175,68 +119,33 @@ const RECTS = {
175119 {
176120 opacity : 0.6 ,
177121 width : '16.8626' ,
178- height : '33.726' ,
179- fill : '#FA4EDF' ,
180- transform : 'matrix(0 1 1 0 0.012 68.510)' ,
181- } ,
182- {
183- opacity : 0.6 ,
184- width : '16.8626' ,
185- height : '102.384' ,
122+ height : '34.24' ,
186123 fill : '#2ABBF8' ,
187- transform : 'matrix(-1 0 0 1 33.787 102.384 )' ,
124+ transform : 'matrix(-1 0 0 1 33.787 68 )' ,
188125 } ,
189126 {
190127 opacity : 0.4 ,
191- x : '17.131' ,
192- y : '153.859' ,
193- width : '34.241' ,
194- height : '16.8626' ,
195- fill : '#00F701' ,
196- transform : 'rotate(-90 17.131 153.859)' ,
197- } ,
198- {
199- opacity : 1 ,
200- x : '17.131' ,
201- y : '153.859' ,
202128 width : '16.8626' ,
203129 height : '16.8626' ,
204- fill : '#00F701 ' ,
205- transform : 'rotate(-90 17.131 153.859 )' ,
130+ fill : '#1A8FCC ' ,
131+ transform : 'matrix(-1 0 0 1 33.787 85 )' ,
206132 } ,
207133 ] ,
208134} as const satisfies Record < string , readonly BlockRect [ ] >
209135
210- type Position = keyof typeof RECTS
211-
212- function enterTime ( pos : Position ) : number {
213- return ( RECTS [ pos ] . length - 1 ) * ENTER_STAGGER + ENTER_DURATION
214- }
215-
216- function exitTime ( pos : Position ) : number {
217- return ( RECTS [ pos ] . length - 1 ) * EXIT_STAGGER + EXIT_DURATION
218- }
219-
220- interface BlockGroupProps {
221- width : number
222- height : number
223- viewBox : string
224- rects : readonly BlockRect [ ]
225- animState : AnimState
226- globalOpacity : number
227- }
136+ const GLOBAL_OPACITY = 0.55
228137
229138const BlockGroup = memo ( function BlockGroup ( {
230139 width,
231140 height,
232141 viewBox,
233142 rects,
234- animState ,
235- globalOpacity ,
236- } : BlockGroupProps ) {
237- const isVisible = animState === 'visible'
238- const isExiting = animState === 'exiting'
239-
143+ } : {
144+ width : number
145+ height : number
146+ viewBox : string
147+ rects : readonly BlockRect [ ]
148+ } ) {
240149 return (
241150 < svg
242151 width = { width }
@@ -245,7 +154,7 @@ const BlockGroup = memo(function BlockGroup({
245154 fill = 'none'
246155 xmlns = 'http://www.w3.org/2000/svg'
247156 className = 'h-auto w-full'
248- style = { { opacity : globalOpacity } }
157+ style = { { opacity : GLOBAL_OPACITY } }
249158 >
250159 { rects . map ( ( r , i ) => (
251160 < rect
@@ -257,114 +166,29 @@ const BlockGroup = memo(function BlockGroup({
257166 rx = { RX }
258167 fill = { r . fill }
259168 transform = { r . transform }
260- style = { {
261- opacity : isVisible ? r . opacity : 0 ,
262- transition : `opacity ${ isExiting ? EXIT_DURATION : ENTER_DURATION } s ease ${
263- isVisible ? i * ENTER_STAGGER : isExiting ? i * EXIT_STAGGER : 0
264- } s`,
265- } }
169+ opacity = { r . opacity }
266170 />
267171 ) ) }
268172 </ svg >
269173 )
270174} )
271175
272- function useGroupState ( ) : [ AnimState , ( s : AnimState ) => void ] {
273- return useState < AnimState > ( 'visible' )
274- }
275-
276- function useBlockCycle ( ) {
277- const [ topRight , setTopRight ] = useGroupState ( )
278- const [ left , setLeft ] = useGroupState ( )
279- const [ right , setRight ] = useGroupState ( )
280-
281- useEffect ( ( ) => {
282- if ( typeof window !== 'undefined' && ! window . matchMedia ( '(min-width: 1024px)' ) . matches ) return
283-
284- const cancelled = { current : false }
285- const wait = ( ms : number ) => new Promise < void > ( ( r ) => setTimeout ( r , ms ) )
286-
287- async function exit ( setter : ( s : AnimState ) => void , pos : Position , pauseAfter : number ) {
288- if ( cancelled . current ) return
289- setter ( 'exiting' )
290- await wait ( exitTime ( pos ) * 1000 )
291- if ( cancelled . current ) return
292- setter ( 'hidden' )
293- await wait ( pauseAfter )
294- }
295-
296- async function enter ( setter : ( s : AnimState ) => void , pos : Position , pauseAfter : number ) {
297- if ( cancelled . current ) return
298- setter ( 'visible' )
299- await wait ( enterTime ( pos ) * 1000 + pauseAfter )
300- }
301-
302- const run = async ( ) => {
303- await wait ( INITIAL_HOLD )
304-
305- while ( ! cancelled . current ) {
306- await exit ( setTopRight , 'topRight' , TRANSITION_PAUSE )
307- await exit ( setLeft , 'left' , HOLD_BETWEEN )
308- await enter ( setLeft , 'left' , TRANSITION_PAUSE )
309- await enter ( setTopRight , 'topRight' , TRANSITION_PAUSE )
310- await exit ( setRight , 'right' , HOLD_BETWEEN )
311- await enter ( setRight , 'right' , HOLD_BETWEEN )
312- }
313- }
314-
315- run ( )
316- return ( ) => {
317- cancelled . current = true
318- }
319- } , [ ] )
320-
321- return { topRight, left, right } as const
322- }
323-
324- /**
325- * Ambient animated block decorations for the docs layout.
326- * Adapts the landing page's colorful block patterns with slightly reduced
327- * opacity and the same staggered enter/exit animation cycle.
328- */
329176export function AnimatedBlocks ( ) {
330- const states = useBlockCycle ( )
331-
332177 return (
333178 < div
334179 className = 'pointer-events-none fixed inset-0 z-0 hidden overflow-hidden lg:block'
335180 aria-hidden = 'true'
336181 >
337182 < div className = 'absolute top-[93px] right-0 w-[calc(140px+10.76vw)] max-w-[295px]' >
338- < BlockGroup
339- width = { 295 }
340- height = { 34 }
341- viewBox = '0 0 295 34'
342- rects = { RECTS . topRight }
343- animState = { states . topRight }
344- globalOpacity = { 0.75 }
345- />
183+ < BlockGroup width = { 295 } height = { 34 } viewBox = '0 0 295 34' rects = { RECTS . topRight } />
346184 </ div >
347185
348- < div className = '-translate-y-1/2 absolute top-[50%] left-0 w-[calc(16px+1.25vw)] max-w-[34px] scale-x-[-1]' >
349- < BlockGroup
350- width = { 34 }
351- height = { 226 }
352- viewBox = '0 0 34 226.021'
353- rects = { RECTS . left }
354- animState = { states . left }
355- globalOpacity = { 0.75 }
356- />
186+ < div className = 'absolute bottom-0 -left-24 w-[calc(140px+10.76vw)] max-w-[295px] rotate-180' >
187+ < BlockGroup width = { 295 } height = { 34 } viewBox = '0 0 295 34' rects = { RECTS . bottomLeft } />
357188 </ div >
358189
359- < div className = '-translate-y-1/2 absolute top-[50%] right-0 w-[calc(16px+1.25vw)] max-w-[34px]' >
360- < BlockGroup
361- width = { 34 }
362- height = { 205 }
363- viewBox = '0 0 34 204.769'
364- rects = { RECTS . right }
365- animState = { states . right }
366- globalOpacity = { 0.75 }
367- />
190+ < div className = 'absolute right-0 -bottom-2 w-[calc(16px+1.25vw)] max-w-[34px]' >
191+ < BlockGroup width = { 34 } height = { 102 } viewBox = '0 0 34 102' rects = { RECTS . bottomRight } />
368192 </ div >
369193 </ div >
370194 )
0 commit comments