init: 初始化 AssetX 项目仓库

包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、
antdesign(管理后台)、landingpage(营销落地页)、
数据库 SQL 和配置文件。
This commit is contained in:
2026-03-27 11:26:43 +00:00
commit 2ee4553b71
634 changed files with 988255 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,181 @@
'use client';
import { useState, useEffect, useRef } from 'react';
import Image from 'next/image';
import { Link, Button } from '@heroui/react';
import { Github, Mail } from 'lucide-react';
import { motion } from 'framer-motion';
const XIcon = ({ color }: { color: string }) => (
<svg width="24" height="24" viewBox="0 0 24 24" fill={color}>
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.744l7.73-8.835L1.254 2.25H8.08l4.253 5.622zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
</svg>
);
const DiscordIcon = ({ color }: { color: string }) => (
<svg width="24" height="24" viewBox="0 0 24 24" fill={color}>
<path d="M20.317 4.492c-1.53-.69-3.17-1.2-4.885-1.49a.075.075 0 0 0-.079.036c-.21.369-.444.85-.608 1.23a18.566 18.566 0 0 0-5.487 0 12.36 12.36 0 0 0-.617-1.23A.077.077 0 0 0 8.562 3c-1.714.29-3.354.8-4.885 1.491a.07.07 0 0 0-.032.027C.533 9.093-.32 13.555.099 17.961a.08.08 0 0 0 .031.055 20.03 20.03 0 0 0 5.993 2.98.078.078 0 0 0 .084-.026 13.83 13.83 0 0 0 1.226-1.963.074.074 0 0 0-.041-.104 13.175 13.175 0 0 1-1.872-.878.075.075 0 0 1-.008-.125c.126-.093.252-.19.372-.287a.075.075 0 0 1 .078-.01c3.927 1.764 8.18 1.764 12.061 0a.075.075 0 0 1 .079.009c.12.098.245.195.372.288a.075.075 0 0 1-.006.125c-.598.344-1.22.635-1.873.877a.075.075 0 0 0-.041.105c.36.687.772 1.341 1.225 1.962a.077.077 0 0 0 .084.028 19.963 19.963 0 0 0 6.002-2.981.076.076 0 0 0 .032-.054c.5-5.094-.838-9.52-3.549-13.442a.06.06 0 0 0-.031-.028zM8.02 15.278c-1.182 0-2.157-1.069-2.157-2.38 0-1.312.956-2.38 2.157-2.38 1.21 0 2.176 1.077 2.157 2.38 0 1.312-.956 2.38-2.157 2.38zm7.975 0c-1.183 0-2.157-1.069-2.157-2.38 0-1.312.955-2.38 2.157-2.38 1.21 0 2.176 1.077 2.157 2.38 0 1.312-.946 2.38-2.157 2.38z"/>
</svg>
);
import { useLanguage } from '@/contexts/LanguageContext';
import { useTheme } from '@/contexts/ThemeContext';
export default function Footer() {
const { t } = useLanguage();
const { theme } = useTheme();
const isDark = theme === 'dark';
const [animate, setAnimate] = useState(false);
const footerRef = useRef<HTMLElement>(null);
useEffect(() => {
const currentRef = footerRef.current;
if (!currentRef) return;
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
setAnimate(true);
observer.disconnect();
}
},
{ threshold: 0.1 }
);
observer.observe(currentRef);
return () => observer.disconnect();
}, []);
const socialIcons = [
{ render: (c: string) => <XIcon color={c} />, alt: 'X' },
{ render: (c: string) => <Github size={24} color={c} />, alt: 'GitHub' },
{ render: (c: string) => <DiscordIcon color={c} />, alt: 'Discord' },
{ render: (c: string) => <Mail size={24} color={c} />, alt: 'Email' },
];
return (
<footer ref={footerRef} className="w-full flex flex-col items-center border-t bg-bg-base border-border-normal">
{/* Main Footer Content */}
<div className="w-full max-w-[1440px] 2xl:max-w-[1760px] 3xl:max-w-[2200px] px-4 md:px-6 2xl:px-10 3xl:px-16 py-10 md:py-20 flex flex-col md:flex-row gap-8">
{/* Left Section */}
<motion.div
className="flex flex-col gap-4 md:gap-6 md:w-[389.33px]"
initial={{ opacity: 0, x: -24 }}
animate={animate ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.6, ease: 'easeOut' }}
>
{/* Logo */}
<div className="flex items-center md:items-center">
<Image
src="/logo0.svg"
alt="AssetX Logo"
width={160}
height={40}
className="w-[160px] h-auto"
style={{ filter: isDark ? 'invert(1) brightness(1.2)' : 'none' }}
/>
</div>
{/* Address + Social Icons */}
<div className="flex flex-col gap-3 md:gap-6">
<div
className="text-text-tertiary text-left font-domine"
style={{ fontSize: '14px', lineHeight: '150%', fontWeight: 400 }}
>
G/F, Hong Kong Museum Of Art, 10 Salisbury
<br />
Rd, Tsim Sha Tsui, HongKong
</div>
<div className="flex flex-row gap-1 md:gap-2 items-center">
{socialIcons.map((icon, index) => (
<Button key={index} isIconOnly variant="light" className="min-w-10 w-10 h-10">
{icon.render(isDark ? '#9ca3af' : '#6b7280')}
</Button>
))}
</div>
</div>
</motion.div>
{/* Right Section */}
<div className="flex flex-col md:flex-row md:justify-between md:flex-1 gap-6 md:gap-0">
{/* Products + Resources */}
<div className="flex flex-row gap-4 md:contents">
<motion.div
className="flex flex-col gap-4 flex-1 md:flex-none md:w-[178.67px]"
initial={{ opacity: 0, y: 24 }}
animate={animate ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, ease: 'easeOut', delay: 0.15 }}
>
<h3 className="font-domine font-bold text-base text-text-primary" style={{ lineHeight: '150%' }}>
{t('footer.products')}
</h3>
<div className="flex flex-col gap-2">
{[1, 2, 3, 4, 5].map((num) => (
<Link key={num} href="#" className="font-domine text-sm text-text-tertiary hover:text-text-primary" style={{ lineHeight: '150%' }}>
{t(`footer.product${num}`)}
</Link>
))}
</div>
</motion.div>
<motion.div
className="flex flex-col gap-4 flex-1 md:flex-none md:w-[178.67px]"
initial={{ opacity: 0, y: 24 }}
animate={animate ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, ease: 'easeOut', delay: 0.25 }}
>
<h3 className="font-domine font-bold text-base text-text-primary" style={{ lineHeight: '150%' }}>
{t('footer.resources')}
</h3>
<div className="flex flex-col gap-2">
{[1, 2, 3, 4, 5].map((num) => (
<Link key={num} href="#" className="font-domine text-sm text-text-tertiary hover:text-text-primary" style={{ lineHeight: '150%' }}>
{t(`footer.resource${num}`)}
</Link>
))}
</div>
</motion.div>
</div>
{/* Company */}
<motion.div
className="flex flex-col gap-4 md:w-[178.67px]"
initial={{ opacity: 0, y: 24 }}
animate={animate ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, ease: 'easeOut', delay: 0.35 }}
>
<h3 className="font-domine font-bold text-base text-text-primary" style={{ lineHeight: '150%' }}>
{t('footer.company')}
</h3>
<div className="flex flex-col gap-2">
{[1, 2, 3, 4].map((num) => (
<Link key={num} href="#" className="font-domine text-sm text-text-tertiary hover:text-text-primary" style={{ lineHeight: '150%' }}>
{t(`footer.company${num}`)}
</Link>
))}
</div>
</motion.div>
</div>
</div>
{/* Bottom Section */}
<motion.div
className="w-full max-w-[1440px] 2xl:max-w-[1760px] 3xl:max-w-[2200px] border-t border-border-subtle px-4 md:px-6 2xl:px-10 3xl:px-16 py-6 md:py-8 flex flex-col md:flex-row items-center md:items-center justify-between gap-3 md:gap-0"
initial={{ opacity: 0 }}
animate={animate ? { opacity: 1 } : {}}
transition={{ duration: 0.6, ease: 'easeOut', delay: 0.5 }}
>
<div className="text-text-tertiary font-domine text-sm text-center md:text-left" style={{ lineHeight: '150%' }}>
{t('footer.copyright')}
</div>
<div className="flex flex-row gap-6">
<Link href="#" className={`font-domine text-sm ${isDark ? 'text-[#9ca1af] hover:text-[#fafafa]' : 'text-[#9ca1af] hover:text-[#111827]'}`} style={{ lineHeight: '150%' }}>
{t('footer.privacy')}
</Link>
<Link href="#" className={`font-domine text-sm ${isDark ? 'text-[#9ca1af] hover:text-[#fafafa]' : 'text-[#9ca1af] hover:text-[#111827]'}`} style={{ lineHeight: '150%' }}>
{t('footer.terms')}
</Link>
</div>
</motion.div>
</footer>
);
}

View File

@@ -0,0 +1,40 @@
'use client';
import { motion } from 'framer-motion';
import { ArrowUpRight } from 'lucide-react';
import { Button } from '@heroui/react';
import { useLanguage } from '@/contexts/LanguageContext';
export default function HeroButtons() {
const { t } = useLanguage();
return (
<motion.div
className="flex flex-col sm:flex-row gap-4 items-stretch sm:items-start justify-start flex-shrink-0 relative w-full sm:w-auto"
initial={{ opacity: 0, scale: 0.92, y: 16 }}
whileInView={{ opacity: 1, scale: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.7, ease: 'easeOut', delay: 1.0 }}
>
{/* Start Investing Button */}
<Button
size="lg"
className="bg-white text-special-black font-bold text-lg h-[60px] px-8 transition-transform duration-300 ease-out hover:scale-105 w-full sm:w-auto"
endContent={<ArrowUpRight size={20} className="flex-shrink-0" />}
onPress={() => window.open('http://152.69.205.186:3010/market', '_blank')}
>
{t('hero.startInvesting')}
</Button>
{/* Read the Whitepaper Button */}
<Button
size="lg"
variant="bordered"
className="border-white/20 bg-white/10 text-white font-bold text-lg h-[60px] px-8 backdrop-blur-[30px] hover:bg-white/20 hover:border-white/40 w-full sm:w-auto"
>
{t('hero.readWhitepaper')}
</Button>
</motion.div>
);
}

View File

