Skip to content

Commit 4b7d9a7

Browse files
committed
updated animated blocks UI
1 parent 607b07b commit 4b7d9a7

File tree

1 file changed

+34
-210
lines changed

1 file changed

+34
-210
lines changed

apps/docs/components/ui/animated-blocks.tsx

Lines changed: 34 additions & 210 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,7 @@
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. */
63
const 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-
165
interface 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-
2815
const 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

229138
const 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-
*/
329176
export 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

Comments
 (0)