feat: add visual customization presets to Settings > Appearance tab
Some checks failed
Lint & Test / test (push) Failing after 30s

Add style presets (font + color combinations) and background effect
presets as a new Appearance tab in the settings modal. Style presets
include Default, Midnight, Ember, Arctic, Terminal, and Neon — each
with curated dark/light theme colors and Google Font pairings.
Background effects (Dot Grid, Gradient Mesh, Scanlines, Particles)
use a dedicated overlay div alongside the existing WebGL Noise Field.
All choices persist to localStorage and restore on page load.
This commit is contained in:
2026-03-23 15:42:08 +03:00
parent 1b5b04afaa
commit 73b2ee6222
9 changed files with 841 additions and 12 deletions

View File

@@ -20,6 +20,7 @@
<button class="demo-banner-dismiss" onclick="dismissDemoBanner()" aria-label="Dismiss">&times;</button>
</div>
<canvas id="bg-anim-canvas"></canvas>
<div id="bg-effect-layer"></div>
<div id="connection-overlay" class="connection-overlay" style="display:none" aria-hidden="true">
<div class="connection-overlay-content">
<div class="connection-spinner-lg"></div>
@@ -221,18 +222,46 @@
// Initialize ambient background
const savedBgAnim = localStorage.getItem('bgAnim') || 'off';
document.documentElement.setAttribute('data-bg-anim', savedBgAnim);
updateBgAnimBtn(savedBgAnim);
// All known CSS bg-effect classes (must match appearance.ts BG_EFFECT_PRESETS)
var _bgEffectClasses = ['bg-effect-grid', 'bg-effect-mesh', 'bg-effect-scanlines', 'bg-effect-particles'];
function toggleBgAnim() {
const cur = document.documentElement.getAttribute('data-bg-anim');
const next = cur === 'on' ? 'off' : 'on';
document.documentElement.setAttribute('data-bg-anim', next);
localStorage.setItem('bgAnim', next);
updateBgAnimBtn(next);
var savedEffect = localStorage.getItem('bgEffect') || 'none';
var isOn = _isBgEffectActive();
if (isOn) {
// Turn everything off
document.documentElement.setAttribute('data-bg-anim', 'off');
document.documentElement.removeAttribute('data-bg-effect');
var lyr = document.getElementById('bg-effect-layer');
if (lyr) _bgEffectClasses.forEach(function(c) { lyr.classList.remove(c); });
updateBgAnimBtn('off');
} else {
// Restore saved effect (or fallback to WebGL noise)
if (savedEffect === 'none') savedEffect = 'noise';
if (typeof window.applyBgEffect === 'function') {
window.applyBgEffect(savedEffect);
} else {
// Fallback before bundle loads: just toggle WebGL
document.documentElement.setAttribute('data-bg-anim', 'on');
}
updateBgAnimBtn('on');
}
}
function _isBgEffectActive() {
if (document.documentElement.getAttribute('data-bg-anim') === 'on') return true;
var lyr = document.getElementById('bg-effect-layer');
if (!lyr) return false;
for (var i = 0; i < _bgEffectClasses.length; i++) {
if (lyr.classList.contains(_bgEffectClasses[i])) return true;
}
return false;
}
function updateBgAnimBtn(state) {
const btn = document.getElementById('bg-anim-btn');
var btn = document.getElementById('bg-anim-btn');
if (btn) btn.style.opacity = state === 'on' ? '1' : '0.5';
}
@@ -341,12 +370,18 @@
const savedAccent = localStorage.getItem('accentColor');
if (savedAccent) applyAccentColor(savedAccent, true);
// Early-apply saved background effect class (before bundle loads)
// Early-apply saved background effect class on the dedicated layer element
const savedBgEffect = localStorage.getItem('bgEffect');
if (savedBgEffect && savedBgEffect !== 'none') {
const effectClasses = { grid: 'bg-effect-grid', mesh: 'bg-effect-mesh', scanlines: 'bg-effect-scanlines', particles: 'bg-effect-particles' };
if (effectClasses[savedBgEffect]) document.documentElement.classList.add(effectClasses[savedBgEffect]);
if (savedBgEffect && savedBgEffect !== 'none' && savedBgEffect !== 'noise') {
var effectClasses = { grid: 'bg-effect-grid', mesh: 'bg-effect-mesh', scanlines: 'bg-effect-scanlines', particles: 'bg-effect-particles' };
var layer = document.getElementById('bg-effect-layer');
if (layer && effectClasses[savedBgEffect]) {
layer.classList.add(effectClasses[savedBgEffect]);
document.documentElement.setAttribute('data-bg-effect', savedBgEffect);
}
}
// Set header toggle button state (reflects both WebGL and CSS effects)
updateBgAnimBtn(_isBgEffectActive() ? 'on' : 'off');
// Initialize auth state
function updateAuthUI() {