feat(web): 新增 Landing 页面与 UI 优化\n\n- 新增 LandingPage、CryptoFeatureCard 等组件\n- 登录/注册页面与样式优化\n- 静态资源 images/main.png

This commit is contained in:
Ember
2025-11-01 23:36:28 +08:00
parent 775446d939
commit 9d4e86cbf5
10 changed files with 1435 additions and 8 deletions

View File

@@ -0,0 +1,119 @@
import * as React from "react";
import { motion } from "framer-motion";
import { Check } from "lucide-react";
import { cn } from "../lib/utils";
interface CryptoFeatureCardProps {
icon: React.ReactNode;
title: string;
description: string;
features: string[];
className?: string;
delay?: number;
}
export const CryptoFeatureCard = React.forwardRef<HTMLDivElement, CryptoFeatureCardProps>(
({ icon, title, description, features, className, delay = 0 }, ref) => {
const [isHovered, setIsHovered] = React.useState(false);
return (
<motion.div
ref={ref}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay }}
onHoverStart={() => setIsHovered(true)}
onHoverEnd={() => setIsHovered(false)}
className="relative h-full"
>
<div
className={cn(
"relative h-full overflow-hidden border-2 transition-all duration-300 rounded-xl",
"bg-gradient-to-br from-[#0C0E12] to-[#1E2329]",
"border-[#2B3139] hover:border-[#F0B90B]/50",
isHovered && "shadow-[0_0_20px_rgba(240,185,11,0.2)]",
className
)}
>
{/* Animated glow border effect */}
<motion.div
className="absolute inset-0 opacity-0 pointer-events-none"
animate={{
opacity: isHovered ? 1 : 0,
}}
transition={{ duration: 0.3 }}
>
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-[#F0B90B]/20 to-transparent animate-[shimmer_2s_infinite]" />
</motion.div>
{/* Background pattern */}
<div className="absolute inset-0 opacity-5">
<div
className="absolute inset-0"
style={{
backgroundImage: `radial-gradient(circle at 2px 2px, #F0B90B 1px, transparent 0)`,
backgroundSize: "32px 32px",
}}
/>
</div>
<div className="relative z-10 p-8 flex flex-col h-full">
{/* Icon container */}
<motion.div
className={cn(
"mb-6 inline-flex items-center justify-center w-16 h-16 rounded-xl",
"bg-gradient-to-br from-[#F0B90B]/20 to-[#F0B90B]/5",
"border border-[#F0B90B]/30"
)}
animate={{
scale: isHovered ? 1.1 : 1,
boxShadow: isHovered
? "0 0 20px rgba(240, 185, 11, 0.4)"
: "0 0 0px rgba(240, 185, 11, 0)",
}}
transition={{ duration: 0.3 }}
>
<div className="text-[#F0B90B]">{icon}</div>
</motion.div>
{/* Title */}
<h3 className="text-2xl font-bold text-[#EAECEF] mb-3">{title}</h3>
{/* Description */}
<p className="text-[#848E9C] mb-6 flex-grow leading-relaxed">{description}</p>
{/* Features list */}
<div className="space-y-3 mb-6">
{features.map((feature, index) => (
<motion.div
key={index}
initial={{ opacity: 0, x: -10 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ delay: delay + index * 0.1 }}
className="flex items-start gap-3"
>
<div className="mt-0.5 flex-shrink-0">
<div className="w-5 h-5 rounded-full bg-[#F0B90B]/20 flex items-center justify-center">
<Check className="w-3 h-3 text-[#F0B90B]" />
</div>
</div>
<span className="text-sm text-[#EAECEF]">{feature}</span>
</motion.div>
))}
</div>
</div>
{/* Corner accent */}
<div className="absolute bottom-0 right-0 w-32 h-32 opacity-10">
<div className="absolute inset-0 bg-gradient-to-tl from-[#F0B90B] to-transparent" />
</div>
</div>
</motion.div>
);
}
);
CryptoFeatureCard.displayName = "CryptoFeatureCard";

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@ import { useAuth } from '../contexts/AuthContext';
import { useLanguage } from '../contexts/LanguageContext';
import { t } from '../i18n/translations';
import { Header } from './Header';
import { ArrowLeft } from 'lucide-react';
export function LoginPage() {
const { language } = useLanguage();
@@ -52,9 +53,22 @@ export function LoginPage() {
return (
<div className="min-h-screen" style={{ background: '#0B0E11' }}>
<Header simple />
<div className="flex items-center justify-center" style={{ minHeight: 'calc(100vh - 80px)' }}>
<div className="w-full max-w-md">
{/* Back to Home */}
<button
onClick={() => {
window.history.pushState({}, '', '/');
window.dispatchEvent(new PopStateEvent('popstate'));
}}
className="flex items-center gap-2 mb-6 text-sm hover:text-[#F0B90B] transition-colors"
style={{ color: '#848E9C' }}
>
<ArrowLeft className="w-4 h-4" />
</button>
{/* Logo */}
<div className="text-center mb-8">
<div className="w-16 h-16 mx-auto mb-4 flex items-center justify-center">
@@ -191,4 +205,4 @@ export function LoginPage() {
</div>
</div>
);
}
}

View File

@@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { useAuth } from '../contexts/AuthContext';
import { useLanguage } from '../contexts/LanguageContext';
import { t } from '../i18n/translations';
import { ArrowLeft } from 'lucide-react';
export function RegisterPage() {
const { language } = useLanguage();
@@ -73,6 +74,21 @@ export function RegisterPage() {
return (
<div className="min-h-screen flex items-center justify-center" style={{ background: '#0B0E11' }}>
<div className="w-full max-w-md">
{/* Back to Home */}
{step === 'register' && (
<button
onClick={() => {
window.history.pushState({}, '', '/');
window.dispatchEvent(new PopStateEvent('popstate'));
}}
className="flex items-center gap-2 mb-6 text-sm hover:text-[#F0B90B] transition-colors"
style={{ color: '#848E9C' }}
>
<ArrowLeft className="w-4 h-4" />
</button>
)}
{/* Logo */}
<div className="text-center mb-8">
<div className="w-16 h-16 mx-auto mb-4 flex items-center justify-center">
@@ -307,4 +323,4 @@ export function RegisterPage() {
</div>
</div>
);
}
}