打磨细节

This commit is contained in:
2026-01-28 17:55:01 +08:00
parent 08af95116e
commit 0a1bd07492
36 changed files with 1649 additions and 466 deletions

File diff suppressed because one or more lines are too long

View File

@@ -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>

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

View File

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

View File

@@ -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>

View File

@@ -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')}

View File

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

View File

@@ -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}

View File

@@ -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>

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

View File

@@ -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 */}

View 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={() => {
// 动画完成后的回调
}}
/>
</>
);
}

View File

@@ -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>

View File

@@ -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>