Add optional ambient animated background with toggle

Three blurred color blobs (green, blue, purple) slowly drift behind
the UI for atmosphere. Toggled via cloud icon in header, persisted
in localStorage, off by default. Works with both dark and light themes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 11:06:56 +03:00
parent 2f221f6219
commit 40ea2e3b99
5 changed files with 112 additions and 0 deletions

View File

@@ -29,6 +29,11 @@
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
</head>
<body style="visibility: hidden;">
<div id="bg-anim-layer">
<div class="bg-blob bg-blob-a"></div>
<div class="bg-blob bg-blob-b"></div>
<div class="bg-blob bg-blob-c"></div>
</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>
@@ -57,6 +62,9 @@
<button class="header-btn" onclick="openCommandPalette()" data-i18n-title="search.open" title="Search (Ctrl+K)">
<svg class="icon" viewBox="0 0 24 24"><path d="m21 21-4.34-4.34"/><circle cx="11" cy="11" r="8"/></svg>
</button>
<button class="header-btn" id="bg-anim-btn" onclick="toggleBgAnim()" data-i18n-title="bg.anim.toggle" title="Toggle ambient background">
<svg class="icon" viewBox="0 0 24 24"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z"/></svg>
</button>
<button class="header-btn" onclick="toggleTheme()" data-i18n-title="theme.toggle" title="Toggle theme">
<span id="theme-icon"><svg class="icon" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg></span>
</button>
@@ -205,6 +213,24 @@
<script type="module" src="/static/js/app.js"></script>
<script>
// Initialize ambient background
const savedBgAnim = localStorage.getItem('bgAnim') || 'off';
document.documentElement.setAttribute('data-bg-anim', savedBgAnim);
updateBgAnimBtn(savedBgAnim);
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);
}
function updateBgAnimBtn(state) {
const btn = document.getElementById('bg-anim-btn');
if (btn) btn.style.opacity = state === 'on' ? '1' : '0.5';
}
// Initialize theme
const savedTheme = localStorage.getItem('theme') || 'dark';
document.documentElement.setAttribute('data-theme', savedTheme);