guanghulab/frontend/ftchat/theme-toggle.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

121 lines
3.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ═══════════════════════════════════════════════════════════
* 主题切换 + 星空动态背景
* 守护: 铸渊 · ICE-GL-ZY001
* ═══════════════════════════════════════════════════════════ */
(function () {
'use strict';
var THEME_KEY = 'ftchat_theme';
var body = document.body;
var toggleBtn = document.getElementById('themeToggle');
var canvas = document.getElementById('starfield');
var ctx = canvas.getContext('2d');
// ── 初始化主题 ──
function applyTheme(theme) {
body.setAttribute('data-theme', theme);
try { localStorage.setItem(THEME_KEY, theme); } catch (_e) { /* ignore */ }
var meta = document.querySelector('meta[name="theme-color"]');
if (meta) meta.setAttribute('content', theme === 'dark' ? '#0a0e27' : '#f5f7fc');
}
var savedTheme = null;
try { savedTheme = localStorage.getItem(THEME_KEY); } catch (_e) { /* ignore */ }
if (!savedTheme) {
savedTheme = (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) ? 'light' : 'dark';
}
applyTheme(savedTheme);
toggleBtn.addEventListener('click', function () {
var cur = body.getAttribute('data-theme');
applyTheme(cur === 'dark' ? 'light' : 'dark');
});
// ─── 星空粒子动画 ───
var stars = [];
var dpr = Math.min(window.devicePixelRatio || 1, 2);
var width = 0, height = 0;
var isMobile = window.innerWidth <= 768;
var STAR_COUNT = isMobile ? 40 : 90;
function resize() {
width = window.innerWidth;
height = window.innerHeight;
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
canvas.width = Math.floor(width * dpr);
canvas.height = Math.floor(height * dpr);
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
function spawn() {
return {
x: Math.random() * width,
y: Math.random() * height,
r: Math.random() * 1.4 + 0.3,
vx: (Math.random() - 0.5) * 0.08,
vy: (Math.random() - 0.5) * 0.08,
tw: Math.random() * Math.PI * 2 // twinkle phase
};
}
function init() {
resize();
stars = [];
for (var i = 0; i < STAR_COUNT; i++) stars.push(spawn());
}
var lastFrame = 0;
var FRAME_BUDGET = 1000 / 30; // 30fps省电
function tick(ts) {
if (ts - lastFrame < FRAME_BUDGET) {
requestAnimationFrame(tick);
return;
}
lastFrame = ts;
ctx.clearRect(0, 0, width, height);
var theme = body.getAttribute('data-theme');
var baseColor = theme === 'dark' ? '255, 255, 255' : '126, 140, 224';
for (var i = 0; i < stars.length; i++) {
var s = stars[i];
s.x += s.vx; s.y += s.vy; s.tw += 0.02;
// 边界回绕
if (s.x < 0) s.x = width;
if (s.x > width) s.x = 0;
if (s.y < 0) s.y = height;
if (s.y > height) s.y = 0;
var alpha = (Math.sin(s.tw) * 0.4 + 0.6) * (theme === 'dark' ? 0.8 : 0.35);
ctx.fillStyle = 'rgba(' + baseColor + ',' + alpha.toFixed(3) + ')';
ctx.beginPath();
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
ctx.fill();
}
requestAnimationFrame(tick);
}
init();
requestAnimationFrame(tick);
// 窗口尺寸变化
var resizeTimer = null;
window.addEventListener('resize', function () {
if (resizeTimer) clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
isMobile = window.innerWidth <= 768;
var newCount = isMobile ? 40 : 90;
if (newCount !== STAR_COUNT) {
STAR_COUNT = newCount;
init();
} else {
resize();
}
}, 200);
});
})();