|
|
|
@ -1,26 +1,56 @@ |
|
|
|
import { useState } from 'react' |
|
|
|
import { useNavigate } from 'react-router-dom' |
|
|
|
import { Mail, Lock, AlertCircle, Zap } from 'lucide-react' |
|
|
|
import { useState, useEffect } from 'react' |
|
|
|
import { useNavigate, useSearchParams } from 'react-router-dom' |
|
|
|
import { UserRound, Lock, AlertCircle, Eye, EyeOff, ArrowRight, Shield, Cpu, BarChart3 } from 'lucide-react' |
|
|
|
import { useAuth } from '@/contexts/AuthContext' |
|
|
|
import { apiClient } from '@/services/api' |
|
|
|
import { Input } from '@/components/ui/Input' |
|
|
|
import { Button } from '@/components/ui/Button' |
|
|
|
import { Card } from '@/components/ui/Card' |
|
|
|
|
|
|
|
export function LoginPage() { |
|
|
|
const [email, setEmail] = useState('') |
|
|
|
const [account, setAccount] = useState('') |
|
|
|
const [password, setPassword] = useState('') |
|
|
|
const [showPassword, setShowPassword] = useState(false) |
|
|
|
const [error, setError] = useState('') |
|
|
|
const [isLoading, setIsLoading] = useState(false) |
|
|
|
const [ssoLoading, setSsoLoading] = useState(false) |
|
|
|
const [mounted, setMounted] = useState(false) |
|
|
|
const navigate = useNavigate() |
|
|
|
const [searchParams] = useSearchParams() |
|
|
|
const { login } = useAuth() |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
setMounted(true) |
|
|
|
// 检查 SSO 错误参数
|
|
|
|
const ssoError = searchParams.get('error') |
|
|
|
if (ssoError === 'sso_failed') { |
|
|
|
setError('SSO 登录失败,请重试') |
|
|
|
} |
|
|
|
}, [searchParams]) |
|
|
|
|
|
|
|
const handleSSOLogin = async () => { |
|
|
|
setSsoLoading(true) |
|
|
|
setError('') |
|
|
|
try { |
|
|
|
const res = await apiClient.getSSOLoginUrl() |
|
|
|
if (res.login_url) { |
|
|
|
window.location.href = res.login_url |
|
|
|
} else { |
|
|
|
setError('获取 SSO 登录链接失败') |
|
|
|
setSsoLoading(false) |
|
|
|
} |
|
|
|
} catch { |
|
|
|
setError('SSO 服务不可用') |
|
|
|
setSsoLoading(false) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => { |
|
|
|
e.preventDefault() |
|
|
|
setError('') |
|
|
|
setIsLoading(true) |
|
|
|
|
|
|
|
try { |
|
|
|
await login(email, password) |
|
|
|
await login(account, password) |
|
|
|
navigate('/dashboard') |
|
|
|
} catch (err) { |
|
|
|
setError(err instanceof Error ? err.message : '登录失败') |
|
|
|
@ -30,64 +60,183 @@ export function LoginPage() { |
|
|
|
} |
|
|
|
|
|
|
|
return ( |
|
|
|
<div className="min-h-screen flex items-center justify-center relative overflow-hidden grid-pattern"> |
|
|
|
{/* Animated background elements */} |
|
|
|
<div className="absolute inset-0 overflow-hidden"> |
|
|
|
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-sky-500/10 rounded-full blur-3xl animate-pulse" /> |
|
|
|
<div className="absolute bottom-1/4 right-1/4 w-96 h-96 bg-purple-500/10 rounded-full blur-3xl animate-pulse" style={{ animationDelay: '1s' }} /> |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* Decorative grid lines */} |
|
|
|
<div className="absolute inset-0 pointer-events-none"> |
|
|
|
<div className="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-sky-500/30 to-transparent" /> |
|
|
|
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-purple-500/30 to-transparent" /> |
|
|
|
<div className="absolute top-0 bottom-0 left-0 w-px bg-gradient-to-b from-transparent via-sky-500/30 to-transparent" /> |
|
|
|
<div className="absolute top-0 bottom-0 right-0 w-px bg-gradient-to-b from-transparent via-purple-500/30 to-transparent" /> |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* Main content */} |
|
|
|
<div className="relative z-10 w-full max-w-md px-4 animate-scale-in"> |
|
|
|
{/* Logo and title */} |
|
|
|
<div className="text-center mb-8"> |
|
|
|
<div className="inline-flex items-center justify-center w-20 h-20 mb-6 rounded-2xl bg-gradient-to-br from-sky-500 to-blue-600 glow-primary relative overflow-hidden"> |
|
|
|
<div className="absolute inset-0 stripe-pattern" /> |
|
|
|
<Zap className="h-10 w-10 text-white relative z-10" /> |
|
|
|
</div> |
|
|
|
<h1 className="text-4xl font-bold text-white font-display tracking-wide mb-2"> |
|
|
|
BASE<span className="text-sky-400">.</span> |
|
|
|
<div className="min-h-screen flex bg-[oklch(0.10_0.01_240)]"> |
|
|
|
{/* Left Panel - Brand Showcase (visible from md breakpoint) */} |
|
|
|
<div |
|
|
|
className={`hidden md:flex md:w-[45%] lg:w-[55%] relative overflow-hidden flex-col justify-between p-8 lg:p-12 transition-all duration-700 ease-out ${ |
|
|
|
mounted ? 'opacity-100 translate-x-0' : 'opacity-0 -translate-x-8' |
|
|
|
}`}
|
|
|
|
> |
|
|
|
{/* Gradient background */} |
|
|
|
<div className="absolute inset-0 bg-gradient-to-br from-indigo-600 via-blue-600 to-sky-500" /> |
|
|
|
|
|
|
|
{/* Subtle pattern overlay */} |
|
|
|
<div |
|
|
|
className="absolute inset-0 opacity-[0.07]" |
|
|
|
style={{ |
|
|
|
backgroundImage: |
|
|
|
'radial-gradient(circle at 1px 1px, white 1px, transparent 0)', |
|
|
|
backgroundSize: '32px 32px', |
|
|
|
}} |
|
|
|
/> |
|
|
|
|
|
|
|
{/* Floating orbs */} |
|
|
|
<div className="absolute top-20 right-20 w-48 lg:w-72 h-48 lg:h-72 bg-white/10 rounded-full blur-3xl animate-login-float" /> |
|
|
|
<div |
|
|
|
className="absolute bottom-32 left-16 w-40 lg:w-56 h-40 lg:h-56 bg-sky-300/15 rounded-full blur-3xl animate-login-float" |
|
|
|
style={{ animationDelay: '2s', animationDuration: '8s' }} |
|
|
|
/> |
|
|
|
<div |
|
|
|
className="absolute top-1/2 right-1/3 w-32 lg:w-40 h-32 lg:h-40 bg-indigo-300/10 rounded-full blur-2xl animate-login-float" |
|
|
|
style={{ animationDelay: '4s', animationDuration: '10s' }} |
|
|
|
/> |
|
|
|
|
|
|
|
{/* Content */} |
|
|
|
<div className="relative z-10"> |
|
|
|
<div className="flex items-center gap-3 mb-2"> |
|
|
|
<div className="w-10 h-10 rounded-xl bg-white/20 backdrop-blur-sm flex items-center justify-center"> |
|
|
|
<Cpu className="h-5 w-5 text-white" /> |
|
|
|
</div> |
|
|
|
<span className="text-xl font-bold text-white tracking-wider font-display"> |
|
|
|
BASE |
|
|
|
</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div className="relative z-10 max-w-lg"> |
|
|
|
<h1 |
|
|
|
className={`text-3xl lg:text-5xl font-bold text-white leading-tight mb-4 lg:mb-6 font-display tracking-wide transition-all duration-700 delay-200 ease-out ${ |
|
|
|
mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-6' |
|
|
|
}`}
|
|
|
|
> |
|
|
|
构建下一代 |
|
|
|
<br /> |
|
|
|
<span className="text-sky-200">智能管理平台</span> |
|
|
|
</h1> |
|
|
|
<p className="text-gray-500 font-body">管理面板登录</p> |
|
|
|
<p |
|
|
|
className={`text-base lg:text-lg text-blue-100/80 leading-relaxed mb-6 lg:mb-10 font-body transition-all duration-700 delay-300 ease-out ${ |
|
|
|
mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-6' |
|
|
|
}`}
|
|
|
|
> |
|
|
|
安全、高效、可扩展的全栈开发脚手架,为您的业务赋能 |
|
|
|
</p> |
|
|
|
|
|
|
|
{/* Feature highlights */} |
|
|
|
<div |
|
|
|
className={`space-y-3 lg:space-y-4 transition-all duration-700 delay-500 ease-out ${ |
|
|
|
mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-6' |
|
|
|
}`}
|
|
|
|
> |
|
|
|
{[ |
|
|
|
{ icon: Shield, text: 'JWT 认证与权限管理' }, |
|
|
|
{ icon: BarChart3, text: '实时数据仪表盘' }, |
|
|
|
{ icon: Cpu, text: 'Go + React 全栈架构' }, |
|
|
|
].map(({ icon: Icon, text }) => ( |
|
|
|
<div key={text} className="flex items-center gap-3"> |
|
|
|
<div className="w-8 h-8 rounded-lg bg-white/10 backdrop-blur-sm flex items-center justify-center flex-shrink-0"> |
|
|
|
<Icon className="h-4 w-4 text-sky-200" /> |
|
|
|
</div> |
|
|
|
<span className="text-blue-100/90 text-sm font-body">{text}</span> |
|
|
|
</div> |
|
|
|
))} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* Login form */} |
|
|
|
<Card className="backdrop-blur-xl bg-gray-900/90 border-gray-800"> |
|
|
|
<form onSubmit={handleSubmit} className="space-y-6"> |
|
|
|
<div className="space-y-4"> |
|
|
|
{/* Bottom */} |
|
|
|
<div className="relative z-10"> |
|
|
|
<p className="text-blue-200/50 text-sm font-body"> |
|
|
|
© 2026 Base System. All rights reserved. |
|
|
|
</p> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* Right Panel - Login Form */} |
|
|
|
<div className="flex-1 flex items-center justify-center px-4 py-8 sm:px-8 md:px-6 lg:px-8 relative overflow-hidden"> |
|
|
|
{/* Subtle background effects for right panel */} |
|
|
|
<div className="absolute inset-0"> |
|
|
|
<div className="absolute top-0 left-0 w-full h-full bg-gradient-to-br from-indigo-500/[0.03] via-transparent to-sky-500/[0.03]" /> |
|
|
|
<div |
|
|
|
className="absolute inset-0 opacity-[0.02]" |
|
|
|
style={{ |
|
|
|
backgroundImage: |
|
|
|
'linear-gradient(rgba(99,102,241,0.3) 1px, transparent 1px), linear-gradient(90deg, rgba(99,102,241,0.3) 1px, transparent 1px)', |
|
|
|
backgroundSize: '48px 48px', |
|
|
|
}} |
|
|
|
/> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div |
|
|
|
className={`relative z-10 w-full max-w-[420px] transition-all duration-700 delay-100 ease-out ${ |
|
|
|
mounted ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8' |
|
|
|
}`}
|
|
|
|
> |
|
|
|
{/* Mobile logo - only visible below md */} |
|
|
|
<div className="md:hidden text-center mb-6"> |
|
|
|
<div className="inline-flex items-center gap-3"> |
|
|
|
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-indigo-500 to-sky-500 flex items-center justify-center"> |
|
|
|
<Cpu className="h-5 w-5 text-white" /> |
|
|
|
</div> |
|
|
|
<span className="text-xl font-bold text-white tracking-wider font-display"> |
|
|
|
BASE |
|
|
|
</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* Form header */} |
|
|
|
<div className="mb-6"> |
|
|
|
<h2 className="text-xl sm:text-2xl font-bold text-white font-display tracking-wide mb-1.5"> |
|
|
|
欢迎回来 |
|
|
|
</h2> |
|
|
|
<p className="text-gray-400 font-body text-sm"> |
|
|
|
请输入您的账号信息登录系统 |
|
|
|
</p> |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* Login card */} |
|
|
|
<div className="rounded-2xl border border-white/[0.06] bg-white/[0.03] backdrop-blur-xl p-5 sm:p-6 lg:p-8 shadow-2xl shadow-black/20"> |
|
|
|
<form onSubmit={handleSubmit} className="space-y-4"> |
|
|
|
<Input |
|
|
|
type="email" |
|
|
|
label="邮箱地址" |
|
|
|
placeholder="user@example.com" |
|
|
|
value={email} |
|
|
|
onChange={(e) => setEmail(e.target.value)} |
|
|
|
type="text" |
|
|
|
label="手机号 / 用户名" |
|
|
|
placeholder="请输入手机号或用户名" |
|
|
|
value={account} |
|
|
|
onChange={(e) => setAccount(e.target.value)} |
|
|
|
required |
|
|
|
leftIcon={<Mail className="h-4 w-4" />} |
|
|
|
leftIcon={<UserRound className="h-4 w-4" />} |
|
|
|
disabled={isLoading} |
|
|
|
/> |
|
|
|
|
|
|
|
<Input |
|
|
|
type="password" |
|
|
|
type={showPassword ? 'text' : 'password'} |
|
|
|
label="密码" |
|
|
|
placeholder="••••••••" |
|
|
|
placeholder="••••••••" |
|
|
|
value={password} |
|
|
|
onChange={(e) => setPassword(e.target.value)} |
|
|
|
required |
|
|
|
leftIcon={<Lock className="h-4 w-4" />} |
|
|
|
rightIcon={ |
|
|
|
<button |
|
|
|
type="button" |
|
|
|
onClick={() => setShowPassword(!showPassword)} |
|
|
|
className="cursor-pointer text-gray-500 hover:text-gray-300 transition-colors duration-200" |
|
|
|
tabIndex={-1} |
|
|
|
aria-label={showPassword ? '隐藏密码' : '显示密码'} |
|
|
|
> |
|
|
|
{showPassword ? ( |
|
|
|
<EyeOff className="h-4 w-4" /> |
|
|
|
) : ( |
|
|
|
<Eye className="h-4 w-4" /> |
|
|
|
)} |
|
|
|
</button> |
|
|
|
} |
|
|
|
disabled={isLoading} |
|
|
|
/> |
|
|
|
</div> |
|
|
|
|
|
|
|
{error && ( |
|
|
|
<div className="flex items-center gap-2 p-3 rounded-lg bg-red-500/10 border border-red-500/30 text-red-400 text-sm font-body"> |
|
|
|
<div |
|
|
|
className="flex items-center gap-2.5 p-3 rounded-xl bg-red-500/10 border border-red-500/20 text-red-400 text-sm font-body animate-scale-in" |
|
|
|
role="alert" |
|
|
|
> |
|
|
|
<AlertCircle className="h-4 w-4 flex-shrink-0" /> |
|
|
|
<span>{error}</span> |
|
|
|
</div> |
|
|
|
@ -97,39 +246,97 @@ export function LoginPage() { |
|
|
|
type="submit" |
|
|
|
variant="primary" |
|
|
|
size="lg" |
|
|
|
className="w-full" |
|
|
|
className="w-full !bg-gradient-to-r !from-indigo-500 !to-sky-500 hover:!from-indigo-400 hover:!to-sky-400 !border-indigo-500/30 !shadow-lg !shadow-indigo-500/20 cursor-pointer group" |
|
|
|
isLoading={isLoading} |
|
|
|
> |
|
|
|
登录 |
|
|
|
<span>登录</span> |
|
|
|
{!isLoading && ( |
|
|
|
<ArrowRight className="h-4 w-4 transition-transform duration-200 group-hover:translate-x-0.5" /> |
|
|
|
)} |
|
|
|
</Button> |
|
|
|
</form> |
|
|
|
|
|
|
|
<div className="text-center"> |
|
|
|
<p className="text-sm text-gray-500 font-body"> |
|
|
|
{/* Divider */} |
|
|
|
<div className="relative my-5"> |
|
|
|
<div className="absolute inset-0 flex items-center"> |
|
|
|
<div className="w-full border-t border-white/[0.06]" /> |
|
|
|
</div> |
|
|
|
<div className="relative flex justify-center text-xs"> |
|
|
|
<span className="px-3 bg-[oklch(0.12_0.01_240)] text-gray-500 font-body"> |
|
|
|
SSO 单点登录 |
|
|
|
</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* SSO Buttons */} |
|
|
|
<div className="flex flex-col sm:flex-row gap-2.5"> |
|
|
|
<button |
|
|
|
type="button" |
|
|
|
className="flex items-center justify-center gap-2.5 h-11 flex-1 rounded-xl border border-white/[0.08] bg-white/[0.03] hover:bg-white/[0.07] transition-colors duration-200 cursor-pointer group" |
|
|
|
aria-label="使用 Google 登录" |
|
|
|
> |
|
|
|
<svg className="h-5 w-5 flex-shrink-0" viewBox="0 0 24 24"> |
|
|
|
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" fill="#4285F4" /> |
|
|
|
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853" /> |
|
|
|
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05" /> |
|
|
|
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335" /> |
|
|
|
</svg> |
|
|
|
<span className="text-sm text-gray-300 font-body group-hover:text-white transition-colors duration-200">Google</span> |
|
|
|
</button> |
|
|
|
|
|
|
|
<button |
|
|
|
type="button" |
|
|
|
className="flex items-center justify-center gap-2.5 h-11 flex-1 rounded-xl border border-white/[0.08] bg-white/[0.03] hover:bg-white/[0.07] transition-colors duration-200 cursor-pointer group" |
|
|
|
aria-label="使用 GitHub 登录" |
|
|
|
> |
|
|
|
<svg className="h-5 w-5 flex-shrink-0 text-gray-300 group-hover:text-white transition-colors duration-200" fill="currentColor" viewBox="0 0 24 24"> |
|
|
|
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2z" /> |
|
|
|
</svg> |
|
|
|
<span className="text-sm text-gray-300 font-body group-hover:text-white transition-colors duration-200">GitHub</span> |
|
|
|
</button> |
|
|
|
|
|
|
|
<button |
|
|
|
type="button" |
|
|
|
onClick={handleSSOLogin} |
|
|
|
disabled={ssoLoading} |
|
|
|
className="flex items-center justify-center gap-2.5 h-11 flex-1 rounded-xl border border-white/[0.08] bg-white/[0.03] hover:bg-white/[0.07] transition-colors duration-200 cursor-pointer group disabled:opacity-50 disabled:cursor-not-allowed" |
|
|
|
aria-label="使用 XHY.GROUP 登录" |
|
|
|
> |
|
|
|
<svg className="h-5 w-5 flex-shrink-0" viewBox="0 0 32 32" fill="none"> |
|
|
|
<defs> |
|
|
|
<linearGradient id="xhy-grad" x1="0" y1="0" x2="32" y2="32" gradientUnits="userSpaceOnUse"> |
|
|
|
<stop offset="0%" stopColor="#6366F1" /> |
|
|
|
<stop offset="100%" stopColor="#06B6D4" /> |
|
|
|
</linearGradient> |
|
|
|
</defs> |
|
|
|
<rect width="32" height="32" rx="7" fill="url(#xhy-grad)" /> |
|
|
|
<path d="M8 10L13.5 16L8 22H11.5L15.5 17.2L19.5 22H23L17.5 16L23 10H19.5L15.5 14.8L11.5 10H8Z" fill="white" fillOpacity="0.95" /> |
|
|
|
<rect x="6" y="25" width="20" height="1.5" rx="0.75" fill="white" fillOpacity="0.4" /> |
|
|
|
</svg> |
|
|
|
<span className="text-sm text-gray-300 font-body group-hover:text-white transition-colors duration-200">XHY.GROUP</span> |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* Register link */} |
|
|
|
<p className="text-center text-sm text-gray-400 font-body mt-5"> |
|
|
|
还没有账号?{' '} |
|
|
|
<button |
|
|
|
type="button" |
|
|
|
className="text-sky-400 hover:text-sky-300 transition-colors font-medium" |
|
|
|
className="text-indigo-400 hover:text-indigo-300 transition-colors duration-200 font-medium cursor-pointer" |
|
|
|
> |
|
|
|
注册账号 |
|
|
|
创建新账号 |
|
|
|
</button> |
|
|
|
</p> |
|
|
|
</div> |
|
|
|
</form> |
|
|
|
</Card> |
|
|
|
|
|
|
|
{/* Footer info */} |
|
|
|
<div className="mt-8 text-center"> |
|
|
|
{/* Mobile footer */} |
|
|
|
<div className="md:hidden mt-6 text-center"> |
|
|
|
<p className="text-xs text-gray-600 font-body"> |
|
|
|
© 2026 Base System. All rights reserved. |
|
|
|
© 2026 Base System. All rights reserved. |
|
|
|
</p> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
{/* Corner decorations */} |
|
|
|
<div className="absolute top-0 left-0 w-24 h-24 border-l-2 border-t-2 border-sky-500/30 rounded-tl-3xl" /> |
|
|
|
<div className="absolute top-0 right-0 w-24 h-24 border-r-2 border-t-2 border-purple-500/30 rounded-tr-3xl" /> |
|
|
|
<div className="absolute bottom-0 left-0 w-24 h-24 border-l-2 border-b-2 border-purple-500/30 rounded-bl-3xl" /> |
|
|
|
<div className="absolute bottom-0 right-0 w-24 h-24 border-r-2 border-b-2 border-sky-500/30 rounded-br-3xl" /> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
) |
|
|
|
} |
|
|
|
|