Merge dev branch: Complete UI optimization with Chinese design system

- Implement comprehensive Chinese-style component library
- Add unified typography system with semantic font classes
- Optimize all pages with responsive design and Chinese aesthetics
- Fix button styling and enhance user experience
- Add loading states, empty states, and toast notifications
- Complete 12 Palaces Details optimization
- Establish consistent color scheme and visual hierarchy
This commit is contained in:
patdelphi
2025-08-19 22:27:40 +08:00
parent 6c295ba80b
commit a22b38babb
40 changed files with 3011 additions and 501 deletions

View File

@@ -395,7 +395,7 @@ const BaziAnalysisDisplay: React.FC<BaziAnalysisDisplayProps> = ({ birthDate })
{/* 天干信息 */}
<div className="bg-gradient-to-r from-red-50 to-yellow-50 rounded-lg p-3">
<h4 className="font-bold text-red-700 mb-2">{pillar.tiangan}</h4>
<div className="grid grid-cols-2 gap-2 text-sm">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 text-sm">
<div className={`px-2 py-1 rounded border ${wuxingColors[pillar.tianganWuxing]}`}>
{pillar.tianganWuxing}
</div>
@@ -408,7 +408,7 @@ const BaziAnalysisDisplay: React.FC<BaziAnalysisDisplayProps> = ({ birthDate })
{/* 地支信息 */}
<div className="bg-gradient-to-r from-yellow-50 to-red-50 rounded-lg p-3">
<h4 className="font-bold text-red-700 mb-2">{pillar.dizhi}</h4>
<div className="grid grid-cols-2 gap-2 text-sm">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 text-sm">
<div className={`px-2 py-1 rounded border ${wuxingColors[pillar.dizhiWuxing]}`}>
{pillar.dizhiWuxing}
</div>

View File

