Add semi-transparent blurred tab icon as background watermark
Large SVG icon on the right side of the viewport reflects the active tab, crossfades on tab switch. Also removes overflow:hidden from cards to fix color picker clipping. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -102,6 +102,35 @@ body.modal-open {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* ── Tab indicator (background watermark) ── */
|
||||
#tab-indicator {
|
||||
position: fixed;
|
||||
right: -10vw;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 55vw;
|
||||
height: 55vw;
|
||||
max-width: 600px;
|
||||
max-height: 600px;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
filter: blur(6px);
|
||||
color: var(--primary-color);
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
#tab-indicator svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.04;
|
||||
}
|
||||
#tab-indicator.tab-indicator-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
[data-theme="light"] #tab-indicator svg {
|
||||
opacity: 0.035;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { t, initLocale, changeLocale } from './core/i18n.js';
|
||||
// Layer 1.5: visual effects
|
||||
import { initCardGlare } from './core/card-glare.js';
|
||||
import { initBgAnim, updateBgAnimAccent, updateBgAnimTheme } from './core/bg-anim.js';
|
||||
import { initTabIndicator, updateTabIndicator } from './core/tab-indicator.js';
|
||||
|
||||
// Layer 2: ui
|
||||
import {
|
||||
@@ -173,6 +174,7 @@ Object.assign(window, {
|
||||
// visual effects (called from inline <script>)
|
||||
_updateBgAnimAccent: updateBgAnimAccent,
|
||||
_updateBgAnimTheme: updateBgAnimTheme,
|
||||
_updateTabIndicator: updateTabIndicator,
|
||||
|
||||
// core / ui
|
||||
toggleHint,
|
||||
@@ -539,6 +541,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
// Initialize visual effects
|
||||
initCardGlare();
|
||||
initBgAnim();
|
||||
initTabIndicator();
|
||||
updateBgAnimTheme(document.documentElement.getAttribute('data-theme') !== 'light');
|
||||
const accent = localStorage.getItem('accentColor') || '#4CAF50';
|
||||
updateBgAnimAccent(accent);
|
||||
|
||||
47
server/src/wled_controller/static/js/core/tab-indicator.js
Normal file
47
server/src/wled_controller/static/js/core/tab-indicator.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Tab indicator — large semi-transparent blurred icon on the right side
|
||||
* of the viewport, reflecting the currently active tab.
|
||||
*/
|
||||
|
||||
const TAB_SVGS = {
|
||||
dashboard: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/></svg>`,
|
||||
automations: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="M12 11h4"/><path d="M12 16h4"/><path d="M8 11h.01"/><path d="M8 16h.01"/></svg>`,
|
||||
targets: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/></svg>`,
|
||||
streams: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><path d="m17 2-5 5-5-5"/><rect width="20" height="15" x="2" y="7" rx="2"/></svg>`,
|
||||
};
|
||||
|
||||
let _el = null;
|
||||
let _currentTab = null;
|
||||
|
||||
function _ensureEl() {
|
||||
if (_el) return _el;
|
||||
_el = document.createElement('div');
|
||||
_el.id = 'tab-indicator';
|
||||
_el.setAttribute('aria-hidden', 'true');
|
||||
document.body.appendChild(_el);
|
||||
return _el;
|
||||
}
|
||||
|
||||
export function updateTabIndicator(tabName) {
|
||||
if (tabName === _currentTab) return;
|
||||
_currentTab = tabName;
|
||||
const svg = TAB_SVGS[tabName];
|
||||
if (!svg) return;
|
||||
|
||||
const el = _ensureEl();
|
||||
// Trigger crossfade: set opacity 0, swap content, fade in
|
||||
el.classList.remove('tab-indicator-visible');
|
||||
setTimeout(() => {
|
||||
el.innerHTML = svg;
|
||||
el.classList.add('tab-indicator-visible');
|
||||
}, 200);
|
||||
}
|
||||
|
||||
export function initTabIndicator() {
|
||||
_ensureEl();
|
||||
// Set initial tab from current active button
|
||||
const active = document.querySelector('.tab-btn.active');
|
||||
if (active) {
|
||||
updateTabIndicator(active.dataset.tab);
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,9 @@ export function switchTab(name, { updateHash = true, skipLoad = false } = {}) {
|
||||
document.querySelectorAll('.tab-panel').forEach(panel => panel.classList.toggle('active', panel.id === `tab-${name}`));
|
||||
localStorage.setItem('activeTab', name);
|
||||
|
||||
// Update background tab indicator
|
||||
if (typeof window._updateTabIndicator === 'function') window._updateTabIndicator(name);
|
||||
|
||||
// Restore scroll position for this tab
|
||||
requestAnimationFrame(() => window.scrollTo(0, _tabScrollPositions[name] || 0));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user