打磨细节
This commit is contained in:
141
components/ConsenSysLogo.tsx
Normal file
141
components/ConsenSysLogo.tsx
Normal file
File diff suppressed because one or more lines are too long
@@ -1,30 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
import { useLanguage } from '@/contexts/LanguageContext';
|
||||
|
||||
export default function Footer() {
|
||||
const productLinks = [
|
||||
'AX-Fund',
|
||||
'AX-Matrix',
|
||||
'Launchpad',
|
||||
'Liquid Market',
|
||||
'Token Factory'
|
||||
];
|
||||
|
||||
const resourceLinks = [
|
||||
'Docs',
|
||||
'FAQ & Support',
|
||||
'Trust & Security',
|
||||
'Learning Center',
|
||||
'Community Forum'
|
||||
];
|
||||
|
||||
const companyLinks = [
|
||||
'About Team',
|
||||
'Careers',
|
||||
'Contact Us',
|
||||
'Press & Media'
|
||||
];
|
||||
const { t } = useLanguage();
|
||||
|
||||
const socialIcons = [
|
||||
{ src: '/component-12.svg', alt: 'Twitter', width: 24, height: 24 },
|
||||
@@ -36,7 +16,7 @@ export default function Footer() {
|
||||
return (
|
||||
<footer className="bg-white border-t border-[#e5e7eb] w-full flex flex-col items-center">
|
||||
{/* Main Footer Content */}
|
||||
<div className="w-full max-w-[1280px] px-6 py-20 flex flex-row gap-8">
|
||||
<div className="w-full max-w-[1440px] px-6 py-20 flex flex-row gap-8">
|
||||
|
||||
{/* Left Section - Logo, Address, Social Icons */}
|
||||
<div className="flex flex-col gap-6 w-[389.33px]">
|
||||
@@ -95,12 +75,12 @@ export default function Footer() {
|
||||
fontWeight: 700
|
||||
}}
|
||||
>
|
||||
Products
|
||||
{t('footer.products')}
|
||||
</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{productLinks.map((link, index) => (
|
||||
{[1, 2, 3, 4, 5].map((num) => (
|
||||
<a
|
||||
key={index}
|
||||
key={num}
|
||||
href="#"
|
||||
className="text-[#9ca1af] hover:text-[#111827] transition-colors font-inter cursor-pointer"
|
||||
style={{
|
||||
@@ -109,7 +89,7 @@ export default function Footer() {
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
{link}
|
||||
{t(`footer.product${num}`)}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
@@ -125,12 +105,12 @@ export default function Footer() {
|
||||
fontWeight: 700
|
||||
}}
|
||||
>
|
||||
Resources
|
||||
{t('footer.resources')}
|
||||
</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{resourceLinks.map((link, index) => (
|
||||
{[1, 2, 3, 4, 5].map((num) => (
|
||||
<a
|
||||
key={index}
|
||||
key={num}
|
||||
href="#"
|
||||
className="text-[#9ca1af] hover:text-[#111827] transition-colors font-inter cursor-pointer"
|
||||
style={{
|
||||
@@ -139,7 +119,7 @@ export default function Footer() {
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
{link}
|
||||
{t(`footer.resource${num}`)}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
@@ -155,12 +135,12 @@ export default function Footer() {
|
||||
fontWeight: 700
|
||||
}}
|
||||
>
|
||||
Company
|
||||
{t('footer.company')}
|
||||
</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{companyLinks.map((link, index) => (
|
||||
{[1, 2, 3, 4].map((num) => (
|
||||
<a
|
||||
key={index}
|
||||
key={num}
|
||||
href="#"
|
||||
className="text-[#9ca1af] hover:text-[#111827] transition-colors font-inter cursor-pointer"
|
||||
style={{
|
||||
@@ -169,7 +149,7 @@ export default function Footer() {
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
{link}
|
||||
{t(`footer.company${num}`)}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
@@ -178,7 +158,7 @@ export default function Footer() {
|
||||
</div>
|
||||
|
||||
{/* Bottom Section - Copyright and Legal Links */}
|
||||
<div className="w-full max-w-[1280px] border-t border-[#f3f4f6] px-6 py-8 flex flex-row items-center justify-between">
|
||||
<div className="w-full max-w-[1440px] border-t border-[#f3f4f6] px-6 py-8 flex flex-row items-center justify-between">
|
||||
{/* Copyright */}
|
||||
<div
|
||||
className="text-[#9ca1af] font-inter"
|
||||
@@ -188,7 +168,7 @@ export default function Footer() {
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
© 2025 ASSETX Protocol. All rights reserved.
|
||||
{t('footer.copyright')}
|
||||
</div>
|
||||
|
||||
{/* Legal Links */}
|
||||
@@ -202,7 +182,7 @@ export default function Footer() {
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
Privacy Policy
|
||||
{t('footer.privacy')}
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
@@ -213,7 +193,7 @@ export default function Footer() {
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
Terms of Service
|
||||
{t('footer.terms')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
86
components/HeroButtons.tsx
Normal file
86
components/HeroButtons.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useLanguage } from '@/contexts/LanguageContext';
|
||||
|
||||
export default function HeroButtons() {
|
||||
const { t } = useLanguage();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('HeroButtons mounted');
|
||||
// 延迟一些时间后再触发按钮动画,形成渐进效果
|
||||
const timer = setTimeout(() => {
|
||||
console.log('HeroButtons animation starting');
|
||||
setMounted(true);
|
||||
}, 500);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row gap-5 items-start justify-start flex-shrink-0 relative"
|
||||
style={{
|
||||
transform: mounted ? 'translateY(0)' : 'translateY(3rem)',
|
||||
opacity: mounted ? 1 : 0,
|
||||
transition: 'all 1s ease-out'
|
||||
}}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
{/* .component-82 - Start Investing Button */}
|
||||
<button
|
||||
className="bg-white flex flex-row gap-2 items-center justify-center flex-shrink-0 h-[60px] relative overflow-hidden hover:bg-[#f3f4f6] transition-colors"
|
||||
style={{
|
||||
borderRadius: '4px',
|
||||
padding: '0px 32px'
|
||||
}}
|
||||
>
|
||||
{/* .text4 */}
|
||||
<div
|
||||
className="text-[#111827] text-center relative flex items-center justify-center font-inter"
|
||||
style={{
|
||||
fontSize: '18px',
|
||||
lineHeight: '150%',
|
||||
fontWeight: 700
|
||||
}}
|
||||
>
|
||||
{t('hero.startInvesting')}
|
||||
</div>
|
||||
|
||||
{/* .component-1 - Arrow icon */}
|
||||
<Image
|
||||
src="/component-10.svg"
|
||||
alt="Arrow"
|
||||
width={20}
|
||||
height={20}
|
||||
className="flex-shrink-0 w-5 h-5 relative overflow-visible"
|
||||
style={{ aspectRatio: 1 }}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{/* .component-9 - Read the Whitepaper Button */}
|
||||
<button
|
||||
className="border border-white/20 flex flex-row gap-2 items-center justify-center self-stretch flex-shrink-0 relative overflow-hidden hover:bg-white/70 hover:border-white/60 transition-all"
|
||||
style={{
|
||||
background: 'rgba(255, 255, 255, 0.1)',
|
||||
borderRadius: '4px',
|
||||
padding: '12px 32px',
|
||||
backdropFilter: 'blur(30px)'
|
||||
}}
|
||||
>
|
||||
{/* .text5 */}
|
||||
<div
|
||||
className="text-[#fcfcfd] text-center relative flex items-center justify-center font-inter"
|
||||
style={{
|
||||
fontSize: '18px',
|
||||
lineHeight: '150%',
|
||||
fontWeight: 700
|
||||
}}
|
||||
>
|
||||
{t('hero.readWhitepaper')}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,125 +1,39 @@
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
import HeroTitle from './HeroTitle';
|
||||
import HeroButtons from './HeroButtons';
|
||||
|
||||
export default function HeroSection() {
|
||||
|
||||
return (
|
||||
<section className="relative w-full">
|
||||
<section className="relative w-full" style={{ overflow: 'hidden' }} suppressHydrationWarning>
|
||||
{/* Background Video */}
|
||||
<video
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
style={{ zIndex: 0 }}
|
||||
>
|
||||
<source src="/hero-background.mp4" type="video/mp4" />
|
||||
</video>
|
||||
|
||||
{/* .header - 背景容器 */}
|
||||
<div
|
||||
className="flex flex-col items-start justify-center flex-shrink-0 h-[670px] relative"
|
||||
className="flex flex-col items-start justify-center flex-shrink-0 relative"
|
||||
style={{
|
||||
background: 'linear-gradient(to left, rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0.55))',
|
||||
padding: '114px 85px'
|
||||
padding: '114px 85px 164px 85px',
|
||||
minHeight: '670px',
|
||||
zIndex: 1
|
||||
}}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
{/* .frame-21 - 主内容容器 */}
|
||||
<div className="flex flex-col gap-[60px] flex-shrink-0 w-[926px] relative">
|
||||
|
||||
{/* .frame-25 - 标题和描述区域 */}
|
||||
<div className="flex flex-col gap-8 items-start justify-start self-stretch flex-shrink-0 relative">
|
||||
|
||||
{/* .frame-22 - 标题容器 */}
|
||||
<div className="flex flex-col gap-4 items-start justify-start self-stretch flex-shrink-0 relative">
|
||||
{/* .yield-bearing-asset */}
|
||||
<div
|
||||
className="text-[#fcfcfd] text-left relative self-stretch font-domine"
|
||||
style={{
|
||||
fontSize: '100px',
|
||||
lineHeight: '100%',
|
||||
letterSpacing: '-0.03em',
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
Yield-Bearing Asset
|
||||
</div>
|
||||
|
||||
{/* .on-chain */}
|
||||
<div
|
||||
className="text-[#fcfcfd] text-left relative w-[926px] h-[100px] font-domine"
|
||||
style={{
|
||||
fontSize: '100px',
|
||||
lineHeight: '100%',
|
||||
letterSpacing: '-0.03em',
|
||||
fontWeight: 700
|
||||
}}
|
||||
>
|
||||
On Chain.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description text */}
|
||||
<div
|
||||
className="text-[#fcfcfd] text-left relative w-[488px] flex items-center justify-start font-domine"
|
||||
style={{
|
||||
fontSize: '16px',
|
||||
lineHeight: '150%',
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
Access Yield- Bearing Asset like Equities, Real Estate, and
|
||||
Commercial Loans with low barriers. Enjoy 10%-30% APY backed by
|
||||
real assets.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* .frame-23 - 按钮容器 */}
|
||||
<div className="flex flex-row gap-5 items-start justify-start flex-shrink-0 relative">
|
||||
|
||||
{/* .component-82 - Start Investing Button */}
|
||||
<button
|
||||
className="bg-white flex flex-row gap-2 items-center justify-center flex-shrink-0 h-[60px] relative overflow-hidden hover:opacity-90 transition-opacity"
|
||||
style={{
|
||||
borderRadius: '4px',
|
||||
padding: '0px 32px'
|
||||
}}
|
||||
>
|
||||
{/* .text4 */}
|
||||
<div
|
||||
className="text-[#111827] text-center relative flex items-center justify-center font-inter"
|
||||
style={{
|
||||
fontSize: '18px',
|
||||
lineHeight: '150%',
|
||||
fontWeight: 700
|
||||
}}
|
||||
>
|
||||
Start Investing
|
||||
</div>
|
||||
|
||||
{/* .component-1 - Arrow icon */}
|
||||
<Image
|
||||
src="/component-10.svg"
|
||||
alt="Arrow"
|
||||
width={20}
|
||||
height={20}
|
||||
className="flex-shrink-0 w-5 h-5 relative overflow-visible"
|
||||
style={{ aspectRatio: 1 }}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{/* .component-9 - Read the Whitepaper Button */}
|
||||
<button
|
||||
className="border border-white/20 flex flex-row gap-2 items-center justify-center self-stretch flex-shrink-0 relative overflow-hidden hover:bg-white/20 transition-colors"
|
||||
style={{
|
||||
background: 'rgba(255, 255, 255, 0.1)',
|
||||
borderRadius: '4px',
|
||||
padding: '12px 32px',
|
||||
backdropFilter: 'blur(30px)'
|
||||
}}
|
||||
>
|
||||
{/* .text5 */}
|
||||
<div
|
||||
className="text-[#fcfcfd] text-center relative flex items-center justify-center font-inter"
|
||||
style={{
|
||||
fontSize: '18px',
|
||||
lineHeight: '150%',
|
||||
fontWeight: 700
|
||||
}}
|
||||
>
|
||||
Read the Whitepaper
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<HeroTitle />
|
||||
<HeroButtons />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
71
components/HeroTitle.tsx
Normal file
71
components/HeroTitle.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useLanguage } from '@/contexts/LanguageContext';
|
||||
|
||||
export default function HeroTitle() {
|
||||
const { t } = useLanguage();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('HeroTitle mounted');
|
||||
const timer = setTimeout(() => {
|
||||
console.log('HeroTitle animation starting');
|
||||
setMounted(true);
|
||||
}, 400);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col gap-8 items-start justify-start self-stretch flex-shrink-0 relative"
|
||||
style={{
|
||||
transform: mounted ? 'translateY(0)' : 'translateY(3rem)',
|
||||
opacity: mounted ? 1 : 0,
|
||||
transition: 'all 1s ease-out'
|
||||
}}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
{/* .frame-22 - 标题容器 */}
|
||||
<div className="flex flex-col gap-4 items-start justify-start self-stretch flex-shrink-0 relative">
|
||||
{/* .yield-bearing-asset */}
|
||||
<div
|
||||
className="text-[#fcfcfd] text-left relative self-stretch font-domine"
|
||||
style={{
|
||||
fontSize: '100px',
|
||||
lineHeight: '100%',
|
||||
letterSpacing: '-0.03em',
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
{t('hero.title1')}
|
||||
</div>
|
||||
|
||||
{/* .on-chain */}
|
||||
<div
|
||||
className="text-[#fcfcfd] text-left relative w-[926px] h-[100px] font-domine"
|
||||
style={{
|
||||
fontSize: '100px',
|
||||
lineHeight: '100%',
|
||||
letterSpacing: '-0.03em',
|
||||
fontWeight: 700
|
||||
}}
|
||||
>
|
||||
{t('hero.title2')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description text */}
|
||||
<div
|
||||
className="text-[#fcfcfd] text-left relative w-[488px] flex items-center justify-start font-domine"
|
||||
style={{
|
||||
fontSize: '16px',
|
||||
lineHeight: '150%',
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
{t('hero.description')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useLanguage } from '@/contexts/LanguageContext';
|
||||
|
||||
// 数字增长动画Hook
|
||||
function useCountUp(end: number, duration: number = 1500, startRangePercent: number = 0.75) {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [count, setCount] = useState(end);
|
||||
const [hasAnimated, setHasAnimated] = useState(false);
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
const startValueRef = useRef<number>(end);
|
||||
const animationRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
@@ -22,8 +23,7 @@ function useCountUp(end: number, duration: number = 1500, startRangePercent: num
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting && !hasAnimated) {
|
||||
setHasAnimated(true);
|
||||
if (entries[0].isIntersecting) {
|
||||
const start = startValueRef.current;
|
||||
const startTime = Date.now();
|
||||
|
||||
@@ -42,7 +42,13 @@ function useCountUp(end: number, duration: number = 1500, startRangePercent: num
|
||||
}
|
||||
}, 16);
|
||||
|
||||
animationRef.current = timer as unknown as number;
|
||||
return () => clearInterval(timer);
|
||||
} else {
|
||||
if (animationRef.current) {
|
||||
clearInterval(animationRef.current);
|
||||
}
|
||||
setCount(startValueRef.current);
|
||||
}
|
||||
},
|
||||
{ threshold: 0.1 }
|
||||
@@ -53,14 +59,14 @@ function useCountUp(end: number, duration: number = 1500, startRangePercent: num
|
||||
}
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [end, duration, hasAnimated, mounted]);
|
||||
}, [end, duration, mounted]);
|
||||
|
||||
return { count, elementRef };
|
||||
}
|
||||
|
||||
export default function HowItWorksSection() {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { t } = useLanguage();
|
||||
const [animate, setAnimate] = useState(false);
|
||||
const [activeStep, setActiveStep] = useState(1); // 默认第1步激活
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
|
||||
@@ -69,49 +75,40 @@ export default function HowItWorksSection() {
|
||||
const earnAmount = useCountUp(5150, 1500, 0.85);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
const currentRef = sectionRef.current;
|
||||
if (!currentRef) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting && !visible) {
|
||||
setVisible(true);
|
||||
}
|
||||
});
|
||||
if (entries[0].isIntersecting) {
|
||||
setAnimate(true);
|
||||
observer.disconnect();
|
||||
}
|
||||
},
|
||||
{
|
||||
threshold: 0.1,
|
||||
}
|
||||
{ threshold: 0.3 }
|
||||
);
|
||||
|
||||
if (sectionRef.current) {
|
||||
observer.observe(sectionRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (sectionRef.current) {
|
||||
observer.unobserve(sectionRef.current);
|
||||
}
|
||||
};
|
||||
}, [visible]);
|
||||
observer.observe(currentRef);
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
const steps = [
|
||||
{
|
||||
number: 1,
|
||||
title: 'Deposit & Mint WUSD',
|
||||
description: 'Connect your wallet and deposit USDC to swap WUSD. This serves as the native stablecoin and gateway to the entire AssetX ecosystem.',
|
||||
title: t('how.step1.title'),
|
||||
description: t('how.step1.desc'),
|
||||
hasLine: true
|
||||
},
|
||||
{
|
||||
number: 2,
|
||||
title: 'Dual Investment Options',
|
||||
description: 'Choose your strategy: use WUSD to purchase specific Yield-Bearing Assets for real-world returns, or provide liquidity in DeFi Pools to earn trading fees.',
|
||||
title: t('how.step2.title'),
|
||||
description: t('how.step2.desc'),
|
||||
hasLine: true
|
||||
},
|
||||
{
|
||||
number: 3,
|
||||
title: 'Earn & Boost',
|
||||
description: 'Receive daily yield distributions. Use Asset Tokens as collateral to borrow WUSD and leverage up to 2.5x for maximized returns.',
|
||||
title: t('how.step3.title'),
|
||||
description: t('how.step3.desc'),
|
||||
hasLine: false
|
||||
}
|
||||
];
|
||||
@@ -128,7 +125,7 @@ export default function HowItWorksSection() {
|
||||
}}
|
||||
>
|
||||
{/* .container20 - Main container */}
|
||||
<div className="flex flex-row items-start justify-between flex-shrink-0 relative w-[1200px]">
|
||||
<div className="flex flex-row items-start justify-between flex-shrink-0 relative w-[1440px]">
|
||||
|
||||
{/* Left Side - Steps (.container21) */}
|
||||
<div className="flex flex-col gap-10 items-start justify-start flex-shrink-0 relative w-[520px]">
|
||||
@@ -144,7 +141,7 @@ export default function HowItWorksSection() {
|
||||
fontWeight: 700
|
||||
}}
|
||||
>
|
||||
How it works
|
||||
{t('how.title')}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -159,7 +156,7 @@ export default function HowItWorksSection() {
|
||||
key={step.number}
|
||||
onClick={() => setActiveStep(step.number)}
|
||||
className={`flex flex-row gap-6 items-start justify-start flex-shrink-0 relative cursor-pointer transition-all duration-300 ease-out hover:opacity-80 ${
|
||||
mounted && visible
|
||||
animate
|
||||
? 'translate-x-0 opacity-100'
|
||||
: '-translate-x-12 opacity-0'
|
||||
}`}
|
||||
@@ -224,12 +221,13 @@ export default function HowItWorksSection() {
|
||||
}}
|
||||
>
|
||||
<h3
|
||||
className="text-[#111827] text-left relative flex items-center justify-start font-inter transition-all duration-300"
|
||||
className="text-left relative flex items-center justify-start font-inter transition-all duration-300"
|
||||
style={{
|
||||
fontSize: '24px',
|
||||
fontSize: '28px',
|
||||
lineHeight: '130%',
|
||||
letterSpacing: '-0.005em',
|
||||
fontWeight: isActive ? 700 : 500
|
||||
fontWeight: 600,
|
||||
color: isActive ? '#111827' : '#6b7280'
|
||||
}}
|
||||
>
|
||||
{step.title}
|
||||
@@ -251,30 +249,33 @@ export default function HowItWorksSection() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Side - Calculator Card */}
|
||||
{/* Right Side - Dynamic Cards Container */}
|
||||
<div
|
||||
className={`calculator-card-container flex flex-col items-start justify-start flex-shrink-0 w-[558px] relative transition-all duration-700 ease-out ${
|
||||
mounted && visible
|
||||
? 'translate-y-0 opacity-100'
|
||||
: 'translate-y-12 opacity-0'
|
||||
}`}
|
||||
className="calculator-card-container flex flex-col items-start justify-start flex-shrink-0 w-[558px] relative"
|
||||
style={{
|
||||
transitionDelay: '450ms',
|
||||
height: '439px'
|
||||
}}
|
||||
>
|
||||
{/* Coin Images Container - Rotated Card */}
|
||||
<div
|
||||
className="bg-[#111827] rounded-[16px] flex flex-row gap-4 items-start justify-start h-[162px] absolute z-0"
|
||||
style={{
|
||||
padding: '25px 25px 1px 25px',
|
||||
left: '205.43px',
|
||||
top: '15.96px',
|
||||
boxShadow: '0px 25px 50px -12px rgba(0, 0, 0, 0.25)',
|
||||
transformOrigin: '0 0',
|
||||
transform: 'rotate(6.535deg) scale(1, 1)'
|
||||
}}
|
||||
>
|
||||
{/* Step 1: Deposit & Mint WUSD Card */}
|
||||
<>
|
||||
{/* Coin Images Container - Rotated Card */}
|
||||
<div
|
||||
className={`bg-[#111827] rounded-[16px] flex flex-row gap-4 items-start justify-start h-[162px] absolute z-0 transition-all duration-700 ease-out ${
|
||||
animate && activeStep === 1 ? 'opacity-100' : 'opacity-0'
|
||||
}`}
|
||||
style={{
|
||||
padding: '25px 25px 1px 25px',
|
||||
left: '205.43px',
|
||||
top: '15.96px',
|
||||
boxShadow: '0px 25px 50px -12px rgba(0, 0, 0, 0.25)',
|
||||
transformOrigin: '0 0',
|
||||
transform: animate && activeStep === 1
|
||||
? 'rotate(6.535deg) scale(1, 1) translateY(0)'
|
||||
: 'rotate(6.535deg) scale(1, 1) translateY(3rem)',
|
||||
transitionDelay: activeStep === 1 ? '200ms' : '0ms',
|
||||
pointerEvents: activeStep === 1 ? 'auto' : 'none'
|
||||
}}
|
||||
>
|
||||
{/* USDC Logo */}
|
||||
<Image
|
||||
src="/usd-coin-usdc-logo-10.svg"
|
||||
@@ -299,10 +300,16 @@ export default function HowItWorksSection() {
|
||||
|
||||
{/* Calculator Card */}
|
||||
<div
|
||||
className="bg-white/85 backdrop-blur-md rounded-[24px] border border-[#e5e7eb] p-8 absolute left-0 top-[99px] w-full shadow-lg z-10"
|
||||
className={`bg-white/85 backdrop-blur-md rounded-[24px] border border-[#e5e7eb] p-8 absolute left-0 top-[99px] w-full shadow-lg z-10 transition-all duration-700 ease-out ${
|
||||
animate && activeStep === 1 ? 'opacity-100' : 'opacity-0'
|
||||
}`}
|
||||
style={{
|
||||
transform: 'rotate(-3deg)',
|
||||
transformOrigin: 'center center'
|
||||
transform: animate && activeStep === 1
|
||||
? 'rotate(-3deg) translateY(0)'
|
||||
: 'rotate(-3deg) translateY(3rem)',
|
||||
transformOrigin: 'center center',
|
||||
transitionDelay: activeStep === 1 ? '200ms' : '0ms',
|
||||
pointerEvents: activeStep === 1 ? 'auto' : 'none'
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -325,7 +332,7 @@ export default function HowItWorksSection() {
|
||||
fontWeight: 500
|
||||
}}
|
||||
>
|
||||
Portfolio Overview Simulator
|
||||
{t('how.simulator.title')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -345,7 +352,7 @@ export default function HowItWorksSection() {
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
If you invest
|
||||
{t('how.simulator.invest')}
|
||||
</span>
|
||||
<span
|
||||
ref={investAmount.elementRef}
|
||||
@@ -377,7 +384,7 @@ export default function HowItWorksSection() {
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
You earn per year
|
||||
{t('how.simulator.earn')}
|
||||
</span>
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<Image
|
||||
@@ -401,6 +408,286 @@ export default function HowItWorksSection() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
{/* Step 2: Dual Investment Options Card */}
|
||||
<div
|
||||
className={`bg-white rounded-[16px] border border-[#f3f4f6] p-6 flex flex-col gap-6 absolute left-0 top-[99px] w-full transition-all duration-700 ease-out ${
|
||||
animate && activeStep === 2
|
||||
? 'opacity-100'
|
||||
: 'opacity-0'
|
||||
}`}
|
||||
style={{
|
||||
boxShadow: '0px 25px 50px -12px rgba(0, 0, 0, 0.25)',
|
||||
transform: animate && activeStep === 2
|
||||
? 'rotate(3deg) translateY(0)'
|
||||
: 'rotate(3deg) translateY(3rem)',
|
||||
transformOrigin: '0 0',
|
||||
transitionDelay: activeStep === 2 ? '200ms' : '0ms',
|
||||
pointerEvents: activeStep === 2 ? 'auto' : 'none'
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div
|
||||
className="border-b border-[#f3f4f6] pb-4 flex flex-row items-center justify-between w-full"
|
||||
>
|
||||
<span className="text-[#000000] font-inter text-[18px] font-bold leading-7">
|
||||
Fund Market
|
||||
</span>
|
||||
<span className="text-[#059669] font-inter text-[16px] font-bold leading-6">
|
||||
Connect Wallet
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Investment Options Container */}
|
||||
<div className="flex flex-col gap-4 w-full">
|
||||
{/* Option 1 - Active */}
|
||||
<div className="bg-[#f9fafb] rounded-[12px] border border-[#f3f4f6] p-4 flex flex-row gap-4 items-center h-24">
|
||||
{/* Icon */}
|
||||
<div className="bg-[#000000] rounded-full w-12 h-12 flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-white font-inter text-[16px] font-bold">O</span>
|
||||
</div>
|
||||
|
||||
{/* Text Placeholders */}
|
||||
<div className="flex flex-col gap-2 flex-1">
|
||||
<div className="bg-[#e5e7eb] rounded h-4 w-24" />
|
||||
<div className="bg-[#f3f4f6] rounded h-3 w-16" />
|
||||
</div>
|
||||
|
||||
{/* APY Info */}
|
||||
<div className="flex flex-col gap-2 items-end flex-shrink-0">
|
||||
<div className="bg-[#e5e7eb] rounded h-4 w-20" />
|
||||
<span className="text-[#059669] font-inter text-[16px] font-bold leading-6">
|
||||
APY 30.2%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Option 2 - Inactive */}
|
||||
<div className="bg-[#f9fafb] rounded-[12px] border border-[#f3f4f6] p-4 flex flex-row gap-4 items-center h-24 opacity-60">
|
||||
{/* Icon */}
|
||||
<div className="bg-[#d1d5db] rounded-full w-12 h-12 flex-shrink-0" />
|
||||
|
||||
{/* Text Placeholders */}
|
||||
<div className="flex flex-col gap-2 flex-1">
|
||||
<div className="bg-[#e5e7eb] rounded h-4 w-32" />
|
||||
<div className="bg-[#f3f4f6] rounded h-3 w-20" />
|
||||
</div>
|
||||
|
||||
{/* APY Info */}
|
||||
<div className="flex flex-col gap-2 items-end flex-shrink-0">
|
||||
<div className="bg-[#e5e7eb] rounded h-4 w-20" />
|
||||
<span className="text-[#059669] font-inter text-[16px] font-bold leading-6">
|
||||
APY 15.2%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step 3: Earn & Boost Card */}
|
||||
<div
|
||||
className={`bg-white rounded-[16px] border border-[#f3f4f6] p-6 flex flex-col gap-6 absolute left-0 top-[99px] w-[520px] transition-all duration-700 ease-out ${
|
||||
animate && activeStep === 3 ? 'opacity-100' : 'opacity-0'
|
||||
}`}
|
||||
style={{
|
||||
boxShadow: '0px 25px 50px -12px rgba(0, 0, 0, 0.25)',
|
||||
transform: animate && activeStep === 3
|
||||
? 'rotate(3deg) translateY(0)'
|
||||
: 'rotate(3deg) translateY(3rem)',
|
||||
transformOrigin: '0 0',
|
||||
transitionDelay: activeStep === 3 ? '200ms' : '0ms',
|
||||
overflow: 'hidden',
|
||||
pointerEvents: activeStep === 3 ? 'auto' : 'none'
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="border-b border-[#f3f4f6] pb-[17px] flex flex-row items-center justify-between" style={{ width: '470px', height: '45px' }}>
|
||||
<span className="text-[#000000] font-inter text-[18px] font-bold leading-7">
|
||||
Defi
|
||||
</span>
|
||||
<span className="text-[#059669] font-inter text-[16px] font-bold leading-6">
|
||||
+5.2% APY
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Cards Grid Container */}
|
||||
<div
|
||||
className="relative"
|
||||
style={{
|
||||
width: '466px',
|
||||
height: '195px',
|
||||
transform: 'rotate(-3deg)',
|
||||
transformOrigin: '0 0',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
{/* Container 1 */}
|
||||
<div
|
||||
className="absolute"
|
||||
style={{
|
||||
width: '135.53px',
|
||||
height: '165.15px',
|
||||
left: '8.58px',
|
||||
top: '0.46px',
|
||||
transform: 'rotate(3deg)',
|
||||
transformOrigin: '0 0'
|
||||
}}
|
||||
>
|
||||
<div className="bg-[#f9fafb] rounded-[12px] border border-[#f3f4f6] p-4 flex flex-col gap-4 items-center absolute" style={{ width: '134.91px', height: '165.18px', left: '0px', top: '0px' }}>
|
||||
<div className="bg-[#000000] rounded-full w-12 h-12 flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-white font-inter text-[16px] font-bold leading-6">O</span>
|
||||
</div>
|
||||
<span
|
||||
className="text-[#000000] font-inter text-[16px] font-bold text-center"
|
||||
style={{ width: '124px', height: '17px', transform: 'rotate(-3deg)', transformOrigin: '0 0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
|
||||
>
|
||||
+10% APY
|
||||
</span>
|
||||
<div
|
||||
className="bg-[#000000] rounded-lg px-4 py-2"
|
||||
style={{ transform: 'rotate(-3deg)', transformOrigin: '0 0' }}
|
||||
>
|
||||
<span className="text-white font-inter text-[12px] font-bold leading-4">
|
||||
Boost
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Container 2 */}
|
||||
<div
|
||||
className="absolute"
|
||||
style={{
|
||||
width: '135.53px',
|
||||
height: '165.15px',
|
||||
left: '160.18px',
|
||||
top: '11.66px',
|
||||
transform: 'rotate(3deg)',
|
||||
transformOrigin: '0 0'
|
||||
}}
|
||||
>
|
||||
<div className="bg-[#f9fafb] rounded-[12px] border border-[#f3f4f6] p-4 flex flex-col gap-4 items-center absolute" style={{ width: '134.91px', height: '165.18px', left: '0px', top: '0px' }}>
|
||||
<div className="bg-[#000000] rounded-full w-12 h-12 flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-white font-inter text-[16px] font-bold leading-6">O</span>
|
||||
</div>
|
||||
<span
|
||||
className="text-[#000000] font-inter text-[16px] font-bold text-center"
|
||||
style={{ width: '124px', height: '17px', transform: 'rotate(-3deg)', transformOrigin: '0 0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
|
||||
>
|
||||
+10% APY
|
||||
</span>
|
||||
<div
|
||||
className="bg-[#000000] rounded-lg px-4 py-2"
|
||||
style={{ transform: 'rotate(-3deg)', transformOrigin: '0 0' }}
|
||||
>
|
||||
<span className="text-white font-inter text-[12px] font-bold leading-4">
|
||||
Boost
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Container 3 */}
|
||||
<div
|
||||
className="absolute"
|
||||
style={{
|
||||
width: '135.53px',
|
||||
height: '165.15px',
|
||||
left: '312.18px',
|
||||
top: '19.66px',
|
||||
transform: 'rotate(3deg)',
|
||||
transformOrigin: '0 0'
|
||||
}}
|
||||
>
|
||||
<div className="bg-[#f9fafb] rounded-[12px] border border-[#f3f4f6] p-4 flex flex-col gap-4 items-center absolute" style={{ width: '134.91px', height: '165.18px', left: '0px', top: '0px' }}>
|
||||
<div className="bg-[#000000] rounded-full w-12 h-12 flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-white font-inter text-[16px] font-bold leading-6">O</span>
|
||||
</div>
|
||||
<span
|
||||
className="text-[#000000] font-inter text-[16px] font-bold text-center"
|
||||
style={{ width: '124px', height: '17px', transform: 'rotate(-3deg)', transformOrigin: '0 0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
|
||||
>
|
||||
+10% APY
|
||||
</span>
|
||||
<div
|
||||
className="bg-[#000000] rounded-lg px-4 py-2"
|
||||
style={{ transform: 'rotate(-3deg)', transformOrigin: '0 0' }}
|
||||
>
|
||||
<span className="text-white font-inter text-[12px] font-bold leading-4">
|
||||
Boost
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Container 4 - with SWAP button */}
|
||||
<div
|
||||
className="absolute"
|
||||
style={{
|
||||
width: '135.53px',
|
||||
height: '165.15px',
|
||||
left: '160.18px',
|
||||
top: '11.66px',
|
||||
transform: 'rotate(3deg)',
|
||||
transformOrigin: '0 0'
|
||||
}}
|
||||
>
|
||||
<div className="bg-[#f9fafb] rounded-[12px] border border-[#f3f4f6] p-4 flex flex-col gap-4 items-center absolute" style={{ width: '134.91px', height: '165.18px', left: '0px', top: '0px' }}>
|
||||
<div className="bg-[#000000] rounded-full w-12 h-12 flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-white font-inter text-[16px] font-bold leading-6">O</span>
|
||||
</div>
|
||||
<span
|
||||
className="text-[#000000] font-inter text-[16px] font-bold text-center"
|
||||
style={{ width: '124px', height: '17px', transform: 'rotate(-3deg)', transformOrigin: '0 0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
|
||||
>
|
||||
Get USDC
|
||||
</span>
|
||||
<div
|
||||
className="bg-[#000000] rounded-lg px-4 py-2"
|
||||
style={{ transform: 'rotate(-3deg)', transformOrigin: '0 0' }}
|
||||
>
|
||||
<span className="text-white font-inter text-[12px] font-bold leading-4">
|
||||
SWAP
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Container 5 - with LP button */}
|
||||
<div
|
||||
className="absolute"
|
||||
style={{
|
||||
width: '135.53px',
|
||||
height: '165.15px',
|
||||
left: '312.18px',
|
||||
top: '19.66px',
|
||||
transform: 'rotate(3deg)',
|
||||
transformOrigin: '0 0'
|
||||
}}
|
||||
>
|
||||
<div className="bg-[#f9fafb] rounded-[12px] border border-[#f3f4f6] p-4 flex flex-col gap-4 items-center absolute" style={{ width: '134.91px', height: '165.18px', left: '0px', top: '0px' }}>
|
||||
<div className="bg-[#000000] rounded-full w-12 h-12 flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-white font-inter text-[16px] font-bold leading-6">O</span>
|
||||
</div>
|
||||
<span
|
||||
className="text-[#000000] font-inter text-[16px] font-bold text-center"
|
||||
style={{ width: '124px', height: '17px', transform: 'rotate(-3deg)', transformOrigin: '0 0', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
|
||||
>
|
||||
10% APY
|
||||
</span>
|
||||
<div
|
||||
className="bg-[#000000] rounded-lg px-4 py-2"
|
||||
style={{ transform: 'rotate(-3deg)', transformOrigin: '0 0' }}
|
||||
>
|
||||
<span className="text-white font-inter text-[12px] font-bold leading-4">
|
||||
LP
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -4,13 +4,15 @@ import { useState, useEffect } from 'react';
|
||||
import Image from 'next/image';
|
||||
import ProductMenu from './ProductMenu';
|
||||
import ResourceMenu from './ResourceMenu';
|
||||
import { useLanguage } from '@/contexts/LanguageContext';
|
||||
|
||||
export default function Navbar() {
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
const [language, setLanguage] = useState<'zh' | 'en'>('zh');
|
||||
const { language, setLanguage, t } = useLanguage();
|
||||
const [showLangMenu, setShowLangMenu] = useState(false);
|
||||
const [showProductMenu, setShowProductMenu] = useState(false);
|
||||
const [showResourceMenu, setShowResourceMenu] = useState(false);
|
||||
const [animate, setAnimate] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
@@ -21,6 +23,15 @@ export default function Navbar() {
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// 页面加载后触发动画
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
setAnimate(true);
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
const target = e.target as Element;
|
||||
@@ -52,7 +63,14 @@ export default function Navbar() {
|
||||
: 'bg-white/90 backdrop-blur-[50px]'
|
||||
}`}>
|
||||
{/* Logo Section */}
|
||||
<div className="flex-1">
|
||||
<div
|
||||
className="flex-1 transition-all duration-1000 ease-out"
|
||||
style={{
|
||||
transform: animate ? 'translateY(0)' : 'translateY(-3rem)',
|
||||
opacity: animate ? 1 : 0
|
||||
}}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<Image
|
||||
src="/logo0.svg"
|
||||
alt="Logo"
|
||||
@@ -68,7 +86,7 @@ export default function Navbar() {
|
||||
{/* Product - Active */}
|
||||
<div className="product-menu-container">
|
||||
<div
|
||||
className={`rounded-lg px-4 py-2 h-9 flex items-center justify-center cursor-pointer transition-colors ${
|
||||
className={`rounded-lg px-4 py-2 h-9 flex items-center justify-center cursor-pointer transition-colors select-none ${
|
||||
showProductMenu ? 'bg-[#f3f4f6]' : 'bg-transparent hover:bg-[#f3f4f6]'
|
||||
}`}
|
||||
onClick={() => {
|
||||
@@ -79,22 +97,22 @@ export default function Navbar() {
|
||||
<span className={`font-bold text-sm leading-[150%] font-inter ${
|
||||
showProductMenu ? 'text-[#111827]' : 'text-[#4b5563]'
|
||||
}`}>
|
||||
{language === 'zh' ? '产品' : 'Product'}
|
||||
{t('nav.product')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Ecosystem */}
|
||||
<div className="bg-transparent rounded-lg px-4 py-2 h-9 flex items-center justify-center hover:bg-[#f3f4f6] transition-colors cursor-pointer">
|
||||
{/* Community */}
|
||||
<div className="bg-transparent rounded-lg px-4 py-2 h-9 flex items-center justify-center hover:bg-[#f3f4f6] transition-colors cursor-pointer select-none">
|
||||
<span className="text-[#4b5563] font-bold text-sm leading-[150%] font-inter">
|
||||
{language === 'zh' ? '生态' : 'Ecosystem'}
|
||||
{t('nav.community')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Resource */}
|
||||
<div className="resource-menu-container">
|
||||
<div
|
||||
className={`rounded-lg px-4 py-2 h-9 flex items-center justify-center cursor-pointer transition-colors ${
|
||||
className={`rounded-lg px-4 py-2 h-9 flex items-center justify-center cursor-pointer transition-colors select-none ${
|
||||
showResourceMenu ? 'bg-[#f3f4f6]' : 'bg-transparent hover:bg-[#f3f4f6]'
|
||||
}`}
|
||||
onClick={() => {
|
||||
@@ -105,7 +123,7 @@ export default function Navbar() {
|
||||
<span className={`font-bold text-sm leading-[150%] font-inter ${
|
||||
showResourceMenu ? 'text-[#111827]' : 'text-[#4b5563]'
|
||||
}`}>
|
||||
{language === 'zh' ? '资源' : 'Resource'}
|
||||
{t('nav.resource')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -114,16 +132,25 @@ export default function Navbar() {
|
||||
|
||||
{/* Launch App Button & Language Selector */}
|
||||
<div className="flex-1 flex justify-end items-center gap-4">
|
||||
<div className="bg-[#111827] rounded-lg px-5 py-2.5 h-10 flex items-center justify-center overflow-hidden cursor-pointer hover:opacity-90 transition-opacity">
|
||||
<div
|
||||
className="bg-[#111827] rounded-lg px-5 py-2.5 h-10 flex items-center justify-center overflow-hidden cursor-pointer hover:opacity-90 select-none"
|
||||
style={{
|
||||
transform: animate ? 'translateY(0)' : 'translateY(-3rem)',
|
||||
opacity: animate ? 1 : 0,
|
||||
transition: 'all 1s ease-out',
|
||||
transitionDelay: '200ms'
|
||||
}}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<span className="text-[#fcfcfd] font-bold text-sm leading-[150%] font-inter">
|
||||
{language === 'zh' ? '启动应用' : 'Launch App'}
|
||||
{t('nav.launchApp')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Language Selector */}
|
||||
<div className="relative language-selector">
|
||||
<div
|
||||
className="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-[#f3f4f6] transition-colors cursor-pointer"
|
||||
className="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-[#f3f4f6] transition-colors cursor-pointer select-none"
|
||||
onClick={() => setShowLangMenu(!showLangMenu)}
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
@@ -143,7 +170,7 @@ export default function Navbar() {
|
||||
{showLangMenu && (
|
||||
<div className="absolute right-0 mt-2 w-32 bg-white rounded-lg shadow-lg border border-[#e5e7eb] overflow-hidden z-50">
|
||||
<div
|
||||
className={`px-4 py-2.5 hover:bg-[#f3f4f6] cursor-pointer transition-colors ${
|
||||
className={`px-4 py-2.5 hover:bg-[#f3f4f6] cursor-pointer transition-colors select-none ${
|
||||
language === 'zh' ? 'bg-[#f3f4f6]' : ''
|
||||
}`}
|
||||
onClick={() => toggleLanguage('zh')}
|
||||
@@ -151,7 +178,7 @@ export default function Navbar() {
|
||||
<span className="text-[#111827] font-inter text-sm font-medium">中文</span>
|
||||
</div>
|
||||
<div
|
||||
className={`px-4 py-2.5 hover:bg-[#f3f4f6] cursor-pointer transition-colors ${
|
||||
className={`px-4 py-2.5 hover:bg-[#f3f4f6] cursor-pointer transition-colors select-none ${
|
||||
language === 'en' ? 'bg-[#f3f4f6]' : ''
|
||||
}`}
|
||||
onClick={() => toggleLanguage('en')}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
interface ProductMenuProps {
|
||||
isOpen: boolean;
|
||||
@@ -9,7 +10,26 @@ interface ProductMenuProps {
|
||||
}
|
||||
|
||||
export default function ProductMenu({ isOpen, onClose, language }: ProductMenuProps) {
|
||||
if (!isOpen) return null;
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setIsVisible(true);
|
||||
setIsClosing(false);
|
||||
} else if (isVisible) {
|
||||
// 触发退出动画
|
||||
setIsClosing(true);
|
||||
// 动画结束后隐藏组件
|
||||
const timer = setTimeout(() => {
|
||||
setIsVisible(false);
|
||||
setIsClosing(false);
|
||||
}, 300); // 匹配动画时长
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [isOpen, isVisible]);
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
const content = {
|
||||
zh: {
|
||||
@@ -68,12 +88,12 @@ export default function ProductMenu({ isOpen, onClose, language }: ProductMenuPr
|
||||
<>
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="fixed inset-0 bg-black/20 z-40 animate-fade-in"
|
||||
className={`fixed inset-0 bg-black/20 z-40 ${isClosing ? 'animate-fade-out-fast' : 'animate-fade-in'}`}
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* Menu */}
|
||||
<div className="fixed left-1/2 -translate-x-1/2 bg-white rounded-2xl shadow-xl border border-[#e5e7eb] p-6 z-50 animate-slide-down"
|
||||
<div className={`fixed left-1/2 -translate-x-1/2 bg-white rounded-2xl shadow-xl border border-[#e5e7eb] p-6 z-50 ${isClosing ? 'animate-slide-up' : 'animate-slide-down'}`}
|
||||
style={{ width: '1080px', top: '90px' }}
|
||||
>
|
||||
<div className="flex flex-row gap-8">
|
||||
|
||||
15
components/Providers.tsx
Normal file
15
components/Providers.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import { LanguageProvider } from '@/contexts/LanguageContext';
|
||||
import TransitionWrapper from './TransitionWrapper';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export default function Providers({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<LanguageProvider>
|
||||
<TransitionWrapper>
|
||||
{children}
|
||||
</TransitionWrapper>
|
||||
</LanguageProvider>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
interface ResourceMenuProps {
|
||||
isOpen: boolean;
|
||||
@@ -9,7 +10,26 @@ interface ResourceMenuProps {
|
||||
}
|
||||
|
||||
export default function ResourceMenu({ isOpen, onClose, language }: ResourceMenuProps) {
|
||||
if (!isOpen) return null;
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setIsVisible(true);
|
||||
setIsClosing(false);
|
||||
} else if (isVisible) {
|
||||
// 触发退出动画
|
||||
setIsClosing(true);
|
||||
// 动画结束后隐藏组件
|
||||
const timer = setTimeout(() => {
|
||||
setIsVisible(false);
|
||||
setIsClosing(false);
|
||||
}, 300); // 匹配动画时长
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [isOpen, isVisible]);
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
const content = {
|
||||
zh: {
|
||||
@@ -76,12 +96,12 @@ export default function ResourceMenu({ isOpen, onClose, language }: ResourceMenu
|
||||
<>
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="fixed inset-0 bg-black/20 z-40 animate-fade-in"
|
||||
className={`fixed inset-0 bg-black/20 z-40 ${isClosing ? 'animate-fade-out-fast' : 'animate-fade-in'}`}
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* Menu */}
|
||||
<div className="fixed left-1/2 -translate-x-1/2 bg-white rounded-2xl shadow-xl border border-[#e5e7eb] p-6 z-50 animate-slide-down"
|
||||
<div className={`fixed left-1/2 -translate-x-1/2 bg-white rounded-2xl shadow-xl border border-[#e5e7eb] p-6 z-50 ${isClosing ? 'animate-slide-up' : 'animate-slide-down'}`}
|
||||
style={{ width: '1080px', top: '90px' }}
|
||||
>
|
||||
<div className="flex flex-row gap-8">
|
||||
@@ -98,7 +118,7 @@ export default function ResourceMenu({ isOpen, onClose, language }: ResourceMenu
|
||||
<div className="bg-transparent hover:bg-[#f9fafb] border border-transparent hover:border-[#e5e7eb] rounded-xl p-3 flex gap-2 cursor-pointer transition-all">
|
||||
<div className="bg-[#111827] rounded-xl w-12 h-12 flex items-center justify-center flex-shrink-0">
|
||||
<Image
|
||||
src="/component-10.svg"
|
||||
src="/icon-book.svg"
|
||||
alt="Docs"
|
||||
width={24}
|
||||
height={24}
|
||||
|
||||
@@ -2,62 +2,54 @@
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useLanguage } from '@/contexts/LanguageContext';
|
||||
|
||||
export default function SecuritySection() {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { t } = useLanguage();
|
||||
const [animate, setAnimate] = useState(false);
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
const currentRef = sectionRef.current;
|
||||
if (!currentRef) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting && !visible) {
|
||||
setVisible(true);
|
||||
}
|
||||
});
|
||||
if (entries[0].isIntersecting) {
|
||||
setAnimate(true);
|
||||
observer.disconnect();
|
||||
}
|
||||
},
|
||||
{
|
||||
threshold: 0.1,
|
||||
}
|
||||
{ threshold: 0.3 }
|
||||
);
|
||||
|
||||
if (sectionRef.current) {
|
||||
observer.observe(sectionRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (sectionRef.current) {
|
||||
observer.unobserve(sectionRef.current);
|
||||
}
|
||||
};
|
||||
}, [visible]);
|
||||
observer.observe(currentRef);
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: '/interface-search-magnifying-glass0.svg',
|
||||
title: 'Audited',
|
||||
description: 'Smart contracts audited by top-tier firms. Underlying assets undergo strict due diligence and financial auditing.',
|
||||
titleKey: 'security.audited.title',
|
||||
descKey: 'security.audited.desc',
|
||||
position: 'top-left'
|
||||
},
|
||||
{
|
||||
icon: '/navigation-building-010.svg',
|
||||
title: 'Segregated',
|
||||
description: 'SPV Setup. User assets are held in segregated Special Purpose Vehicles, isolated from platform risks.',
|
||||
titleKey: 'security.segregated.title',
|
||||
descKey: 'security.segregated.desc',
|
||||
position: 'top-right'
|
||||
},
|
||||
{
|
||||
icon: '/interface-chart-bar-vertical-010.svg',
|
||||
title: 'Transparency',
|
||||
description: 'Real-Time NAV. Fund performance data and asset valuations are publicly available and updated on-chain.',
|
||||
titleKey: 'security.transparency.title',
|
||||
descKey: 'security.transparency.desc',
|
||||
position: 'bottom-left'
|
||||
},
|
||||
{
|
||||
icon: '/component-11.svg',
|
||||
title: 'Powered By NASDAQ & HKEX Listed Partners',
|
||||
description: null,
|
||||
titleKey: 'security.partners.title',
|
||||
descKey: null,
|
||||
position: 'bottom-right',
|
||||
special: true
|
||||
}
|
||||
@@ -78,7 +70,7 @@ export default function SecuritySection() {
|
||||
{/* Left Side - Title */}
|
||||
<div
|
||||
className={`flex flex-row items-center justify-center flex-shrink-0 w-[459px] h-[604px] relative transition-all duration-700 ease-out ${
|
||||
mounted && visible
|
||||
animate
|
||||
? 'translate-x-0 opacity-100'
|
||||
: '-translate-x-12 opacity-0'
|
||||
}`}
|
||||
@@ -99,7 +91,7 @@ export default function SecuritySection() {
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
Security First Architecture
|
||||
{t('security.title')}
|
||||
</h2>
|
||||
<p
|
||||
className="text-[#9ca1af] text-left relative w-[290px] flex items-center justify-start font-domine"
|
||||
@@ -109,7 +101,7 @@ export default function SecuritySection() {
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
Real-time data transparency with segregated asset management.
|
||||
{t('security.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -125,9 +117,9 @@ export default function SecuritySection() {
|
||||
>
|
||||
{features.map((feature, index) => (
|
||||
<div
|
||||
key={feature.title}
|
||||
key={feature.titleKey}
|
||||
className={`flex flex-col items-start justify-center relative transition-all duration-700 ease-out ${
|
||||
mounted && visible
|
||||
animate
|
||||
? 'translate-y-0 opacity-100'
|
||||
: 'translate-y-12 opacity-0'
|
||||
}`}
|
||||
@@ -158,7 +150,7 @@ export default function SecuritySection() {
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
{feature.title}
|
||||
{t(feature.titleKey)}
|
||||
</h3>
|
||||
<Image
|
||||
src={feature.icon}
|
||||
@@ -174,7 +166,7 @@ export default function SecuritySection() {
|
||||
<div className="flex flex-col gap-6 items-start justify-start self-stretch flex-shrink-0 relative">
|
||||
<Image
|
||||
src={feature.icon}
|
||||
alt={feature.title}
|
||||
alt={t(feature.titleKey)}
|
||||
width={32}
|
||||
height={32}
|
||||
className="flex-shrink-0"
|
||||
@@ -189,7 +181,7 @@ export default function SecuritySection() {
|
||||
fontWeight: 700
|
||||
}}
|
||||
>
|
||||
{feature.title}
|
||||
{t(feature.titleKey)}
|
||||
</h3>
|
||||
<p
|
||||
className="text-[#9ca1af] text-left relative self-stretch font-inter"
|
||||
@@ -199,7 +191,7 @@ export default function SecuritySection() {
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
{feature.description}
|
||||
{feature.descKey && t(feature.descKey)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
103
components/ShatterTransition.tsx
Normal file
103
components/ShatterTransition.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState, useMemo } from 'react';
|
||||
|
||||
interface ShatterTransitionProps {
|
||||
isActive: boolean;
|
||||
onComplete: () => void;
|
||||
}
|
||||
|
||||
// 增加网格密度以获得更细腻的破碎感
|
||||
const GRID_COLS = 10;
|
||||
const GRID_ROWS = 10;
|
||||
|
||||
export default function ShatterTransition({ isActive, onComplete }: ShatterTransitionProps) {
|
||||
const [isAnimating, setIsAnimating] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isActive) {
|
||||
setIsAnimating(true);
|
||||
// 总动画时间 = 最大延迟 + 单个动画时长
|
||||
// 这里预留充足时间确保最后一片落完
|
||||
const timer = setTimeout(() => {
|
||||
setIsAnimating(false);
|
||||
onComplete();
|
||||
}, 1500);
|
||||
return () => clearTimeout(timer);
|
||||
} else {
|
||||
setIsAnimating(false);
|
||||
}
|
||||
}, [isActive, onComplete]);
|
||||
|
||||
// 预先计算碎片数据,避免重渲染时的性能抖动
|
||||
const fragments = useMemo(() => {
|
||||
return Array.from({ length: GRID_COLS * GRID_ROWS }, (_, i) => {
|
||||
const col = i % GRID_COLS;
|
||||
const row = Math.floor(i / GRID_COLS);
|
||||
|
||||
// 核心算法:计算距离左上角的距离 (欧几里得距离)
|
||||
// 距离越远,delay 越大
|
||||
const distance = Math.sqrt(col * col + row * row);
|
||||
const maxDistance = Math.sqrt(GRID_COLS * GRID_COLS + GRID_ROWS * GRID_ROWS);
|
||||
const normalizedDistance = distance / maxDistance;
|
||||
|
||||
// 爆炸参数
|
||||
const randomX = (Math.random() - 0.5) * 200; // X轴偏移
|
||||
const randomY = Math.random() * 300 + 100; // Y轴主要向下掉落
|
||||
const randomRotateX = (Math.random() - 0.5) * 720; // 剧烈翻滚
|
||||
const randomRotateY = (Math.random() - 0.5) * 720;
|
||||
const randomScale = 0.5 + Math.random() * 0.5; // 碎片大小不一
|
||||
|
||||
return {
|
||||
id: i,
|
||||
col,
|
||||
row,
|
||||
// 延迟时间:基准延迟 + 随机微扰 (让波浪不那么死板)
|
||||
delay: normalizedDistance * 600 + (Math.random() * 100),
|
||||
randomX,
|
||||
randomY,
|
||||
randomRotateX,
|
||||
randomRotateY,
|
||||
randomScale
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (!isActive && !isAnimating) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-[9999] pointer-events-none overflow-hidden"
|
||||
style={{ perspective: '1200px' }} // 开启3D透视,这很重要!
|
||||
>
|
||||
<div className="relative w-full h-full bg-transparent">
|
||||
{fragments.map((frag) => (
|
||||
<div
|
||||
key={frag.id}
|
||||
className="absolute shatter-fragment will-change-transform"
|
||||
style={{
|
||||
left: `${(frag.col / GRID_COLS) * 100}%`,
|
||||
top: `${(frag.row / GRID_ROWS) * 100}%`,
|
||||
width: `${100 / GRID_COLS + 0.1}%`, // +0.1% 防止渲染缝隙
|
||||
height: `${100 / GRID_ROWS + 0.1}%`,
|
||||
|
||||
// 动画变量传入 CSS
|
||||
'--delay': `${frag.delay}ms`,
|
||||
'--tx': `${frag.randomX}px`,
|
||||
'--ty': `${frag.randomY}px`,
|
||||
'--rx': `${frag.randomRotateX}deg`,
|
||||
'--ry': `${frag.randomRotateY}deg`,
|
||||
'--s': `${frag.randomScale}`,
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
{/* 内部容器负责材质,外部容器负责位置,分离关注点 */}
|
||||
<div className="w-full h-full glass-shard bg-white backdrop-blur-lg border-[0.5px] border-white/30 shadow-md" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 可选:背景遮罩,防止碎片飞走后直接漏出底部内容,根据需求调整 */}
|
||||
<div className="absolute inset-0 bg-white -z-10 animate-fade-out" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useLanguage } from '@/contexts/LanguageContext';
|
||||
|
||||
// 数字增长动画Hook
|
||||
// 数字增长动画Hook - 可重复触发
|
||||
function useCountUp(end: number, duration: number = 1500, startRangePercent: number = 0.75) {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [count, setCount] = useState(end); // 初始值设为目标值,避免hydration错误
|
||||
const [hasAnimated, setHasAnimated] = useState(false);
|
||||
const elementRef = useRef<HTMLDivElement>(null);
|
||||
const startValueRef = useRef<number>(end);
|
||||
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
// 客户端挂载后设置随机起始值
|
||||
useEffect(() => {
|
||||
@@ -23,28 +24,38 @@ function useCountUp(end: number, duration: number = 1500, startRangePercent: num
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting && !hasAnimated) {
|
||||
setHasAnimated(true);
|
||||
const start = startValueRef.current;
|
||||
const startTime = Date.now();
|
||||
if (entries[0].isIntersecting) {
|
||||
// 进入视口 - 检查是否需要开始动画
|
||||
if (!timerRef.current) {
|
||||
const start = startValueRef.current;
|
||||
const startTime = Date.now();
|
||||
|
||||
const timer = setInterval(() => {
|
||||
const elapsed = Date.now() - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
timerRef.current = setInterval(() => {
|
||||
const elapsed = Date.now() - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
|
||||
// 使用缓动函数 - easeOutCubic 更自然
|
||||
const easeOutCubic = 1 - Math.pow(1 - progress, 3);
|
||||
const currentCount = Math.floor(start + (end - start) * easeOutCubic);
|
||||
// 使用缓动函数 - easeOutCubic 更自然
|
||||
const easeOutCubic = 1 - Math.pow(1 - progress, 3);
|
||||
const currentCount = Math.floor(start + (end - start) * easeOutCubic);
|
||||
|
||||
setCount(currentCount);
|
||||
setCount(currentCount);
|
||||
|
||||
if (progress === 1) {
|
||||
setCount(end); // 确保最终值准确
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, 16);
|
||||
|
||||
return () => clearInterval(timer);
|
||||
if (progress === 1) {
|
||||
setCount(end); // 确保最终值准确
|
||||
if (timerRef.current) {
|
||||
clearInterval(timerRef.current);
|
||||
timerRef.current = null;
|
||||
}
|
||||
}
|
||||
}, 16);
|
||||
}
|
||||
} else {
|
||||
// 离开视口 - 停止并重置
|
||||
if (timerRef.current) {
|
||||
clearInterval(timerRef.current);
|
||||
timerRef.current = null;
|
||||
}
|
||||
setCount(startValueRef.current);
|
||||
}
|
||||
},
|
||||
{ threshold: 0.1 }
|
||||
@@ -54,8 +65,14 @@ function useCountUp(end: number, duration: number = 1500, startRangePercent: num
|
||||
observer.observe(elementRef.current);
|
||||
}
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [end, duration, hasAnimated, mounted]);
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
if (timerRef.current) {
|
||||
clearInterval(timerRef.current);
|
||||
timerRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [end, duration, mounted]);
|
||||
|
||||
return { count, elementRef };
|
||||
}
|
||||
@@ -75,6 +92,7 @@ function formatNumber(num: number, prefix: string = '', suffix: string = '', for
|
||||
}
|
||||
|
||||
export default function StatsSection() {
|
||||
const { t } = useLanguage();
|
||||
const tvl = useCountUp(485, 1500, 0.80); // 从80-95%开始
|
||||
const apy = useCountUp(515, 1500, 0.85); // 5.15 * 100,从85-100%开始
|
||||
const yield_ = useCountUp(45, 1500, 0.75); // 从75-90%开始
|
||||
@@ -135,7 +153,7 @@ export default function StatsSection() {
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
Total Value Locked
|
||||
{t('stats.tvl')}
|
||||
</div>
|
||||
<div
|
||||
ref={tvl.elementRef}
|
||||
@@ -173,7 +191,7 @@ export default function StatsSection() {
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
Avg. APY
|
||||
{t('stats.apy')}
|
||||
</div>
|
||||
<div
|
||||
ref={apy.elementRef}
|
||||
@@ -211,7 +229,7 @@ export default function StatsSection() {
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
Yield Captured
|
||||
{t('stats.yield')}
|
||||
</div>
|
||||
<div
|
||||
ref={yield_.elementRef}
|
||||
@@ -249,7 +267,7 @@ export default function StatsSection() {
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
Connected Users
|
||||
{t('stats.users')}
|
||||
</div>
|
||||
|
||||
{/* .frame-38 - Number and avatars */}
|
||||
|
||||
36
components/TransitionWrapper.tsx
Normal file
36
components/TransitionWrapper.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
'use client';
|
||||
|
||||
import { useLanguage } from '@/contexts/LanguageContext';
|
||||
import ShatterTransition from './ShatterTransition';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export default function TransitionWrapper({ children }: { children: React.ReactNode }) {
|
||||
const { transitionKey } = useLanguage();
|
||||
const [isActive, setIsActive] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// transitionKey > 0 表示已经发生过语言切换
|
||||
if (transitionKey > 0) {
|
||||
setIsActive(true);
|
||||
|
||||
// 动画结束后重置状态
|
||||
const timer = setTimeout(() => {
|
||||
setIsActive(false);
|
||||
}, 1500);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [transitionKey]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<ShatterTransition
|
||||
isActive={isActive}
|
||||
onComplete={() => {
|
||||
// 动画完成后的回调
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -2,55 +2,47 @@
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useLanguage } from '@/contexts/LanguageContext';
|
||||
import ConsenSysLogo from './ConsenSysLogo';
|
||||
|
||||
export default function TrustedBySection() {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { t } = useLanguage();
|
||||
const [animate, setAnimate] = useState(false);
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
const currentRef = sectionRef.current;
|
||||
if (!currentRef) return;
|
||||
|
||||
// 使用 IntersectionObserver 检测元素进入视口
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting && !visible) {
|
||||
setVisible(true);
|
||||
}
|
||||
});
|
||||
if (entries[0].isIntersecting) {
|
||||
setAnimate(true);
|
||||
observer.disconnect(); // 触发后立即断开
|
||||
}
|
||||
},
|
||||
{
|
||||
threshold: 0.2, // 当20%的元素可见时触发
|
||||
}
|
||||
{ threshold: 0.3 }
|
||||
);
|
||||
|
||||
if (sectionRef.current) {
|
||||
observer.observe(sectionRef.current);
|
||||
}
|
||||
observer.observe(currentRef);
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
return () => {
|
||||
if (sectionRef.current) {
|
||||
observer.unobserve(sectionRef.current);
|
||||
}
|
||||
};
|
||||
}, [visible]);
|
||||
|
||||
// 合作伙伴 logo 数据
|
||||
// 合作伙伴 logo 数据 - 统一高度为40px
|
||||
const partners = [
|
||||
{
|
||||
name: 'BlackRock',
|
||||
src: '/nav-ireland0.svg',
|
||||
width: 193,
|
||||
height: 28,
|
||||
className: 'w-[193px] h-[28px]'
|
||||
width: 200,
|
||||
height: 40,
|
||||
className: 'w-[200px] h-[40px]'
|
||||
},
|
||||
{
|
||||
name: 'Coinbase',
|
||||
src: '/coinbase-10.svg',
|
||||
width: 179,
|
||||
height: 32,
|
||||
className: 'w-[179px] h-[32px]'
|
||||
width: 180,
|
||||
height: 40,
|
||||
className: 'w-[180px] h-[40px]'
|
||||
},
|
||||
{
|
||||
name: 'Wintermute',
|
||||
@@ -68,10 +60,10 @@ export default function TrustedBySection() {
|
||||
},
|
||||
{
|
||||
name: 'ConsenSys',
|
||||
src: '/logo1.svg', // 临时使用一个 logo
|
||||
width: 176,
|
||||
height: 40,
|
||||
className: 'w-[176px] h-[40px]'
|
||||
src: '', // 使用组合的vector文件
|
||||
width: 220,
|
||||
height: 50,
|
||||
className: 'w-[220px] h-[50px]'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -96,33 +88,39 @@ export default function TrustedBySection() {
|
||||
fontWeight: 500
|
||||
}}
|
||||
>
|
||||
Trusted by Industry Leaders
|
||||
{t('trusted.title')}
|
||||
</div>
|
||||
|
||||
{/* Logo容器 - .frame-26 */}
|
||||
<div
|
||||
className="flex flex-row items-center justify-between flex-shrink-0 w-[1200px] relative"
|
||||
className="flex flex-row items-center justify-between flex-shrink-0 w-[1440px] relative"
|
||||
>
|
||||
{partners.map((partner, index) => (
|
||||
<div
|
||||
key={partner.name}
|
||||
className={`flex-shrink-0 relative overflow-hidden transition-all duration-700 ease-out ${partner.className} ${
|
||||
mounted && visible
|
||||
className={`flex-shrink-0 relative overflow-hidden transition-all duration-700 ease-out ${
|
||||
animate
|
||||
? 'translate-y-0 opacity-100'
|
||||
: 'translate-y-12 opacity-0'
|
||||
}`}
|
||||
style={{
|
||||
transitionDelay: `${index * 150}ms`, // 每个logo延迟150ms
|
||||
transitionDelay: `${index * 100}ms`,
|
||||
width: `${partner.width}px`,
|
||||
height: `${partner.height}px`,
|
||||
aspectRatio: `${partner.width}/${partner.height}`
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={partner.src}
|
||||
alt={partner.name}
|
||||
width={partner.width}
|
||||
height={partner.height}
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
{partner.name === 'ConsenSys' ? (
|
||||
<ConsenSysLogo width={partner.width} height={partner.height} />
|
||||
) : (
|
||||
<Image
|
||||
src={partner.src}
|
||||
alt={partner.name}
|
||||
width={partner.width}
|
||||
height={partner.height}
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -2,67 +2,31 @@
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useLanguage } from '@/contexts/LanguageContext';
|
||||
|
||||
export default function WhyAssetXSection() {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { t } = useLanguage();
|
||||
const [animate, setAnimate] = useState(false);
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
const currentRef = sectionRef.current;
|
||||
if (!currentRef) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting && !visible) {
|
||||
setVisible(true);
|
||||
}
|
||||
});
|
||||
if (entries[0].isIntersecting) {
|
||||
setAnimate(true);
|
||||
observer.disconnect(); // 触发后立即断开,不再监听
|
||||
}
|
||||
},
|
||||
{
|
||||
threshold: 0.1,
|
||||
}
|
||||
{ threshold: 0.5 } // 当50%可见时才触发
|
||||
);
|
||||
|
||||
if (sectionRef.current) {
|
||||
observer.observe(sectionRef.current);
|
||||
}
|
||||
observer.observe(currentRef);
|
||||
|
||||
return () => {
|
||||
if (sectionRef.current) {
|
||||
observer.unobserve(sectionRef.current);
|
||||
}
|
||||
};
|
||||
}, [visible]);
|
||||
|
||||
const features = [
|
||||
{
|
||||
id: 'sustainable',
|
||||
icon: '/system-data0.svg',
|
||||
title: 'Sustainable Real Yield',
|
||||
description: 'Access 15%-30% returns from Delta-neutral arbitrage strategies and commercial credit. No inflationary token emissions, just real profits.',
|
||||
image: '/frame-110.svg',
|
||||
imageWidth: 305,
|
||||
imageHeight: 162,
|
||||
large: true
|
||||
},
|
||||
{
|
||||
id: 'reliability',
|
||||
icon: '/warning-shield-check0.svg',
|
||||
title: 'Proven Reliability',
|
||||
description: 'Backed by partners holding HK SFC Licenses (1/2/4/5/9) and NASDAQ-listed entities. Audited, compliant, and transparent.',
|
||||
rightIcon: '/icon0.svg',
|
||||
large: false
|
||||
},
|
||||
{
|
||||
id: 'liquidity',
|
||||
icon: '/arrow-arrow-left-right0.svg',
|
||||
title: 'Flexible Liquidity',
|
||||
description: 'Solve the illiquidity of traditional finance. Enjoy instant exit via secondary markets or leverage your positions for up to 40% APY.',
|
||||
badges: ['40%+ APR', 'Instant Exit'],
|
||||
large: false
|
||||
}
|
||||
];
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section
|
||||
@@ -74,9 +38,9 @@ export default function WhyAssetXSection() {
|
||||
alignContent: 'center'
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center w-[1200px]">
|
||||
<div className="flex flex-col items-center justify-center w-[1440px]">
|
||||
{/* Header - .frame-27 */}
|
||||
<div className="flex flex-col gap-2 items-start justify-start flex-shrink-0 w-[1200px] relative mb-10">
|
||||
<div className="flex flex-col gap-2 items-start justify-start flex-shrink-0 w-[1440px] relative mb-10">
|
||||
{/* Title - .why-assetx */}
|
||||
<h2
|
||||
className="text-[#111827] text-left relative font-inter"
|
||||
@@ -87,7 +51,7 @@ export default function WhyAssetXSection() {
|
||||
fontWeight: 700
|
||||
}}
|
||||
>
|
||||
Why ASSETX?
|
||||
{t('why.title')}
|
||||
</h2>
|
||||
|
||||
{/* Subtitle */}
|
||||
@@ -99,18 +63,18 @@ export default function WhyAssetXSection() {
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
Institutional-grade access to real-world yield, with DeFi-native composability.
|
||||
{t('why.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Cards Container - .frame-30 */}
|
||||
<div className="flex flex-row gap-6 items-center justify-start flex-shrink-0 w-[1199px] relative">
|
||||
<div className="flex flex-row gap-6 items-center justify-start flex-shrink-0 w-[1440px] relative">
|
||||
|
||||
{/* Left Large Card - Sustainable Real Yield */}
|
||||
<div
|
||||
className={`bg-white rounded-[24px] border border-[#e5e7eb] p-10 flex flex-col items-start justify-between self-stretch flex-1 relative overflow-hidden
|
||||
${
|
||||
mounted && visible
|
||||
animate
|
||||
? 'translate-y-0 opacity-100'
|
||||
: 'translate-y-12 opacity-0'
|
||||
}
|
||||
@@ -142,7 +106,7 @@ export default function WhyAssetXSection() {
|
||||
fontWeight: 700
|
||||
}}
|
||||
>
|
||||
Sustainable Real Yield
|
||||
{t('why.sustainable.title')}
|
||||
</h3>
|
||||
<p
|
||||
className="text-[#9ca1af] text-left relative self-stretch font-inter"
|
||||
@@ -152,7 +116,7 @@ export default function WhyAssetXSection() {
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
Access 15%-30% returns from Delta-neutral arbitrage strategies and commercial credit. No inflationary token emissions, just real profits.
|
||||
{t('why.sustainable.desc')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -163,18 +127,18 @@ export default function WhyAssetXSection() {
|
||||
alt="Chart"
|
||||
width={305}
|
||||
height={162}
|
||||
className="flex-shrink-0 relative"
|
||||
className="w-full h-auto flex-shrink-0 relative"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Right Column - .frame-29 */}
|
||||
<div className="flex flex-col gap-6 items-start justify-start flex-shrink-0 w-[790px] relative">
|
||||
<div className="flex flex-col gap-6 items-start justify-start flex-shrink-0 w-[850px] relative">
|
||||
|
||||
{/* Proven Reliability Card */}
|
||||
<div
|
||||
className={`bg-white rounded-[24px] border border-[#e5e7eb] p-10 flex flex-col items-start justify-start self-stretch flex-shrink-0 relative overflow-hidden
|
||||
${
|
||||
mounted && visible
|
||||
animate
|
||||
? 'translate-y-0 opacity-100'
|
||||
: 'translate-y-12 opacity-0'
|
||||
}
|
||||
@@ -208,7 +172,7 @@ export default function WhyAssetXSection() {
|
||||
fontWeight: 700
|
||||
}}
|
||||
>
|
||||
Proven Reliability
|
||||
{t('why.reliability.title')}
|
||||
</h3>
|
||||
<p
|
||||
className="text-[#9ca1af] text-left relative font-inter"
|
||||
@@ -218,19 +182,56 @@ export default function WhyAssetXSection() {
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
Backed by partners holding HK SFC Licenses (1/2/4/5/9) and NASDAQ-listed entities. Audited, compliant, and transparent.
|
||||
{t('why.reliability.desc')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Icon */}
|
||||
<div className="flex items-center justify-center">
|
||||
<Image
|
||||
src="/icon0.svg"
|
||||
alt="Icon"
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
<div
|
||||
className="bg-[#f9fafb] rounded-[16px] border border-[#e5e7eb] flex items-center justify-center flex-shrink-0 relative"
|
||||
style={{
|
||||
padding: '32px',
|
||||
height: '180px',
|
||||
width: '196px',
|
||||
isolation: 'isolate',
|
||||
willChange: 'auto',
|
||||
contain: 'layout style paint'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="rounded-full border-2 border-[#e5e7eb] flex items-center justify-center flex-shrink-0 relative"
|
||||
style={{
|
||||
width: '132px',
|
||||
height: '132px',
|
||||
minWidth: '132px',
|
||||
minHeight: '132px',
|
||||
maxWidth: '132px',
|
||||
maxHeight: '132px'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="bg-[#e5e7eb] rounded-full flex items-center justify-center flex-shrink-0 relative"
|
||||
style={{
|
||||
width: '88.59px',
|
||||
height: '88.59px',
|
||||
minWidth: '88.59px',
|
||||
minHeight: '88.59px',
|
||||
maxWidth: '88.59px',
|
||||
maxHeight: '88.59px'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/icon0.svg"
|
||||
alt="Lock Icon"
|
||||
style={{
|
||||
width: '44.29px',
|
||||
height: '44.29px',
|
||||
display: 'block'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -239,7 +240,7 @@ export default function WhyAssetXSection() {
|
||||
<div
|
||||
className={`bg-white rounded-[24px] border border-[#e5e7eb] p-10 flex flex-col items-start justify-start self-stretch flex-shrink-0 relative overflow-hidden
|
||||
${
|
||||
mounted && visible
|
||||
animate
|
||||
? 'translate-y-0 opacity-100'
|
||||
: 'translate-y-12 opacity-0'
|
||||
}
|
||||
@@ -248,56 +249,57 @@ export default function WhyAssetXSection() {
|
||||
transition: 'transform 0.3s ease-out, box-shadow 0.3s ease-out, opacity 0.7s ease-out 0.3s, translate 0.7s ease-out 0.3s'
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-6 items-start justify-start self-stretch flex-shrink-0 relative">
|
||||
{/* Top Section with Icon and Badges */}
|
||||
<div className="flex flex-col gap-6 items-start justify-start flex-shrink-0 relative">
|
||||
{/* Icon and Badges Row */}
|
||||
<div className="flex flex-row gap-6 items-center justify-start relative">
|
||||
{/* Icon */}
|
||||
<div className="bg-[#f9fafb] rounded-[16px] flex flex-row items-center justify-center flex-shrink-0 w-12 h-12 relative">
|
||||
<Image
|
||||
src="/arrow-arrow-left-right0.svg"
|
||||
alt="Arrow"
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row items-start justify-between self-stretch flex-shrink-0 relative">
|
||||
{/* Left Content */}
|
||||
<div className="flex flex-col gap-6 items-start justify-start flex-shrink-0 w-[550px] relative">
|
||||
{/* Icon */}
|
||||
<div className="bg-[#f9fafb] rounded-[16px] flex flex-row items-center justify-center flex-shrink-0 w-12 h-12 relative">
|
||||
<Image
|
||||
src="/arrow-arrow-left-right0.svg"
|
||||
alt="Arrow"
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Badges */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="bg-[#f9fafb] rounded-lg px-3 py-1 flex items-center justify-center">
|
||||
<span className="text-[#111827] font-inter text-sm font-bold">40%+ APR</span>
|
||||
</div>
|
||||
<div className="bg-[#f9fafb] rounded-lg px-3 py-1 flex items-center justify-center">
|
||||
<span className="text-[#111827] font-inter text-sm font-bold">Instant Exit</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Text Content */}
|
||||
<div className="flex flex-col gap-4 items-start justify-start flex-shrink-0 relative">
|
||||
<h3
|
||||
className="text-[#111827] text-left relative font-inter"
|
||||
style={{
|
||||
fontSize: '24px',
|
||||
lineHeight: '130%',
|
||||
letterSpacing: '-0.005em',
|
||||
fontWeight: 700
|
||||
}}
|
||||
>
|
||||
{t('why.liquidity.title')}
|
||||
</h3>
|
||||
<p
|
||||
className="text-[#9ca1af] text-left relative font-inter"
|
||||
style={{
|
||||
fontSize: '16px',
|
||||
lineHeight: '150%',
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
{t('why.liquidity.desc')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Text Content */}
|
||||
<div className="flex flex-col gap-4 items-start justify-start self-stretch flex-shrink-0 relative">
|
||||
<h3
|
||||
className="text-[#111827] text-left relative font-inter"
|
||||
style={{
|
||||
fontSize: '24px',
|
||||
lineHeight: '130%',
|
||||
letterSpacing: '-0.005em',
|
||||
fontWeight: 700
|
||||
}}
|
||||
>
|
||||
Flexible Liquidity
|
||||
</h3>
|
||||
<p
|
||||
className="text-[#9ca1af] text-left relative font-inter"
|
||||
style={{
|
||||
fontSize: '16px',
|
||||
lineHeight: '150%',
|
||||
fontWeight: 400
|
||||
}}
|
||||
>
|
||||
Solve the illiquidity of traditional finance. Enjoy instant exit via secondary markets or leverage your positions for up to 40% APY.
|
||||
</p>
|
||||
{/* Right Badges */}
|
||||
<div className="flex flex-row gap-3 items-center justify-end flex-shrink-0">
|
||||
<div className="bg-[#e1f8ec] rounded-lg px-4 py-2 flex items-center justify-center">
|
||||
<span className="text-[#10b981] font-inter font-bold" style={{ fontSize: '12px' }}>
|
||||
{t('why.liquidity.badge1')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="bg-[#ebf2ff] rounded-lg px-4 py-2 flex items-center justify-center">
|
||||
<span className="text-[#1447e6] font-inter font-bold" style={{ fontSize: '12px' }}>
|
||||
{t('why.liquidity.badge2')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user