@@ -236,7 +236,7 @@ const CompleteBaziAnalysis: React.FC<CompleteBaziAnalysisProps> = ({ birthDate,
const total = Object.values(elements).reduce((sum: number, count: any) => sum + (typeof count === 'number' ? count : 0), 0) as number;
return (
<div className="grid grid-cols-5 gap-4">
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3 md:gap-4">
{Object.entries(elements).map(([element, count]) => {
const numCount = typeof count === 'number' ? count : 0;
const percentage = total > 0 ? Math.round((numCount / total) * 100) : 0;

View File

@@ -2,7 +2,10 @@ import React, { useState, useEffect } from 'react';
import { Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, ResponsiveContainer } from 'recharts';
import { Calendar, Star, BookOpen, Sparkles, User, BarChart3, Zap, TrendingUp, Loader2, Clock, Target, Heart, DollarSign, Activity, Crown, Compass, Moon, Sun } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from './ui/Card';
import { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle } from './ui/ChineseCard';
import { ChineseLoading } from './ui/ChineseLoading';
import { localApi } from '../lib/localApi';
import { cn } from '../lib/utils';
interface CompleteZiweiAnalysisProps {
birthDate: {
@@ -211,35 +214,35 @@ const CompleteZiweiAnalysis: React.FC<CompleteZiweiAnalysisProps> = ({ birthDate
}
};
// 星曜颜色配置
// 星曜颜色配置(中式配色)
const starColors: { [key: string]: string } = {
'紫微': 'bg-purple-100 text-purple-800 border-purple-300',
'紫微': 'bg-red-100 text-red-800 border-red-300',
'天机': 'bg-blue-100 text-blue-800 border-blue-300',
'太阳': 'bg-orange-100 text-orange-800 border-orange-300',
'太阳': 'bg-yellow-100 text-yellow-800 border-yellow-300',
'武曲': 'bg-gray-100 text-gray-800 border-gray-300',
'天同': 'bg-green-100 text-green-800 border-green-300',
'廉贞': 'bg-red-100 text-red-800 border-red-300',
'天府': 'bg-yellow-100 text-yellow-800 border-yellow-300',
'太阴': 'bg-indigo-100 text-indigo-800 border-indigo-300',
'贪狼': 'bg-pink-100 text-pink-800 border-pink-300',
'巨门': 'bg-slate-100 text-slate-800 border-slate-300',
'天相': 'bg-cyan-100 text-cyan-800 border-cyan-300',
'天梁': 'bg-emerald-100 text-emerald-800 border-emerald-300',
'七杀': 'bg-rose-100 text-rose-800 border-rose-300',
'破军': 'bg-amber-100 text-amber-800 border-amber-300'
'太阴': 'bg-blue-100 text-blue-800 border-blue-300',
'贪狼': 'bg-orange-100 text-orange-800 border-orange-300',
'巨门': 'bg-gray-100 text-gray-800 border-gray-300',
'天相': 'bg-green-100 text-green-800 border-green-300',
'天梁': 'bg-yellow-100 text-yellow-800 border-yellow-300',
'七杀': 'bg-red-100 text-red-800 border-red-300',
'破军': 'bg-orange-100 text-orange-800 border-orange-300'
};
// 吉星煞星颜色配置
const luckyStarColors = 'bg-green-50 text-green-700 border-green-200';
// 吉星煞星颜色配置(中式配色)
const luckyStarColors = 'bg-yellow-50 text-yellow-700 border-yellow-200';
const unluckyStarColors = 'bg-red-50 text-red-700 border-red-200';
// 宫位强度颜色
// 宫位强度颜色(中式配色)
const strengthColors: { [key: string]: string } = {
'旺': 'text-green-600 bg-green-50',
'得地': 'text-blue-600 bg-blue-50',
'平': 'text-yellow-600 bg-yellow-50',
'旺': 'text-red-600 bg-red-50',
'得地': 'text-yellow-600 bg-yellow-50',
'平': 'text-gray-600 bg-gray-50',
'不得地': 'text-orange-600 bg-orange-50',
'陷': 'text-red-600 bg-red-50'
'陷': 'text-gray-500 bg-gray-100'
};
// 五行局颜色
@@ -299,14 +302,19 @@ const CompleteZiweiAnalysis: React.FC<CompleteZiweiAnalysisProps> = ({ birthDate
// 渲染加载状态
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-purple-50 to-indigo-50">
<Card className="chinese-card-decoration border-2 border-purple-400 p-8">
<CardContent className="text-center">
<Loader2 className="h-12 w-12 animate-spin text-purple-600 mx-auto mb-4" />
<h3 className="text-xl font-bold text-purple-800 mb-2"></h3>
<p className="text-purple-600">...</p>
</CardContent>
</Card>
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-red-50 to-yellow-50">
<ChineseCard variant="elevated" className="p-8">
<ChineseCardContent className="text-center">
<ChineseLoading
size="lg"
variant="chinese"
text="正在进行专业紫微斗数分析"
className="mb-4"
/>
<h3 className="text-xl font-bold text-red-600 mb-2 font-chinese"></h3>
<p className="text-gray-600 font-chinese">...</p>
</ChineseCardContent>
</ChineseCard>
</div>
);
}
@@ -346,31 +354,63 @@ const CompleteZiweiAnalysis: React.FC<CompleteZiweiAnalysisProps> = ({ birthDate
);
}
// 渲染宫位卡片
// 渲染宫位卡片(中式设计)
const renderPalaceCard = (palaceName: string, palace: any) => {
if (!palace) return null;
// 宫位图标映射
const palaceIcons: { [key: string]: any } = {
'命宫': User,
'兄弟宫': Heart,
'夫妻宫': Heart,
'子女宫': Star,
'财帛宫': DollarSign,
'疾厄宫': Activity,
'迁移宫': Compass,
'交友宫': Heart,
'事业宫': Crown,
'田宅宫': Target,
'福德宫': Sun,
'父母宫': Moon
};
const PalaceIcon = palaceIcons[palaceName] || Star;
return (
<Card key={palaceName} className="chinese-card-decoration hover:shadow-xl transition-all duration-300 border-2 border-purple-400 min-h-[280px] w-full">
<CardHeader className="text-center pb-2">
<CardTitle className="text-purple-800 text-lg font-bold chinese-text-shadow">
{palaceName}
</CardTitle>
<div className="flex justify-center items-center space-x-2">
<span className="text-purple-600 text-sm">{palace.position}</span>
<span className={`px-2 py-1 rounded text-xs font-medium ${strengthColors[palace.strength] || 'text-gray-600 bg-gray-50'}`}>
{palace.strength}
</span>
<ChineseCard key={palaceName} variant="bordered" className="hover:shadow-xl transition-all duration-300 min-h-[320px] w-full">
<ChineseCardHeader className="text-center pb-3">
<div className="flex flex-col items-center space-y-2">
<div className="w-10 h-10 bg-gradient-to-br from-red-500 to-red-600 rounded-full flex items-center justify-center shadow-lg">
<PalaceIcon className="h-5 w-5 text-white" />
</div>
<ChineseCardTitle className="text-red-600 text-heading-lg font-bold font-chinese">
{palaceName}
</ChineseCardTitle>
<div className="flex items-center space-x-2">
<span className="text-gray-600 text-body-md font-chinese">{palace.position || palace.branch}</span>
<span className={cn(
'px-2 py-1 rounded-full text-label-md font-medium font-chinese',
strengthColors[palace.strength] || 'text-gray-600 bg-gray-50'
)}>
{palace.strength}
</span>
</div>
</div>
</CardHeader>
<CardContent className="space-y-3">
</ChineseCardHeader>
<ChineseCardContent className="space-y-3">
{/* 主星 */}
{palace.main_stars && palace.main_stars.length > 0 && (
<div>
<h5 className="text-xs font-semibold text-purple-800 mb-2"></h5>
<h5 className="text-label-lg font-semibold text-red-800 mb-2 font-chinese flex items-center">
<Star className="h-4 w-4 mr-1" />
</h5>
<div className="flex flex-wrap gap-1">
{palace.main_stars.map((star: string, index: number) => (
<span key={index} className={`px-2 py-1 rounded text-xs font-medium border ${starColors[star] || 'bg-gray-100 text-gray-800 border-gray-300'}`}>
<span key={index} className={cn(
'px-2 py-1 rounded-full text-label-md font-medium border font-chinese',
starColors[star] || 'bg-gray-100 text-gray-800 border-gray-300'
)}>
{star}
</span>
))}
@@ -381,10 +421,16 @@ const CompleteZiweiAnalysis: React.FC<CompleteZiweiAnalysisProps> = ({ birthDate
{/* 吉星 */}
{palace.lucky_stars && palace.lucky_stars.length > 0 && (
<div>
<h5 className="text-xs font-semibold text-green-800 mb-2"></h5>
<h5 className="text-label-lg font-semibold text-yellow-800 mb-2 font-chinese flex items-center">
<Sparkles className="h-4 w-4 mr-1" />
</h5>
<div className="flex flex-wrap gap-1">
{palace.lucky_stars.map((star: string, index: number) => (
<span key={index} className={`px-2 py-1 rounded text-xs font-medium border ${luckyStarColors}`}>
<span key={index} className={cn(
'px-2 py-1 rounded-full text-label-md font-medium border font-chinese',
luckyStarColors
)}>
{star}
</span>
))}
@@ -395,10 +441,16 @@ const CompleteZiweiAnalysis: React.FC<CompleteZiweiAnalysisProps> = ({ birthDate
{/* 煞星 */}
{palace.unlucky_stars && palace.unlucky_stars.length > 0 && (
<div>
<h5 className="text-xs font-semibold text-red-800 mb-2"></h5>
<h5 className="text-label-lg font-semibold text-red-800 mb-2 font-chinese flex items-center">
<Zap className="h-4 w-4 mr-1" />
</h5>
<div className="flex flex-wrap gap-1">
{palace.unlucky_stars.map((star: string, index: number) => (
<span key={index} className={`px-2 py-1 rounded text-xs font-medium border ${unluckyStarColors}`}>
<span key={index} className={cn(
'px-2 py-1 rounded-full text-label-md font-medium border font-chinese',
unluckyStarColors
)}>
{star}
</span>
))}
@@ -408,12 +460,16 @@ const CompleteZiweiAnalysis: React.FC<CompleteZiweiAnalysisProps> = ({ birthDate
{/* 宫位解读 */}
{palace.interpretation && (
<div className="border-t pt-2">
<p className="text-xs text-gray-700 leading-relaxed">{palace.interpretation}</p>
<div className="border-t border-red-100 pt-3 mt-3">
<h5 className="text-label-lg font-semibold text-gray-800 mb-2 font-chinese flex items-center">
<BookOpen className="h-4 w-4 mr-1" />
</h5>
<p className="text-body-md text-gray-700 leading-relaxed font-chinese">{palace.interpretation}</p>
</div>
)}
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
);
};
@@ -601,22 +657,26 @@ const CompleteZiweiAnalysis: React.FC<CompleteZiweiAnalysisProps> = ({ birthDate
)}
{/* 十二宫位详解 */}
<Card className="chinese-card-decoration border-2 border-purple-400">
<CardHeader>
<CardTitle className="text-purple-800 text-2xl font-bold chinese-text-shadow flex items-center space-x-2">
<Compass className="h-6 w-6" />
<span></span>
</CardTitle>
<p className="text-purple-600 mt-2"></p>
</CardHeader>
<CardContent>
<div className="grid xl:grid-cols-4 lg:grid-cols-3 md:grid-cols-2 sm:grid-cols-1 gap-4">
<ChineseCard variant="elevated" className="bg-gradient-to-br from-red-50 to-yellow-50">
<ChineseCardHeader>
<div className="text-center">
<div className="w-12 h-12 bg-gradient-to-br from-red-600 to-red-700 rounded-full flex items-center justify-center mx-auto mb-4 shadow-lg">
<Compass className="h-6 w-6 text-white" />
</div>
<ChineseCardTitle className="text-red-600 text-2xl md:text-3xl font-bold font-chinese">
</ChineseCardTitle>
<p className="text-gray-600 mt-2 font-chinese"></p>
</div>
</ChineseCardHeader>
<ChineseCardContent>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 md:gap-6">
{analysisData.ziwei_analysis?.twelve_palaces && Object.entries(analysisData.ziwei_analysis.twelve_palaces).map(([palaceName, palace]) =>
renderPalaceCard(palaceName, palace)
)}
</div>
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
{/* 四化飞星 */}
{analysisData.ziwei_analysis?.si_hua && (

View File

@@ -1,9 +1,10 @@
import React, { ReactNode } from 'react';
import React, { ReactNode, useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
import { Sparkles, User, History, LogOut, Home, Stars } from 'lucide-react';
import { Button } from './ui/Button';
import { Sparkles, User, History, LogOut, Home, Menu, X } from 'lucide-react';
import { ChineseButton } from './ui/ChineseButton';
import { toast } from 'sonner';
import { cn } from '../lib/utils';
interface LayoutProps {
children: ReactNode;
@@ -12,11 +13,13 @@ interface LayoutProps {
const Layout: React.FC<LayoutProps> = ({ children }) => {
const { user, signOut } = useAuth();
const location = useLocation();
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const handleSignOut = async () => {
try {
await signOut();
toast.success('登出成功');
setIsMobileMenuOpen(false);
} catch (error) {
toast.error('登出失败');
}
@@ -24,32 +27,44 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
const navigationItems = [
{ path: '/', label: '首页', icon: Home },
{ path: '/analysis', label: '命理分析', icon: Sparkles, requireAuth: true },
{ path: '/history', label: '历史记录', icon: History, requireAuth: true },
{ path: '/profile', label: '个人档案', icon: User, requireAuth: true },
{ path: '/analysis', label: '分析', icon: Sparkles, requireAuth: true },
{ path: '/history', label: '历史', icon: History, requireAuth: true },
{ path: '/profile', label: '档案', icon: User, requireAuth: true },
];
const toggleMobileMenu = () => {
console.log('Toggle mobile menu:', !isMobileMenuOpen);
setIsMobileMenuOpen(!isMobileMenuOpen);
};
const closeMobileMenu = () => {
setIsMobileMenuOpen(false);
};
return (
<div className="min-h-screen relative">
{/* 导航栏 */}
<nav className="chinese-traditional-bg shadow-2xl border-b-4 border-yellow-400 relative overflow-hidden">
<nav className="bg-gradient-to-r from-red-600 to-red-700 shadow-xl border-b-2 border-yellow-500 relative overflow-hidden z-[9998]">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div className="flex justify-between h-16">
<div className="flex justify-between items-center h-16">
{/* 品牌Logo */}
<div className="flex items-center">
<Link to="/" className="flex items-center space-x-3 group">
{/* 品牌图标 */}
<div className="w-8 h-8 bg-gradient-to-br from-yellow-400 to-amber-600 rounded-full flex items-center justify-center shadow-lg border-2 border-red-300 group-hover:scale-110 transition-transform duration-300">
<Link to="/" className="flex items-center space-x-2 group" onClick={closeMobileMenu}>
<div className="w-10 h-10 bg-gradient-to-br from-yellow-400 to-yellow-600 rounded-full flex items-center justify-center shadow-lg border-2 border-yellow-600 group-hover:scale-110 transition-transform duration-300">
<img
src="/traditional-chinese-bagua-eight-trigrams-black-gold.jpg"
alt="神机阁"
className="w-6 h-6 rounded-full object-cover"
className="w-7 h-7 rounded-full object-cover"
/>
</div>
<span className="text-2xl font-bold text-yellow-200 font-serif chinese-text-shadow group-hover:text-yellow-100 transition-colors duration-300"></span>
<span className="text-xl md:text-2xl font-bold text-white font-chinese group-hover:text-gold-100 transition-colors duration-300">
</span>
</Link>
</div>
<div className="flex items-center space-x-6">
{/* 桌面端导航 */}
<div className="hidden md:flex items-center space-x-4">
{navigationItems.map((item) => {
if (item.requireAuth && !user) return null;
@@ -60,38 +75,119 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
<Link
key={item.path}
to={item.path}
className={`flex items-center space-x-2 px-4 py-2 rounded-lg font-medium transition-all duration-300 border-2 font-serif ${
className={cn(
'flex items-center space-x-1.5 px-3 py-2 rounded-lg font-medium transition-all duration-300 text-sm',
'border border-transparent hover:border-yellow-400',
isActive
? 'text-red-800 chinese-golden-glow border-red-600 shadow-lg transform scale-105'
: 'text-yellow-200 hover:text-red-800 hover:chinese-golden-glow border-transparent hover:border-red-600 hover:shadow-lg hover:scale-105'
}`}
? 'text-yellow-100 bg-white/10 border-yellow-400 shadow-lg'
: 'text-white hover:text-yellow-100 hover:bg-white/10'
)}
>
<Icon className="h-5 w-5" />
<span>{item.label}</span>
<Icon className="h-4 w-4" />
<span className="whitespace-nowrap">{item.label}</span>
</Link>
);
})}
{user ? (
<Button
<ChineseButton
onClick={handleSignOut}
variant="outline"
className="flex items-center space-x-2 chinese-golden-glow text-red-800 border-2 border-red-600 hover:shadow-xl transition-all duration-300 font-serif"
size="sm"
className="text-white border-white hover:bg-white hover:text-red-600"
>
<LogOut className="h-5 w-5" />
<span></span>
</Button>
<LogOut className="h-4 w-4 mr-1" />
<span className="hidden lg:inline"></span>
</ChineseButton>
) : (
<div className="flex items-center space-x-3">
<div className="flex items-center space-x-2">
<Link to="/login">
<Button variant="outline" className="chinese-golden-glow text-red-800 border-2 border-red-600 hover:shadow-xl transition-all duration-300 font-serif">
<ChineseButton variant="outline" size="sm" className="text-white border-white hover:bg-white hover:text-red-600">
</Button>
</ChineseButton>
</Link>
<Link to="/register">
<Button className="chinese-red-glow text-white border-2 border-yellow-400 hover:shadow-xl transition-all duration-300 font-serif">
<ChineseButton variant="secondary" size="sm">
</Button>
</ChineseButton>
</Link>
</div>
)}
</div>
{/* 移动端汉堡菜单按钮 */}
<div className="md:hidden">
<button
onClick={toggleMobileMenu}
className="p-2 rounded-lg text-white hover:bg-white/10 transition-colors duration-200"
aria-label="切换菜单"
>
{isMobileMenuOpen ? (
<X className="h-6 w-6" />
) : (
<Menu className="h-6 w-6" />
)}
</button>
</div>
</div>
</div>
{/* 移动端菜单面板 */}
<div className={cn(
'md:hidden fixed top-16 left-0 right-0 z-[9999]',
'bg-red-600/95 backdrop-blur-md border-t border-yellow-500/30',
'transform transition-all duration-300 ease-in-out',
isMobileMenuOpen
? 'translate-y-0 opacity-100 visible'
: '-translate-y-2 opacity-0 invisible'
)}>
<div className="px-4 py-4 space-y-2">
{navigationItems.map((item) => {
if (item.requireAuth && !user) return null;
const Icon = item.icon;
const isActive = location.pathname === item.path;
return (
<Link
key={item.path}
to={item.path}
onClick={closeMobileMenu}
className={cn(
'flex items-center space-x-3 px-4 py-3 rounded-lg font-medium transition-all duration-200',
'border border-transparent',
isActive
? 'text-yellow-100 bg-white/15 border-yellow-400/50'
: 'text-white hover:text-yellow-100 hover:bg-white/10'
)}
>
<Icon className="h-5 w-5" />
<span>{item.label}</span>
</Link>
);
})}
<div className="pt-4 border-t border-white/20">
{user ? (
<ChineseButton
onClick={handleSignOut}
variant="outline"
className="w-full text-white border-white hover:bg-white hover:text-red-600"
>
<LogOut className="h-5 w-5 mr-2" />
</ChineseButton>
) : (
<div className="space-y-2">
<Link to="/login" onClick={closeMobileMenu} className="block">
<ChineseButton variant="outline" className="w-full text-white border-white hover:bg-white hover:text-red-600">
</ChineseButton>
</Link>
<Link to="/register" onClick={closeMobileMenu} className="block">
<ChineseButton variant="secondary" className="w-full">
</ChineseButton>
</Link>
</div>
)}
@@ -101,16 +197,16 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
</nav>
{/* 主内容区域 */}
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 relative">
{/* 主内容区装饰元素 */}
<div className="absolute top-0 left-0 w-24 h-24 opacity-10 pointer-events-none">
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6 md:py-8 relative min-h-[calc(100vh-200px)]">
{/* 装饰元素 - 仅在桌面端显示 */}
<div className="hidden lg:block absolute top-0 left-0 w-20 h-20 opacity-10 pointer-events-none">
<img
src="/chinese_traditional_golden_ornate_frame.png"
alt=""
className="w-full h-full object-contain"
/>
</div>
<div className="absolute bottom-0 right-0 w-24 h-24 opacity-10 pointer-events-none">
<div className="hidden lg:block absolute bottom-0 right-0 w-20 h-20 opacity-10 pointer-events-none">
<img
src="/chinese_traditional_golden_ornate_frame.png"
alt=""
@@ -118,22 +214,36 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
/>
</div>
{children}
{/* 点击遮罩层关闭移动端菜单 */}
{isMobileMenuOpen && (
<div
className="fixed inset-0 bg-black/20 z-[9997] md:hidden"
onClick={closeMobileMenu}
/>
)}
<div className="relative z-10">
{children}
</div>
</main>
{/* 页脚装饰 */}
<footer className="mt-16 py-8 border-t-2 border-yellow-400 mystical-gradient">
{/* 页脚 */}
<footer className="mt-auto py-6 md:py-8 border-t border-red-200 bg-gradient-to-br from-yellow-50 to-red-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center">
<div className="w-12 h-12 mx-auto mb-4 bg-gradient-to-br from-yellow-400 to-amber-600 rounded-full flex items-center justify-center shadow-lg border-2 border-red-600">
<div className="w-10 h-10 md:w-12 md:h-12 mx-auto mb-3 md:mb-4 bg-gradient-to-br from-yellow-400 to-yellow-600 rounded-full flex items-center justify-center shadow-lg border-2 border-red-500">
<img
src="/traditional_chinese_gold_red_dragon_symbol.jpg"
alt="龙符"
className="w-8 h-8 rounded-full object-cover"
className="w-6 h-6 md:w-8 md:h-8 rounded-full object-cover"
/>
</div>
<p className="text-red-700 font-medium font-serif"> - </p>
<p className="text-red-600 text-sm mt-2">© 2025 AI命理分析平台 - Created by MiniMax Agent</p>
<p className="text-red-600 font-medium font-chinese text-sm md:text-base">
-
</p>
<p className="text-gray-500 text-xs md:text-sm mt-1 md:mt-2">
© 2025 AI命理分析平台
</p>
</div>
</div>
</footer>

View File

@@ -0,0 +1,98 @@
import React from 'react';
import { cn } from '../../lib/utils';
interface ChineseButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
size?: 'sm' | 'md' | 'lg';
children: React.ReactNode;
}
const ChineseButton = React.forwardRef<HTMLButtonElement, ChineseButtonProps>(
({ className, variant = 'primary', size = 'md', children, ...props }, ref) => {
const baseClasses = [
'inline-flex items-center justify-center',
'font-chinese font-medium',
'transition-all duration-200 ease-in-out',
'focus:outline-none focus:ring-2 focus:ring-offset-2',
'disabled:opacity-50 disabled:cursor-not-allowed',
'relative overflow-hidden',
'active:scale-95 hover-lift',
];
const variantClasses = {
primary: [
'bg-gradient-to-r from-red-600 to-red-700 !text-white',
'border border-red-600',
'shadow-lg hover:shadow-xl',
'hover:scale-105 active:scale-95 hover:!text-white',
'focus:ring-red-500',
'relative overflow-hidden',
'before:absolute before:inset-0',
'before:bg-gradient-to-r before:from-transparent before:via-white/20 before:to-transparent',
'before:translate-x-[-100%] hover:before:translate-x-[100%]',
'before:transition-transform before:duration-700',
],
secondary: [
'bg-gradient-to-r from-yellow-400 to-yellow-500 text-gray-900',
'border border-yellow-500',
'shadow-lg hover:shadow-xl',
'hover:scale-105 active:scale-95',
'focus:ring-yellow-500',
],
outline: [
'bg-transparent text-red-600',
'border-2 border-red-600',
'hover:bg-red-600 hover:text-white',
'focus:ring-red-500',
],
ghost: [
'bg-transparent text-gray-700',
'hover:bg-gray-100 hover:text-red-600',
'focus:ring-gray-500',
],
};
const sizeClasses = {
sm: [
'px-3 py-1.5 text-button-sm rounded-md',
'min-h-[36px]', // 移动端友好的最小高度
],
md: [
'px-6 py-2.5 text-button-md rounded-lg',
'min-h-[44px]', // 移动端友好的最小高度
],
lg: [
'px-8 py-3 text-button-lg rounded-xl',
'min-h-[52px]', // 移动端友好的最小高度
],
};
// 移动端响应式调整
const responsiveClasses = [
'md:hover:scale-105', // 只在桌面端启用悬停缩放
'active:scale-95', // 所有设备都有点击反馈
'touch-manipulation', // 优化触摸体验
];
return (
<button
className={cn(
baseClasses,
variantClasses[variant],
sizeClasses[size],
responsiveClasses,
className
)}
ref={ref}
{...props}
>
{children}
</button>
);
}
);
ChineseButton.displayName = 'ChineseButton';
export { ChineseButton };
export type { ChineseButtonProps };

View File

@@ -0,0 +1,233 @@
import React from 'react';
import { cn } from '../../lib/utils';
interface ChineseCardProps extends React.HTMLAttributes<HTMLDivElement> {
variant?: 'default' | 'elevated' | 'bordered' | 'golden';
padding?: 'sm' | 'md' | 'lg';
children: React.ReactNode;
}
const ChineseCard = React.forwardRef<HTMLDivElement, ChineseCardProps>(
({ className, variant = 'default', padding = 'md', children, ...props }, ref) => {
const baseClasses = [
'relative',
'transition-all duration-300 ease-in-out',
'font-chinese hover-lift animate-fade-in-up',
];
const variantClasses = {
default: [
'bg-white/90 backdrop-blur-sm',
'border border-paper-300',
'rounded-lg',
'shadow-chinese-sm hover:shadow-chinese',
],
elevated: [
'bg-white/95 backdrop-blur-md',
'border border-cinnabar-200',
'rounded-xl',
'shadow-chinese hover:shadow-chinese-md',
'hover:-translate-y-1',
],
bordered: [
'bg-paper-50/80 backdrop-blur-sm',
'border-2 border-cinnabar-300',
'rounded-lg',
'shadow-paper',
// 传统边框装饰
'before:absolute before:inset-2',
'before:border before:border-gold-300/50',
'before:rounded-md before:pointer-events-none',
],
golden: [
'bg-gold-gradient',
'border-2 border-gold-600',
'rounded-xl',
'shadow-gold hover:shadow-gold',
'text-ink-900',
// 金色光晕效果
'before:absolute before:inset-0',
'before:bg-gradient-to-br before:from-white/20 before:to-transparent',
'before:rounded-xl before:pointer-events-none',
],
};
const paddingClasses = {
sm: 'p-4',
md: 'p-6',
lg: 'p-8',
};
// 移动端响应式调整
const responsiveClasses = [
// 移动端减少内边距
'max-md:p-4',
// 移动端优化圆角
'max-md:rounded-lg',
];
return (
<div
className={cn(
baseClasses,
variantClasses[variant],
paddingClasses[padding],
responsiveClasses,
className
)}
ref={ref}
{...props}
>
{children}
</div>
);
}
);
ChineseCard.displayName = 'ChineseCard';
// 卡片标题组件
interface ChineseCardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
const ChineseCardHeader = React.forwardRef<HTMLDivElement, ChineseCardHeaderProps>(
({ className, children, ...props }, ref) => {
return (
<div
className={cn(
'flex flex-col space-y-1.5',
'pb-4 mb-4',
'border-b border-cinnabar-200',
className
)}
ref={ref}
{...props}
>
{children}
</div>
);
}
);
ChineseCardHeader.displayName = 'ChineseCardHeader';
// 卡片标题文字组件
interface ChineseCardTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
children: React.ReactNode;
}
const ChineseCardTitle = React.forwardRef<HTMLParagraphElement, ChineseCardTitleProps>(
({ className, children, ...props }, ref) => {
return (
<h3
className={cn(
'text-heading-md font-semibold leading-none tracking-tight',
'text-cinnabar-500',
className
)}
ref={ref}
{...props}
>
{children}
</h3>
);
}
);
ChineseCardTitle.displayName = 'ChineseCardTitle';
// 卡片描述组件
interface ChineseCardDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {
children: React.ReactNode;
}
const ChineseCardDescription = React.forwardRef<HTMLParagraphElement, ChineseCardDescriptionProps>(
({ className, children, ...props }, ref) => {
return (
<p
className={cn(
'text-body-md text-ink-500',
'font-chinese',
'leading-relaxed',
className
)}
ref={ref}
{...props}
>
{children}
</p>
);
}
);
ChineseCardDescription.displayName = 'ChineseCardDescription';
// 卡片内容组件
interface ChineseCardContentProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
const ChineseCardContent = React.forwardRef<HTMLDivElement, ChineseCardContentProps>(
({ className, children, ...props }, ref) => {
return (
<div
className={cn(
'text-ink-900',
'leading-relaxed',
className
)}
ref={ref}
{...props}
>
{children}
</div>
);
}
);
ChineseCardContent.displayName = 'ChineseCardContent';
// 卡片底部组件
interface ChineseCardFooterProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
const ChineseCardFooter = React.forwardRef<HTMLDivElement, ChineseCardFooterProps>(
({ className, children, ...props }, ref) => {
return (
<div
className={cn(
'flex items-center',
'pt-4 mt-4',
'border-t border-paper-300',
className
)}
ref={ref}
{...props}
>
{children}
</div>
);
}
);
ChineseCardFooter.displayName = 'ChineseCardFooter';
export {
ChineseCard,
ChineseCardHeader,
ChineseCardTitle,
ChineseCardDescription,
ChineseCardContent,
ChineseCardFooter,
};
export type {
ChineseCardProps,
ChineseCardHeaderProps,
ChineseCardTitleProps,
ChineseCardDescriptionProps,
ChineseCardContentProps,
ChineseCardFooterProps,
};

View File

@@ -0,0 +1,91 @@
import React from 'react';
import { cn } from '../../lib/utils';
import { FileX, Search, Inbox, AlertCircle } from 'lucide-react';
import { ChineseButton } from './ChineseButton';
interface ChineseEmptyProps {
type?: 'default' | 'search' | 'data' | 'error';
title?: string;
description?: string;
action?: {
label: string;
onClick: () => void;
};
className?: string;
}
const ChineseEmpty: React.FC<ChineseEmptyProps> = ({
type = 'default',
title,
description,
action,
className
}) => {
const iconMap = {
default: Inbox,
search: Search,
data: FileX,
error: AlertCircle
};
const defaultTitles = {
default: '暂无数据',
search: '未找到相关内容',
data: '暂无记录',
error: '加载失败'
};
const defaultDescriptions = {
default: '这里还没有任何内容',
search: '请尝试其他关键词或调整筛选条件',
data: '您还没有创建任何记录',
error: '数据加载出现问题,请稍后重试'
};
const colorMap = {
default: 'text-gray-400',
search: 'text-blue-400',
data: 'text-yellow-400',
error: 'text-red-400'
};
const Icon = iconMap[type];
const displayTitle = title || defaultTitles[type];
const displayDescription = description || defaultDescriptions[type];
const iconColor = colorMap[type];
return (
<div className={cn(
'flex flex-col items-center justify-center py-12 px-4 text-center',
className
)}>
{/* 图标 */}
<div className="mb-4">
<Icon className={cn('h-16 w-16', iconColor)} />
</div>
{/* 标题 */}
<h3 className="text-lg font-semibold text-gray-900 mb-2 font-chinese">
{displayTitle}
</h3>
{/* 描述 */}
<p className="text-gray-600 mb-6 max-w-sm font-chinese leading-relaxed">
{displayDescription}
</p>
{/* 操作按钮 */}
{action && (
<ChineseButton
variant={type === 'error' ? 'primary' : 'secondary'}
onClick={action.onClick}
>
{action.label}
</ChineseButton>
)}
</div>
);
};
export { ChineseEmpty };
export type { ChineseEmptyProps };

View File

@@ -0,0 +1,115 @@
import React from 'react';
import { cn } from '../../lib/utils';
interface ChineseInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string;
error?: string;
helperText?: string;
variant?: 'default' | 'bordered' | 'filled';
size?: 'sm' | 'md' | 'lg';
}
const ChineseInput = React.forwardRef<HTMLInputElement, ChineseInputProps>(
({ className, label, error, helperText, variant = 'default', size = 'md', ...props }, ref) => {
const baseClasses = [
'w-full font-chinese transition-all duration-200 ease-in-out',
'focus:outline-none focus:ring-2 focus:ring-offset-1',
'disabled:opacity-50 disabled:cursor-not-allowed',
'placeholder:text-gray-400',
];
const variantClasses = {
default: [
'bg-white border border-gray-300',
'hover:border-red-400 focus:border-red-500 focus:ring-red-500/20',
error ? 'border-red-500 focus:border-red-500 focus:ring-red-500/20' : '',
],
bordered: [
'bg-transparent border-2 border-red-300',
'hover:border-red-500 focus:border-red-600 focus:ring-red-500/20',
error ? 'border-red-500 focus:border-red-600 focus:ring-red-500/20' : '',
],
filled: [
'bg-red-50 border border-red-200',
'hover:bg-red-100 hover:border-red-300',
'focus:bg-white focus:border-red-500 focus:ring-red-500/20',
error ? 'bg-red-100 border-red-500 focus:border-red-500 focus:ring-red-500/20' : '',
],
};
const sizeClasses = {
sm: [
'px-3 py-2 text-body-md rounded-md',
'min-h-[36px]', // 移动端友好
],
md: [
'px-4 py-2.5 text-body-lg rounded-lg',
'min-h-[44px]', // 移动端友好
],
lg: [
'px-5 py-3 text-body-xl rounded-xl',
'min-h-[52px]', // 移动端友好
],
};
// 移动端响应式调整
const responsiveClasses = [
'touch-manipulation', // 优化触摸体验
'max-md:text-base', // 移动端字体调整
];
return (
<div className="w-full">
{/* 标签 */}
{label && (
<label className="block text-label-lg font-medium text-gray-700 mb-2 font-chinese">
{label}
{props.required && <span className="text-red-500 ml-1">*</span>}
</label>
)}
{/* 输入框 */}
<div className="relative">
<input
className={cn(
baseClasses,
variantClasses[variant],
sizeClasses[size],
responsiveClasses,
className
)}
ref={ref}
{...props}
/>
{/* 错误状态图标 */}
{error && (
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<svg className="h-5 w-5 text-red-500" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
</svg>
</div>
)}
</div>
{/* 错误信息或帮助文本 */}
{(error || helperText) && (
<div className="mt-1.5">
{error ? (
<p className="text-body-sm text-red-600 font-chinese">{error}</p>
) : (
helperText && (
<p className="text-body-sm text-gray-500 font-chinese">{helperText}</p>
)
)}
</div>
)}
</div>
);
}
);
ChineseInput.displayName = 'ChineseInput';
export { ChineseInput };
export type { ChineseInputProps };

View File

@@ -0,0 +1,99 @@
import React from 'react';
import { cn } from '../../lib/utils';
import { Loader2, Sparkles } from 'lucide-react';
interface ChineseLoadingProps {
size?: 'sm' | 'md' | 'lg';
variant?: 'spinner' | 'dots' | 'chinese';
text?: string;
className?: string;
}
const ChineseLoading: React.FC<ChineseLoadingProps> = ({
size = 'md',
variant = 'chinese',
text,
className
}) => {
const sizeClasses = {
sm: 'h-4 w-4',
md: 'h-8 w-8',
lg: 'h-12 w-12'
};
const textSizeClasses = {
sm: 'text-sm',
md: 'text-base',
lg: 'text-lg'
};
const renderSpinner = () => (
<Loader2 className={cn(
'animate-spin text-red-600',
sizeClasses[size]
)} />
);
const renderDots = () => (
<div className="flex space-x-1">
{[0, 1, 2].map((i) => (
<div
key={i}
className={cn(
'bg-red-600 rounded-full animate-pulse',
size === 'sm' ? 'h-2 w-2' : size === 'md' ? 'h-3 w-3' : 'h-4 w-4'
)}
style={{
animationDelay: `${i * 0.2}s`,
animationDuration: '1s'
}}
/>
))}
</div>
);
const renderChinese = () => (
<div className="relative">
<div className={cn(
'border-4 border-red-200 border-t-red-600 rounded-full animate-spin',
sizeClasses[size]
)} />
<Sparkles className={cn(
'absolute inset-0 m-auto text-yellow-500 animate-pulse',
size === 'sm' ? 'h-2 w-2' : size === 'md' ? 'h-4 w-4' : 'h-6 w-6'
)} />
</div>
);
const renderVariant = () => {
switch (variant) {
case 'spinner':
return renderSpinner();
case 'dots':
return renderDots();
case 'chinese':
default:
return renderChinese();
}
};
return (
<div className={cn(
'flex flex-col items-center justify-center space-y-3',
className
)}>
{renderVariant()}
{text && (
<p className={cn(
'text-gray-600 font-chinese',
textSizeClasses[size]
)}>
{text}
</p>
)}
</div>
);
};
export { ChineseLoading };
export type { ChineseLoadingProps };

View File

@@ -0,0 +1,160 @@
import React from 'react';
import { cn } from '../../lib/utils';
import { ChevronDown } from 'lucide-react';
interface ChineseSelectOption {
value: string;
label: string;
disabled?: boolean;
}
interface ChineseSelectProps extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, 'size'> {
label?: string;
error?: string;
helperText?: string;
variant?: 'default' | 'bordered' | 'filled';
size?: 'sm' | 'md' | 'lg';
options: ChineseSelectOption[];
placeholder?: string;
}
const ChineseSelect = React.forwardRef<HTMLSelectElement, ChineseSelectProps>(
({ className, label, error, helperText, variant = 'default', size = 'md', options, placeholder, ...props }, ref) => {
const baseClasses = [
'w-full font-chinese transition-all duration-200 ease-in-out',
'focus:outline-none focus:ring-2 focus:ring-offset-1',
'disabled:opacity-50 disabled:cursor-not-allowed',
'appearance-none cursor-pointer',
'bg-no-repeat bg-right',
];
const variantClasses = {
default: [
'bg-white border border-gray-300',
'hover:border-red-400 focus:border-red-500 focus:ring-red-500/20',
error ? 'border-red-500 focus:border-red-500 focus:ring-red-500/20' : '',
],
bordered: [
'bg-transparent border-2 border-red-300',
'hover:border-red-500 focus:border-red-600 focus:ring-red-500/20',
error ? 'border-red-500 focus:border-red-600 focus:ring-red-500/20' : '',
],
filled: [
'bg-red-50 border border-red-200',
'hover:bg-red-100 hover:border-red-300',
'focus:bg-white focus:border-red-500 focus:ring-red-500/20',
error ? 'bg-red-100 border-red-500 focus:border-red-500 focus:ring-red-500/20' : '',
],
};
const sizeClasses = {
sm: [
'px-3 py-2 pr-8 text-sm rounded-md',
'min-h-[36px]', // 移动端友好
],
md: [
'px-4 py-2.5 pr-10 text-base rounded-lg',
'min-h-[44px]', // 移动端友好
],
lg: [
'px-5 py-3 pr-12 text-lg rounded-xl',
'min-h-[52px]', // 移动端友好
],
};
// 移动端响应式调整
const responsiveClasses = [
'touch-manipulation', // 优化触摸体验
'max-md:text-base', // 移动端字体调整
];
return (
<div className="w-full">
{/* 标签 */}
{label && (
<label className="block text-sm font-medium text-gray-700 mb-2 font-chinese">
{label}
{props.required && <span className="text-red-500 ml-1">*</span>}
</label>
)}
{/* 选择器容器 */}
<div className="relative">
<select
className={cn(
baseClasses,
variantClasses[variant],
sizeClasses[size],
responsiveClasses,
className
)}
ref={ref}
{...props}
>
{/* 占位符选项 */}
{placeholder && (
<option value="" disabled>
{placeholder}
</option>
)}
{/* 选项列表 */}
{options.map((option) => (
<option
key={option.value}
value={option.value}
disabled={option.disabled}
className="font-chinese"
>
{option.label}
</option>
))}
</select>
{/* 下拉箭头 */}
<div className="absolute inset-y-0 right-0 flex items-center pointer-events-none">
<div className={cn(
'pr-2',
size === 'sm' ? 'pr-2' : size === 'md' ? 'pr-3' : 'pr-4'
)}>
<ChevronDown className={cn(
'text-gray-400',
size === 'sm' ? 'h-4 w-4' : size === 'md' ? 'h-5 w-5' : 'h-6 w-6'
)} />
</div>
</div>
{/* 错误状态图标 */}
{error && (
<div className={cn(
'absolute inset-y-0 right-0 flex items-center pointer-events-none',
size === 'sm' ? 'pr-7' : size === 'md' ? 'pr-9' : 'pr-11'
)}>
<svg className="h-5 w-5 text-red-500" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
</svg>
</div>
)}
</div>
{/* 错误信息或帮助文本 */}
{(error || helperText) && (
<div className="mt-1.5">
{error ? (
<p className="text-sm text-red-600 font-chinese">{error}</p>
) : (
helperText && (
<p className="text-sm text-gray-500 font-chinese">{helperText}</p>
)
)}
</div>
)}
</div>
);
}
);
ChineseSelect.displayName = 'ChineseSelect';
export { ChineseSelect };
export type { ChineseSelectProps, ChineseSelectOption };

View File

@@ -0,0 +1,109 @@
import React from 'react';
import { cn } from '../../lib/utils';
import { CheckCircle, XCircle, AlertCircle, Info, X } from 'lucide-react';
interface ChineseToastProps {
type?: 'success' | 'error' | 'warning' | 'info';
title?: string;
message: string;
onClose?: () => void;
className?: string;
}
const ChineseToast: React.FC<ChineseToastProps> = ({
type = 'info',
title,
message,
onClose,
className
}) => {
const iconMap = {
success: CheckCircle,
error: XCircle,
warning: AlertCircle,
info: Info
};
const colorMap = {
success: {
bg: 'bg-green-50',
border: 'border-green-200',
icon: 'text-green-600',
title: 'text-green-800',
message: 'text-green-700'
},
error: {
bg: 'bg-red-50',
border: 'border-red-200',
icon: 'text-red-600',
title: 'text-red-800',
message: 'text-red-700'
},
warning: {
bg: 'bg-yellow-50',
border: 'border-yellow-200',
icon: 'text-yellow-600',
title: 'text-yellow-800',
message: 'text-yellow-700'
},
info: {
bg: 'bg-blue-50',
border: 'border-blue-200',
icon: 'text-blue-600',
title: 'text-blue-800',
message: 'text-blue-700'
}
};
const Icon = iconMap[type];
const colors = colorMap[type];
return (
<div className={cn(
'flex items-start p-4 rounded-lg border shadow-lg',
'animate-in slide-in-from-top-2 duration-300',
colors.bg,
colors.border,
className
)}>
{/* 图标 */}
<div className="flex-shrink-0">
<Icon className={cn('h-5 w-5', colors.icon)} />
</div>
{/* 内容 */}
<div className="ml-3 flex-1">
{title && (
<h4 className={cn(
'text-sm font-semibold font-chinese mb-1',
colors.title
)}>
{title}
</h4>
)}
<p className={cn(
'text-sm font-chinese leading-relaxed',
colors.message
)}>
{message}
</p>
</div>
{/* 关闭按钮 */}
{onClose && (
<button
onClick={onClose}
className={cn(
'flex-shrink-0 ml-4 p-1 rounded-md hover:bg-white/50 transition-colors',
colors.icon
)}
>
<X className="h-4 w-4" />
</button>
)}
</div>
);
};
export { ChineseToast };
export type { ChineseToastProps };

View File

@@ -1,11 +1,31 @@
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700;900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
/* 导入字体规范系统 */
@import './styles/typography.css';
@layer base {
:root {
--radius: 0.5rem;
/* 中式设计系统颜色变量 */
--cinnabar-50: #fef2f2;
--cinnabar-500: #DC143C;
--cinnabar-900: #7c1420;
--gold-50: #fffbeb;
--gold-500: #FFD700;
--gold-900: #78350f;
--ink-50: #f8fafc;
--ink-500: #64748b;
--ink-900: #2C2C2C;
--paper-50: #fefefe;
--paper-500: #F5F5DC;
--paper-900: #a8a88a;
/* 传统组件颜色 */
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
@@ -14,11 +34,27 @@
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
/* 传统中式颜色 */
--chinese-red: #dc2626;
--chinese-gold: #facc15;
--chinese-dark-red: #991b1b;
--chinese-deep-gold: #d97706;
/* 兼容性颜色变量 */
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 346 77% 49.8%;
--primary-foreground: 355.7 100% 97.3%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 346 77% 49.8%;
}
.dark {
@@ -33,10 +69,56 @@
}
body {
font-family: 'Noto Serif SC', serif;
background: linear-gradient(135deg, #fef7cd 0%, #fed7aa 25%, #fecaca 50%, #fed7aa 75%, #fef7cd 100%);
font-family: var(--font-chinese);
background: linear-gradient(135deg, var(--paper-500) 0%, var(--gold-50) 100%);
background-attachment: fixed;
position: relative;
color: var(--ink-900);
/* 使用标准正文字体规范 */
@apply text-body-lg;
}
/* 标题默认样式 - 使用新的字体规范 */
h1 {
@apply text-heading-xl font-chinese font-semibold;
color: var(--cinnabar-500);
}
h2 {
@apply text-heading-lg font-chinese font-semibold;
color: var(--cinnabar-500);
}
h3 {
@apply text-heading-md font-chinese font-semibold;
color: var(--ink-900);
}
h4 {
@apply text-heading-sm font-chinese font-semibold;
color: var(--ink-900);
}
h5 {
@apply text-heading-xs font-chinese font-semibold;
color: var(--ink-900);
}
h6 {
@apply text-body-xl font-chinese font-semibold;
color: var(--ink-900);
}
/* 按钮基础样式 */
button {
font-family: inherit;
transition: all 0.2s ease-in-out;
}
/* 输入框基础样式 */
input, textarea, select {
font-family: inherit;
transition: all 0.2s ease-in-out;
}
body::before {
@@ -206,6 +288,137 @@ img {
object-position: top;
}
/* 中式动画效果 */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes slideInFromTop {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.animate-fade-in-up {
animation: fadeInUp 0.6s ease-out;
}
.animate-slide-in-right {
animation: slideInRight 0.5s ease-out;
}
.animate-scale-in {
animation: scaleIn 0.3s ease-out;
}
.animate-slide-in-from-top {
animation: slideInFromTop 0.4s ease-out;
}
/* 页面切换动画 */
.page-transition-enter {
opacity: 0;
transform: translateY(20px);
}
.page-transition-enter-active {
opacity: 1;
transform: translateY(0);
transition: opacity 0.3s ease-out, transform 0.3s ease-out;
}
.page-transition-exit {
opacity: 1;
transform: translateY(0);
}
.page-transition-exit-active {
opacity: 0;
transform: translateY(-20px);
transition: opacity 0.3s ease-out, transform 0.3s ease-out;
}
/* 悬停效果增强 */
.hover-lift {
transition: transform 0.2s ease-out, box-shadow 0.2s ease-out;
}
.hover-lift:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
/* 中式装饰动画 */
@keyframes float {
0%, 100% {
transform: translateY(0px);
}
50% {
transform: translateY(-10px);
}
}
.animate-float {
animation: float 3s ease-in-out infinite;
}
/* 加载动画优化 */
@keyframes spin-slow {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.animate-spin-slow {
animation: spin-slow 2s linear infinite;
}
.fixed {
position: fixed;
}

View File

@@ -1,14 +1,15 @@
import React, { useState, useEffect, useMemo } from 'react';
import { useAuth } from '../contexts/AuthContext';
import { localApi } from '../lib/localApi';
import { Button } from '../components/ui/Button';
import { Input } from '../components/ui/Input';
import { Select } from '../components/ui/Select';
import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/Card';
import { ChineseButton } from '../components/ui/ChineseButton';
import { ChineseInput } from '../components/ui/ChineseInput';
import { ChineseSelect } from '../components/ui/ChineseSelect';
import { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle } from '../components/ui/ChineseCard';
import AnalysisResultDisplay from '../components/AnalysisResultDisplay';
import { toast } from 'sonner';
import { Sparkles, Star, Compass, Calendar, MapPin, User, Loader2 } from 'lucide-react';
import { UserProfile, AnalysisRequest, NumerologyReading } from '../types';
import { cn } from '../lib/utils';
type AnalysisType = 'bazi' | 'ziwei' | 'yijing';
@@ -173,40 +174,46 @@ const AnalysisPage: React.FC = () => {
title: '八字命理',
description: '基于传统八字学说,分析五行平衡、格局特点、四柱信息',
icon: Sparkles,
color: 'text-purple-600',
bgColor: 'bg-purple-50',
borderColor: 'border-purple-200'
color: 'text-red-600',
bgColor: 'bg-red-50',
borderColor: 'border-red-300'
},
{
type: 'ziwei' as AnalysisType,
title: '紫微斗数',
description: '通过星曜排布和十二宫位分析性格命运',
icon: Star,
color: 'text-blue-600',
bgColor: 'bg-blue-50',
borderColor: 'border-blue-200'
color: 'text-yellow-600',
bgColor: 'bg-yellow-50',
borderColor: 'border-yellow-300'
},
{
type: 'yijing' as AnalysisType,
title: '易经占卜',
description: '运用梅花易数起卦法,解读卦象含义,指导人生决策',
icon: Compass,
color: 'text-amber-600',
bgColor: 'bg-amber-50',
borderColor: 'border-amber-200'
color: 'text-orange-600',
bgColor: 'bg-orange-50',
borderColor: 'border-orange-300'
}
];
return (
<div className="max-w-7xl mx-auto px-4 space-y-8">
<div className="max-w-7xl mx-auto px-4 py-6 space-y-6 md:space-y-8">
{/* 页面标题 */}
<div className="text-center">
<h1 className="text-2xl md:text-3xl font-bold text-red-600 font-chinese mb-2"></h1>
<p className="text-gray-600 font-chinese"></p>
</div>
{/* 分析类型选择 */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
<p className="text-gray-600"></p>
</CardHeader>
<CardContent>
<div className="grid md:grid-cols-3 gap-4">
<ChineseCard variant="elevated">
<ChineseCardHeader>
<ChineseCardTitle className="text-red-600 font-chinese"></ChineseCardTitle>
<p className="text-gray-600 font-chinese"></p>
</ChineseCardHeader>
<ChineseCardContent>
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
{analysisTypes.map((type) => {
const Icon = type.icon;
const isSelected = analysisType === type.type;
@@ -214,66 +221,80 @@ const AnalysisPage: React.FC = () => {
<div
key={type.type}
onClick={() => setAnalysisType(type.type)}
className={`p-4 rounded-lg border-2 cursor-pointer transition-all ${
className={cn(
'p-4 md:p-5 rounded-lg border-2 cursor-pointer transition-all duration-200',
'hover:shadow-md active:scale-95',
isSelected
? `${type.borderColor} ${type.bgColor}`
: 'border-gray-200 hover:border-gray-300'
}`}
? `${type.borderColor} ${type.bgColor} shadow-md`
: 'border-gray-200 hover:border-gray-300 bg-white'
)}
>
<div className="flex items-center space-x-3 mb-2">
<Icon className={`h-6 w-6 ${isSelected ? type.color : 'text-gray-400'}`} />
<h3 className={`font-medium ${isSelected ? type.color : 'text-gray-700'}`}>
<div className="flex items-center space-x-3 mb-3">
<div className={cn(
'w-10 h-10 rounded-full flex items-center justify-center',
isSelected ? type.bgColor : 'bg-gray-100'
)}>
<Icon className={cn(
'h-5 w-5',
isSelected ? type.color : 'text-gray-400'
)} />
</div>
<h3 className={cn(
'font-semibold font-chinese text-lg',
isSelected ? type.color : 'text-gray-700'
)}>
{type.title}
</h3>
</div>
<p className="text-sm text-gray-600">{type.description}</p>
<p className="text-sm text-gray-600 font-chinese leading-relaxed">{type.description}</p>
</div>
);
})}
</div>
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
{/* 分析表单 */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
<p className="text-gray-600">
<ChineseCard variant="bordered">
<ChineseCardHeader>
<ChineseCardTitle className="text-red-600 font-chinese"></ChineseCardTitle>
<p className="text-gray-600 font-chinese">
{profile ? '已从您的档案中自动填充,您可以修改' : '请填写以下信息进行分析'}
</p>
</CardHeader>
<CardContent>
</ChineseCardHeader>
<ChineseCardContent>
{analysisType === 'yijing' ? (
// 易经占卜表单
<div className="mb-6">
<Input
label="占卜问题 *"
<ChineseInput
label="占卜问题"
value={formData.question}
onChange={(e) => setFormData(prev => ({ ...prev, question: e.target.value }))}
placeholder="请输入您希望占卜的具体问题,如:我的事业发展如何?"
required
variant="filled"
helperText="💡 提示:问题越具体,占卜结果越准确。可以询问事业、感情、财运、健康等方面的问题。"
/>
<p className="text-sm text-gray-500 mt-2">
💡
</p>
</div>
) : (
// 八字和紫微表单
<>
<div className="grid md:grid-cols-2 gap-4 mb-6">
<div className="grid md:grid-cols-2 gap-4 md:gap-6 mb-6">
<div className="relative">
<Input
label="姓名 *"
<ChineseInput
label="姓名"
value={formData.name}
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
required
placeholder="请输入真实姓名"
variant="filled"
className="pr-10"
/>
<User className="absolute right-3 top-8 h-4 w-4 text-gray-400 pointer-events-none" />
<User className="absolute right-3 top-9 h-4 w-4 text-gray-400 pointer-events-none" />
</div>
<Select
label="性别 *"
<ChineseSelect
label="性别"
value={formData.gender}
onChange={(e) => setFormData(prev => ({ ...prev, gender: e.target.value as 'male' | 'female' }))}
options={[
@@ -281,14 +302,15 @@ const AnalysisPage: React.FC = () => {
{ value: 'female', label: '女性' }
]}
required
variant="filled"
/>
</div>
<div className="grid md:grid-cols-2 gap-4 mb-6">
<div className="grid md:grid-cols-2 gap-4 md:gap-6 mb-6">
<div className="relative">
<Input
<ChineseInput
type="date"
label="出生日期 *"
label="出生日期"
value={formData.birth_date}
onChange={(e) => {
const value = e.target.value;
@@ -301,40 +323,47 @@ const AnalysisPage: React.FC = () => {
min="1900-01-01"
max="2100-12-31"
required
variant="filled"
className="pr-10"
/>
<Calendar className="absolute right-3 top-8 h-4 w-4 text-gray-400 pointer-events-none" />
<Calendar className="absolute right-3 top-9 h-4 w-4 text-gray-400 pointer-events-none" />
</div>
<Input
<ChineseInput
type="time"
label="出生时间"
value={formData.birth_time}
onChange={(e) => setFormData(prev => ({ ...prev, birth_time: e.target.value }))}
placeholder="选填,但强烈建议填写"
helperText="选填,但强烈建议填写以提高准确性"
variant="filled"
/>
</div>
{analysisType !== 'ziwei' && (
<div className="mb-6">
<div className="relative">
<Input
<ChineseInput
label="出生地点"
value={formData.birth_place}
onChange={(e) => setFormData(prev => ({ ...prev, birth_place: e.target.value }))}
placeholder="如:北京市朝阳区(选填)"
variant="filled"
className="pr-10"
helperText="选填,用于更精确的地理位置分析"
/>
<MapPin className="absolute right-3 top-8 h-4 w-4 text-gray-400 pointer-events-none" />
<MapPin className="absolute right-3 top-9 h-4 w-4 text-gray-400 pointer-events-none" />
</div>
</div>
)}
</>
)}
<Button
<ChineseButton
onClick={handleAnalysis}
disabled={loading || (analysisType === 'yijing' ? !formData.question : (!formData.name || !formData.birth_date))}
className="w-full"
className="w-full mt-6"
size="lg"
variant="primary"
>
{loading ? (
<>
@@ -347,9 +376,9 @@ const AnalysisPage: React.FC = () => {
{analysisTypes.find(t => t.type === analysisType)?.title}
</>
)}
</Button>
</CardContent>
</Card>
</ChineseButton>
</ChineseCardContent>
</ChineseCard>
{/* 分析结果 */}
{analysisResult && (

View File

@@ -1,12 +1,15 @@
import React, { useState, useEffect } from 'react';
import { useAuth } from '../contexts/AuthContext';
import { localApi } from '../lib/localApi';
import { Button } from '../components/ui/Button';
import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/Card';
import { ChineseButton } from '../components/ui/ChineseButton';
import { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle } from '../components/ui/ChineseCard';
import { ChineseEmpty } from '../components/ui/ChineseEmpty';
import { ChineseLoading } from '../components/ui/ChineseLoading';
import AnalysisResultDisplay from '../components/AnalysisResultDisplay';
import { toast } from 'sonner';
import { History, Calendar, User, Sparkles, Star, Compass, Eye, Trash2 } from 'lucide-react';
import { NumerologyReading } from '../types';
import { cn } from '../lib/utils';
const HistoryPage: React.FC = () => {
const { user } = useAuth();
@@ -132,9 +135,9 @@ const HistoryPage: React.FC = () => {
const getAnalysisTypeColor = (type: string) => {
switch (type) {
case 'bazi': return 'text-purple-600 bg-purple-50';
case 'ziwei': return 'text-blue-600 bg-blue-50';
case 'yijing': return 'text-green-600 bg-green-50';
case 'bazi': return 'text-red-600 bg-red-50';
case 'ziwei': return 'text-yellow-600 bg-yellow-50';
case 'yijing': return 'text-orange-600 bg-orange-50';
default: return 'text-gray-600 bg-gray-50';
}
};
@@ -152,15 +155,15 @@ const HistoryPage: React.FC = () => {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<Button
<ChineseButton
variant="outline"
onClick={() => setViewingResult(false)}
>
</Button>
</ChineseButton>
<div className="text-right">
<h2 className="text-xl font-semibold">{selectedReading.name} {getAnalysisTypeName(selectedReading.reading_type)}</h2>
<p className="text-gray-600">{new Date(selectedReading.created_at).toLocaleString('zh-CN')}</p>
<h2 className="text-xl font-semibold font-chinese text-red-600">{selectedReading.name} {getAnalysisTypeName(selectedReading.reading_type)}</h2>
<p className="text-gray-600 font-chinese">{new Date(selectedReading.created_at).toLocaleString('zh-CN')}</p>
</div>
</div>
@@ -185,97 +188,104 @@ const HistoryPage: React.FC = () => {
}
return (
<div className="space-y-6">
<Card>
<CardHeader>
<div className="max-w-7xl mx-auto px-4 py-6 space-y-6">
<div className="text-center">
<h1 className="text-2xl md:text-3xl font-bold text-red-600 font-chinese mb-2"></h1>
<p className="text-gray-600 font-chinese"></p>
</div>
<ChineseCard variant="elevated">
<ChineseCardHeader>
<div className="flex items-center space-x-3">
<div className="w-12 h-12 bg-purple-100 rounded-full flex items-center justify-center">
<History className="h-6 w-6 text-purple-600" />
<div className="w-12 h-12 bg-red-100 rounded-full flex items-center justify-center">
<History className="h-6 w-6 text-red-600" />
</div>
<div>
<CardTitle></CardTitle>
<p className="text-gray-600"></p>
<ChineseCardTitle className="text-red-600 font-chinese"></ChineseCardTitle>
<p className="text-gray-600 font-chinese"></p>
</div>
</div>
</CardHeader>
</Card>
{loading ? (
<div className="flex items-center justify-center py-16">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-purple-600"></div>
</div>
) : readings.length === 0 ? (
<Card>
<CardContent className="text-center py-16">
<History className="h-16 w-16 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2"></h3>
<p className="text-gray-600 mb-6"></p>
<Button onClick={() => window.location.href = '/analysis'}>
<Sparkles className="mr-2 h-4 w-4" />
</Button>
</CardContent>
</Card>
) : (
<div className="grid gap-4">
{readings.map((reading) => {
const Icon = getAnalysisTypeIcon(reading.reading_type);
const colorClass = getAnalysisTypeColor(reading.reading_type);
return (
<Card key={reading.id} className="hover:shadow-lg transition-shadow">
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className={`w-10 h-10 rounded-full flex items-center justify-center ${colorClass}`}>
<Icon className="h-5 w-5" />
</div>
<div>
<h3 className="font-medium text-gray-900">
{reading.name || '未知姓名'} - {getAnalysisTypeName(reading.reading_type)}
</h3>
<div className="flex items-center space-x-4 text-sm text-gray-600 mt-1">
<div className="flex items-center space-x-1">
<Calendar className="h-3 w-3" />
<span>{new Date(reading.created_at).toLocaleString('zh-CN')}</span>
</ChineseCardHeader>
<ChineseCardContent>
{loading ? (
<ChineseLoading
size="lg"
variant="chinese"
text="正在加载历史记录..."
className="py-16"
/>
) : readings.length === 0 ? (
<ChineseEmpty
type="data"
title="暂无分析记录"
description="您还没有进行过任何命理分析"
action={{
label: '立即开始分析',
onClick: () => window.location.href = '/analysis'
}}
/>
) : (
<div className="grid gap-4">
{readings.map((reading) => {
const Icon = getAnalysisTypeIcon(reading.reading_type);
const colorClass = getAnalysisTypeColor(reading.reading_type);
return (
<ChineseCard key={reading.id} variant="bordered" className="hover:shadow-lg transition-all duration-200">
<ChineseCardContent className="p-4 md:p-6">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div className="flex items-center space-x-4">
<div className={cn('w-10 h-10 rounded-full flex items-center justify-center', colorClass)}>
<Icon className="h-5 w-5" />
</div>
<div className="flex items-center space-x-1">
<User className="h-3 w-3" />
<span>
{reading.reading_type === 'yijing'
? `问题:${getInputDataValue(reading.input_data, 'question', '综合运势').substring(0, 20)}${getInputDataValue(reading.input_data, 'question', '').length > 20 ? '...' : ''}`
: reading.birth_date}
</span>
<div className="flex-1">
<h3 className="font-semibold text-gray-900 font-chinese">
{reading.name || '未知姓名'} - {getAnalysisTypeName(reading.reading_type)}
</h3>
<div className="flex flex-col sm:flex-row sm:items-center sm:space-x-4 text-sm text-gray-600 mt-1 space-y-1 sm:space-y-0">
<div className="flex items-center space-x-1">
<Calendar className="h-3 w-3" />
<span className="font-chinese">{new Date(reading.created_at).toLocaleString('zh-CN')}</span>
</div>
<div className="flex items-center space-x-1">
<User className="h-3 w-3" />
<span className="font-chinese">
{reading.reading_type === 'yijing'
? `问题:${getInputDataValue(reading.input_data, 'question', '综合运势').substring(0, 20)}${getInputDataValue(reading.input_data, 'question', '').length > 20 ? '...' : ''}`
: reading.birth_date}
</span>
</div>
</div>
</div>
</div>
<div className="flex items-center space-x-2 self-end sm:self-center">
<ChineseButton
variant="outline"
size="md"
onClick={() => handleViewReading(reading)}
>
<Eye className="mr-1 h-4 w-4" />
</ChineseButton>
<ChineseButton
variant="ghost"
size="md"
onClick={() => handleDeleteReading(reading.id)}
className="text-red-600 hover:text-red-700 hover:bg-red-50"
>
<Trash2 className="h-4 w-4" />
</ChineseButton>
</div>
</div>
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => handleViewReading(reading)}
>
<Eye className="mr-1 h-3 w-3" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => handleDeleteReading(reading.id)}
className="text-red-600 hover:text-red-700 hover:bg-red-50"
>
<Trash2 className="h-3 w-3" />
</Button>
</div>
</div>
</CardContent>
</Card>
);
})}
</div>
)}
</ChineseCardContent>
</ChineseCard>
);
})}
</div>
)}
</ChineseCardContent>
</ChineseCard>
</div>
);
};

View File

@@ -1,8 +1,8 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Sparkles, Star, Compass, Heart, BarChart3, BookOpen } from 'lucide-react';
import { Button } from '../components/ui/Button';
import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/Card';
import { ChineseButton } from '../components/ui/ChineseButton';
import { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle } from '../components/ui/ChineseCard';
import { useAuth } from '../contexts/AuthContext';
const HomePage: React.FC = () => {
@@ -57,58 +57,58 @@ const HomePage: React.FC = () => {
</div>
{/* Hero Section */}
<div className="text-center space-y-8 relative">
<div className="text-center space-y-6 md:space-y-8 relative">
<div className="relative">
{/* 传统中式背景装饰 */}
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-80 h-80 chinese-red-glow rounded-full opacity-30 blur-3xl"></div>
<div className="w-60 h-60 md:w-80 md:h-80 bg-gradient-to-r from-red-500/30 to-red-600/30 rounded-full blur-3xl"></div>
</div>
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-96 h-96 chinese-golden-glow rounded-full opacity-20 blur-3xl"></div>
<div className="w-80 h-80 md:w-96 md:h-96 bg-gradient-to-r from-yellow-400/20 to-yellow-500/20 rounded-full blur-3xl"></div>
</div>
<div className="relative z-10">
{/* 太极符号装饰 */}
<div className="w-14 h-14 mx-auto mb-6 bg-gradient-to-br from-yellow-400 to-amber-600 rounded-full flex items-center justify-center shadow-lg border-2 border-red-600">
<div className="w-12 h-12 md:w-14 md:h-14 mx-auto mb-4 md:mb-6 bg-gradient-to-br from-yellow-400 to-yellow-600 rounded-full flex items-center justify-center shadow-lg border-2 border-red-600">
<img
src="/traditional-chinese-bagua-eight-trigrams-black-gold.jpg"
alt="太极八卦"
className="w-10 h-10 rounded-full object-cover"
className="w-8 h-8 md:w-10 md:h-10 rounded-full object-cover"
/>
</div>
<h1 className="text-5xl md:text-6xl font-bold text-red-800 mb-6 chinese-text-shadow font-serif">
<h1 className="text-display-xl font-bold text-red-600 mb-4 md:mb-6 font-chinese">
<span className="block text-3xl md:text-4xl text-yellow-600 mt-2 chinese-text-shadow">
AI智能命理分析
<span className="block text-display-md text-yellow-600 mt-2">
</span>
</h1>
<p className="text-xl text-red-700 max-w-3xl mx-auto leading-relaxed font-medium">
<p className="text-body-xl text-gray-700 max-w-2xl lg:max-w-3xl mx-auto leading-relaxed font-chinese px-4">
AI技术
</p>
</div>
</div>
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center relative z-10">
<div className="flex flex-col sm:flex-row gap-3 md:gap-4 justify-center items-center relative z-10 px-4">
{user ? (
<Link to="/analysis">
<Button size="lg" className="w-full sm:w-auto chinese-red-glow text-white hover:shadow-xl transition-all duration-300 border-2 border-yellow-400">
<Link to="/analysis" className="w-full sm:w-auto">
<ChineseButton size="lg" className="w-full">
<Sparkles className="mr-2 h-5 w-5" />
</Button>
</ChineseButton>
</Link>
) : (
<>
<Link to="/register">
<Button size="lg" className="w-full sm:w-auto chinese-golden-glow text-red-800 hover:shadow-xl transition-all duration-300 border-2 border-red-600">
<Link to="/register" className="w-full sm:w-auto">
<ChineseButton variant="secondary" size="lg" className="w-full">
<Heart className="mr-2 h-5 w-5" />
</Button>
</ChineseButton>
</Link>
<Link to="/login">
<Button variant="outline" size="lg" className="w-full sm:w-auto border-2 border-yellow-500 text-red-700 hover:bg-yellow-50 hover:shadow-lg transition-all duration-300">
<Link to="/login" className="w-full sm:w-auto">
<ChineseButton variant="outline" size="lg" className="w-full">
</Button>
</ChineseButton>
</Link>
</>
)}
@@ -116,16 +116,16 @@ const HomePage: React.FC = () => {
</div>
{/* Features Section */}
<div className="grid md:grid-cols-3 gap-6 relative justify-center max-w-6xl mx-auto">
{/* 装饰元素 - 调整为更适合3列布局的位置 */}
<div className="absolute -left-12 top-1/4 w-20 h-20 opacity-20 pointer-events-none hidden md:block">
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-6 relative max-w-6xl mx-auto px-4">
{/* 装饰元素 - 仅在大屏幕显示 */}
<div className="absolute -left-12 top-1/4 w-16 h-16 opacity-15 pointer-events-none hidden xl:block">
<img
src="/chinese_traditional_red_gold_auspicious_cloud_pattern.jpg"
alt=""
className="w-full h-full object-cover rounded-lg"
/>
</div>
<div className="absolute -right-12 bottom-1/4 w-20 h-20 opacity-20 pointer-events-none hidden md:block">
<div className="absolute -right-12 bottom-1/4 w-16 h-16 opacity-15 pointer-events-none hidden xl:block">
<img
src="/chinese_traditional_red_gold_auspicious_cloud_pattern.jpg"
alt=""
@@ -136,48 +136,48 @@ const HomePage: React.FC = () => {
{features.map((feature, index) => {
const Icon = feature.icon;
return (
<Card key={index} className="text-center hover:shadow-2xl transition-all duration-300 chinese-card-decoration dragon-corner transform hover:scale-105">
<CardHeader>
<div className={`w-12 h-12 ${feature.iconBg} rounded-full flex items-center justify-center mx-auto mb-4 shadow-lg border-2 border-red-600`}>
<Icon className={`h-6 w-6 text-red-800`} />
<ChineseCard key={index} variant="elevated" className="text-center sm:col-span-1 lg:col-span-1 last:sm:col-span-2 last:lg:col-span-1">
<ChineseCardHeader>
<div className="w-12 h-12 md:w-14 md:h-14 bg-gradient-to-br from-yellow-400 to-yellow-600 rounded-full flex items-center justify-center mx-auto mb-3 md:mb-4 shadow-lg border-2 border-red-600">
<Icon className="h-6 w-6 md:h-7 md:w-7 text-red-800" />
</div>
<CardTitle className={`${feature.color} text-2xl font-bold font-serif chinese-text-shadow`}>{feature.title}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-red-700 leading-relaxed font-medium mb-4">{feature.description}</p>
<ChineseCardTitle className="text-red-600 text-heading-md font-bold font-chinese">{feature.title}</ChineseCardTitle>
</ChineseCardHeader>
<ChineseCardContent>
<p className="text-gray-700 leading-relaxed font-chinese mb-4 text-body-md">{feature.description}</p>
{user && (
<Link to={feature.link}>
<Button className="w-full chinese-golden-glow text-red-800 hover:shadow-lg transition-all duration-300 border border-red-600">
<ChineseButton variant="secondary" className="w-full">
</Button>
</ChineseButton>
</Link>
)}
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
);
})}
</div>
{/* CTA Section */}
<Card className="chinese-traditional-bg text-white text-center dragon-corner relative overflow-hidden">
<CardContent className="py-12 relative z-10">
<div className="w-16 h-16 mx-auto mb-6 bg-gradient-to-br from-yellow-400 to-amber-600 rounded-full flex items-center justify-center shadow-2xl border-3 border-yellow-300">
<Sparkles className="w-8 h-8 text-red-800" />
<ChineseCard variant="golden" className="text-center relative overflow-hidden mx-4">
<ChineseCardContent className="py-8 md:py-12 relative z-10">
<div className="w-14 h-14 md:w-16 md:h-16 mx-auto mb-4 md:mb-6 bg-gradient-to-br from-red-600 to-red-700 rounded-full flex items-center justify-center shadow-2xl border-2 border-red-800">
<Sparkles className="w-7 h-7 md:w-8 md:h-8 text-yellow-400" />
</div>
<h2 className="text-4xl font-bold mb-4 chinese-text-shadow font-serif"></h2>
<p className="text-red-100 mb-8 text-lg font-medium leading-relaxed">
<h2 className="text-display-md font-bold mb-3 md:mb-4 font-chinese text-red-800"></h2>
<p className="text-red-700 mb-6 md:mb-8 text-body-lg font-chinese leading-relaxed px-4">
AI帮您解读人生密码
</p>
{!user && (
<Link to="/register">
<Button variant="outline" size="lg" className="chinese-golden-glow text-red-800 border-3 border-yellow-300 hover:shadow-2xl transition-all duration-300 transform hover:scale-105">
<ChineseButton variant="primary" size="lg" className="shadow-xl">
</Button>
</ChineseButton>
</Link>
)}
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
</div>
);
};

View File

@@ -1,9 +1,9 @@
import React, { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
import { Button } from '../components/ui/Button';
import { Input } from '../components/ui/Input';
import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/Card';
import { ChineseButton } from '../components/ui/ChineseButton';
import { ChineseInput } from '../components/ui/ChineseInput';
import { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle } from '../components/ui/ChineseCard';
import { toast } from 'sonner';
import { Mail, Lock, LogIn } from 'lucide-react';
@@ -34,62 +34,71 @@ const LoginPage: React.FC = () => {
};
return (
<div className="min-h-[80vh] flex items-center justify-center">
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<div className="w-12 h-12 bg-purple-100 rounded-full flex items-center justify-center mx-auto mb-4">
<LogIn className="h-6 w-6 text-purple-600" />
<div className="min-h-[80vh] flex items-center justify-center px-4 py-8">
{/* 背景装饰 */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute top-1/4 left-1/4 w-32 h-32 bg-gradient-to-r from-red-500/10 to-yellow-500/10 rounded-full blur-3xl"></div>
<div className="absolute bottom-1/4 right-1/4 w-40 h-40 bg-gradient-to-r from-yellow-500/10 to-red-500/10 rounded-full blur-3xl"></div>
</div>
<ChineseCard variant="elevated" className="w-full max-w-md relative z-10">
<ChineseCardHeader className="text-center">
<div className="w-14 h-14 bg-gradient-to-br from-red-600 to-red-700 rounded-full flex items-center justify-center mx-auto mb-4 shadow-lg border-2 border-yellow-500">
<LogIn className="h-7 w-7 text-yellow-400" />
</div>
<CardTitle className="text-2xl"></CardTitle>
<p className="text-gray-600"></p>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<Input
type="email"
label="邮箱地址"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
placeholder="请输入您的邮箱"
className="pl-10"
/>
<ChineseCardTitle className="text-2xl md:text-3xl text-red-600 font-chinese"></ChineseCardTitle>
<p className="text-gray-600 font-chinese mt-2"></p>
</ChineseCardHeader>
<ChineseCardContent>
<form onSubmit={handleSubmit} className="space-y-5">
<div className="relative">
<Mail className="absolute left-3 top-8 h-4 w-4 text-gray-400" />
<ChineseInput
type="email"
label="邮箱地址"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
placeholder="请输入您的邮箱"
variant="bordered"
className="pl-10"
/>
<Mail className="absolute left-3 top-9 h-4 w-4 text-gray-400 pointer-events-none" />
</div>
<Input
type="password"
label="密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
placeholder="请输入您的密码"
className="pl-10"
/>
<div className="relative">
<Lock className="absolute left-3 top-8 h-4 w-4 text-gray-400" />
<ChineseInput
type="password"
label="密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
placeholder="请输入您的密码"
variant="bordered"
className="pl-10"
/>
<Lock className="absolute left-3 top-9 h-4 w-4 text-gray-400 pointer-events-none" />
</div>
<Button
<ChineseButton
type="submit"
className="w-full"
size="lg"
className="w-full mt-6"
disabled={loading}
>
{loading ? '登录中...' : '登录'}
</Button>
</ChineseButton>
</form>
<div className="mt-6 text-center">
<p className="text-gray-600">
<p className="text-gray-600 font-chinese">
<Link to="/register" className="text-purple-600 hover:text-purple-700 font-medium ml-1">
<Link to="/register" className="text-red-600 hover:text-red-700 font-medium ml-1 transition-colors duration-200">
</Link>
</p>
</div>
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
</div>
);
};

View File

@@ -1,10 +1,10 @@
import React, { useState, useEffect } from 'react';
import { useAuth } from '../contexts/AuthContext';
import { localApi } from '../lib/localApi';
import { Button } from '../components/ui/Button';
import { Input } from '../components/ui/Input';
import { Select } from '../components/ui/Select';
import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/Card';
import { ChineseButton } from '../components/ui/ChineseButton';
import { ChineseInput } from '../components/ui/ChineseInput';
import { ChineseSelect } from '../components/ui/ChineseSelect';
import { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle } from '../components/ui/ChineseCard';
import { toast } from 'sonner';
import { User, Calendar, MapPin, Save } from 'lucide-react';
import { UserProfile } from '../types';
@@ -88,62 +88,72 @@ const ProfilePage: React.FC = () => {
};
return (
<div className="max-w-2xl mx-auto">
<Card>
<CardHeader>
<div className="max-w-4xl mx-auto px-4 py-6">
<div className="text-center mb-6">
<h1 className="text-2xl md:text-3xl font-bold text-red-600 font-chinese mb-2"></h1>
<p className="text-gray-600 font-chinese"></p>
</div>
<ChineseCard variant="elevated">
<ChineseCardHeader>
<div className="flex items-center space-x-3">
<div className="w-12 h-12 bg-purple-100 rounded-full flex items-center justify-center">
<User className="h-6 w-6 text-purple-600" />
<div className="w-12 h-12 bg-red-100 rounded-full flex items-center justify-center">
<User className="h-6 w-6 text-red-600" />
</div>
<div>
<CardTitle></CardTitle>
<p className="text-gray-600"></p>
<ChineseCardTitle className="text-red-600 font-chinese"></ChineseCardTitle>
<p className="text-gray-600 font-chinese"></p>
</div>
</div>
</CardHeader>
<CardContent>
</ChineseCardHeader>
<ChineseCardContent>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid md:grid-cols-2 gap-4">
<Input
label="姓名 *"
<div className="grid md:grid-cols-2 gap-4 md:gap-6">
<ChineseInput
label="姓名"
value={formData.full_name}
onChange={(e) => handleInputChange('full_name', e.target.value)}
required
placeholder="请输入您的真实姓名"
variant="filled"
/>
<Input
<ChineseInput
label="用户名"
value={formData.username}
onChange={(e) => handleInputChange('username', e.target.value)}
placeholder="请输入用户名(可选)"
variant="filled"
/>
</div>
<div className="grid md:grid-cols-2 gap-4">
<div className="grid md:grid-cols-2 gap-4 md:gap-6">
<div className="relative">
<Input
<ChineseInput
type="date"
label="出生日期 *"
label="出生日期"
value={formData.birth_date}
onChange={(e) => handleInputChange('birth_date', e.target.value)}
required
variant="filled"
className="pr-10"
/>
<Calendar className="absolute right-3 top-8 h-4 w-4 text-gray-400 pointer-events-none" />
<Calendar className="absolute right-3 top-9 h-4 w-4 text-gray-400 pointer-events-none" />
</div>
<Input
<ChineseInput
type="time"
label="出生时间"
value={formData.birth_time}
onChange={(e) => handleInputChange('birth_time', e.target.value)}
placeholder="选填,但强烈建议填写"
helperText="选填,但强烈建议填写以提高分析准确性"
variant="filled"
/>
</div>
<div className="grid md:grid-cols-2 gap-4">
<Select
label="性别 *"
<div className="grid md:grid-cols-2 gap-4 md:gap-6">
<ChineseSelect
label="性别"
value={formData.gender}
onChange={(e) => handleInputChange('gender', e.target.value)}
options={[
@@ -151,47 +161,52 @@ const ProfilePage: React.FC = () => {
{ value: 'female', label: '女性' }
]}
required
variant="filled"
/>
<div className="relative">
<Input
<ChineseInput
label="出生地点"
value={formData.birth_location}
onChange={(e) => handleInputChange('birth_location', e.target.value)}
placeholder="如:北京市朝阳区"
variant="filled"
className="pr-10"
helperText="选填,用于更精确的地理位置分析"
/>
<MapPin className="absolute right-3 top-8 h-4 w-4 text-gray-400 pointer-events-none" />
<MapPin className="absolute right-3 top-9 h-4 w-4 text-gray-400 pointer-events-none" />
</div>
</div>
<div className="bg-blue-50 p-4 rounded-lg">
<h4 className="font-medium text-blue-800 mb-2"></h4>
<ul className="text-sm text-blue-700 space-y-1">
<div className="bg-red-50 p-4 rounded-lg border border-red-200">
<h4 className="font-semibold text-red-800 mb-2 font-chinese"></h4>
<ul className="text-sm text-red-700 space-y-1 font-chinese">
<li> </li>
<li> </li>
<li> </li>
</ul>
</div>
<Button
<ChineseButton
type="submit"
className="w-full"
className="w-full mt-6"
size="lg"
disabled={loading}
>
<Save className="mr-2 h-4 w-4" />
{loading ? '保存中...' : '保存档案'}
</Button>
</ChineseButton>
</form>
{profile && (
<div className="mt-6 pt-6 border-t border-gray-200">
<p className="text-sm text-gray-500">
<p className="text-sm text-gray-500 font-chinese">
{new Date(profile.updated_at).toLocaleString('zh-CN')}
</p>
</div>
)}
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
</div>
);
};

View File

@@ -1,9 +1,9 @@
import React, { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
import { Button } from '../components/ui/Button';
import { Input } from '../components/ui/Input';
import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/Card';
import { ChineseButton } from '../components/ui/ChineseButton';
import { ChineseInput } from '../components/ui/ChineseInput';
import { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle } from '../components/ui/ChineseCard';
import { toast } from 'sonner';
import { Mail, Lock, UserPlus } from 'lucide-react';
@@ -46,75 +46,88 @@ const RegisterPage: React.FC = () => {
};
return (
<div className="min-h-[80vh] flex items-center justify-center">
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<div className="w-12 h-12 bg-purple-100 rounded-full flex items-center justify-center mx-auto mb-4">
<UserPlus className="h-6 w-6 text-purple-600" />
<div className="min-h-[80vh] flex items-center justify-center px-4 py-8">
{/* 背景装饰 */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute top-1/3 left-1/3 w-36 h-36 bg-gradient-to-r from-yellow-500/10 to-red-500/10 rounded-full blur-3xl"></div>
<div className="absolute bottom-1/3 right-1/3 w-44 h-44 bg-gradient-to-r from-red-500/10 to-yellow-500/10 rounded-full blur-3xl"></div>
</div>
<ChineseCard variant="elevated" className="w-full max-w-md relative z-10">
<ChineseCardHeader className="text-center">
<div className="w-14 h-14 bg-gradient-to-br from-yellow-500 to-yellow-600 rounded-full flex items-center justify-center mx-auto mb-4 shadow-lg border-2 border-red-600">
<UserPlus className="h-7 w-7 text-red-800" />
</div>
<CardTitle className="text-2xl"></CardTitle>
<p className="text-gray-600"></p>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<ChineseCardTitle className="text-2xl md:text-3xl text-red-600 font-chinese"></ChineseCardTitle>
<p className="text-gray-600 font-chinese mt-2"></p>
</ChineseCardHeader>
<ChineseCardContent>
<form onSubmit={handleSubmit} className="space-y-5">
<div className="relative">
<Input
<ChineseInput
type="email"
label="邮箱地址"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
placeholder="请输入您的邮箱"
variant="bordered"
className="pl-10"
/>
<Mail className="absolute left-3 top-8 h-4 w-4 text-gray-400" />
<Mail className="absolute left-3 top-9 h-4 w-4 text-gray-400 pointer-events-none" />
</div>
<div className="relative">
<Input
<ChineseInput
type="password"
label="密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
placeholder="请输入您的密码不少于6位"
variant="bordered"
className="pl-10"
helperText="密码长度不能少于6位"
/>
<Lock className="absolute left-3 top-8 h-4 w-4 text-gray-400" />
<Lock className="absolute left-3 top-9 h-4 w-4 text-gray-400 pointer-events-none" />
</div>
<div className="relative">
<Input
<ChineseInput
type="password"
label="确认密码"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
placeholder="请再次输入密码"
variant="bordered"
className="pl-10"
error={confirmPassword && password !== confirmPassword ? '两次输入的密码不一致' : undefined}
/>
<Lock className="absolute left-3 top-8 h-4 w-4 text-gray-400" />
<Lock className="absolute left-3 top-9 h-4 w-4 text-gray-400 pointer-events-none" />
</div>
<Button
<ChineseButton
type="submit"
className="w-full"
variant="secondary"
size="lg"
className="w-full mt-6"
disabled={loading}
>
{loading ? '注册中...' : '注册账户'}
</Button>
</ChineseButton>
</form>
<div className="mt-6 text-center">
<p className="text-gray-600">
<p className="text-gray-600 font-chinese">
<Link to="/login" className="text-purple-600 hover:text-purple-700 font-medium ml-1">
<Link to="/login" className="text-red-600 hover:text-red-700 font-medium ml-1 transition-colors duration-200">
</Link>
</p>
</div>
</CardContent>
</Card>
</ChineseCardContent>
</ChineseCard>
</div>
);
};

274
src/styles/typography.css Normal file
View File

@@ -0,0 +1,274 @@
/* 神机阁字体规范系统 */
/* ===== 字体族定义 ===== */
:root {
/* 中文字体栈 - 优先级从高到低 */
--font-chinese: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', 'Noto Sans SC', 'STHeiti', 'WenQuanYi Micro Hei', sans-serif;
/* 中文衬线字体 - 用于特殊标题 */
--font-chinese-serif: 'Noto Serif SC', 'STSong', 'SimSun', '宋体', serif;
/* 英文字体 - 用于数字和英文内容 */
--font-english: 'Inter', 'Helvetica Neue', 'Arial', sans-serif;
}
/* ===== 字体大小规范 ===== */
/* 基于 16px 基准,使用 rem 单位确保可访问性 */
/* 超大标题 - 用于首页主标题 */
.text-display-xl {
font-size: 3.5rem; /* 56px */
line-height: 1.1;
font-weight: 800;
letter-spacing: -0.02em;
}
.text-display-lg {
font-size: 3rem; /* 48px */
line-height: 1.1;
font-weight: 700;
letter-spacing: -0.02em;
}
.text-display-md {
font-size: 2.5rem; /* 40px */
line-height: 1.2;
font-weight: 700;
letter-spacing: -0.01em;
}
/* 标题系列 */
.text-heading-xl {
font-size: 2rem; /* 32px */
line-height: 1.25;
font-weight: 600;
}
.text-heading-lg {
font-size: 1.75rem; /* 28px */
line-height: 1.3;
font-weight: 600;
}
.text-heading-md {
font-size: 1.5rem; /* 24px */
line-height: 1.35;
font-weight: 600;
}
.text-heading-sm {
font-size: 1.25rem; /* 20px */
line-height: 1.4;
font-weight: 600;
}
.text-heading-xs {
font-size: 1.125rem; /* 18px */
line-height: 1.4;
font-weight: 600;
}
/* 正文系列 */
.text-body-xl {
font-size: 1.125rem; /* 18px */
line-height: 1.6;
font-weight: 400;
}
.text-body-lg {
font-size: 1rem; /* 16px */
line-height: 1.6;
font-weight: 400;
}
.text-body-md {
font-size: 0.875rem; /* 14px */
line-height: 1.6;
font-weight: 400;
}
.text-body-sm {
font-size: 0.75rem; /* 12px */
line-height: 1.5;
font-weight: 400;
}
/* 标签和辅助文字 */
.text-label-lg {
font-size: 0.875rem; /* 14px */
line-height: 1.4;
font-weight: 500;
}
.text-label-md {
font-size: 0.75rem; /* 12px */
line-height: 1.4;
font-weight: 500;
}
.text-label-sm {
font-size: 0.6875rem; /* 11px */
line-height: 1.4;
font-weight: 500;
}
/* 按钮文字 */
.text-button-lg {
font-size: 1rem; /* 16px */
line-height: 1.4;
font-weight: 600;
letter-spacing: 0.01em;
}
.text-button-md {
font-size: 0.875rem; /* 14px */
line-height: 1.4;
font-weight: 600;
letter-spacing: 0.01em;
}
.text-button-sm {
font-size: 0.75rem; /* 12px */
line-height: 1.4;
font-weight: 600;
letter-spacing: 0.01em;
}
/* ===== 响应式字体规范 ===== */
/* 移动端优化 - 在小屏幕上适当缩小字体 */
@media (max-width: 768px) {
.text-display-xl {
font-size: 2.5rem; /* 40px */
}
.text-display-lg {
font-size: 2.25rem; /* 36px */
}
.text-display-md {
font-size: 2rem; /* 32px */
}
.text-heading-xl {
font-size: 1.75rem; /* 28px */
}
.text-heading-lg {
font-size: 1.5rem; /* 24px */
}
.text-heading-md {
font-size: 1.25rem; /* 20px */
}
.text-body-xl {
font-size: 1rem; /* 16px */
}
}
/* 超小屏幕进一步优化 */
@media (max-width: 480px) {
.text-display-xl {
font-size: 2rem; /* 32px */
}
.text-display-lg {
font-size: 1.875rem; /* 30px */
}
.text-display-md {
font-size: 1.75rem; /* 28px */
}
}
/* ===== 字体族应用类 ===== */
.font-chinese {
font-family: var(--font-chinese);
}
.font-chinese-serif {
font-family: var(--font-chinese-serif);
}
.font-english {
font-family: var(--font-english);
}
/* ===== 特殊用途字体类 ===== */
/* 数字专用 - 确保数字对齐 */
.font-numeric {
font-family: var(--font-english);
font-variant-numeric: tabular-nums;
}
/* 代码字体 */
.font-mono {
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Consolas', monospace;
}
/* ===== 字重规范 ===== */
.font-thin { font-weight: 100; }
.font-extralight { font-weight: 200; }
.font-light { font-weight: 300; }
.font-normal { font-weight: 400; }
.font-medium { font-weight: 500; }
.font-semibold { font-weight: 600; }
.font-bold { font-weight: 700; }
.font-extrabold { font-weight: 800; }
.font-black { font-weight: 900; }
/* ===== 行高规范 ===== */
.leading-none { line-height: 1; }
.leading-tight { line-height: 1.25; }
.leading-snug { line-height: 1.375; }
.leading-normal { line-height: 1.5; }
.leading-relaxed { line-height: 1.625; }
.leading-loose { line-height: 2; }
/* ===== 字间距规范 ===== */
.tracking-tighter { letter-spacing: -0.05em; }
.tracking-tight { letter-spacing: -0.025em; }
.tracking-normal { letter-spacing: 0em; }
.tracking-wide { letter-spacing: 0.025em; }
.tracking-wider { letter-spacing: 0.05em; }
.tracking-widest { letter-spacing: 0.1em; }
/* ===== 使用指南注释 ===== */
/*
字体使用指南:
1. 显示级标题 (Display):
- text-display-xl: 首页主标题
- text-display-lg: 重要页面标题
- text-display-md: 次要页面标题
2. 标题 (Heading):
- text-heading-xl: H1 标题
- text-heading-lg: H2 标题
- text-heading-md: H3 标题
- text-heading-sm: H4 标题
- text-heading-xs: H5 标题
3. 正文 (Body):
- text-body-xl: 重要描述文字
- text-body-lg: 标准正文 (默认)
- text-body-md: 次要正文
- text-body-sm: 辅助信息
4. 标签 (Label):
- text-label-lg: 表单标签
- text-label-md: 小标签
- text-label-sm: 微小标签
5. 按钮 (Button):
- text-button-lg: 大按钮
- text-button-md: 标准按钮
- text-button-sm: 小按钮
字体族使用:
- font-chinese: 中文内容 (默认)
- font-chinese-serif: 特殊标题
- font-english: 英文和数字
- font-numeric: 数字对齐
- font-mono: 代码
*/