feat: 优化全局样式与组件视觉升级
- 更新 globals.css 全局样式 - 优化 not-found 404 页面 - 升级 footer, mega-dropdown, mobile-tab-bar 布局组件 - 改进 challenge, cta, hero 等核心 section 组件 - 优化 challenge-card, ink-glow-card, product-card 等 UI 组件 - 更新产品常量配置
This commit is contained in:
@@ -14,34 +14,13 @@ interface ChallengeCardProps {
|
||||
|
||||
interface ScenarioConfig {
|
||||
icon: LucideIcon;
|
||||
accentColor: string;
|
||||
accentColorRgb: string;
|
||||
glowStart: string;
|
||||
glowEnd: string;
|
||||
iconBgOpacity: number;
|
||||
}
|
||||
|
||||
const scenarioConfig: Record<string, ScenarioConfig> = {
|
||||
isolation: {
|
||||
icon: Lock,
|
||||
accentColor: 'var(--color-brand-primary)',
|
||||
accentColorRgb: '196, 30, 58',
|
||||
glowStart: 'var(--color-brand-primary)',
|
||||
glowEnd: 'var(--color-accent-purple)',
|
||||
},
|
||||
growth: {
|
||||
icon: TrendingUp,
|
||||
accentColor: 'var(--color-warning)',
|
||||
accentColorRgb: '217, 119, 6',
|
||||
glowStart: 'var(--color-warning)',
|
||||
glowEnd: 'var(--color-success)',
|
||||
},
|
||||
compliance: {
|
||||
icon: Shield,
|
||||
accentColor: 'var(--color-success)',
|
||||
accentColorRgb: '22, 163, 74',
|
||||
glowStart: 'var(--color-success)',
|
||||
glowEnd: 'var(--color-accent-cyan)',
|
||||
},
|
||||
isolation: { icon: Lock, iconBgOpacity: 0.06 },
|
||||
growth: { icon: TrendingUp, iconBgOpacity: 0.08 },
|
||||
compliance: { icon: Shield, iconBgOpacity: 0.05 },
|
||||
};
|
||||
|
||||
export function ChallengeCard({ title, description, scenario, href, index }: ChallengeCardProps) {
|
||||
@@ -52,19 +31,15 @@ export function ChallengeCard({ title, description, scenario, href, index }: Cha
|
||||
<InkGlowCard
|
||||
index={index}
|
||||
href={href}
|
||||
accentColorRgb={config.accentColorRgb}
|
||||
glowStart={config.glowStart}
|
||||
glowEnd={config.glowEnd}
|
||||
>
|
||||
<div className="p-6 md:p-8">
|
||||
<div className="flex items-start justify-between mb-5">
|
||||
<div
|
||||
className="w-11 h-11 rounded-xl flex items-center justify-center"
|
||||
style={{ backgroundColor: `rgba(${config.accentColorRgb}, 0.06)` }}
|
||||
style={{ backgroundColor: `rgba(var(--color-brand-primary-rgb), ${config.iconBgOpacity})` }}
|
||||
>
|
||||
<IconComponent
|
||||
className="w-5 h-5"
|
||||
style={{ color: config.accentColor }}
|
||||
className="w-5 h-5 text-[var(--color-brand-primary)]"
|
||||
strokeWidth={1.8}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useEffect, useRef, useMemo, useSyncExternalStore } from 'react';
|
||||
import { motion, useScroll, useTransform } from 'framer-motion';
|
||||
import { useReducedMotion } from '@/hooks/use-reduced-motion';
|
||||
|
||||
const PARTICLE_COUNT = 24;
|
||||
const PARTICLE_COUNT = 12;
|
||||
|
||||
interface Particle {
|
||||
x: number;
|
||||
|
||||
@@ -7,24 +7,16 @@ import { StaticLink } from '@/components/ui/static-link';
|
||||
interface InkGlowCardProps {
|
||||
children: ReactNode;
|
||||
index?: number;
|
||||
accentColorRgb?: string;
|
||||
glowStart?: string;
|
||||
glowEnd?: string;
|
||||
className?: string;
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const DEFAULT_ACCENT = '196, 30, 58';
|
||||
const DEFAULT_GLOW_START = 'var(--color-brand-primary)';
|
||||
const DEFAULT_GLOW_END = 'var(--color-warning)';
|
||||
const DEFAULT_ACCENT_RGB = '196, 30, 58';
|
||||
|
||||
export function InkGlowCard({
|
||||
children,
|
||||
index = 0,
|
||||
accentColorRgb = DEFAULT_ACCENT,
|
||||
glowStart = DEFAULT_GLOW_START,
|
||||
glowEnd = DEFAULT_GLOW_END,
|
||||
className = '',
|
||||
href,
|
||||
onClick,
|
||||
@@ -48,7 +40,7 @@ export function InkGlowCard({
|
||||
className="absolute inset-0 pointer-events-none transition-opacity duration-500"
|
||||
style={{
|
||||
opacity: isHovered ? 1 : 0,
|
||||
background: `radial-gradient(400px circle at ${mousePos.x}px ${mousePos.y}px, rgba(${accentColorRgb}, 0.04), transparent 40%)`,
|
||||
background: `radial-gradient(400px circle at ${mousePos.x}px ${mousePos.y}px, rgba(${DEFAULT_ACCENT_RGB}, 0.04), transparent 40%)`,
|
||||
}}
|
||||
/>
|
||||
<div className="relative z-10">{children}</div>
|
||||
@@ -58,19 +50,21 @@ export function InkGlowCard({
|
||||
return (
|
||||
<motion.div
|
||||
ref={cardRef}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
initial={{ opacity: 0, scale: 0.96, y: 16 }}
|
||||
whileInView={{ opacity: 1, scale: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: '-60px' }}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
delay: index * 0.08,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
}}
|
||||
className={`relative ink-glow-border rounded-2xl ${className}`}
|
||||
style={{
|
||||
'--glow-start': glowStart,
|
||||
'--glow-end': glowEnd,
|
||||
} as React.CSSProperties}
|
||||
style={
|
||||
{
|
||||
'--glow-start': 'var(--color-brand-primary)',
|
||||
'--glow-end': 'var(--color-warning)',
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{href ? (
|
||||
<StaticLink
|
||||
@@ -78,7 +72,7 @@ export function InkGlowCard({
|
||||
className="relative block rounded-2xl bg-[var(--color-bg-primary)] overflow-hidden transition-all duration-500"
|
||||
style={{
|
||||
boxShadow: isHovered
|
||||
? `0 20px 40px rgba(0,0,0,0.1), 0 0 0 1px rgba(${accentColorRgb}, 0.12)`
|
||||
? `0 20px 40px rgba(0,0,0,0.1), 0 0 0 1px rgba(${DEFAULT_ACCENT_RGB}, 0.12)`
|
||||
: '0 1px 3px rgba(0,0,0,0.04), 0 0 0 1px rgba(0,0,0,0.06)',
|
||||
transform: isHovered ? 'translateY(-6px)' : 'translateY(0)',
|
||||
}}
|
||||
@@ -93,7 +87,7 @@ export function InkGlowCard({
|
||||
className="relative rounded-2xl bg-[var(--color-bg-primary)] overflow-hidden transition-all duration-500"
|
||||
style={{
|
||||
boxShadow: isHovered
|
||||
? `0 20px 40px rgba(0,0,0,0.1), 0 0 0 1px rgba(${accentColorRgb}, 0.12)`
|
||||
? `0 20px 40px rgba(0,0,0,0.1), 0 0 0 1px rgba(${DEFAULT_ACCENT_RGB}, 0.12)`
|
||||
: '0 1px 3px rgba(0,0,0,0.04), 0 0 0 1px rgba(0,0,0,0.06)',
|
||||
transform: isHovered ? 'translateY(-6px)' : 'translateY(0)',
|
||||
}}
|
||||
|
||||
@@ -14,29 +14,25 @@ interface ProductCardProps {
|
||||
|
||||
interface ProductStyleConfig {
|
||||
icon: LucideIcon;
|
||||
accentColor: string;
|
||||
accentColorRgb: string;
|
||||
glowStart: string;
|
||||
glowEnd: string;
|
||||
iconBgOpacity: number;
|
||||
}
|
||||
|
||||
const productConfig: ProductStyleConfig[] = [
|
||||
{ icon: Database, accentColor: 'var(--color-brand-primary)', accentColorRgb: '196, 30, 58', glowStart: 'var(--color-brand-primary)', glowEnd: 'var(--color-warning)' },
|
||||
{ icon: Users, accentColor: 'var(--color-warning)', accentColorRgb: '217, 119, 6', glowStart: 'var(--color-warning)', glowEnd: 'var(--color-success)' },
|
||||
{ icon: FileText, accentColor: 'var(--color-accent-blue)', accentColorRgb: '37, 99, 235', glowStart: 'var(--color-accent-blue)', glowEnd: 'var(--color-accent-purple)' },
|
||||
{ icon: BarChart3, accentColor: 'var(--color-success)', accentColorRgb: '22, 163, 74', glowStart: 'var(--color-success)', glowEnd: 'var(--color-accent-cyan)' },
|
||||
{ icon: Truck, accentColor: 'var(--color-accent-purple)', accentColorRgb: '124, 58, 237', glowStart: 'var(--color-accent-purple)', glowEnd: 'var(--color-brand-primary)' },
|
||||
{ icon: Building2, accentColor: 'var(--color-accent-cyan)', accentColorRgb: '8, 145, 178', glowStart: 'var(--color-accent-cyan)', glowEnd: 'var(--color-accent-blue)' },
|
||||
];
|
||||
const productIcons: LucideIcon[] = [Database, Users, FileText, BarChart3, Truck, Building2];
|
||||
|
||||
const productConfig: ProductStyleConfig[] = productIcons.map((icon, i) => ({
|
||||
icon,
|
||||
iconBgOpacity: 0.04 + i * 0.015,
|
||||
}));
|
||||
|
||||
const defaultConfig: ProductStyleConfig = {
|
||||
icon: Database, accentColor: 'var(--color-brand-primary)', accentColorRgb: '196, 30, 58', glowStart: 'var(--color-brand-primary)', glowEnd: 'var(--color-warning)',
|
||||
icon: Database,
|
||||
iconBgOpacity: 0.06,
|
||||
};
|
||||
|
||||
const statusConfig = {
|
||||
'研发中': { bg: 'rgba(var(--color-brand-primary-rgb), 0.08)', text: 'var(--color-brand-primary)', border: 'rgba(var(--color-brand-primary-rgb), 0.15)' },
|
||||
'内测中': { bg: 'rgba(var(--color-warning-rgb), 0.08)', text: 'var(--color-warning)', border: 'rgba(var(--color-warning-rgb), 0.15)' },
|
||||
'已发布': { bg: 'rgba(var(--color-success-rgb), 0.08)', text: 'var(--color-success)', border: 'rgba(var(--color-success-rgb), 0.15)' },
|
||||
'研发中': { bg: 'rgba(var(--color-brand-primary-rgb), 0.06)', text: 'var(--color-brand-primary)', border: 'rgba(var(--color-brand-primary-rgb), 0.12)' },
|
||||
'内测中': { bg: 'rgba(var(--color-warning-rgb), 0.06)', text: 'var(--color-warning)', border: 'rgba(var(--color-warning-rgb), 0.12)' },
|
||||
'已发布': { bg: 'rgba(var(--color-success-rgb), 0.06)', text: 'var(--color-success)', border: 'rgba(var(--color-success-rgb), 0.12)' },
|
||||
} as const;
|
||||
|
||||
export function ProductCard({ title, description, href, index, status }: ProductCardProps) {
|
||||
@@ -48,19 +44,15 @@ export function ProductCard({ title, description, href, index, status }: Product
|
||||
<InkGlowCard
|
||||
index={index}
|
||||
href={href}
|
||||
accentColorRgb={config.accentColorRgb}
|
||||
glowStart={config.glowStart}
|
||||
glowEnd={config.glowEnd}
|
||||
>
|
||||
<div className="p-6 md:p-8">
|
||||
<div className="flex items-start justify-between mb-5">
|
||||
<div
|
||||
className="w-11 h-11 rounded-xl flex items-center justify-center"
|
||||
style={{ backgroundColor: `rgba(${config.accentColorRgb}, 0.06)` }}
|
||||
style={{ backgroundColor: `rgba(var(--color-brand-primary-rgb), ${config.iconBgOpacity})` }}
|
||||
>
|
||||
<IconComponent
|
||||
className="w-5 h-5"
|
||||
style={{ color: config.accentColor }}
|
||||
className="w-5 h-5 text-[var(--color-brand-primary)]"
|
||||
strokeWidth={1.8}
|
||||
/>
|
||||
</div>
|
||||
@@ -91,7 +83,7 @@ export function ProductCard({ title, description, href, index, status }: Product
|
||||
{description}
|
||||
</p>
|
||||
|
||||
<div className="flex items-center gap-1.5 text-sm font-medium text-[var(--color-text-subtle)] group-hover:text-[var(--color-brand-primary)] transition-colors">
|
||||
<div className="flex items-center gap-1.5 text-sm font-medium text-[var(--color-text-subtle)] group-hover:text-[var(--color-brand-primary)] transition-colors min-h-[44px]">
|
||||
<span>了解规划</span>
|
||||
<ArrowUpRight className="w-4 h-4" strokeWidth={2} />
|
||||
</div>
|
||||
|
||||
@@ -28,58 +28,22 @@ export function ScrollProgress() {
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: -4 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={shouldReduceMotion ? {} : { opacity: 0, y: -4 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
className="fixed top-0 left-0 right-0 h-[3px] z-[100] bg-transparent"
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={shouldReduceMotion ? {} : { opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="fixed top-0 left-0 right-0 h-[2px] z-[100]"
|
||||
role="progressbar"
|
||||
aria-label="页面滚动进度"
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
>
|
||||
<motion.div
|
||||
className="h-full origin-left relative"
|
||||
className="h-full origin-left"
|
||||
style={{
|
||||
scaleX,
|
||||
background:
|
||||
'linear-gradient(90deg, #C41E3A 0%, #E04A68 50%, #C41E3A 100%)',
|
||||
backgroundSize: '200% 100%',
|
||||
background: 'var(--color-brand-primary)',
|
||||
}}
|
||||
>
|
||||
<motion.div
|
||||
className="absolute inset-y-0 right-0 w-6 -mr-[2px]"
|
||||
style={{
|
||||
background: 'linear-gradient(90deg, transparent, rgba(196,30,58,0.6))',
|
||||
filter: 'blur(4px)',
|
||||
opacity: shouldReduceMotion ? 0 : 1,
|
||||
}}
|
||||
/>
|
||||
{!shouldReduceMotion && (
|
||||
<>
|
||||
<motion.div
|
||||
className="absolute top-1/2 -translate-y-1/2 right-0 w-2 h-2 rounded-full"
|
||||
style={{ background: '#fff', boxShadow: '0 0 6px rgba(196,30,58,0.8)' }}
|
||||
animate={{ scale: [1, 1.5, 1], opacity: [0.8, 1, 0.8] }}
|
||||
transition={{ duration: 1.5, repeat: Infinity, ease: 'easeInOut' }}
|
||||
/>
|
||||
<motion.div
|
||||
className="absolute bottom-0 left-0 right-0 h-[1px]"
|
||||
style={{
|
||||
background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.15), transparent)',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</motion.div>
|
||||
<div
|
||||
className="absolute inset-0 pointer-events-none"
|
||||
style={{
|
||||
background: 'linear-gradient(90deg, transparent, rgba(196,30,58,0.08), transparent)',
|
||||
filter: 'blur(8px)',
|
||||
transform: `scaleX(${typeof scaleX === 'object' ? 'var(--progress, 0)' : 1})`,
|
||||
}}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user