style: 添加预设样式

This commit is contained in:
Baobhan Sith
2025-05-27 21:36:06 +08:00
parent 9c109f4240
commit 4966d2e575
16 changed files with 1779 additions and 148 deletions
-123
View File
@@ -1,123 +0,0 @@
<style>
#matrix-rain-container {
width: 100%; height: 100%; background: #000;
overflow: hidden; position: relative;
}
#matrix-canvas { display: block; position: absolute; top: 0; left: 0; }
</style>
<div id="matrix-rain-container">
<canvas id="matrix-canvas"></canvas>
</div>
<script>
(function() {
const canvas = document.getElementById('matrix-canvas');
const layerElement = canvas.closest('.terminal-custom-html-layer'); // Or document.body if not in that specific env
if (!canvas || !layerElement) {
console.error("Canvas or layerElement not found.");
return;
}
const ctx = canvas.getContext('2d');
let animationFrameId;
const characters = 'アァカサタナハマヤャラワABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
const fontSize = 14;
let columns, drops = [];
// --- Slowdown Configuration ---
const slowdownFactor = 10; // 数字越大,动画越慢 (例如 2 = 半速, 3 = 1/3速度)
const originalAlpha = 0.1; // 原始的背景淡出alpha值
// 计算调整后的alpha值,以保持拖尾视觉效果
// 如果 slowdownFactor 为 1, adjustedAlpha 等于 originalAlpha
// (1 - newAlpha)^slowdownFactor = (1 - originalAlpha)^1
// 1 - newAlpha = (1 - originalAlpha)^(1/slowdownFactor)
// newAlpha = 1 - (1 - originalAlpha)^(1/slowdownFactor)
const adjustedAlpha = slowdownFactor <= 1 ? originalAlpha : 1 - Math.pow(1 - originalAlpha, 1 / slowdownFactor);
let frameCounter = 0; // 用于控制逻辑更新频率的计数器
// --- End Slowdown Configuration ---
function setupDimensionsAndColumns() {
canvas.width = layerElement.offsetWidth;
canvas.height = layerElement.offsetHeight;
columns = Math.floor(canvas.width / fontSize);
drops = [];
for (let x = 0; x < columns; x++) {
drops[x] = 1 + Math.floor(Math.random() * (canvas.height / fontSize));
}
frameCounter = 0; // 重置帧计数器
}
function drawMatrix() {
if (!ctx || canvas.width === 0 || canvas.height === 0) {
animationFrameId = requestAnimationFrame(drawMatrix);
return;
}
// 背景淡出效果:每一帧都执行,使用调整后的alpha值
ctx.fillStyle = `rgba(0, 0, 0, ${adjustedAlpha})`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
frameCounter++;
// 只有当帧计数器达到 slowdownFactor 时,才更新雨滴逻辑和绘制字符
if (frameCounter >= slowdownFactor) {
frameCounter = 0; // 重置计数器
ctx.fillStyle = '#0F0'; // 雨滴颜色
ctx.font = fontSize + 'px monospace';
for (let i = 0; i < drops.length; i++) {
const text = characters.charAt(Math.floor(Math.random() * characters.length));
ctx.fillText(text, i * fontSize, drops[i] * fontSize);
// 如果雨滴超出画布底部,并且随机条件满足,则重置雨滴到顶部
if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) {
drops[i] = 0;
}
drops[i]++; // 雨滴下落
}
}
animationFrameId = requestAnimationFrame(drawMatrix);
}
const debounce = (func, delay) => {
let t;
return (...a) => {
clearTimeout(t);
t = setTimeout(() => func.apply(this, a), delay);
};
};
const debouncedReinitialize = debounce(() => {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
setupDimensionsAndColumns();
if (columns > 0) {
drawMatrix();
}
}, 250);
// 确保 layerElement 存在才进行监听
if (layerElement) {
const resizeObserver = new ResizeObserver(entries => {
entries.forEach(entry => {
if (entry.target === layerElement) {
debouncedReinitialize();
}
});
});
resizeObserver.observe(layerElement);
} else {
// Fallback if layerElement is not specific, e.g. observe window or body
// window.addEventListener('resize', debouncedReinitialize); // Example fallback
console.warn("layerElement not found for ResizeObserver, resize handling might be limited.");
}
setupDimensionsAndColumns();
if (columns > 0) {
drawMatrix();
}
})();
</script>
+164
View File
@@ -0,0 +1,164 @@
<style>
#color-ribbons-container {
width: 100%; height: 100%;
background-color: #222733; /* 柔和的深蓝灰色 */
overflow: hidden; position: relative;
}
#ribbons-canvas { display: block; position: absolute; top: 0; left: 0; }
</style>
<div id="color-ribbons-container">
<canvas id="ribbons-canvas"></canvas>
</div>
<script>
(function() {
const canvas = document.getElementById('ribbons-canvas');
const layerElement = canvas.closest('.terminal-custom-html-layer') || canvas.parentElement;
if (!canvas || !layerElement) return;
const ctx = canvas.getContext('2d');
let width, height, animationFrameId;
const ribbons = [];
const numRibbons = 5; // 彩带数量
let time = 0;
// HSL颜色辅助函数
function hslToRgb(h, s, l) {
let r, g, b;
if (s == 0) {
r = g = b = l; // achromatic
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
class Ribbon {
constructor() {
this.numPoints = 50 + Math.floor(Math.random() * 30); // 组成彩带路径的点数
this.points = [];
this.baseY = height * (0.2 + Math.random() * 0.6);
this.amplitude = height * (0.05 + Math.random() * 0.15);
this.frequency = 0.002 + Math.random() * 0.003;
this.phase = Math.random() * Math.PI * 2;
this.thickness = 20 + Math.random() * 30; // 彩带厚度
this.speed = 0.5 + Math.random() * 0.5; // 彩带横向移动速度
this.hueStart = Math.random(); // 0-1
this.hueChangeSpeed = 0.0005 + Math.random() * 0.001;
this.opacity = 0.1 + Math.random() * 0.2;
this.xOffset = -width * Math.random(); // 初始横向偏移,让彩带从不同位置进入
}
update(deltaTime) {
this.xOffset += this.speed * (deltaTime / (1000/60)); // Normalize speed to 60fps
if (this.xOffset > width + this.thickness * 5) { // 彩带完全移出右侧
this.xOffset = -this.thickness * 5 - Math.random() * width * 0.5; // 从左侧重新进入
this.baseY = height * (0.2 + Math.random() * 0.6); // 改变Y基线
this.hueStart = Math.random(); // 改变颜色
}
this.hueStart = (this.hueStart + this.hueChangeSpeed) % 1;
this.points = [];
for (let i = 0; i < this.numPoints; i++) {
const progress = i / (this.numPoints - 1); // 0 to 1
const x = this.xOffset + progress * (width + this.thickness * 10); // 彩带绘制范围比屏幕宽
const y = this.baseY + Math.sin(x * this.frequency + this.phase + time * 0.01) * this.amplitude +
Math.cos(x * this.frequency * 0.5 + this.phase * 0.7 + time * 0.005) * this.amplitude * 0.5;
this.points.push({ x, y });
}
}
draw() {
if (this.points.length < 2) return;
ctx.beginPath();
for (let i = 0; i < this.points.length -1; i++) {
const p1 = this.points[i];
const p2 = this.points[i+1];
// 计算垂直于路径的偏移点,形成彩带的上下边缘
const angle = Math.atan2(p2.y - p1.y, p2.x - p1.x);
const dxTop = Math.sin(angle) * this.thickness / 2;
const dyTop = -Math.cos(angle) * this.thickness / 2;
const dxBottom = -Math.sin(angle) * this.thickness / 2;
const dyBottom = Math.cos(angle) * this.thickness / 2;
if (i === 0) {
ctx.moveTo(p1.x + dxTop, p1.y + dyTop);
} else {
ctx.lineTo(p1.x + dxTop, p1.y + dyTop);
}
}
// 绘制下边缘 (反向)
for (let i = this.points.length - 1; i > 0; i--) {
const p1 = this.points[i];
// p2 is points[i-1] but we use p1's angle approx or recalculate
const p_prev = this.points[i-1];
const angle = Math.atan2(p1.y - p_prev.y, p1.x - p_prev.x); // Angle from prev to current
const dxBottom = -Math.sin(angle) * this.thickness / 2;
const dyBottom = Math.cos(angle) * this.thickness / 2;
ctx.lineTo(p1.x + dxBottom, p1.y + dyBottom);
}
ctx.closePath();
const [r,g,b] = hslToRgb(this.hueStart, 0.7, 0.6); // (饱和度0.7, 亮度0.6)
ctx.fillStyle = `rgba(${r},${g},${b}, ${this.opacity})`;
ctx.fill();
}
}
function init() {
width = layerElement.offsetWidth;
height = layerElement.offsetHeight;
canvas.width = width;
canvas.height = height;
ribbons.length = 0;
for (let i = 0; i < numRibbons; i++) {
ribbons.push(new Ribbon());
}
// 使用 'lighter' 或 'screen' 混合模式让彩带叠加时颜色混合更好
ctx.globalCompositeOperation = 'screen';
}
let lastTime = 0;
function animate(currentTime) {
const deltaTime = currentTime - lastTime;
lastTime = currentTime;
time++;
ctx.clearRect(0, 0, width, height); // 清晰的背景,或者用低alpha填充制造拖尾
ribbons.forEach(ribbon => {
ribbon.update(deltaTime || (1000/60)); // Provide a fallback for deltaTime on first frame
ribbon.draw();
});
animationFrameId = requestAnimationFrame(animate);
}
let resizeTimeout;
function onResize() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
if (animationFrameId) cancelAnimationFrame(animationFrameId);
init();
// animate(0); // Restart animation loop
}, 250);
}
init();
animate(0); // Start animation loop
window.addEventListener('resize', onResize);
canvas.cleanup = function() { if (animationFrameId) cancelAnimationFrame(animationFrameId); };
})();
</script>
+88
View File
@@ -0,0 +1,88 @@
<style>
#svg-lava-lamp-background {
width: 100%;
height: 100%;
background-color: #111827; /* 深灰蓝 */
overflow: hidden;
}
#svg-lava-lamp-background svg {
width: 100%;
height: 100%;
}
.lava-g {
filter: url(#gooeyFilter); /* 应用"粘稠"滤镜 */
}
.lava-blob {
fill: #ffcc00; /* 岩浆颜色 */
/* 动画由JS控制更灵活,但这里用CSS尝试 */
animation: moveBlob 15s infinite ease-in-out alternate;
}
.lava-blob:nth-child(1) { animation-duration: 18s; animation-delay: -2s; fill: #ff9900;}
.lava-blob:nth-child(2) { animation-duration: 12s; animation-delay: -5s; fill: #ff6600;}
.lava-blob:nth-child(3) { animation-duration: 20s; animation-delay: 0s; fill: #ff3300;}
.lava-blob:nth-child(4) { animation-duration: 16s; animation-delay: -8s; fill: #cc3300;}
.lava-blob:nth-child(5) { animation-duration: 14s; animation-delay: -12s;fill: #ffcc00;}
@keyframes moveBlob {
0% { transform: translate(0px, 0px) scale(1); }
25% { transform: translate(30px, -50px) scale(1.2); }
50% { transform: translate(-20px, 40px) scale(0.8); }
75% { transform: translate(10px, -30px) scale(1.1); }
100% { transform: translate(0px, 0px) scale(1); }
}
</style>
<div id="svg-lava-lamp-background">
<svg viewBox="0 0 300 400" preserveAspectRatio="xMidYMid slice" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="gooeyFilter">
<!-- 高斯模糊使边缘模糊 -->
<feGaussianBlur in="SourceGraphic" stdDeviation="12" result="blur" />
<!-- 颜色矩阵增加alpha对比度,使模糊的边缘更"粘稠"地融合 -->
<feColorMatrix in="blur" mode="matrix"
values="1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 25 -10"
result="gooey" />
<!-- 可选:将原始图形叠在上面,如果想要更清晰的中心 -->
<!-- <feComposite in="SourceGraphic" in2="gooey" operator="atop"/> -->
</filter>
</defs>
<!-- 将所有气泡放在一个应用了滤镜的组中 -->
<g class="lava-g">
<!-- 定义一些圆形作为气泡 -->
<circle class="lava-blob" cx="100" cy="300" r="40" />
<circle class="lava-blob" cx="200" cy="350" r="55" />
<circle class="lava-blob" cx="150" cy="100" r="30" />
<circle class="lava-blob" cx="50" cy="150" r="45" />
<circle class="lava-blob" cx="250" cy="200" r="60" />
<circle class="lava-blob" cx="120" cy="220" r="35" />
</g>
</svg>
</div>
<script>
(function() {
// 用JS可以更精细地控制每个blob的动画,例如使用随机目标点
const blobs = document.querySelectorAll('#svg-lava-lamp-background .lava-blob');
const svg = document.querySelector('#svg-lava-lamp-background svg');
if (!blobs.length || !svg) return;
const viewBoxWidth = svg.viewBox.baseVal.width;
const viewBoxHeight = svg.viewBox.baseVal.height;
blobs.forEach((blob, index) => {
// 可以用JS设置更复杂的动画,这里CSS动画已经够用
// 为了让CSS动画不完全同步,已经用了nth-child和animation-delay
// 如果想完全随机化CSS动画的参数,可以用JS来动态设置style属性
// 例如: blob.style.animationDuration = (10 + Math.random() * 10) + 's';
// blob.style.animationDelay = (-Math.random() * 5) + 's';
// 动态改变transform-origin让缩放更有趣
blob.style.transformOrigin = `${Math.random()*50+25}% ${Math.random()*50+25}%`;
});
})();
</script>
@@ -0,0 +1,92 @@
<style>
#css-3d-cubes-background {
width: 100%;
height: 100%;
background-color: #080010; /* 深紫色背景 */
overflow: hidden;
position: relative;
perspective: 1000px; /* 为3D效果设置透视 */
}
.scene {
width: 100%;
height: 100%;
position: absolute;
transform-style: preserve-3d;
}
.cube-wrapper {
position: absolute;
width: 100px; /* 调整立方体大小 */
height: 100px;
transform-style: preserve-3d;
/* 动画由JS控制或预设不同参数 */
}
.cube {
width: 100%;
height: 100%;
position: relative;
transform-style: preserve-3d;
transform: rotateX(0deg) rotateY(0deg); /* 初始旋转,会被动画覆盖 */
animation: spin 15s infinite linear;
}
.cube .face {
position: absolute;
width: 100px;
height: 100px;
border: 1px solid rgba(0, 255, 255, 0.7); /* 青色霓虹边框 */
background-color: rgba(0, 50, 80, 0.2); /* 半透明深蓝面 */
box-shadow: 0 0 10px rgba(0, 255, 255, 0.5), inset 0 0 8px rgba(0, 255, 255, 0.3);
}
/* 定位立方体的六个面 */
.cube .front { transform: rotateY( 0deg) translateZ(50px); }
.cube .back { transform: rotateY(180deg) translateZ(50px); }
.cube .right { transform: rotateY( 90deg) translateZ(50px); }
.cube .left { transform: rotateY(-90deg) translateZ(50px); }
.cube .top { transform: rotateX( 90deg) translateZ(50px); }
.cube .bottom { transform: rotateX(-90deg) translateZ(50px); }
@keyframes spin {
from { transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg); }
to { transform: rotateX(360deg) rotateY(360deg) rotateZ(360deg); }
}
/* 示例立方体位置和动画差异化 */
.cube-wrapper-1 { top: 20%; left: 30%; transform: scale(0.8) translateZ(-100px); }
.cube-wrapper-1 .cube { animation-duration: 18s; animation-delay: -2s; }
.cube-wrapper-2 { top: 50%; left: 60%; transform: scale(1.2) translateZ(50px); }
.cube-wrapper-2 .cube { animation-duration: 12s; }
.cube-wrapper-3 { top: 70%; left: 15%; transform: scale(0.6) translateZ(-200px); }
.cube-wrapper-3 .cube { animation-duration: 22s; animation-delay: -5s; }
</style>
<div id="css-3d-cubes-background">
<div class="scene">
<div class="cube-wrapper cube-wrapper-1">
<div class="cube">
<div class="face front"></div> <div class="face back"></div>
<div class="face right"></div> <div class="face left"></div>
<div class="face top"></div> <div class="face bottom"></div>
</div>
</div>
<div class="cube-wrapper cube-wrapper-2">
<div class="cube">
<div class="face front"></div> <div class="face back"></div>
<div class="face right"></div> <div class="face left"></div>
<div class="face top"></div> <div class="face bottom"></div>
</div>
</div>
<div class="cube-wrapper cube-wrapper-3">
<div class="cube">
<div class="face front"></div> <div class="face back"></div>
<div class="face right"></div> <div class="face left"></div>
<div class="face top"></div> <div class="face bottom"></div>
</div>
</div>
<!-- 可以用JS动态添加更多cube-wrapper并随机化参数 -->
</div>
</div>
@@ -11,31 +11,23 @@
<script>
(function() {
const canvas = document.getElementById('matrix-canvas');
const layerElement = canvas.closest('.terminal-custom-html-layer'); // Or document.body if not in that specific env
const layerElement = canvas.closest('.terminal-custom-html-layer');
if (!canvas || !layerElement) {
console.error("Canvas or layerElement not found.");
return;
}
const ctx = canvas.getContext('2d');
let animationFrameId;
const characters = 'アァカサタナハマヤャラワABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
const fontSize = 14;
let columns, drops = [];
// --- Slowdown Configuration ---
const slowdownFactor = 10; // 数字越大,动画越慢 (例如 2 = 半速, 3 = 1/3速度)
const originalAlpha = 0.1; // 原始的背景淡出alpha值
// 计算调整后的alpha值,以保持拖尾视觉效果
// 如果 slowdownFactor 为 1, adjustedAlpha 等于 originalAlpha
// (1 - newAlpha)^slowdownFactor = (1 - originalAlpha)^1
// 1 - newAlpha = (1 - originalAlpha)^(1/slowdownFactor)
// newAlpha = 1 - (1 - originalAlpha)^(1/slowdownFactor)
// 拖尾视觉效果配置
const slowdownFactor = 10;
const originalAlpha = 0.17;
const adjustedAlpha = slowdownFactor <= 1 ? originalAlpha : 1 - Math.pow(1 - originalAlpha, 1 / slowdownFactor);
let frameCounter = 0; // 用于控制逻辑更新频率的计数器
// --- End Slowdown Configuration ---
let frameCounter = 0;
function setupDimensionsAndColumns() {
canvas.width = layerElement.offsetWidth;
@@ -45,7 +37,7 @@
for (let x = 0; x < columns; x++) {
drops[x] = 1 + Math.floor(Math.random() * (canvas.height / fontSize));
}
frameCounter = 0; // 重置帧计数器
frameCounter = 0;
}
function drawMatrix() {
@@ -54,30 +46,27 @@
return;
}
// 背景淡出效果:每一帧都执行,使用调整后的alpha值
ctx.fillStyle = `rgba(0, 0, 0, ${adjustedAlpha})`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
frameCounter++;
// 只有当帧计数器达到 slowdownFactor 时,才更新雨滴逻辑和绘制字符
if (frameCounter >= slowdownFactor) {
frameCounter = 0; // 重置计数器
frameCounter = 0;
ctx.fillStyle = '#0F0'; // 雨滴颜色
ctx.fillStyle = '#0F0';
ctx.font = fontSize + 'px monospace';
for (let i = 0; i < drops.length; i++) {
const text = characters.charAt(Math.floor(Math.random() * characters.length));
ctx.fillText(text, i * fontSize, drops[i] * fontSize);
// 如果雨滴超出画布底部,并且随机条件满足,则重置雨滴到顶部
if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) {
drops[i] = 0;
}
drops[i]++; // 雨滴下落
drops[i]++;
}
}
animationFrameId = requestAnimationFrame(drawMatrix);
}
@@ -99,7 +88,6 @@
}
}, 250);
// 确保 layerElement 存在才进行监听
if (layerElement) {
const resizeObserver = new ResizeObserver(entries => {
entries.forEach(entry => {
@@ -110,8 +98,6 @@
});
resizeObserver.observe(layerElement);
} else {
// Fallback if layerElement is not specific, e.g. observe window or body
// window.addEventListener('resize', debouncedReinitialize); // Example fallback
console.warn("layerElement not found for ResizeObserver, resize handling might be limited.");
}
@@ -0,0 +1,125 @@
<style>
#svg-hexgrid-background {
width: 100%;
height: 100%;
background-color: #0a0f14; /* 非常暗的冷灰色 */
overflow: hidden;
}
#svg-hexgrid-background svg {
width: 100%;
height: 100%;
}
.hexagon-bg {
stroke: rgba(70, 100, 130, 0.25); /* 暗青蓝色背景六边形 */
stroke-width: 1px;
fill: rgba(30, 40, 50, 0.1);
}
.trace-path {
fill: none;
stroke-width: 1.5px;
stroke-linecap: round;
stroke-dasharray: 600; /* 一个足够大的值,确保能覆盖路径总长 */
stroke-dashoffset: 600; /* 初始状态,路径不可见 */
animation: drawAndFadePath 12s ease-in-out infinite;
filter: drop-shadow(0 0 4px currentColor) drop-shadow(0 0 8px currentColor); /* 使用当前描边色发光 */
}
/* 不同路径使用不同颜色和动画延迟 */
.path-cyan { stroke: #00e5ff; }
.path-magenta { stroke: #ff00ff; animation-delay: -3s; }
.path-lime { stroke: #aeff00; animation-delay: -6s; }
.path-orange { stroke: #ffaa00; animation-delay: -9s; animation-direction: alternate; }
@keyframes drawAndFadePath {
0% { stroke-dashoffset: 600; opacity: 0; } /* 开始时完全不可见 */
10% { opacity: 0.8; } /* 快速显现 */
40% { stroke-dashoffset: 0; opacity: 1; } /* 路径完全绘制出来,不透明 */
60% { stroke-dashoffset: 0; opacity: 1; } /* 保持显示 */
90% { opacity: 0.8; } /* 开始褪色 */
100% { stroke-dashoffset: -600; opacity: 0; } /* 路径从起点开始消失并完全透明 */
}
</style>
<div id="svg-hexgrid-background">
<svg width="100%" height="100%" viewBox="0 0 800 600" preserveAspectRatio="xMidYMid slice" xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- 定义一个六边形单元,方便复用 -->
<!-- 六边形顶点计算: r=半径, h=r*sqrt(3)/2 (内切圆半径/高度的一半) -->
<!-- points="r,0 r/2,h -r/2,h -r,0 -r/2,-h r/2,-h" for radius r, height 2h -->
<symbol id="hexSymbol" viewBox="-52 -45 104 90"> <!-- viewBox slightly larger than hex -->
<polygon class="hexagon-bg" points="50,0 25,43.3 -25,43.3 -50,0 -25,-43.3 25,-43.3" />
</symbol>
</defs>
<!-- 静态六边形网格背景 -->
<g id="staticHexGrid">
<!-- 手动放置一些六边形作为示例,更完善的网格可以用JS生成 -->
<use href="#hexSymbol" x="100" y="100" width="100" height="86.6"/>
<use href="#hexSymbol" x="200" y="100" width="100" height="86.6"/>
<use href="#hexSymbol" x="150" y="100 + 86.6*0.5" width="100" height="86.6"/> <!-- Staggered row -->
<use href="#hexSymbol" x="250" y="100 + 86.6*0.5" width="100" height="86.6"/>
<use href="#hexSymbol" x="50" y="100 + 86.6" width="100" height="86.6"/>
<use href="#hexSymbol" x="300" y="100 + 86.6" width="100" height="86.6"/>
<!-- ... 可以添加更多 for better coverage ... -->
<use href="#hexSymbol" x="400" y="100" width="100" height="86.6"/>
<use href="#hexSymbol" x="350" y="100 + 86.6*0.5" width="100" height="86.6"/>
<use href="#hexSymbol" x="450" y="100 + 86.6*0.5" width="100" height="86.6"/>
<use href="#hexSymbol" x="500" y="100 + 86.6" width="100" height="86.6"/>
<use href="#hexSymbol" x="600" y="300" width="100" height="86.6"/>
<use href="#hexSymbol" x="700" y="300" width="100" height="86.6"/>
<use href="#hexSymbol" x="650" y="300 + 86.6*0.5" width="100" height="86.6"/>
<use href="#hexSymbol" x="550" y="300 + 86.6*0.5" width="100" height="86.6"/>
</g>
<!-- 动态追踪线条 -->
<g id="animatedTraces">
<path class="trace-path path-cyan" d="M50,500 Q200,50 400,300 T750,100" />
<path class="trace-path path-magenta" d="M750,500 Q600,50 400,300 T50,100" />
<path class="trace-path path-lime" d="M20,150 C150,550 650,50 780,450 S 400, 200 100, 300 Z" />
<path class="trace-path path-orange" d="M400,50 C50,250 750,350 400,550 C200,450 600,150 400,50" />
</g>
</svg>
<!--
可选JS: 动态生成六边形网格覆盖整个SVG区域
<script>
(function() {
const svg = document.querySelector("#svg-hexgrid-background svg");
const gridGroup = document.getElementById("staticHexGrid");
if (!svg || !gridGroup) return;
const svgNS = "http://www.w3.org/2000/svg";
const hexRadius = 50; // 半径
const hexWidth = hexRadius * 2;
const hexHeight = Math.sqrt(3) * hexRadius; // 整体高度
const vertDist = hexHeight;
const horizDist = hexWidth * 3/4;
const vbWidth = svg.viewBox.baseVal.width;
const vbHeight = svg.viewBox.baseVal.height;
gridGroup.innerHTML = ''; // 清空手动放置的六边形
for (let y = 0, row = 0; y < vbHeight + hexHeight; y += vertDist / 2, row++) {
for (let x = 0, col = 0; x < vbWidth + horizDist; x += horizDist, col++) {
let currentX = x;
if (row % 2 !== 0) { // 奇数行水平偏移
currentX += horizDist / 2;
}
if (currentX > vbWidth + horizDist/2) continue; // 超出右边界
const use = document.createElementNS(svgNS, "use");
use.setAttributeNS(null, "href", "#hexSymbol");
use.setAttributeNS(null, "x", currentX);
use.setAttributeNS(null, "y", y);
use.setAttributeNS(null, "width", hexWidth);
use.setAttributeNS(null, "height", hexHeight);
gridGroup.appendChild(use);
}
}
})();
</script>
-->
</div>
@@ -0,0 +1,203 @@
<style>
#elegant-network-background {
width: 100%;
height: 100%;
background-color: #1d2331; /* 深灰蓝,比纯黑柔和 */
overflow: hidden;
position: relative;
}
#elegant-network-svg { width: 100%; height: 100%; position:absolute; top:0; left:0; }
.network-node {
fill: rgba(150, 180, 230, 0.7); /* 柔和的节点颜色 */
r: 2; /* 节点半径 */
/* CSS动画驱动节点移动 */
animation: nodeFloat 20s infinite ease-in-out alternate;
}
.network-node:nth-child(5n+1) { animation-duration: 22s; animation-delay: -3s; }
.network-node:nth-child(5n+2) { animation-duration: 18s; animation-delay: -7s; fill: rgba(120,160,210,0.6); r:1.5;}
.network-node:nth-child(5n+3) { animation-duration: 25s; animation-delay: -1s; }
.network-node:nth-child(5n+4) { animation-duration: 19s; animation-delay: -5s; fill: rgba(170,200,240,0.8); r:2.5;}
@keyframes nodeFloat { /* 定义一个通用的漂浮路径 */
0% { transform: translate(0px, 0px); }
25% { transform: translate(20px, -15px); }
50% { transform: translate(-10px, 25px); }
75% { transform: translate(15px, 5px); }
100% { transform: translate(0px, 0px); }
}
.network-line {
stroke: rgba(100, 130, 180, 0.15); /* 非常淡的连接线 */
stroke-width: 0.5px;
/* 线条的显隐和动画通过JS控制 */
}
</style>
<div id="elegant-network-background">
<svg id="elegant-network-svg" xmlns="http://www.w3.org/2000/svg">
<g id="network-nodes-group"></g>
<g id="network-lines-group"></g>
</svg>
</div>
<script>
(function() {
const svg = document.getElementById('elegant-network-svg');
const nodesGroup = document.getElementById('network-nodes-group');
const linesGroup = document.getElementById('network-lines-group');
const layerElement = svg.closest('.terminal-custom-html-layer') || svg.parentElement;
if (!svg || !nodesGroup || !linesGroup || !layerElement) return;
const svgNS = "http://www.w3.org/2000/svg";
let width, height, animationFrameId;
const nodes = [];
const numNodes = 40; // 节点数量
const maxDistance = 150; // 连接线最大距离
class Node {
constructor(x, y, id) {
this.x = x;
this.y = y;
this.id = `node-${id}`;
this.baseX = x; // 用于CSS动画的基准位置
this.baseY = y;
this.element = document.createElementNS(svgNS, "circle");
this.element.setAttribute("id", this.id);
this.element.setAttribute("class", "network-node");
// this.element.setAttribute("cx", this.x); // cx, cy 将由CSS transform 控制相对基准的偏移
// this.element.setAttribute("cy", this.y);
// 使用transform来应用CSS动画,而不是直接改变cx/cy
this.element.style.transform = `translate(${this.x}px, ${this.y}px)`;
// 随机化CSS动画参数,让每个节点漂浮不同
const duration = 15 + Math.random() * 15; // 15-30s
const delay = -(Math.random() * duration);
this.element.style.animationDuration = `${duration}s`;
this.element.style.animationDelay = `${delay}s`;
nodesGroup.appendChild(this.element);
}
// JS不再直接更新位置,依赖CSS动画。但需要获取当前渲染位置用于连线
getCurrentPosition() {
// 获取由CSS transform计算后的实际位置
const ctm = this.element.getCTM();
// 如果SVG嵌套复杂,可能需要更复杂的计算,或使用getBoundingClientRect
// 对于直接在SVG根下的元素,CTM的e和f就是视口坐标系下的x和y
// 但由于我们用translate(baseX, baseY)来设置初始位置,
// CSS动画的transform是相对这个的。
// 简便方法:仍然在JS中保留一个缓慢的漂移,或者接受CSS动画的位置
// 如果依赖CSS动画,获取精确位置会复杂。
// 为简化,这里假设CSS动画的漂移是小范围的,我们仍用js的x,y做连线,CSS只是视觉漂移
// 或者,我们让JS也做动画,CSS只定义样式
// 重新考虑:让JS驱动位置,CSS仅定义样式和基础动画(如闪烁)
return { x: this.x, y: this.y }; // 简化处理
}
}
// --- 重新设计Node的动画方式:JS驱动位置,CSS负责样式 ---
nodesGroup.innerHTML = ''; // 清空旧的
class JsAnimatedNode {
constructor(x, y, id) {
this.x = x;
this.y = y;
this.vx = (Math.random() - 0.5) * 0.2; // 缓慢速度
this.vy = (Math.random() - 0.5) * 0.2;
this.id = `node-${id}`;
this.element = document.createElementNS(svgNS, "circle");
this.element.setAttribute("id", this.id);
this.element.classList.add("network-node"); // 应用CSS样式
// 移除animation, 因为JS驱动位置
this.element.style.animation = 'none';
// 随机化样式,比如半径和颜色 (通过覆盖CSS定义的)
const r = 1.5 + Math.random() * 1.5; // 1.5 to 3
this.element.setAttribute("r", r);
if (Math.random() < 0.3) {
this.element.style.fill = `rgba(${120 + Math.floor(Math.random()*50)}, ${150 + Math.floor(Math.random()*50)}, ${200 + Math.floor(Math.random()*50)}, ${0.5 + Math.random()*0.3})`;
}
nodesGroup.appendChild(this.element);
}
update() {
this.x += this.vx;
this.y += this.vy;
if (this.x < 0 || this.x > width) this.vx *= -1;
if (this.y < 0 || this.y > height) this.vy *= -1;
this.element.setAttribute("cx", this.x);
this.element.setAttribute("cy", this.y);
}
getCurrentPosition() { return {x: this.x, y: this.y}; }
}
// --- 结束重新设计 ---
function connectNodes() {
linesGroup.innerHTML = ""; // 清空旧线条
for (let i = 0; i < nodes.length; i++) {
for (let j = i + 1; j < nodes.length; j++) {
const posA = nodes[i].getCurrentPosition();
const posB = nodes[j].getCurrentPosition();
const dx = posA.x - posB.x;
const dy = posA.y - posB.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < maxDistance) {
const line = document.createElementNS(svgNS, "line");
line.setAttribute("x1", posA.x);
line.setAttribute("y1", posA.y);
line.setAttribute("x2", posB.x);
line.setAttribute("y2", posB.y);
line.setAttribute("class", "network-line");
// 线条透明度随距离变化
const opacity = Math.max(0, (1 - distance / maxDistance) * 0.3); // 0.3是基础最大透明度
line.style.strokeOpacity = opacity.toFixed(2);
linesGroup.appendChild(line);
}
}
}
}
function init() {
width = layerElement.offsetWidth;
height = layerElement.offsetHeight;
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
nodes.length = 0;
nodesGroup.innerHTML = ''; // 清空,因为JsAnimatedNode会重新添加
linesGroup.innerHTML = '';
for (let i = 0; i < numNodes; i++) {
// nodes.push(new Node(Math.random() * width, Math.random() * height, i)); // 旧的CSS动画Node
nodes.push(new JsAnimatedNode(Math.random() * width, Math.random() * height, i)); // 新的JS动画Node
}
}
function animate() {
nodes.forEach(node => node.update()); // 如果是JS动画Node
connectNodes();
animationFrameId = requestAnimationFrame(animate);
}
let resizeTimeout;
function onResize() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
if (animationFrameId) cancelAnimationFrame(animationFrameId);
init();
animate();
}, 250);
}
init();
animate();
window.addEventListener('resize', onResize);
svg.cleanup = function() { if (animationFrameId) cancelAnimationFrame(animationFrameId); };
})();
</script>
@@ -0,0 +1,195 @@
<style>
#circuit-board-container {
width: 100%; height: 100%;
background-color: #0a192f; /* 非常深的科技蓝 */
overflow: hidden; position: relative;
}
#circuit-canvas { display: block; position: absolute; top: 0; left: 0; }
</style>
<div id="circuit-board-container">
<canvas id="circuit-canvas"></canvas>
</div>
<script>
(function() {
const canvas = document.getElementById('circuit-canvas');
const layerElement = canvas.closest('.terminal-custom-html-layer') || canvas.parentElement;
if (!canvas || !layerElement) return;
const ctx = canvas.getContext('2d');
let width, height, animationFrameId;
const traces = [];
const numTraces = 30;
const gridSize = 40; // 网格大小,用于确定路径点
const traceColor = "rgba(50, 180, 220, 0.15)"; // 静态轨迹颜色
const signalColor = "rgba(100, 220, 255, 0.9)"; // 信号颜色
const signalSpeed = 2; // 信号移动速度 (px/frame)
class Trace {
constructor() {
this.points = [];
this.generatePath();
this.signalPosition = 0; // 信号在路径上的当前距离
this.signalActive = Math.random() < 0.7; // 70% 的轨迹初始有信号
this.signalRestartDelay = 0; // 信号消失后的重启延迟
}
generatePath() {
this.points = [];
const startX = Math.floor(Math.random() * (width / gridSize)) * gridSize;
const startY = Math.floor(Math.random() * (height / gridSize)) * gridSize;
this.points.push({ x: startX, y: startY });
let currentX = startX;
let currentY = startY;
const pathLength = 5 + Math.floor(Math.random() * 10); // 路径段数
for (let i = 0; i < pathLength; i++) {
const directions = []; // 0: up, 1: down, 2: left, 3: right
if (currentY > 0) directions.push(0);
if (currentY < height - gridSize) directions.push(1);
if (currentX > 0) directions.push(2);
if (currentX < width - gridSize) directions.push(3);
if (directions.length === 0) break; // No valid moves
const move = directions[Math.floor(Math.random() * directions.length)];
let nextX = currentX, nextY = currentY;
switch (move) {
case 0: nextY -= gridSize; break; // Up
case 1: nextY += gridSize; break; // Down
case 2: nextX -= gridSize; break; // Left
case 3: nextX += gridSize; break; // Right
}
// 避免立即回头
if (this.points.length > 1) {
const prevPoint = this.points[this.points.length - 2];
if (prevPoint.x === nextX && prevPoint.y === nextY) {
i--; // Try again
continue;
}
}
this.points.push({ x: nextX, y: nextY });
currentX = nextX;
currentY = nextY;
}
this.totalLength = 0;
for(let i = 0; i < this.points.length -1; i++) {
this.totalLength += gridSize; // All segments are gridSize long
}
}
update() {
if (this.signalActive) {
this.signalPosition += signalSpeed;
if (this.signalPosition > this.totalLength + 20) { // +20 to let it fade out
this.signalActive = false;
this.signalPosition = 0;
this.signalRestartDelay = 100 + Math.random() * 300; // 100-400 frames delay
}
} else {
this.signalRestartDelay--;
if (this.signalRestartDelay <= 0 && Math.random() < 0.01) { // Low chance to reactivate
this.signalActive = true;
// Optional: re-generate path
// this.generatePath();
}
}
}
draw() {
if (this.points.length < 2) return;
// Draw static trace
ctx.beginPath();
ctx.moveTo(this.points[0].x, this.points[0].y);
for (let i = 1; i < this.points.length; i++) {
ctx.lineTo(this.points[i].x, this.points[i].y);
}
ctx.strokeStyle = traceColor;
ctx.lineWidth = 1 + Math.random()*0.5;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.stroke();
// Draw signal
if (this.signalActive) {
ctx.beginPath();
let distanceTraveled = 0;
let signalDrawn = false;
for (let i = 0; i < this.points.length - 1; i++) {
const p1 = this.points[i];
const p2 = this.points[i+1];
const segmentLength = gridSize;
if (!signalDrawn && this.signalPosition >= distanceTraveled && this.signalPosition < distanceTraveled + segmentLength) {
const progressInSegment = (this.signalPosition - distanceTraveled) / segmentLength;
const sigX = p1.x + (p2.x - p1.x) * progressInSegment;
const sigY = p1.y + (p2.y - p1.y) * progressInSegment;
ctx.moveTo(sigX, sigY); // Start of signal for this frame
signalDrawn = true; // Draw only one moveTo
}
if(signalDrawn && this.signalPosition + 10 > distanceTraveled) { // 10 is signal "tail" length
ctx.lineTo(p2.x, p2.y);
if(this.signalPosition > distanceTraveled + segmentLength + 10) {
// break; // Signal has passed this segment
}
}
if (this.signalPosition < distanceTraveled + segmentLength && signalDrawn) {
// Draw up to the end of current segment or signal head
const endProgress = Math.min(1, (this.signalPosition + 10 - distanceTraveled) / segmentLength); // +10 for tail
const endX = p1.x + (p2.x - p1.x) * endProgress;
const endY = p1.y + (p2.y - p1.y) * endProgress;
ctx.lineTo(endX, endY);
break; // Signal is within this segment or ends here
}
distanceTraveled += segmentLength;
}
if (signalDrawn) {
ctx.strokeStyle = signalColor;
ctx.lineWidth = 2 + Math.random();
ctx.shadowColor = signalColor;
ctx.shadowBlur = 8 + Math.random() * 5;
ctx.stroke();
ctx.shadowBlur = 0; // Reset shadow
}
}
}
}
function init() {
width = layerElement.offsetWidth;
height = layerElement.offsetHeight;
canvas.width = width;
canvas.height = height;
traces.length = 0;
for (let i = 0; i < numTraces; i++) {
traces.push(new Trace());
}
}
function animate() {
ctx.clearRect(0, 0, width, height);
traces.forEach(trace => {
trace.update();
trace.draw();
});
animationFrameId = requestAnimationFrame(animate);
}
let resizeTimeout;
function onResize() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
if (animationFrameId) cancelAnimationFrame(animationFrameId);
init();
animate();
}, 250);
}
init();
animate();
window.addEventListener('resize', onResize);
canvas.cleanup = function() { if (animationFrameId) cancelAnimationFrame(animationFrameId); };
})();
</script>
@@ -0,0 +1,139 @@
<style>
#orbiting-rings-background {
width: 100%;
height: 100%;
background-color: #101218; /* 近黑色,略带冷调 */
overflow: hidden;
position: relative;
perspective: 1000px; /* 为3D旋转提供透视 */
}
#orbiting-rings-svg {
width: 100%;
height: 100%;
position: absolute;
top: 0; left: 0;
transform-style: preserve-3d; /* 允许SVG内的组进行3D变换 */
}
.stardust-ring {
transform-origin: center center; /* 确保从中心旋转 */
/* CSS动画驱动整体旋转和倾斜 */
animation: orbit 40s linear infinite;
}
.ring-particle {
fill: rgba(180, 200, 230, 0.6); /* 淡雅的粒子颜色 */
/* 粒子可以有自己的细微动画,如闪烁 */
animation: particleTwinkle 5s infinite ease-in-out alternate;
}
/* 不同环的参数 */
.ring-1 { animation-duration: 50s; transform: rotateX(70deg) rotateZ(0deg) scale(1); }
.ring-1 .ring-particle { animation-delay: -1s; }
.ring-2 { animation-duration: 40s; animation-direction: reverse; transform: rotateX(75deg) rotateZ(20deg) scale(0.7); }
.ring-2 .ring-particle { fill: rgba(200, 180, 230, 0.5); animation-delay: -2.5s; }
.ring-3 { animation-duration: 60s; transform: rotateX(80deg) rotateZ(-15deg) scale(0.45); }
.ring-3 .ring-particle { fill: rgba(180, 230, 200, 0.55); animation-delay: -0.5s; }
.ring-4 { animation-duration: 35s; animation-direction: reverse; transform: rotateX(65deg) rotateZ(30deg) scale(1.2); }
.ring-4 .ring-particle { animation-delay: -3s; }
@keyframes orbit {
from { transform: rotateX(var(--initial-rotateX, 70deg)) rotateZ(var(--initial-rotateZ, 0deg)) rotateY(0deg) scale(var(--initial-scale,1)); }
to { transform: rotateX(var(--initial-rotateX, 70deg)) rotateZ(var(--initial-rotateZ, 0deg)) rotateY(360deg) scale(var(--initial-scale,1)); }
}
/* 修正: CSS动画的transform会覆盖初始的transform。
需要将初始的rotateX/Z/scale放入CSS变量或JS动态应用,或者在@keyframes中包含它们。
更简单的方式是让 @keyframes orbit 只控制 rotateY,初始的X,Z,scale由.ring-N类设定。
我们将修改.stardust-ring 的动画,使其只改变Y轴旋转。
*/
@keyframes orbitYOnly {
from { transform: rotateY(0deg); } /* 这个transform会应用在.ring-N的transform之上 */
to { transform: rotateY(360deg); }
}
/* 在 .stardust-ring 中使用一个包装器来应用初始变换,然后让内部元素旋转Y轴 */
/* 或者,更简单,直接在 .ring-N 的 animation 中指定完整的 transform */
/* 为简化,以下采用修改后的 @keyframes orbit,它会作用于已变换的元素 */
</style>
<div id="orbiting-rings-background">
<svg id="orbiting-rings-svg" viewBox="-250 -250 500 500" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg">
<!-- JS会在这里动态生成环和粒子 -->
</svg>
</div>
<script>
(function() {
const svg = document.getElementById('orbiting-rings-svg');
if (!svg) return;
const svgNS = "http://www.w3.org/2000/svg";
const numRings = 4;
const particlesPerRing = 80;
const ringBaseRadius = 80;
// 预定义的环参数 (可以从CSS中读取或在此处定义)
const ringConfigs = [
{ class: 'ring-1', initialTransform: 'rotateX(70deg) rotateZ(0deg) scale(1)', duration: 50, particleFill: 'rgba(180, 200, 230, 0.6)', particleDelay: -1 },
{ class: 'ring-2', initialTransform: 'rotateX(75deg) rotateZ(20deg) scale(0.7)', duration: 40, direction: 'reverse', particleFill: 'rgba(200, 180, 230, 0.5)', particleDelay: -2.5 },
{ class: 'ring-3', initialTransform: 'rotateX(80deg) rotateZ(-15deg) scale(0.45)', duration: 60, particleFill: 'rgba(180, 230, 200, 0.55)', particleDelay: -0.5 },
{ class: 'ring-4', initialTransform: 'rotateX(65deg) rotateZ(30deg) scale(1.2)', duration: 35, direction: 'reverse', particleFill: 'rgba(180, 200, 230, 0.6)', particleDelay: -3 }
];
ringConfigs.forEach((config, ringIndex) => {
const group = document.createElementNS(svgNS, "g");
group.classList.add('stardust-ring');
group.classList.add(config.class); // 应用CSS中定义的类
group.style.transform = config.initialTransform; // 应用初始3D变换
// 应用Y轴旋转动画
group.style.animationName = 'orbitYOnly'; // 使用只旋转Y轴的动画
group.style.animationDuration = `${config.duration}s`;
group.style.animationTimingFunction = 'linear';
group.style.animationIterationCount = 'infinite';
if (config.direction) {
group.style.animationDirection = config.direction;
}
const currentRingRadius = ringBaseRadius * (1 + ringIndex * 0.4 + Math.random()*0.2); // 半径随环增加
for (let i = 0; i < particlesPerRing; i++) {
const angle = (i / particlesPerRing) * Math.PI * 2;
const particle = document.createElementNS(svgNS, "circle");
particle.classList.add('ring-particle');
const r = 0.8 + Math.random() * 0.7; // 粒子半径
particle.setAttribute("r", r);
particle.setAttribute("cx", Math.cos(angle) * currentRingRadius);
particle.setAttribute("cy", Math.sin(angle) * currentRingRadius);
particle.style.fill = config.particleFill;
// 粒子闪烁动画由CSS的.ring-particle和.ring-N .ring-particle控制
// particle.style.animationDelay = `${config.particleDelay + Math.random() * 0.5 - 0.25}s`; // 为每个粒子稍微错开闪烁
// 为了简化,让所有同环粒子用相同的闪烁延迟(由CSS :nth-child 或 config.particleDelay 控制)
group.appendChild(particle);
}
svg.appendChild(group);
});
// CSS中已有的particleTwinkle动画
// @keyframes particleTwinkle {
// 0%, 100% { opacity: 0.5; transform: scale(0.8); }
// 50% { opacity: 1; transform: scale(1.2); }
// }
// 将其加入style标签,如果它不在那里
if (!document.querySelector("style[data-added-by-js='particleTwinkle']")) {
const styleSheet = document.createElement("style");
styleSheet.setAttribute("data-added-by-js", "particleTwinkle");
styleSheet.innerHTML = `
@keyframes particleTwinkle {
0%, 100% { opacity: 0.4; transform: scale(0.7); }
50% { opacity: 0.8; transform: scale(1.1); }
}
/* 如果需要,可以在这里动态添加.ring-N .ring-particle 的 animation-delay */
`;
document.head.appendChild(styleSheet);
}
})();
</script>
@@ -0,0 +1,107 @@
<style>
#svg-soft-halos-background {
width: 100%;
height: 100%;
background-color: #282c34; /* 柔和的深灰色 */
overflow: hidden;
position: relative;
}
#svg-soft-halos-background svg {
width: 100%;
height: 100%;
position: absolute;
top: 0; left: 0;
}
.halo-source {
/* 光晕源本身可以不可见,或者非常暗淡 */
opacity: 0.05;
}
.rotating-shape {
fill: rgba(200, 220, 255, 0.1); /* 非常淡的形状颜色 */
stroke: rgba(200, 220, 255, 0.3);
stroke-width: 1px;
transform-origin: center center; /* 确保从中心旋转 */
animation: slowRotate 30s linear infinite, pulseOpacity 15s ease-in-out infinite alternate;
}
.shape-1 { transform: translate(25%, 30%) scale(0.8); animation-delay: -5s, -2s; }
.shape-2 { transform: translate(70%, 60%) scale(1.1); animation-delay: -15s, -6s; }
.shape-3 { transform: translate(50%, 75%) scale(0.6); animation-delay: -2s, -10s; }
.shape-4 { transform: translate(15%, 80%) scale(0.9); animation-delay: -10s, -4s; }
@keyframes slowRotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes pulseOpacity {
0%, 100% { opacity: 0.3; filter: drop-shadow(0 0 5px rgba(180,200,255,0.2)); }
50% { opacity: 0.7; filter: drop-shadow(0 0 15px rgba(200,220,255,0.4));}
}
</style>
<div id="svg-soft-halos-background">
<svg viewBox="0 0 800 600" preserveAspectRatio="xMidYMid slice">
<defs>
<filter id="softGlow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="25" result="coloredBlur"/> <!-- 调整stdDeviation控制模糊程度 -->
<feMerge>
<feMergeNode in="coloredBlur"/>
<feMergeNode in="SourceGraphic"/> <!-- 可选:将原始图形叠在模糊之上,如果需要更清晰的源 -->
</feMerge>
</filter>
</defs>
<!-- 光晕源 - 可以是几个大的圆形 -->
<g filter="url(#softGlow)">
<circle class="halo-source" cx="15%" cy="20%" r="150" fill="#5070A0" /> <!-- 淡蓝色光晕 -->
<ellipse class="halo-source" cx="80%" cy="75%" rx="200" ry="120" fill="#7050A0" /> <!-- 淡紫色光晕 -->
<circle class="halo-source" cx="50%" cy="50%" r="100" fill="#60A070" /> <!-- 淡绿色光晕 -->
</g>
<!-- 缓慢旋转的几何图形 -->
<g id="rotatingShapesLayer">
<!-- 三角形 -->
<polygon class="rotating-shape shape-1" points="0,-30 26,15 -26,15" />
<!-- 方形 (旋转45度成菱形) -->
<rect class="rotating-shape shape-2" x="-20" y="-20" width="40" height="40" transform="rotate(45)"/>
<!-- 六边形 -->
<polygon class="rotating-shape shape-3" points="30,0 15,26 -15,26 -30,0 -15,-26 15,-26" />
<!-- 细长矩形 -->
<rect class="rotating-shape shape-4" x="-40" y="-5" width="80" height="10" />
</g>
</svg>
<script>
// JS为几何图形添加更随机的初始位置和动画参数
(function() {
const shapes = document.querySelectorAll('#rotatingShapesLayer .rotating-shape');
const svgViewBox = { width: 800, height: 600 }; // 从SVG viewBox获取或硬编码
shapes.forEach((shape, index) => {
const scale = Math.random() * 0.6 + 0.5; // 0.5 to 1.1
const posX = Math.random() * svgViewBox.width;
const posY = Math.random() * svgViewBox.height;
// 移除CSS中的transform,由JS控制
shape.style.transform = `translate(${posX}px, ${posY}px) scale(${scale})`;
const rotateDuration = Math.random() * 20 + 20; // 20s to 40s
const opacityDuration = Math.random() * 10 + 10; // 10s to 20s
const delayRotate = -(Math.random() * rotateDuration);
const delayOpacity = -(Math.random() * opacityDuration);
shape.style.animation = `slowRotate ${rotateDuration}s linear ${delayRotate}s infinite, pulseOpacity ${opacityDuration}s ease-in-out ${delayOpacity}s infinite alternate`;
// 确保transform-origin在JS设置transform后依然有效
// 对于SVG元素,transform-origin需要通过transform属性自身来模拟或依赖CSS。
// 由于我们直接在JS中设置translate和scale, CSS的transform-origin: center center 会基于其初始0,0点。
// 对于SVG <polygon> 和 <rect>, 它们的坐标是相对于自身(0,0)或x,y属性的。
// CSS transform-origin center center 是针对HTML元素的。
// SVG元素的旋转中心是其(0,0)点。如果图形不是以(0,0)为中心绘制的,需要调整。
// 上面的图形都是以(0,0)为大致中心绘制的,所以默认旋转中心是OK的。
});
})();
</script>
</div>
@@ -0,0 +1,122 @@
<style>
#particle-network-container {
width: 100%; height: 100%; background: #111; /* 深色背景 */
overflow: hidden; position: relative;
}
#particle-canvas { display: block; position: absolute; top: 0; left: 0; }
</style>
<div id="particle-network-container">
<canvas id="particle-canvas"></canvas>
</div>
<script>
(function() {
const canvas = document.getElementById('particle-canvas');
const layerElement = canvas.closest('.terminal-custom-html-layer') || canvas.parentElement; // Fallback to parentElement if not in specific terminal
if (!canvas || !layerElement) return;
const ctx = canvas.getContext('2d');
let width, height, particles = [];
const particleCount = 80;
const maxDistance = 100; // 最大连线距离
const particleSpeed = 0.5;
const particleColor = "rgba(173, 216, 230, 0.7)"; // 淡蓝色粒子
const lineColor = "rgba(173, 216, 230, 0.2)"; // 淡蓝色连线
class Particle {
constructor() {
this.x = Math.random() * width;
this.y = Math.random() * height;
this.vx = (Math.random() - 0.5) * particleSpeed * 2;
this.vy = (Math.random() - 0.5) * particleSpeed * 2;
this.radius = Math.random() * 2 + 1;
}
update() {
this.x += this.vx;
this.y += this.vy;
if (this.x < 0 || this.x > width) this.vx *= -1;
if (this.y < 0 || this.y > height) this.vy *= -1;
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = particleColor;
ctx.fill();
}
}
function init() {
width = layerElement.offsetWidth;
height = layerElement.offsetHeight;
canvas.width = width;
canvas.height = height;
particles = [];
for (let i = 0; i < particleCount; i++) {
particles.push(new Particle());
}
}
function connectParticles() {
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const dx = particles[i].x - particles[j].x;
const dy = particles[i].y - particles[j].y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < maxDistance) {
ctx.beginPath();
ctx.moveTo(particles[i].x, particles[i].y);
ctx.lineTo(particles[j].x, particles[j].y);
ctx.strokeStyle = lineColor;
// 线条透明度随距离变化
ctx.globalAlpha = 1 - (distance / maxDistance);
ctx.stroke();
ctx.globalAlpha = 1; // Reset globalAlpha
}
}
}
}
let animationFrameId;
function animate() {
ctx.clearRect(0, 0, width, height);
particles.forEach(p => {
p.update();
p.draw();
});
connectParticles();
animationFrameId = requestAnimationFrame(animate);
}
// Debounce resize function
let resizeTimeout;
function onResize() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
init();
animate();
}, 250);
}
// Initialize and start animation
init();
animate();
// Listen for resize events to reinitialize
window.addEventListener('resize', onResize);
// Cleanup function for potential hot-reloading or component unmount
canvas.cleanup = function() {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
window.removeEventListener('resize', onResize);
console.log("Particle network cleaned up");
};
})();
</script>
@@ -0,0 +1,98 @@
<style>
#elegant-sparkle-background {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #1c1c2e, #2a2a3f); /* 深邃蓝紫色渐变 */
overflow: hidden;
position: relative;
}
.sparkle {
position: absolute;
background-color: rgba(220, 220, 255, 0.8); /* 淡雅的亮白色/淡蓝色 */
border-radius: 50%;
box-shadow: 0 0 8px rgba(200, 200, 255, 0.6),
0 0 12px rgba(180, 180, 255, 0.4),
0 0 20px rgba(150, 150, 255, 0.2);
opacity: 0;
animation: driftAndSparkle 15s infinite ease-in-out;
}
@keyframes driftAndSparkle {
0%, 100% {
opacity: 0;
transform: translateY(20px) scale(0.5);
}
10%, 90% { /* 显现和消失的过程 */
opacity: 0.8;
}
50% { /* 主要可见状态 */
opacity: 1;
transform: translateY(-20px) scale(1.1);
}
20%, 80% { /* 中途的闪烁增强 */
box-shadow: 0 0 10px rgba(220, 220, 255, 0.8),
0 0 18px rgba(200, 200, 255, 0.6),
0 0 30px rgba(180, 180, 255, 0.4);
}
}
/* 通过JS动态生成和定位sparkle元素效果更佳 */
/* 这里用CSS nth-child做少量示例 */
.sparkle:nth-child(1) { width: 3px; height: 3px; top: 15%; left: 20%; animation-duration: 18s; animation-delay: -2s; }
.sparkle:nth-child(2) { width: 2px; height: 2px; top: 40%; left: 80%; animation-duration: 14s; animation-delay: -5s; }
.sparkle:nth-child(3) { width: 4px; height: 4px; top: 70%; left: 50%; animation-duration: 20s; animation-delay: -8s; }
.sparkle:nth-child(4) { width: 2px; height: 2px; top: 85%; left: 10%; animation-duration: 16s; animation-delay: -1s; }
.sparkle:nth-child(5) { width: 3px; height: 3px; top: 5%; left: 60%; animation-duration: 22s; animation-delay: -12s; }
.sparkle:nth-child(6) { width: 2px; height: 2px; top: 55%; left: 30%; animation-duration: 17s; animation-delay: -6s; }
.sparkle:nth-child(7) { width: 4px; height: 4px; top: 30%; left: 90%; animation-duration: 19s; animation-delay: -9s; }
.sparkle:nth-child(8) { width: 3px; height: 3px; top: 75%; left: 70%; animation-duration: 15s; animation-delay: -3s; }
</style>
<div id="elegant-sparkle-background">
<div class="sparkle"></div> <div class="sparkle"></div>
<div class="sparkle"></div> <div class="sparkle"></div>
<div class="sparkle"></div> <div class="sparkle"></div>
<div class="sparkle"></div> <div class="sparkle"></div>
<!-- 用JS生成更多,随机化参数 -->
</div>
<script>
(function() {
const container = document.getElementById('elegant-sparkle-background');
if (!container) return;
const numSparkles = 25; // 控制光点数量
const existingSparkles = container.querySelectorAll('.sparkle').length;
for (let i = 0; i < numSparkles - existingSparkles; i++) {
const sparkle = document.createElement('div');
sparkle.classList.add('sparkle');
const size = Math.random() * 2.5 + 1.5; // 1.5px to 4px
sparkle.style.width = `${size}px`;
sparkle.style.height = `${size}px`;
sparkle.style.top = `${Math.random() * 100}%`;
sparkle.style.left = `${Math.random() * 100}%`;
const duration = Math.random() * 10 + 10; // 10s to 20s
const delay = -(Math.random() * duration); // 立即开始,但相位不同
sparkle.style.animationDuration = `${duration}s`;
sparkle.style.animationDelay = `${delay}s`;
// 随机漂移方向(通过自定义属性)
sparkle.style.setProperty('--drift-x', `${(Math.random() - 0.5) * 60}px`); // -30px to 30px
sparkle.style.setProperty('--drift-y', `${(Math.random() - 0.5) * 60}px`); // -30px to 30px
container.appendChild(sparkle);
}
// 更新CSS @keyframes 来使用这些自定义属性
// (或者在JS中直接修改transform,但CSS动画通常更流畅)
// 由于直接在@keyframes中引用父元素(sparkle)的var较复杂,
// 可以考虑每个sparkle元素内再套一个div,让这个内部div做transform动画
// 或者,更简单的方法是让动画本身包含通用的漂移,通过animation-delay和duration差异化
// 目前的 driftAndSparkle 动画是垂直漂移,可以通过多个keyframes或JS控制更复杂的漂移
})();
// 修正: 要让JS控制的随机漂移生效,需要修改@keyframes或采用不同策略。
// 为简化,当前CSS动画已包含垂直漂移。JS主要用于生成更多粒子和随机化基础动画参数。
</script>
@@ -0,0 +1,219 @@
<style>
#floating-bubbles-container {
width: 100%; height: 100%;
background: radial-gradient(circle at center, #2c3e50, #1a2533); /* 深蓝灰色径向渐变 */
overflow: hidden; position: relative;
}
#bubbles-canvas { display: block; position: absolute; top: 0; left: 0; }
</style>
<div id="floating-bubbles-container">
<canvas id="bubbles-canvas"></canvas>
</div>
<script>
(function() {
const canvas = document.getElementById('bubbles-canvas');
const layerElement = canvas.closest('.terminal-custom-html-layer') || canvas.parentElement;
if (!canvas || !layerElement) return;
const ctx = canvas.getContext('2d');
let width, height, animationFrameId;
const bubbles = [];
const numBubbles = 10; // 少量大气泡
const baseSpeed = 0.1;
const baseRadius = 60;
const radiusVariance = 40;
// Perlin noise function (simple implementation for distortion)
// For more sophisticated noise, consider a library
const ClassicalNoise = function(r) { // Classic Perlin noise
if (r == undefined) r = Math;
this.grad3 = [[1,1,0],[-1,1,0],[1,-1,0],[-1,-1,0],
[1,0,1],[-1,0,1],[1,0,-1],[-1,0,-1],
[0,1,1],[0,-1,1],[0,1,-1],[0,-1,-1]];
this.p = [];
for (var i=0; i<256; i++) {
this.p[i] = Math.floor(r.random()*256);
}
// To remove the need for index wrapping, double the permutation table length
this.perm = [];
for(var i=0; i<512; i++) {
this.perm[i]=this.p[i&255];
}
};
ClassicalNoise.prototype.dot = function(g, x, y, z) {
return g[0]*x + g[1]*y + g[2]*z;
};
ClassicalNoise.prototype.mix = function(a, b, t) {
return (1.0-t)*a + t*b;
};
ClassicalNoise.prototype.fade = function(t) {
return t*t*t*(t*(t*6.0-15.0)+10.0);
};
ClassicalNoise.prototype.noise = function(x, y, z) {
// Find unit grid cell containing point
var X = Math.floor(x);
var Y = Math.floor(y);
var Z = Math.floor(z);
// Get relative xyz coordinates of point within that cell
x = x - X;
y = y - Y;
z = z - Z;
// Wrap the integer cells at 255 (smaller integer period can be introduced here)
X = X & 255;
Y = Y & 255;
Z = Z & 255;
// Calculate a set of eight hashed gradient indices
var gi000 = this.perm[X+this.perm[Y+this.perm[Z]]];
var gi001 = this.perm[X+this.perm[Y+this.perm[Z+1]]];
var gi010 = this.perm[X+this.perm[Y+1+this.perm[Z]]];
var gi011 = this.perm[X+this.perm[Y+1+this.perm[Z+1]]];
var gi100 = this.perm[X+1+this.perm[Y+this.perm[Z]]];
var gi101 = this.perm[X+1+this.perm[Y+this.perm[Z+1]]];
var gi110 = this.perm[X+1+this.perm[Y+1+this.perm[Z]]];
var gi111 = this.perm[X+1+this.perm[Y+1+this.perm[Z+1]]];
// Calculate noise contributions from eight corners
var n000= this.dot(this.grad3[gi000%12], x, y, z);
var n100= this.dot(this.grad3[gi100%12], x-1, y, z);
var n010= this.dot(this.grad3[gi010%12], x, y-1, z);
var n110= this.dot(this.grad3[gi110%12], x-1, y-1, z);
var n001= this.dot(this.grad3[gi001%12], x, y, z-1);
var n101= this.dot(this.grad3[gi101%12], x-1, y, z-1);
var n011= this.dot(this.grad3[gi011%12], x, y-1, z-1);
var n111= this.dot(this.grad3[gi111%12], x-1, y-1, z-1);
// Compute the fade curve value for x, y, z
var u = this.fade(x);
var v = this.fade(y);
var w = this.fade(z);
// Interpolate along x the contributions from each of the corners
var nx00 = this.mix(n000, n100, u);
var nx01 = this.mix(n001, n101, u);
var nx10 = this.mix(n010, n110, u);
var nx11 = this.mix(n011, n111, u);
// Interpolate the four results along y
var nxy0 = this.mix(nx00, nx10, v);
var nxy1 = this.mix(nx01, nx11, v);
// Interpolate the two last results along z
var nxyz = this.mix(nxy0, nxy1, w);
return nxyz;
};
const perlin = new ClassicalNoise();
let noiseTime = 0;
class Bubble {
constructor() {
this.x = Math.random() * width;
this.y = Math.random() * height;
this.radius = baseRadius + Math.random() * radiusVariance;
this.color1 = `rgba(${Math.floor(Math.random()*50+50)}, ${Math.floor(Math.random()*50+100)}, ${Math.floor(Math.random()*50+150)}, 0.1)`; // Blues/Greens
this.color2 = `rgba(${Math.floor(Math.random()*50+100)}, ${Math.floor(Math.random()*50+50)}, ${Math.floor(Math.random()*50+120)}, 0.2)`; // Purples/Pinks
this.vx = (Math.random() - 0.5) * baseSpeed;
this.vy = (Math.random() - 0.5) * baseSpeed;
this.numPoints = 30 + Math.floor(Math.random() * 20); // 组成气泡边缘的点数
this.distortionFactor = 0.1 + Math.random() * 0.2; // 变形程度
this.noiseSeedX = Math.random() * 1000;
this.noiseSeedY = Math.random() * 1000;
}
update() {
this.x += this.vx;
this.y += this.vy;
if (this.x - this.radius > width) this.x = -this.radius;
if (this.x + this.radius < 0) this.x = width + this.radius;
if (this.y - this.radius > height) this.y = -this.radius;
if (this.y + this.radius < 0) this.y = height + this.radius;
}
draw() {
ctx.beginPath();
const points = [];
for (let i = 0; i < this.numPoints; i++) {
const angle = (i / this.numPoints) * Math.PI * 2;
const noiseVal = perlin.noise(
(Math.cos(angle) + 1) * 0.5 + this.noiseSeedX + noiseTime * 0.1, // x for noise
(Math.sin(angle) + 1) * 0.5 + this.noiseSeedY + noiseTime * 0.1, // y for noise
noiseTime * 0.2 // z for noise (time evolution)
);
const r = this.radius * (1 + noiseVal * this.distortionFactor);
points.push({
x: this.x + r * Math.cos(angle),
y: this.y + r * Math.sin(angle)
});
}
ctx.moveTo(points[0].x, points[0].y);
for (let i = 0; i < this.numPoints; i++) {
const p1 = points[i];
const p2 = points[(i + 1) % this.numPoints];
const xc = (p1.x + p2.x) / 2;
const yc = (p1.y + p2.y) / 2;
ctx.quadraticCurveTo(p1.x, p1.y, xc, yc);
}
ctx.closePath();
const gradient = ctx.createRadialGradient(this.x, this.y, this.radius * 0.2, this.x, this.y, this.radius);
gradient.addColorStop(0, this.color1);
gradient.addColorStop(1, this.color2);
ctx.fillStyle = gradient;
ctx.globalAlpha = 0.4 + Math.sin(noiseTime + this.noiseSeedX) * 0.2; // Subtle opacity pulse
ctx.fill();
// Optional: subtle highlight
ctx.beginPath();
ctx.arc(this.x - this.radius * 0.3, this.y - this.radius * 0.3, this.radius * 0.3, 0, Math.PI * 2);
ctx.fillStyle = "rgba(200, 220, 255, 0.05)";
ctx.fill();
ctx.globalAlpha = 1;
}
}
function init() {
width = layerElement.offsetWidth;
height = layerElement.offsetHeight;
canvas.width = width;
canvas.height = height;
bubbles.length = 0;
for (let i = 0; i < numBubbles; i++) {
bubbles.push(new Bubble());
}
}
function animate() {
ctx.clearRect(0, 0, width, height);
noiseTime += 0.005;
bubbles.forEach(bubble => {
bubble.update();
bubble.draw();
});
animationFrameId = requestAnimationFrame(animate);
}
let resizeTimeout;
function onResize() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
if (animationFrameId) cancelAnimationFrame(animationFrameId);
init();
animate();
}, 250);
}
init();
animate();
window.addEventListener('resize', onResize);
canvas.cleanup = function() { if (animationFrameId) cancelAnimationFrame(animationFrameId); };
})();
</script>
@@ -0,0 +1,72 @@
<style>
#css-gradient-mesh-background {
width: 100%;
height: 100%;
background-color: #1a1a2e; /* 基础背景色 */
overflow: hidden;
position: relative;
}
.gradient-blob {
position: absolute;
border-radius: 50%; /* 圆形 */
opacity: 0; /* 初始不可见,通过动画显现 */
mix-blend-mode: screen; /* 或者 'overlay', 'soft-light' 尝试不同效果 */
animation: flowAndFade 40s infinite ease-in-out;
filter: blur(80px); /* 大量模糊制造柔和边缘 */
}
.blob-1 {
width: 60vw; height: 60vw; /* 相对视窗大小 */
top: -20%; left: -10%;
background: radial-gradient(ellipse at center, #ff8a00 0%, transparent 70%); /* 橙色 */
animation-delay: 0s;
animation-duration: 45s;
}
.blob-2 {
width: 50vw; height: 50vw;
top: 10%; right: -15%;
background: radial-gradient(ellipse at center, #e91e63 0%, transparent 70%); /* 粉色 */
animation-delay: -10s;
animation-duration: 50s;
}
.blob-3 {
width: 70vw; height: 70vw;
bottom: -25%; left: 20%;
background: radial-gradient(ellipse at center, #00bcd4 0%, transparent 70%); /* 青色 */
animation-delay: -20s;
animation-duration: 55s;
}
.blob-4 {
width: 55vw; height: 55vw;
bottom: 5%; right: 5%;
background: radial-gradient(ellipse at center, #8bc34a 0%, transparent 70%); /* 绿色 */
animation-delay: -30s;
animation-duration: 40s;
}
@keyframes flowAndFade {
0%, 100% {
transform: translateX(0px) translateY(0px) scale(0.8);
opacity: 0.3;
}
25% {
transform: translateX(50px) translateY(-30px) scale(1.1);
opacity: 0.7;
}
50% {
transform: translateX(-40px) translateY(60px) scale(0.9);
opacity: 0.5;
}
75% {
transform: translateX(20px) translateY(-50px) scale(1.2);
opacity: 0.6;
}
}
</style>
<div id="css-gradient-mesh-background">
<div class="gradient-blob blob-1"></div>
<div class="gradient-blob blob-2"></div>
<div class="gradient-blob blob-3"></div>
<div class="gradient-blob blob-4"></div>
</div>
@@ -0,0 +1,115 @@
<style>
#canvas-stardust-container {
width: 100%; height: 100%; background: #020208; /* 非常暗的蓝黑色 */
overflow: hidden; position: relative;
}
#stardust-canvas { display: block; position: absolute; top: 0; left: 0; }
</style>
<div id="canvas-stardust-container">
<canvas id="stardust-canvas"></canvas>
</div>
<script>
(function() {
const canvas = document.getElementById('stardust-canvas');
const layerElement = canvas.closest('.terminal-custom-html-layer') || canvas.parentElement;
if (!canvas || !layerElement) return;
const ctx = canvas.getContext('2d');
let width, height, particles = [];
const particleCount = 300; // 粒子数量,根据性能调整
const baseSpeed = 0.3;
const lifeSpan = 200; // 粒子生命周期(帧数)
const baseRadius = 0.5;
const radiusVariance = 1.5;
const colors = ["#FFFFFF", "#DDDDFF", "#BBBBFF", "#9999EE"]; // 星尘颜色
class Particle {
constructor() {
this.reset();
}
reset() {
this.x = Math.random() * width;
this.y = Math.random() * height; // 初始位置随机
// 或者让粒子从一个方向进入
// this.x = Math.random() * width;
// this.y = height + Math.random() * 100; // 从底部进入
this.vx = (Math.random() - 0.5) * baseSpeed * 0.5; // x方向轻微随机漂移
this.vy = -baseSpeed - Math.random() * baseSpeed; // 主要向上流动
this.life = Math.random() * lifeSpan * 0.5 + lifeSpan * 0.5;
this.initialLife = this.life;
this.radius = baseRadius + Math.random() * radiusVariance;
this.color = colors[Math.floor(Math.random() * colors.length)];
}
update() {
this.x += this.vx;
this.y += this.vy;
this.life--;
if (this.life <= 0 || this.y < -this.radius || this.x < -this.radius || this.x > width + this.radius) {
this.reset();
}
}
draw() {
ctx.beginPath();
// 透明度随生命周期变化,实现淡入淡出
const blinkFactor = Math.sin( (this.initialLife - this.life) * 0.1 + Math.random() * Math.PI ); // 模拟闪烁
const alpha = (this.life / this.initialLife) * 0.5 + 0.3 * blinkFactor;
ctx.globalAlpha = Math.max(0, Math.min(1, alpha));
ctx.fillStyle = this.color;
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fill();
}
}
function init() {
width = layerElement.offsetWidth;
height = layerElement.offsetHeight;
canvas.width = width;
canvas.height = height;
particles = [];
for (let i = 0; i < particleCount; i++) {
particles.push(new Particle());
}
}
let animationFrameId;
function animate() {
ctx.clearRect(0, 0, width, height);
ctx.globalAlpha = 1; // Reset globalAlpha for clearing
particles.forEach(p => {
p.update();
p.draw();
});
ctx.globalAlpha = 1; // Reset globalAlpha after drawing all particles
animationFrameId = requestAnimationFrame(animate);
}
let resizeTimeout;
function onResize() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
if (animationFrameId) cancelAnimationFrame(animationFrameId);
init();
animate();
}, 250);
}
init();
animate();
window.addEventListener('resize', onResize);
canvas.cleanup = function() {
if (animationFrameId) cancelAnimationFrame(animationFrameId);
window.removeEventListener('resize', onResize);
console.log("Stardust canvas cleaned up");
};
})();
</script>
@@ -0,0 +1,29 @@
<style>
#static-grid-background {
width: 100%;
height: 100%;
background-color: #282c34;
--grid-color-small: rgba(100, 100, 100, 0.1);
--grid-size-small: 20px;
--grid-color-large: rgba(120, 120, 120, 0.15);
--grid-size-large: 100px;
background-image:
linear-gradient(to right, var(--grid-color-small) 1px, transparent 1px),
linear-gradient(to bottom, var(--grid-color-small) 1px, transparent 1px),
linear-gradient(to right, var(--grid-color-large) 1px, transparent 1px),
linear-gradient(to bottom, var(--grid-color-large) 1px, transparent 1px);
background-size:
var(--grid-size-small) var(--grid-size-small),
var(--grid-size-small) var(--grid-size-small),
var(--grid-size-large) var(--grid-size-large),
var(--grid-size-large) var(--grid-size-large);
}
</style>
<div id="static-grid-background">
</div>