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:
@@ -102,6 +102,37 @@ body.modal-open {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* When bg-anim is active, make entity cards slightly translucent
|
||||||
|
so the shader bleeds through. Only target cards — NOT modals,
|
||||||
|
pickers, tab bars, headers, or other chrome. */
|
||||||
|
[data-bg-anim="on"][data-theme="dark"] .card,
|
||||||
|
[data-bg-anim="on"][data-theme="dark"] .template-card,
|
||||||
|
[data-bg-anim="on"][data-theme="dark"] .add-device-card,
|
||||||
|
[data-bg-anim="on"][data-theme="dark"] .dashboard-target {
|
||||||
|
background: rgba(45, 45, 45, 0.88);
|
||||||
|
}
|
||||||
|
[data-bg-anim="on"][data-theme="light"] .card,
|
||||||
|
[data-bg-anim="on"][data-theme="light"] .template-card,
|
||||||
|
[data-bg-anim="on"][data-theme="light"] .add-device-card,
|
||||||
|
[data-bg-anim="on"][data-theme="light"] .dashboard-target {
|
||||||
|
background: rgba(255, 255, 255, 0.85);
|
||||||
|
}
|
||||||
|
/* Blur behind header via pseudo-element — applying backdrop-filter directly
|
||||||
|
to header would create a containing block and break position:fixed on
|
||||||
|
the .tab-bar nested inside it (mobile bottom nav). */
|
||||||
|
[data-bg-anim="on"] header {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
[data-bg-anim="on"] header::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: -1;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
background: color-mix(in srgb, var(--bg-color) 60%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Tab indicator (background watermark) ── */
|
/* ── Tab indicator (background watermark) ── */
|
||||||
#tab-indicator {
|
#tab-indicator {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -128,7 +159,7 @@ body.modal-open {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
[data-theme="light"] #tab-indicator svg {
|
[data-theme="light"] #tab-indicator svg {
|
||||||
opacity: 0.035;
|
opacity: 0.07;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ section {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 2px dashed var(--border-color);
|
border: 2px dashed var(--border-color);
|
||||||
background: transparent;
|
background: var(--card-bg);
|
||||||
min-height: 160px;
|
min-height: 160px;
|
||||||
transition: border-color 0.25s ease, background 0.25s ease, transform 0.2s ease;
|
transition: border-color 0.25s ease, background 0.25s ease, transform 0.2s ease;
|
||||||
}
|
}
|
||||||
@@ -34,8 +34,8 @@ section {
|
|||||||
|
|
||||||
.add-device-icon {
|
.add-device-icon {
|
||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
font-weight: 300;
|
font-weight: 400;
|
||||||
color: var(--text-secondary);
|
color: var(--text-color);
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
transition: color 0.2s;
|
transition: color 0.2s;
|
||||||
}
|
}
|
||||||
@@ -156,14 +156,25 @@ section {
|
|||||||
to { --border-angle: 360deg; }
|
to { --border-angle: 360deg; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-theme="light"] .card-running {
|
||||||
|
background: linear-gradient(
|
||||||
|
calc(var(--border-angle) + 45deg),
|
||||||
|
var(--card-bg) 0%,
|
||||||
|
color-mix(in srgb, var(--primary-color) 18%, var(--card-bg)) 40%,
|
||||||
|
var(--card-bg) 60%,
|
||||||
|
color-mix(in srgb, var(--primary-color) 14%, var(--card-bg)) 85%,
|
||||||
|
var(--card-bg) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
[data-theme="light"] .card-running::before {
|
[data-theme="light"] .card-running::before {
|
||||||
background:
|
background:
|
||||||
conic-gradient(
|
conic-gradient(
|
||||||
from var(--border-angle),
|
from var(--border-angle),
|
||||||
var(--primary-color),
|
var(--primary-color),
|
||||||
rgba(0,0,0,0.05) 25%,
|
rgba(0,0,0,0.12) 25%,
|
||||||
var(--primary-color) 50%,
|
var(--primary-color) 50%,
|
||||||
rgba(0,0,0,0.05) 75%,
|
rgba(0,0,0,0.12) 75%,
|
||||||
var(--primary-color)
|
var(--primary-color)
|
||||||
) border-box;
|
) border-box;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ uniform float u_time;
|
|||||||
uniform vec2 u_res;
|
uniform vec2 u_res;
|
||||||
uniform vec3 u_accent;
|
uniform vec3 u_accent;
|
||||||
uniform vec3 u_bg;
|
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
|
uniform vec3 u_particles[${PARTICLE_COUNT}]; // xy = position (0-1), z = radius
|
||||||
|
|
||||||
// Simplex-style noise
|
// Simplex-style noise
|
||||||
@@ -72,8 +73,12 @@ void main() {
|
|||||||
float colorMix = n2 * 0.5 + 0.5;
|
float colorMix = n2 * 0.5 + 0.5;
|
||||||
vec3 color = mix(col1, col2, colorMix);
|
vec3 color = mix(col1, col2, colorMix);
|
||||||
|
|
||||||
// Noise background
|
float boost = 1.0 + u_light * 1.2; // 1.0 dark, 2.2 light
|
||||||
vec3 result = mix(u_bg, u_bg + color * 0.6, glow * 0.14);
|
|
||||||
|
// 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
|
// Floating particles — accumulate glow from each
|
||||||
float particleGlow = 0.0;
|
float particleGlow = 0.0;
|
||||||
@@ -90,20 +95,24 @@ void main() {
|
|||||||
particleColor += g * step(0.5, fract(float(i) * 0.5));
|
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));
|
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);
|
gl_FragColor = vec4(result, 1.0);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
let _canvas, _gl, _prog;
|
let _canvas, _gl, _prog;
|
||||||
let _uTime, _uRes, _uAccent, _uBg, _uParticles;
|
let _uTime, _uRes, _uAccent, _uBg, _uLight, _uParticles;
|
||||||
let _raf = null;
|
let _raf = null;
|
||||||
let _startTime = 0;
|
let _startTime = 0;
|
||||||
let _accent = [76 / 255, 175 / 255, 80 / 255];
|
let _accent = [76 / 255, 175 / 255, 80 / 255];
|
||||||
let _bgColor = [26 / 255, 26 / 255, 26 / 255];
|
let _bgColor = [26 / 255, 26 / 255, 26 / 255];
|
||||||
|
let _isLight = 0.0;
|
||||||
|
|
||||||
// Particle state (CPU-side, positions in 0..1 UV space)
|
// Particle state (CPU-side, positions in 0..1 UV space)
|
||||||
const _particles = [];
|
const _particles = [];
|
||||||
@@ -174,6 +183,7 @@ function _initGL() {
|
|||||||
_uRes = gl.getUniformLocation(_prog, 'u_res');
|
_uRes = gl.getUniformLocation(_prog, 'u_res');
|
||||||
_uAccent = gl.getUniformLocation(_prog, 'u_accent');
|
_uAccent = gl.getUniformLocation(_prog, 'u_accent');
|
||||||
_uBg = gl.getUniformLocation(_prog, 'u_bg');
|
_uBg = gl.getUniformLocation(_prog, 'u_bg');
|
||||||
|
_uLight = gl.getUniformLocation(_prog, 'u_light');
|
||||||
_uParticles = [];
|
_uParticles = [];
|
||||||
for (let i = 0; i < PARTICLE_COUNT; i++) {
|
for (let i = 0; i < PARTICLE_COUNT; i++) {
|
||||||
_uParticles.push(gl.getUniformLocation(_prog, `u_particles[${i}]`));
|
_uParticles.push(gl.getUniformLocation(_prog, `u_particles[${i}]`));
|
||||||
@@ -202,6 +212,7 @@ function _draw(time) {
|
|||||||
gl.uniform2f(_uRes, _canvas.width, _canvas.height);
|
gl.uniform2f(_uRes, _canvas.width, _canvas.height);
|
||||||
gl.uniform3f(_uAccent, _accent[0], _accent[1], _accent[2]);
|
gl.uniform3f(_uAccent, _accent[0], _accent[1], _accent[2]);
|
||||||
gl.uniform3f(_uBg, _bgColor[0], _bgColor[1], _bgColor[2]);
|
gl.uniform3f(_uBg, _bgColor[0], _bgColor[1], _bgColor[2]);
|
||||||
|
gl.uniform1f(_uLight, _isLight);
|
||||||
|
|
||||||
for (let i = 0; i < PARTICLE_COUNT; i++) {
|
for (let i = 0; i < PARTICLE_COUNT; i++) {
|
||||||
const p = _particles[i];
|
const p = _particles[i];
|
||||||
@@ -241,6 +252,7 @@ export function updateBgAnimAccent(hex) {
|
|||||||
|
|
||||||
export function updateBgAnimTheme(isDark) {
|
export function updateBgAnimTheme(isDark) {
|
||||||
_bgColor = isDark ? [26 / 255, 26 / 255, 26 / 255] : [245 / 255, 245 / 255, 245 / 255];
|
_bgColor = isDark ? [26 / 255, 26 / 255, 26 / 255] : [245 / 255, 245 / 255, 245 / 255];
|
||||||
|
_isLight = isDark ? 0.0 : 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initBgAnim() {
|
export function initBgAnim() {
|
||||||
|
|||||||
@@ -88,15 +88,25 @@ window._cpToggle = function (id) {
|
|||||||
const card = pop.closest('.card, .template-card');
|
const card = pop.closest('.card, .template-card');
|
||||||
if (card) card.classList.toggle('cp-elevated', true);
|
if (card) card.classList.toggle('cp-elevated', true);
|
||||||
|
|
||||||
// On small screens, if inside an overflow container (e.g. header toolbar),
|
// On small/touch screens, move popover to body as a centered bottom-sheet
|
||||||
// switch to fixed positioning so the popover isn't clipped.
|
// to avoid any parent overflow/containment/positioning issues.
|
||||||
const wrapper = pop.closest('.color-picker-wrapper');
|
const isMobile = window.innerWidth <= 768 || ('ontouchstart' in window);
|
||||||
if (wrapper && window.innerWidth <= 600) {
|
if (isMobile && pop.parentElement !== document.body) {
|
||||||
const rect = wrapper.getBoundingClientRect();
|
pop._cpOrigParent = pop.parentElement;
|
||||||
|
pop._cpOrigNext = pop.nextSibling;
|
||||||
|
document.body.appendChild(pop);
|
||||||
|
}
|
||||||
|
if (isMobile) {
|
||||||
pop.style.position = 'fixed';
|
pop.style.position = 'fixed';
|
||||||
pop.style.top = (rect.bottom + 4) + 'px';
|
pop.style.left = '0';
|
||||||
pop.style.right = Math.max(4, window.innerWidth - rect.right) + 'px';
|
pop.style.right = '0';
|
||||||
pop.style.left = 'auto';
|
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');
|
pop.classList.add('cp-fixed');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,6 +128,18 @@ function _cpClosePopover(pop) {
|
|||||||
pop.style.top = '';
|
pop.style.top = '';
|
||||||
pop.style.right = '';
|
pop.style.right = '';
|
||||||
pop.style.left = '';
|
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');
|
const card = pop.closest('.card, .template-card');
|
||||||
if (card) card.classList.remove('cp-elevated');
|
if (card) card.classList.remove('cp-elevated');
|
||||||
|
|||||||
Reference in New Issue
Block a user