b19c42989d
- 搬入 framer-motion,复刻参考项目登录体验 - AICoreEffect:点芯片启动神经链接进度,S-core 数据流点亮,100% 解锁 - AIBanner:青色数据流 + 紫色神经网络背景 - LoginCharacters:四只卡通角色,眼睛跟随鼠标,输密码时遮眼/偷看 - 表单去掉验证码,接现有 login api;账号密码 + 密码显示切换 - 品牌名换 sundynix admin,登录按钮用青蓝渐变 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
169 lines
6.3 KiB
TypeScript
169 lines
6.3 KiB
TypeScript
import { motion } from 'framer-motion'
|
|
|
|
/** 登录背景:左青色数据流 + 右紫色神经网络 + 中央连线(呼应 logo)。搬自参考项目。 */
|
|
export default function AIBanner() {
|
|
const dataParticles = Array.from({ length: 100 }).map((_, i) => ({
|
|
id: `dp-${i}`,
|
|
y: 10 + Math.random() * 80,
|
|
size: Math.random() > 0.8 ? 6 : Math.random() * 3 + 1.5,
|
|
isSquare: Math.random() > 0.6,
|
|
duration: Math.random() * 2 + 1.5,
|
|
delay: Math.random() * 3,
|
|
opacity: Math.random() * 0.7 + 0.3,
|
|
}))
|
|
|
|
const neuralNodes = [
|
|
{ x: 10, y: 50 }, { x: 30, y: 25 }, { x: 35, y: 75 },
|
|
{ x: 55, y: 45 }, { x: 75, y: 15 }, { x: 70, y: 85 },
|
|
{ x: 90, y: 55 }, { x: 80, y: 40 }, { x: 50, y: 90 },
|
|
{ x: 20, y: 95 }, { x: 95, y: 20 }, { x: 45, y: 10 },
|
|
{ x: 85, y: 75 }, { x: 15, y: 20 },
|
|
]
|
|
const neuralLines = [
|
|
[0, 1], [0, 2], [1, 3], [2, 3], [1, 4], [3, 4],
|
|
[3, 6], [2, 5], [3, 5], [5, 6], [4, 7], [6, 7],
|
|
[2, 8], [5, 8], [0, 9], [2, 9], [4, 10], [7, 10],
|
|
[1, 11], [4, 11], [6, 12], [5, 12], [0, 13], [1, 13],
|
|
]
|
|
|
|
return (
|
|
<div className="absolute inset-0 h-full w-full overflow-hidden bg-[#080d17]">
|
|
<div className="pointer-events-none absolute left-[15%] top-1/2 h-[800px] w-[800px] -translate-y-1/2 rounded-full bg-cyan-600/15 blur-[150px] mix-blend-screen" />
|
|
<div className="pointer-events-none absolute right-[15%] top-1/2 h-[800px] w-[800px] -translate-y-1/2 rounded-full bg-fuchsia-600/15 blur-[150px] mix-blend-screen" />
|
|
|
|
<div
|
|
className="pointer-events-none absolute inset-y-0 left-0 w-[65%]"
|
|
style={{
|
|
maskImage:
|
|
'linear-gradient(to right, black 0%, black 60%, transparent 100%)',
|
|
WebkitMaskImage:
|
|
'linear-gradient(to right, black 0%, black 60%, transparent 100%)',
|
|
}}
|
|
>
|
|
{dataParticles.map((p) => (
|
|
<motion.div
|
|
key={p.id}
|
|
className={`absolute bg-cyan-400 ${p.isSquare ? 'rounded-sm' : 'rounded-full'}`}
|
|
style={{
|
|
width: p.size,
|
|
height: p.size,
|
|
top: `${p.y}%`,
|
|
boxShadow: p.isSquare
|
|
? '0 0 12px 3px rgba(6,182,212,0.9)'
|
|
: '0 0 8px 2px rgba(6,182,212,0.6)',
|
|
}}
|
|
initial={{ x: '-10vw', opacity: 0 }}
|
|
animate={{ x: '75vw', opacity: [0, p.opacity, p.opacity, 0] }}
|
|
transition={{
|
|
duration: p.duration * 3.5,
|
|
repeat: Infinity,
|
|
delay: p.delay,
|
|
ease: 'linear',
|
|
}}
|
|
/>
|
|
))}
|
|
<div className="absolute left-0 top-1/2 flex h-[80%] w-full -translate-y-1/2 flex-col justify-between opacity-70">
|
|
{Array.from({ length: 20 }).map((_, i) => (
|
|
<motion.div
|
|
key={`streak-${i}`}
|
|
className="h-[2px] bg-gradient-to-r from-transparent via-cyan-400 to-transparent"
|
|
style={{
|
|
width: `${Math.random() * 40 + 20}%`,
|
|
filter: 'drop-shadow(0 0 5px rgba(6,182,212,0.8))',
|
|
}}
|
|
initial={{ x: '-50%', opacity: 0 }}
|
|
animate={{ x: '150%', opacity: [0, 1, 1, 0] }}
|
|
transition={{
|
|
duration: 7 + Math.random() * 6,
|
|
repeat: Infinity,
|
|
delay: Math.random() * 4,
|
|
ease: 'linear',
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pointer-events-none absolute inset-y-0 right-0 flex w-[45%] items-center justify-end pr-[5%]">
|
|
<svg
|
|
viewBox="0 0 100 100"
|
|
className="h-[80%] w-full max-w-[500px] overflow-visible"
|
|
style={{ filter: 'drop-shadow(0 0 10px rgba(217,70,239,0.6))' }}
|
|
>
|
|
{neuralLines.map((line, i) => {
|
|
const n1 = neuralNodes[line[0]]
|
|
const n2 = neuralNodes[line[1]]
|
|
return (
|
|
<motion.line
|
|
key={`line-${i}`}
|
|
x1={n1.x}
|
|
y1={n1.y}
|
|
x2={n2.x}
|
|
y2={n2.y}
|
|
stroke="rgba(217,70,239,0.5)"
|
|
strokeWidth="0.5"
|
|
initial={{ pathLength: 0, opacity: 0 }}
|
|
animate={{ pathLength: [0, 1, 1, 0], opacity: [0, 1, 1, 0] }}
|
|
transition={{
|
|
duration: 10,
|
|
repeat: Infinity,
|
|
ease: 'easeInOut',
|
|
delay: i * 0.3,
|
|
}}
|
|
/>
|
|
)
|
|
})}
|
|
{neuralNodes.map((node, i) => (
|
|
<motion.circle
|
|
key={`node-${i}`}
|
|
cx={node.x}
|
|
cy={node.y}
|
|
r="1.5"
|
|
fill="#d946ef"
|
|
animate={{ r: [1.5, 2.5, 1.5], opacity: [0.4, 0.9, 0.4] }}
|
|
transition={{ duration: 4.5, repeat: Infinity, delay: i * 0.4 }}
|
|
/>
|
|
))}
|
|
</svg>
|
|
</div>
|
|
|
|
<div className="pointer-events-none absolute left-0 top-1/2 flex h-[600px] w-full -translate-y-1/2 items-center justify-center opacity-60 mix-blend-screen">
|
|
<svg
|
|
viewBox="0 0 200 100"
|
|
preserveAspectRatio="none"
|
|
className="h-full w-full overflow-visible"
|
|
>
|
|
<defs>
|
|
<linearGradient id="elegant-grad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
<stop offset="0%" stopColor="#06b6d4" stopOpacity="0" />
|
|
<stop offset="25%" stopColor="#06b6d4" stopOpacity="0.8" />
|
|
<stop offset="50%" stopColor="#8b5cf6" stopOpacity="0.5" />
|
|
<stop offset="75%" stopColor="#d946ef" stopOpacity="0.8" />
|
|
<stop offset="100%" stopColor="#d946ef" stopOpacity="0" />
|
|
</linearGradient>
|
|
</defs>
|
|
{Array.from({ length: 4 }).map((_, i) => (
|
|
<motion.path
|
|
key={`line-${i}`}
|
|
d={`M -20 ${30 + i * 10} C 60 ${10 + i * 5}, 140 ${90 - i * 5}, 220 ${70 - i * 10}`}
|
|
fill="none"
|
|
stroke="url(#elegant-grad)"
|
|
strokeWidth={0.2 + i * 0.05}
|
|
animate={{
|
|
strokeDasharray: ['0, 400', '400, 0'],
|
|
opacity: [0.3, 0.7, 0.3],
|
|
}}
|
|
transition={{
|
|
duration: 16 + i * 4,
|
|
repeat: Infinity,
|
|
ease: 'easeInOut',
|
|
delay: i * 2,
|
|
}}
|
|
/>
|
|
))}
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|