feat: 初始化暗黑色时尚 UI 设计系统

- 安装和配置 TailwindCSS + shadcn/ui 框架
- 实现完整的 CSS 变量系统(颜色、排版、间距、阴影)
- 创建暗黑色主题,紫色系强调色和发光效果
- 实现按钮、卡片、输入框、徽章等组件样式
- 添加流畅的动画和过渡效果
- 创建示例展示页面
- 配置路径别名和 TypeScript 设置
- 添加设计文档 DESIGN.md
This commit is contained in:
CoderLambert
2026-03-03 22:15:18 +08:00
parent d68d489efa
commit 6ef7da65ef
13 changed files with 2307 additions and 136 deletions
+234
View File
@@ -0,0 +1,234 @@
# DarkUI - 暗黑色时尚设计系统
基于 TailwindCSS 和 shadcn/ui 构建的现代化暗黑色 UI 设计系统。
## 🎨 设计理念
- **深邃优雅** - 多层次暗黑色调营造沉浸式体验
- **微光点缀** - 精致的发光效果和渐变
- **现代极简** - 干净利落,注重留白和层次
- **科技感** - 融入现代科技元素
## 📦 安装
```bash
# 安装依赖
npm install
# 启动开发服务器
npm run dev
```
## 🎯 颜色系统
### 基础颜色
| Token | HSL 值 | 预览 | 用途 |
|-------|--------|------|------|
| `--background` | `240 10% 4%` | 🌑 | 主背景色 |
| `--foreground` | `0 0% 98%` | ⬜ | 前景文字 |
| `--surface-1` | `240 8% 7%` | 🌑 | 一级卡片 |
| `--surface-2` | `240 6% 11%` | 🌑 | 二级卡片 |
| `--surface-3` | `240 5% 16%` | 🌑 | 三级元素 |
### 主色(紫色系)
| Token | HSL 值 | 用途 |
|-------|--------|------|
| `--primary-500` | `255 87% 70%` | 主强调色 |
| `--primary-600` | `258 87% 61%` | 悬停状态 |
| `--primary-700` | `262 71% 53%` | 按下状态 |
### 功能色
| Token | HSL 值 | 用途 |
|-------|--------|------|
| `--success` | `152 76% 47%` | 成功状态 🟢 |
| `--warning` | `38 92% 56%` | 警告提示 🟡 |
| `--error` | `350 86% 61%` | 错误信息 🔴 |
| `--info` | `199 89% 54%` | 信息提示 🔵 |
## 🔤 排版系统
```css
/* 字体家族 */
--font-sans: 'Inter', 'SF Pro Display', sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
/* 字体大小 */
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.5rem; /* 24px */
--text-3xl: 1.875rem; /* 30px */
--text-4xl: 2.25rem; /* 36px */
```
## 🧩 组件
### 按钮
```jsx
// 主要按钮
<button className="btn-primary">
主要按钮
</button>
// 次要按钮
<button className="btn-secondary">
次要按钮
</button>
// 幽灵按钮
<button className="btn-ghost">
幽灵按钮
</button>
// 发光按钮
<button className="btn-glow">
发光按钮
</button>
```
### 卡片
```jsx
<div className="card">
<h3>卡片标题</h3>
<p>卡片内容</p>
</div>
// 带发光效果
<div className="card card-glow">
<h3>发光卡片</h3>
</div>
```
### 输入框
```jsx
<input
type="email"
className="input"
placeholder="name@example.com"
/>
```
### 徽章
```jsx
<span className="badge-primary">主要</span>
<span className="badge-success">成功</span>
<span className="badge-warning">警告</span>
<span className="badge-error">错误</span>
```
## 🎭 特效
### 发光效果
```css
.glow-primary {
box-shadow: 0 0 20px -5px hsl(var(--glow-primary) / 0.5);
}
```
### 渐变背景
```css
.gradient-bg {
background:
radial-gradient(circle at 50% 0%,
hsl(var(--primary) / 0.15) 0%,
transparent 50%
),
linear-gradient(180deg,
hsl(var(--background)) 0%,
hsl(var(--surface-1)) 100%
);
}
```
### 玻璃态
```css
.glass {
@apply bg-surface-2/60 backdrop-blur-xl;
border: 1px solid hsl(var(--border) / 0.5);
}
```
## 📐 间距系统
基于 4px 网格:
```css
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-5: 1.25rem; /* 20px */
--space-6: 1.5rem; /* 24px */
--space-8: 2rem; /* 32px */
--space-10: 2.5rem; /* 40px */
--space-12: 3rem; /* 48px */
--space-16: 4rem; /* 64px */
```
## 🎬 动画
### 淡入
```css
.fade-in {
animation: fade-in 0.5s ease-out forwards;
}
```
### 滑入
```css
.slide-in-left {
animation: slide-in-left 0.3s ease-out forwards;
}
```
### 缩放
```css
.scale-in {
animation: scale-in 0.2s ease-out forwards;
}
```
## 📱 响应式断点
| 断点 | 最小宽度 | 设备 |
|------|----------|------|
| `sm` | 640px | 小屏手机 |
| `md` | 768px | 平板 |
| `lg` | 1024px | 笔记本 |
| `xl` | 1280px | 桌面 |
| `2xl` | 1536px | 大屏 |
## ♿ 可访问性
- 所有文本对比度符合 WCAG AA 标准(至少 4.5:1
- 焦点指示器清晰可见
- 最小点击区域 44x44px
- 支持键盘导航
## 🛠 工具函数
```ts
// 合并类名
import { cn } from '@/lib/utils'
cn('btn', 'btn-primary', className)
```
## 📄 许可证
MIT License
+20
View File
@@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/index.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}
+5 -1
View File
@@ -4,7 +4,11 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>myrepo</title>
<title>DarkUI - 暗黑色时尚设计系统</title>
<!-- Google Fonts - Inter -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
</head>
<body>
<div id="root"></div>
+1119 -5
View File
File diff suppressed because it is too large Load Diff
+12 -2
View File
@@ -10,20 +10,30 @@
"preview": "vite preview"
},
"dependencies": {
"@radix-ui/react-slot": "^1.2.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.576.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
"react-dom": "^19.2.0",
"tailwind-merge": "^3.5.0"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@types/node": "^24.10.1",
"@tailwindcss/cli": "^4.2.1",
"@types/node": "^24.11.0",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"autoprefixer": "^10.4.27",
"babel-plugin-react-compiler": "^1.0.0",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"path": "^0.12.7",
"postcss": "^8.5.8",
"tailwindcss": "^4.2.1",
"typescript": "~5.9.3",
"typescript-eslint": "^8.48.0",
"vite": "^7.3.1"
+6
View File
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
+1 -42
View File
@@ -1,42 +1 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}
/* App 特定样式 - 大部分样式已在 index.css 中定义 */
+324 -24
View File
@@ -1,34 +1,334 @@
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import {
Zap,
Layers,
Palette,
Type,
Box,
Sparkles,
ArrowRight,
Check,
Copy,
Moon,
Github,
Twitter,
Command,
BarChart3,
Users,
Activity
} from 'lucide-react'
import './App.css'
function App() {
const [count, setCount] = useState(0)
const [email, setEmail] = useState('')
const features = [
{
icon: Palette,
title: '暗黑色主题',
description: '深邃优雅的暗黑色调,多层次表面设计,营造沉浸式视觉体验'
},
{
icon: Sparkles,
title: '发光效果',
description: '精致的发光阴影和渐变效果,让界面更加生动立体'
},
{
icon: Type,
title: '排版系统',
description: '基于 Inter 字体的完整排版层级,确保出色的可读性'
},
{
icon: Layers,
title: '组件丰富',
description: '按钮、卡片、输入框、徽章等完整组件样式库'
},
{
icon: Box,
title: '响应式设计',
description: '完美适配各种屏幕尺寸,从手机到桌面无缝切换'
},
{
icon: Zap,
title: '高性能',
description: '基于 TailwindCSS 的原子化 CSS,极致加载性能'
}
]
const stats = [
{ label: '活跃用户', value: '10K+', icon: Users },
{ label: '组件数量', value: '50+', icon: Box },
{ label: '下载量', value: '100K+', icon: BarChart3 },
{ label: '满意度', value: '99%', icon: Activity },
]
return (
<>
<div>
<a href="https://vite.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
<div className="min-h-screen gradient-bg">
{/* 导航栏 */}
<nav className="glass sticky top-0 z-50 border-b border-border/50">
<div className="max-w-7xl mx-auto px-6 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-primary-600 flex items-center justify-center glow-primary">
<Sparkles className="w-5 h-5 text-white" />
</div>
<span className="text-xl font-semibold text-gradient-primary">DarkUI</span>
</div>
<div className="flex items-center gap-4">
<a href="#features" className="text-sm text-secondary hover:text-foreground transition-colors"></a>
<a href="#components" className="text-sm text-secondary hover:text-foreground transition-colors"></a>
<a href="#docs" className="text-sm text-secondary hover:text-foreground transition-colors"></a>
<button className="btn-ghost text-sm">
<Moon className="w-4 h-4 mr-2" />
</button>
<button className="btn-primary">
使
<ArrowRight className="w-4 h-4 ml-2" />
</button>
</div>
</div>
</div>
</nav>
{/* Hero 区域 */}
<section className="relative pt-24 pb-32 overflow-hidden">
{/* 背景光效 */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute top-1/4 left-1/2 -translate-x-1/2 w-[600px] h-[600px] bg-primary/20 rounded-full blur-[120px]" />
<div className="absolute bottom-0 left-1/4 w-[400px] h-[400px] bg-primary/10 rounded-full blur-[100px]" />
</div>
<div className="max-w-7xl mx-auto px-6 relative z-10">
<div className="text-center fade-in">
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-primary/10 border border-primary/20 text-primary text-sm mb-8">
<Sparkles className="w-4 h-4" />
</div>
<h1 className="text-5xl md:text-7xl font-bold mb-6">
<span className="text-gradient"></span>
<br />
<span className="text-gradient-primary"> UI </span>
</h1>
<p className="text-xl text-secondary max-w-2xl mx-auto mb-10">
TailwindCSS shadcn/ui
</p>
<div className="flex items-center justify-center gap-4">
<button className="btn-primary px-8 py-3 text-base">
<ArrowRight className="w-5 h-5 ml-2" />
</button>
<button className="btn-secondary px-8 py-3 text-base">
<Command className="w-5 h-5 mr-2" />
npm install darkui
</button>
</div>
</div>
{/* 数据统计 */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-6 mt-24">
{stats.map((stat, index) => (
<div key={index} className="card text-center border-gradient">
<stat.icon className="w-6 h-6 text-primary mx-auto mb-3" />
<div className="text-3xl font-bold text-foreground mb-1">{stat.value}</div>
<div className="text-sm text-secondary">{stat.label}</div>
</div>
))}
</div>
</div>
</section>
{/* 特性展示 */}
<section id="features" className="py-24 border-t border-border/50">
<div className="max-w-7xl mx-auto px-6">
<div className="text-center mb-16">
<h2 className="text-4xl font-bold text-foreground mb-4">
</h2>
<p className="text-lg text-secondary max-w-2xl mx-auto">
</p>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{features.map((feature, index) => (
<div
key={index}
className="card border-gradient hover:border-primary/30 transition-all duration-300 group"
>
<div className="w-12 h-12 rounded-lg bg-surface-2 border border-border flex items-center justify-center mb-4 group-hover:scale-110 transition-transform">
<feature.icon className="w-6 h-6 text-primary" />
</div>
<h3 className="text-lg font-semibold text-foreground mb-2">{feature.title}</h3>
<p className="text-secondary text-sm leading-relaxed">{feature.description}</p>
</div>
))}
</div>
</div>
</section>
{/* 组件展示 */}
<section id="components" className="py-24 border-t border-border/50">
<div className="max-w-7xl mx-auto px-6">
<div className="text-center mb-16">
<h2 className="text-4xl font-bold text-foreground mb-4">
</h2>
<p className="text-lg text-secondary max-w-2xl mx-auto">
</p>
</div>
{/* 按钮展示 */}
<div className="card mb-8">
<h3 className="text-lg font-semibold text-foreground mb-6 flex items-center gap-2">
<Box className="w-5 h-5 text-primary" />
</h3>
<div className="flex flex-wrap gap-4">
<button className="btn-primary">
<ArrowRight className="w-4 h-4 ml-2" />
</button>
<button className="btn-secondary"></button>
<button className="btn-ghost"></button>
<button className="btn-glow"></button>
<button className="btn-primary" disabled>
</button>
</div>
</div>
{/* 输入框展示 */}
<div className="card mb-8">
<h3 className="text-lg font-semibold text-foreground mb-6 flex items-center gap-2">
<Copy className="w-5 h-5 text-primary" />
</h3>
<div className="grid md:grid-cols-2 gap-6">
<div>
<label className="text-sm text-secondary mb-2 block"></label>
<input
type="email"
className="input"
placeholder="name@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div>
<label className="text-sm text-secondary mb-2 block"></label>
<input
type="text"
className="input"
placeholder="输入关键词搜索..."
/>
</div>
</div>
</div>
{/* 徽章展示 */}
<div className="card mb-8">
<h3 className="text-lg font-semibold text-foreground mb-6 flex items-center gap-2">
<Check className="w-5 h-5 text-primary" />
</h3>
<div className="flex flex-wrap gap-3">
<span className="badge-primary"></span>
<span className="badge-success"></span>
<span className="badge-warning"></span>
<span className="badge-error"></span>
</div>
</div>
{/* 交互演示 */}
<div className="card border-gradient">
<h3 className="text-lg font-semibold text-foreground mb-6 flex items-center gap-2">
<Zap className="w-5 h-5 text-primary" />
</h3>
<div className="flex items-center gap-6">
<div className="text-center">
<div className="text-5xl font-bold text-primary mb-2">{count}</div>
<p className="text-sm text-secondary mb-4"></p>
<div className="flex gap-2">
<button
className="btn-secondary px-4"
onClick={() => setCount(Math.max(0, count - 1))}
>
-1
</button>
<button
className="btn-primary"
onClick={() => setCount(count + 1)}
>
+1
</button>
</div>
</div>
<div className="divider w-px h-32" />
<div>
<p className="text-secondary text-sm mb-3"></p>
<p className="text-tertiary text-xs">
</p>
</div>
</div>
</div>
</div>
</section>
{/* CTA 区域 */}
<section className="py-24 border-t border-border/50">
<div className="max-w-4xl mx-auto px-6 text-center">
<div className="card border-gradient p-12">
<h2 className="text-3xl font-bold text-foreground mb-4">
</h2>
<p className="text-secondary mb-8 max-w-xl mx-auto">
使 DarkUI
</p>
<div className="flex items-center justify-center gap-4">
<button className="btn-primary px-8 py-3">
</button>
<button className="btn-secondary px-8 py-3">
<Github className="w-5 h-5" />
</button>
<button className="btn-secondary px-8 py-3">
<Twitter className="w-5 h-5" />
</button>
</div>
</div>
</div>
</section>
{/* 页脚 */}
<footer className="border-t border-border/50 py-12">
<div className="max-w-7xl mx-auto px-6">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-primary-500 to-primary-600 flex items-center justify-center">
<Sparkles className="w-4 h-4 text-white" />
</div>
<span className="text-sm text-secondary">© 2026 DarkUI. All rights reserved.</span>
</div>
<div className="flex items-center gap-6">
<a href="#" className="text-sm text-secondary hover:text-foreground transition-colors"></a>
<a href="#" className="text-sm text-secondary hover:text-foreground transition-colors">使</a>
<a href="#" className="text-sm text-secondary hover:text-foreground transition-colors"></a>
</div>
</div>
</div>
</footer>
</div>
)
}
+428 -61
View File
@@ -1,68 +1,435 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
@tailwind base;
@tailwind components;
@tailwind utilities;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
/* ============================================
暗黑色时尚 UI 设计系统
基于 TailwindCSS + shadcn/ui
============================================ */
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
@layer base {
:root {
color: #213547;
background-color: #ffffff;
/* ===== 基础颜色 - 暗黑色主题 ===== */
--background: 240 10% 4%;
--foreground: 0 0% 98%;
/* 表面层 - 多层次深色背景 */
--surface-1: 240 8% 7%;
--surface-2: 240 6% 11%;
--surface-3: 240 5% 16%;
/* 卡片和 popover */
--card: 240 8% 7%;
--card-foreground: 0 0% 98%;
--popover: 240 8% 7%;
--popover-foreground: 0 0% 98%;
/* ===== 主色 - 紫色系 ===== */
--primary-50: 250 95% 97%;
--primary-100: 251 92% 95%;
--primary-200: 252 88% 91%;
--primary-300: 252 82% 85%;
--primary-400: 256 86% 77%;
--primary-500: 255 87% 70%;
--primary-600: 258 87% 61%;
--primary-700: 262 71% 53%;
--primary-800: 265 68% 44%;
--primary-900: 268 65% 35%;
--primary: 255 87% 70%;
--primary-foreground: 0 0% 100%;
/* ===== 次要色 - 蓝紫色 ===== */
--secondary: 240 6% 16%;
--secondary-foreground: 0 0% 98%;
/* ===== 强调色 - 靛蓝色 ===== */
--accent: 245 72% 65%;
--accent-foreground: 0 0% 100%;
/* ===== Muted 色调 ===== */
--muted: 240 5% 16%;
--muted-foreground: 240 4% 64%;
/* ===== 功能色 ===== */
/* 成功 - 翡翠绿 */
--success: 152 76% 47%;
--success-foreground: 0 0% 100%;
--success-glow: 152 76% 47%;
/* 警告 - 琥珀色 */
--warning: 38 92% 56%;
--warning-foreground: 0 0% 100%;
--warning-glow: 38 92% 56%;
/* 错误/破坏性 - 玫瑰红 */
--destructive: 350 86% 61%;
--destructive-foreground: 0 0% 100%;
--error: 350 86% 61%;
--error-foreground: 0 0% 100%;
--error-glow: 350 86% 61%;
/* 信息 - 天空蓝 */
--info: 199 89% 54%;
--info-foreground: 0 0% 100%;
--info-glow: 199 89% 54%;
/* ===== 边框和输入 ===== */
--border: 240 5% 20%;
--input: 240 5% 16%;
--ring: 255 87% 70%;
/* ===== 发光效果颜色 ===== */
--glow-primary: 255 87% 70%;
--glow-success: 152 76% 47%;
--glow-error: 350 86% 61%;
--glow-warning: 38 92% 56%;
--glow-info: 199 89% 54%;
/* ===== 圆角 ===== */
--radius: 0.75rem;
/* ===== 阴影 ===== */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -2px rgba(0, 0, 0, 0.4);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.4);
/* ===== 动画 ===== */
--duration-150: 150ms;
--duration-200: 200ms;
--duration-300: 300ms;
--duration-500: 500ms;
--duration-700: 700ms;
}
a:hover {
color: #747bff;
}
@layer base {
* {
@apply border-border;
}
button {
background-color: #f9f9f9;
html {
font-family: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
@apply bg-background text-foreground;
margin: 0;
min-height: 100vh;
}
/* 自定义滚动条 */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: hsl(var(--surface-1));
}
::-webkit-scrollbar-thumb {
background: hsl(var(--surface-3));
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: hsl(var(--muted));
}
/* 选中文本 */
::selection {
background: hsl(var(--primary) / 0.3);
color: hsl(var(--foreground));
}
}
@layer components {
/* ===== 卡片组件 ===== */
.card {
@apply bg-surface-1 border border-border rounded-xl p-6;
transition: all var(--duration-300) cubic-bezier(0.215, 0.61, 0.355, 1);
box-shadow: var(--shadow-md);
}
.card:hover {
@apply border-border-hover;
box-shadow: var(--shadow-lg), 0 0 40px -10px hsl(var(--glow-primary) / 0.3);
transform: translateY(-2px);
}
.card-glow {
position: relative;
overflow: hidden;
}
.card-glow::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg,
transparent,
hsl(var(--primary) / 0.5),
transparent
);
animation: shimmer 3s infinite;
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
/* ===== 按钮组件 ===== */
.btn {
@apply inline-flex items-center justify-center;
@apply font-medium transition-all;
@apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2;
@apply disabled:pointer-events-none disabled:opacity-50;
}
.btn-primary {
@apply btn;
@apply bg-gradient-to-br from-primary-500 to-primary-600 text-white;
@apply rounded-lg px-5 py-2.5 text-sm;
box-shadow: 0 4px 14px -2px hsl(var(--glow-primary) / 0.5);
}
.btn-primary:hover {
@apply from-primary-400 to-primary-500;
transform: translateY(-1px);
box-shadow: 0 6px 20px -4px hsl(var(--glow-primary) / 0.6);
}
.btn-primary:active {
transform: translateY(0);
}
.btn-secondary {
@apply btn;
@apply bg-surface-2 text-foreground border border-border;
@apply rounded-lg px-5 py-2.5 text-sm;
}
.btn-secondary:hover {
@apply bg-surface-3 border-border-hover;
}
.btn-ghost {
@apply btn;
@apply bg-transparent text-foreground;
@apply rounded-lg px-5 py-2.5 text-sm;
}
.btn-ghost:hover {
@apply bg-surface-2;
}
.btn-glow {
@apply btn-primary;
animation: pulse-glow 2s ease-in-out infinite;
}
@keyframes pulse-glow {
0%, 100% {
box-shadow: 0 4px 14px -2px hsl(var(--glow-primary) / 0.5);
}
50% {
box-shadow: 0 6px 20px -4px hsl(var(--glow-primary) / 0.7);
}
}
/* ===== 输入框组件 ===== */
.input {
@apply w-full px-4 py-2.5 text-sm;
@apply bg-surface-1 border border-border;
@apply rounded-lg text-foreground;
@apply placeholder:text-muted-foreground;
@apply transition-all duration-200;
@apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.3);
}
.input:focus {
@apply border-primary;
box-shadow:
inset 0 1px 2px rgba(0, 0, 0, 0.3),
0 0 0 3px hsl(var(--primary) / 0.15),
0 0 20px -5px hsl(var(--glow-primary) / 0.4);
}
/* ===== 徽章组件 ===== */
.badge {
@apply inline-flex items-center;
@apply px-2.5 py-0.5 text-xs font-medium;
@apply rounded-full;
}
.badge-primary {
@apply badge;
@apply bg-primary/10 text-primary border border-primary/20;
}
.badge-success {
@apply badge;
@apply bg-success/10 text-success border border-success/20;
}
.badge-warning {
@apply badge;
@apply bg-warning/10 text-warning border border-warning/20;
}
.badge-error {
@apply badge;
@apply bg-error/10 text-error border border-error/20;
}
/* ===== 玻璃态效果 ===== */
.glass {
@apply bg-surface-2/60 backdrop-blur-xl;
border: 1px solid hsl(var(--border) / 0.5);
}
.glass-card {
@apply glass rounded-xl p-6;
box-shadow: var(--shadow-md);
}
/* ===== 渐变背景 ===== */
.gradient-bg {
background:
radial-gradient(circle at 50% 0%,
hsl(var(--primary) / 0.15) 0%,
transparent 50%
),
linear-gradient(180deg,
hsl(var(--background)) 0%,
hsl(var(--surface-1)) 100%
);
}
/* ===== 文字渐变 ===== */
.text-gradient {
@apply bg-gradient-to-br from-white via-white to-white/60;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.text-gradient-primary {
@apply bg-gradient-to-br from-primary-300 via-primary to-primary-700;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* ===== 分隔线 ===== */
.divider {
@apply h-px w-full bg-gradient-to-r from-transparent via-border to-transparent;
}
/* ===== 动画类 ===== */
.fade-in {
animation: fade-in 0.5s ease-out forwards;
}
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.slide-in-left {
animation: slide-in-left 0.3s ease-out forwards;
}
@keyframes slide-in-left {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.scale-in {
animation: scale-in 0.2s ease-out forwards;
}
@keyframes scale-in {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
}
@layer utilities {
/* 文字颜色层级 */
.text-primary {
color: hsl(var(--foreground));
}
.text-secondary {
color: hsl(var(--muted-foreground));
}
.text-tertiary {
color: hsl(var(--muted-foreground) / 0.7);
}
/* 发光工具类 */
.glow-primary {
box-shadow: 0 0 20px -5px hsl(var(--glow-primary) / 0.5);
}
.glow-success {
box-shadow: 0 0 20px -5px hsl(var(--glow-success) / 0.5);
}
.glow-error {
box-shadow: 0 0 20px -5px hsl(var(--glow-error) / 0.5);
}
/* 边框渐变 */
.border-gradient {
position: relative;
background: hsl(var(--surface-1));
border-radius: var(--radius);
}
.border-gradient::before {
content: '';
position: absolute;
inset: 0;
padding: 1px;
border-radius: inherit;
background: linear-gradient(135deg,
hsl(var(--primary) / 0.5),
hsl(var(--accent) / 0.3)
);
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
}
}
+6
View File
@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
+139
View File
@@ -0,0 +1,139 @@
/** @type {import('tailwindcss').Config} */
export default {
darkMode: 'class',
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
50: "hsl(var(--primary-50))",
100: "hsl(var(--primary-100))",
200: "hsl(var(--primary-200))",
300: "hsl(var(--primary-300))",
400: "hsl(var(--primary-400))",
500: "hsl(var(--primary-500))",
600: "hsl(var(--primary-600))",
700: "hsl(var(--primary-700))",
800: "hsl(var(--primary-800))",
900: "hsl(var(--primary-900))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
surface: {
1: "hsl(var(--surface-1))",
2: "hsl(var(--surface-2))",
3: "hsl(var(--surface-3))",
},
success: {
DEFAULT: "hsl(var(--success))",
foreground: "hsl(var(--success-foreground))",
},
warning: {
DEFAULT: "hsl(var(--warning))",
foreground: "hsl(var(--warning-foreground))",
},
error: {
DEFAULT: "hsl(var(--error))",
foreground: "hsl(var(--error-foreground))",
},
info: {
DEFAULT: "hsl(var(--info))",
foreground: "hsl(var(--info-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.25rem' }],
base: ['1rem', { lineHeight: '1.5rem' }],
lg: ['1.125rem', { lineHeight: '1.75rem' }],
xl: ['1.25rem', { lineHeight: '1.75rem' }],
'2xl': ['1.5rem', { lineHeight: '2rem' }],
'3xl': ['1.875rem', { lineHeight: '2.25rem' }],
'4xl': ['2.25rem', { lineHeight: '2.5rem' }],
},
fontFamily: {
sans: ['Inter', 'SF Pro Display', '-apple-system', 'BlinkMacSystemFont', 'sans-serif'],
mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
},
boxShadow: {
'glow': '0 0 20px -5px var(--glow-primary)',
'glow-lg': '0 0 40px -10px var(--glow-primary)',
'glow-sm': '0 0 10px -3px var(--glow-primary)',
'card': '0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2)',
'card-hover': '0 8px 16px -4px rgba(0, 0, 0, 0.4), 0 0 40px -10px var(--glow-primary)',
},
backgroundImage: {
'gradient-primary': 'linear-gradient(135deg, hsl(var(--primary-500)) 0%, hsl(var(--primary-600)) 100%)',
'gradient-glow': 'radial-gradient(circle, hsl(var(--primary-500) / 0.15) 0%, transparent 70%)',
'gradient-card': 'linear-gradient(180deg, hsl(var(--surface-2) / 0.8) 0%, hsl(var(--surface-1) / 0.9) 100%)',
},
keyframes: {
'fade-in': {
'0%': { opacity: '0', transform: 'translateY(10px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
},
'slide-in': {
'0%': { transform: 'translateX(-100%)' },
'100%': { transform: 'translateX(0)' },
},
'pulse-glow': {
'0%, 100%': { boxShadow: '0 0 20px -5px var(--glow-primary)' },
'50%': { boxShadow: '0 0 30px -8px var(--glow-primary)' },
},
'border-beam': {
'100%': {
'offsetDistance': '100%',
},
},
},
animation: {
'fade-in': 'fade-in 0.5s ease-out forwards',
'slide-in': 'slide-in 0.3s ease-out forwards',
'pulse-glow': 'pulse-glow 2s ease-in-out infinite',
},
transitionTimingFunction: {
'ease-out': 'cubic-bezier(0.215, 0.61, 0.355, 1)',
'ease-in-out': 'cubic-bezier(0.645, 0.045, 0.355, 1)',
'spring': 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
},
},
},
plugins: [],
}
+7 -1
View File
@@ -22,7 +22,13 @@
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
"noUncheckedSideEffectImports": true,
/* Path aliases */
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"]
}
+6
View File
@@ -1,5 +1,6 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
// https://vite.dev/config/
export default defineConfig({
@@ -10,4 +11,9 @@ export default defineConfig({
},
}),
],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})