<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>LED Grab</title>
    <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='0.9em' font-size='90'>💡</text></svg>">
    <!-- PWA -->
    <meta name="theme-color" content="#1a1a1a">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <meta name="apple-mobile-web-app-title" content="LED Grab">
    <link rel="apple-touch-icon" href="/static/icons/icon-192.png">
    <link rel="manifest" href="/manifest.json">
    <link rel="stylesheet" href="/static/dist/app.bundle.css">
</head>
<body style="visibility: hidden;">
    <div class="demo-banner" id="demo-banner" style="display:none">
        <span data-i18n="demo.banner">You're in demo mode &mdash; all devices and data are virtual. No real hardware is used.</span>
        <button class="demo-banner-dismiss" onclick="dismissDemoBanner()" aria-label="Dismiss">&times;</button>
    </div>
    <canvas id="bg-anim-canvas"></canvas>
    <canvas id="bg-effect-canvas"></canvas>
    <div id="bg-effect-layer"></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>
                <div id="conn-msg-offline">
                    <h2 data-i18n="app.connection_lost">Server unreachable</h2>
                    <p data-i18n="app.connection_retrying">Attempting to reconnect…</p>
                </div>
                <div id="conn-msg-restarting" style="display:none">
                    <h2 data-i18n="app.server_restarting">Server restarting…</h2>
                    <p data-i18n="app.server_restarting_sub">Please wait, the server will be back shortly.</p>
                </div>
            </div>
        </div>
        <header>
            <div class="header-title">
                <span id="server-status" class="status-badge">●</span>
                <h1 data-i18n="app.title">LED Grab</h1>
                <span id="server-version"><span id="version-number"></span></span>
                <span class="demo-badge" id="demo-badge" style="display:none" data-i18n="demo.badge">DEMO</span>
            </div>
            <div class="tab-bar" role="tablist">
                <button class="tab-btn" data-tab="dashboard" onclick="switchTab('dashboard')" role="tab" aria-selected="true" aria-controls="tab-dashboard" id="tab-btn-dashboard" title="Ctrl+1"><svg class="icon" viewBox="0 0 24 24"><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> <span data-i18n="dashboard.title">Dashboard</span></button>
                <button class="tab-btn" data-tab="automations" onclick="switchTab('automations')" role="tab" aria-selected="false" aria-controls="tab-automations" id="tab-btn-automations" title="Ctrl+2"><svg class="icon" viewBox="0 0 24 24"><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> <span data-i18n="automations.title">Automations</span></button>
                <button class="tab-btn" data-tab="targets" onclick="switchTab('targets')" role="tab" aria-selected="false" aria-controls="tab-targets" id="tab-btn-targets" title="Ctrl+3"><svg class="icon" viewBox="0 0 24 24"><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> <span data-i18n="targets.title">Targets</span></button>
                <button class="tab-btn" data-tab="streams" onclick="switchTab('streams')" role="tab" aria-selected="false" aria-controls="tab-streams" id="tab-btn-streams" title="Ctrl+4"><svg class="icon" viewBox="0 0 24 24"><path d="m17 2-5 5-5-5"/><rect width="20" height="15" x="2" y="7" rx="2"/></svg> <span data-i18n="streams.title">Sources</span></button>
                <button class="tab-btn" data-tab="graph" onclick="switchTab('graph')" role="tab" aria-selected="false" aria-controls="tab-graph" id="tab-btn-graph" title="Ctrl+5"><svg class="icon" viewBox="0 0 24 24"><circle cx="5" cy="6" r="3"/><circle cx="19" cy="6" r="3"/><circle cx="12" cy="18" r="3"/><path d="M7.5 7.5 10.5 16"/><path d="M16.5 7.5 13.5 16"/></svg> <span data-i18n="graph.title">Graph</span></button>
            </div>
            <div class="header-toolbar">
                <a href="/docs" target="_blank" class="header-link" data-i18n-title="app.api_docs" title="API Docs">API</a>
                <span class="header-toolbar-sep"></span>
                <button class="header-btn" id="tour-restart-btn" onclick="startGettingStartedTutorial()" data-i18n-title="tour.restart" title="Restart tutorial">
                    <svg class="icon" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>
                </button>
                <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>
                <span class="color-picker-wrapper" id="cp-wrap-accent">
                    <button class="header-btn" onclick="event.stopPropagation(); window._cpToggle('accent')" data-i18n-title="accent.title" title="Accent color">
                        <span id="cp-swatch-accent" class="color-picker-swatch" style="background: var(--primary-color)"></span>
                    </button>
                    <div class="color-picker-popover anchor-right" id="cp-pop-accent" style="display:none" onclick="event.stopPropagation()">
                        <div class="color-picker-grid">
                            <button class="color-picker-dot" style="background:#4CAF50" onclick="event.stopPropagation(); window._cpPick('accent','#4CAF50')"></button>
                            <button class="color-picker-dot" style="background:#7C4DFF" onclick="event.stopPropagation(); window._cpPick('accent','#7C4DFF')"></button>
                            <button class="color-picker-dot" style="background:#FF6D00" onclick="event.stopPropagation(); window._cpPick('accent','#FF6D00')"></button>
                            <button class="color-picker-dot" style="background:#E91E63" onclick="event.stopPropagation(); window._cpPick('accent','#E91E63')"></button>
                            <button class="color-picker-dot" style="background:#00BCD4" onclick="event.stopPropagation(); window._cpPick('accent','#00BCD4')"></button>
                            <button class="color-picker-dot" style="background:#FF5252" onclick="event.stopPropagation(); window._cpPick('accent','#FF5252')"></button>
                            <button class="color-picker-dot" style="background:#26A69A" onclick="event.stopPropagation(); window._cpPick('accent','#26A69A')"></button>
                            <button class="color-picker-dot" style="background:#2196F3" onclick="event.stopPropagation(); window._cpPick('accent','#2196F3')"></button>
                            <button class="color-picker-dot" style="background:#FFC107" onclick="event.stopPropagation(); window._cpPick('accent','#FFC107')"></button>
                        </div>
                        <div class="color-picker-custom" onclick="this.querySelector('input').click()">
                            <input type="color" id="cp-native-accent" value="#4CAF50"
                                   oninput="event.stopPropagation(); window._cpPick('accent',this.value)" onchange="event.stopPropagation(); window._cpPick('accent',this.value)">
                            <span data-i18n="accent.custom">Custom</span>
                        </div>
                    </div>
                </span>
                <button class="header-btn" onclick="openSettingsModal()" data-i18n-title="settings.title" title="Settings">
                    <svg class="icon" viewBox="0 0 24 24"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915"/><circle cx="12" cy="12" r="3"/></svg>
                </button>
                <select id="locale-select" class="header-locale" onchange="changeLocale()" data-i18n-title="locale.change" title="Change language">
                    <option value="en">EN</option>
                    <option value="ru">RU</option>
                    <option value="zh">ZH</option>
                </select>
                <span class="header-toolbar-sep"></span>
                <button id="login-btn" class="header-btn" onclick="showLogin()" style="display: none" data-i18n-title="auth.login" title="Login">
                    <svg class="icon" viewBox="0 0 24 24"><path d="M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z"/><circle cx="16.5" cy="7.5" r=".5" fill="currentColor"/></svg>
                </button>
                <button id="logout-btn" class="header-btn" onclick="logout()" style="display: none" data-i18n-title="auth.logout" title="Logout">
                    <svg class="icon" viewBox="0 0 24 24"><path d="m16 17 5-5-5-5"/><path d="M21 12H9"/><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/></svg>
                </button>
            </div>
        </header>
        <div id="update-banner" class="update-banner" style="display:none"></div>
        <div id="donation-banner" class="donation-banner" style="display:none"></div>
    <div class="container">
        <div class="tabs">
            <div class="tab-panel" id="tab-dashboard" role="tabpanel" aria-labelledby="tab-btn-dashboard">
                <div id="dashboard-content">
                    <div class="devices-grid">
                        <div class="skeleton-card"><div class="skeleton-line skeleton-line-title"></div><div class="skeleton-line skeleton-line-medium"></div><div class="skeleton-line skeleton-line-short"></div><div class="skeleton-actions"><div class="skeleton-btn"></div><div class="skeleton-btn"></div></div></div>
                        <div class="skeleton-card"><div class="skeleton-line skeleton-line-title"></div><div class="skeleton-line skeleton-line-medium"></div><div class="skeleton-line skeleton-line-short"></div><div class="skeleton-actions"><div class="skeleton-btn"></div><div class="skeleton-btn"></div></div></div>
                        <div class="skeleton-card"><div class="skeleton-line skeleton-line-title"></div><div class="skeleton-line skeleton-line-medium"></div><div class="skeleton-line skeleton-line-short"></div><div class="skeleton-actions"><div class="skeleton-btn"></div><div class="skeleton-btn"></div></div></div>
                    </div>
                </div>
            </div>

            <div class="tab-panel" id="tab-automations" role="tabpanel" aria-labelledby="tab-btn-automations">
                <div class="tree-layout">
                    <nav class="tree-sidebar" id="automations-tree-nav"></nav>
                    <div class="tree-content" id="automations-content">
                        <div class="loading-spinner"></div>
                    </div>
                </div>
            </div>

            <div class="tab-panel" id="tab-targets" role="tabpanel" aria-labelledby="tab-btn-targets">
                <div class="tree-layout">
                    <nav class="tree-sidebar" id="targets-tree-nav"></nav>
                    <div class="tree-content" id="targets-panel-content">
                        <div class="loading-spinner"></div>
                    </div>
                </div>
            </div>

            <div class="tab-panel" id="tab-streams" role="tabpanel" aria-labelledby="tab-btn-streams">
                <div class="tree-layout">
                    <nav class="tree-sidebar" id="streams-tree-nav"></nav>
                    <div class="tree-content" id="streams-list">
                        <div class="loading-spinner"></div>
                    </div>
                </div>
            </div>

            <div class="tab-panel" id="tab-graph" role="tabpanel" aria-labelledby="tab-btn-graph">
                <div id="graph-editor-content">
                    <div class="loading-spinner"></div>
                </div>
            </div>


            <script>
                // Apply saved tab immediately during parse to prevent visible jump
                (function() {
                    var hash = location.hash.replace(/^#/, '');
                    var saved = hash ? hash.split('/')[0] : localStorage.getItem('activeTab');
                    if (saved === 'devices') saved = 'targets';
                    if (!saved || !document.getElementById('tab-' + saved)) saved = 'dashboard';
                    /* graph tab is valid */
                    document.querySelectorAll('.tab-btn').forEach(function(btn) { btn.classList.toggle('active', btn.dataset.tab === saved); });
                    document.querySelectorAll('.tab-panel').forEach(function(panel) { panel.classList.toggle('active', panel.id === 'tab-' + saved); });
                })();
            </script>
        </div>

        <footer class="app-footer">
            <div class="footer-content">
                <p>
                    Created by <strong>Alexei Dolgolyov</strong>
                    • <a href="mailto:dolgolyov.alexei@gmail.com">dolgolyov.alexei@gmail.com</a>
                    • <a href="#" onclick="openSettingsModal();switchSettingsTab('about');return false" data-i18n="donation.about_title">About</a>
                </p>
            </div>
        </footer>
    </div>

    <button id="scroll-to-top" class="scroll-to-top" onclick="window.scrollTo({top:0,behavior:'smooth'})" aria-label="Scroll to top">
        <svg class="icon" viewBox="0 0 24 24"><path d="m18 15-6-6-6 6"/></svg>
    </button>

    <div id="toast" class="toast" role="status" aria-live="polite" aria-atomic="true"></div>

    {% include 'modals/calibration.html' %}
    {% include 'modals/advanced-calibration.html' %}
    {% include 'modals/device-settings.html' %}
    {% include 'modals/target-editor.html' %}
    {% include 'modals/css-editor.html' %}
    {% include 'modals/gradient-editor.html' %}
    {% include 'modals/test-css-source.html' %}
    {% include 'modals/notification-history.html' %}
    {% include 'modals/kc-editor.html' %}
    {% include 'modals/pattern-template.html' %}
    {% include 'modals/api-key.html' %}
    {% include 'modals/confirm.html' %}
    {% include 'modals/add-device.html' %}
    {% include 'modals/capture-template.html' %}
    {% include 'modals/test-template.html' %}
    {% include 'modals/test-stream.html' %}
    {% include 'modals/test-pp-template.html' %}
    {% include 'modals/stream.html' %}
    {% include 'modals/pp-template.html' %}
    {% include 'modals/cspt-modal.html' %}
    {% include 'modals/automation-editor.html' %}
    {% include 'modals/scene-preset-editor.html' %}
    {% include 'modals/audio-source-editor.html' %}
    {% include 'modals/test-audio-source.html' %}
    {% include 'modals/audio-template.html' %}
    {% include 'modals/test-audio-template.html' %}
    {% include 'modals/value-source-editor.html' %}
    {% include 'modals/test-value-source.html' %}
    {% include 'modals/sync-clock-editor.html' %}
    {% include 'modals/weather-source-editor.html' %}
    {% include 'modals/ha-source-editor.html' %}
    {% include 'modals/ha-light-editor.html' %}
    {% include 'modals/asset-upload.html' %}
    {% include 'modals/asset-editor.html' %}
    {% include 'modals/settings.html' %}

    {% include 'partials/tutorial-overlay.html' %}
    {% include 'partials/image-lightbox.html' %}
    {% include 'partials/display-picker.html' %}

    <div id="command-palette" style="display:none">
        <div class="cp-backdrop"></div>
        <div class="cp-dialog">
            <input id="cp-input" class="cp-input" placeholder="Search..." autocomplete="off">
            <div id="cp-results" class="cp-results"></div>
            <div class="cp-footer" data-i18n="search.footer">↑↓ navigate · Enter select · Esc close</div>
        </div>
    </div>

    <script defer src="/static/dist/app.bundle.js"></script>
    <script>
        // Initialize ambient background
        const savedBgAnim = localStorage.getItem('bgAnim') || 'off';
        document.documentElement.setAttribute('data-bg-anim', savedBgAnim);

        // All known CSS bg-effect classes (must match appearance.ts BG_EFFECT_PRESETS)
        var _bgEffectClasses = ['bg-effect-grid', 'bg-effect-mesh', 'bg-effect-scanlines', 'bg-effect-particles'];

        function toggleBgAnim() {
            var savedEffect = localStorage.getItem('bgEffect') || 'none';
            var isOn = _isBgEffectActive();

            if (isOn) {
                // Turn everything off
                document.documentElement.setAttribute('data-bg-anim', 'off');
                document.documentElement.removeAttribute('data-bg-effect');
                var lyr = document.getElementById('bg-effect-layer');
                if (lyr) _bgEffectClasses.forEach(function(c) { lyr.classList.remove(c); });
                updateBgAnimBtn('off');
            } else {
                // Restore saved effect (or fallback to WebGL noise)
                if (savedEffect === 'none') savedEffect = 'noise';
                if (typeof window.applyBgEffect === 'function') {
                    window.applyBgEffect(savedEffect);
                } else {
                    // Fallback before bundle loads: just toggle WebGL
                    document.documentElement.setAttribute('data-bg-anim', 'on');
                }
                updateBgAnimBtn('on');
            }
        }

        function _isBgEffectActive() {
            if (document.documentElement.getAttribute('data-bg-anim') === 'on') return true;
            var lyr = document.getElementById('bg-effect-layer');
            if (!lyr) return false;
            for (var i = 0; i < _bgEffectClasses.length; i++) {
                if (lyr.classList.contains(_bgEffectClasses[i])) return true;
            }
            return false;
        }

        function updateBgAnimBtn(state) {
            var 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);
        updateThemeIcon(savedTheme);

        function updateThemeIcon(theme) {
            const icon = document.getElementById('theme-icon');
            icon.innerHTML = theme === 'dark'
                ? '<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>'
                : '<svg class="icon" viewBox="0 0 24 24"><path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"/></svg>';
        }

        function toggleTheme() {
            const currentTheme = document.documentElement.getAttribute('data-theme');
            const newTheme = currentTheme === 'dark' ? 'light' : 'dark';

            document.documentElement.setAttribute('data-theme', newTheme);
            localStorage.setItem('theme', newTheme);
            updateThemeIcon(newTheme);
            if (window._updateBgAnimTheme) window._updateBgAnimTheme(newTheme === 'dark');
            // Re-derive accent text variant for the new theme
            const accent = localStorage.getItem('accentColor');
            if (accent) applyAccentColor(accent, true);
            showToast(window.t ? t(newTheme === 'dark' ? 'theme.switched.dark' : 'theme.switched.light') : `Switched to ${newTheme} theme`, 'info');
        }

        // Initialize accent color
        function adjustLightness(hex, amount) {
            const r = parseInt(hex.slice(1,3),16)/255;
            const g = parseInt(hex.slice(3,5),16)/255;
            const b = parseInt(hex.slice(5,7),16)/255;
            const max = Math.max(r,g,b), min = Math.min(r,g,b);
            let h, s, l = (max+min)/2;
            if (max===min) { h=s=0; } else {
                const d = max-min;
                s = l>0.5 ? d/(2-max-min) : d/(max+min);
                if (max===r) h=((g-b)/d+(g<b?6:0))/6;
                else if (max===g) h=((b-r)/d+2)/6;
                else h=((r-g)/d+4)/6;
            }
            l = Math.max(0, Math.min(1, l + amount/100));
            const hue2rgb = (p,q,t) => { if(t<0)t+=1; if(t>1)t-=1; if(t<1/6)return p+(q-p)*6*t; if(t<1/2)return q; if(t<2/3)return p+(q-p)*(2/3-t)*6; return p; };
            let rr,gg,bb;
            if (s===0) { rr=gg=bb=l; } else {
                const q = l<0.5 ? l*(1+s) : l+s-l*s, p = 2*l-q;
                rr=hue2rgb(p,q,h+1/3); gg=hue2rgb(p,q,h); bb=hue2rgb(p,q,h-1/3);
            }
            return '#'+[rr,gg,bb].map(x=>Math.round(x*255).toString(16).padStart(2,'0')).join('');
        }

        function contrastColor(hex) {
            const r = parseInt(hex.slice(1,3),16)/255;
            const g = parseInt(hex.slice(3,5),16)/255;
            const b = parseInt(hex.slice(5,7),16)/255;
            const lin = c => c <= 0.03928 ? c/12.92 : Math.pow((c+0.055)/1.055, 2.4);
            const L = 0.2126*lin(r) + 0.7152*lin(g) + 0.0722*lin(b);
            return L > 0.36 ? '#1a1a1a' : '#ffffff';
        }

        function applyAccentColor(hex, silent) {
            const root = document.documentElement;
            root.style.setProperty('--primary-color', hex);
            const theme = root.getAttribute('data-theme');
            root.style.setProperty('--primary-text-color', adjustLightness(hex, theme === 'dark' ? 15 : -15));
            root.style.setProperty('--primary-hover', adjustLightness(hex, 8));
            root.style.setProperty('--primary-contrast', contrastColor(hex));
            // Update canvas background blobs if module loaded
            if (window._updateBgAnimAccent) window._updateBgAnimAccent(hex);
            const swatch = document.getElementById('cp-swatch-accent');
            if (swatch) swatch.style.background = hex;
            const native = document.getElementById('cp-native-accent');
            if (native) native.value = hex;
            localStorage.setItem('accentColor', hex);
            document.dispatchEvent(new CustomEvent('accentColorChanged', { detail: { color: hex } }));
            if (!silent) showToast(window.t ? t('accent.color.updated') : 'Accent color updated', 'info');
        }

        // Bootstrap _cpToggle/_cpPick globals before the color-picker module loads
        // (the module will overwrite them with proper versions that handle all pickers)
        window._cpCallbacks = { accent: function(hex) { applyAccentColor(hex); } };
        window._cpToggle = window._cpToggle || function(id) {
            document.querySelectorAll('.color-picker-popover').forEach(p => {
                if (p.id !== 'cp-pop-' + id) p.style.display = 'none';
            });
            const pop = document.getElementById('cp-pop-' + id);
            if (pop) pop.style.display = pop.style.display === 'none' ? '' : 'none';
        };
        window._cpPick = window._cpPick || function(id, hex) {
            var s = document.getElementById('cp-swatch-' + id);
            if (s) s.style.background = hex;
            var n = document.getElementById('cp-native-' + id);
            if (n) n.value = hex;
            var p = document.getElementById('cp-pop-' + id);
            if (p) p.style.display = 'none';
            if (window._cpCallbacks[id]) window._cpCallbacks[id](hex);
        };

        // Close all color pickers on outside click
        document.addEventListener('click', function() {
            document.querySelectorAll('.color-picker-popover').forEach(function(p) { p.style.display = 'none'; });
        });

        const savedAccent = localStorage.getItem('accentColor');
        if (savedAccent) applyAccentColor(savedAccent, true);

        // Early-apply saved background effect class on the dedicated layer element
        const savedBgEffect = localStorage.getItem('bgEffect');
        if (savedBgEffect && savedBgEffect !== 'none' && savedBgEffect !== 'noise') {
            var effectClasses = { grid: 'bg-effect-grid', mesh: 'bg-effect-mesh', scanlines: 'bg-effect-scanlines', particles: 'bg-effect-particles' };
            var layer = document.getElementById('bg-effect-layer');
            if (layer && effectClasses[savedBgEffect]) {
                layer.classList.add(effectClasses[savedBgEffect]);
                document.documentElement.setAttribute('data-bg-effect', savedBgEffect);
            }
        }
        // Set header toggle button state (reflects both WebGL and CSS effects)
        updateBgAnimBtn(_isBgEffectActive() ? 'on' : 'off');

        // Initialize auth state
        function updateAuthUI() {
            const apiKey = localStorage.getItem('wled_api_key');
            const loginBtn = document.getElementById('login-btn');
            const logoutBtn = document.getElementById('logout-btn');
            const tabBar = document.querySelector('.tab-bar');
            const authDisabled = window._authRequired === false;

            if (authDisabled) {
                // Auth disabled — hide login/logout, always show tabs
                loginBtn.style.display = 'none';
                logoutBtn.style.display = 'none';
                if (tabBar) tabBar.style.display = '';
            } else if (apiKey) {
                loginBtn.style.display = 'none';
                logoutBtn.style.display = 'inline-block';
                if (tabBar) tabBar.style.display = '';
            } else {
                loginBtn.style.display = 'inline-block';
                logoutBtn.style.display = 'none';
                if (tabBar) tabBar.style.display = 'none';
            }
        }

        function showLogin() {
            showApiKeyModal(t('auth.message'));
            document.getElementById('modal-cancel-btn').style.display = 'inline-block';
        }

        async function logout() {
            const confirmed = await showConfirm(t('auth.logout.confirm'));
            if (!confirmed) {
                return;
            }

            localStorage.removeItem('wled_api_key');
            if (window.setApiKey) window.setApiKey(null);
            updateAuthUI();
            showToast(t('auth.logout.success'), 'info');

            // Stop background activity
            if (window.stopDashboardWS) window.stopDashboardWS();
            if (window.stopPerfPolling) window.stopPerfPolling();
            if (window.stopUptimeTimer) window.stopUptimeTimer();
            if (window.disconnectAllKCWebSockets) window.disconnectAllKCWebSockets();

            // Clear all tab panels
            const loginMsg = `<div class="loading">${t('auth.please_login')}</div>`;
            document.getElementById('dashboard-content').innerHTML = loginMsg;
            document.getElementById('automations-content').innerHTML = loginMsg;
            document.getElementById('targets-panel-content').innerHTML = loginMsg;
            document.getElementById('streams-list').innerHTML = loginMsg;
            document.getElementById('graph-editor-content').innerHTML = loginMsg;
        }

        // Demo banner dismiss
        function dismissDemoBanner() {
            var banner = document.getElementById('demo-banner');
            if (banner) banner.style.display = 'none';
            localStorage.setItem('demo-banner-dismissed', 'true');
        }

        // Initialize on load
        updateAuthUI();

        // Modal functions
        function togglePasswordVisibility() {
            const input = document.getElementById('api-key-input');
            const button = document.querySelector('.password-toggle');
            if (input.type === 'password') {
                input.type = 'text';
                button.innerHTML = '<svg class="icon" viewBox="0 0 24 24"><path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"/><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"/><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"/><path d="m2 2 20 20"/></svg>';
            } else {
                input.type = 'password';
                button.innerHTML = '<svg class="icon" viewBox="0 0 24 24"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/></svg>';
            }
        }

        function showApiKeyModal(message, hideCancel = false) {
            const modal = document.getElementById('api-key-modal');
            const description = document.querySelector('.modal-description');
            const input = document.getElementById('api-key-input');
            const error = document.getElementById('api-key-error');
            const cancelBtn = document.getElementById('modal-cancel-btn');

            description.textContent = message || t('auth.message');

            input.value = '';
            input.placeholder = t('auth.placeholder');
            error.style.display = 'none';
            modal.style.display = 'flex';
            lockBody();

            // Hide cancel button and close X if this is required login (no existing session)
            cancelBtn.style.display = hideCancel ? 'none' : 'inline-block';
            const closeXBtn = document.getElementById('modal-close-x-btn');
            if (closeXBtn) closeXBtn.style.display = hideCancel ? 'none' : '';

            // Hide login button while modal is open
            document.getElementById('login-btn').style.display = 'none';

            setTimeout(() => input.focus(), 100);
        }

        function closeApiKeyModal() {
            const modal = document.getElementById('api-key-modal');
            modal.style.display = 'none';
            unlockBody();
            updateAuthUI();
        }

        function submitApiKey(event) {
            if (event) {
                event.preventDefault();
            }

            const input = document.getElementById('api-key-input');
            const error = document.getElementById('api-key-error');
            const key = input.value.trim();

            if (!key) {
                error.textContent = t('auth.error.required');
                error.style.display = 'block';
                return;
            }

            // Store the key
            localStorage.setItem('wled_api_key', key);
            if (window.setApiKey) window.setApiKey(key);
            updateAuthUI();

            closeApiKeyModal();
            showToast(t('auth.success'), 'success');

            // Reload data
            loadServerInfo();
            loadDisplays();
            loadTargetsTab();

            // Start auto-refresh
            startAutoRefresh();

            // Show getting-started tutorial on first login
            if (!localStorage.getItem('tour_completed') && typeof startGettingStartedTutorial === 'function') {
                setTimeout(() => startGettingStartedTutorial(), 600);
            }
        }
    </script>
    <script>if ('serviceWorker' in navigator) navigator.serviceWorker.register('/sw.js');</script>
</body>
</html>
