init: 初始化 AssetX 项目仓库
包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、 antdesign(管理后台)、landingpage(营销落地页)、 数据库 SQL 和配置文件。
This commit is contained in:
175
landingpage/components/TrustedBySection.tsx
Normal file
175
landingpage/components/TrustedBySection.tsx
Normal file
@@ -0,0 +1,175 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user