feat: 登录页5种主流高级风格重写 + 主题/登录页设置独立

5个登录组件全面重新设计:
- SplitView: 分屏聚焦风格(Linear/Notion) — 左55%深色渐变+几何动画装饰,右45%纯净表单+底线输入框
- CenteredCard: 沉浸渐变风格(Stripe/Vercel) — 紫蓝辐射背景+毛玻璃卡片+发光效果
- SidePanel: 品牌侧栏风格(Slack/Figma) — 左38%大号标语+feature pills,右62%宽留白表单
- FullImage: 粒子网格风格(GitHub/AWS) — CSS粒子光点+网格线+发光球体+深色内嵌卡片
- MinimalDark: 极简线条风格(Apple/Linear) — 纯黑背景+16px渐变圆点logo+宽字距+底线输入框

AppearanceSettings 主题与登录页完全独立:
- 界面配色区: 5种主题配色即时预览
- 登录页风格区: 5种风格缩略图预览+参考品牌标签(Linear/Notion, Stripe/Vercel等)
- 移除登录页主题切换按钮,风格与主题彻底解耦
This commit is contained in:
logan committed 2026-05-27 03:25:58 +08:00
1 parent ed582e30e6
commit 46054c8b2f
6 files changed
+836 -227

No files matched your search

+144 -26
View File
@@ -1,57 +1,175 @@
import { useLogin } from '@/hooks/useLogin';
import { useTheme, THEME_OPTIONS } from '@/hooks/useTheme';
import { Sun, Moon } from 'lucide-react';
export default function CenteredCard() {
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
const { theme, toggleTheme } = useTheme();
const isDark = THEME_OPTIONS.find(o => o.id === theme)?.isDark ?? true;
return (
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24, position: 'relative' }}>
{/* Gradient background */}
<div className="dg-gradient-br" style={{ position: 'fixed', inset: 0, zIndex: 0 }} />
{/* Theme toggle */}
<button
onClick={toggleTheme}
style={{ position: 'fixed', top: 20, right: 20, zIndex: 50, background: 'rgba(255,255,255,0.1)', border: 'none', borderRadius: 8, padding: 8, cursor: 'pointer', color: '#fff', backdropFilter: 'blur(4px)' }}
<div
className="flex items-center justify-center min-h-screen relative"
style={{ padding: 24 }}
>
{isDark ? <Sun size={18} /> : <Moon size={18} />}
</button>
{/* Full-screen dark gradient background with noise texture */}
<div
style={{
position: 'fixed',
inset: 0,
zIndex: 0,
background: `
radial-gradient(ellipse at 50% 40%, #0a0a1a 0%, #1a0a2e 70%, #0f0520 100%)
`,
}}
/>
{/* Subtle noise / dot pattern overlay */}
<div
style={{
position: 'fixed',
inset: 0,
zIndex: 1,
background: `
radial-gradient(circle at 20% 80%, rgba(99, 102, 241, 0.04) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(139, 92, 246, 0.05) 0%, transparent 50%),
radial-gradient(circle at 50% 50%, rgba(99,102,241,0.02) 0%, transparent 80%)
`,
}}
/>
{/* Extra floating glow orbs */}
<div
style={{
position: 'fixed',
top: '10%',
left: '60%',
width: 400,
height: 400,
borderRadius: '50%',
background: 'radial-gradient(circle, rgba(99,102,241,0.06), transparent 70%)',
zIndex: 1,
filter: 'blur(40px)',
}}
/>
<div
style={{
position: 'fixed',
bottom: '5%',
right: '60%',
width: 300,
height: 300,
borderRadius: '50%',
background: 'radial-gradient(circle, rgba(139,92,246,0.05), transparent 70%)',
zIndex: 1,
filter: 'blur(40px)',
}}
/>
{/* Card */}
<div style={{ position: 'relative', zIndex: 10, width: '100%', maxWidth: 400, background: 'var(--dg-bg-primary)', borderRadius: 14, padding: '40px 32px', boxShadow: '0 8px 32px rgba(0,0,0,0.3)' }}>
<div style={{ textAlign: 'center', marginBottom: 32 }}>
<h1 style={{ fontSize: 24, fontWeight: 700, marginBottom: 6 }}>
<span className="dg-gradient-text">SQL审核平台</span>
</h1>
<p style={{ color: 'var(--dg-text-muted)', fontSize: 14 }}>登录您的账号</p>
{/* Center glassmorphism card */}
<div
style={{
position: 'relative',
zIndex: 10,
width: '100%',
maxWidth: 420,
background: 'rgba(255,255,255,0.03)',
backdropFilter: 'blur(24px)',
WebkitBackdropFilter: 'blur(24px)',
borderRadius: 20,
padding: '48px 36px',
boxShadow: `
0 0 0 1px rgba(255,255,255,0.06),
0 8px 40px rgba(0,0,0,0.4),
0 0 80px rgba(99,102,241,0.08)
`,
}}
>
{/* Logo icon */}
<div
style={{
width: 44,
height: 44,
borderRadius: 12,
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: '0 auto 28px',
boxShadow: '0 4px 20px rgba(99,102,241,0.35)',
}}
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<ellipse cx="12" cy="5" rx="9" ry="3" />
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
</svg>
</div>
<h1 style={{ textAlign: 'center', fontSize: 22, fontWeight: 600, color: '#fff', marginBottom: 6 }}>
SQL审核平台
</h1>
<p style={{ textAlign: 'center', color: 'rgba(255,255,255,0.45)', fontSize: 14, marginBottom: 36 }}>
登录您的工作区
</p>
<form onSubmit={handleLogin} style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
{error && <div style={{ color: '#f87171', fontSize: 13, textAlign: 'center' }}>{error}</div>}
{error && (
<div style={{ color: '#f87171', fontSize: 13, textAlign: 'center', padding: '10px 14px', background: 'rgba(248,113,113,0.08)', borderRadius: 8 }}>
{error}
</div>
)}
<input
type="text"
placeholder="用户名"
value={username}
onChange={(e) => setUsername(e.target.value)}
style={{ padding: '11px 14px', borderRadius: 8, background: 'var(--dg-bg-secondary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)', fontSize: 14, outline: 'none' }}
className="w-full"
style={{
padding: '13px 16px',
borderRadius: 10,
background: 'rgba(255,255,255,0.06)',
border: '1px solid rgba(255,255,255,0.08)',
color: '#fff',
fontSize: 14,
outline: 'none',
transition: 'border-color 0.2s, background 0.2s',
}}
onFocus={(e) => { e.target.style.borderColor = 'rgba(99,102,241,0.4)'; e.target.style.background = 'rgba(255,255,255,0.08)'; }}
onBlur={(e) => { e.target.style.borderColor = 'rgba(255,255,255,0.08)'; e.target.style.background = 'rgba(255,255,255,0.06)'; }}
/>
<input
type="password"
placeholder="密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
style={{ padding: '11px 14px', borderRadius: 8, background: 'var(--dg-bg-secondary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)', fontSize: 14, outline: 'none' }}
className="w-full"
style={{
padding: '13px 16px',
borderRadius: 10,
background: 'rgba(255,255,255,0.06)',
border: '1px solid rgba(255,255,255,0.08)',
color: '#fff',
fontSize: 14,
outline: 'none',
transition: 'border-color 0.2s, background 0.2s',
}}
onFocus={(e) => { e.target.style.borderColor = 'rgba(99,102,241,0.4)'; e.target.style.background = 'rgba(255,255,255,0.08)'; }}
onBlur={(e) => { e.target.style.borderColor = 'rgba(255,255,255,0.08)'; e.target.style.background = 'rgba(255,255,255,0.06)'; }}
/>
<button
type="submit"
disabled={loading || !username || !password}
className="dg-gradient-bg"
style={{ padding: '11px 14px', borderRadius: 8, border: 'none', color: '#fff', fontWeight: 600, fontSize: 14, cursor: loading || !username || !password ? 'not-allowed' : 'pointer', opacity: loading || !username || !password ? 0.6 : 1, marginTop: 4 }}
className="dg-gradient-bg w-full"
style={{
padding: '13px 20px',
borderRadius: 10,
border: 'none',
color: '#fff',
fontWeight: 600,
fontSize: 15,
cursor: loading || !username || !password ? 'not-allowed' : 'pointer',
opacity: loading || !username || !password ? 0.5 : 1,
marginTop: 8,
transition: 'opacity 0.2s, transform 0.1s',
boxShadow: '0 4px 20px rgba(99,102,241,0.25)',
}}
>
{loading ? '登录中…' : '登录'}
</button>
+165 -35
View File
@@ -1,74 +1,204 @@
import { useLogin } from '@/hooks/useLogin';
import { useTheme, THEME_OPTIONS } from '@/hooks/useTheme';
import { Sun, Moon } from 'lucide-react';
export default function FullImage() {
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
const { theme, toggleTheme } = useTheme();
const isDark = THEME_OPTIONS.find(o => o.id === theme)?.isDark ?? true;
return (
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24, position: 'relative', overflow: 'hidden' }}>
{/* Circuit board pattern background */}
<div
className="flex items-center justify-center min-h-screen relative"
style={{ padding: 24, overflow: 'hidden' }}
>
{/* Deep dark background #0d1117 */}
<div
style={{
position: 'fixed', inset: 0, zIndex: 0,
background: `
repeating-linear-gradient(0deg, transparent, transparent 48px, var(--dg-border) 48px, var(--dg-border) 49px),
repeating-linear-gradient(90deg, transparent, transparent 48px, var(--dg-border) 48px, var(--dg-border) 49px),
var(--dg-bg-primary)
`,
opacity: 0.5,
position: 'fixed',
inset: 0,
zIndex: 0,
background: '#0d1117',
}}
/>
{/* Theme toggle */}
<button
onClick={toggleTheme}
style={{ position: 'fixed', top: 20, right: 20, zIndex: 50, background: 'var(--dg-bg-tertiary)', border: 'none', borderRadius: 8, padding: 8, cursor: 'pointer', color: 'var(--dg-text-secondary)', backdropFilter: 'blur(8px)' }}
>
{isDark ? <Sun size={18} /> : <Moon size={18} />}
</button>
{/* Glass card */}
{/* Particle grid — dots and lines */}
<div
style={{
position: 'relative', zIndex: 10, width: '100%', maxWidth: 400,
background: 'rgba(0,0,0,0.45)', backdropFilter: 'blur(16px)',
borderRadius: 16, padding: '40px 32px',
color: '#fff',
position: 'fixed',
inset: 0,
zIndex: 1,
backgroundImage: `
radial-gradient(circle at 15% 25%, rgba(99,102,241,0.15) 1px, transparent 1px),
radial-gradient(circle at 35% 60%, rgba(99,102,241,0.12) 1px, transparent 1px),
radial-gradient(circle at 55% 15%, rgba(139,92,246,0.12) 1px, transparent 1px),
radial-gradient(circle at 75% 45%, rgba(99,102,241,0.10) 1px, transparent 1px),
radial-gradient(circle at 85% 70%, rgba(139,92,246,0.10) 1px, transparent 1px),
radial-gradient(circle at 25% 80%, rgba(99,102,241,0.08) 1px, transparent 1px),
radial-gradient(circle at 65% 85%, rgba(139,92,246,0.08) 1px, transparent 1px),
radial-gradient(circle at 45% 40%, rgba(99,102,241,0.14) 1px, transparent 1px),
radial-gradient(circle at 10% 55%, rgba(139,92,246,0.06) 1px, transparent 1px),
radial-gradient(circle at 90% 20%, rgba(99,102,241,0.08) 1px, transparent 1px),
radial-gradient(circle at 5% 90%, rgba(139,92,246,0.07) 1px, transparent 1px),
radial-gradient(circle at 50% 70%, rgba(99,102,241,0.06) 1px, transparent 1px)
`,
backgroundSize: '200px 200px',
}}
/>
{/* Connecting grid lines */}
<div
style={{
position: 'fixed',
inset: 0,
zIndex: 1,
backgroundImage: `
linear-gradient(rgba(99,102,241,0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(99,102,241,0.03) 1px, transparent 1px)
`,
backgroundSize: '80px 80px',
}}
/>
{/* Large glow orb — top right */}
<div
style={{
position: 'fixed',
top: '-15%',
right: '-10%',
width: 600,
height: 600,
borderRadius: '50%',
background: 'radial-gradient(circle, rgba(99,102,241,0.12) 0%, rgba(139,92,246,0.06) 40%, transparent 70%)',
zIndex: 1,
filter: 'blur(60px)',
}}
/>
{/* Secondary glow — bottom left */}
<div
style={{
position: 'fixed',
bottom: '-20%',
left: '-10%',
width: 400,
height: 400,
borderRadius: '50%',
background: 'radial-gradient(circle, rgba(139,92,246,0.06), transparent 70%)',
zIndex: 1,
filter: 'blur(40px)',
}}
/>
{/* Center card */}
<div
style={{
position: 'relative',
zIndex: 10,
width: '100%',
maxWidth: 400,
background: 'rgba(13, 17, 23, 0.8)',
backdropFilter: 'blur(20px)',
WebkitBackdropFilter: 'blur(20px)',
borderRadius: 16,
padding: '40px 32px',
boxShadow: `
0 0 0 1px rgba(255,255,255,0.08),
0 8px 40px rgba(0,0,0,0.5),
0 0 100px rgba(99,102,241,0.04)
`,
}}
>
{/* Small brand mark at top */}
<div style={{ textAlign: 'center', marginBottom: 32 }}>
<h1 style={{ fontSize: 24, fontWeight: 700, marginBottom: 6 }}>
<span className="dg-gradient-text">SQL审核平台</span>
<div
style={{
width: 36,
height: 36,
borderRadius: 10,
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
marginBottom: 20,
boxShadow: '0 2px 12px rgba(99,102,241,0.3)',
}}
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<ellipse cx="12" cy="5" rx="9" ry="3" />
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
</svg>
</div>
<h1 style={{ fontSize: 20, fontWeight: 600, color: '#fff', marginBottom: 4 }}>
SQL审核平台
</h1>
<p style={{ color: 'rgba(255,255,255,0.6)', fontSize: 14 }}>登录您的工作区</p>
<p style={{ color: 'rgba(255,255,255,0.4)', fontSize: 13 }}>
登录您的工作区
</p>
</div>
<form onSubmit={handleLogin} style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
{error && <div style={{ color: '#f87171', fontSize: 13, textAlign: 'center' }}>{error}</div>}
<form onSubmit={handleLogin} style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
{error && (
<div style={{ color: '#f87171', fontSize: 13, textAlign: 'center', padding: '10px 14px', background: 'rgba(248,113,113,0.06)', borderRadius: 8 }}>
{error}
</div>
)}
{/* Dark inset inputs */}
<input
type="text"
placeholder="用户名"
value={username}
onChange={(e) => setUsername(e.target.value)}
style={{ padding: '11px 14px', borderRadius: 8, background: 'rgba(255,255,255,0.08)', border: '1px solid rgba(255,255,255,0.12)', color: '#fff', fontSize: 14, outline: 'none' }}
className="w-full"
style={{
padding: '12px 16px',
borderRadius: 8,
background: 'rgba(255,255,255,0.04)',
border: '1px solid rgba(255,255,255,0.06)',
color: '#fff',
fontSize: 14,
outline: 'none',
transition: 'border-color 0.2s, background 0.2s',
boxShadow: 'inset 0 1px 3px rgba(0,0,0,0.2)',
}}
onFocus={(e) => { e.target.style.borderColor = 'rgba(99,102,241,0.3)'; e.target.style.background = 'rgba(255,255,255,0.06)'; }}
onBlur={(e) => { e.target.style.borderColor = 'rgba(255,255,255,0.06)'; e.target.style.background = 'rgba(255,255,255,0.04)'; }}
/>
<input
type="password"
placeholder="密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
style={{ padding: '11px 14px', borderRadius: 8, background: 'rgba(255,255,255,0.08)', border: '1px solid rgba(255,255,255,0.12)', color: '#fff', fontSize: 14, outline: 'none' }}
className="w-full"
style={{
padding: '12px 16px',
borderRadius: 8,
background: 'rgba(255,255,255,0.04)',
border: '1px solid rgba(255,255,255,0.06)',
color: '#fff',
fontSize: 14,
outline: 'none',
transition: 'border-color 0.2s, background 0.2s',
boxShadow: 'inset 0 1px 3px rgba(0,0,0,0.2)',
}}
onFocus={(e) => { e.target.style.borderColor = 'rgba(99,102,241,0.3)'; e.target.style.background = 'rgba(255,255,255,0.06)'; }}
onBlur={(e) => { e.target.style.borderColor = 'rgba(255,255,255,0.06)'; e.target.style.background = 'rgba(255,255,255,0.04)'; }}
/>
<button
type="submit"
disabled={loading || !username || !password}
className="dg-gradient-bg"
style={{ padding: '11px 14px', borderRadius: 8, border: 'none', color: '#fff', fontWeight: 600, fontSize: 14, cursor: loading || !username || !password ? 'not-allowed' : 'pointer', opacity: loading || !username || !password ? 0.6 : 1, marginTop: 4 }}
className="dg-gradient-bg w-full"
style={{
padding: '12px 20px',
borderRadius: 8,
border: 'none',
color: '#fff',
fontWeight: 600,
fontSize: 14,
cursor: loading || !username || !password ? 'not-allowed' : 'pointer',
opacity: loading || !username || !password ? 0.5 : 1,
marginTop: 6,
transition: 'opacity 0.2s, transform 0.1s',
}}
>
{loading ? '登录中…' : '登录'}
</button>
+88 -21
View File
@@ -1,51 +1,118 @@
import { useLogin } from '@/hooks/useLogin';
import { useTheme, THEME_OPTIONS } from '@/hooks/useTheme';
import { Sun, Moon, Database } from 'lucide-react';
export default function MinimalDark() {
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
const { theme, toggleTheme } = useTheme();
const isDark = THEME_OPTIONS.find(o => o.id === theme)?.isDark ?? true;
return (
<div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 24, background: 'var(--dg-bg-primary)', color: 'var(--dg-text-primary)' }}>
<button
onClick={toggleTheme}
style={{ position: 'fixed', top: 20, right: 20, zIndex: 50, background: 'var(--dg-bg-tertiary)', border: 'none', borderRadius: 8, padding: 8, cursor: 'pointer', color: 'var(--dg-text-secondary)' }}
<div
className="flex flex-col items-center justify-center min-h-screen"
style={{ background: '#0a0a0a', padding: 24 }}
>
{isDark ? <Sun size={18} /> : <Moon size={18} />}
</button>
<div style={{ width: '100%', maxWidth: 320, textAlign: 'center' }}>
<Database size={36} className="dg-gradient-text" style={{ marginBottom: 16, display: 'inline-block' }} />
<h1 style={{ fontSize: 22, fontWeight: 700, marginBottom: 4 }}>
<span className="dg-gradient-text">SQL审核平台</span>
{/* Tiny gradient dot logo */}
<div
style={{
width: 16,
height: 16,
borderRadius: '50%',
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
margin: '0 auto 24px',
boxShadow: '0 0 12px rgba(99,102,241,0.3)',
}}
/>
{/* Brand name with wide tracking */}
<h1
className="tracking-wider"
style={{ fontSize: 15, fontWeight: 500, color: 'rgba(255,255,255,0.8)', letterSpacing: '3px', marginBottom: 0 }}
>
SQL审核平台
</h1>
<p style={{ color: 'var(--dg-text-muted)', fontSize: 13, marginBottom: 36 }}>安全的数据库审核与合规管理</p>
<form onSubmit={handleLogin} style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
{error && <div style={{ color: '#f87171', fontSize: 13 }}>{error}</div>}
{/* Thin divider */}
<div
style={{
width: 40,
height: 1,
background: 'rgba(255,255,255,0.1)',
margin: '32px auto',
}}
/>
<form
onSubmit={handleLogin}
style={{ display: 'flex', flexDirection: 'column' }}
>
{error && (
<div style={{ color: '#f87171', fontSize: 13, marginBottom: 20, textAlign: 'left' }}>
{error}
</div>
)}
{/* Borderless inputs — bottom line only */}
<div style={{ marginBottom: 24 }}>
<input
type="text"
placeholder="用户名"
value={username}
onChange={(e) => setUsername(e.target.value)}
style={{ padding: '10px 14px', borderRadius: 8, background: 'var(--dg-bg-secondary)', border: 'none', color: 'var(--dg-text-primary)', fontSize: 14, outline: 'none' }}
className="w-full"
style={{
padding: '12px 0',
background: 'transparent',
border: 'none',
borderBottom: '1px solid rgba(255,255,255,0.1)',
borderRadius: 0,
color: '#fff',
fontSize: 14,
outline: 'none',
transition: 'border-color 0.25s',
}}
onFocus={(e) => { e.target.style.borderBottomColor = 'rgba(99,102,241,0.5)'; }}
onBlur={(e) => { e.target.style.borderBottomColor = 'rgba(255,255,255,0.1)'; }}
/>
</div>
<div style={{ marginBottom: 40 }}>
<input
type="password"
placeholder="密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
style={{ padding: '10px 14px', borderRadius: 8, background: 'var(--dg-bg-secondary)', border: 'none', color: 'var(--dg-text-primary)', fontSize: 14, outline: 'none' }}
className="w-full"
style={{
padding: '12px 0',
background: 'transparent',
border: 'none',
borderBottom: '1px solid rgba(255,255,255,0.1)',
borderRadius: 0,
color: '#fff',
fontSize: 14,
outline: 'none',
transition: 'border-color 0.25s',
}}
onFocus={(e) => { e.target.style.borderBottomColor = 'rgba(99,102,241,0.5)'; }}
onBlur={(e) => { e.target.style.borderBottomColor = 'rgba(255,255,255,0.1)'; }}
/>
</div>
{/* Full-width slim button */}
<button
type="submit"
disabled={loading || !username || !password}
className="dg-gradient-bg"
style={{ padding: '10px 14px', borderRadius: 8, border: 'none', color: '#fff', fontWeight: 600, fontSize: 14, cursor: loading || !username || !password ? 'not-allowed' : 'pointer', opacity: loading || !username || !password ? 0.6 : 1, marginTop: 8 }}
className="dg-gradient-bg w-full"
style={{
padding: '12px 20px',
borderRadius: 6,
border: 'none',
color: '#fff',
fontWeight: 500,
fontSize: 14,
cursor: loading || !username || !password ? 'not-allowed' : 'pointer',
opacity: loading || !username || !password ? 0.4 : 1,
transition: 'opacity 0.2s, transform 0.1s',
letterSpacing: '0.5px',
}}
>
{loading ? '登录中…' : '登录'}
</button>
+148 -38
View File
@@ -1,70 +1,180 @@
import { useLogin } from '@/hooks/useLogin';
import { useTheme, THEME_OPTIONS } from '@/hooks/useTheme';
import { Sun, Moon, Database } from 'lucide-react';
const features = ['SQL审核', '变更审批', '实时监控', '权限管理'];
export default function SidePanel() {
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
const { theme, toggleTheme } = useTheme();
const isDark = THEME_OPTIONS.find(o => o.id === theme)?.isDark ?? true;
return (
<div style={{ display: 'flex', minHeight: '100vh', background: 'var(--dg-bg-primary)', color: 'var(--dg-text-primary)' }}>
<button
onClick={toggleTheme}
style={{ position: 'fixed', top: 20, right: 20, zIndex: 50, background: 'var(--dg-bg-tertiary)', border: 'none', borderRadius: 8, padding: 8, cursor: 'pointer', color: 'var(--dg-text-secondary)' }}
>
{isDark ? <Sun size={18} /> : <Moon size={18} />}
</button>
{/* Left Panel — 40% */}
<div className="flex min-h-screen" style={{ background: 'var(--dg-bg-primary)', color: 'var(--dg-text-primary)' }}>
{/* Left Panel — 38% Brand Area */}
<div
className="dg-gradient-br"
style={{ width: '40%', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', padding: 48, position: 'relative', overflow: 'hidden' }}
className="relative flex flex-col justify-between"
style={{
width: '38%',
background: 'linear-gradient(180deg, #111827 0%, #1f2937 100%)',
padding: '56px 44px',
overflow: 'hidden',
}}
>
{/* Decorative circles */}
<div style={{ position: 'absolute', top: '15%', left: '10%', width: 80, height: 80, borderRadius: '50%', background: 'rgba(255,255,255,0.06)' }} />
<div style={{ position: 'absolute', bottom: '20%', right: '15%', width: 120, height: 120, borderRadius: '50%', background: 'rgba(255,255,255,0.04)' }} />
<div style={{ position: 'absolute', top: '55%', left: '60%', width: 40, height: 40, borderRadius: '50%', background: 'rgba(255,255,255,0.07)' }} />
<div style={{ position: 'absolute', top: '30%', right: '30%', width: 12, height: 12, borderRadius: '50%', background: 'rgba(255,255,255,0.12)' }} />
<div style={{ position: 'absolute', bottom: '40%', left: '25%', width: 8, height: 8, borderRadius: '50%', background: 'rgba(255,255,255,0.10)' }} />
{/* Subtle decorative gradient orb */}
<div
style={{
position: 'absolute',
top: '-80px',
right: '-80px',
width: 280,
height: 280,
borderRadius: '50%',
background: 'radial-gradient(circle, rgba(99,102,241,0.08), transparent 70%)',
filter: 'blur(30px)',
}}
/>
<div style={{ position: 'relative', zIndex: 2, textAlign: 'center', maxWidth: 320 }}>
<Database size={40} style={{ color: '#fff', marginBottom: 20 }} />
<h1 style={{ fontSize: 26, fontWeight: 700, color: '#fff', marginBottom: 12 }}>SQL审核平台</h1>
<p style={{ color: 'rgba(255,255,255,0.7)', fontSize: 14, lineHeight: 1.6 }}>
为团队提供智能 SQL 审核、实时合规监控与数据库安全保障。
{/* Top brand section */}
<div style={{ position: 'relative', zIndex: 2 }}>
{/* Logo mark */}
<div
style={{
width: 40,
height: 40,
borderRadius: 10,
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginBottom: 56,
}}
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<ellipse cx="12" cy="5" rx="9" ry="3" />
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
</svg>
</div>
{/* Stacked large text — left aligned */}
<div style={{ marginBottom: 24 }}>
<div style={{ fontSize: 52, fontWeight: 700, color: '#fff', lineHeight: 1.1, letterSpacing: '-1px' }}>
SQL审核
</div>
<div style={{ fontSize: 52, fontWeight: 700, color: '#fff', lineHeight: 1.1, letterSpacing: '-1px', marginTop: 4 }}>
平台
</div>
</div>
<p style={{ color: 'rgba(255,255,255,0.4)', fontSize: 15, lineHeight: 1.6, maxWidth: 280 }}>
企业级数据库审核与合规管理解决方案,为团队提供安全可靠的数据管理体验。
</p>
</div>
{/* Bottom feature pills */}
<div style={{ position: 'relative', zIndex: 2, display: 'flex', flexWrap: 'wrap', gap: 8 }}>
{features.map((tag) => (
<span
key={tag}
style={{
padding: '6px 14px',
borderRadius: 20,
background: 'rgba(255,255,255,0.06)',
color: 'rgba(255,255,255,0.6)',
fontSize: 12,
fontWeight: 500,
letterSpacing: '0.3px',
}}
>
{tag}
</span>
))}
</div>
</div>
{/* Right Panel — Login Form */}
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 48 }}>
<form onSubmit={handleLogin} style={{ width: '100%', maxWidth: 360, display: 'flex', flexDirection: 'column', gap: 20 }}>
<h2 style={{ fontSize: 22, fontWeight: 600, marginBottom: 4 }}>欢迎回来</h2>
<p style={{ color: 'var(--dg-text-muted)', fontSize: 14, marginBottom: 8 }}>登录以访问控制台</p>
{/* Right Panel — 62% Login Form */}
<div
className="flex items-center justify-center"
style={{ flex: 1, padding: 64 }}
>
<form
onSubmit={handleLogin}
style={{ width: '100%', maxWidth: 380, display: 'flex', flexDirection: 'column' }}
>
<h2 style={{ fontSize: 30, fontWeight: 600, marginBottom: 8, color: 'var(--dg-text-primary)' }}>
欢迎回来
</h2>
<p style={{ color: 'var(--dg-text-muted)', fontSize: 15, marginBottom: 44 }}>
登录以继续
</p>
{error && <div style={{ color: '#f87171', fontSize: 13 }}>{error}</div>}
{error && (
<div style={{ color: '#f87171', fontSize: 13, marginBottom: 16, padding: '10px 14px', background: 'rgba(248,113,113,0.08)', borderRadius: 8 }}>
{error}
</div>
)}
<div style={{ marginBottom: 24 }}>
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, color: 'var(--dg-text-secondary)', marginBottom: 8 }}>
用户名
</label>
<input
type="text"
placeholder="用户名"
placeholder="请输入用户名"
value={username}
onChange={(e) => setUsername(e.target.value)}
style={{ padding: '10px 14px', borderRadius: 8, background: 'var(--dg-bg-tertiary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)', fontSize: 14, outline: 'none' }}
className="w-full"
style={{
padding: '13px 16px',
borderRadius: 10,
background: 'var(--dg-bg-secondary)',
border: 'none',
color: 'var(--dg-text-primary)',
fontSize: 14,
outline: 'none',
transition: 'background 0.2s',
}}
onFocus={(e) => { e.target.style.background = 'var(--dg-bg-tertiary)'; }}
onBlur={(e) => { e.target.style.background = 'var(--dg-bg-secondary)'; }}
/>
</div>
<div style={{ marginBottom: 36 }}>
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, color: 'var(--dg-text-secondary)', marginBottom: 8 }}>
密码
</label>
<input
type="password"
placeholder="密码"
placeholder="请输入密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
style={{ padding: '10px 14px', borderRadius: 8, background: 'var(--dg-bg-tertiary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)', fontSize: 14, outline: 'none' }}
className="w-full"
style={{
padding: '13px 16px',
borderRadius: 10,
background: 'var(--dg-bg-secondary)',
border: 'none',
color: 'var(--dg-text-primary)',
fontSize: 14,
outline: 'none',
transition: 'background 0.2s',
}}
onFocus={(e) => { e.target.style.background = 'var(--dg-bg-tertiary)'; }}
onBlur={(e) => { e.target.style.background = 'var(--dg-bg-secondary)'; }}
/>
</div>
<button
type="submit"
disabled={loading || !username || !password}
className="dg-gradient-bg"
style={{ padding: '10px 14px', borderRadius: 8, border: 'none', color: '#fff', fontWeight: 600, fontSize: 14, cursor: loading || !username || !password ? 'not-allowed' : 'pointer', opacity: loading || !username || !password ? 0.6 : 1 }}
className="dg-gradient-bg w-full"
style={{
padding: '13px 20px',
borderRadius: 10,
border: 'none',
color: '#fff',
fontWeight: 600,
fontSize: 15,
cursor: loading || !username || !password ? 'not-allowed' : 'pointer',
opacity: loading || !username || !password ? 0.5 : 1,
transition: 'opacity 0.2s, transform 0.1s',
}}
>
{loading ? '登录中…' : '登录'}
</button>
+212 -48
View File
@@ -1,82 +1,246 @@
import { useLogin } from '@/hooks/useLogin';
import { useTheme, THEME_OPTIONS } from '@/hooks/useTheme';
import { Sun, Moon, Database, Code, GitBranch, Bell, History, UserCog } from 'lucide-react';
const features = [
{ icon: Database, label: 'SQL审核', desc: '实时查询分析与合规检测' },
{ icon: Code, label: '智能规则', desc: '自定义审核规则引擎' },
{ icon: GitBranch, label: '变更管理', desc: 'DDL/DML 变更审批流' },
{ icon: Bell, label: '即时通知', desc: '多渠道消息推送' },
{ icon: History, label: '审计追踪', desc: '完整操作历史记录' },
{ icon: UserCog, label: '权限管理', desc: '基于角色的访问控制' },
];
export default function SplitView() {
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
const { theme, toggleTheme } = useTheme();
const isDark = THEME_OPTIONS.find(o => o.id === theme)?.isDark ?? true;
return (
<div style={{ display: 'flex', minHeight: '100vh', background: 'var(--dg-bg-primary)', color: 'var(--dg-text-primary)' }}>
<button
onClick={toggleTheme}
style={{ position: 'fixed', top: 20, right: 20, zIndex: 50, background: 'var(--dg-bg-tertiary)', border: 'none', borderRadius: 8, padding: 8, cursor: 'pointer', color: 'var(--dg-text-secondary)' }}
<div className="flex min-h-screen" style={{ background: 'var(--dg-bg-primary)', color: 'var(--dg-text-primary)' }}>
{/* Left Panel — 55% Brand Area */}
<div
className="relative overflow-hidden flex items-center justify-center"
style={{
width: '55%',
background: 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)',
padding: '48px 56px',
}}
>
{isDark ? <Sun size={18} /> : <Moon size={18} />}
</button>
{/* Animated geometric decorations */}
{/* Large circle — top right */}
<div
style={{
position: 'absolute',
top: '-60px',
right: '-40px',
width: 260,
height: 260,
borderRadius: '50%',
background: 'rgba(99, 102, 241, 0.08)',
animation: 'splitFloat1 8s ease-in-out infinite',
}}
/>
{/* Medium circle — bottom left */}
<div
style={{
position: 'absolute',
bottom: '40px',
left: '30px',
width: 160,
height: 160,
borderRadius: '50%',
background: 'rgba(139, 92, 246, 0.06)',
animation: 'splitFloat2 10s ease-in-out infinite',
}}
/>
{/* Small circle — center upper */}
<div
style={{
position: 'absolute',
top: '25%',
right: '18%',
width: 48,
height: 48,
borderRadius: '50%',
background: 'rgba(99, 102, 241, 0.15)',
animation: 'splitFloat3 6s ease-in-out infinite',
}}
/>
{/* Tiny dot */}
<div
style={{
position: 'absolute',
top: '65%',
left: '20%',
width: 10,
height: 10,
borderRadius: '50%',
background: 'rgba(255,255,255,0.12)',
animation: 'splitFloat1 7s ease-in-out infinite',
}}
/>
{/* Decorative lines */}
<div
style={{
position: 'absolute',
top: '15%',
left: '10%',
width: 120,
height: 1,
background: 'linear-gradient(90deg, rgba(99,102,241,0.3), transparent)',
transform: 'rotate(-25deg)',
}}
/>
<div
style={{
position: 'absolute',
bottom: '25%',
right: '10%',
width: 180,
height: 1,
background: 'linear-gradient(90deg, rgba(139,92,246,0.2), transparent)',
transform: 'rotate(15deg)',
}}
/>
{/* Another small ring */}
<div
style={{
position: 'absolute',
bottom: '15%',
right: '25%',
width: 32,
height: 32,
borderRadius: '50%',
border: '1px solid rgba(99,102,241,0.2)',
animation: 'splitFloat2 9s ease-in-out infinite',
}}
/>
{/* Left Panel */}
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: '48px 40px', background: 'var(--dg-bg-secondary)' }}>
<div style={{ maxWidth: 440 }}>
<h1 style={{ fontSize: 28, fontWeight: 700, marginBottom: 8 }}>
<span className="dg-gradient-text">SQL审核平台</span>
{/* Brand content */}
<div style={{ position: 'relative', zIndex: 2, maxWidth: 420 }}>
{/* Logo mark */}
<div
style={{
width: 48,
height: 48,
borderRadius: 12,
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginBottom: 40,
boxShadow: '0 4px 24px rgba(99,102,241,0.3)',
}}
>
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<ellipse cx="12" cy="5" rx="9" ry="3" />
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
</svg>
</div>
<h1 style={{ fontSize: 40, fontWeight: 700, color: '#fff', marginBottom: 16, letterSpacing: '-0.5px', lineHeight: 1.2 }}>
SQL审核平台
</h1>
<p style={{ color: 'var(--dg-text-muted)', fontSize: 15, marginBottom: 36, lineHeight: 1.5 }}>
企业级 SQL 审核与合规管理平台。
<p style={{ color: 'rgba(255,255,255,0.5)', fontSize: 16, lineHeight: 1.7, fontWeight: 400 }}>
安全 · 高效 · 可信赖的企业级数据库管理
</p>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
{features.map((f) => (
<div key={f.label} style={{ padding: 16, borderRadius: 10, background: 'var(--dg-bg-tertiary)' }}>
<f.icon size={20} className="dg-gradient-text" />
<div style={{ fontWeight: 600, fontSize: 13, marginTop: 8 }}>{f.label}</div>
<div style={{ fontSize: 12, color: 'var(--dg-text-muted)', marginTop: 2 }}>{f.desc}</div>
</div>
))}
</div>
</div>
</div>
{/* Right Panel — Login Form */}
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 48 }}>
{/* Keyframe animations */}
<style>{`
@keyframes splitFloat1 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-20px); }
}
@keyframes splitFloat2 {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(15px); }
}
@keyframes splitFloat3 {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(8px, -12px); }
}
`}</style>
</div>
{/* Right Panel — 45% Login Form */}
<div
className="flex items-center justify-center"
style={{ width: '45%', padding: 48 }}
>
<form
onSubmit={handleLogin}
style={{ width: '100%', maxWidth: 360, display: 'flex', flexDirection: 'column', gap: 20 }}
style={{ width: '100%', maxWidth: 340, display: 'flex', flexDirection: 'column' }}
>
<h2 style={{ fontSize: 22, fontWeight: 600, marginBottom: 4 }}>登录</h2>
<p style={{ color: 'var(--dg-text-muted)', fontSize: 14, marginBottom: 8 }}>请输入您的账号信息</p>
<h2 style={{ fontSize: 28, fontWeight: 600, marginBottom: 8, color: 'var(--dg-text-primary)' }}>
登录
</h2>
<p style={{ color: 'var(--dg-text-muted)', fontSize: 14, marginBottom: 40 }}>
请输入您的账号信息以继续
</p>
{error && <div style={{ color: '#f87171', fontSize: 13 }}>{error}</div>}
{error && (
<div style={{ color: '#f87171', fontSize: 13, marginBottom: 16, padding: '10px 14px', background: 'rgba(248,113,113,0.08)', borderRadius: 8 }}>
{error}
</div>
)}
<div style={{ marginBottom: 28 }}>
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, color: 'var(--dg-text-secondary)', marginBottom: 8 }}>
用户名
</label>
<input
type="text"
placeholder="用户名"
placeholder="请输入用户名"
value={username}
onChange={(e) => setUsername(e.target.value)}
style={{ padding: '10px 14px', borderRadius: 8, background: 'var(--dg-bg-tertiary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)', fontSize: 14, outline: 'none' }}
className="w-full"
style={{
padding: '12px 0',
background: 'transparent',
border: 'none',
borderBottom: '1px solid var(--dg-border)',
borderRadius: 0,
color: 'var(--dg-text-primary)',
fontSize: 15,
outline: 'none',
transition: 'border-color 0.2s',
}}
onFocus={(e) => { e.target.style.borderBottomColor = '#6366f1'; }}
onBlur={(e) => { e.target.style.borderBottomColor = 'var(--dg-border)'; }}
/>
</div>
<div style={{ marginBottom: 36 }}>
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, color: 'var(--dg-text-secondary)', marginBottom: 8 }}>
密码
</label>
<input
type="password"
placeholder="密码"
placeholder="请输入密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
style={{ padding: '10px 14px', borderRadius: 8, background: 'var(--dg-bg-tertiary)', border: '1px solid var(--dg-border)', color: 'var(--dg-text-primary)', fontSize: 14, outline: 'none' }}
className="w-full"
style={{
padding: '12px 0',
background: 'transparent',
border: 'none',
borderBottom: '1px solid var(--dg-border)',
borderRadius: 0,
color: 'var(--dg-text-primary)',
fontSize: 15,
outline: 'none',
transition: 'border-color 0.2s',
}}
onFocus={(e) => { e.target.style.borderBottomColor = '#6366f1'; }}
onBlur={(e) => { e.target.style.borderBottomColor = 'var(--dg-border)'; }}
/>
</div>
<button
type="submit"
disabled={loading || !username || !password}
className="dg-gradient-bg"
style={{ padding: '10px 14px', borderRadius: 8, border: 'none', color: '#fff', fontWeight: 600, fontSize: 14, cursor: loading || !username || !password ? 'not-allowed' : 'pointer', opacity: loading || !username || !password ? 0.6 : 1 }}
className="dg-gradient-bg w-full"
style={{
padding: '13px 20px',
borderRadius: 10,
border: 'none',
color: '#fff',
fontWeight: 600,
fontSize: 15,
cursor: loading || !username || !password ? 'not-allowed' : 'pointer',
opacity: loading || !username || !password ? 0.5 : 1,
transition: 'opacity 0.2s, transform 0.1s',
}}
>
{loading ? '登录中…' : '登录'}
</button>
+79 -59
View File
@@ -5,6 +5,15 @@ import { Check, Save, Loader2 } from 'lucide-react';
import api from '@/api/request';
import { toast } from '@/hooks/useToast';
// Login style preview color themes (independent from app theme)
const LOGIN_PREVIEW_COLORS: Record<LoginStyle, { bg: string; accent: string; label: string }> = {
split: { bg: 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)', accent: '#6366f1', label: 'Linear/Notion' },
centered: { bg: 'radial-gradient(ellipse at 50% 40%, #0a0a1a 0%, #1a0a2e 100%)', accent: '#8b5cf6', label: 'Stripe/Vercel' },
sidepanel: { bg: 'linear-gradient(180deg, #111827 0%, #1f2937 100%)', accent: '#6366f1', label: 'Slack/Figma' },
fullimage: { bg: '#0d1117', accent: '#6366f1', label: 'GitHub/AWS' },
minimal: { bg: '#0a0a0a', accent: '#8b5cf6', label: 'Apple/Linear' },
};
export default function AppearanceSettings() {
const { theme, setTheme } = useTheme();
const [loginStyle, setLoginStyle] = useState<LoginStyle>('split');
@@ -12,12 +21,10 @@ export default function AppearanceSettings() {
const [loading, setLoading] = useState(true);
const [dirty, setDirty] = useState(false);
// Load preferences from backend on mount
useEffect(() => {
api.get<{ theme?: string; login_style?: string }>('/user/preferences')
.then(prefs => {
if (prefs?.theme) {
// Backward compat: map old values
const t = prefs.theme;
if (t === 'dark' || t === 'light') {
setTheme(t === 'dark' ? 'ocean-deep' : 'light-professional');
@@ -28,7 +35,6 @@ export default function AppearanceSettings() {
if (prefs?.login_style) setLoginStyle(prefs.login_style as LoginStyle);
})
.catch(() => {
// Fallback to localStorage
const stored = localStorage.getItem('login-style') as LoginStyle | null;
if (stored) setLoginStyle(stored);
})
@@ -71,10 +77,10 @@ export default function AppearanceSettings() {
}
return (
<div className="space-y-8">
{/* Theme Mode */}
<div className="space-y-10">
{/* ===== Theme Mode Section ===== */}
<div>
<h3 className="text-sm font-semibold mb-1">主题模式</h3>
<h3 className="text-sm font-semibold mb-1">界面配色</h3>
<p className="text-xs text-[var(--dg-text-muted)] mb-4">选择界面配色方案,即时预览效果</p>
<div className="grid grid-cols-5 gap-3">
{THEME_OPTIONS.map(opt => (
@@ -87,7 +93,7 @@ export default function AppearanceSettings() {
>
{/* Color preview swatch */}
<div
className="w-full h-6 rounded-md mb-3"
className="w-full h-8 rounded-lg mb-3"
style={{
background: opt.isDark
? `linear-gradient(135deg, ${opt.primaryColor} 0%, color-mix(in srgb, ${opt.primaryColor} 40%, #1a1a2e) 50%, #0d1117 100%)`
@@ -109,100 +115,114 @@ export default function AppearanceSettings() {
</div>
</div>
{/* Login Page Style */}
{/* ===== Login Page Style Section ===== */}
<div>
<h3 className="text-sm font-semibold mb-1">登录页风格</h3>
<p className="text-xs text-[var(--dg-text-muted)] mb-4">选择登录页面的视觉风格,保存后退出登录即可看到效果</p>
<p className="text-xs text-[var(--dg-text-muted)] mb-4">选择登录页面的视觉风格,保存后退出登录即可预览</p>
<div className="grid grid-cols-5 gap-3">
{LOGIN_STYLE_OPTIONS.map(opt => (
{LOGIN_STYLE_OPTIONS.map(opt => {
const preview = LOGIN_PREVIEW_COLORS[opt.id];
return (
<button
key={opt.id}
onClick={() => handleLoginStyleChange(opt.id)}
className={`group relative rounded-xl p-4 text-left transition-all ${
loginStyle === opt.id
? 'bg-blue-500/10 ring-1 ring-blue-500/30'
? 'ring-2 ring-indigo-500/50 bg-[var(--dg-bg-tertiary)]'
: 'bg-[var(--dg-bg-tertiary)] hover:bg-[var(--dg-bg-hover)]'
}`}
>
{/* Thumbnail preview */}
<div className={`w-full aspect-[4/3] rounded-lg mb-3 overflow-hidden ${
opt.id === 'split' ? 'flex' :
opt.id === 'centered' ? 'flex items-center justify-center dg-gradient-br' :
opt.id === 'sidepanel' ? 'flex' :
opt.id === 'fullimage' ? 'flex items-center justify-center' :
'flex items-center justify-center bg-[#0a0a0a]'
}`}>
{/* Login style miniature preview */}
<div
className="w-full aspect-[4/3] rounded-lg mb-3 overflow-hidden relative"
style={{ background: preview.bg }}
>
{/* Abstract mini layout */}
{opt.id === 'split' && (
<>
<div className="flex-1 bg-[var(--dg-bg-primary)] p-1.5">
<div className="w-3 h-1 rounded-sm bg-blue-500/30 mb-1" />
<div className="grid grid-cols-2 gap-0.5">
{[0,1,2,3].map(i => <div key={i} className="h-1.5 rounded-sm bg-[var(--dg-bg-tertiary)]" />)}
<div className="absolute inset-0 flex">
<div className="flex-1 p-2 flex flex-col justify-center gap-1">
<div className="w-6 h-1.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.3)' }} />
<div className="w-8 h-1 rounded-sm" style={{ background: 'rgba(255,255,255,0.15)' }} />
</div>
<div className="w-2/5 bg-[var(--dg-bg-secondary)] p-2 flex flex-col justify-center gap-1">
<div className="h-0.5 rounded-sm bg-[var(--dg-bg-hover)]" />
<div className="h-0.5 rounded-sm bg-[var(--dg-bg-hover)]" />
<div className="h-1 rounded-sm mt-0.5" style={{ background: preview.accent, opacity: 0.6 }} />
</div>
<div className="w-2/5 bg-[var(--dg-bg-secondary)] p-1.5 flex flex-col gap-0.5 justify-center">
<div className="h-1 rounded-sm bg-[var(--dg-bg-tertiary)]" />
<div className="h-1 rounded-sm bg-[var(--dg-bg-tertiary)]" />
<div className="h-1.5 rounded-sm dg-gradient-bg opacity-60" />
</div>
</>
)}
{opt.id === 'centered' && (
<div className="w-3/4 bg-white/10 backdrop-blur rounded-md p-1.5 flex flex-col gap-0.5 items-center">
<div className="w-4 h-1 rounded-sm bg-white/40" />
<div className="w-6 h-1 rounded-sm bg-white/20" />
<div className="w-full h-1 rounded-sm bg-white/15" />
<div className="w-full h-1 rounded-sm bg-white/15" />
<div className="w-full h-1.5 rounded-sm bg-white/30" />
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-3/4 rounded-lg p-2 flex flex-col items-center gap-1" style={{ background: 'rgba(255,255,255,0.04)', backdropFilter: 'blur(4px)', border: '1px solid rgba(255,255,255,0.06)' }}>
<div className="w-4 h-1 rounded-sm" style={{ background: 'rgba(255,255,255,0.4)' }} />
<div className="w-6 h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.15)' }} />
<div className="w-full h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.08)' }} />
<div className="w-full h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.08)' }} />
<div className="w-full h-1 rounded-sm mt-0.5" style={{ background: preview.accent, opacity: 0.5 }} />
</div>
</div>
)}
{opt.id === 'sidepanel' && (
<>
<div className="w-2/5 dg-gradient-br p-1.5 flex flex-col justify-center items-center gap-0.5">
<div className="w-3 h-3 rounded-full bg-white/20" />
<div className="w-4 h-0.5 rounded-sm bg-white/15" />
<div className="absolute inset-0 flex">
<div className="w-[38%] p-2 flex flex-col justify-end gap-1" style={{ background: preview.bg }}>
<div className="flex flex-wrap gap-0.5">
{['SQL', 'DDL', 'ACL'].map(t => (
<span key={t} className="px-1 rounded text-[5px]" style={{ background: 'rgba(255,255,255,0.08)', color: 'rgba(255,255,255,0.5)' }}>{t}</span>
))}
</div>
</div>
<div className="flex-1 bg-[var(--dg-bg-primary)] p-2 flex flex-col justify-center gap-1">
<div className="h-0.5 rounded-sm bg-[var(--dg-bg-hover)]" />
<div className="h-0.5 rounded-sm bg-[var(--dg-bg-hover)]" />
<div className="h-1 rounded-sm mt-0.5" style={{ background: preview.accent, opacity: 0.5 }} />
</div>
<div className="flex-1 bg-[var(--dg-bg-primary)] p-1.5 flex flex-col gap-0.5 justify-center">
<div className="h-1 rounded-sm bg-[var(--dg-bg-tertiary)]" />
<div className="h-1 rounded-sm bg-[var(--dg-bg-tertiary)]" />
<div className="h-1.5 rounded-sm dg-gradient-bg opacity-60" />
</div>
</>
)}
{opt.id === 'fullimage' && (
<div className="w-full h-full" style={{
background: 'repeating-linear-gradient(0deg, transparent, transparent 8px, rgba(59,130,246,0.15) 8px, rgba(59,130,246,0.15) 9px), repeating-linear-gradient(90deg, transparent, transparent 8px, rgba(59,130,246,0.15) 8px, rgba(59,130,246,0.15) 9px)',
}}>
<div className="w-3/4 mx-auto mt-3 bg-black/40 backdrop-blur rounded-md p-1.5 flex flex-col gap-0.5 items-center">
<div className="w-3 h-0.5 rounded-sm bg-white/30" />
<div className="w-full h-0.5 rounded-sm bg-white/10" />
<div className="w-full h-0.5 rounded-sm bg-white/10" />
<div className="w-full h-1 rounded-sm bg-blue-500/40" />
<div className="absolute inset-0">
{/* Grid dots */}
<div className="absolute inset-0" style={{
backgroundImage: 'radial-gradient(circle, rgba(99,102,241,0.15) 0.5px, transparent 0.5px)',
backgroundSize: '12px 12px',
}} />
{/* Center card */}
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-3/4 rounded p-1.5 flex flex-col items-center gap-0.5" style={{ background: 'rgba(0,0,0,0.4)', border: '1px solid rgba(255,255,255,0.06)' }}>
<div className="w-3 h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.25)' }} />
<div className="w-full h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.06)' }} />
<div className="w-full h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.06)' }} />
<div className="w-full h-0.5 rounded-sm mt-0.5" style={{ background: preview.accent, opacity: 0.4 }} />
</div>
</div>
</div>
)}
{opt.id === 'minimal' && (
<div className="w-full h-full flex flex-col items-center justify-center gap-1 p-2">
<div className="w-4 h-1 rounded-sm bg-white/20" />
<div className="w-6 h-0.5 rounded-sm bg-white/10" />
<div className="w-full h-1 rounded-sm bg-white/8" />
<div className="w-full h-1 rounded-sm bg-white/8" />
<div className="w-full h-1.5 rounded-sm bg-white/15" />
<div className="absolute inset-0 flex flex-col items-center justify-center gap-1.5 p-2">
<div className="w-2 h-2 rounded-full" style={{ background: `linear-gradient(135deg, ${preview.accent}, #a855f7)` }} />
<div className="w-8 h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.15)' }} />
<div className="w-6 h-px my-1" style={{ background: 'rgba(255,255,255,0.1)' }} />
<div className="w-full h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.06)' }} />
<div className="w-full h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.06)' }} />
<div className="w-full h-0.5 rounded-sm mt-0.5" style={{ background: preview.accent, opacity: 0.4 }} />
</div>
)}
</div>
{/* Active check mark */}
{loginStyle === opt.id && (
<div className="absolute top-2 right-2 w-5 h-5 rounded-full bg-blue-500 flex items-center justify-center">
<div className="absolute top-2 right-2 w-5 h-5 rounded-full bg-indigo-500 flex items-center justify-center">
<Check className="w-3 h-3 text-white" />
</div>
)}
<h4 className="text-sm font-medium mb-0.5">{opt.name}</h4>
<p className="text-xs text-[var(--dg-text-muted)]">{opt.desc}</p>
<p className="text-xs text-[var(--dg-text-muted)] leading-snug">{opt.desc}</p>
<p className="text-[10px] text-[var(--dg-text-muted)] mt-1 opacity-60">{preview.label}</p>
</button>
))}
);
})}
</div>
</div>