@@ -0,0 +1,71 @@
'use client';
import { motion } from 'framer-motion';
import HeroTitle from './HeroTitle';
import HeroButtons from './HeroButtons';
import { useLanguage } from '@/contexts/LanguageContext';
export default function HeroSection() {
const { t } = useLanguage();
// StatsSection 桌面端高度约 182px
const statsHeight = 182;
return (
<section
className="relative w-full min-h-[calc(100vh-var(--navbar-height,72px))] md:min-h-[calc(100vh-var(--navbar-height,72px)-182px)]"
style={{ overflow: 'hidden' }}
>
{/* Video background */}
<video
autoPlay
loop
muted
playsInline
className="absolute inset-0 w-full h-full object-cover"
style={{
zIndex: 0,
objectFit: 'cover',
objectPosition: 'center center',
minWidth: '100%',
minHeight: '100%',
transform: 'scale(1.3)'
}}
>
<source src="https://pub-0b813d5d97b84a06a71fd3d4283fce59.r2.dev/hero-background.mp4" type="video/mp4" />
</video>
<div
className="relative flex flex-col items-start justify-center min-h-[calc(100vh-var(--navbar-height,72px))] md:min-h-[calc(100vh-var(--navbar-height,72px)-182px)] px-6 py-20 md:px-[85px] md:py-16"
style={{
background: 'linear-gradient(to left, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0.55))',
zIndex: 1
}}
>
<div className="flex flex-col gap-12 md:gap-[88px] w-full max-w-[926px] 2xl:max-w-[1200px] 3xl:max-w-[1500px]">
<div className="flex flex-col gap-5">
<HeroTitle />
<motion.p
className="text-base sm:text-xl max-w-[640px] 2xl:max-w-[800px] 3xl:max-w-[1000px]"
style={{
color: 'rgba(255,255,255,0.65)',
fontFamily: 'Inter, sans-serif',
lineHeight: '1.625',
fontWeight: 400,
minHeight: 'calc(3 * 1.625em)',
}}
initial={{ opacity: 0, y: 16, filter: 'blur(8px)' }}
whileInView={{ opacity: 1, y: 0, filter: 'blur(0px)' }}
viewport={{ once: true }}
transition={{ duration: 0.9, ease: 'easeOut', delay: 0.75 }}
>
{t('hero.description')}
</motion.p>
</div>
<HeroButtons />
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,40 @@
'use client';
import { motion } from 'framer-motion';
import { useLanguage } from '@/contexts/LanguageContext';
export default function HeroTitle() {
const { t } = useLanguage();
const titleStyle = {
fontSize: 'clamp(48px, 6.5vw, 140px)',
lineHeight: '100%',
letterSpacing: '-0.03em',
};
return (
<div className="flex flex-col gap-4 items-start justify-start self-stretch flex-shrink-0 relative overflow-hidden select-none">
<motion.div
className="text-special-white text-left relative self-stretch font-domine md:whitespace-nowrap"
style={{ ...titleStyle, fontWeight: 400 }}
initial={{ opacity: 0, x: -60, filter: 'blur(12px)' }}
whileInView={{ opacity: 1, x: 0, filter: 'blur(0px)' }}
viewport={{ once: true }}
transition={{ duration: 0.9, ease: 'easeOut', delay: 0.2 }}
>
{t('hero.title1')}
</motion.div>
<motion.div
className="text-special-white text-left relative w-full font-domine md:whitespace-nowrap"
style={{ ...titleStyle, fontWeight: 700 }}
initial={{ opacity: 0, x: 60, filter: 'blur(12px)' }}
whileInView={{ opacity: 1, x: 0, filter: 'blur(0px)' }}
viewport={{ once: true }}
transition={{ duration: 0.9, ease: 'easeOut', delay: 0.4 }}
>
{t('hero.title2')}
</motion.div>
</div>
);
}

View File

@@ -0,0 +1,485 @@
'use client';
import { useState, useEffect, useRef } from 'react';
import Image from 'next/image';
import { motion, useInView } from 'framer-motion';
import { Card, CardBody, Chip } from '@heroui/react';
import { Calculator, TrendingUp } from 'lucide-react';
import { useLanguage } from '@/contexts/LanguageContext';
import { useTheme } from '@/contexts/ThemeContext';
import { useCountUp } from '@/hooks/useCountUp';
export default function HowItWorksSection() {
const { t } = useLanguage();
const { theme } = useTheme();
const [activeStep, setActiveStep] = useState(1);
const sectionRef = useRef<HTMLElement>(null);
const isInView = useInView(sectionRef, { once: true, amount: 0.3 });
const investAmount = useCountUp(100000, 1500, 0.85);
const earnAmount = useCountUp(5150, 1500, 0.85);
// Step 3 inner grid 自适应缩放
const gridRef = useRef<HTMLDivElement>(null);
const [gridScale, setGridScale] = useState(1);
useEffect(() => {
const el = gridRef.current;
if (!el) return;
const observer = new ResizeObserver(([entry]) => {
setGridScale(Math.min(1, entry.contentRect.width / 466));
});
observer.observe(el);
return () => observer.disconnect();
}, []);
const isDark = theme === 'dark';
const steps = [
{
number: 1,
title: t('how.step1.title'),
description: t('how.step1.desc'),
hasLine: true
},
{
number: 2,
title: t('how.step2.title'),
description: t('how.step2.desc'),
hasLine: true
},
{
number: 3,
title: t('how.step3.title'),
description: t('how.step3.desc'),
hasLine: false
}
];
return (
<section
ref={sectionRef}
className="flex flex-col items-center justify-center flex-shrink-0 w-full relative border-y bg-bg-subtle border-border-normal overflow-x-clip py-10 md:py-20 2xl:py-28 3xl:py-36"
>
<div className="flex flex-col md:flex-row items-start justify-between flex-shrink-0 relative w-full max-w-[1440px] 2xl:max-w-[1760px] 3xl:max-w-[2200px] px-4 md:px-10 2xl:px-16 3xl:px-24 gap-10 md:gap-0">
{/* 左侧:标题 + 步骤 */}
<div className="flex flex-col gap-8 md:gap-10 items-start justify-start flex-shrink-0 relative w-full md:w-[520px] 2xl:w-[640px] 3xl:w-[800px]">
<motion.div
className="flex flex-col items-start justify-start flex-shrink-0 relative"
initial={{ y: '-1.5rem', opacity: 0 }}
animate={isInView ? { y: 0, opacity: 1 } : { y: '-1.5rem', opacity: 0 }}
transition={{ duration: 0.5, ease: 'easeOut' }}
>
<h2
className="text-left relative font-domine font-bold text-text-primary"
style={{ fontSize: 'clamp(30px, 3.5vw, 80px)', lineHeight: '120%', letterSpacing: '-0.01em' }}
>
{t('how.title')}
</h2>
</motion.div>
<div className="flex flex-col gap-6 items-start justify-start flex-shrink-0 relative w-full">
{steps.map((step, index) => {
const isActive = activeStep === step.number;
return (
<motion.div
key={step.number}
onClick={() => setActiveStep(step.number)}
className="flex flex-row gap-6 items-start justify-start flex-shrink-0 relative cursor-pointer"
initial={{ x: '-3rem', opacity: 0 }}
animate={isInView ? { x: 0, opacity: 1 } : { x: '-3rem', opacity: 0 }}
whileHover={{ opacity: 0.8 }}
transition={{ duration: 0.3, ease: 'easeOut', delay: index * 0.15 }}
>
<div className="pt-2 flex flex-col items-center justify-start self-stretch flex-shrink-0 relative">
{isActive ? (
<div
className={`rounded-full flex items-center justify-center flex-shrink-0 w-8 h-[21.63px] transition-all duration-300 ${
isDark ? 'bg-white' : 'bg-[#111827]'
}`}
style={{ padding: '0.31px 0px 1.32px 0px' }}
>
<span className={`text-center text-sm font-bold font-domine transition-all duration-300 ${
isDark ? 'text-[#0a0a0a]' : 'text-[#fcfcfd]'
}`}>
{step.number}
</span>
</div>
) : (
<div className={`rounded-full border-2 flex items-center justify-center flex-shrink-0 w-8 h-[24.5px] transition-all duration-300 ${
isDark ? 'bg-[#0a0a0a] border-[#3f3f46]' : 'bg-[#f9fafb] border-[#d1d5db]'
}`}>
<span className={`text-center text-sm font-bold font-domine transition-all duration-300 ${
isDark ? 'text-[#71717a]' : 'text-[#9ca1af]'
}`}>
{step.number}
</span>
</div>
)}
{step.hasLine && (
<div className="pt-6 flex flex-col items-start justify-center flex-1 w-[2px] relative">
<div className="flex-1 w-[2px] bg-border-normal" />
</div>
)}
</div>
<div
className="flex flex-col gap-2 items-start justify-start flex-1 relative"
style={{ paddingBottom: step.hasLine ? '32px' : '0px' }}
>
<h3
className="text-left font-semibold font-domine transition-all duration-300"
style={{
fontSize: 'clamp(20px, 2vw, 36px)',
lineHeight: '130%',
letterSpacing: '-0.005em',
color: isActive
? (isDark ? '#fafafa' : '#111827')
: (isDark ? '#52525b' : '#6b7280')
}}
>
{step.title}
</h3>
<p className="text-left text-sm md:text-base font-domine text-text-tertiary" style={{ lineHeight: '150%' }}>
{step.description}
</p>
</div>
</motion.div>
);
})}
</div>
</div>
{/* 右侧:卡片区 */}
<motion.div
className="calculator-card-container flex flex-col items-start justify-start flex-shrink-0 w-full md:w-[558px] 2xl:w-[680px] 3xl:w-[720px] relative h-[380px] md:h-[439px] 2xl:h-[520px] 3xl:h-[540px] md:mr-16 2xl:mr-24"
initial={{ x: '3rem', opacity: 0 }}
animate={isInView ? { x: 0, opacity: 1 } : { x: '3rem', opacity: 0 }}
transition={{ duration: 0.5, ease: 'easeOut', delay: 0.2 }}
>
<>
{/* Step 1 背景装饰卡片 — 仅桌面端显示 */}
<Card
className={`h-[162px] absolute z-0 transition-all duration-700 ease-out ${
isInView && activeStep === 1 ? 'opacity-100' : 'opacity-0'
} ${
isDark ? 'bg-white border-[#e5e7eb]' : 'bg-[#111827] border-transparent'
}`}
shadow="lg"
style={{
left: '205.43px',
top: '15.96px',
transformOrigin: '0 0',
transform: isInView && activeStep === 1
? 'rotate(6.535deg) scale(1, 1) translateX(0)'
: 'rotate(6.535deg) scale(1, 1) translateX(3rem)',
transitionDelay: activeStep === 1 ? '200ms' : '0ms',
pointerEvents: activeStep === 1 ? 'auto' : 'none'
}}
>
<div className="flex flex-row items-center gap-4" style={{ padding: '25px 25px 1px 25px' }}>
<Image src="/usd-coin-usdc-logo-10.svg" alt="USDC" width={64} height={64} className="flex-shrink-0" />
<div className="flex-shrink-0 w-[66px] h-[66px] relative">
<div className="absolute inset-0 rounded-full bg-gradient-to-br from-green-400 to-emerald-500" />
<Image src="/image-220.png" alt="Token" width={66} height={66} className="absolute inset-0 rounded-full" />
</div>
</div>
</Card>
{/* Step 1 主卡片 */}
<Card
className={`absolute left-0 top-0 md:top-[99px] w-full z-10 transition-all duration-700 ease-out ${
isInView && activeStep === 1 ? 'opacity-100' : 'opacity-0'
} ${
isDark ? 'bg-[#18181b]/85 border-[#27272a]' : 'bg-white/85 border-[#e5e7eb]'
}`}
shadow="lg"
style={{
backdropFilter: 'blur(12px)',
transform: isInView && activeStep === 1
? 'rotate(-3deg) translateX(0)'
: 'rotate(-3deg) translateX(3rem)',
transformOrigin: 'center center',
transitionDelay: activeStep === 1 ? '200ms' : '0ms',
pointerEvents: activeStep === 1 ? 'auto' : 'none'
}}
>
<CardBody className="p-8">
<div className="flex flex-row items-center justify-between mb-6">
<div className="flex flex-row gap-3 items-center">
<div className={`rounded-xl w-10 h-10 flex items-center justify-center ${
isDark ? 'bg-white' : 'bg-[#111827]'
}`}>
<Calculator
size={24}
color={isDark ? '#000000' : '#ffffff'}
/>
</div>
<span
className={`font-domine text-base font-medium ${
isDark ? 'text-[#fafafa]' : 'text-[#111827]'
}`}
>
{t('how.simulator.title')}
</span>
</div>
<Chip className="bg-green-50 text-green-600 font-bold" size="sm">
+5.2% APY
</Chip>
</div>
<div className="mb-4">
<div className="flex flex-col gap-2">
<span
className={`font-domine text-sm ${
isDark ? 'text-[#71717a]' : 'text-[#9ca1af]'
}`}
>
{t('how.simulator.invest')}
</span>
<span
ref={investAmount.elementRef}
className={`font-domine text-3xl font-bold ${
isDark ? 'text-[#fafafa]' : 'text-[#111827]'
}`}
style={{
lineHeight: '120%',
letterSpacing: '-0.01em'
}}
>
${investAmount.count.toLocaleString()}
</span>
</div>
<div className={`mt-4 h-2 rounded-full relative ${
isDark ? 'bg-[#27272a]' : 'bg-[#e5e7eb]'
}`}>
<div className={`absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-4 h-4 rounded-full ${
isDark ? 'bg-white' : 'bg-[#111827]'
}`} />
</div>
</div>
<div className={`mt-6 pt-6 border-t ${
isDark ? 'border-[#27272a]' : 'border-[#e5e7eb]'
}`}>
<span
className={`font-domine text-sm block mb-2 ${
isDark ? 'text-[#71717a]' : 'text-[#9ca1af]'
}`}
>
{t('how.simulator.earn')}
</span>
<div className="flex flex-row gap-2 items-center">
<TrendingUp size={24} color="#059669" />
<span
ref={earnAmount.elementRef}
className="text-green-600 font-domine text-3xl font-bold"
style={{
lineHeight: '120%',
letterSpacing: '-0.01em'
}}
>
+${earnAmount.count.toLocaleString()}
</span>
</div>
</div>
</CardBody>
</Card>
</>
<Card
className={`absolute left-0 top-0 md:top-[99px] w-full transition-all duration-700 ease-out ${
isInView && activeStep === 2 ? 'opacity-100' : 'opacity-0'
} ${
isDark ? 'bg-[#18181b]/85 border-[#27272a]' : 'bg-white/85 border-[#f3f4f6]'
}`}
shadow="lg"
style={{
backdropFilter: 'blur(12px)',
transform: isInView && activeStep === 2
? 'rotate(3deg) translateX(0)'
: 'rotate(3deg) translateX(3rem)',
transformOrigin: '0 0',
transitionDelay: activeStep === 2 ? '200ms' : '0ms',
pointerEvents: activeStep === 2 ? 'auto' : 'none'
}}
>
<CardBody className="p-6 flex flex-col gap-6">
<div
className={`border-b pb-4 flex flex-row items-center justify-between w-full ${
isDark ? 'border-[#27272a]' : 'border-[#f3f4f6]'
}`}
>
<span className={`font-domine text-lg font-bold ${
isDark ? 'text-[#fafafa]' : 'text-[#000000]'
}`}>
Fund Market
</span>
<span className="text-[#059669] font-domine text-base font-bold">
Connect Wallet
</span>
</div>
<div className="flex flex-col gap-4 w-full">
<div className={`rounded-xl border p-4 flex flex-row gap-4 items-center h-24 ${
isDark ? 'bg-[#27272a] border-[#3f3f46]' : 'bg-[#f9fafb] border-[#f3f4f6]'
}`}>
<div className={`rounded-full w-12 h-12 flex items-center justify-center flex-shrink-0 ${
isDark ? 'bg-white' : 'bg-[#000000]'
}`}>
<span className={`font-domine text-base font-bold ${
isDark ? 'text-[#000000]' : 'text-white'
}`}>O</span>
</div>
<div className="flex flex-col gap-2 flex-1">
<div className={`rounded h-4 w-24 ${
isDark ? 'bg-[#3f3f46]' : 'bg-[#e5e7eb]'
}`} />
<div className={`rounded h-3 w-16 ${
isDark ? 'bg-[#52525b]' : 'bg-[#f3f4f6]'
}`} />
</div>
<div className="flex flex-col gap-2 items-end flex-shrink-0">
<div className={`rounded h-4 w-20 ${
isDark ? 'bg-[#3f3f46]' : 'bg-[#e5e7eb]'
}`} />
<span className="text-[#059669] font-domine text-base font-bold">
APY 30.2%
</span>
</div>
</div>
<div className={`rounded-xl border p-4 flex flex-row gap-4 items-center h-24 opacity-60 ${
isDark ? 'bg-[#27272a] border-[#3f3f46]' : 'bg-[#f9fafb] border-[#f3f4f6]'
}`}>
<div className={`rounded-full w-12 h-12 flex-shrink-0 ${
isDark ? 'bg-[#52525b]' : 'bg-[#d1d5db]'
}`} />
<div className="flex flex-col gap-2 flex-1">
<div className={`rounded h-4 w-32 ${
isDark ? 'bg-[#3f3f46]' : 'bg-[#e5e7eb]'
}`} />
<div className={`rounded h-3 w-20 ${
isDark ? 'bg-[#52525b]' : 'bg-[#f3f4f6]'
}`} />
</div>
<div className="flex flex-col gap-2 items-end flex-shrink-0">
<div className={`rounded h-4 w-20 ${
isDark ? 'bg-[#3f3f46]' : 'bg-[#e5e7eb]'
}`} />
<span className="text-[#059669] font-domine text-base font-bold">
APY 15.2%
</span>
</div>
</div>
</div>
</CardBody>
</Card>
<Card
className={`absolute left-0 top-0 md:top-[99px] w-full transition-all duration-700 ease-out ${
isInView && activeStep === 3 ? 'opacity-100' : 'opacity-0'
} ${
isDark ? 'bg-[#18181b]/85 border-[#27272a]' : 'bg-white/85 border-[#f3f4f6]'
}`}
shadow="lg"
style={{
backdropFilter: 'blur(12px)',
transform: isInView && activeStep === 3
? 'rotate(3deg) translateX(0)'
: 'rotate(3deg) translateX(3rem)',
transformOrigin: '0 0',
transitionDelay: activeStep === 3 ? '200ms' : '0ms',
overflow: 'hidden',
pointerEvents: activeStep === 3 ? 'auto' : 'none'
}}
>
<CardBody className="p-6 flex flex-col gap-6">
<div className={`border-b pb-[17px] flex flex-row items-center justify-between ${
isDark ? 'border-[#27272a]' : 'border-[#f3f4f6]'
}`} style={{ height: '45px' }}>
<span className={`font-domine text-lg font-bold ${
isDark ? 'text-[#fafafa]' : 'text-[#000000]'
}`}>
Defi
</span>
<span className="text-[#059669] font-domine text-base font-bold">
+5.2% APY
</span>
</div>
{/* Cards Grid Container — 按比例缩放 */}
<div
ref={gridRef}
className="w-full overflow-hidden"
style={{ height: `${Math.round(195 * gridScale)}px` }}
>
<div
className="relative"
style={{
width: '466px',
height: '195px',
transform: `scale(${gridScale}) rotate(-3deg)`,
transformOrigin: '0 0',
overflow: 'hidden'
}}
>
{[
{ left: '8.58px', top: '0.46px', label: '+10% APY', button: 'Boost' },
{ left: '160.18px', top: '11.66px', label: '+10% APY', button: 'Boost' },
{ left: '312.18px', top: '19.66px', label: '+10% APY', button: 'Boost' },
{ left: '160.18px', top: '11.66px', label: 'Get USDC', button: 'SWAP' },
{ left: '312.18px', top: '19.66px', label: '10% APY', button: 'LP' },
].map((item, i) => (
<div
key={i}
className="absolute"
style={{
width: '135.53px',
height: '165.15px',
left: item.left,
top: item.top,
transform: 'rotate(3deg)',
transformOrigin: '0 0'
}}
>
<div className="bg-bg-elevated rounded-[12px] border border-border-subtle p-4 flex flex-col gap-4 items-center absolute" style={{ width: '134.91px', height: '165.18px', left: '0px', top: '0px' }}>
<div className="bg-text-primary rounded-full w-12 h-12 flex items-center justify-center flex-shrink-0">
<span className="text-text-inverse font-domine text-[16px] font-bold leading-6">O</span>
</div>
<span
className="text-text-primary font-domine text-[16px] font-bold text-center"
style={{ width: '124px', height: '17px', transform: 'rotate(-3deg)', transformOrigin: '0 0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
>
{item.label}
</span>
<div
className="bg-text-primary rounded-lg px-4 py-2 flex items-center justify-center"
style={{ transform: 'rotate(-3deg)', transformOrigin: '0 0' }}
>
<span className="text-text-inverse font-domine text-[12px] font-bold leading-4">
{item.button}
</span>
</div>
</div>
</div>
))}
</div>
</div>
</CardBody>
</Card>
</motion.div>
</div>
</section>
);
}

View File

@@ -0,0 +1,726 @@
'use client';
import { useState, useEffect, useRef } from 'react';
import Image from 'next/image';
import { motion, AnimatePresence } from 'framer-motion';
import {
Navbar as HeroNavbar,
NavbarBrand,
NavbarContent,
NavbarItem,
Button,
Dropdown,
DropdownTrigger,
DropdownMenu,
DropdownItem
} from '@heroui/react';
import { ArrowRight, ChevronDown, Menu, X, Sun, Moon, Globe, Layers, Rocket, ArrowLeftRight, Coins, LayoutDashboard, Radio, BookOpen, ShieldCheck, GraduationCap, MessageCircle, Headphones, Users, Briefcase, Mail, Newspaper, Rainbow } from 'lucide-react';
import ProductMenu from './ProductMenu';
import ResourceMenu from './ResourceMenu';
import { useLanguage } from '@/contexts/LanguageContext';
import { useTheme } from '@/contexts/ThemeContext';
export default function Navbar() {
const [scrolled, setScrolled] = useState(false);
const { language, setLanguage, t } = useLanguage();
const { theme, toggleTheme } = useTheme();
const [showProductMenu, setShowProductMenu] = useState(false);
const [showResourceMenu, setShowResourceMenu] = useState(false);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [mobileProductOpen, setMobileProductOpen] = useState(false);
const [mobileResourceOpen, setMobileResourceOpen] = useState(false);
const [navBottom, setNavBottom] = useState(64);
const navRef = useRef<HTMLElement | null>(null);
useEffect(() => {
const handleScroll = () => {
setScrolled(window.scrollY > 50);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
useEffect(() => {
if (mobileMenuOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
return () => { document.body.style.overflow = ''; };
}, [mobileMenuOpen]);
useEffect(() => {
if (!showProductMenu && !showResourceMenu) return;
const handleClickOutside = (e: MouseEvent) => {
const target = e.target as Element;
if (showProductMenu && !target.closest('.product-menu-container')) {
setShowProductMenu(false);
}
if (showResourceMenu && !target.closest('.resource-menu-container')) {
setShowResourceMenu(false);
}
};
// 延迟注册,避免当前 click 事件冒泡立即触发
const timer = requestAnimationFrame(() => {
document.addEventListener('click', handleClickOutside);
});
return () => {
cancelAnimationFrame(timer);
document.removeEventListener('click', handleClickOutside);
};
}, [showProductMenu, showResourceMenu]);
useEffect(() => {
const handleResize = () => {
if (window.innerWidth >= 1024) {
setMobileMenuOpen(false);
setMobileProductOpen(false);
setMobileResourceOpen(false);
}
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
useEffect(() => {
const measure = () => {
const el = navRef.current ?? (document.querySelector('header, nav') as HTMLElement);
if (el) {
navRef.current = el;
const h = Math.ceil(el.getBoundingClientRect().height);
setNavBottom(h);
document.documentElement.style.setProperty('--navbar-height', `${h}px`);
}
};
measure();
const ro = new ResizeObserver(measure);
const el = document.querySelector('header, nav') as HTMLElement;
if (el) ro.observe(el);
return () => ro.disconnect();
}, []);
const isDark = theme === 'dark';
return (
<>
<HeroNavbar
maxWidth="full"
position="static"
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
zIndex: 50,
backgroundColor: isDark
? scrolled ? 'rgba(10, 10, 10, 0.7)' : 'rgba(10, 10, 10, 0.9)'
: scrolled ? 'rgba(255, 255, 255, 0.7)' : 'rgba(255, 255, 255, 0.9)',
backdropFilter: 'blur(50px)',
borderTop: 'none',
borderLeft: 'none',
borderRight: 'none',
borderBottom: isDark
? scrolled ? '1px solid #27272a' : '1px solid #18181b'
: '1px solid #f3f4f6',
boxShadow: 'none',
transition: 'background-color 0.3s ease-out, border-color 0.3s ease-out'
}}
classNames={{
wrapper: "px-4 lg:px-10 2xl:px-20 3xl:px-32 py-5"
}}
>
{/* Logo */}
<NavbarBrand>
<motion.div
initial={{ y: '-3rem', opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 1, ease: 'easeOut' }}
>
<Image
src="/logo0.svg"
alt="Logo"
width={160}
height={40}
priority
style={{
filter: isDark ? 'invert(1) brightness(1.2)' : 'none',
transition: 'filter 0.3s ease-out'
}}
/>
</motion.div>
</NavbarBrand>
{/* Center Menu */}
<NavbarContent className="hidden lg:flex gap-6" justify="center">
{/* Product */}
<NavbarItem className="product-menu-container">
<Button
variant="light"
className="font-bold text-sm select-none bg-transparent"
style={{ color: showProductMenu ? '#059669' : (isDark ? '#a1a1aa' : '#4b5563') }}
endContent={
<ChevronDown
size={14}
color={showProductMenu ? '#059669' : (isDark ? '#a1a1aa' : '#4b5563')}
style={{
transform: showProductMenu ? 'rotate(180deg)' : 'rotate(0deg)',
transition: 'transform 0.2s ease'
}}
/>
}
onPress={() => {
setShowProductMenu(!showProductMenu);
setShowResourceMenu(false);
}}
>
{t('nav.product')}
</Button>
</NavbarItem>
{/* Ecosystem */}
<NavbarItem>
<Button
variant="light"
className="font-bold text-sm select-none bg-transparent"
style={{ color: isDark ? '#a1a1aa' : '#4b5563' }}
>
{t('nav.ecosystem')}
</Button>
</NavbarItem>
{/* Resource */}
<NavbarItem className="resource-menu-container">
<Button
variant="light"
className="font-bold text-sm select-none bg-transparent"
style={{ color: showResourceMenu ? '#059669' : (isDark ? '#a1a1aa' : '#4b5563') }}
endContent={
<ChevronDown
size={14}
color={showResourceMenu ? '#059669' : (isDark ? '#a1a1aa' : '#4b5563')}
style={{
transform: showResourceMenu ? 'rotate(180deg)' : 'rotate(0deg)',
transition: 'transform 0.2s ease'
}}
/>
}
onPress={() => {
setShowResourceMenu(!showResourceMenu);
setShowProductMenu(false);
}}
>
{t('nav.resource')}
</Button>
</NavbarItem>
</NavbarContent>
{/* Right Content */}
<NavbarContent justify="end" className="gap-2 lg:gap-4">
{/* Launch App Button */}
<NavbarItem>
<motion.div
initial={{ y: '-3rem', opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 1, ease: 'easeOut', delay: 0.2 }}
>
<Button
className="font-bold text-sm hover:scale-105 transition-transform duration-200 ease-out"
style={{
backgroundColor: isDark ? '#ffffff' : '#111827',
color: isDark ? '#0a0a0a' : '#fafafa',
borderRadius: '9999px',
padding: '10px 20px',
transition: 'background-color 0.3s ease-out, color 0.3s ease-out, transform 0.2s ease-out',
boxShadow: '0px 4px 6px -4px rgba(229,231,235,1), 0px 10px 15px -3px rgba(229,231,235,1)'
}}
endContent={
<ArrowRight size={16} color={isDark ? '#0a0a0a' : '#ffffff'} />
}
onPress={() => window.open('http://152.69.205.186:3010/market', '_blank')}
>
{t('nav.launchApp')}
</Button>
</motion.div>
</NavbarItem>
{/* Theme Toggle - desktop only */}
<NavbarItem className="hidden lg:flex">
<Button
isIconOnly
variant="light"
onPress={toggleTheme}
className="transition-colors"
>
<div className="relative w-5 h-5">
<AnimatePresence mode="wait" initial={false}>
{isDark ? (
<motion.div key="moon" className="absolute inset-0 flex items-center justify-center"
initial={{ opacity: 0, rotate: -90, scale: 0.5 }}
animate={{ opacity: 1, rotate: 0, scale: 1 }}
exit={{ opacity: 0, rotate: 90, scale: 0.5 }}
transition={{ duration: 0.3 }}
>
<Moon size={18} color="#a1a1aa" />
</motion.div>
) : (
<motion.div key="sun" className="absolute inset-0 flex items-center justify-center"
initial={{ opacity: 0, rotate: 90, scale: 0.5 }}
animate={{ opacity: 1, rotate: 0, scale: 1 }}
exit={{ opacity: 0, rotate: -90, scale: 0.5 }}
transition={{ duration: 0.3 }}
>
<Sun size={18} color="#4B5563" />
</motion.div>
)}
</AnimatePresence>
</div>
</Button>
</NavbarItem>
{/* Language Selector - desktop only */}
<NavbarItem className="hidden lg:flex">
<Dropdown
classNames={{
content: 'bg-bg-surface border-none shadow-lg min-w-[120px] w-[120px]'
}}
>
<DropdownTrigger>
<Button
variant="light"
className="font-bold text-sm text-text-secondary"
startContent={<Globe size={16} color={isDark ? '#a1a1aa' : '#4B5563'} />}
endContent={<ChevronDown size={12} color={isDark ? '#a1a1aa' : '#4B5563'} />}
>
{language === 'zh' ? '中文' : 'EN'}
</Button>
</DropdownTrigger>
<DropdownMenu
aria-label="Language selection"
onAction={(key) => setLanguage(key as 'zh' | 'en')}
selectedKeys={new Set([language])}
classNames={{
list: "py-2"
}}
itemClasses={{
base: "py-3 flex items-center justify-center"
}}
>
<DropdownItem
key="zh"
className="text-text-primary hover:bg-bg-elevated text-center"
>
</DropdownItem>
<DropdownItem
key="en"
className="text-text-primary hover:bg-bg-elevated text-center"
>
English
</DropdownItem>
</DropdownMenu>
</Dropdown>
</NavbarItem>
{/* Hamburger - mobile only */}
<NavbarItem className="lg:hidden">
<Button
isIconOnly
variant="light"
onPress={() => setMobileMenuOpen(!mobileMenuOpen)}
aria-label="Toggle menu"
>
<AnimatePresence mode="wait" initial={false}>
{mobileMenuOpen ? (
<motion.div
key="close"
initial={{ opacity: 0, rotate: -90 }}
animate={{ opacity: 1, rotate: 0 }}
exit={{ opacity: 0, rotate: 90 }}
transition={{ duration: 0.15 }}
>
<X size={20} color={isDark ? '#a1a1aa' : '#4b5563'} />
</motion.div>
) : (
<motion.div
key="menu"
initial={{ opacity: 0, rotate: 90 }}
animate={{ opacity: 1, rotate: 0 }}
exit={{ opacity: 0, rotate: -90 }}
transition={{ duration: 0.15 }}
>
<Menu size={20} color={isDark ? '#a1a1aa' : '#4b5563'} />
</motion.div>
)}
</AnimatePresence>
</Button>
</NavbarItem>
</NavbarContent>
</HeroNavbar>
{/* Mobile Menu */}
<AnimatePresence>
{mobileMenuOpen && (
<>
{/* Backdrop — click outside to close */}
<motion.div
key="mobile-backdrop"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
className="lg:hidden fixed inset-0 z-[45]"
onClick={() => {
setMobileMenuOpen(false);
setMobileProductOpen(false);
setMobileResourceOpen(false);
}}
/>
<motion.div
initial={{ opacity: 0, y: -8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -8 }}
transition={{ duration: 0.2, ease: 'easeOut' }}
className="lg:hidden fixed left-0 right-0 z-[50]"
style={{
top: navBottom,
backgroundColor: isDark ? 'rgba(10, 10, 10, 0.97)' : 'rgba(255, 255, 255, 0.97)',
backdropFilter: 'blur(50px)',
outline: 'none',
border: 'none',
borderBottom: isDark ? '1px solid #27272a' : '1px solid #e5e7eb',
maxHeight: `calc(100vh - ${navBottom}px)`,
overflowY: 'auto'
}}
>
<div className="flex flex-col px-4 py-3">
{/* Product */}
<button
className="flex items-center justify-between w-full px-3 py-4 text-sm font-bold rounded-lg transition-colors"
style={{ color: mobileProductOpen ? '#059669' : (isDark ? '#a1a1aa' : '#4b5563') }}
onClick={() => {
setMobileProductOpen(!mobileProductOpen);
setMobileResourceOpen(false);
}}
>
{t('nav.product')}
<ChevronDown
size={14}
color={mobileProductOpen ? '#059669' : (isDark ? '#a1a1aa' : '#4b5563')}
style={{ transform: mobileProductOpen ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform 0.2s ease' }}
/>
</button>
{/* Product Accordion */}
<AnimatePresence>
{mobileProductOpen && (
<motion.div
key="product-accordion"
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.2, ease: 'easeOut' }}
style={{ overflow: 'hidden' }}
>
<div className="px-2 pb-3 flex flex-col gap-4">
{/* Core Yield Assets */}
<div>
<span className={`text-[10px] font-bold uppercase tracking-[1.2px] font-domine px-1 ${isDark ? 'text-[#52525b]' : 'text-[#9CA3AF]'}`}>
{language === 'zh' ? '核心收益资产' : 'Core Yield Assets'}
</span>
<div className="flex flex-col gap-2 mt-2">
<div className={`flex items-center gap-3 p-3 rounded-xl cursor-pointer ${isDark ? 'bg-[#27272a]' : 'bg-[#F9FAFB]'}`}>
<div className="w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0" style={{ background: isDark ? '#ffffff' : '#000000' }}>
<Rainbow size={16} color={isDark ? '#000000' : '#ffffff'} />
</div>
<div className="flex items-center gap-2">
<span className={`text-sm font-bold font-domine ${isDark ? 'text-[#fafafa]' : 'text-[#111827]'}`}>AX-Fund</span>
<div className="px-1.5 py-0.5 rounded flex-shrink-0" style={{ background: '#D1FAE5' }}>
<span className="text-[9px] font-bold uppercase font-domine" style={{ color: '#047857' }}>
{language === 'zh' ? '最高22% APY' : 'up to 22% APY'}
</span>
</div>
</div>
</div>
<div className={`flex items-center gap-3 p-3 rounded-xl cursor-pointer ${isDark ? 'bg-[#27272a]' : 'bg-[#F9FAFB]'}`}>
<div className="w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0" style={{ background: isDark ? '#27272a' : '#ffffff', outline: `1px solid ${isDark ? '#3f3f46' : '#E5E7EB'}`, outlineOffset: '-1px' }}>
<Layers size={16} color={isDark ? '#d1d5db' : '#111827'} />
</div>
<div className="flex items-center gap-2">
<span className={`text-sm font-bold font-domine ${isDark ? 'text-[#fafafa]' : 'text-[#111827]'}`}>AX-Pool</span>
<div className="px-1.5 py-0.5 rounded flex-shrink-0" style={{ background: isDark ? '#3f3f46' : '#E5E7EB' }}>
<span className={`text-[9px] font-bold uppercase font-domine ${isDark ? 'text-[#a1a1aa]' : 'text-[#4B5563]'}`}>
{language === 'zh' ? '多元化' : 'Diversified'}
</span>
</div>
</div>
</div>
</div>
</div>
{/* Platforms & Protocols */}
<div>
<span className={`text-[10px] font-bold uppercase tracking-[1.2px] font-domine px-1 ${isDark ? 'text-[#52525b]' : 'text-[#9CA3AF]'}`}>
{language === 'zh' ? '平台与协议' : 'Platforms & Protocols'}
</span>
<div className="flex flex-col gap-0.5 mt-2">
{[
{ Icon: Rocket, titleZh: 'Launchpad', titleEn: 'Launchpad' },
{ Icon: ArrowLeftRight, titleZh: 'DeFi市场', titleEn: 'DeFi Market' },
{ Icon: Coins, titleZh: 'Token Factory', titleEn: 'Token Factory' },
].map(({ Icon, titleZh, titleEn }) => (
<div key={titleEn} className={`flex items-center gap-3 px-2 py-2.5 rounded-lg cursor-pointer transition-colors ${isDark ? 'hover:bg-[#27272a]' : 'hover:bg-[#F9FAFB]'}`}>
<div className={`w-7 h-7 rounded-lg flex items-center justify-center flex-shrink-0 ${isDark ? 'bg-[#27272a] outline outline-1 outline-[#3f3f46]' : 'bg-[#F9FAFB] outline outline-1 outline-[#F3F4F6]'}`} style={{ outlineOffset: '-1px' }}>
<Icon size={14} color={isDark ? '#d1d5db' : '#111827'} />
</div>
<span className={`text-sm font-bold font-domine ${isDark ? 'text-[#fafafa]' : 'text-black'}`}>
{language === 'zh' ? titleZh : titleEn}
</span>
</div>
))}
</div>
</div>
{/* Infrastructure */}
<div>
<span className={`text-[10px] font-bold uppercase tracking-[1.2px] font-domine px-1 ${isDark ? 'text-[#52525b]' : 'text-[#9CA3AF]'}`}>
{language === 'zh' ? '基础设施' : 'Infrastructure'}
</span>
<div className="flex flex-col gap-0.5 mt-2">
{[
{ Icon: LayoutDashboard, titleZh: 'Asset Cockpit', titleEn: 'Asset Cockpit' },
{ Icon: Radio, titleZh: 'Oracle Network', titleEn: 'Oracle Network' },
].map(({ Icon, titleZh, titleEn }) => (
<div key={titleEn} className={`flex items-center gap-3 px-2 py-2.5 rounded-lg cursor-pointer transition-colors ${isDark ? 'hover:bg-[#27272a]' : 'hover:bg-[#F9FAFB]'}`}>
<div className={`w-7 h-7 rounded-lg flex items-center justify-center flex-shrink-0 ${isDark ? 'bg-[#27272a] outline outline-1 outline-[#3f3f46]' : 'bg-[#F9FAFB] outline outline-1 outline-[#F3F4F6]'}`} style={{ outlineOffset: '-1px' }}>
<Icon size={14} color={isDark ? '#d1d5db' : '#111827'} />
</div>
<span className={`text-sm font-bold font-domine ${isDark ? 'text-[#fafafa]' : 'text-black'}`}>
{language === 'zh' ? titleZh : titleEn}
</span>
</div>
))}
</div>
</div>
{/* Bottom */}
<div className={`pt-2 border-t flex items-center justify-between ${isDark ? 'border-[#27272a]' : 'border-[#F3F4F6]'}`}>
<span className="text-xs font-domine text-[#9CA3AF]">
{language === 'zh' ? '最新审计:' : 'Latest Audit: '}
<span className={`font-medium ${isDark ? 'text-[#fafafa]' : 'text-black'}`}>Oct 2025 (Certik)</span>
</span>
<span className="text-xs font-bold uppercase font-domine cursor-pointer hover:opacity-70 tracking-[0.3px]" style={{ color: '#059669' }}>
{language === 'zh' ? '查看文档 →' : 'View Docs →'}
</span>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
<div style={{ height: '1px', backgroundColor: isDark ? '#27272a' : '#f3f4f6' }} />
{/* Ecosystem */}
<button
className="flex items-center w-full px-3 py-4 text-sm font-bold rounded-lg transition-colors"
style={{ color: isDark ? '#a1a1aa' : '#4b5563' }}
onClick={() => setMobileMenuOpen(false)}
>
{t('nav.ecosystem')}
</button>
<div style={{ height: '1px', backgroundColor: isDark ? '#27272a' : '#f3f4f6' }} />
{/* Resource */}
<button
className="flex items-center justify-between w-full px-3 py-4 text-sm font-bold rounded-lg transition-colors"
style={{ color: mobileResourceOpen ? '#059669' : (isDark ? '#a1a1aa' : '#4b5563') }}
onClick={() => {
setMobileResourceOpen(!mobileResourceOpen);
setMobileProductOpen(false);
}}
>
{t('nav.resource')}
<ChevronDown
size={14}
color={mobileResourceOpen ? '#059669' : (isDark ? '#a1a1aa' : '#4b5563')}
style={{ transform: mobileResourceOpen ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform 0.2s ease' }}
/>
</button>
{/* Resource Accordion */}
<AnimatePresence>
{mobileResourceOpen && (
<motion.div
key="resource-accordion"
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.2, ease: 'easeOut' }}
style={{ overflow: 'hidden' }}
>
<div className="px-2 pb-3 flex flex-col gap-4">
{/* Documentation & Learning */}
<div>
<span className={`text-[10px] font-bold uppercase tracking-[1.2px] font-domine px-1 ${isDark ? 'text-[#52525b]' : 'text-[#9CA3AF]'}`}>
{language === 'zh' ? '文档与学习' : 'Documentation & Learning'}
</span>
<div className="flex flex-col gap-2 mt-2">
<div className={`flex items-center gap-3 p-3 rounded-xl cursor-pointer ${isDark ? 'bg-[#27272a]' : 'bg-[#F9FAFB]'}`}>
<div className="w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0" style={{ background: isDark ? '#ffffff' : '#000000' }}>
<BookOpen size={16} color={isDark ? '#000000' : '#ffffff'} />
</div>
<div className="flex items-center gap-2">
<span className={`text-sm font-bold font-domine ${isDark ? 'text-[#fafafa]' : 'text-[#111827]'}`}>
{language === 'zh' ? '文档' : 'Docs'}
</span>
<div className="px-1.5 py-0.5 rounded flex-shrink-0" style={{ background: '#D1FAE5' }}>
<span className="text-[9px] font-bold uppercase font-domine" style={{ color: '#047857' }}>
{language === 'zh' ? '已更新' : 'Updated'}
</span>
</div>
</div>
</div>
<div className={`flex items-center gap-3 p-3 rounded-xl cursor-pointer ${isDark ? 'bg-[#27272a]' : 'bg-[#F9FAFB]'}`}>
<div className="w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0" style={{ background: isDark ? '#052e16' : '#ffffff', outline: '2px solid #10B981', outlineOffset: '-2px' }}>
<ShieldCheck size={16} color="#059669" />
</div>
<span className={`text-sm font-bold font-domine ${isDark ? 'text-[#fafafa]' : 'text-[#111827]'}`}>
{language === 'zh' ? '信任与安全' : 'Trust & Security'}
</span>
</div>
</div>
</div>
{/* Help & Support */}
<div>
<span className={`text-[10px] font-bold uppercase tracking-[1.2px] font-domine px-1 ${isDark ? 'text-[#52525b]' : 'text-[#9CA3AF]'}`}>
{language === 'zh' ? '帮助与支持' : 'Help & Support'}
</span>
<div className="flex flex-col gap-0.5 mt-2">
{[
{ Icon: GraduationCap, titleZh: '学习中心', titleEn: 'Learning Center' },
{ Icon: MessageCircle, titleZh: '社区论坛', titleEn: 'Community Forum' },
{ Icon: Headphones, titleZh: '联系支持', titleEn: 'Contact Support' },
].map(({ Icon, titleZh, titleEn }) => (
<div key={titleEn} className={`flex items-center gap-3 px-2 py-2.5 rounded-lg cursor-pointer transition-colors ${isDark ? 'hover:bg-[#27272a]' : 'hover:bg-[#F9FAFB]'}`}>
<div className={`w-7 h-7 rounded-lg flex items-center justify-center flex-shrink-0 ${isDark ? 'bg-[#27272a] outline outline-1 outline-[#3f3f46]' : 'bg-[#F9FAFB] outline outline-1 outline-[#F3F4F6]'}`} style={{ outlineOffset: '-1px' }}>
<Icon size={14} color={isDark ? '#d1d5db' : '#111827'} />
</div>
<span className={`text-sm font-bold font-domine ${isDark ? 'text-[#fafafa]' : 'text-black'}`}>
{language === 'zh' ? titleZh : titleEn}
</span>
</div>
))}
</div>
</div>
{/* Company */}
<div>
<span className={`text-[10px] font-bold uppercase tracking-[1.2px] font-domine px-1 ${isDark ? 'text-[#52525b]' : 'text-[#9CA3AF]'}`}>
{language === 'zh' ? '公司' : 'Company'}
</span>
<div className="flex flex-col gap-0.5 mt-2">
{[
{ Icon: Users, titleZh: '关于团队', titleEn: 'About Team' },
{ Icon: Briefcase, titleZh: '招聘', titleEn: 'Careers' },
{ Icon: Mail, titleZh: '联系我们', titleEn: 'Contact Us' },
{ Icon: Newspaper, titleZh: '新闻媒体', titleEn: 'Press & Media' },
].map(({ Icon, titleZh, titleEn }) => (
<div key={titleEn} className={`flex items-center gap-3 px-2 py-2.5 rounded-lg cursor-pointer transition-colors ${isDark ? 'hover:bg-[#27272a]' : 'hover:bg-[#F9FAFB]'}`}>
<div className={`w-7 h-7 rounded-lg flex items-center justify-center flex-shrink-0 ${isDark ? 'bg-[#27272a] outline outline-1 outline-[#3f3f46]' : 'bg-[#F9FAFB] outline outline-1 outline-[#F3F4F6]'}`} style={{ outlineOffset: '-1px' }}>
<Icon size={14} color={isDark ? '#d1d5db' : '#111827'} />
</div>
<span className={`text-sm font-bold font-domine ${isDark ? 'text-[#fafafa]' : 'text-black'}`}>
{language === 'zh' ? titleZh : titleEn}
</span>
</div>
))}
</div>
</div>
{/* Bottom */}
<div className={`pt-2 border-t flex items-center justify-between ${isDark ? 'border-[#27272a]' : 'border-[#F3F4F6]'}`}>
<span className="text-xs font-domine text-[#9CA3AF]">
{language === 'zh' ? '最新更新:' : 'Latest Update: '}
<span className={`font-medium ${isDark ? 'text-[#fafafa]' : 'text-black'}`}>December 2025</span>
</span>
<div className="flex gap-4">
<span className={`text-xs font-bold font-domine cursor-pointer ${isDark ? 'text-[#6b7280]' : 'text-[#6B7280]'}`}>
{language === 'zh' ? '隐私政策' : 'Privacy Policy'}
</span>
<span className={`text-xs font-bold font-domine cursor-pointer ${isDark ? 'text-[#6b7280]' : 'text-[#6B7280]'}`}>
{language === 'zh' ? '服务条款' : 'Terms of Service'}
</span>
</div>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
{/* Theme + Language */}
<div style={{ height: '1px', backgroundColor: isDark ? '#27272a' : '#f3f4f6', margin: '4px 0' }} />
<div className="flex items-center gap-2 px-3 py-3">
{/* Theme Toggle */}
<Button
isIconOnly
variant="light"
onPress={toggleTheme}
>
<div className="relative w-5 h-5">
<AnimatePresence mode="wait" initial={false}>
{isDark ? (
<motion.div key="moon" className="absolute inset-0 flex items-center justify-center"
initial={{ opacity: 0, rotate: -90, scale: 0.5 }} animate={{ opacity: 1, rotate: 0, scale: 1 }} exit={{ opacity: 0, rotate: 90, scale: 0.5 }} transition={{ duration: 0.3 }}>
<Moon size={18} color="#a1a1aa" />
</motion.div>
) : (
<motion.div key="sun" className="absolute inset-0 flex items-center justify-center"
initial={{ opacity: 0, rotate: 90, scale: 0.5 }} animate={{ opacity: 1, rotate: 0, scale: 1 }} exit={{ opacity: 0, rotate: -90, scale: 0.5 }} transition={{ duration: 0.3 }}>
<Sun size={18} color="#4B5563" />
</motion.div>
)}
</AnimatePresence>
</div>
</Button>
{/* Language */}
<Dropdown classNames={{ content: 'bg-bg-surface border-none shadow-lg min-w-[120px] w-[120px]' }}>
<DropdownTrigger>
<Button variant="light" className="font-bold text-sm text-text-secondary"
startContent={<Globe size={16} color={isDark ? '#a1a1aa' : '#4B5563'} />}
endContent={<ChevronDown size={12} color={isDark ? '#a1a1aa' : '#4B5563'} />}
>
{language === 'zh' ? '中文' : 'EN'}
</Button>
</DropdownTrigger>
<DropdownMenu aria-label="Language selection" onAction={(key) => setLanguage(key as 'zh' | 'en')} selectedKeys={new Set([language])} classNames={{ list: "py-2" }} itemClasses={{ base: "py-3 flex items-center justify-center" }}>
<DropdownItem key="zh" className="text-text-primary hover:bg-bg-elevated text-center"></DropdownItem>
<DropdownItem key="en" className="text-text-primary hover:bg-bg-elevated text-center">English</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
</div>
</motion.div>
</>
)}
</AnimatePresence>
{/* Custom Menus */}
<ProductMenu
isOpen={showProductMenu}
onClose={() => setShowProductMenu(false)}
language={language}
top={navBottom}
/>
<ResourceMenu
isOpen={showResourceMenu}
onClose={() => setShowResourceMenu(false)}
language={language}
top={navBottom}
/>
</>
);
}

View File

@@ -0,0 +1,274 @@
'use client';
import Image from 'next/image';
import { AnimatePresence, motion } from 'framer-motion';
import { useTheme } from '@/contexts/ThemeContext';
import { Layers, Rocket, ArrowLeftRight, Coins, LayoutDashboard, Radio, Rainbow } from 'lucide-react';
interface ProductMenuProps {
isOpen: boolean;
onClose: () => void;
language: 'zh' | 'en';
top?: number;
}
export default function ProductMenu({ isOpen, onClose, language, top = 64 }: ProductMenuProps) {
const { theme } = useTheme();
const isDark = theme === 'dark';
const content = {
zh: {
coreYieldAssets: '核心收益资产',
axFund: 'AX-Fund',
axFundDesc: '购买AX-Fund收益代币获得市场中性收益策略的敞口。收益随时间在代币价格中累积。',
axFundBadge: '最高22% APY',
axPool: 'AX-Pool',
axPoolDesc: '以ALP提供流动性支持收益代币市场并赚取交易费',
axPoolBadge: '多元化',
platformsProtocols: '平台与协议',
launchpad: 'Launchpad',
launchpadDesc: '发行与代币化新RWA',
defiMarket: 'DeFi市场',
defiMarketDesc: '资产代币的交换与借贷',
tokenFactory: 'Token Factory',
tokenFactoryDesc: '标准化铸造协议',
infrastructure: '基础设施',
assetCockpit: 'Asset Cockpit',
assetCockpitDesc: '投资组合分析与管理',
oracleNetwork: 'Oracle Network',
oracleNetworkDesc: '实时链下数据源',
latestAudit: '最新审计:',
auditInfo: 'Oct 2025 (Certik)',
viewDocs: '查看文档 →',
},
en: {
coreYieldAssets: 'Core Yield Assets',
axFund: 'AX-Fund',
axFundDesc: 'Buy the AX-Fund Yield Token to gain exposure to market-neutral yield strategies. Yield accrues in token price over time.',
axFundBadge: 'up to 22% APY',
axPool: 'AX-Pool',
axPoolDesc: 'Provide liquidity as ALP to support Yield Token markets and earn trading fees',
axPoolBadge: 'Diversified',
platformsProtocols: 'Platforms & Protocols',
launchpad: 'Launchpad',
launchpadDesc: 'Issue & tokenize new RWAs.',
defiMarket: 'Defi Market',
defiMarketDesc: 'Swap & Lending for assets tokens.',
tokenFactory: 'Token Factory',
tokenFactoryDesc: 'Standardized minting protocol.',
infrastructure: 'Infrastructure',
assetCockpit: 'Asset Cockpit',
assetCockpitDesc: 'Portfolio analytics & mgmt.',
oracleNetwork: 'Oracle Network',
oracleNetworkDesc: 'Real-time off-chain data feeds.',
latestAudit: 'Latest Audit:',
auditInfo: 'Oct 2025 (Certik)',
viewDocs: 'View Documentation →',
},
};
const t = content[language];
const platformItems = [
{ Icon: Rocket, title: t.launchpad, desc: t.launchpadDesc },
{ Icon: ArrowLeftRight, title: t.defiMarket, desc: t.defiMarketDesc },
{ Icon: Coins, title: t.tokenFactory, desc: t.tokenFactoryDesc },
];
const infraItems = [
{ Icon: LayoutDashboard, title: t.assetCockpit, desc: t.assetCockpitDesc },
{ Icon: Radio, title: t.oracleNetwork, desc: t.oracleNetworkDesc },
];
return (
<AnimatePresence>
{isOpen && (
<>
{/* Transparent backdrop for click-outside */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
className="fixed inset-0 z-40"
onClick={onClose}
/>
{/* Menu — centered card */}
<motion.div
initial={{ opacity: 0, y: -8, x: '-50%' }}
animate={{ opacity: 1, y: 0, x: '-50%' }}
exit={{ opacity: 0, y: -8, x: '-50%' }}
transition={{ duration: 0.25, ease: 'easeOut' }}
className={`fixed left-1/2 z-50 rounded-2xl border transition-colors ${
isDark ? 'bg-[#18181b] border-[#27272a]' : 'bg-white border-[#E5E7EB]'
}`}
style={{ top: top, width: '960px', boxShadow: '0px 20px 50px rgba(0,0,0,0.10)' }}
>
<div style={{ padding: '32px' }}>
{/* Three-column layout */}
<div className="flex gap-12">
{/* Left — Core Yield Assets (large cards) */}
<div className="flex flex-col gap-6 flex-shrink-0" style={{ width: '345px' }}>
<span className={`text-xs font-bold uppercase tracking-[1.2px] font-domine ${isDark ? 'text-[#52525b]' : 'text-[#9CA3AF]'}`}>
{t.coreYieldAssets}
</span>
{/* AX-Fund card */}
<div
className={`p-5 rounded-2xl cursor-pointer transition-opacity hover:opacity-75 ${
isDark
? 'bg-[#27272a] outline outline-1 outline-[#3f3f46]'
: 'bg-[#F9FAFB] outline outline-1 outline-[#E5E7EB]'
}`}
style={{ outlineOffset: '-1px' }}
>
<div className="flex items-start gap-4">
<div
className="w-12 h-12 rounded-full flex items-center justify-center flex-shrink-0"
style={{ background: isDark ? '#ffffff' : '#000000' }}
>
<Rainbow size={24} color={isDark ? '#000000' : '#ffffff'} />
</div>
<div className="flex flex-col gap-1 flex-1">
<div className="flex items-center gap-2">
<span className={`font-bold text-lg font-domine leading-7 ${isDark ? 'text-[#fafafa]' : 'text-[#111827]'}`}>
{t.axFund}
</span>
<div className="px-2 py-0.5 rounded flex items-center" style={{ background: '#D1FAE5' }}>
<span className="text-[10px] font-bold uppercase font-domine leading-none" style={{ color: '#047857' }}>
{t.axFundBadge}
</span>
</div>
</div>
<p className="text-sm font-domine text-[#6B7280] leading-relaxed">{t.axFundDesc}</p>
</div>
</div>
</div>
{/* AX-Pool card */}
<div
className={`p-5 rounded-2xl cursor-pointer transition-opacity hover:opacity-75 ${
isDark
? 'bg-[#27272a] outline outline-1 outline-[#3f3f46]'
: 'bg-[#F9FAFB] outline outline-1 outline-[#E5E7EB]'
}`}
style={{ outlineOffset: '-1px' }}
>
<div className="flex items-start gap-4">
<div
className="w-12 h-12 rounded-full flex items-center justify-center flex-shrink-0"
style={{
background: isDark ? '#27272a' : '#ffffff',
outline: `1px solid ${isDark ? '#3f3f46' : '#E5E7EB'}`,
outlineOffset: '-1px',
}}
>
<Layers size={24} color={isDark ? '#d1d5db' : '#111827'} />
</div>
<div className="flex flex-col gap-1 flex-1">
<div className="flex items-center gap-2">
<span className={`font-bold text-lg font-domine leading-7 ${isDark ? 'text-[#fafafa]' : 'text-[#111827]'}`}>
{t.axPool}
</span>
<div className="px-2 py-0.5 rounded flex items-center" style={{ background: isDark ? '#3f3f46' : '#E5E7EB' }}>
<span className={`text-[10px] font-bold uppercase font-domine leading-none ${isDark ? 'text-[#a1a1aa]' : 'text-[#4B5563]'}`}>
{t.axPoolBadge}
</span>
</div>
</div>
<p className="text-sm font-domine text-[#6B7280] leading-relaxed">{t.axPoolDesc}</p>
</div>
</div>
</div>
</div>
{/* Middle — Platforms & Protocols */}
<div className="flex flex-col gap-6 flex-shrink-0" style={{ width: '227px' }}>
<span className={`text-xs font-bold uppercase tracking-[1.2px] font-domine ${isDark ? 'text-[#52525b]' : 'text-[#9CA3AF]'}`}>
{t.platformsProtocols}
</span>
<div className="flex flex-col gap-6">
{platformItems.map(({ Icon, title, desc }) => (
<div
key={title}
className={`flex items-start gap-4 cursor-pointer rounded-lg p-2 -mx-2 transition-colors ${
isDark ? 'hover:bg-[#27272a]' : 'hover:bg-[#F9FAFB]'
}`}
>
<div
className={`w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0 ${
isDark ? 'bg-[#27272a] outline outline-1 outline-[#3f3f46]' : 'bg-[#F9FAFB] outline outline-1 outline-[#F3F4F6]'
}`}
style={{ outlineOffset: '-1px' }}
>
<Icon size={20} color={isDark ? '#d1d5db' : '#111827'} />
</div>
<div className="flex flex-col gap-0.5">
<span className={`text-sm font-bold font-domine leading-5 ${isDark ? 'text-[#fafafa]' : 'text-black'}`}>
{title}
</span>
<span className="text-xs font-domine text-[#6B7280] leading-4">{desc}</span>
</div>
</div>
))}
</div>
</div>
{/* Right — Infrastructure */}
<div className="flex flex-col gap-6 flex-1">
<span className={`text-xs font-bold uppercase tracking-[1.2px] font-domine ${isDark ? 'text-[#52525b]' : 'text-[#9CA3AF]'}`}>
{t.infrastructure}
</span>
<div className="flex flex-col gap-6">
{infraItems.map(({ Icon, title, desc }) => (
<div
key={title}
className={`flex items-start gap-4 cursor-pointer rounded-lg p-2 -mx-2 transition-colors ${
isDark ? 'hover:bg-[#27272a]' : 'hover:bg-[#F9FAFB]'
}`}
>
<div
className={`w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0 ${
isDark ? 'bg-[#27272a] outline outline-1 outline-[#3f3f46]' : 'bg-[#F9FAFB] outline outline-1 outline-[#F3F4F6]'
}`}
style={{ outlineOffset: '-1px' }}
>
<Icon size={20} color={isDark ? '#d1d5db' : '#111827'} />
</div>
<div className="flex flex-col gap-0.5">
<span className={`text-sm font-bold font-domine leading-5 ${isDark ? 'text-[#fafafa]' : 'text-black'}`}>
{title}
</span>
<span className="text-xs font-domine text-[#6B7280] leading-4">{desc}</span>
</div>
</div>
))}
</div>
</div>
</div>
{/* Bottom bar */}
<div className={`mt-8 pt-6 border-t flex items-center justify-between ${isDark ? 'border-[#27272a]' : 'border-[#F3F4F6]'}`}>
<div className="text-xs font-domine">
<span className="text-[#9CA3AF]">{t.latestAudit} </span>
<span className={`font-medium ${isDark ? 'text-[#fafafa]' : 'text-black'}`}>{t.auditInfo}</span>
</div>
<span
className="text-xs font-bold uppercase font-domine cursor-pointer hover:opacity-70 transition-opacity tracking-[0.3px]"
style={{ color: '#059669' }}
>
{t.viewDocs}
</span>
</div>
</div>
</motion.div>
</>
)}
</AnimatePresence>
);
}

View File

@@ -0,0 +1,18 @@
'use client';
import { HeroUIProvider } from '@heroui/react';
import { LanguageProvider } from '@/contexts/LanguageContext';
import { ThemeProvider } from '@/contexts/ThemeContext';
import { ReactNode } from 'react';
export default function Providers({ children }: { children: ReactNode }) {
return (
<HeroUIProvider>
<ThemeProvider>
<LanguageProvider>
{children}
</LanguageProvider>
</ThemeProvider>
</HeroUIProvider>
);
}

View File

@@ -0,0 +1,280 @@
'use client';
import { AnimatePresence, motion } from 'framer-motion';
import { useTheme } from '@/contexts/ThemeContext';
import { BookOpen, ShieldCheck, GraduationCap, MessageCircle, Headphones, Users, Briefcase, Mail, Newspaper } from 'lucide-react';
interface ResourceMenuProps {
isOpen: boolean;
onClose: () => void;
language: 'zh' | 'en';
top?: number;
}
export default function ResourceMenu({ isOpen, onClose, language, top = 64 }: ResourceMenuProps) {
const { theme } = useTheme();
const isDark = theme === 'dark';
const content = {
zh: {
docLearning: '文档与学习',
docs: '文档',
docsDesc: '阅读技术文档、智能合约参考和集成指南。',
docsBadge: '已更新',
trustSecurity: '信任与安全',
trustSecurityDesc: '查看审计、合规认证和我们的安全实践。',
helpSupport: '帮助与支持',
learningCenter: '学习中心',
learningCenterDesc: '教程与视频指南。',
communityForum: '社区论坛',
communityForumDesc: '提问与交流。',
contactSupport: '联系支持',
contactSupportDesc: '从我们的团队获得帮助。',
company: '公司',
aboutTeam: '关于团队',
aboutTeamDesc: '认识AssetX背后的团队。',
careers: '招聘',
careersDesc: '加入我们不断成长的团队。',
contactUs: '联系我们',
contactUsDesc: '合作与媒体咨询。',
pressMedia: '新闻媒体',
pressMediaDesc: '最新消息与品牌资产。',
latestUpdate: '最新更新:',
updateInfo: 'December 2025',
privacyPolicy: '隐私政策',
termsOfService: '服务条款',
},
en: {
docLearning: 'Documentation & Learning',
docs: 'Docs',
docsDesc: 'Read technical docs, smart contract references, and integration guides.',
docsBadge: 'Updated',
trustSecurity: 'Trust & Security',
trustSecurityDesc: 'Review audits, compliance attestations, and our security practices.',
helpSupport: 'Help & Support',
learningCenter: 'Learning Center',
learningCenterDesc: 'Tutorials & video guides.',
communityForum: 'Community Forum',
communityForumDesc: 'Ask questions & connect.',
contactSupport: 'Contact Support',
contactSupportDesc: 'Get help from our team.',
company: 'Company',
aboutTeam: 'About Team',
aboutTeamDesc: 'Meet the people behind AssetX.',
careers: 'Careers',
careersDesc: 'Join our growing team.',
contactUs: 'Contact Us',
contactUsDesc: 'Partnerships & media inquiries.',
pressMedia: 'Press & Media',
pressMediaDesc: 'Latest news & brand assets.',
latestUpdate: 'Latest Update:',
updateInfo: 'December 2025',
privacyPolicy: 'Privacy Policy',
termsOfService: 'Terms of Service',
},
};
const t = content[language];
const helpItems = [
{ Icon: GraduationCap, title: t.learningCenter, desc: t.learningCenterDesc },
{ Icon: MessageCircle, title: t.communityForum, desc: t.communityForumDesc },
{ Icon: Headphones, title: t.contactSupport, desc: t.contactSupportDesc },
];
const companyItems = [
{ Icon: Users, title: t.aboutTeam, desc: t.aboutTeamDesc },
{ Icon: Briefcase, title: t.careers, desc: t.careersDesc },
{ Icon: Mail, title: t.contactUs, desc: t.contactUsDesc },
{ Icon: Newspaper, title: t.pressMedia, desc: t.pressMediaDesc },
];
return (
<AnimatePresence>
{isOpen && (
<>
{/* Transparent backdrop for click-outside */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
className="fixed inset-0 z-40"
onClick={onClose}
/>
{/* Menu — centered card */}
<motion.div
initial={{ opacity: 0, y: -8, x: '-50%' }}
animate={{ opacity: 1, y: 0, x: '-50%' }}
exit={{ opacity: 0, y: -8, x: '-50%' }}
transition={{ duration: 0.25, ease: 'easeOut' }}
className={`fixed left-1/2 z-50 rounded-2xl border transition-colors ${
isDark ? 'bg-[#18181b] border-[#27272a]' : 'bg-white border-[#E5E7EB]'
}`}
style={{ top: top, width: '960px', boxShadow: '0px 20px 50px rgba(0,0,0,0.10)' }}
>
<div>
<div style={{ padding: '32px' }}>
{/* Three-column layout */}
<div className="flex gap-12">
{/* Left — Documentation & Learning (large cards) */}
<div className="flex flex-col gap-6 flex-shrink-0" style={{ width: '345px' }}>
<span className={`text-xs font-bold uppercase tracking-[1.2px] font-domine ${isDark ? 'text-[#52525b]' : 'text-[#9CA3AF]'}`}>
{t.docLearning}
</span>
{/* Docs card */}
<div
className={`p-5 rounded-2xl cursor-pointer transition-opacity hover:opacity-75 ${
isDark
? 'bg-[#27272a] outline outline-1 outline-[#3f3f46]'
: 'bg-[#F9FAFB] outline outline-1 outline-[#E5E7EB]'
}`}
style={{ outlineOffset: '-1px' }}
>
<div className="flex items-start gap-4">
<div
className="w-12 h-12 rounded-full flex items-center justify-center flex-shrink-0"
style={{ background: isDark ? '#ffffff' : '#000000' }}
>
<BookOpen size={24} color={isDark ? '#000000' : '#ffffff'} />
</div>
<div className="flex flex-col gap-1 flex-1">
<div className="flex items-center gap-2">
<span className={`font-bold text-lg font-domine leading-7 ${isDark ? 'text-[#fafafa]' : 'text-[#111827]'}`}>
{t.docs}
</span>
<div className="px-2 py-0.5 rounded flex items-center" style={{ background: '#D1FAE5' }}>
<span className="text-[10px] font-bold uppercase font-domine leading-none" style={{ color: '#047857' }}>
{t.docsBadge}
</span>
</div>
</div>
<p className="text-sm font-domine text-[#6B7280] leading-relaxed">{t.docsDesc}</p>
</div>
</div>
</div>
{/* Trust & Security card */}
<div
className={`p-5 rounded-2xl cursor-pointer transition-opacity hover:opacity-75 ${
isDark
? 'bg-[#27272a] outline outline-1 outline-[#3f3f46]'
: 'bg-[#F9FAFB] outline outline-1 outline-[#E5E7EB]'
}`}
style={{ outlineOffset: '-1px' }}
>
<div className="flex items-start gap-4">
<div
className="w-12 h-12 rounded-full flex items-center justify-center flex-shrink-0"
style={{
background: isDark ? '#052e16' : '#ffffff',
outline: '2px solid #10B981',
outlineOffset: '-2px',
}}
>
<ShieldCheck size={24} color="#059669" />
</div>
<div className="flex flex-col gap-1 flex-1">
<span className={`font-bold text-lg font-domine leading-7 ${isDark ? 'text-[#fafafa]' : 'text-[#111827]'}`}>
{t.trustSecurity}
</span>
<p className="text-sm font-domine text-[#6B7280] leading-relaxed">{t.trustSecurityDesc}</p>
</div>
</div>
</div>
</div>
{/* Middle — Help & Support */}
<div className="flex flex-col gap-6 flex-shrink-0" style={{ width: '227px' }}>
<span className={`text-xs font-bold uppercase tracking-[1.2px] font-domine ${isDark ? 'text-[#52525b]' : 'text-[#9CA3AF]'}`}>
{t.helpSupport}
</span>
<div className="flex flex-col gap-6">
{helpItems.map(({ Icon, title, desc }) => (
<div
key={title}
className={`flex items-start gap-4 cursor-pointer rounded-lg p-2 -mx-2 transition-colors ${
isDark ? 'hover:bg-[#27272a]' : 'hover:bg-[#F9FAFB]'
}`}
>
<div
className={`w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0 ${
isDark ? 'bg-[#27272a] outline outline-1 outline-[#3f3f46]' : 'bg-[#F9FAFB] outline outline-1 outline-[#F3F4F6]'
}`}
style={{ outlineOffset: '-1px' }}
>
<Icon size={20} color={isDark ? '#d1d5db' : '#111827'} />
</div>
<div className="flex flex-col gap-0.5">
<span className={`text-sm font-bold font-domine leading-5 ${isDark ? 'text-[#fafafa]' : 'text-black'}`}>
{title}
</span>
<span className="text-xs font-domine text-[#6B7280] leading-4">{desc}</span>
</div>
</div>
))}
</div>
</div>
{/* Right — Company */}
<div className="flex flex-col gap-6 flex-1">
<span className={`text-xs font-bold uppercase tracking-[1.2px] font-domine ${isDark ? 'text-[#52525b]' : 'text-[#9CA3AF]'}`}>
{t.company}
</span>
<div className="flex flex-col gap-6">
{companyItems.map(({ Icon, title, desc }) => (
<div
key={title}
className={`flex items-start gap-4 cursor-pointer rounded-lg p-2 -mx-2 transition-colors ${
isDark ? 'hover:bg-[#27272a]' : 'hover:bg-[#F9FAFB]'
}`}
>
<div
className={`w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0 ${
isDark ? 'bg-[#27272a] outline outline-1 outline-[#3f3f46]' : 'bg-[#F9FAFB] outline outline-1 outline-[#F3F4F6]'
}`}
style={{ outlineOffset: '-1px' }}
>
<Icon size={20} color={isDark ? '#d1d5db' : '#111827'} />
</div>
<div className="flex flex-col gap-0.5">
<span className={`text-sm font-bold font-domine leading-5 ${isDark ? 'text-[#fafafa]' : 'text-black'}`}>
{title}
</span>
<span className="text-xs font-domine text-[#6B7280] leading-4">{desc}</span>
</div>
</div>
))}
</div>
</div>
</div>
{/* Bottom bar */}
<div className={`mt-8 pt-6 border-t flex items-center justify-between ${isDark ? 'border-[#27272a]' : 'border-[#F3F4F6]'}`}>
<div className="text-xs font-domine">
<span className="text-[#9CA3AF]">{t.latestUpdate} </span>
<span className={`font-medium ${isDark ? 'text-[#fafafa]' : 'text-black'}`}>{t.updateInfo}</span>
</div>
<div className="flex gap-6">
<span className={`text-xs font-bold font-domine cursor-pointer transition-colors ${isDark ? 'text-[#6b7280] hover:text-[#fafafa]' : 'text-[#6B7280] hover:text-[#111827]'}`}>
{t.privacyPolicy}
</span>
<span className={`text-xs font-bold font-domine cursor-pointer transition-colors ${isDark ? 'text-[#6b7280] hover:text-[#fafafa]' : 'text-[#6B7280] hover:text-[#111827]'}`}>
{t.termsOfService}
</span>
</div>
</div>
</div>
</div>
</motion.div>
</>
)}
</AnimatePresence>
);
}

View File

@@ -0,0 +1,247 @@
'use client';
import { useState, useEffect, useRef } from 'react';
import Image from 'next/image';
import { Card, CardBody } from '@heroui/react';
import { SearchCheck, Landmark, BarChart2, ArrowRight, LucideIcon } from 'lucide-react';
import { useLanguage, TranslationKey } from '@/contexts/LanguageContext';
const features: { Icon: LucideIcon; titleKey: TranslationKey; descKey: TranslationKey | null; position: string; special?: boolean }[] = [
{
Icon: SearchCheck,
titleKey: 'security.audited.title',
descKey: 'security.audited.desc',
position: 'top-left'
},
{
Icon: Landmark,
titleKey: 'security.segregated.title',
descKey: 'security.segregated.desc',
position: 'top-right'
},
{
Icon: BarChart2,
titleKey: 'security.transparency.title',
descKey: 'security.transparency.desc',
position: 'bottom-left'
},
{
Icon: ArrowRight,
titleKey: 'security.partners.title',
descKey: null,
position: 'bottom-right',
special: true
}
];
export default function SecuritySection() {
const { t } = useLanguage();
const [animate, setAnimate] = useState(false);
const sectionRef = useRef<HTMLElement>(null);
const [cardHovers, setCardHovers] = useState<{[key: number]: {x: number, y: number} | null}>({});
useEffect(() => {
const currentRef = sectionRef.current;
if (!currentRef) return;
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
setAnimate(true);
observer.disconnect();
}
},
{ threshold: 0.3 }
);
observer.observe(currentRef);
return () => observer.disconnect();
}, []);
const handleCardMouseMove = (e: React.MouseEvent<HTMLDivElement>, index: number) => {
const rect = e.currentTarget.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100;
const y = ((e.clientY - rect.top) / rect.height) * 100;
setCardHovers(prev => ({...prev, [index]: {x, y}}));
};
const handleCardMouseLeave = (index: number) => {
setCardHovers(prev => ({...prev, [index]: null}));
};
const getBorderGradients = (index: number) => {
const hover = cardHovers[index];
if (!hover) return null;
const { x, y } = hover;
const spreadRange = 20;
const horizontalGradient = `linear-gradient(to right,
transparent 0%,
rgba(136,136,136,0.3) ${Math.max(0, x - spreadRange)}%,
rgba(136,136,136,1) ${x}%,
rgba(136,136,136,0.3) ${Math.min(100, x + spreadRange)}%,
transparent 100%)`;
const verticalGradient = `linear-gradient(to bottom,
transparent 0%,
rgba(136,136,136,0.3) ${Math.max(0, y - spreadRange)}%,
rgba(136,136,136,1) ${y}%,
rgba(136,136,136,0.3) ${Math.min(100, y + spreadRange)}%,
transparent 100%)`;
// 根据鼠标位置判断显示哪些边,使用 opacity 平滑过渡
const topOpacity = y < 50 ? 1 : 0;
const leftOpacity = x < 50 ? 1 : 0;
const rightOpacity = x >= 50 ? 1 : 0;
const bottomOpacity = y >= 50 ? 1 : 0;
return { horizontalGradient, verticalGradient, topOpacity, leftOpacity, rightOpacity, bottomOpacity };
};
return (
<section
ref={sectionRef}
className="bg-black flex flex-col items-center justify-start self-stretch flex-shrink-0 w-full relative"
style={{
padding: '1px 0px 0px 0px',
gap: '40px'
}}
>
<div className="flex flex-col md:flex-row items-stretch justify-start flex-shrink-0 w-full max-w-[1440px] 2xl:max-w-[1760px] 3xl:max-w-[2200px] relative">
{/* 标题区域 */}
<div
className={`flex flex-row items-center justify-center flex-shrink-0 w-full md:w-[459px] 2xl:w-[560px] 3xl:w-[700px] relative transition-all duration-700 ease-out border-b border-[#222222] px-6 py-12 md:py-0 md:pr-6 md:pl-[120px] 2xl:pl-[160px] 3xl:pl-[200px] ${
animate ? 'translate-x-0 opacity-100' : '-translate-x-12 opacity-0'
}`}
>
<div className="flex flex-col gap-6 items-start justify-start flex-1 relative">
<h2
className="text-[#fcfcfd] text-left relative self-stretch font-domine select-none"
style={{
fontSize: 'clamp(36px, 3.5vw, 80px)',
lineHeight: '120%',
letterSpacing: '-0.01em'
}}
>
{t('security.title')}
</h2>
<p
className="text-[#9ca1af] text-left relative flex items-center justify-start font-domine text-base select-none"
style={{
lineHeight: '150%'
}}
>
{t('security.subtitle')}
</p>
</div>
</div>
{/* 卡片区域移动端单列桌面端2x2 */}
<div className="w-full md:flex-1 grid grid-cols-1 md:grid-cols-2 gap-0 relative overflow-visible">
{features.map((feature, index) => {
const borders = getBorderGradients(index);
return (
<div
key={feature.titleKey}
className="relative group h-full overflow-visible"
style={{ zIndex: 0 }}
>
{/* 顶部边框光效 */}
<div
className="absolute left-0 right-0 h-[1px] z-10 transition-opacity duration-300"
style={{ top: '-1px', background: borders?.horizontalGradient, opacity: borders?.topOpacity ?? 0, pointerEvents: 'none' }}
/>
{/* 右边边框光效 */}
<div
className="absolute top-0 right-0 bottom-0 w-[1px] z-10 transition-opacity duration-300"
style={{ background: borders?.verticalGradient, opacity: borders?.rightOpacity ?? 0, pointerEvents: 'none' }}
/>
{/* 底部边框光效 */}
<div
className="absolute bottom-0 left-0 right-0 h-[1px] z-10 transition-opacity duration-300"
style={{ background: borders?.horizontalGradient, opacity: borders?.bottomOpacity ?? 0, pointerEvents: 'none' }}
/>
{/* 左边边框光效 */}
<div
className="absolute top-0 left-0 bottom-0 w-[1px] z-10 transition-opacity duration-300"
style={{ background: borders?.verticalGradient, opacity: borders?.leftOpacity ?? 0, pointerEvents: 'none' }}
/>
<Card
className={`rounded-none bg-black border-[#222222] transition-all duration-300 ${
animate ? 'translate-y-0 opacity-100' : 'translate-y-12 opacity-0'
}`}
shadow="none"
style={{
borderStyle: 'solid',
borderWidth: '0px 0px 1px 0px',
height: '100%',
transitionDelay: animate ? `${index * 150}ms` : '0ms'
}}
onMouseMove={(e: React.MouseEvent<HTMLDivElement>) => handleCardMouseMove(e, index)}
onMouseLeave={() => handleCardMouseLeave(index)}
>
<CardBody className="p-8 md:p-[72px_48px] flex items-start justify-center overflow-hidden">
{feature.special ? (
<div className="flex flex-col gap-6 items-start justify-start self-stretch">
<div className="flex flex-col gap-4 items-start justify-start self-stretch">
<h3
className="text-[#fcfcfd] text-left relative self-stretch font-domine"
style={{ fontSize: 'clamp(18px, 1.5vw, 28px)', lineHeight: '140%' }}
>
{t(feature.titleKey)}
</h3>
<feature.Icon
size={24}
className="flex-shrink-0 text-[#fcfcfd] transition-transform duration-300 ease-out"
style={{
transform: cardHovers[index] ? 'translateX(6px)' : 'translateX(0px)'
}}
/>
</div>
</div>
) : (
<div className="flex flex-col gap-6 items-start justify-start self-stretch">
<feature.Icon size={32} className="flex-shrink-0 text-[#fcfcfd]" />
<div className="flex flex-col gap-2 items-start justify-start self-stretch">
<h3
className="text-[#fcfcfd] text-left relative self-stretch font-domine font-bold"
style={{
fontSize: 'clamp(18px, 1.5vw, 28px)',
lineHeight: '130%',
letterSpacing: '-0.005em'
}}
>
{t(feature.titleKey)}
</h3>
<p
className="text-[#9ca1af] text-left relative self-stretch font-domine text-sm"
style={{ lineHeight: '150%' }}
>
{feature.descKey && t(feature.descKey)}
</p>
</div>
</div>
)}
</CardBody>
</Card>
</div>
);
})}
</div>
</div>
<div className="flex-shrink-0 w-full max-w-[1200px] px-6 md:px-0" style={{ height: 'auto' }}>
<Image
src="/logo1.svg"
alt="Logo"
width={1200}
height={187}
className="w-full h-auto object-contain"
/>
</div>
</section>
);
}

View File

@@ -0,0 +1,139 @@
'use client';
import { motion } from 'framer-motion';
import { Card, CardBody, Avatar, AvatarGroup } from '@heroui/react';
import { useLanguage } from '@/contexts/LanguageContext';
import { useTheme } from '@/contexts/ThemeContext';
import { useCountUp } from '@/hooks/useCountUp';
// 格式化数字 - 确保始终显示正确的单位
function formatNumber(num: number, prefix: string = '', suffix: string = '', forceUnit: 'M' | 'K' | '' = '') {
if (forceUnit === 'M') {
return `${prefix}${Math.floor(num)}${suffix}`;
} else if (forceUnit === 'K') {
return `${prefix}${Math.floor(num)}${suffix}`;
} else if (num >= 1000000) {
return `${prefix}${Math.floor(num / 1000000)}M${suffix}`;
} else if (num >= 1000) {
return `${prefix}${Math.floor(num / 1000)}K${suffix}`;
}
return `${prefix}${num.toLocaleString()}${suffix}`;
}
export default function StatsSection() {
const { t } = useLanguage();
const { theme } = useTheme();
const tvl = useCountUp(485, 1500, 0.80);
const apy = useCountUp(515, 1500, 0.85);
const yield_ = useCountUp(45, 1500, 0.75);
const users = useCountUp(23928, 1800, 0.80);
const isDark = theme === 'dark';
return (
<section
className="w-full overflow-x-clip border-b transition-colors bg-bg-base border-border-normal flex-shrink-0 flex justify-center"
style={{ paddingLeft: 'clamp(16px, 4vw, 80px)', paddingRight: 'clamp(16px, 4vw, 80px)' }}
>
<div className="w-full max-w-[1440px] 2xl:max-w-[1760px] 3xl:max-w-[2200px] grid grid-cols-2 md:grid-cols-4">
{/* TVL */}
<Card
className="rounded-none border-r border-b md:border-b-0 transition-colors bg-bg-base border-border-normal"
shadow="none"
>
<CardBody className="flex flex-col gap-2 items-start justify-center py-4 md:py-0 md:h-[182px] px-4 md:px-0">
<p className="font-domine transition-colors text-text-secondary" style={{ fontSize: 'clamp(14px, 1vw, 20px)' }}>
{t('stats.tvl')}
</p>
<div
ref={tvl.elementRef}
className="font-extrabold font-domine transition-colors text-text-primary"
style={{ fontSize: 'clamp(20px, 2.5vw, 48px)', lineHeight: '110%', letterSpacing: '-0.01em' }}
>
{formatNumber(tvl.count, '$', 'M+', 'M')}
</div>
</CardBody>
</Card>
{/* APY */}
<Card
className={`rounded-none border-b md:border-b-0 md:border-r transition-colors ${
isDark ? 'bg-[#0a0a0a] border-[#27272a]' : 'bg-white border-[#e5e7eb]'
}`}
shadow="none"
>
<CardBody className="flex flex-col gap-2 items-start justify-center py-4 md:py-0 md:h-[182px] px-4 md:px-8">
<p className="font-domine transition-colors text-text-secondary" style={{ fontSize: 'clamp(14px, 1vw, 20px)' }}>
{t('stats.apy')}
</p>
<div
ref={apy.elementRef}
className="font-extrabold font-domine transition-colors"
style={{ fontSize: 'clamp(20px, 2.5vw, 48px)', lineHeight: '110%', letterSpacing: '-0.01em', color: '#059669' }}
>
{(apy.count / 100).toFixed(2)}%
</div>
</CardBody>
</Card>
{/* Yield */}
<Card
className={`rounded-none border-r transition-colors ${
isDark ? 'bg-[#0a0a0a] border-[#27272a]' : 'bg-white border-[#e5e7eb]'
}`}
shadow="none"
>
<CardBody className="flex flex-col gap-2 items-start justify-center py-4 md:py-0 md:h-[182px] px-4 md:px-8">
<p className="font-domine transition-colors text-text-secondary" style={{ fontSize: 'clamp(14px, 1vw, 20px)' }}>
{t('stats.yield')}
</p>
<div
ref={yield_.elementRef}
className="font-extrabold font-domine transition-colors text-text-primary"
style={{ fontSize: 'clamp(20px, 2.5vw, 48px)', lineHeight: '110%', letterSpacing: '-0.01em' }}
>
{formatNumber(yield_.count, '$', 'M', 'M')}
</div>
</CardBody>
</Card>
{/* Users */}
<Card
className="rounded-none transition-colors bg-bg-base overflow-hidden"
shadow="none"
>
<CardBody className="flex flex-col gap-2 items-start justify-center py-4 md:py-0 md:h-[182px] px-4 md:pl-8 overflow-hidden">
<p className="font-domine transition-colors text-text-secondary" style={{ fontSize: 'clamp(14px, 1vw, 20px)' }}>
{t('stats.users')}
</p>
<div className="flex flex-row items-center gap-4">
<div
ref={users.elementRef}
className="text-xl md:text-2xl xl:text-4xl font-extrabold font-domine transition-colors text-text-primary"
style={{ lineHeight: '110%', letterSpacing: '-0.01em' }}
>
{users.count.toLocaleString()}+
</div>
<motion.div
className="hidden lg:block"
initial={{ y: '3rem', opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.7, ease: 'easeOut', delay: 0.5 }}
>
<AvatarGroup max={4}>
<Avatar src="/image-230.png" size="sm" />
<Avatar src="/image-240.png" size="sm" />
<Avatar src="/image-250.png" size="sm" />
<Avatar src="/image-251.png" size="sm" />
</AvatarGroup>
</motion.div>
</div>
</CardBody>
</Card>
</div>
</section>
);
}

View 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>
);
}

View File

@@ -0,0 +1,256 @@
'use client';
import { useRef } from 'react';
import { motion, useInView } from 'framer-motion';
import Image from 'next/image';
import { Card, CardBody, Chip } from '@heroui/react';
import { TrendingUp, ShieldCheck, ArrowLeftRight, Lock } from 'lucide-react';
import { useLanguage } from '@/contexts/LanguageContext';
export default function WhyAssetXSection() {
const { t } = useLanguage();
const sectionRef = useRef<HTMLElement>(null);
const isInView = useInView(sectionRef, { once: true, amount: 0.2 });
return (
<section
ref={sectionRef}
className="flex flex-row items-center justify-center flex-wrap flex-shrink-0 w-full relative bg-bg-base"
style={{
padding: 'clamp(40px, 5vw, 100px) clamp(24px, 4vw, 80px)',
gap: 'clamp(24px, 3vw, 64px)',
alignContent: 'center'
}}
>
<div className="flex flex-col items-center justify-center w-full max-w-[1440px] 2xl:max-w-[1760px] 3xl:max-w-[2200px]">
<div className="flex flex-col gap-2 items-start justify-start flex-shrink-0 w-full relative mb-10">
<motion.h2
className="text-left relative font-domine font-bold text-text-primary"
style={{
fontSize: 'clamp(36px, 3.5vw, 80px)',
lineHeight: '120%',
letterSpacing: '-0.01em'
}}
initial={{ opacity: 0, y: 24 }}
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 24 }}
transition={{ duration: 0.6, ease: 'easeOut' }}
>
{t('why.title')}
</motion.h2>
<motion.p
className="text-left relative font-domine text-text-tertiary"
style={{ fontSize: 'clamp(16px, 1.2vw, 22px)', lineHeight: '140%' }}
initial={{ opacity: 0, y: 24 }}
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 24 }}
transition={{ duration: 0.6, ease: 'easeOut', delay: 0.15 }}
>
{t('why.subtitle')}
</motion.p>
</div>
<div className="flex flex-col md:flex-row gap-6 items-stretch justify-start w-full relative">
<motion.div
initial={{ opacity: 0, y: 32 }}
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 32 }}
transition={{ duration: 0.6, ease: 'easeOut', delay: 0.2 }}
className="flex-1"
>
<Card
className="self-stretch hover:-translate-y-2 bg-bg-surface border-border-normal h-full"
shadow="sm"
style={{
transition: 'transform 0.3s ease-out, box-shadow 0.3s ease-out',
willChange: 'transform'
}}
>
<CardBody className="flex flex-col items-start justify-between p-6 md:p-10 gap-6">
<div className="flex flex-col gap-6 items-start justify-start self-stretch">
<div className="rounded-2xl flex items-center justify-center w-12 h-12 bg-bg-elevated">
<TrendingUp size={24} className="text-text-primary" />
</div>
<div className="flex flex-col gap-4 items-start justify-start self-stretch">
<h3 className="text-2xl font-bold font-domine text-text-primary"
style={{
lineHeight: '130%',
letterSpacing: '-0.005em'
}}
>
{t('why.sustainable.title')}
</h3>
<p className="text-base font-domine text-text-tertiary"
style={{
lineHeight: '150%'
}}
>
{t('why.sustainable.desc')}
</p>
</div>
</div>
<Image
src="/frame-110.svg"
alt="Chart"
width={305}
height={162}
className="w-full h-auto"
/>
</CardBody>
</Card>
</motion.div>
<div className="flex flex-col gap-6 items-start justify-start w-full md:flex-1 relative self-stretch">
<motion.div
initial={{ opacity: 0, y: 32 }}
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 32 }}
transition={{ duration: 0.6, ease: 'easeOut', delay: 0.35 }}
className="w-full flex-1 flex flex-col"
>
<Card
className="self-stretch hover:-translate-y-2 bg-bg-surface border-border-normal flex-1"
shadow="sm"
style={{
transition: 'transform 0.3s ease-out, box-shadow 0.3s ease-out',
willChange: 'transform'
}}
>
<CardBody className="flex flex-col md:flex-row items-start md:items-center justify-between p-6 md:p-10 gap-6 md:gap-0">
<div className="flex flex-col gap-6 items-start justify-start w-full md:w-[414px]">
<div className="rounded-2xl flex items-center justify-center w-12 h-12 bg-bg-elevated">
<ShieldCheck size={24} className="text-text-primary" />
</div>
<div className="flex flex-col gap-4 items-start justify-start self-stretch">
<h3 className="text-2xl font-bold font-domine text-text-primary"
style={{
lineHeight: '130%',
letterSpacing: '-0.005em'
}}
>
{t('why.reliability.title')}
</h3>
<p className="text-base font-domine text-text-tertiary"
style={{
lineHeight: '150%'
}}
>
{t('why.reliability.desc')}
</p>
</div>
</div>
<div
className="relative bg-bg-elevated border border-border-strong rounded-2xl mx-auto md:mx-0"
style={{
width: '196px',
height: '180px',
flexShrink: 0
}}
>
{/* 外层圆环 */}
<div
className="absolute rounded-full border-2 border-border-strong"
style={{
width: '132px',
height: '132px',
left: '50%',
top: '50%',
transform: 'translate(-50%, -50%)'
}}
>
{/* 内层圆 */}
<div
className="absolute rounded-full bg-border-strong"
style={{
width: '88.59px',
height: '88.59px',
left: '50%',
top: '50%',
transform: 'translate(-50%, -50%)'
}}
>
{/* 图标 */}
<Lock
size={44}
className="text-white"
style={{
position: 'absolute',
left: '50%',
top: '50%',
transform: 'translate(-50%, -50%)'
}}
/>
</div>
</div>
</div>
</CardBody>
</Card>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 32 }}
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 32 }}
transition={{ duration: 0.6, ease: 'easeOut', delay: 0.5 }}
className="w-full flex-1 flex flex-col"
>
<Card
className="self-stretch hover:-translate-y-2 bg-bg-surface border-border-normal flex-1"
shadow="sm"
style={{
transition: 'transform 0.3s ease-out, box-shadow 0.3s ease-out',
willChange: 'transform'
}}
>
<CardBody className="flex flex-col md:flex-row items-start justify-between p-6 md:p-10 gap-6 md:gap-0">
<div className="flex flex-col gap-6 items-start justify-start w-full md:w-[550px]">
<div className="rounded-2xl flex items-center justify-center w-12 h-12 bg-bg-elevated">
<ArrowLeftRight size={24} className="text-text-primary" />
</div>
<div className="flex flex-col gap-4 items-start justify-start">
<h3 className="text-2xl font-bold font-domine text-text-primary"
style={{
lineHeight: '130%',
letterSpacing: '-0.005em'
}}
>
{t('why.liquidity.title')}
</h3>
<p className="text-base font-domine text-text-tertiary"
style={{
lineHeight: '150%'
}}
>
{t('why.liquidity.desc')}
</p>
</div>
</div>
<div className="flex flex-row gap-3 items-center justify-end">
<Chip
className="bg-accent-green-bg text-accent-green font-bold"
size="sm"
>
{t('why.liquidity.badge1')}
</Chip>
<Chip
className="bg-accent-blue-bg text-accent-blue font-bold"
size="sm"
>
{t('why.liquidity.badge2')}
</Chip>
</div>
</CardBody>
</Card>
</motion.div>
</div>
</div>
</div>
</section>
);
}