feat: 极简暗色替换为深蓝紫曲线风格 — 粉紫流动曲线优雅暗黑

MinimalDark.tsx 全面重写:
- 背景: 深蓝紫渐变(#0a0a1a→#0f0a2a→#150825)代替纯黑
- 7条流动曲线: 粉紫/靛蓝/青色渐变彩带 + blur效果 + 缓慢浮动动画
- 5个脉动装饰点: 粉色/紫色/靛蓝不同delay闪烁
- 2个发光球: 右上紫色光球 + 左下粉色光球
- 毛玻璃居中卡片: backdrop-blur + 半透明边框
- 粉紫渐变logo(#ec4899→#a855f7) + 紫色聚焦高亮
- 按钮粉紫渐变发光阴影

LoginPage + AppearanceSettings 描述和缩略图同步更新
This commit is contained in:
logan committed 2026-05-27 13:06:17 +08:00
1 parent 1aa729b61b
commit 439eb6d6d7
3 files changed
+211 -166

No files matched your search

+194 -157
View File
@@ -1,223 +1,260 @@
import { useLogin } from '@/hooks/useLogin'; import { useLogin } from '@/hooks/useLogin';
/* ---------- static star positions (generated once) ---------- */ /* ---------- Flowing curve configs ---------- */
const STARS = [ const CURVES = [
{ top: '8%', left: '15%', size: 2, opacity: 0.25 }, {
{ top: '12%', left: '72%', size: 1, opacity: 0.15 }, // Curve 1: Wide pink-to-purple ribbon, top area
{ top: '25%', left: '45%', size: 3, opacity: 0.10 }, width: '140%', height: 320, top: '-5%', left: '-20%',
{ top: '18%', left: '88%', size: 1, opacity: 0.35 }, background: 'linear-gradient(135deg, #ec4899 0%, #a855f7 40%, #6366f1 100%)',
{ top: '38%', left: '22%', size: 2, opacity: 0.20 }, opacity: 0.12, blur: 60, rotate: -12,
{ top: '55%', left: '68%', size: 1, opacity: 0.30 }, animation: 'curve-flow-1 20s ease-in-out infinite',
{ top: '62%', left: '35%', size: 2, opacity: 0.15 }, },
{ top: '75%', left: '80%', size: 3, opacity: 0.12 }, {
{ top: '82%', left: '50%', size: 1, opacity: 0.40 }, // Curve 2: Thin bright pink ribbon, mid-upper
{ top: '90%', left: '10%', size: 2, opacity: 0.18 }, width: '120%', height: 180, top: '15%', left: '-10%',
background: 'linear-gradient(90deg, transparent 0%, #f472b6 30%, #c084fc 60%, transparent 100%)',
opacity: 0.18, blur: 40, rotate: -8,
animation: 'curve-flow-2 16s ease-in-out infinite',
},
{
// Curve 3: Deep purple band, center
width: '160%', height: 240, top: '35%', left: '-30%',
background: 'linear-gradient(135deg, #7c3aed 0%, #4f46e5 50%, #2563eb 100%)',
opacity: 0.10, blur: 70, rotate: 5,
animation: 'curve-flow-3 24s ease-in-out infinite',
},
{
// Curve 4: Bright cyan-pink accent, lower-mid
width: '110%', height: 120, top: '55%', left: '-5%',
background: 'linear-gradient(90deg, transparent 10%, #06b6d4 35%, #a855f7 65%, transparent 90%)',
opacity: 0.15, blur: 35, rotate: -15,
animation: 'curve-flow-4 14s ease-in-out infinite',
},
{
// Curve 5: Soft pink glow, bottom
width: '130%', height: 200, top: '70%', left: '-15%',
background: 'linear-gradient(135deg, #db2777 0%, #9333ea 60%, #4f46e5 100%)',
opacity: 0.08, blur: 80, rotate: 10,
animation: 'curve-flow-5 22s ease-in-out infinite',
},
{
// Curve 6: Thin bright line accent
width: '100%', height: 3, top: '40%', left: '0%',
background: 'linear-gradient(90deg, transparent 0%, #f472b6 20%, #c084fc 50%, #818cf8 80%, transparent 100%)',
opacity: 0.35, blur: 8, rotate: -5,
animation: 'curve-line-float 18s ease-in-out infinite',
},
{
// Curve 7: Another thin accent line
width: '90%', height: 2, top: '65%', left: '5%',
background: 'linear-gradient(90deg, transparent 5%, #818cf8 30%, #e879f9 70%, transparent 95%)',
opacity: 0.25, blur: 6, rotate: 8,
animation: 'curve-line-float 15s ease-in-out infinite reverse',
},
]; ];
/* ---------- meteor configs ---------- */ const KEYFRAMES = `
const METEORS = [ @keyframes curve-flow-1 {
{ width: 140, height: 1, duration: '1.5s', delay: '0s', top: '-20px', right: '10%' }, 0%, 100% { transform: rotate(-12deg) translateX(0) scaleY(1); }
{ width: 120, height: 2, duration: '1.2s', delay: '2s', top: '-60px', right: '30%' }, 33% { transform: rotate(-10deg) translateX(30px) scaleY(1.05); }
{ width: 150, height: 1, duration: '1.8s', delay: '4s', top: '-10px', right: '55%' }, 66% { transform: rotate(-14deg) translateX(-20px) scaleY(0.95); }
{ width: 110, height: 2, duration: '2s', delay: '6.5s', top: '-80px', right: '20%' },
{ width: 130, height: 1, duration: '1.4s', delay: '9s', top: '-40px', right: '70%' },
{ width: 100, height: 1, duration: '1.6s', delay: '12s', top: '-30px', right: '45%' },
];
const KEYFRAMES = METEORS.map((_, i) => `
@keyframes meteor-shoot-${i + 1} {
0% {
opacity: 0;
transform: translate(100vw, -100px) rotate(-35deg);
}
10% {
opacity: 1;
}
100% {
opacity: 0;
transform: translate(-100px, 100vh) rotate(-35deg);
}
} }
`).join('\n'); @keyframes curve-flow-2 {
0%, 100% { transform: rotate(-8deg) translateX(0); }
50% { transform: rotate(-6deg) translateX(40px); }
}
@keyframes curve-flow-3 {
0%, 100% { transform: rotate(5deg) translateX(0) scaleX(1); }
40% { transform: rotate(3deg) translateX(-30px) scaleX(1.02); }
70% { transform: rotate(7deg) translateX(20px) scaleX(0.98); }
}
@keyframes curve-flow-4 {
0%, 100% { transform: rotate(-15deg) translateX(0); }
50% { transform: rotate(-13deg) translateX(50px); }
}
@keyframes curve-flow-5 {
0%, 100% { transform: rotate(10deg) translateX(0); }
50% { transform: rotate(8deg) translateX(-40px); }
}
@keyframes curve-line-float {
0%, 100% { transform: rotate(-5deg) translateX(0); opacity: 0.3; }
25% { opacity: 0.5; }
50% { transform: rotate(-3deg) translateX(30px); opacity: 0.35; }
75% { opacity: 0.5; }
}
@keyframes curve-dot-pulse {
0%, 100% { opacity: 0.15; transform: scale(1); }
50% { opacity: 0.35; transform: scale(1.5); }
}
`;
/* ---------- Decorative dots ---------- */
const DOTS = [
{ top: '18%', left: '75%', size: 4, color: '#f472b6', delay: '0s' },
{ top: '30%', left: '20%', size: 3, color: '#818cf8', delay: '2s' },
{ top: '72%', left: '80%', size: 5, color: '#c084fc', delay: '1s' },
{ top: '85%', left: '30%', size: 3, color: '#ec4899', delay: '3s' },
{ top: '45%', left: '90%', size: 4, color: '#a855f7', delay: '1.5s' },
];
export default function MinimalDark() { export default function MinimalDark() {
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin(); const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
return ( return (
<> <>
{/* Inject keyframes once */}
<style>{KEYFRAMES}</style> <style>{KEYFRAMES}</style>
<div <div
style={{ style={{
position: 'relative', position: 'relative',
display: 'flex', display: 'flex',
flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
minHeight: '100vh', minHeight: '100vh',
padding: 24, padding: 24,
background: 'radial-gradient(ellipse at 50% 50%, #151030 0%, #0c0a1a 70%)', background: 'linear-gradient(160deg, #0a0a1a 0%, #0f0a2a 30%, #150825 60%, #0a0a1a 100%)',
overflow: 'hidden', overflow: 'hidden',
}} }}
> >
{/* -------- Static star dots -------- */} {/* -------- Background glow orbs -------- */}
{STARS.map((s, i) => ( <div
style={{
position: 'fixed', top: '-20%', right: '-10%', width: 500, height: 500,
borderRadius: '50%', background: 'radial-gradient(circle, rgba(168,85,247,0.08), transparent 70%)',
filter: 'blur(40px)', pointerEvents: 'none',
}}
/>
<div
style={{
position: 'fixed', bottom: '-15%', left: '-5%', width: 400, height: 400,
borderRadius: '50%', background: 'radial-gradient(circle, rgba(236,72,153,0.06), transparent 70%)',
filter: 'blur(40px)', pointerEvents: 'none',
}}
/>
{/* -------- Flowing curves -------- */}
{CURVES.map((c, i) => (
<div <div
key={`star-${i}`} key={`curve-${i}`}
style={{ style={{
position: 'absolute', position: 'absolute',
top: s.top, top: c.top, left: c.left,
left: s.left, width: c.width, height: c.height,
width: s.size, background: c.background,
height: s.size, opacity: c.opacity,
filter: `blur(${c.blur}px)`,
borderRadius: '50%', borderRadius: '50%',
background: '#fff', animation: c.animation,
opacity: s.opacity,
pointerEvents: 'none', pointerEvents: 'none',
}} }}
/> />
))} ))}
{/* -------- Shooting meteors -------- */} {/* -------- Decorative dots -------- */}
{METEORS.map((m, i) => ( {DOTS.map((d, i) => (
<div <div
key={`meteor-${i}`} key={`dot-${i}`}
style={{ style={{
position: 'absolute', position: 'absolute',
top: m.top, top: d.top, left: d.left,
right: m.right, width: d.size, height: d.size,
width: m.width, borderRadius: '50%',
height: m.height, background: d.color,
background: 'linear-gradient(to left, rgba(255,255,255,0.8), transparent)', animation: `curve-dot-pulse ${3 + i * 0.5}s ${d.delay} ease-in-out infinite`,
transform: 'rotate(-35deg)',
animation: `meteor-shoot-${i + 1} ${m.duration} ${m.delay} linear infinite forwards`,
pointerEvents: 'none', pointerEvents: 'none',
}} }}
/> />
))} ))}
{/* -------- Central content card -------- */} {/* -------- Central content -------- */}
<div style={{ width: '100%', maxWidth: 320, textAlign: 'center', position: 'relative', zIndex: 1 }}> <div
{/* Gradient diamond */} style={{
width: '100%', maxWidth: 400, textAlign: 'center',
position: 'relative', zIndex: 10,
background: 'rgba(255,255,255,0.03)',
backdropFilter: 'blur(20px)', WebkitBackdropFilter: 'blur(20px)',
borderRadius: 24, padding: '48px 36px',
boxShadow: '0 0 0 1px rgba(255,255,255,0.05), 0 8px 40px rgba(0,0,0,0.3)',
}}
>
{/* Brand logo */}
<div <div
style={{ style={{
width: 20, width: 44, height: 44, borderRadius: 12,
height: 20, background: 'linear-gradient(135deg, #ec4899, #a855f7)',
background: 'linear-gradient(135deg, #6366f1, #a855f7)', display: 'flex', alignItems: 'center', justifyContent: 'center',
borderRadius: 4,
transform: 'rotate(45deg)',
margin: '0 auto 24px', margin: '0 auto 24px',
boxShadow: '0 0 16px rgba(99,102,241,0.35)', boxShadow: '0 4px 20px rgba(168,85,247,0.3)',
}}
/>
{/* Brand */}
<h1
style={{
fontSize: 15,
fontWeight: 500,
letterSpacing: '3px',
color: 'rgba(255,255,255,0.8)',
margin: 0,
}} }}
> >
<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>
{/* Brand name */}
<h1 style={{ fontSize: 22, fontWeight: 600, color: '#fff', marginBottom: 6, letterSpacing: '-0.3px' }}>
SQL审核平台 SQL审核平台
</h1> </h1>
<p style={{ color: 'rgba(255,255,255,0.4)', fontSize: 14, marginBottom: 36 }}>
{/* Thin divider */} 企业级数据库审核与合规管理
<div </p>
style={{
width: 40,
height: 1,
background: 'rgba(255,255,255,0.1)',
margin: '32px auto',
}}
/>
{/* Form */} {/* Form */}
<form <form onSubmit={handleLogin} style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
onSubmit={handleLogin}
style={{ display: 'flex', flexDirection: 'column' }}
>
{error && ( {error && (
<div style={{ color: '#f87171', fontSize: 13, marginBottom: 20, textAlign: 'left' }}> <div style={{ color: '#f87171', fontSize: 13, textAlign: 'center', padding: '10px 14px', background: 'rgba(248,113,113,0.08)', borderRadius: 8 }}>
{error} {error}
</div> </div>
)} )}
{/* Username */} <input
<div style={{ marginBottom: 24 }}> type="text"
<input placeholder="用户名"
type="text" value={username}
placeholder="用户名" onChange={(e) => setUsername(e.target.value)}
value={username} className="w-full"
onChange={(e) => setUsername(e.target.value)} style={{
className="w-full" padding: '13px 16px', borderRadius: 10,
style={{ background: 'rgba(255,255,255,0.05)',
padding: '12px 0', border: '1px solid rgba(255,255,255,0.08)',
background: 'transparent', color: '#fff', fontSize: 14, outline: 'none',
border: 'none', transition: 'border-color 0.2s, background 0.2s',
borderBottom: '1px solid rgba(255,255,255,0.1)', }}
borderRadius: 0, onFocus={(e) => { e.target.style.borderColor = 'rgba(168,85,247,0.4)'; e.target.style.background = 'rgba(255,255,255,0.08)'; }}
color: '#fff', onBlur={(e) => { e.target.style.borderColor = 'rgba(255,255,255,0.08)'; e.target.style.background = 'rgba(255,255,255,0.05)'; }}
fontSize: 14, />
outline: 'none', <input
transition: 'border-color 0.25s', type="password"
}} placeholder="密码"
onFocus={(e) => { value={password}
e.target.style.borderBottomColor = 'rgba(99,102,241,0.5)'; onChange={(e) => setPassword(e.target.value)}
}} className="w-full"
onBlur={(e) => { style={{
e.target.style.borderBottomColor = 'rgba(255,255,255,0.1)'; padding: '13px 16px', borderRadius: 10,
}} background: 'rgba(255,255,255,0.05)',
/> border: '1px solid rgba(255,255,255,0.08)',
</div> color: '#fff', fontSize: 14, outline: 'none',
transition: 'border-color 0.2s, background 0.2s',
}}
onFocus={(e) => { e.target.style.borderColor = 'rgba(168,85,247,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.05)'; }}
/>
{/* Password */}
<div style={{ marginBottom: 40 }}>
<input
type="password"
placeholder="密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
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>
{/* Submit */}
<button <button
type="submit" type="submit"
disabled={loading || !username || !password} disabled={loading || !username || !password}
className="dg-gradient-bg w-full" className="dg-gradient-bg w-full"
style={{ style={{
padding: 12, padding: '13px 20px', borderRadius: 10,
borderRadius: 6, border: 'none', color: '#fff', fontWeight: 600, fontSize: 15,
border: 'none',
color: '#fff',
fontWeight: 500,
fontSize: 14,
cursor: loading || !username || !password ? 'not-allowed' : 'pointer', cursor: loading || !username || !password ? 'not-allowed' : 'pointer',
opacity: loading || !username || !password ? 0.4 : 1, opacity: loading || !username || !password ? 0.5 : 1,
marginTop: 4,
transition: 'opacity 0.2s, transform 0.1s', transition: 'opacity 0.2s, transform 0.1s',
letterSpacing: '0.5px', boxShadow: '0 4px 20px rgba(168,85,247,0.25)',
}} }}
> >
{loading ? '登录中…' : '登录'} {loading ? '登录中…' : '登录'}
+16 -8
View File
@@ -11,7 +11,7 @@ const LOGIN_PREVIEW_COLORS: Record<LoginStyle, { bg: string; accent: string; lab
centered: { bg: 'radial-gradient(ellipse at 50% 40%, #0a0a1a 0%, #1a0a2e 100%)', accent: '#8b5cf6', label: 'Stripe/Vercel' }, 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' }, sidepanel: { bg: 'linear-gradient(180deg, #111827 0%, #1f2937 100%)', accent: '#6366f1', label: 'Slack/Figma' },
fullimage: { bg: '#0d1117', accent: '#6366f1', label: 'GitHub/AWS' }, fullimage: { bg: '#0d1117', accent: '#6366f1', label: 'GitHub/AWS' },
minimal: { bg: '#0a0a0a', accent: '#8b5cf6', label: 'Apple/Linear' }, minimal: { bg: 'linear-gradient(160deg, #0a0a1a 0%, #0f0a2a 40%, #150825 70%, #0a0a1a 100%)', accent: '#ec4899', label: 'Premium Dark' },
}; };
export default function AppearanceSettings() { export default function AppearanceSettings() {
@@ -199,13 +199,21 @@ export default function AppearanceSettings() {
</div> </div>
)} )}
{opt.id === 'minimal' && ( {opt.id === 'minimal' && (
<div className="absolute inset-0 flex flex-col items-center justify-center gap-1.5 p-2"> <div className="absolute inset-0" style={{ background: 'linear-gradient(160deg, #0a0a1a, #0f0a2a, #150825)' }}>
<div className="w-2 h-2 rounded-full" style={{ background: `linear-gradient(135deg, ${preview.accent}, #a855f7)` }} /> {/* Flowing curve ribbons */}
<div className="w-8 h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.15)' }} /> <div className="absolute" style={{ top: '15%', left: '-20%', width: '140%', height: '40%', background: 'linear-gradient(135deg, rgba(236,72,153,0.2), rgba(168,85,247,0.15))', borderRadius: '50%', filter: 'blur(8px)', transform: 'rotate(-12deg)' }} />
<div className="w-6 h-px my-1" style={{ background: 'rgba(255,255,255,0.1)' }} /> <div className="absolute" style={{ top: '35%', left: '-10%', width: '120%', height: '30%', background: 'linear-gradient(135deg, rgba(129,140,248,0.15), rgba(192,132,252,0.1))', borderRadius: '50%', filter: 'blur(6px)', transform: 'rotate(5deg)' }} />
<div className="w-full h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.06)' }} /> <div className="absolute" style={{ top: '55%', left: '5%', width: '90%', height: '15%', background: 'linear-gradient(90deg, transparent, rgba(244,114,182,0.2), rgba(192,132,252,0.15), transparent)', borderRadius: '50%', filter: 'blur(4px)', transform: 'rotate(-8deg)' }} />
<div className="w-full h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.06)' }} /> {/* Center card */}
<div className="w-full h-0.5 rounded-sm mt-0.5" style={{ background: preview.accent, opacity: 0.4 }} /> <div className="absolute inset-0 flex items-center justify-center">
<div className="w-3/4 rounded-md p-1.5 flex flex-col items-center gap-0.5" style={{ background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.06)', backdropFilter: 'blur(4px)' }}>
<div className="w-3 h-1 rounded-sm" style={{ background: 'rgba(255,255,255,0.3)' }} />
<div className="w-5 h-0.5 rounded-sm" style={{ background: 'rgba(255,255,255,0.12)' }} />
<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-1 rounded-sm mt-0.5" style={{ background: 'linear-gradient(90deg, #ec4899, #a855f7)', opacity: 0.5 }} />
</div>
</div>
</div> </div>
)} )}
</div> </div>
+1 -1
View File
@@ -20,7 +20,7 @@ export const LOGIN_STYLE_OPTIONS: { id: LoginStyle; name: string; desc: string }
{ id: 'centered', name: '居中卡片', desc: '渐变背景,居中登录卡片' }, { id: 'centered', name: '居中卡片', desc: '渐变背景,居中登录卡片' },
{ id: 'sidepanel', name: '侧边面板', desc: '左侧品牌面板 + 右侧表单' }, { id: 'sidepanel', name: '侧边面板', desc: '左侧品牌面板 + 右侧表单' },
{ id: 'fullimage', name: '全屏背景', desc: '科技背景,毛玻璃卡片' }, { id: 'fullimage', name: '全屏背景', desc: '科技背景,毛玻璃卡片' },
{ id: 'minimal', name: '极简暗色', desc: '极致简约,纯暗色风格' }, { id: 'minimal', name: '深蓝紫曲线', desc: '优雅暗黑,粉紫流动曲线' },
]; ];
export default function LoginPage() { export default function LoginPage() {