feat: init ai-platform - 源码 + 配置

This commit is contained in:
2026-08-25 16:27:43 +08:00
commit 83210a606a
122 changed files with 27401 additions and 0 deletions
@@ -0,0 +1,234 @@
import { useEffect, useRef } from 'react';
import type { Theme } from '../context/ThemeContext';
interface Orb {
x: number;
y: number;
r: number;
vx: number;
vy: number;
color: string;
glowAlpha: number;
pulsePhase: number;
pulseSpeed: number;
}
export default function StarBackground({ theme = 'dark' }: { theme?: Theme }) {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const resize = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
resize();
window.addEventListener('resize', resize);
// 生成星星
const stars = Array.from({ length: 220 }, () => ({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
r: Math.random() * 1.2 + 0.2,
opacity: Math.random() * 0.6 + 0.2,
speed: Math.random() * 0.015 + 0.005,
phase: Math.random() * Math.PI * 2,
}));
// 深空云雾粒子
const nebula = Array.from({ length: 6 }, (_, i) => ({
x: (i / 6) * canvas.width + Math.random() * 200 - 100,
y: Math.random() * canvas.height,
r: Math.random() * 180 + 120,
color: ['rgba(0,80,160,', 'rgba(40,0,120,', 'rgba(0,60,100,', 'rgba(20,0,80,', 'rgba(0,100,120,', 'rgba(60,0,100,'][i],
}));
// 大小球体 — 飞来飞去的光球
const darkOrbColors = [
'rgba(220,240,255,',
'rgba(200,230,255,',
'rgba(255,255,255,',
'rgba(230,240,255,',
'rgba(0,180,255,',
'rgba(120,100,255,',
'rgba(0,229,153,',
'rgba(0,212,255,',
];
const lightOrbColors = [
'rgba(160,185,230,',
'rgba(180,200,240,',
'rgba(200,215,245,',
'rgba(140,170,220,',
'rgba(100,140,210,',
'rgba(170,190,235,',
'rgba(120,160,220,',
'rgba(190,210,245,',
];
const orbColors = theme === 'dark' ? darkOrbColors : lightOrbColors;
const orbGlowBase = theme === 'dark' ? 0.025 : 0.07;
const orbs: Orb[] = Array.from({ length: 64 }, (_, i) => ({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
r: i < 24 ? (Math.random() * 30 + 35) : (Math.random() * 12 + 10),
vx: (Math.random() - 0.5) * 2.2,
vy: (Math.random() - 0.5) * 1.6,
color: orbColors[i % orbColors.length],
glowAlpha: Math.random() * orbGlowBase + orbGlowBase * 0.4,
pulsePhase: Math.random() * Math.PI * 2,
pulseSpeed: Math.random() * 0.012 + 0.006,
}));
let frame = 0;
let animId: number;
const draw = () => {
frame++;
const W = canvas.width;
const H = canvas.height;
ctx.clearRect(0, 0, W, H);
// 背景渐变
const grad = ctx.createRadialGradient(W * 0.5, H * 0.5, 0, W * 0.5, H * 0.5, W * 0.7);
if (theme === 'dark') {
grad.addColorStop(0, 'rgba(0,15,40,1)');
grad.addColorStop(0.5, 'rgba(0,8,25,1)');
grad.addColorStop(1, 'rgba(0,4,15,1)');
} else {
grad.addColorStop(0, 'rgba(248,250,255,1)');
grad.addColorStop(0.5, 'rgba(243,246,255,1)');
grad.addColorStop(1, 'rgba(236,241,255,1)');
}
ctx.fillStyle = grad;
ctx.fillRect(0, 0, W, H);
// 星云雾
nebula.forEach(n => {
const g = ctx.createRadialGradient(n.x, n.y, 0, n.x, n.y, n.r);
const nebulaAlpha = theme === 'dark' ? '0.06' : '0.04';
g.addColorStop(0, n.color + nebulaAlpha + ')');
g.addColorStop(1, n.color + '0)');
ctx.fillStyle = g;
ctx.beginPath();
ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2);
ctx.fill();
});
// ★ 飞行球体
orbs.forEach(orb => {
// 更新位置
orb.x += orb.vx;
orb.y += orb.vy;
// 边界反弹(加上球体半径,让球不会从中心出界)
if (orb.x < -orb.r) orb.x = W + orb.r;
if (orb.x > W + orb.r) orb.x = -orb.r;
if (orb.y < -orb.r) orb.y = H + orb.r;
if (orb.y > H + orb.r) orb.y = -orb.r;
// 脉冲效果
const pulse = 0.7 + 0.3 * Math.sin(frame * orb.pulseSpeed + orb.pulsePhase);
const alpha = orb.glowAlpha * pulse;
// 外层大光晕
const outerR = orb.r * 1.5;
const outerGrad = ctx.createRadialGradient(orb.x, orb.y, 0, orb.x, orb.y, outerR);
outerGrad.addColorStop(0, orb.color + (alpha * 0.9).toFixed(3) + ')');
outerGrad.addColorStop(0.5, orb.color + (alpha * 0.3).toFixed(3) + ')');
outerGrad.addColorStop(1, orb.color + '0)');
ctx.fillStyle = outerGrad;
ctx.beginPath();
ctx.arc(orb.x, orb.y, outerR, 0, Math.PI * 2);
ctx.fill();
// 内层球体
const innerGrad = ctx.createRadialGradient(
orb.x - orb.r * 0.2, orb.y - orb.r * 0.2, 0,
orb.x, orb.y, orb.r
);
innerGrad.addColorStop(0, orb.color + (alpha * 2.0).toFixed(3) + ')');
innerGrad.addColorStop(0.6, orb.color + (alpha * 0.8).toFixed(3) + ')');
innerGrad.addColorStop(1, orb.color + '0)');
ctx.fillStyle = innerGrad;
ctx.beginPath();
ctx.arc(orb.x, orb.y, orb.r, 0, Math.PI * 2);
ctx.fill();
// 高光点
const hlR = orb.r * 0.25;
const hlGrad = ctx.createRadialGradient(
orb.x - orb.r * 0.25, orb.y - orb.r * 0.25, 0,
orb.x - orb.r * 0.25, orb.y - orb.r * 0.25, hlR
);
hlGrad.addColorStop(0, 'rgba(255,255,255,' + (alpha * 4).toFixed(3) + ')');
hlGrad.addColorStop(1, 'rgba(255,255,255,0)');
ctx.fillStyle = hlGrad;
ctx.beginPath();
ctx.arc(orb.x - orb.r * 0.25, orb.y - orb.r * 0.25, hlR, 0, Math.PI * 2);
ctx.fill();
});
// 星星
stars.forEach(s => {
const twinkle = 0.3 + 0.7 * (0.5 + 0.5 * Math.sin(frame * s.speed + s.phase));
ctx.beginPath();
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
ctx.fillStyle = theme === 'dark'
? `rgba(180,220,255,${s.opacity * twinkle})`
: `rgba(100,140,200,${s.opacity * twinkle * 0.5})`;
ctx.fill();
// 亮星加光晕
if (s.r > 1.0) {
const halo = ctx.createRadialGradient(s.x, s.y, 0, s.x, s.y, s.r * 4);
halo.addColorStop(0, theme === 'dark'
? `rgba(100,200,255,${0.2 * twinkle})`
: `rgba(80,130,220,${0.08 * twinkle})`);
halo.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = halo;
ctx.beginPath();
ctx.arc(s.x, s.y, s.r * 4, 0, Math.PI * 2);
ctx.fill();
}
});
// 水平光线
if (frame % 300 === 0) {
const y = Math.random() * H;
const lineGrad = ctx.createLinearGradient(0, y, W, y);
lineGrad.addColorStop(0, 'rgba(0,212,255,0)');
lineGrad.addColorStop(0.5, 'rgba(0,212,255,0.06)');
lineGrad.addColorStop(1, 'rgba(0,212,255,0)');
ctx.strokeStyle = lineGrad;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(W, y);
ctx.stroke();
}
animId = requestAnimationFrame(draw);
};
draw();
return () => {
cancelAnimationFrame(animId);
window.removeEventListener('resize', resize);
};
}, [theme]);
return (
<canvas
ref={canvasRef}
style={{
position: 'fixed',
top: 0, left: 0,
width: '100%', height: '100%',
zIndex: 0,
pointerEvents: 'none',
}}
/>
);
}