Improve background effects for light theme and fix mobile color picker

- WebGL shader: theme-aware blending (tint toward accent on light, additive
  glow on dark) with u_light uniform for proper light-theme visibility
- Cards: translucent backgrounds only on entity cards when bg-anim is active,
  keeping modals/pickers/tab bars/header fully opaque
- Running card border and tab indicator: boosted contrast for light theme
- Header: backdrop-filter via pseudo-element to avoid breaking fixed tab-bar
- Color picker: move popover to document.body on mobile as centered bottom-sheet
- Add card: use --card-bg background and bolder + icon for visibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 21:42:22 +03:00
parent b9c71d5bb9
commit 812d15419c
4 changed files with 95 additions and 19 deletions

View File

@@ -18,6 +18,7 @@ uniform float u_time;
uniform vec2 u_res;
uniform vec3 u_accent;
uniform vec3 u_bg;
uniform float u_light; // 0.0 = dark theme, 1.0 = light theme
uniform vec3 u_particles[${PARTICLE_COUNT}]; // xy = position (0-1), z = radius
// Simplex-style noise
@@ -72,8 +73,12 @@ void main() {
float colorMix = n2 * 0.5 + 0.5;
vec3 color = mix(col1, col2, colorMix);
// Noise background
vec3 result = mix(u_bg, u_bg + color * 0.6, glow * 0.14);
float boost = 1.0 + u_light * 1.2; // 1.0 dark, 2.2 light
// Noise background: additive glow on dark, tint toward accent on light
vec3 noiseDark = mix(u_bg, u_bg + color * 0.6, glow * 0.14);
vec3 noiseLight = mix(u_bg, u_accent * 0.82, glow * 0.14 * boost);
vec3 result = mix(noiseDark, noiseLight, u_light);
// Floating particles — accumulate glow from each
float particleGlow = 0.0;
@@ -90,20 +95,24 @@ void main() {
particleColor += g * step(0.5, fract(float(i) * 0.5));
}
// Mix particle colors: accent and white-ish accent
// Dark: additive white-ish accent glow; Light: blend toward accent color
vec3 pCol = mix(u_accent, mix(u_accent, vec3(1.0), 0.5), particleColor / max(particleGlow, 0.001));
result += pCol * particleGlow * 0.5;
float pI = particleGlow * 0.5;
vec3 pDark = result + pCol * pI;
vec3 pLight = mix(result, u_accent * 0.7, pI * boost * 0.35);
result = mix(pDark, pLight, u_light);
gl_FragColor = vec4(result, 1.0);
}
`;
let _canvas, _gl, _prog;
let _uTime, _uRes, _uAccent, _uBg, _uParticles;
let _uTime, _uRes, _uAccent, _uBg, _uLight, _uParticles;
let _raf = null;
let _startTime = 0;
let _accent = [76 / 255, 175 / 255, 80 / 255];
let _bgColor = [26 / 255, 26 / 255, 26 / 255];
let _isLight = 0.0;
// Particle state (CPU-side, positions in 0..1 UV space)
const _particles = [];
@@ -174,6 +183,7 @@ function _initGL() {
_uRes = gl.getUniformLocation(_prog, 'u_res');
_uAccent = gl.getUniformLocation(_prog, 'u_accent');
_uBg = gl.getUniformLocation(_prog, 'u_bg');
_uLight = gl.getUniformLocation(_prog, 'u_light');
_uParticles = [];
for (let i = 0; i < PARTICLE_COUNT; i++) {
_uParticles.push(gl.getUniformLocation(_prog, `u_particles[${i}]`));
@@ -202,6 +212,7 @@ function _draw(time) {
gl.uniform2f(_uRes, _canvas.width, _canvas.height);
gl.uniform3f(_uAccent, _accent[0], _accent[1], _accent[2]);
gl.uniform3f(_uBg, _bgColor[0], _bgColor[1], _bgColor[2]);
gl.uniform1f(_uLight, _isLight);
for (let i = 0; i < PARTICLE_COUNT; i++) {
const p = _particles[i];
@@ -241,6 +252,7 @@ export function updateBgAnimAccent(hex) {
export function updateBgAnimTheme(isDark) {
_bgColor = isDark ? [26 / 255, 26 / 255, 26 / 255] : [245 / 255, 245 / 255, 245 / 255];
_isLight = isDark ? 0.0 : 1.0;
}
export function initBgAnim() {

View File

@@ -88,15 +88,25 @@ window._cpToggle = function (id) {
const card = pop.closest('.card, .template-card');
if (card) card.classList.toggle('cp-elevated', true);
// On small screens, if inside an overflow container (e.g. header toolbar),
// switch to fixed positioning so the popover isn't clipped.
const wrapper = pop.closest('.color-picker-wrapper');
if (wrapper && window.innerWidth <= 600) {
const rect = wrapper.getBoundingClientRect();
// On small/touch screens, move popover to body as a centered bottom-sheet
// to avoid any parent overflow/containment/positioning issues.
const isMobile = window.innerWidth <= 768 || ('ontouchstart' in window);
if (isMobile && pop.parentElement !== document.body) {
pop._cpOrigParent = pop.parentElement;
pop._cpOrigNext = pop.nextSibling;
document.body.appendChild(pop);
}
if (isMobile) {
pop.style.position = 'fixed';
pop.style.top = (rect.bottom + 4) + 'px';
pop.style.right = Math.max(4, window.innerWidth - rect.right) + 'px';
pop.style.left = 'auto';
pop.style.left = '0';
pop.style.right = '0';
pop.style.top = 'auto';
pop.style.bottom = '70px';
pop.style.margin = '0 auto';
pop.style.width = 'fit-content';
pop.style.transform = 'none';
pop.style.animation = 'none';
pop.style.zIndex = '10000';
pop.classList.add('cp-fixed');
}
@@ -118,6 +128,18 @@ function _cpClosePopover(pop) {
pop.style.top = '';
pop.style.right = '';
pop.style.left = '';
pop.style.bottom = '';
pop.style.transform = '';
pop.style.animation = '';
pop.style.margin = '';
pop.style.width = '';
pop.style.zIndex = '';
// Return popover to its original parent
if (pop._cpOrigParent) {
pop._cpOrigParent.insertBefore(pop, pop._cpOrigNext || null);
delete pop._cpOrigParent;
delete pop._cpOrigNext;
}
}
const card = pop.closest('.card, .template-card');
if (card) card.classList.remove('cp-elevated');