From d2ca0d61cc5b333af40318e86623a203ad8dc13e Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Fri, 29 May 2026 21:27:35 +0300 Subject: [PATCH] fix(bg): add translucent veil so animated bgs don't bleed UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The single bg-fx layer was painting at full vibrancy behind the entire app. Most UI elements use rgba() fills — chips, sub-panels, the achievements .ach-item, the goal-tier bar — so saturated colors bled right through, hurting readability on the Достижения / dashboard / mocks tabs. Layered fix: • bg-fx drops to z-index:-2 (the animated layer) • new #ls-bg-veil sits on z-index:-1 with rgba(245,247,251,.78) (light) or rgba(15,23,42,.55) when body[data-bg-tone='dark'] • applyCosmetics injects both elements and tags the body with bg-tone based on the slug (dark/stars/aurora/nebula/grid go dark, everything else light) • clearing the bg removes both layers + the tone attribute Result: animations stay perceptible (~22% of the chosen palette comes through the veil), but the page chrome reads at normal contrast. Shop swatches keep full vibrancy — the .bg-preview is meant to show the raw palette so users can compare. Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/css/ls.css | 20 +++++++++++++++++++- js/api.js | 24 +++++++++++++++++++----- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/frontend/css/ls.css b/frontend/css/ls.css index e87bc33..1c325ee 100644 --- a/frontend/css/ls.css +++ b/frontend/css/ls.css @@ -1211,12 +1211,30 @@ body.no-gamification [data-gamified] { display: none !important; } #ls-bg-fx { position: fixed; inset: 0; - z-index: -1; + z-index: -2; pointer-events: none; overflow: hidden; will-change: opacity, transform, background-position; transition: opacity .4s ease; } +/* The veil: a translucent white sheet that sits between the animated + bg layer and page content. Without it, vibrant backgrounds bleed + straight through every UI element that uses an rgba() fill — chips, + cards, panel backdrops. With it, the animation reads as a subtle + accent rather than a takeover. */ +#ls-bg-veil { + position: fixed; + inset: 0; + z-index: -1; + pointer-events: none; + background: rgba(245, 247, 251, 0.78); + transition: background .4s ease; +} +/* On the 'dark' preset we want a darker veil so light content still + pops. body[data-bg-tone="dark"] is set by api.js for dark slugs. */ +body[data-bg-tone="dark"] #ls-bg-veil { + background: rgba(15, 23, 42, 0.55); +} .bg-preview { position: relative; width: 100%; diff --git a/js/api.js b/js/api.js index f751a98..a4bf2e4 100644 --- a/js/api.js +++ b/js/api.js @@ -1080,19 +1080,33 @@ async function applyCosmetics() { // ── Background: paint a fixed div behind the whole UI ── // The element is created on demand and reused on subsequent calls - // so swapping backgrounds doesn't flash. + // so swapping backgrounds doesn't flash. A second fixed div sits + // ON TOP of it (#ls-bg-veil, z-index:-1 vs bg-fx -2) acting as a + // translucent veil so vibrant animations don't drown out the UI. + // The tone attr on lets the veil darken for dark presets. + const DARK_BG_SLUGS = new Set(['dark', 'stars', 'aurora', 'nebula', 'grid']); if (c.background && c.background.slug && c.background.slug !== 'none') { + const slug = String(c.background.slug).replace(/[^a-z0-9_-]/gi, ''); let bgEl = document.getElementById('ls-bg-fx'); if (!bgEl) { bgEl = document.createElement('div'); bgEl.id = 'ls-bg-fx'; document.body.insertBefore(bgEl, document.body.firstChild); } - bgEl.className = 'bg-' + String(c.background.slug).replace(/[^a-z0-9_-]/gi, ''); + bgEl.className = 'bg-' + slug; + let veilEl = document.getElementById('ls-bg-veil'); + if (!veilEl) { + veilEl = document.createElement('div'); + veilEl.id = 'ls-bg-veil'; + // Insert just after bg-fx so it paints above (same z-index gap). + bgEl.parentNode.insertBefore(veilEl, bgEl.nextSibling); + } + document.body.dataset.bgTone = DARK_BG_SLUGS.has(slug) ? 'dark' : 'light'; } else { - // Active bg was cleared — remove the element if present. - const bgEl = document.getElementById('ls-bg-fx'); - if (bgEl) bgEl.remove(); + // Active bg was cleared — remove both layers. + document.getElementById('ls-bg-fx')?.remove(); + document.getElementById('ls-bg-veil')?.remove(); + delete document.body.dataset.bgTone; } // ── Frame: apply CSS to sidebar avatar on every page ──