|
|
@@ -1,3 +1,5 @@
|
|
|
+import { useCallback, useEffect, useRef, useState } from 'react'
|
|
|
+
|
|
|
import { useTranslation } from 'react-i18next'
|
|
|
|
|
|
import appWallBg from '@/assets/images/home/app-wall-bg.png'
|
|
|
@@ -26,8 +28,35 @@ const BRAND_LOGOS = [
|
|
|
{ src: logoTwitch, alt: 'Twitch', className: 'h-[19px] lg:h-[70px]' },
|
|
|
] as const
|
|
|
|
|
|
+const SCROLL_SPEED = 60
|
|
|
+
|
|
|
export function AppAccess() {
|
|
|
const { t } = useTranslation()
|
|
|
+ const trackRef = useRef<HTMLDivElement>(null)
|
|
|
+ const [scrollStyle, setScrollStyle] = useState<React.CSSProperties>({})
|
|
|
+
|
|
|
+ const measure = useCallback(() => {
|
|
|
+ const track = trackRef.current
|
|
|
+ if (!track) return
|
|
|
+ const firstSet = track.children[0] as HTMLElement | undefined
|
|
|
+ if (!firstSet) return
|
|
|
+ const w = firstSet.offsetWidth
|
|
|
+ if (w > 0) {
|
|
|
+ setScrollStyle({
|
|
|
+ '--scroll-distance': `-${w}px`,
|
|
|
+ animationDuration: `${w / SCROLL_SPEED}s`,
|
|
|
+ } as React.CSSProperties)
|
|
|
+ }
|
|
|
+ }, [])
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ const track = trackRef.current
|
|
|
+ if (!track) return
|
|
|
+ measure()
|
|
|
+ const ro = new ResizeObserver(measure)
|
|
|
+ ro.observe(track)
|
|
|
+ return () => ro.disconnect()
|
|
|
+ }, [measure])
|
|
|
|
|
|
return (
|
|
|
<section className="w-full py-10 lg:py-16">
|
|
|
@@ -65,7 +94,11 @@ export function AppAccess() {
|
|
|
{/* Scrolling brand logos */}
|
|
|
<div className="relative w-full max-w-[1280px] overflow-hidden">
|
|
|
<div className="absolute inset-0 z-10 bg-[linear-gradient(90deg,rgb(1_2_3)_0%,transparent_50%,rgb(1_2_3)_100%)]" />
|
|
|
- <div className="flex animate-scroll-left">
|
|
|
+ <div
|
|
|
+ ref={trackRef}
|
|
|
+ className="flex w-max animate-scroll-left"
|
|
|
+ style={scrollStyle}
|
|
|
+ >
|
|
|
{[0, 1].map((setIndex) => (
|
|
|
<div
|
|
|
key={setIndex}
|