Files
assetx/landingpage/components/TrustedBySection.tsx

176 lines
5.9 KiB
TypeScript
Raw Normal View History

'use client';
import { useRef, useEffect, useState } from 'react';
import Image from 'next/image';
import { motion, Variants } from 'framer-motion';
import { useLanguage } from '@/contexts/LanguageContext';
import { useTheme } from '@/contexts/ThemeContext';
import ConsenSysLogo from './ConsenSysLogo';
const itemVariants: Variants = {
hidden: { y: '3rem', opacity: 0 },
visible: (i: number) => ({
y: 0,
opacity: 1,
transition: { duration: 0.7, ease: 'easeOut' as const, delay: i * 0.1 }
})
};
export default function TrustedBySection() {
const { t } = useLanguage();
const { theme } = useTheme();
const isDark = theme === 'dark';
const scrollRef = useRef<HTMLDivElement>(null);
const rafRef = useRef<number>(0);
const isPaused = useRef(false);
const [isMounted, setIsMounted] = useState(false);
// 确保组件挂载后才启动滚动
useEffect(() => {
setIsMounted(true);
}, []);
useEffect(() => {
if (!isMounted) return;
const el = scrollRef.current;
if (!el) return;
const speed = 1.2;
let lastTime = performance.now();
const tick = (currentTime: number) => {
const deltaTime = currentTime - lastTime;
lastTime = currentTime;
if (!isPaused.current && el) {
el.scrollLeft += speed * (deltaTime / 16); // 标准化到60fps
if (el.scrollLeft >= el.scrollWidth / 2) {
el.scrollLeft = 0;
}
}
rafRef.current = requestAnimationFrame(tick);
};
// 延迟启动,确保 DOM 完全渲染
const startTimer = setTimeout(() => {
rafRef.current = requestAnimationFrame(tick);
}, 100);
return () => {
clearTimeout(startTimer);
if (rafRef.current) {
cancelAnimationFrame(rafRef.current);
}
};
}, [isMounted]);
const logoClass = isDark
? 'brightness-0 invert opacity-50 group-hover:opacity-100'
: 'brightness-0 opacity-50 group-hover:opacity-100';
const partners = [
{ name: 'BlackRock', src: '/nav-ireland0.svg', width: 200, height: 40 },
{ name: 'Coinbase', src: '/coinbase-10.svg', width: 180, height: 40 },
{ name: 'Wintermute', src: '/wintermute0.svg', width: 247, height: 40 },
{ name: 'Circle', src: '/group0.svg', width: 156, height: 40 },
{ name: 'ConsenSys', src: '', width: 220, height: 50 },
];
const renderLogo = (partner: typeof partners[number], key: string) => (
<div
key={key}
className="relative flex items-center justify-center transition-all duration-300 group"
style={{ width: `${partner.width}px`, height: `${partner.height}px` }}
>
{partner.name === 'ConsenSys' ? (
<div className={`w-full h-full flex items-center justify-center transition-all duration-300 ${logoClass}`}>
<ConsenSysLogo width={partner.width} height={partner.height} />
</div>
) : (
<Image
src={partner.src}
alt={partner.name}
width={partner.width}
height={partner.height}
className={`w-full h-full object-contain transition-all duration-300 ${logoClass}`}
/>
)}
</div>
);
return (
<section
className="flex flex-col items-center justify-center w-full overflow-hidden border-b bg-bg-subtle border-border-normal py-10 gap-8"
style={{ paddingLeft: 'clamp(16px, 4vw, 80px)', paddingRight: 'clamp(16px, 4vw, 80px)' }}
>
<div className="text-lg font-medium text-center font-domine text-text-primary px-4">
{t('trusted.title')}
</div>
{/* ── 移动端:自动滚动 + 手动滑动 ── */}
<div
ref={scrollRef}
className="md:hidden w-full overflow-x-auto cursor-grab active:cursor-grabbing"
style={{
WebkitOverflowScrolling: 'touch',
scrollbarWidth: 'none',
msOverflowStyle: 'none',
}}
onTouchStart={() => { isPaused.current = true; }}
onTouchEnd={() => { isPaused.current = false; }}
onTouchCancel={() => { isPaused.current = false; }}
>
<div className="flex items-center gap-10" style={{ width: 'max-content', padding: '0 24px' }}>
{[...partners, ...partners].map((partner, i) => (
<div
key={`${partner.name}-${i}`}
className="flex-shrink-0 flex items-center justify-center h-[60px]"
style={{ width: `${partner.width}px` }}
>
{renderLogo(partner, `m-${partner.name}-${i}`)}
</div>
))}
</div>
</div>
{/* ── 桌面端:单行不换行 ── */}
<div className="hidden md:flex w-full max-w-[1440px] 2xl:max-w-[1760px] 3xl:max-w-[2200px] flex-row flex-nowrap items-center justify-between gap-x-6">
{partners.map((partner, index) => (
<motion.div
key={partner.name}
custom={index}
variants={itemVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true, amount: 0.3 }}
className="relative cursor-pointer group flex items-center justify-center"
style={{
maxWidth: `${partner.width}px`,
width: '100%',
height: `${partner.height}px`,
flexShrink: 1,
flexBasis: `${partner.width}px`,
minWidth: 60,
}}
>
{partner.name === 'ConsenSys' ? (
<div className={`w-full h-full flex items-center justify-center transition-all duration-300 ${logoClass}`}>
<ConsenSysLogo width={partner.width} height={partner.height} />
</div>
) : (
<Image
src={partner.src}
alt={partner.name}
width={partner.width}
height={partner.height}
className={`w-full h-full object-contain transition-all duration-300 ${logoClass}`}
/>
)}
</motion.div>
))}
</div>
</section>
);
}