init: 初始化 AssetX 项目仓库
包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、 antdesign(管理后台)、landingpage(营销落地页)、 数据库 SQL 和配置文件。
This commit is contained in:
141
landingpage/components/ConsenSysLogo.tsx
Normal file
141
landingpage/components/ConsenSysLogo.tsx
Normal file
File diff suppressed because one or more lines are too long
181
landingpage/components/Footer.tsx
Normal file
181
landingpage/components/Footer.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
40
landingpage/components/HeroButtons.tsx
Normal file
40
landingpage/components/HeroButtons.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
71
landingpage/components/HeroSection.tsx
Normal file
71
landingpage/components/HeroSection.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
40
landingpage/components/HeroTitle.tsx
Normal file
40
landingpage/components/HeroTitle.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
485
landingpage/components/HowItWorksSection.tsx
Normal file
485
landingpage/components/HowItWorksSection.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
726
landingpage/components/Navbar.tsx
Normal file
726
landingpage/components/Navbar.tsx
Normal 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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
274
landingpage/components/ProductMenu.tsx
Normal file
274
landingpage/components/ProductMenu.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
18
landingpage/components/Providers.tsx
Normal file
18
landingpage/components/Providers.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
280
landingpage/components/ResourceMenu.tsx
Normal file
280
landingpage/components/ResourceMenu.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
247
landingpage/components/SecuritySection.tsx
Normal file
247
landingpage/components/SecuritySection.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
139
landingpage/components/StatsSection.tsx
Normal file
139
landingpage/components/StatsSection.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
256
landingpage/components/WhyAssetXSection.tsx
Normal file
256
landingpage/components/WhyAssetXSection.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user