@@ -259,5 +264,153 @@ async function handleEmailRegister(e) {
// 初始化图形验证码(默认手机选项卡)
fetchCaptcha('phone');
+
+// ========== 云团动态背景动画 ==========
+const canvas = document.getElementById('cloudCanvas');
+const ctx = canvas.getContext('2d');
+let width, height;
+let clouds = [];
+let mouse = { x: -1000, y: -1000 };
+
+function resize() {
+ width = canvas.width = window.innerWidth;
+ height = canvas.height = window.innerHeight;
+}
+window.addEventListener('resize', resize);
+resize();
+
+window.addEventListener('mousemove', (e) => {
+ mouse.x = e.clientX;
+ mouse.y = e.clientY;
+});
+window.addEventListener('mouseleave', () => {
+ mouse.x = -1000;
+ mouse.y = -1000;
+});
+
+// 预渲染柔和的云朵粒子,提升性能和真实感
+const puffCanvas = document.createElement('canvas');
+puffCanvas.width = 200;
+puffCanvas.height = 200;
+const pctx = puffCanvas.getContext('2d');
+const grad = pctx.createRadialGradient(100, 100, 0, 100, 100, 100);
+grad.addColorStop(0, 'rgba(255, 255, 255, 1)');
+grad.addColorStop(0.2, 'rgba(255, 255, 255, 0.8)');
+grad.addColorStop(0.5, 'rgba(255, 255, 255, 0.3)');
+grad.addColorStop(1, 'rgba(255, 255, 255, 0)');
+pctx.fillStyle = grad;
+pctx.beginPath();
+pctx.arc(100, 100, 100, 0, Math.PI * 2);
+pctx.fill();
+
+class Cloud {
+ constructor() {
+ this.reset(true);
+ }
+
+ reset(initial = false) {
+ this.size = Math.random() * 150 + 100; // 更大的云团
+ this.x = initial ? Math.random() * width : -this.size * 2;
+ this.y = Math.random() * height * 0.8; // 主要分布在上方
+ this.speedX = Math.random() * 0.5 + 0.2;
+ this.speedY = (Math.random() - 0.5) * 0.1;
+ this.baseOpacity = Math.random() * 0.5 + 0.3;
+ this.opacity = this.baseOpacity;
+ this.scattered = false;
+
+ // 生成云朵形状(由多个柔和粒子组成)
+ this.parts = [];
+ const numParts = Math.floor(Math.random() * 6) + 6;
+ for (let i = 0; i < numParts; i++) {
+ // 云朵底部较平,顶部不规则
+ const nx = (Math.random() - 0.5) * this.size * 1.5;
+ const ny = (Math.random() * 0.5 - 0.8) * this.size * 0.5;
+ this.parts.push({
+ offsetX: nx,
+ offsetY: ny,
+ radius: Math.random() * this.size * 0.5 + this.size * 0.4,
+ vx: 0,
+ vy: 0
+ });
+ }
+ }
+
+ update() {
+ if (!this.scattered) {
+ this.x += this.speedX;
+ this.y += this.speedY;
+
+ // 鼠标交互(散开效果)
+ const dx = this.x - mouse.x;
+ const dy = this.y - mouse.y;
+ const dist = Math.sqrt(dx * dx + dy * dy);
+
+ // 鼠标靠近时触发散开
+ if (dist < 250) {
+ this.scattered = true;
+ // 给每个粒子一个远离鼠标的速度
+ for (let part of this.parts) {
+ const pdx = (this.x + part.offsetX) - mouse.x;
+ const pdy = (this.y + part.offsetY) - mouse.y;
+ const pdist = Math.sqrt(pdx * pdx + pdy * pdy) || 1;
+ const force = Math.max(0, 300 - pdist) / 300;
+ part.vx = (pdx / pdist) * force * (Math.random() * 6 + 2);
+ part.vy = (pdy / pdist) * force * (Math.random() * 6 + 2);
+ }
+ }
+ } else {
+ // 散开状态:粒子移动并淡出
+ this.opacity -= 0.015;
+ for (let part of this.parts) {
+ part.offsetX += part.vx;
+ part.offsetY += part.vy;
+ part.radius += 0.5; // 散开时稍微膨胀
+ // 增加空气阻力
+ part.vx *= 0.95;
+ part.vy *= 0.95;
+ }
+ }
+
+ // 边界检测或完全淡出后重置
+ if (this.x > width + this.size * 2 || this.opacity <= 0 || this.y < -this.size * 2 || this.y > height + this.size * 2) {
+ this.reset();
+ }
+ }
+
+ draw() {
+ if (this.opacity <= 0) return;
+ ctx.save();
+ ctx.globalAlpha = Math.max(0, this.opacity);
+
+ for (let part of this.parts) {
+ const cx = this.x + part.offsetX;
+ const cy = this.y + part.offsetY;
+ const r = part.radius;
+
+ // 视野外不绘制
+ if (cx + r < 0 || cx - r > width || cy + r < 0 || cy - r > height) continue;
+
+ // 使用预渲染的柔和云朵粒子
+ ctx.drawImage(puffCanvas, cx - r, cy - r, r * 2, r * 2);
+ }
+
+ ctx.restore();
+ }
+}
+
+// 初始化云朵
+for (let i = 0; i < 12; i++) {
+ clouds.push(new Cloud());
+}
+
+function animate() {
+ ctx.clearRect(0, 0, width, height);
+ for (let cloud of clouds) {
+ cloud.update();
+ cloud.draw();
+ }
+ requestAnimationFrame(animate);
+}
+animate();
{% endblock %}
\ No newline at end of file